diff --git a/.data/gamevals/area.rscm b/.data/gamevals/area.rscm index cdc1b1b61..2a3513df7 100644 --- a/.data/gamevals/area.rscm +++ b/.data/gamevals/area.rscm @@ -56,6 +56,32 @@ revenant_caves=55 mining_guild=56 rune_essence_mine=57 fishing_guild=58 +draynor_village=59 +draynor_manor=60 +varrock=61 +barbarian_village=62 +edgeville=63 +falador=64 +falador_south=65 +falador_north=66 +taverley=67 +burthorpe=68 +rimmington=69 +goblin_village=70 +ice_mountain=71 +catherby=72 +seers_village=73 +camelot=74 +rellekka=75 +barbarian_outpost=76 +al_kharid=77 +port_sarim=78 +wilderness_music=79 +east_ardougne=80 +west_ardougne=81 +ardougne_sewers=82 +south_ardougne=83 +legends_guild=84 minor_wilderness_bosses_dungeons=32757 ferox_enclave=32758 @@ -66,3 +92,556 @@ chaos_temple=32763 lumbridge=32764 singles_plus=32765 multiway=32766 +emirs_arena=32756 +mage_training_arena=32755 +emirs_arena_music=32754 +demon_slayer_stone_circle=32753 +grand_exchange=32752 +wizards_tower=32751 +mudskipper_point=32750 +tree_gnome_stronghold=32749 +gnome_ball_field=32748 +yanille=32747 +yanille_chain=32746 +yanille_agility_dungeon=32745 +castle_wars=32744 +castle_wars_underground=32743 +a_dangerous_game=32742 +a_desolate_past=32741 +a_forgotten_religion=32740 +a_mothers_curse=32739 +a_taste_of_hope=32738 +a_walk_in_the_woods=32737 +aldarin=32736 +alls_fairy_in_love_and_war=32735 +alone=32734 +altar_ego=32733 +alternative_root=32732 +amascuts_promise=32731 +ambient_jungle=32730 +ape_atoll=32729 +ape_ex_predator=32728 +arachnids_of_vampyrium=32727 +arboretum=32726 +arcane=32725 +are_you_not_entertained=32724 +armadyl_alliance=32723 +armageddon=32722 +artistry=32721 +ascent=32720 +assault_and_battery=32719 +attack_1=32718 +attack_2=32717 +attack_3=32716 +attack_4=32715 +attack_5=32714 +attack_6=32713 +auburn_valley=32712 +avium_savannah=32711 +aye_car_rum_ba=32710 +aztec=32709 +back_to_life=32708 +background=32707 +bandit_camp=32706 +bandos_battalion=32705 +bane=32704 +barb_wire=32703 +barking_mad=32702 +barren_land=32701 +beetle_juice=32700 +below_the_conch=32699 +beneath_cursed_sands=32698 +beneath_the_kingdom=32697 +beneath_the_stronghold=32696 +beyond=32695 +beyond_the_meadow=32694 +black_of_knight=32693 +blistering_barnacles=32692 +bloodbath=32691 +body_parts=32690 +bone_dance=32689 +bone_dry=32688 +box_of_delights=32687 +brew_hoo_hoo=32686 +brimhaven=32685 +brimstails_scales=32684 +bubble_and_squeak=32683 +burning_desire=32682 +business_as_unusual=32681 +cabin_fever=32680 +call_of_the_tlati=32679 +cannonballin=32678 +catacombs_and_tombs=32677 +catch_me_if_you_can=32676 +cave_background=32675 +cave_of_beasts=32674 +cave_of_the_goblins=32673 +cavern=32672 +cellar_song=32671 +chain_of_command=32670 +chamber=32669 +chickened_out=32668 +city_of_the_dead=32667 +civitas_illa_fortis=32666 +clan_wars=32665 +clanliness=32664 +claustrophobia=32663 +coil=32662 +colossus_of_the_deep=32661 +competition=32660 +complication=32659 +conspiracy=32658 +corporal_punishment=32657 +corridors_of_power=32656 +country_jig=32655 +creature_cruelty=32654 +creeping_vines=32653 +crystal_castle=32652 +crystal_sword=32651 +cursed=32650 +dagannoth_dawn=32649 +dance_of_death=32648 +dance_of_the_undead=32647 +dangerous_logic=32646 +dangerous_road=32645 +dangerous_way=32644 +darkly_altared=32643 +darkmeyer_music=32642 +darkness_in_the_depths=32641 +davy_jones_locker=32640 +dead_quiet=32639 +dead_quiet_2=32638 +deadlands=32637 +deep_down=32636 +devils_may_care=32635 +digsite=32634 +dimension_x=32633 +distant_land=32632 +distillery_hilarity=32631 +dogs_of_war=32630 +doorways=32629 +dorgeshuun_city=32628 +dorgeshuun_deep=32627 +down_and_out=32626 +down_below=32625 +down_by_the_docks=32624 +down_to_earth=32623 +dragontooth_island=32622 +dreamstate=32621 +dunes_of_eternity=32620 +dusk_in_yubiusk=32619 +dwarf_theme=32618 +dwarven_domain=32617 +dynasty=32616 +eagles_peak=32615 +east_ardougne_music=32614 +elder_wisdom=32613 +elven_seed=32612 +emissaries_of_twilight=32611 +emotion=32610 +escape=32609 +espionage=32608 +etceteria=32607 +everywhere=32606 +evil_bobs_island=32605 +expecting=32604 +expedition=32603 +exposed=32602 +fanfare_2=32601 +fanfare_3=32600 +fangs_for_the_memory=32599 +far_away=32598 +farming_guild=32597 +fe_fi_fo_fum=32596 +fear_and_loathing=32595 +feldip_hills=32594 +fenkenstrains_refrain=32593 +find_my_way=32592 +fire_and_brimstone=32591 +fire_in_the_deep=32590 +food_for_thought=32589 +forever=32588 +forgettable_melody=32587 +fossilised=32586 +fremennik_province=32585 +frogland=32584 +frostbite=32583 +fruits_de_mer=32582 +gaol=32581 +garden_of_autumn=32580 +garden_of_spring=32579 +garden_of_summer=32578 +garden_of_winter=32577 +getting_down_to_business=32576 +gill_bill=32575 +goblin_game=32574 +grotto=32573 +guardians_of_the_rift=32572 +h_a_m_and_seek=32571 +h_a_m_attack=32570 +h_a_m_fisted=32569 +harmony=32568 +harmony_2=32567 +haunted_mine=32566 +have_a_blast=32565 +have_an_ice_day=32564 +head_to_head=32563 +heart_and_mind=32562 +hells_bells=32561 +hemenster=32560 +hermit=32559 +home_sweet_home=32558 +horizon=32557 +iban=32556 +ice_and_fire=32555 +ice_melody=32554 +impetuous=32553 +in_between=32552 +in_the_brine=32551 +in_the_clink=32550 +in_the_manor=32549 +in_the_pits=32548 +inferno=32547 +insect_queen=32546 +into_the_abyss=32545 +into_the_tombs=32544 +intrepid=32543 +invader=32542 +isafdar=32541 +island_life=32540 +island_of_the_trolls=32539 +its_not_over_til=32538 +itsy_bitsy=32537 +jaws_of_gluttony=32536 +judgement_of_the_depths=32535 +karamja=32534 +karuulm_slayer_dungeon_music=32533 +keldagrim_music=32532 +kharidian_desert=32531 +knightmare=32530 +kourend_the_magnificent=32529 +la_mort=32528 +labyrinth=32527 +lagoon=32526 +laid_to_rest=32525 +lair=32524 +lair_of_the_basilisk=32523 +lament=32522 +lament_for_the_hallowed=32521 +lament_of_meiyerditch=32520 +lamistards_labyrinth=32519 +land_down_under=32518 +last_man_standing=32517 +lava_is_mine=32516 +legion=32515 +life_at_sea=32514 +lighthouse=32513 +little_cave_of_horrors=32512 +lonesome=32511 +looking_back=32510 +lord_of_the_larvae=32509 +lore_and_order=32508 +lost_soul=32507 +lost_to_time=32506 +lower_depths=32505 +lucid_dream=32504 +lucid_nightmare=32503 +lumbering=32502 +lunar_isle=32501 +lurking_threats=32500 +mad_eadgar=32499 +mage_arena=32498 +magic_magic_magic=32497 +major_miner=32496 +making_waves=32495 +malady=32494 +march_of_the_shayzien=32493 +marooned=32492 +marzipan=32491 +masquerade=32490 +mausoleum=32489 +maws_jaws_and_claws=32488 +method_of_madness=32487 +miles_away=32486 +military_life=32485 +mind_over_matter=32484 +miracle_dance=32483 +mirage=32482 +miscellania=32481 +mistrock=32480 +monarch_waltz=32479 +monkey_business=32478 +monkey_madness=32477 +monkey_sadness=32476 +monkey_trouble=32475 +monster_melee=32474 +mor_ul_rek=32473 +more_than_meets_the_eye=32472 +morytania=32471 +mos_leharmless=32470 +mouse_trap=32469 +musa_point=32468 +museum_medley=32467 +mutant_medley=32466 +my_arms_journey=32465 +mysterious_lands=32464 +mystics_of_nature=32463 +mythical=32462 +narnodes_theme=32461 +natural=32460 +nether_realm=32459 +neverland=32458 +newbie_farming=32457 +newbie_melody=32456 +night_of_the_vampyre=32455 +no_pasaran=32454 +no_way_out=32453 +nomad=32452 +norse_code=32451 +not_a_moment_of_relief=32450 +noxious_awakening=32449 +null_and_void=32448 +observatory=32447 +ogre_the_top=32446 +oh_rats=32445 +on_the_frontline=32444 +on_the_wing=32443 +on_thin_ice=32442 +oriental=32441 +out_at_the_mines=32440 +out_of_the_deep=32439 +outpost=32438 +over_the_mountains=32437 +over_to_nardah=32436 +overpass=32435 +parade=32434 +path_of_peril=32433 +pest_control=32432 +pharaohs_tomb=32431 +phasmatys=32430 +pheasant_peasant=32429 +pick_and_shovel=32428 +pinball_wizard=32427 +pirates_of_penance=32426 +piscatoris_hunter_area=32425 +poles_apart=32424 +power_of_the_shadow_realm=32423 +predator_xarpus=32422 +preservation=32421 +preserved=32420 +prifddinas=32419 +prime_time=32418 +prospering_fortune=32417 +quest=32416 +race_against_the_clock=32415 +rat_a_tat_tat=32414 +rat_hunt=32413 +ready_for_the_hunt=32412 +regal_pomp=32411 +relics=32410 +remnants=32409 +rest_in_peace=32408 +revenants=32407 +righteousness=32406 +romancing_the_crone=32405 +romper_chomper=32404 +roots_and_flutes=32403 +rose=32402 +rugged_terrain=32401 +ruins_of_isolation=32400 +rune_essence=32399 +sad_meadow=32398 +safety_in_numbers=32397 +saga=32396 +sands_of_time=32395 +sarachnis=32394 +sarcophagus=32393 +sarims_vermin=32392 +scape_cave=32391 +scape_main=32390 +scar_tissue=32389 +schools_out=32388 +scorpia_dances=32387 +sea_shanty_2=32386 +secrets_of_the_north=32385 +serene=32384 +servants_of_strife=32383 +settlement=32382 +seventh_realm=32381 +shadowland=32380 +sharp_end_of_the_crystal=32379 +shining_spirit=32378 +shipwrecked=32377 +showdown=32376 +sigmunds_showdown=32375 +sign_here=32374 +slice_of_silent_movie=32373 +slice_of_station=32372 +slither_and_thither=32371 +slug_a_bug_ball=32370 +solitude=32369 +song_of_the_elves=32368 +soul_fall=32367 +soul_wars=32366 +spirits_of_the_elid=32365 +spooky=32364 +spooky_2=32363 +spooky_jungle=32362 +stagnant=32361 +stalkers=32360 +stillness=32359 +stones_of_old=32358 +storeroom_shuffle=32357 +storm_brew=32356 +stranded=32355 +strange_place=32354 +strangled=32353 +stratosphere=32352 +strength_of_saradomin=32351 +stuck_in_the_mire=32350 +subterranea=32349 +superstition=32348 +suspicious=32347 +tears_of_guthix=32346 +technology=32345 +teklan=32344 +tempest=32343 +temple=32342 +temple_desecrated=32341 +temple_of_light=32340 +temple_of_the_eye=32339 +temple_of_tribes=32338 +tempor_of_the_storm=32337 +that_sullen_hall=32336 +the_ancient_prison=32335 +the_angels_fury=32334 +the_bane_of_ashihama=32333 +the_chosen=32332 +the_consortium=32331 +the_curtain_closes=32330 +the_depths=32329 +the_desolate_isle=32328 +the_desolate_mage=32327 +the_doom=32326 +the_doors_of_dinh=32325 +the_enchanter=32324 +the_enclave=32323 +the_everlasting_slumber=32322 +the_fallen_empire=32321 +the_far_side=32320 +the_feathered_serpent=32319 +the_fires_of_lletya=32318 +the_forests_of_shayzien=32317 +the_forgotten_tomb=32316 +the_forlorn_homestead=32315 +the_forsaken=32314 +the_forsaken_tower=32313 +the_foundry=32312 +the_fragment=32311 +the_galleon=32310 +the_gauntlet=32309 +the_genie=32308 +the_golem=32307 +the_great_conch=32306 +the_guardian_of_tapoyauik=32305 +the_guidance_of_ralos=32304 +the_houses_of_kourend=32303 +the_longramble_scramble=32302 +the_lost_melody=32301 +the_lost_tribe=32300 +the_mad_mole=32299 +the_maidens_anger=32298 +the_maze=32297 +the_militia=32296 +the_mollusc_menace=32295 +the_monsters_below=32294 +the_moons_of_ruin=32293 +the_navigator=32292 +the_nightmare_continues=32291 +the_north=32290 +the_other_side=32289 +the_pandemonium=32288 +the_part_where_you_die=32287 +the_plundered_tomb=32286 +the_power_of_tears=32285 +the_quizmaster=32284 +the_rogues_den=32283 +the_route_of_all_evil=32282 +the_ruins_of_camdozaal=32281 +the_shadow=32280 +the_slayer=32279 +the_sound_of_guthix=32278 +the_spurned_demon=32277 +the_talkasti_people=32276 +the_terrible_tower=32275 +the_terrible_tunnels=32274 +the_tortugan_way=32273 +the_vault=32272 +the_waiting_game=32271 +the_walking_dead=32270 +theme=32269 +thrall_of_the_serpent=32268 +throne_of_the_demon=32267 +time_out=32266 +time_to_mine=32265 +tiptoe=32264 +title_fight=32263 +tomb_raider=32262 +tomorrow=32261 +too_many_cooks=32260 +trahaearn_toil=32259 +tranquillity=32258 +trawler=32257 +trawler_minor=32256 +tribal_background=32255 +trinity=32254 +troll_country=32253 +troll_shuffle=32252 +trouble_brewing=32251 +troubled_waters=32250 +twilight=32249 +tzhaar=32248 +undead_dungeon=32247 +under_the_mountain=32246 +underground=32245 +underground_pass=32244 +understanding=32243 +untouchable=32242 +unturned_stones=32241 +upcoming=32240 +upir_likhyi=32239 +upper_depths=32238 +varlamores_sunset=32237 +venture_2=32236 +victory_is_mine=32235 +village=32234 +volcanic_vikings=32233 +voodoo_cult=32232 +waking_dream=32231 +warrior=32230 +warriors_guild=32229 +waste_defaced=32228 +watch_your_step=32227 +waterfall=32226 +waterlogged=32225 +way_of_the_enchanter=32224 +wayward=32223 +we_are_the_fairies=32222 +welcome_to_the_theatre=32221 +well_hallowed_air=32220 +well_of_voyage=32219 +what_happens_below=32218 +what_the_shell=32217 +where_eagles_lair=32216 +whispering_wind=32215 +wilderness_2=32214 +wings_of_madness=32213 +wonder=32212 +work_work_work=32211 +wrath_and_ruin=32210 +xenophobe=32209 +zamorak_zoo=32208 +zaniks_theme=32207 +zealot=32206 +zogre_dance=32205 +zombiism=32204 diff --git a/.data/gamevals/currency.rscm b/.data/gamevals/currency.rscm index ac308180a..7455c5617 100644 --- a/.data/gamevals/currency.rscm +++ b/.data/gamevals/currency.rscm @@ -1 +1,2 @@ standard_gp=0 +pvp_arena_points=1 diff --git a/.data/gamevals/dbrow.rscm b/.data/gamevals/dbrow.rscm index 18a96f764..d7ae36d2e 100644 --- a/.data/gamevals/dbrow.rscm +++ b/.data/gamevals/dbrow.rscm @@ -1,5 +1,35 @@ gameframe_fullscreen=65530 music_modern_lumbridge=65531 +music_modern_draynor_village=63869 +music_modern_draynor_manor=63893 +music_modern_varrock=63892 +music_modern_barbarian_village=63891 +music_modern_edgeville=63890 +music_modern_wilderness_music=63889 +music_modern_falador=63888 +music_modern_falador_south=63887 +music_modern_falador_north=63886 +music_modern_taverley=63885 +music_modern_rimmington=63884 +music_modern_burthorpe=63883 +music_modern_goblin_village=63882 +music_modern_ice_mountain=63881 +music_modern_asgarnian_ice_dungeon=63880 +music_modern_taverley_dungeon=63879 +music_modern_catherby=63878 +music_modern_seers_village=63877 +music_modern_camelot=63876 +music_modern_rellekka=63875 +music_modern_brineratcavern=63874 +music_modern_barbarian_outpost=63873 +music_modern_al_kharid=63872 +music_modern_port_sarim=63871 +music_modern_east_ardougne=63870 +music_modern_west_ardougne=63868 +music_modern_ardougne_sewers=63867 +music_modern_south_ardougne=63866 +music_modern_legends_guild=63865 +music_modern_mourner_tunnels=63864 gameframe_pre_eoc=65532 gameframe_osrs_stretch=65533 gameframe_toplevel=65534 @@ -38,4 +68,558 @@ 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 +shop_currency_pvp_arena_points=63500 +music_modern_emirs_arena_music=63863 +music_modern_mage_training_arena=63862 +music_modern_grand_exchange=63861 +music_modern_wizards_tower=63860 +music_modern_fishing_guild=63859 +music_modern_mudskipper_point=63858 +music_modern_tree_gnome_stronghold=63857 +music_modern_gnome_ball_field=63856 +music_modern_yanille=63855 +music_modern_yanille_chain=63854 +music_modern_yanille_agility_dungeon=63853 +music_modern_castle_wars=63852 +music_modern_castle_wars_underground=63851 +music_modern_zanaris=63850 +music_modern_a_dangerous_game=63849 +music_modern_a_desolate_past=63848 +music_modern_a_forgotten_religion=63847 +music_modern_a_mothers_curse=63846 +music_modern_a_taste_of_hope=63845 +music_modern_a_walk_in_the_woods=63844 +music_modern_aldarin=63843 +music_modern_alls_fairy_in_love_and_war=63842 +music_modern_alone=63841 +music_modern_altar_ego=63840 +music_modern_alternative_root=63839 +music_modern_amascuts_promise=63838 +music_modern_ambient_jungle=63837 +music_modern_ape_atoll=63836 +music_modern_ape_ex_predator=63835 +music_modern_arachnids_of_vampyrium=63834 +music_modern_arboretum=63833 +music_modern_arcane=63832 +music_modern_are_you_not_entertained=63831 +music_modern_armadyl_alliance=63830 +music_modern_armageddon=63829 +music_modern_artistry=63828 +music_modern_ascent=63827 +music_modern_assault_and_battery=63826 +music_modern_attack_1=63825 +music_modern_attack_2=63824 +music_modern_attack_3=63823 +music_modern_attack_4=63822 +music_modern_attack_5=63821 +music_modern_attack_6=63820 +music_modern_auburn_valley=63819 +music_modern_avium_savannah=63818 +music_modern_aye_car_rum_ba=63817 +music_modern_aztec=63816 +music_modern_back_to_life=63815 +music_modern_background=63814 +music_modern_bandit_camp=63813 +music_modern_bandos_battalion=63812 +music_modern_bane=63811 +music_modern_barb_wire=63810 +music_modern_barking_mad=63809 +music_modern_barren_land=63808 +music_modern_beetle_juice=63807 +music_modern_below_the_conch=63806 +music_modern_beneath_cursed_sands=63805 +music_modern_beneath_the_kingdom=63804 +music_modern_beneath_the_stronghold=63803 +music_modern_beyond=63802 +music_modern_beyond_the_meadow=63801 +music_modern_black_of_knight=63800 +music_modern_blistering_barnacles=63799 +music_modern_bloodbath=63798 +music_modern_body_parts=63797 +music_modern_bone_dance=63796 +music_modern_bone_dry=63795 +music_modern_box_of_delights=63794 +music_modern_brew_hoo_hoo=63793 +music_modern_brimhaven=63792 +music_modern_brimstails_scales=63791 +music_modern_bubble_and_squeak=63790 +music_modern_burning_desire=63789 +music_modern_business_as_unusual=63788 +music_modern_cabin_fever=63787 +music_modern_call_of_the_tlati=63786 +music_modern_cannonballin=63785 +music_modern_catacombs_and_tombs=63784 +music_modern_catch_me_if_you_can=63783 +music_modern_cave_background=63782 +music_modern_cave_of_beasts=63781 +music_modern_cave_of_the_goblins=63780 +music_modern_cavern=63779 +music_modern_cellar_song=63778 +music_modern_chain_of_command=63777 +music_modern_chamber=63776 +music_modern_chickened_out=63775 +music_modern_city_of_the_dead=63774 +music_modern_civitas_illa_fortis=63773 +music_modern_clan_wars=63772 +music_modern_clanliness=63771 +music_modern_claustrophobia=63770 +music_modern_coil=63769 +music_modern_colossus_of_the_deep=63768 +music_modern_competition=63767 +music_modern_complication=63766 +music_modern_conspiracy=63765 +music_modern_corporal_punishment=63764 +music_modern_corridors_of_power=63763 +music_modern_country_jig=63762 +music_modern_creature_cruelty=63761 +music_modern_creeping_vines=63760 +music_modern_crystal_castle=63759 +music_modern_crystal_sword=63758 +music_modern_cursed=63757 +music_modern_dagannoth_dawn=63756 +music_modern_dance_of_death=63755 +music_modern_dance_of_the_undead=63754 +music_modern_dangerous_logic=63753 +music_modern_dangerous_road=63752 +music_modern_dangerous_way=63751 +music_modern_darkly_altared=63750 +music_modern_darkmeyer_music=63749 +music_modern_darkness_in_the_depths=63748 +music_modern_davy_jones_locker=63747 +music_modern_dead_quiet=63746 +music_modern_dead_quiet_2=63745 +music_modern_deadlands=63744 +music_modern_deep_down=63743 +music_modern_devils_may_care=63742 +music_modern_digsite=63741 +music_modern_dimension_x=63740 +music_modern_distant_land=63739 +music_modern_distillery_hilarity=63738 +music_modern_dogs_of_war=63737 +music_modern_doorways=63736 +music_modern_dorgeshuun_city=63735 +music_modern_dorgeshuun_deep=63734 +music_modern_down_and_out=63733 +music_modern_down_below=63732 +music_modern_down_by_the_docks=63731 +music_modern_down_to_earth=63730 +music_modern_dragontooth_island=63729 +music_modern_dreamstate=63728 +music_modern_dunes_of_eternity=63727 +music_modern_dusk_in_yubiusk=63726 +music_modern_dwarf_theme=63725 +music_modern_dwarven_domain=63724 +music_modern_dynasty=63723 +music_modern_eagles_peak=63722 +music_modern_east_ardougne_music=63721 +music_modern_elder_wisdom=63720 +music_modern_elven_seed=63719 +music_modern_emissaries_of_twilight=63718 +music_modern_emotion=63717 +music_modern_escape=63716 +music_modern_espionage=63715 +music_modern_etceteria=63714 +music_modern_everywhere=63713 +music_modern_evil_bobs_island=63712 +music_modern_expecting=63711 +music_modern_expedition=63710 +music_modern_exposed=63709 +music_modern_fanfare_2=63708 +music_modern_fanfare_3=63707 +music_modern_fangs_for_the_memory=63706 +music_modern_far_away=63705 +music_modern_farming_guild=63704 +music_modern_fe_fi_fo_fum=63703 +music_modern_fear_and_loathing=63702 +music_modern_feldip_hills=63701 +music_modern_fenkenstrains_refrain=63700 +music_modern_find_my_way=63699 +music_modern_fire_and_brimstone=63698 +music_modern_fire_in_the_deep=63697 +music_modern_food_for_thought=63696 +music_modern_forever=63695 +music_modern_forgettable_melody=63694 +music_modern_fossilised=63693 +music_modern_fremennik_province=63692 +music_modern_frogland=63691 +music_modern_frostbite=63690 +music_modern_fruits_de_mer=63689 +music_modern_gaol=63688 +music_modern_garden_of_autumn=63687 +music_modern_garden_of_spring=63686 +music_modern_garden_of_summer=63685 +music_modern_garden_of_winter=63684 +music_modern_getting_down_to_business=63683 +music_modern_gill_bill=63682 +music_modern_goblin_game=63681 +music_modern_grotto=63680 +music_modern_guardians_of_the_rift=63679 +music_modern_h_a_m_and_seek=63678 +music_modern_h_a_m_attack=63677 +music_modern_h_a_m_fisted=63676 +music_modern_harmony=63675 +music_modern_harmony_2=63674 +music_modern_haunted_mine=63673 +music_modern_have_a_blast=63672 +music_modern_have_an_ice_day=63671 +music_modern_head_to_head=63670 +music_modern_heart_and_mind=63669 +music_modern_hells_bells=63668 +music_modern_hemenster=63667 +music_modern_hermit=63666 +music_modern_home_sweet_home=63665 +music_modern_horizon=63664 +music_modern_iban=63663 +music_modern_ice_and_fire=63662 +music_modern_ice_melody=63661 +music_modern_impetuous=63660 +music_modern_in_between=63659 +music_modern_in_the_brine=63658 +music_modern_in_the_clink=63657 +music_modern_in_the_manor=63656 +music_modern_in_the_pits=63655 +music_modern_inferno=63654 +music_modern_insect_queen=63653 +music_modern_into_the_abyss=63652 +music_modern_into_the_tombs=63651 +music_modern_intrepid=63650 +music_modern_invader=63649 +music_modern_isafdar=63648 +music_modern_island_life=63647 +music_modern_island_of_the_trolls=63646 +music_modern_its_not_over_til=63645 +music_modern_itsy_bitsy=63644 +music_modern_jaws_of_gluttony=63643 +music_modern_judgement_of_the_depths=63642 +music_modern_karamja=63641 +music_modern_karuulm_slayer_dungeon_music=63640 +music_modern_keldagrim_music=63639 +music_modern_kharidian_desert=63638 +music_modern_knightmare=63637 +music_modern_kourend_the_magnificent=63636 +music_modern_la_mort=63635 +music_modern_labyrinth=63634 +music_modern_lagoon=63633 +music_modern_laid_to_rest=63632 +music_modern_lair=63631 +music_modern_lair_of_the_basilisk=63630 +music_modern_lament=63629 +music_modern_lament_for_the_hallowed=63628 +music_modern_lament_of_meiyerditch=63627 +music_modern_lamistards_labyrinth=63626 +music_modern_land_down_under=63625 +music_modern_last_man_standing=63624 +music_modern_lava_is_mine=63623 +music_modern_legion=63622 +music_modern_life_at_sea=63621 +music_modern_lighthouse=63620 +music_modern_little_cave_of_horrors=63619 +music_modern_lonesome=63618 +music_modern_looking_back=63617 +music_modern_lord_of_the_larvae=63616 +music_modern_lore_and_order=63615 +music_modern_lost_soul=63614 +music_modern_lost_to_time=63613 +music_modern_lower_depths=63612 +music_modern_lucid_dream=63611 +music_modern_lucid_nightmare=63610 +music_modern_lumbering=63609 +music_modern_lunar_isle=63608 +music_modern_lurking_threats=63607 +music_modern_mad_eadgar=63606 +music_modern_mage_arena=63605 +music_modern_magic_magic_magic=63604 +music_modern_major_miner=63603 +music_modern_making_waves=63602 +music_modern_malady=63601 +music_modern_march_of_the_shayzien=63600 +music_modern_marooned=63599 +music_modern_marzipan=63598 +music_modern_masquerade=63597 +music_modern_mausoleum=63596 +music_modern_maws_jaws_and_claws=63595 +music_modern_method_of_madness=63594 +music_modern_miles_away=63593 +music_modern_military_life=63592 +music_modern_mind_over_matter=63591 +music_modern_miracle_dance=63590 +music_modern_mirage=63589 +music_modern_miscellania=63588 +music_modern_mistrock=63587 +music_modern_monarch_waltz=63586 +music_modern_monkey_business=63585 +music_modern_monkey_madness=63584 +music_modern_monkey_sadness=63583 +music_modern_monkey_trouble=63582 +music_modern_monster_melee=63581 +music_modern_mor_ul_rek=63580 +music_modern_more_than_meets_the_eye=63579 +music_modern_morytania=63578 +music_modern_mos_leharmless=63577 +music_modern_mouse_trap=63576 +music_modern_musa_point=63575 +music_modern_museum_medley=63574 +music_modern_mutant_medley=63573 +music_modern_my_arms_journey=63572 +music_modern_mysterious_lands=63571 +music_modern_mystics_of_nature=63570 +music_modern_mythical=63569 +music_modern_narnodes_theme=63568 +music_modern_natural=63567 +music_modern_nether_realm=63566 +music_modern_neverland=63565 +music_modern_newbie_farming=63564 +music_modern_newbie_melody=63563 +music_modern_night_of_the_vampyre=63562 +music_modern_no_pasaran=63561 +music_modern_no_way_out=63560 +music_modern_nomad=63559 +music_modern_norse_code=63558 +music_modern_not_a_moment_of_relief=63557 +music_modern_noxious_awakening=63556 +music_modern_null_and_void=63555 +music_modern_observatory=63554 +music_modern_ogre_the_top=63553 +music_modern_oh_rats=63552 +music_modern_on_the_frontline=63551 +music_modern_on_the_wing=63550 +music_modern_on_thin_ice=63549 +music_modern_oriental=63548 +music_modern_out_at_the_mines=63547 +music_modern_out_of_the_deep=63546 +music_modern_outpost=63545 +music_modern_over_the_mountains=63544 +music_modern_over_to_nardah=63543 +music_modern_overpass=63542 +music_modern_parade=63541 +music_modern_path_of_peril=63540 +music_modern_pest_control=63539 +music_modern_pharaohs_tomb=63538 +music_modern_phasmatys=63537 +music_modern_pheasant_peasant=63536 +music_modern_pick_and_shovel=63535 +music_modern_pinball_wizard=63534 +music_modern_pirates_of_penance=63533 +music_modern_piscatoris_hunter_area=63532 +music_modern_poles_apart=63531 +music_modern_power_of_the_shadow_realm=63530 +music_modern_predator_xarpus=63529 +music_modern_preservation=63528 +music_modern_preserved=63527 +music_modern_prifddinas=63526 +music_modern_prime_time=63525 +music_modern_prospering_fortune=63524 +music_modern_quest=63523 +music_modern_race_against_the_clock=63522 +music_modern_rat_a_tat_tat=63521 +music_modern_rat_hunt=63520 +music_modern_ready_for_the_hunt=63519 +music_modern_regal_pomp=63518 +music_modern_relics=63517 +music_modern_remnants=63516 +music_modern_rest_in_peace=63515 +music_modern_revenants=63514 +music_modern_righteousness=63513 +music_modern_romancing_the_crone=63512 +music_modern_romper_chomper=63511 +music_modern_roots_and_flutes=63510 +music_modern_rose=63509 +music_modern_rugged_terrain=63508 +music_modern_ruins_of_isolation=63507 +music_modern_rune_essence=63506 +music_modern_sad_meadow=63505 +music_modern_safety_in_numbers=63504 +music_modern_saga=63503 +music_modern_sands_of_time=63502 +music_modern_sarachnis=63501 +music_modern_sarcophagus=63499 +music_modern_sarims_vermin=63498 +music_modern_scape_cave=63497 +music_modern_scape_main=63496 +music_modern_scar_tissue=63495 +music_modern_schools_out=63494 +music_modern_scorpia_dances=63493 +music_modern_sea_shanty_2=63492 +music_modern_secrets_of_the_north=63491 +music_modern_serene=63490 +music_modern_servants_of_strife=63489 +music_modern_settlement=63488 +music_modern_seventh_realm=63487 +music_modern_shadowland=63486 +music_modern_sharp_end_of_the_crystal=63485 +music_modern_shining_spirit=63484 +music_modern_shipwrecked=63483 +music_modern_showdown=63482 +music_modern_sigmunds_showdown=63481 +music_modern_sign_here=63480 +music_modern_slice_of_silent_movie=63479 +music_modern_slice_of_station=63478 +music_modern_slither_and_thither=63477 +music_modern_slug_a_bug_ball=63476 +music_modern_solitude=63475 +music_modern_song_of_the_elves=63474 +music_modern_soul_fall=63473 +music_modern_soul_wars=63472 +music_modern_spirits_of_the_elid=63471 +music_modern_spooky=63470 +music_modern_spooky_2=63469 +music_modern_spooky_jungle=63468 +music_modern_stagnant=63467 +music_modern_stalkers=63466 +music_modern_stillness=63465 +music_modern_stones_of_old=63464 +music_modern_storeroom_shuffle=63463 +music_modern_storm_brew=63462 +music_modern_stranded=63461 +music_modern_strange_place=63460 +music_modern_strangled=63459 +music_modern_stratosphere=63458 +music_modern_strength_of_saradomin=63457 +music_modern_stuck_in_the_mire=63456 +music_modern_subterranea=63455 +music_modern_superstition=63454 +music_modern_suspicious=63453 +music_modern_tears_of_guthix=63452 +music_modern_technology=63451 +music_modern_teklan=63450 +music_modern_tempest=63449 +music_modern_temple=63448 +music_modern_temple_desecrated=63447 +music_modern_temple_of_light=63446 +music_modern_temple_of_the_eye=63445 +music_modern_temple_of_tribes=63444 +music_modern_tempor_of_the_storm=63443 +music_modern_that_sullen_hall=63442 +music_modern_the_ancient_prison=63441 +music_modern_the_angels_fury=63440 +music_modern_the_bane_of_ashihama=63439 +music_modern_the_chosen=63438 +music_modern_the_consortium=63437 +music_modern_the_curtain_closes=63436 +music_modern_the_depths=63435 +music_modern_the_desolate_isle=63434 +music_modern_the_desolate_mage=63433 +music_modern_the_doom=63432 +music_modern_the_doors_of_dinh=63431 +music_modern_the_enchanter=63430 +music_modern_the_enclave=63429 +music_modern_the_everlasting_slumber=63428 +music_modern_the_fallen_empire=63427 +music_modern_the_far_side=63426 +music_modern_the_feathered_serpent=63425 +music_modern_the_fires_of_lletya=63424 +music_modern_the_forests_of_shayzien=63423 +music_modern_the_forgotten_tomb=63422 +music_modern_the_forlorn_homestead=63421 +music_modern_the_forsaken=63420 +music_modern_the_forsaken_tower=63419 +music_modern_the_foundry=63418 +music_modern_the_fragment=63417 +music_modern_the_galleon=63416 +music_modern_the_gauntlet=63415 +music_modern_the_genie=63414 +music_modern_the_golem=63413 +music_modern_the_great_conch=63412 +music_modern_the_guardian_of_tapoyauik=63411 +music_modern_the_guidance_of_ralos=63410 +music_modern_the_houses_of_kourend=63409 +music_modern_the_longramble_scramble=63408 +music_modern_the_lost_melody=63407 +music_modern_the_lost_tribe=63406 +music_modern_the_mad_mole=63405 +music_modern_the_maidens_anger=63404 +music_modern_the_maze=63403 +music_modern_the_militia=63402 +music_modern_the_mollusc_menace=63401 +music_modern_the_monsters_below=63400 +music_modern_the_moons_of_ruin=63399 +music_modern_the_navigator=63398 +music_modern_the_nightmare_continues=63397 +music_modern_the_north=63396 +music_modern_the_other_side=63395 +music_modern_the_pandemonium=63394 +music_modern_the_part_where_you_die=63393 +music_modern_the_plundered_tomb=63392 +music_modern_the_power_of_tears=63391 +music_modern_the_quizmaster=63390 +music_modern_the_rogues_den=63389 +music_modern_the_route_of_all_evil=63388 +music_modern_the_ruins_of_camdozaal=63387 +music_modern_the_shadow=63386 +music_modern_the_slayer=63385 +music_modern_the_sound_of_guthix=63384 +music_modern_the_spurned_demon=63383 +music_modern_the_talkasti_people=63382 +music_modern_the_terrible_tower=63381 +music_modern_the_terrible_tunnels=63380 +music_modern_the_tortugan_way=63379 +music_modern_the_vault=63378 +music_modern_the_waiting_game=63377 +music_modern_the_walking_dead=63376 +music_modern_theme=63375 +music_modern_thrall_of_the_serpent=63374 +music_modern_throne_of_the_demon=63373 +music_modern_time_out=63372 +music_modern_time_to_mine=63371 +music_modern_tiptoe=63370 +music_modern_title_fight=63369 +music_modern_tomb_raider=63368 +music_modern_tomorrow=63367 +music_modern_too_many_cooks=63366 +music_modern_trahaearn_toil=63365 +music_modern_tranquillity=63364 +music_modern_trawler=63363 +music_modern_trawler_minor=63362 +music_modern_tribal_background=63361 +music_modern_trinity=63360 +music_modern_troll_country=63359 +music_modern_troll_shuffle=63358 +music_modern_trouble_brewing=63357 +music_modern_troubled_waters=63356 +music_modern_twilight=63355 +music_modern_tzhaar=63354 +music_modern_undead_dungeon=63353 +music_modern_under_the_mountain=63352 +music_modern_underground=63351 +music_modern_underground_pass=63350 +music_modern_understanding=63349 +music_modern_untouchable=63348 +music_modern_unturned_stones=63347 +music_modern_upcoming=63346 +music_modern_upir_likhyi=63345 +music_modern_upper_depths=63344 +music_modern_varlamores_sunset=63343 +music_modern_venture_2=63342 +music_modern_victory_is_mine=63341 +music_modern_village=63340 +music_modern_volcanic_vikings=63339 +music_modern_voodoo_cult=63338 +music_modern_waking_dream=63337 +music_modern_warrior=63336 +music_modern_warriors_guild=63335 +music_modern_waste_defaced=63334 +music_modern_watch_your_step=63333 +music_modern_waterfall=63332 +music_modern_waterlogged=63331 +music_modern_way_of_the_enchanter=63330 +music_modern_wayward=63329 +music_modern_we_are_the_fairies=63328 +music_modern_welcome_to_the_theatre=63327 +music_modern_well_hallowed_air=63326 +music_modern_well_of_voyage=63325 +music_modern_what_happens_below=63324 +music_modern_what_the_shell=63323 +music_modern_where_eagles_lair=63322 +music_modern_whispering_wind=63321 +music_modern_wilderness_2=63320 +music_modern_wings_of_madness=63319 +music_modern_wonder=63318 +music_modern_work_work_work=63317 +music_modern_wrath_and_ruin=63316 +music_modern_xenophobe=63315 +music_modern_zamorak_zoo=63314 +music_modern_zaniks_theme=63313 +music_modern_zealot=63312 +music_modern_zogre_dance=63311 +music_modern_zombiism=63310 diff --git a/.data/gamevals/inv.rscm b/.data/gamevals/inv.rscm new file mode 100644 index 000000000..5adee63c6 --- /dev/null +++ b/.data/gamevals/inv.rscm @@ -0,0 +1 @@ +emirs_arena_stake=1101 diff --git a/.data/gamevals/queue.rscm b/.data/gamevals/queue.rscm index bf528ae4e..f19e1856b 100644 --- a/.data/gamevals/queue.rscm +++ b/.data/gamevals/queue.rscm @@ -25,3 +25,7 @@ runmode_toggle=23 preserve_activation=24 client_mode=25 fullscreen_map=26 +emirs_arena_start=28 +emirs_arena_leave=29 +dark_wizard_curse=27 +demonslayer_delrith_timeout=30 diff --git a/.data/gamevals/synth.rscm b/.data/gamevals/synth.rscm index b1ab2a387..0812271ca 100644 --- a/.data/gamevals/synth.rscm +++ b/.data/gamevals/synth.rscm @@ -193,3 +193,6 @@ toa_shadow_weapon_cast_fire_01=6410 shooting_star_whisle=4924 shooting_star_crash=4925 mm2_gorilla_boulder_impact=1444 +claws_of_guthix_cast=1653 +flames_of_zamorak_cast=1655 +saradomin_strike_cast=1659 diff --git a/.data/gamevals/timer.rscm b/.data/gamevals/timer.rscm index 701a308f4..3fb8fdb21 100644 --- a/.data/gamevals/timer.rscm +++ b/.data/gamevals/timer.rscm @@ -33,3 +33,5 @@ potion_moonlight=33 food_delayed_heal=34 heart_cooldown=35 boss_hp_bar_check=114 +npc_poison=36 +combat_stun=37 diff --git a/.data/gamevals/varn.rscm b/.data/gamevals/varn.rscm index 99794df1b..31d0f4489 100644 --- a/.data/gamevals/varn.rscm +++ b/.data/gamevals/varn.rscm @@ -15,3 +15,4 @@ td_shield_up=13 td_overhead_style=14 guaranteed_hit=15 venenatis_attacks=16 +npc_poison_severity=17 diff --git a/.data/raw-cache/map/area/a_dangerous_game.toml b/.data/raw-cache/map/area/a_dangerous_game.toml new file mode 100644 index 000000000..c8fa1aef7 --- /dev/null +++ b/.data/raw-cache/map/area/a_dangerous_game.toml @@ -0,0 +1,16 @@ +# A Dangerous Game music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: A Dangerous Game. +# Bounds x 3328-3455, z 10048-10175; 16384 tiles as 1 rectangles. + +[[area]] +name = "A Dangerous Game" +area_id = "area.a_dangerous_game" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3328, 10048], + [3455, 10048], + [3455, 10175], + [3328, 10175], +] diff --git a/.data/raw-cache/map/area/a_desolate_past.toml b/.data/raw-cache/map/area/a_desolate_past.toml new file mode 100644 index 000000000..b5a9a79e3 --- /dev/null +++ b/.data/raw-cache/map/area/a_desolate_past.toml @@ -0,0 +1,74 @@ +# A Desolate Past music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: A Desolate Past. +# Bounds x 1280-1343, z 9344-9557; 12857 tiles as 9 rectangles. + +[[area]] +name = "A Desolate Past" +area_id = "area.a_desolate_past" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1280, 9344], + [1299, 9344], + [1299, 9557], + [1280, 9557], +] + +[[area.polygons]] +vertices = [ + [1338, 9358], + [1343, 9358], + [1343, 9557], + [1338, 9557], +] + +[[area.polygons]] +vertices = [ + [1321, 9360], + [1337, 9360], + [1337, 9557], + [1321, 9557], +] + +[[area.polygons]] +vertices = [ + [1300, 9363], + [1308, 9363], + [1308, 9557], + [1300, 9557], +] + +[[area.polygons]] +vertices = [ + [1315, 9363], + [1320, 9363], + [1320, 9557], + [1315, 9557], +] + +[[area.polygons]] +vertices = [ + [1314, 9376], + [1314, 9557], +] + +[[area.polygons]] +vertices = [ + [1309, 9377], + [1311, 9377], + [1311, 9557], + [1309, 9557], +] + +[[area.polygons]] +vertices = [ + [1313, 9377], + [1313, 9557], +] + +[[area.polygons]] +vertices = [ + [1312, 9378], + [1312, 9557], +] diff --git a/.data/raw-cache/map/area/a_forgotten_religion.toml b/.data/raw-cache/map/area/a_forgotten_religion.toml new file mode 100644 index 000000000..8136c1080 --- /dev/null +++ b/.data/raw-cache/map/area/a_forgotten_religion.toml @@ -0,0 +1,16 @@ +# A Forgotten Religion music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: A Forgotten Religion. +# Bounds x 1792-1855, z 9912-9983; 4608 tiles as 1 rectangles. + +[[area]] +name = "A Forgotten Religion" +area_id = "area.a_forgotten_religion" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1792, 9912], + [1855, 9912], + [1855, 9983], + [1792, 9983], +] diff --git a/.data/raw-cache/map/area/a_mothers_curse.toml b/.data/raw-cache/map/area/a_mothers_curse.toml new file mode 100644 index 000000000..9a2e21fb9 --- /dev/null +++ b/.data/raw-cache/map/area/a_mothers_curse.toml @@ -0,0 +1,16 @@ +# A Mother's Curse music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: A Mother's Curse. +# Bounds x 3520-3583, z 5376-5439; 4096 tiles as 1 rectangles. + +[[area]] +name = "A Mother's Curse" +area_id = "area.a_mothers_curse" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3520, 5376], + [3583, 5376], + [3583, 5439], + [3520, 5439], +] diff --git a/.data/raw-cache/map/area/a_taste_of_hope.toml b/.data/raw-cache/map/area/a_taste_of_hope.toml new file mode 100644 index 000000000..3b00afa0d --- /dev/null +++ b/.data/raw-cache/map/area/a_taste_of_hope.toml @@ -0,0 +1,16 @@ +# A Taste of Hope music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: A Taste of Hope. +# Bounds x 2048-2111, z 4864-4927; 4096 tiles as 1 rectangles. + +[[area]] +name = "A Taste of Hope" +area_id = "area.a_taste_of_hope" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2048, 4864], + [2111, 4864], + [2111, 4927], + [2048, 4927], +] diff --git a/.data/raw-cache/map/area/a_walk_in_the_woods.toml b/.data/raw-cache/map/area/a_walk_in_the_woods.toml new file mode 100644 index 000000000..6017f5c43 --- /dev/null +++ b/.data/raw-cache/map/area/a_walk_in_the_woods.toml @@ -0,0 +1,1042 @@ +# A Walk in the Woods music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: A Walk in the Woods. +# Bounds x 1472-1660, z 3394-9855; 21601 tiles as 163 rectangles. + +[[area]] +name = "A Walk in the Woods" +area_id = "area.a_walk_in_the_woods" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1500, 3394], + [1508, 3394], + [1508, 3396], + [1500, 3396], +] + +[[area.polygons]] +vertices = [[1509, 3395]] + +[[area.polygons]] +vertices = [ + [1504, 3397], + [1504, 3522], +] + +[[area.polygons]] +vertices = [ + [1491, 3398], + [1492, 3398], + [1492, 3407], + [1491, 3407], +] + +[[area.polygons]] +vertices = [ + [1493, 3399], + [1493, 3406], +] + +[[area.polygons]] +vertices = [ + [1502, 3399], + [1503, 3399], + [1503, 3518], + [1502, 3518], +] + +[[area.polygons]] +vertices = [ + [1505, 3399], + [1506, 3399], + [1506, 3524], + [1505, 3524], +] + +[[area.polygons]] +vertices = [ + [1496, 3402], + [1501, 3402], + [1501, 3405], + [1496, 3405], +] + +[[area.polygons]] +vertices = [ + [1494, 3403], + [1495, 3403], +] + +[[area.polygons]] +vertices = [ + [1590, 3405], + [1595, 3405], + [1595, 3515], + [1590, 3515], +] + +[[area.polygons]] +vertices = [ + [1583, 3406], + [1589, 3406], + [1589, 3515], + [1583, 3515], +] + +[[area.polygons]] +vertices = [ + [1596, 3406], + [1597, 3406], + [1597, 3517], + [1596, 3517], +] + +[[area.polygons]] +vertices = [ + [1508, 3407], + [1513, 3407], + [1513, 3535], + [1508, 3535], +] + +[[area.polygons]] +vertices = [ + [1559, 3407], + [1565, 3407], + [1565, 3525], + [1559, 3525], +] + +[[area.polygons]] +vertices = [ + [1579, 3407], + [1582, 3407], + [1582, 3410], + [1579, 3410], +] + +[[area.polygons]] +vertices = [ + [1598, 3407], + [1598, 3518], +] + +[[area.polygons]] +vertices = [ + [1507, 3408], + [1507, 3535], +] + +[[area.polygons]] +vertices = [ + [1514, 3408], + [1517, 3408], + [1517, 3533], + [1514, 3533], +] + +[[area.polygons]] +vertices = [ + [1558, 3408], + [1558, 3525], +] + +[[area.polygons]] +vertices = [ + [1566, 3408], + [1566, 3525], +] + +[[area.polygons]] +vertices = [ + [1599, 3408], + [1599, 3518], +] + +[[area.polygons]] +vertices = [ + [1501, 3409], + [1501, 3517], +] + +[[area.polygons]] +vertices = [ + [1518, 3409], + [1518, 3532], +] + +[[area.polygons]] +vertices = [ + [1567, 3409], + [1567, 3525], +] + +[[area.polygons]] +vertices = [ + [1600, 3409], + [1601, 3409], + [1601, 3510], + [1600, 3510], +] + +[[area.polygons]] +vertices = [ + [1519, 3410], + [1520, 3410], + [1520, 3531], + [1519, 3531], +] + +[[area.polygons]] +vertices = [ + [1557, 3410], + [1557, 3525], +] + +[[area.polygons]] +vertices = [ + [1568, 3410], + [1568, 3525], +] + +[[area.polygons]] +vertices = [ + [1602, 3410], + [1602, 3510], +] + +[[area.polygons]] +vertices = [ + [1500, 3411], + [1500, 3516], +] + +[[area.polygons]] +vertices = [ + [1521, 3411], + [1521, 3530], +] + +[[area.polygons]] +vertices = [ + [1580, 3411], + [1582, 3411], +] + +[[area.polygons]] +vertices = [ + [1603, 3411], + [1604, 3411], + [1604, 3510], + [1603, 3510], +] + +[[area.polygons]] +vertices = [ + [1556, 3412], + [1556, 3525], +] + +[[area.polygons]] +vertices = [ + [1581, 3412], + [1582, 3412], + [1582, 3515], + [1581, 3515], +] + +[[area.polygons]] +vertices = [ + [1605, 3412], + [1606, 3412], + [1606, 3510], + [1605, 3510], +] + +[[area.polygons]] +vertices = [ + [1499, 3413], + [1499, 3515], +] + +[[area.polygons]] +vertices = [ + [1554, 3413], + [1555, 3413], + [1555, 3525], + [1554, 3525], +] + +[[area.polygons]] +vertices = [ + [1569, 3413], + [1569, 3525], +] + +[[area.polygons]] +vertices = [ + [1607, 3413], + [1607, 3422], +] + +[[area.polygons]] +vertices = [ + [1498, 3414], + [1498, 3514], +] + +[[area.polygons]] +vertices = [ + [1522, 3414], + [1522, 3530], +] + +[[area.polygons]] +vertices = [ + [1553, 3414], + [1553, 3525], +] + +[[area.polygons]] +vertices = [ + [1570, 3414], + [1570, 3525], +] + +[[area.polygons]] +vertices = [ + [1608, 3414], + [1608, 3419], +] + +[[area.polygons]] +vertices = [ + [1497, 3415], + [1497, 3513], +] + +[[area.polygons]] +vertices = [ + [1523, 3415], + [1523, 3529], +] + +[[area.polygons]] +vertices = [ + [1532, 3415], + [1537, 3415], + [1537, 3529], + [1532, 3529], +] + +[[area.polygons]] +vertices = [ + [1552, 3415], + [1552, 3525], +] + +[[area.polygons]] +vertices = [ + [1571, 3415], + [1571, 3525], +] + +[[area.polygons]] +vertices = [ + [1580, 3415], + [1580, 3516], +] + +[[area.polygons]] +vertices = [ + [1495, 3416], + [1496, 3416], + [1496, 3444], + [1495, 3444], +] + +[[area.polygons]] +vertices = [ + [1524, 3416], + [1531, 3416], + [1531, 3529], + [1524, 3529], +] + +[[area.polygons]] +vertices = [ + [1538, 3416], + [1539, 3416], + [1539, 3527], + [1538, 3527], +] + +[[area.polygons]] +vertices = [ + [1550, 3416], + [1551, 3416], + [1551, 3525], + [1550, 3525], +] + +[[area.polygons]] +vertices = [ + [1572, 3416], + [1573, 3416], + [1573, 3518], + [1572, 3518], +] + +[[area.polygons]] +vertices = [ + [1578, 3416], + [1579, 3416], + [1579, 3516], + [1578, 3516], +] + +[[area.polygons]] +vertices = [ + [1493, 3417], + [1494, 3417], + [1494, 3441], + [1493, 3441], +] + +[[area.polygons]] +vertices = [ + [1540, 3417], + [1540, 3526], +] + +[[area.polygons]] +vertices = [ + [1549, 3417], + [1549, 3525], +] + +[[area.polygons]] +vertices = [ + [1574, 3417], + [1577, 3417], + [1577, 3517], + [1574, 3517], +] + +[[area.polygons]] +vertices = [ + [1492, 3418], + [1492, 3423], +] + +[[area.polygons]] +vertices = [ + [1541, 3418], + [1542, 3418], + [1542, 3525], + [1541, 3525], +] + +[[area.polygons]] +vertices = [ + [1548, 3418], + [1548, 3525], +] + +[[area.polygons]] +vertices = [ + [1543, 3419], + [1547, 3419], + [1547, 3525], + [1543, 3525], +] + +[[area.polygons]] +vertices = [ + [1491, 3420], + [1491, 3421], +] + +[[area.polygons]] +vertices = [ + [1607, 3426], + [1607, 3513], +] + +[[area.polygons]] +vertices = [ + [1614, 3426], + [1620, 3426], + [1620, 3515], + [1614, 3515], +] + +[[area.polygons]] +vertices = [ + [1488, 3427], + [1492, 3427], + [1492, 3437], + [1488, 3437], +] + +[[area.polygons]] +vertices = [ + [1613, 3427], + [1613, 3515], +] + +[[area.polygons]] +vertices = [ + [1621, 3427], + [1621, 3516], +] + +[[area.polygons]] +vertices = [ + [1487, 3428], + [1487, 3436], +] + +[[area.polygons]] +vertices = [ + [1608, 3428], + [1612, 3428], + [1612, 3513], + [1608, 3513], +] + +[[area.polygons]] +vertices = [ + [1622, 3428], + [1624, 3428], + [1624, 3517], + [1622, 3517], +] + +[[area.polygons]] +vertices = [ + [1625, 3429], + [1625, 3518], +] + +[[area.polygons]] +vertices = [ + [1486, 3430], + [1486, 3434], +] + +[[area.polygons]] +vertices = [ + [1626, 3430], + [1626, 3518], +] + +[[area.polygons]] +vertices = [ + [1627, 3437], + [1627, 3518], +] + +[[area.polygons]] +vertices = [ + [1489, 3438], + [1492, 3438], +] + +[[area.polygons]] +vertices = [ + [1490, 3439], + [1492, 3439], +] + +[[area.polygons]] +vertices = [ + [1628, 3439], + [1629, 3439], + [1629, 3518], + [1628, 3518], +] + +[[area.polygons]] +vertices = [ + [1491, 3440], + [1492, 3440], +] + +[[area.polygons]] +vertices = [ + [1630, 3440], + [1630, 3518], +] + +[[area.polygons]] +vertices = [ + [1639, 3440], + [1639, 3516], +] + +[[area.polygons]] +vertices = [[1492, 3441]] + +[[area.polygons]] +vertices = [ + [1631, 3441], + [1631, 3517], +] + +[[area.polygons]] +vertices = [ + [1637, 3441], + [1638, 3441], + [1638, 3516], + [1637, 3516], +] + +[[area.polygons]] +vertices = [ + [1640, 3441], + [1640, 3516], +] + +[[area.polygons]] +vertices = [[1494, 3442]] + +[[area.polygons]] +vertices = [ + [1632, 3442], + [1633, 3442], + [1633, 3516], + [1632, 3516], +] + +[[area.polygons]] +vertices = [ + [1636, 3442], + [1636, 3516], +] + +[[area.polygons]] +vertices = [ + [1641, 3442], + [1641, 3516], +] + +[[area.polygons]] +vertices = [ + [1634, 3443], + [1635, 3443], + [1635, 3516], + [1634, 3516], +] + +[[area.polygons]] +vertices = [ + [1642, 3443], + [1642, 3459], +] + +[[area.polygons]] +vertices = [ + [1643, 3444], + [1644, 3444], + [1644, 3456], + [1643, 3456], +] + +[[area.polygons]] +vertices = [[1496, 3445]] + +[[area.polygons]] +vertices = [ + [1645, 3445], + [1645, 3454], +] + +[[area.polygons]] +vertices = [ + [1646, 3446], + [1646, 3452], +] + +[[area.polygons]] +vertices = [ + [1647, 3447], + [1647, 3451], +] + +[[area.polygons]] +vertices = [ + [1496, 3451], + [1496, 3513], +] + +[[area.polygons]] +vertices = [ + [1494, 3452], + [1495, 3452], + [1495, 3513], + [1494, 3513], +] + +[[area.polygons]] +vertices = [ + [1493, 3453], + [1493, 3513], +] + +[[area.polygons]] +vertices = [ + [1492, 3454], + [1492, 3513], +] + +[[area.polygons]] +vertices = [ + [1491, 3457], + [1491, 3513], +] + +[[area.polygons]] +vertices = [[1643, 3457]] + +[[area.polygons]] +vertices = [ + [1490, 3459], + [1490, 3513], +] + +[[area.polygons]] +vertices = [ + [1489, 3462], + [1489, 3514], +] + +[[area.polygons]] +vertices = [ + [1487, 3463], + [1488, 3463], + [1488, 3514], + [1487, 3514], +] + +[[area.polygons]] +vertices = [ + [1486, 3464], + [1486, 3515], +] + +[[area.polygons]] +vertices = [ + [1484, 3465], + [1485, 3465], + [1485, 3516], + [1484, 3516], +] + +[[area.polygons]] +vertices = [ + [1482, 3466], + [1483, 3466], + [1483, 3516], + [1482, 3516], +] + +[[area.polygons]] +vertices = [ + [1642, 3466], + [1642, 3516], +] + +[[area.polygons]] +vertices = [ + [1479, 3467], + [1481, 3467], + [1481, 3484], + [1479, 3484], +] + +[[area.polygons]] +vertices = [ + [1478, 3468], + [1478, 3483], +] + +[[area.polygons]] +vertices = [ + [1476, 3469], + [1477, 3469], + [1477, 3482], + [1476, 3482], +] + +[[area.polygons]] +vertices = [ + [1643, 3469], + [1647, 3469], + [1647, 3516], + [1643, 3516], +] + +[[area.polygons]] +vertices = [ + [1475, 3470], + [1475, 3477], +] + +[[area.polygons]] +vertices = [ + [1648, 3470], + [1648, 3516], +] + +[[area.polygons]] +vertices = [ + [1474, 3471], + [1474, 3477], +] + +[[area.polygons]] +vertices = [ + [1649, 3471], + [1650, 3471], + [1650, 3490], + [1649, 3490], +] + +[[area.polygons]] +vertices = [ + [1473, 3472], + [1473, 3477], +] + +[[area.polygons]] +vertices = [ + [1651, 3472], + [1651, 3490], +] + +[[area.polygons]] +vertices = [ + [1472, 3473], + [1472, 3477], +] + +[[area.polygons]] +vertices = [ + [1652, 3473], + [1652, 3490], +] + +[[area.polygons]] +vertices = [ + [1653, 3475], + [1653, 3490], +] + +[[area.polygons]] +vertices = [ + [1654, 3476], + [1654, 3490], +] + +[[area.polygons]] +vertices = [ + [1655, 3477], + [1658, 3477], + [1658, 3490], + [1655, 3490], +] + +[[area.polygons]] +vertices = [ + [1659, 3478], + [1659, 3481], +] + +[[area.polygons]] +vertices = [[1477, 3483]] + +[[area.polygons]] +vertices = [[1481, 3485]] + +[[area.polygons]] +vertices = [ + [1659, 3487], + [1659, 3490], +] + +[[area.polygons]] +vertices = [ + [1660, 3489], + [1660, 3490], +] + +[[area.polygons]] +vertices = [ + [1649, 3491], + [1649, 3516], +] + +[[area.polygons]] +vertices = [ + [1650, 3492], + [1650, 3516], +] + +[[area.polygons]] +vertices = [ + [1651, 3493], + [1651, 3516], +] + +[[area.polygons]] +vertices = [ + [1652, 3494], + [1652, 3516], +] + +[[area.polygons]] +vertices = [ + [1481, 3495], + [1481, 3516], +] + +[[area.polygons]] +vertices = [ + [1653, 3495], + [1653, 3516], +] + +[[area.polygons]] +vertices = [ + [1480, 3496], + [1480, 3516], +] + +[[area.polygons]] +vertices = [ + [1654, 3496], + [1654, 3515], +] + +[[area.polygons]] +vertices = [ + [1478, 3497], + [1479, 3497], + [1479, 3514], + [1478, 3514], +] + +[[area.polygons]] +vertices = [ + [1655, 3497], + [1655, 3508], +] + +[[area.polygons]] +vertices = [ + [1477, 3498], + [1477, 3513], +] + +[[area.polygons]] +vertices = [ + [1476, 3499], + [1476, 3512], +] + +[[area.polygons]] +vertices = [ + [1475, 3500], + [1475, 3511], +] + +[[area.polygons]] +vertices = [ + [1656, 3502], + [1656, 3507], +] + +[[area.polygons]] +vertices = [ + [1657, 3503], + [1657, 3506], +] + +[[area.polygons]] +vertices = [ + [1600, 3511], + [1600, 3519], +] + +[[area.polygons]] +vertices = [ + [1610, 3514], + [1612, 3514], +] + +[[area.polygons]] +vertices = [[1479, 3515]] + +[[area.polygons]] +vertices = [[1487, 3515]] + +[[area.polygons]] +vertices = [ + [1611, 3515], + [1612, 3515], +] + +[[area.polygons]] +vertices = [ + [1594, 3516], + [1595, 3516], +] + +[[area.polygons]] +vertices = [ + [1574, 3518], + [1575, 3518], +] + +[[area.polygons]] +vertices = [ + [1623, 3518], + [1624, 3518], +] + +[[area.polygons]] +vertices = [[1503, 3519]] + +[[area.polygons]] +vertices = [[1572, 3519]] + +[[area.polygons]] +vertices = [ + [1506, 3525], + [1506, 3526], +] + +[[area.polygons]] +vertices = [[1538, 3528]] + +[[area.polygons]] +vertices = [[1519, 3532]] + +[[area.polygons]] +vertices = [ + [1514, 3534], + [1515, 3534], +] + +[[area.polygons]] +vertices = [[1514, 3535]] + +[[area.polygons]] +vertices = [ + [1536, 9792], + [1599, 9792], + [1599, 9855], + [1536, 9855], +] diff --git a/.data/raw-cache/map/area/al_kharid.toml b/.data/raw-cache/map/area/al_kharid.toml new file mode 100644 index 000000000..415b2e977 --- /dev/null +++ b/.data/raw-cache/map/area/al_kharid.toml @@ -0,0 +1,666 @@ +[[area]] +name = "Al Kharid" +area_id = "area.al_kharid" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3265, 3140], + [3325, 3140], + [3325, 3200], + [3265, 3200], +] + +# Extension traced from the OSRS wiki music map (11571 tiles). + +[[area.polygons]] +vertices = [ + [3302, 3117], + [3304, 3117], + [3304, 3139], + [3302, 3139], +] + +[[area.polygons]] +vertices = [ + [3300, 3118], + [3301, 3118], + [3301, 3129], + [3300, 3129], +] + +[[area.polygons]] +vertices = [ + [3305, 3118], + [3306, 3118], + [3306, 3130], + [3305, 3130], +] + +[[area.polygons]] +vertices = [ + [3298, 3119], + [3299, 3119], + [3299, 3127], + [3298, 3127], +] + +[[area.polygons]] +vertices = [ + [3307, 3119], + [3308, 3119], + [3308, 3126], + [3307, 3126], +] + +[[area.polygons]] +vertices = [ + [3309, 3120], + [3309, 3124], +] + +[[area.polygons]] +vertices = [ + [3297, 3121], + [3297, 3126], +] + +[[area.polygons]] +vertices = [ + [3295, 3122], + [3296, 3122], + [3296, 3126], + [3295, 3126], +] + +[[area.polygons]] +vertices = [[3310, 3122]] + +[[area.polygons]] +vertices = [ + [3307, 3127], + [3307, 3128], +] + +[[area.polygons]] +vertices = [[3299, 3128]] + +[[area.polygons]] +vertices = [[3301, 3130]] + +[[area.polygons]] +vertices = [[3305, 3131]] + +[[area.polygons]] +vertices = [ + [3301, 3133], + [3301, 3139], +] + +[[area.polygons]] +vertices = [ + [3300, 3134], + [3300, 3139], +] + +[[area.polygons]] +vertices = [ + [3305, 3135], + [3305, 3139], +] + +[[area.polygons]] +vertices = [ + [3281, 3136], + [3299, 3136], + [3299, 3139], + [3281, 3139], +] + +[[area.polygons]] +vertices = [ + [3306, 3136], + [3327, 3136], + [3327, 3139], + [3306, 3139], +] + +[[area.polygons]] +vertices = [ + [3364, 3136], + [3383, 3136], + [3383, 3199], + [3364, 3199], +] + +[[area.polygons]] +vertices = [ + [3280, 3137], + [3280, 3139], +] + +[[area.polygons]] +vertices = [ + [3363, 3137], + [3363, 3199], +] + +[[area.polygons]] +vertices = [ + [3384, 3137], + [3384, 3199], +] + +[[area.polygons]] +vertices = [ + [3278, 3138], + [3279, 3138], + [3279, 3139], + [3278, 3139], +] + +[[area.polygons]] +vertices = [ + [3328, 3138], + [3329, 3138], + [3329, 3195], + [3328, 3195], +] + +[[area.polygons]] +vertices = [ + [3362, 3138], + [3362, 3199], +] + +[[area.polygons]] +vertices = [ + [3385, 3138], + [3385, 3199], +] + +[[area.polygons]] +vertices = [ + [3330, 3139], + [3330, 3195], +] + +[[area.polygons]] +vertices = [ + [3361, 3139], + [3361, 3199], +] + +[[area.polygons]] +vertices = [ + [3386, 3139], + [3386, 3199], +] + +[[area.polygons]] +vertices = [ + [3326, 3140], + [3327, 3140], + [3327, 3196], + [3326, 3196], +] + +[[area.polygons]] +vertices = [ + [3360, 3140], + [3360, 3199], +] + +[[area.polygons]] +vertices = [ + [3387, 3140], + [3387, 3199], +] + +[[area.polygons]] +vertices = [ + [3331, 3141], + [3331, 3196], +] + +[[area.polygons]] +vertices = [ + [3359, 3141], + [3359, 3199], +] + +[[area.polygons]] +vertices = [ + [3388, 3141], + [3388, 3199], +] + +[[area.polygons]] +vertices = [ + [3358, 3142], + [3358, 3199], +] + +[[area.polygons]] +vertices = [ + [3389, 3142], + [3389, 3199], +] + +[[area.polygons]] +vertices = [ + [3357, 3143], + [3357, 3199], +] + +[[area.polygons]] +vertices = [ + [3390, 3143], + [3390, 3199], +] + +[[area.polygons]] +vertices = [ + [3356, 3144], + [3356, 3199], +] + +[[area.polygons]] +vertices = [ + [3391, 3144], + [3391, 3199], +] + +[[area.polygons]] +vertices = [ + [3332, 3145], + [3332, 3197], +] + +[[area.polygons]] +vertices = [ + [3355, 3145], + [3355, 3199], +] + +[[area.polygons]] +vertices = [ + [3392, 3145], + [3392, 3199], +] + +[[area.polygons]] +vertices = [ + [3354, 3146], + [3354, 3199], +] + +[[area.polygons]] +vertices = [ + [3393, 3146], + [3393, 3199], +] + +[[area.polygons]] +vertices = [ + [3333, 3147], + [3333, 3149], +] + +[[area.polygons]] +vertices = [ + [3353, 3147], + [3353, 3199], +] + +[[area.polygons]] +vertices = [ + [3264, 3150], + [3264, 3201], +] + +[[area.polygons]] +vertices = [ + [3263, 3152], + [3263, 3200], +] + +[[area.polygons]] +vertices = [ + [3333, 3152], + [3335, 3152], + [3335, 3197], + [3333, 3197], +] + +[[area.polygons]] +vertices = [ + [3262, 3153], + [3262, 3190], +] + +[[area.polygons]] +vertices = [ + [3261, 3154], + [3261, 3188], +] + +[[area.polygons]] +vertices = [ + [3260, 3155], + [3260, 3186], +] + +[[area.polygons]] +vertices = [ + [3259, 3157], + [3259, 3185], +] + +[[area.polygons]] +vertices = [ + [3352, 3157], + [3352, 3199], +] + +[[area.polygons]] +vertices = [ + [3258, 3158], + [3258, 3183], +] + +[[area.polygons]] +vertices = [ + [3351, 3158], + [3351, 3199], +] + +[[area.polygons]] +vertices = [ + [3350, 3159], + [3350, 3199], +] + +[[area.polygons]] +vertices = [ + [3257, 3160], + [3257, 3178], +] + +[[area.polygons]] +vertices = [ + [3336, 3160], + [3349, 3160], + [3349, 3199], + [3336, 3199], +] + +[[area.polygons]] +vertices = [ + [3256, 3162], + [3256, 3177], +] + +[[area.polygons]] +vertices = [ + [3403, 3162], + [3408, 3162], + [3408, 3199], + [3403, 3199], +] + +[[area.polygons]] +vertices = [ + [3401, 3163], + [3402, 3163], + [3402, 3199], + [3401, 3199], +] + +[[area.polygons]] +vertices = [ + [3409, 3163], + [3409, 3199], +] + +[[area.polygons]] +vertices = [ + [3400, 3164], + [3400, 3199], +] + +[[area.polygons]] +vertices = [ + [3410, 3164], + [3410, 3199], +] + +[[area.polygons]] +vertices = [ + [3394, 3165], + [3399, 3165], + [3399, 3199], + [3394, 3199], +] + +[[area.polygons]] +vertices = [ + [3411, 3165], + [3411, 3199], +] + +[[area.polygons]] +vertices = [ + [3412, 3166], + [3412, 3199], +] + +[[area.polygons]] +vertices = [ + [3255, 3168], + [3255, 3175], +] + +[[area.polygons]] +vertices = [ + [3254, 3170], + [3254, 3171], +] + +[[area.polygons]] +vertices = [ + [3413, 3170], + [3420, 3170], + [3420, 3193], + [3413, 3193], +] + +[[area.polygons]] +vertices = [ + [3421, 3172], + [3423, 3172], + [3423, 3191], + [3421, 3191], +] + +[[area.polygons]] +vertices = [ + [3424, 3175], + [3425, 3175], + [3425, 3190], + [3424, 3190], +] + +[[area.polygons]] +vertices = [ + [3426, 3177], + [3427, 3177], + [3427, 3188], + [3426, 3188], +] + +[[area.polygons]] +vertices = [[3426, 3189]] + +[[area.polygons]] +vertices = [[3424, 3191]] + +[[area.polygons]] +vertices = [ + [3421, 3192], + [3422, 3192], +] + +[[area.polygons]] +vertices = [ + [3413, 3194], + [3416, 3194], +] + +[[area.polygons]] +vertices = [ + [3262, 3195], + [3262, 3197], +] + +[[area.polygons]] +vertices = [ + [3413, 3195], + [3414, 3195], + [3414, 3199], + [3413, 3199], +] + +[[area.polygons]] +vertices = [ + [3415, 3197], + [3416, 3197], + [3416, 3199], + [3415, 3199], +] + +[[area.polygons]] +vertices = [ + [3334, 3198], + [3335, 3198], +] + +[[area.polygons]] +vertices = [ + [3417, 3198], + [3419, 3198], + [3419, 3199], + [3417, 3199], +] + +[[area.polygons]] +vertices = [[3335, 3199]] + +[[area.polygons]] +vertices = [[3420, 3199]] + +[[area.polygons]] +vertices = [ + [3265, 3201], + [3307, 3201], + [3307, 3202], + [3265, 3202], +] + +[[area.polygons]] +vertices = [ + [3266, 3203], + [3307, 3203], +] + +[[area.polygons]] +vertices = [ + [3267, 3204], + [3307, 3204], + [3307, 3223], + [3267, 3223], +] + +[[area.polygons]] +vertices = [ + [3268, 3224], + [3307, 3224], + [3307, 3323], + [3268, 3323], +] + +[[area.polygons]] +vertices = [ + [3267, 3233], + [3267, 3296], +] + +[[area.polygons]] +vertices = [ + [3266, 3240], + [3266, 3295], +] + +[[area.polygons]] +vertices = [ + [3265, 3248], + [3265, 3254], +] + +[[area.polygons]] +vertices = [ + [3308, 3293], + [3329, 3293], + [3329, 3328], + [3308, 3328], +] + +[[area.polygons]] +vertices = [ + [3267, 3300], + [3267, 3322], +] + +[[area.polygons]] +vertices = [ + [3269, 3324], + [3307, 3324], +] + +[[area.polygons]] +vertices = [ + [3270, 3325], + [3307, 3325], +] + +[[area.polygons]] +vertices = [ + [3271, 3326], + [3307, 3326], + [3307, 3328], + [3271, 3328], +] + +[[area.polygons]] +vertices = [ + [3271, 3329], + [3306, 3329], +] + +[[area.polygons]] +vertices = [ + [3316, 3329], + [3329, 3329], +] + +[[area.polygons]] +vertices = [ + [3300, 3330], + [3303, 3330], +] + +[[area.polygons]] +vertices = [ + [3319, 3330], + [3321, 3330], +] diff --git a/.data/raw-cache/map/area/aldarin.toml b/.data/raw-cache/map/area/aldarin.toml new file mode 100644 index 000000000..7bbe3f114 --- /dev/null +++ b/.data/raw-cache/map/area/aldarin.toml @@ -0,0 +1,860 @@ +# Aldarin music music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Isle of Serenity; Peace and Prosperity. +# Bounds x 1314-1484, z 2887-9343; 21822 tiles as 135 rectangles. + +[[area]] +name = "Aldarin music" +area_id = "area.aldarin" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1392, 2887], + [1401, 2887], + [1401, 2994], + [1392, 2994], +] + +[[area.polygons]] +vertices = [ + [1402, 2888], + [1402, 2999], +] + +[[area.polygons]] +vertices = [ + [1403, 2889], + [1403, 2999], +] + +[[area.polygons]] +vertices = [ + [1370, 2890], + [1391, 2890], + [1391, 2984], + [1370, 2984], +] + +[[area.polygons]] +vertices = [ + [1404, 2890], + [1404, 2999], +] + +[[area.polygons]] +vertices = [ + [1413, 2890], + [1417, 2890], + [1417, 2998], + [1413, 2998], +] + +[[area.polygons]] +vertices = [ + [1369, 2891], + [1369, 2984], +] + +[[area.polygons]] +vertices = [ + [1405, 2891], + [1405, 2999], +] + +[[area.polygons]] +vertices = [ + [1412, 2891], + [1412, 2998], +] + +[[area.polygons]] +vertices = [ + [1418, 2891], + [1418, 2999], +] + +[[area.polygons]] +vertices = [ + [1355, 2892], + [1368, 2892], + [1368, 2983], + [1355, 2983], +] + +[[area.polygons]] +vertices = [ + [1410, 2892], + [1411, 2892], + [1411, 2998], + [1410, 2998], +] + +[[area.polygons]] +vertices = [ + [1419, 2892], + [1419, 3000], +] + +[[area.polygons]] +vertices = [ + [1354, 2893], + [1354, 2987], +] + +[[area.polygons]] +vertices = [ + [1408, 2893], + [1409, 2893], + [1409, 2998], + [1408, 2998], +] + +[[area.polygons]] +vertices = [ + [1349, 2894], + [1353, 2894], + [1353, 2987], + [1349, 2987], +] + +[[area.polygons]] +vertices = [ + [1406, 2894], + [1407, 2894], + [1407, 2998], + [1406, 2998], +] + +[[area.polygons]] +vertices = [ + [1347, 2895], + [1348, 2895], + [1348, 2987], + [1347, 2987], +] + +[[area.polygons]] +vertices = [ + [1420, 2895], + [1420, 3000], +] + +[[area.polygons]] +vertices = [ + [1344, 2896], + [1346, 2896], + [1346, 2986], + [1344, 2986], +] + +[[area.polygons]] +vertices = [ + [1421, 2896], + [1421, 3000], +] + +[[area.polygons]] +vertices = [ + [1426, 2896], + [1430, 2896], + [1430, 2990], + [1426, 2990], +] + +[[area.polygons]] +vertices = [ + [1330, 2897], + [1343, 2897], + [1343, 2980], + [1330, 2980], +] + +[[area.polygons]] +vertices = [ + [1422, 2897], + [1425, 2897], + [1425, 3000], + [1422, 3000], +] + +[[area.polygons]] +vertices = [ + [1431, 2897], + [1431, 2989], +] + +[[area.polygons]] +vertices = [ + [1432, 2898], + [1433, 2898], + [1433, 2989], + [1432, 2989], +] + +[[area.polygons]] +vertices = [ + [1434, 2899], + [1435, 2899], + [1435, 2987], + [1434, 2987], +] + +[[area.polygons]] +vertices = [ + [1443, 2899], + [1446, 2899], + [1446, 2958], + [1443, 2958], +] + +[[area.polygons]] +vertices = [ + [1436, 2900], + [1438, 2900], + [1438, 2970], + [1436, 2970], +] + +[[area.polygons]] +vertices = [ + [1441, 2900], + [1442, 2900], + [1442, 2962], + [1441, 2962], +] + +[[area.polygons]] +vertices = [ + [1447, 2900], + [1450, 2900], + [1450, 2956], + [1447, 2956], +] + +[[area.polygons]] +vertices = [ + [1439, 2901], + [1440, 2901], + [1440, 2963], + [1439, 2963], +] + +[[area.polygons]] +vertices = [ + [1451, 2901], + [1452, 2901], + [1452, 2956], + [1451, 2956], +] + +[[area.polygons]] +vertices = [ + [1453, 2902], + [1455, 2902], + [1455, 2956], + [1453, 2956], +] + +[[area.polygons]] +vertices = [ + [1456, 2903], + [1461, 2903], + [1461, 2956], + [1456, 2956], +] + +[[area.polygons]] +vertices = [ + [1462, 2904], + [1462, 2956], +] + +[[area.polygons]] +vertices = [ + [1463, 2905], + [1463, 2953], +] + +[[area.polygons]] +vertices = [ + [1464, 2906], + [1466, 2906], + [1466, 2953], + [1464, 2953], +] + +[[area.polygons]] +vertices = [ + [1467, 2907], + [1468, 2907], + [1468, 2952], + [1467, 2952], +] + +[[area.polygons]] +vertices = [ + [1469, 2908], + [1469, 2951], +] + +[[area.polygons]] +vertices = [ + [1470, 2909], + [1470, 2951], +] + +[[area.polygons]] +vertices = [ + [1471, 2911], + [1471, 2950], +] + +[[area.polygons]] +vertices = [ + [1472, 2912], + [1472, 2949], +] + +[[area.polygons]] +vertices = [ + [1473, 2913], + [1473, 2948], +] + +[[area.polygons]] +vertices = [ + [1474, 2914], + [1474, 2947], +] + +[[area.polygons]] +vertices = [ + [1475, 2915], + [1475, 2946], +] + +[[area.polygons]] +vertices = [ + [1476, 2917], + [1476, 2944], +] + +[[area.polygons]] +vertices = [ + [1477, 2918], + [1479, 2918], + [1479, 2941], + [1477, 2941], +] + +[[area.polygons]] +vertices = [ + [1480, 2919], + [1480, 2939], +] + +[[area.polygons]] +vertices = [ + [1481, 2920], + [1481, 2936], +] + +[[area.polygons]] +vertices = [ + [1482, 2922], + [1482, 2935], +] + +[[area.polygons]] +vertices = [ + [1329, 2923], + [1329, 2979], +] + +[[area.polygons]] +vertices = [ + [1483, 2923], + [1483, 2932], +] + +[[area.polygons]] +vertices = [ + [1328, 2924], + [1328, 2979], +] + +[[area.polygons]] +vertices = [ + [1327, 2925], + [1327, 2978], +] + +[[area.polygons]] +vertices = [[1484, 2925]] + +[[area.polygons]] +vertices = [ + [1326, 2926], + [1326, 2978], +] + +[[area.polygons]] +vertices = [ + [1325, 2927], + [1325, 2978], +] + +[[area.polygons]] +vertices = [ + [1324, 2928], + [1324, 2978], +] + +[[area.polygons]] +vertices = [ + [1323, 2929], + [1323, 2978], +] + +[[area.polygons]] +vertices = [ + [1322, 2930], + [1322, 2978], +] + +[[area.polygons]] +vertices = [ + [1484, 2930], + [1484, 2931], +] + +[[area.polygons]] +vertices = [ + [1321, 2931], + [1321, 2977], +] + +[[area.polygons]] +vertices = [ + [1320, 2932], + [1320, 2943], +] + +[[area.polygons]] +vertices = [ + [1319, 2933], + [1319, 2942], +] + +[[area.polygons]] +vertices = [ + [1318, 2934], + [1318, 2941], +] + +[[area.polygons]] +vertices = [ + [1317, 2935], + [1317, 2940], +] + +[[area.polygons]] +vertices = [ + [1316, 2937], + [1316, 2939], +] + +[[area.polygons]] +vertices = [ + [1315, 2938], + [1315, 2939], +] + +[[area.polygons]] +vertices = [ + [1477, 2942], + [1478, 2942], +] + +[[area.polygons]] +vertices = [[1477, 2943]] + +[[area.polygons]] +vertices = [ + [1320, 2946], + [1320, 2950], +] + +[[area.polygons]] +vertices = [ + [1319, 2947], + [1319, 2949], +] + +[[area.polygons]] +vertices = [ + [1320, 2952], + [1320, 2977], +] + +[[area.polygons]] +vertices = [ + [1319, 2953], + [1319, 2976], +] + +[[area.polygons]] +vertices = [[1467, 2953]] + +[[area.polygons]] +vertices = [ + [1316, 2954], + [1318, 2954], + [1318, 2969], + [1316, 2969], +] + +[[area.polygons]] +vertices = [ + [1315, 2955], + [1315, 2968], +] + +[[area.polygons]] +vertices = [[1447, 2957]] + +[[area.polygons]] +vertices = [ + [1452, 2957], + [1454, 2957], +] + +[[area.polygons]] +vertices = [ + [1457, 2957], + [1461, 2957], +] + +[[area.polygons]] +vertices = [ + [1314, 2958], + [1314, 2963], +] + +[[area.polygons]] +vertices = [[1453, 2958]] + +[[area.polygons]] +vertices = [ + [1458, 2958], + [1460, 2958], +] + +[[area.polygons]] +vertices = [ + [1443, 2959], + [1445, 2959], +] + +[[area.polygons]] +vertices = [ + [1443, 2960], + [1444, 2960], +] + +[[area.polygons]] +vertices = [[1443, 2961]] + +[[area.polygons]] +vertices = [ + [1444, 2962], + [1446, 2962], + [1446, 2969], + [1444, 2969], +] + +[[area.polygons]] +vertices = [ + [1458, 2963], + [1459, 2963], + [1459, 2972], + [1458, 2972], +] + +[[area.polygons]] +vertices = [[1439, 2964]] + +[[area.polygons]] +vertices = [ + [1460, 2964], + [1460, 2971], +] + +[[area.polygons]] +vertices = [ + [1439, 2967], + [1443, 2967], + [1443, 2969], + [1439, 2969], +] + +[[area.polygons]] +vertices = [ + [1447, 2967], + [1455, 2967], + [1455, 2969], + [1447, 2969], +] + +[[area.polygons]] +vertices = [ + [1456, 2968], + [1457, 2968], +] + +[[area.polygons]] +vertices = [ + [1317, 2970], + [1318, 2970], +] + +[[area.polygons]] +vertices = [ + [1441, 2970], + [1443, 2970], + [1443, 2978], + [1441, 2978], +] + +[[area.polygons]] +vertices = [ + [1318, 2971], + [1318, 2976], +] + +[[area.polygons]] +vertices = [ + [1436, 2971], + [1437, 2971], + [1437, 2972], + [1436, 2972], +] + +[[area.polygons]] +vertices = [ + [1446, 2972], + [1448, 2972], + [1448, 2980], + [1446, 2980], +] + +[[area.polygons]] +vertices = [ + [1436, 2973], + [1436, 2985], +] + +[[area.polygons]] +vertices = [ + [1444, 2977], + [1445, 2977], +] + +[[area.polygons]] +vertices = [ + [1437, 2979], + [1437, 2983], +] + +[[area.polygons]] +vertices = [ + [1332, 2981], + [1343, 2981], + [1343, 2982], + [1332, 2982], +] + +[[area.polygons]] +vertices = [ + [1446, 2981], + [1447, 2981], +] + +[[area.polygons]] +vertices = [ + [1333, 2983], + [1343, 2983], + [1343, 2984], + [1333, 2984], +] + +[[area.polygons]] +vertices = [ + [1355, 2984], + [1359, 2984], +] + +[[area.polygons]] +vertices = [[1364, 2984]] + +[[area.polygons]] +vertices = [[1368, 2984]] + +[[area.polygons]] +vertices = [ + [1335, 2985], + [1343, 2985], +] + +[[area.polygons]] +vertices = [ + [1355, 2985], + [1357, 2985], +] + +[[area.polygons]] +vertices = [ + [1373, 2985], + [1391, 2985], +] + +[[area.polygons]] +vertices = [ + [1337, 2986], + [1343, 2986], +] + +[[area.polygons]] +vertices = [ + [1355, 2986], + [1356, 2986], +] + +[[area.polygons]] +vertices = [ + [1381, 2986], + [1391, 2986], +] + +[[area.polygons]] +vertices = [[1346, 2987]] + +[[area.polygons]] +vertices = [ + [1384, 2987], + [1391, 2987], +] + +[[area.polygons]] +vertices = [ + [1385, 2988], + [1391, 2988], + [1391, 2989], + [1385, 2989], +] + +[[area.polygons]] +vertices = [[1434, 2988]] + +[[area.polygons]] +vertices = [ + [1386, 2990], + [1391, 2990], +] + +[[area.polygons]] +vertices = [ + [1387, 2991], + [1391, 2991], + [1391, 2992], + [1387, 2992], +] + +[[area.polygons]] +vertices = [ + [1426, 2991], + [1429, 2991], + [1429, 2997], + [1426, 2997], +] + +[[area.polygons]] +vertices = [ + [1388, 2993], + [1391, 2993], + [1391, 2995], + [1388, 2995], +] + +[[area.polygons]] +vertices = [[1392, 2995]] + +[[area.polygons]] +vertices = [ + [1394, 2995], + [1401, 2995], +] + +[[area.polygons]] +vertices = [ + [1430, 2995], + [1430, 2996], +] + +[[area.polygons]] +vertices = [ + [1397, 2996], + [1401, 2996], +] + +[[area.polygons]] +vertices = [ + [1398, 2997], + [1401, 2997], +] + +[[area.polygons]] +vertices = [ + [1399, 2998], + [1401, 2998], + [1401, 2999], + [1399, 2999], +] + +[[area.polygons]] +vertices = [ + [1426, 2998], + [1428, 2998], +] + +[[area.polygons]] +vertices = [[1406, 2999]] + +[[area.polygons]] +vertices = [ + [1414, 2999], + [1417, 2999], +] + +[[area.polygons]] +vertices = [ + [1426, 2999], + [1427, 2999], +] + +[[area.polygons]] +vertices = [[1426, 3000]] + +[[area.polygons]] +vertices = [ + [1422, 3001], + [1424, 3001], +] + +[[area.polygons]] +vertices = [ + [1344, 9280], + [1471, 9280], + [1471, 9343], + [1344, 9343], +] diff --git a/.data/raw-cache/map/area/alls_fairy_in_love_and_war.toml b/.data/raw-cache/map/area/alls_fairy_in_love_and_war.toml new file mode 100644 index 000000000..3c1045bee --- /dev/null +++ b/.data/raw-cache/map/area/alls_fairy_in_love_and_war.toml @@ -0,0 +1,16 @@ +# All's Fairy in Love & War music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: All's Fairy in Love & War. +# Bounds x 2304-2367, z 4416-4479; 4096 tiles as 1 rectangles. + +[[area]] +name = "All's Fairy in Love & War" +area_id = "area.alls_fairy_in_love_and_war" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2304, 4416], + [2367, 4416], + [2367, 4479], + [2304, 4479], +] diff --git a/.data/raw-cache/map/area/alone.toml b/.data/raw-cache/map/area/alone.toml new file mode 100644 index 000000000..258c333bc --- /dev/null +++ b/.data/raw-cache/map/area/alone.toml @@ -0,0 +1,16 @@ +# Alone music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Alone. +# Bounds x 2560-2623, z 9600-9663; 4096 tiles as 1 rectangles. + +[[area]] +name = "Alone" +area_id = "area.alone" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 9600], + [2623, 9600], + [2623, 9663], + [2560, 9663], +] diff --git a/.data/raw-cache/map/area/altar_ego.toml b/.data/raw-cache/map/area/altar_ego.toml new file mode 100644 index 000000000..8c5174e76 --- /dev/null +++ b/.data/raw-cache/map/area/altar_ego.toml @@ -0,0 +1,16 @@ +# Altar Ego music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Altar Ego. +# Bounds x 3008-3071, z 5568-5631; 4096 tiles as 1 rectangles. + +[[area]] +name = "Altar Ego" +area_id = "area.altar_ego" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 5568], + [3071, 5568], + [3071, 5631], + [3008, 5631], +] diff --git a/.data/raw-cache/map/area/alternative_root.toml b/.data/raw-cache/map/area/alternative_root.toml new file mode 100644 index 000000000..16458d873 --- /dev/null +++ b/.data/raw-cache/map/area/alternative_root.toml @@ -0,0 +1,16 @@ +# Alternative Root music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Alternative Root. +# Bounds x 3136-3199, z 5184-5247; 4096 tiles as 1 rectangles. + +[[area]] +name = "Alternative Root" +area_id = "area.alternative_root" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 5184], + [3199, 5184], + [3199, 5247], + [3136, 5247], +] diff --git a/.data/raw-cache/map/area/amascuts_promise.toml b/.data/raw-cache/map/area/amascuts_promise.toml new file mode 100644 index 000000000..504027f28 --- /dev/null +++ b/.data/raw-cache/map/area/amascuts_promise.toml @@ -0,0 +1,16 @@ +# Amascut's Promise music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Amascut's Promise. +# Bounds x 3776-3839, z 5120-5183; 4096 tiles as 1 rectangles. + +[[area]] +name = "Amascut's Promise" +area_id = "area.amascuts_promise" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3776, 5120], + [3839, 5120], + [3839, 5183], + [3776, 5183], +] diff --git a/.data/raw-cache/map/area/ambient_jungle.toml b/.data/raw-cache/map/area/ambient_jungle.toml new file mode 100644 index 000000000..18b2b9bd2 --- /dev/null +++ b/.data/raw-cache/map/area/ambient_jungle.toml @@ -0,0 +1,155 @@ +# Ambient Jungle music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Ambient Jungle. +# Bounds x 2816-2879, z 2944-9407; 5892 tiles as 22 rectangles. + +[[area]] +name = "Ambient Jungle" +area_id = "area.ambient_jungle" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2841, 2944], + [2870, 2944], + [2870, 2945], + [2841, 2945], +] + +[[area.polygons]] +vertices = [ + [2840, 2945], + [2840, 3006], +] + +[[area.polygons]] +vertices = [ + [2821, 2946], + [2839, 2946], + [2839, 3006], + [2821, 3006], +] + +[[area.polygons]] +vertices = [ + [2841, 2946], + [2869, 2946], +] + +[[area.polygons]] +vertices = [ + [2820, 2947], + [2820, 3005], +] + +[[area.polygons]] +vertices = [ + [2841, 2947], + [2868, 2947], + [2868, 2948], + [2841, 2948], +] + +[[area.polygons]] +vertices = [ + [2817, 2948], + [2819, 2948], + [2819, 2974], + [2817, 2974], +] + +[[area.polygons]] +vertices = [ + [2841, 2949], + [2867, 2949], + [2867, 3006], + [2841, 3006], +] + +[[area.polygons]] +vertices = [ + [2868, 2957], + [2868, 3006], +] + +[[area.polygons]] +vertices = [ + [2869, 2958], + [2870, 2958], + [2870, 3006], + [2869, 3006], +] + +[[area.polygons]] +vertices = [ + [2871, 2959], + [2871, 3006], +] + +[[area.polygons]] +vertices = [ + [2872, 2960], + [2873, 2960], + [2873, 3006], + [2872, 3006], +] + +[[area.polygons]] +vertices = [ + [2874, 2961], + [2874, 3006], +] + +[[area.polygons]] +vertices = [ + [2875, 2962], + [2875, 3006], +] + +[[area.polygons]] +vertices = [ + [2876, 2963], + [2876, 3006], +] + +[[area.polygons]] +vertices = [ + [2877, 2964], + [2877, 3006], +] + +[[area.polygons]] +vertices = [ + [2878, 2965], + [2878, 3006], +] + +[[area.polygons]] +vertices = [ + [2879, 2966], + [2879, 3006], +] + +[[area.polygons]] +vertices = [ + [2818, 2975], + [2819, 2975], + [2819, 3003], + [2818, 3003], +] + +[[area.polygons]] +vertices = [ + [2817, 2977], + [2817, 3002], +] + +[[area.polygons]] +vertices = [[2819, 3004]] + +[[area.polygons]] +vertices = [ + [2816, 9366], + [2867, 9366], + [2867, 9407], + [2816, 9407], +] diff --git a/.data/raw-cache/map/area/ape_atoll.toml b/.data/raw-cache/map/area/ape_atoll.toml new file mode 100644 index 000000000..a9c8fd81f --- /dev/null +++ b/.data/raw-cache/map/area/ape_atoll.toml @@ -0,0 +1,700 @@ +# Ape Atoll music music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Monkey Madness; Anywhere; Island Life. +# Bounds x 2689-2814, z 2689-2813; 13688 tiles as 108 rectangles. + +[[area]] +name = "Ape Atoll music" +area_id = "area.ape_atoll" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2802, 2689], + [2809, 2689], + [2809, 2695], + [2802, 2695], +] + +[[area.polygons]] +vertices = [ + [2770, 2690], + [2778, 2690], + [2778, 2810], + [2770, 2810], +] + +[[area.polygons]] +vertices = [ + [2783, 2690], + [2786, 2690], + [2786, 2809], + [2783, 2809], +] + +[[area.polygons]] +vertices = [ + [2799, 2690], + [2801, 2690], + [2801, 2811], + [2799, 2811], +] + +[[area.polygons]] +vertices = [ + [2810, 2690], + [2810, 2694], +] + +[[area.polygons]] +vertices = [ + [2751, 2691], + [2753, 2691], + [2753, 2810], + [2751, 2810], +] + +[[area.polygons]] +vertices = [ + [2760, 2691], + [2760, 2812], +] + +[[area.polygons]] +vertices = [ + [2766, 2691], + [2769, 2691], + [2769, 2811], + [2766, 2811], +] + +[[area.polygons]] +vertices = [ + [2779, 2691], + [2782, 2691], + [2782, 2809], + [2779, 2809], +] + +[[area.polygons]] +vertices = [ + [2787, 2691], + [2791, 2691], + [2791, 2810], + [2787, 2810], +] + +[[area.polygons]] +vertices = [ + [2795, 2691], + [2798, 2691], + [2798, 2811], + [2795, 2811], +] + +[[area.polygons]] +vertices = [ + [2811, 2691], + [2811, 2693], +] + +[[area.polygons]] +vertices = [ + [2727, 2692], + [2737, 2692], + [2737, 2806], + [2727, 2806], +] + +[[area.polygons]] +vertices = [ + [2754, 2692], + [2755, 2692], + [2755, 2811], + [2754, 2811], +] + +[[area.polygons]] +vertices = [ + [2758, 2692], + [2759, 2692], + [2759, 2812], + [2758, 2812], +] + +[[area.polygons]] +vertices = [ + [2761, 2692], + [2761, 2812], +] + +[[area.polygons]] +vertices = [ + [2764, 2692], + [2765, 2692], + [2765, 2811], + [2764, 2811], +] + +[[area.polygons]] +vertices = [ + [2792, 2692], + [2792, 2811], +] + +[[area.polygons]] +vertices = [ + [2794, 2692], + [2794, 2810], +] + +[[area.polygons]] +vertices = [ + [2726, 2693], + [2726, 2808], +] + +[[area.polygons]] +vertices = [ + [2738, 2693], + [2739, 2693], + [2739, 2810], + [2738, 2810], +] + +[[area.polygons]] +vertices = [ + [2750, 2693], + [2750, 2812], +] + +[[area.polygons]] +vertices = [ + [2756, 2693], + [2757, 2693], + [2757, 2811], + [2756, 2811], +] + +[[area.polygons]] +vertices = [ + [2762, 2693], + [2763, 2693], + [2763, 2812], + [2762, 2812], +] + +[[area.polygons]] +vertices = [ + [2793, 2693], + [2793, 2811], +] + +[[area.polygons]] +vertices = [ + [2740, 2694], + [2740, 2810], +] + +[[area.polygons]] +vertices = [ + [2749, 2694], + [2749, 2812], +] + +[[area.polygons]] +vertices = [ + [2711, 2695], + [2712, 2695], + [2712, 2805], + [2711, 2805], +] + +[[area.polygons]] +vertices = [[2725, 2695]] + +[[area.polygons]] +vertices = [ + [2745, 2695], + [2748, 2695], + [2748, 2812], + [2745, 2812], +] + +[[area.polygons]] +vertices = [ + [2709, 2696], + [2710, 2696], + [2710, 2804], + [2709, 2804], +] + +[[area.polygons]] +vertices = [ + [2713, 2696], + [2715, 2696], + [2715, 2805], + [2713, 2805], +] + +[[area.polygons]] +vertices = [ + [2741, 2696], + [2744, 2696], + [2744, 2810], + [2741, 2810], +] + +[[area.polygons]] +vertices = [ + [2802, 2696], + [2808, 2696], +] + +[[area.polygons]] +vertices = [ + [2708, 2697], + [2708, 2804], +] + +[[area.polygons]] +vertices = [ + [2716, 2697], + [2717, 2697], + [2717, 2805], + [2716, 2805], +] + +[[area.polygons]] +vertices = [ + [2802, 2697], + [2805, 2697], +] + +[[area.polygons]] +vertices = [ + [2718, 2698], + [2718, 2806], +] + +[[area.polygons]] +vertices = [ + [2802, 2698], + [2804, 2698], + [2804, 2699], + [2802, 2699], +] + +[[area.polygons]] +vertices = [ + [2707, 2699], + [2707, 2803], +] + +[[area.polygons]] +vertices = [ + [2719, 2699], + [2720, 2699], + [2720, 2806], + [2719, 2806], +] + +[[area.polygons]] +vertices = [ + [2706, 2700], + [2706, 2803], +] + +[[area.polygons]] +vertices = [ + [2721, 2700], + [2725, 2700], + [2725, 2807], + [2721, 2807], +] + +[[area.polygons]] +vertices = [ + [2802, 2700], + [2803, 2700], + [2803, 2702], + [2802, 2702], +] + +[[area.polygons]] +vertices = [ + [2705, 2701], + [2705, 2802], +] + +[[area.polygons]] +vertices = [ + [2702, 2702], + [2704, 2702], + [2704, 2801], + [2702, 2801], +] + +[[area.polygons]] +vertices = [ + [2698, 2703], + [2701, 2703], + [2701, 2796], + [2698, 2796], +] + +[[area.polygons]] +vertices = [ + [2802, 2703], + [2802, 2811], +] + +[[area.polygons]] +vertices = [ + [2696, 2704], + [2697, 2704], + [2697, 2792], + [2696, 2792], +] + +[[area.polygons]] +vertices = [ + [2808, 2704], + [2812, 2704], + [2812, 2720], + [2808, 2720], +] + +[[area.polygons]] +vertices = [ + [2695, 2705], + [2695, 2722], +] + +[[area.polygons]] +vertices = [ + [2807, 2705], + [2807, 2725], +] + +[[area.polygons]] +vertices = [ + [2813, 2705], + [2813, 2713], +] + +[[area.polygons]] +vertices = [ + [2803, 2706], + [2806, 2706], + [2806, 2725], + [2803, 2725], +] + +[[area.polygons]] +vertices = [ + [2694, 2707], + [2694, 2716], +] + +[[area.polygons]] +vertices = [ + [2693, 2709], + [2693, 2711], +] + +[[area.polygons]] +vertices = [ + [2808, 2721], + [2811, 2721], + [2811, 2722], + [2808, 2722], +] + +[[area.polygons]] +vertices = [ + [2808, 2723], + [2810, 2723], +] + +[[area.polygons]] +vertices = [ + [2808, 2724], + [2809, 2724], +] + +[[area.polygons]] +vertices = [ + [2695, 2725], + [2695, 2791], +] + +[[area.polygons]] +vertices = [ + [2803, 2726], + [2804, 2726], +] + +[[area.polygons]] +vertices = [ + [2694, 2727], + [2694, 2732], +] + +[[area.polygons]] +vertices = [ + [2803, 2727], + [2803, 2811], +] + +[[area.polygons]] +vertices = [ + [2693, 2728], + [2693, 2730], +] + +[[area.polygons]] +vertices = [ + [2804, 2730], + [2805, 2730], + [2805, 2811], + [2804, 2811], +] + +[[area.polygons]] +vertices = [ + [2806, 2731], + [2806, 2811], +] + +[[area.polygons]] +vertices = [ + [2807, 2732], + [2808, 2732], + [2808, 2809], + [2807, 2809], +] + +[[area.polygons]] +vertices = [ + [2809, 2733], + [2809, 2747], +] + +[[area.polygons]] +vertices = [ + [2810, 2734], + [2810, 2739], +] + +[[area.polygons]] +vertices = [ + [2811, 2735], + [2811, 2737], +] + +[[area.polygons]] +vertices = [ + [2694, 2736], + [2694, 2789], +] + +[[area.polygons]] +vertices = [ + [2693, 2741], + [2693, 2787], +] + +[[area.polygons]] +vertices = [ + [2692, 2744], + [2692, 2785], +] + +[[area.polygons]] +vertices = [ + [2691, 2745], + [2691, 2764], +] + +[[area.polygons]] +vertices = [ + [2690, 2747], + [2690, 2758], +] + +[[area.polygons]] +vertices = [ + [2809, 2750], + [2809, 2809], +] + +[[area.polygons]] +vertices = [ + [2810, 2752], + [2811, 2752], + [2811, 2765], + [2810, 2765], +] + +[[area.polygons]] +vertices = [[2689, 2753]] + +[[area.polygons]] +vertices = [ + [2812, 2753], + [2812, 2762], +] + +[[area.polygons]] +vertices = [ + [2813, 2754], + [2813, 2760], +] + +[[area.polygons]] +vertices = [[2810, 2766]] + +[[area.polygons]] +vertices = [ + [2691, 2768], + [2691, 2781], +] + +[[area.polygons]] +vertices = [ + [2810, 2773], + [2810, 2808], +] + +[[area.polygons]] +vertices = [ + [2811, 2774], + [2811, 2807], +] + +[[area.polygons]] +vertices = [ + [2812, 2780], + [2812, 2806], +] + +[[area.polygons]] +vertices = [ + [2813, 2782], + [2813, 2804], +] + +[[area.polygons]] +vertices = [ + [2814, 2790], + [2814, 2795], +] + +[[area.polygons]] +vertices = [ + [2697, 2793], + [2697, 2794], +] + +[[area.polygons]] +vertices = [ + [2699, 2797], + [2701, 2797], + [2701, 2798], + [2699, 2798], +] + +[[area.polygons]] +vertices = [ + [2700, 2799], + [2701, 2799], +] + +[[area.polygons]] +vertices = [[2701, 2800]] + +[[area.polygons]] +vertices = [[2710, 2805]] + +[[area.polygons]] +vertices = [[2717, 2806]] + +[[area.polygons]] +vertices = [[2727, 2807]] + +[[area.polygons]] +vertices = [ + [2730, 2807], + [2737, 2807], +] + +[[area.polygons]] +vertices = [ + [2724, 2808], + [2725, 2808], +] + +[[area.polygons]] +vertices = [ + [2731, 2808], + [2737, 2808], + [2737, 2810], + [2731, 2810], +] + +[[area.polygons]] +vertices = [[2730, 2810]] + +[[area.polygons]] +vertices = [ + [2779, 2810], + [2780, 2810], +] + +[[area.polygons]] +vertices = [[2807, 2810]] + +[[area.polygons]] +vertices = [ + [2742, 2811], + [2744, 2811], +] + +[[area.polygons]] +vertices = [ + [2751, 2811], + [2752, 2811], +] + +[[area.polygons]] +vertices = [ + [2770, 2811], + [2774, 2811], +] + +[[area.polygons]] +vertices = [ + [2777, 2811], + [2778, 2811], +] + +[[area.polygons]] +vertices = [ + [2789, 2811], + [2791, 2811], +] + +[[area.polygons]] +vertices = [[2764, 2812]] + +[[area.polygons]] +vertices = [ + [2769, 2812], + [2772, 2812], +] + +[[area.polygons]] +vertices = [ + [2770, 2813], + [2771, 2813], +] diff --git a/.data/raw-cache/map/area/ape_ex_predator.toml b/.data/raw-cache/map/area/ape_ex_predator.toml new file mode 100644 index 000000000..8f10c331e --- /dev/null +++ b/.data/raw-cache/map/area/ape_ex_predator.toml @@ -0,0 +1,16 @@ +# Ape-ex Predator music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Ape-ex Predator. +# Bounds x 3776-3839, z 5376-5439; 4096 tiles as 1 rectangles. + +[[area]] +name = "Ape-ex Predator" +area_id = "area.ape_ex_predator" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3776, 5376], + [3839, 5376], + [3839, 5439], + [3776, 5439], +] diff --git a/.data/raw-cache/map/area/arachnids_of_vampyrium.toml b/.data/raw-cache/map/area/arachnids_of_vampyrium.toml new file mode 100644 index 000000000..a7f0e2d53 --- /dev/null +++ b/.data/raw-cache/map/area/arachnids_of_vampyrium.toml @@ -0,0 +1,16 @@ +# Arachnids of Vampyrium music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Arachnids of Vampyrium. +# Bounds x 3264-3327, z 4224-4287; 4096 tiles as 1 rectangles. + +[[area]] +name = "Arachnids of Vampyrium" +area_id = "area.arachnids_of_vampyrium" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 4224], + [3327, 4224], + [3327, 4287], + [3264, 4287], +] diff --git a/.data/raw-cache/map/area/arboretum.toml b/.data/raw-cache/map/area/arboretum.toml new file mode 100644 index 000000000..0afe3df8a --- /dev/null +++ b/.data/raw-cache/map/area/arboretum.toml @@ -0,0 +1,78 @@ +# Arboretum music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Arboretum. +# Bounds x 3615-3636, z 3354-3369; 242 tiles as 11 rectangles. + +[[area]] +name = "Arboretum" +area_id = "area.arboretum" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3625, 3354], + [3631, 3354], + [3631, 3369], + [3625, 3369], +] + +[[area.polygons]] +vertices = [ + [3624, 3355], + [3624, 3368], +] + +[[area.polygons]] +vertices = [ + [3632, 3355], + [3632, 3368], +] + +[[area.polygons]] +vertices = [ + [3623, 3356], + [3623, 3367], +] + +[[area.polygons]] +vertices = [ + [3633, 3356], + [3633, 3367], +] + +[[area.polygons]] +vertices = [ + [3622, 3357], + [3622, 3366], +] + +[[area.polygons]] +vertices = [ + [3634, 3357], + [3634, 3366], +] + +[[area.polygons]] +vertices = [ + [3621, 3358], + [3621, 3365], +] + +[[area.polygons]] +vertices = [ + [3635, 3358], + [3635, 3365], +] + +[[area.polygons]] +vertices = [ + [3615, 3359], + [3620, 3359], + [3620, 3364], + [3615, 3364], +] + +[[area.polygons]] +vertices = [ + [3636, 3359], + [3636, 3364], +] diff --git a/.data/raw-cache/map/area/arcane.toml b/.data/raw-cache/map/area/arcane.toml new file mode 100644 index 000000000..6a71c3ac4 --- /dev/null +++ b/.data/raw-cache/map/area/arcane.toml @@ -0,0 +1,653 @@ +# Arcane music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Arcane. +# Bounds x 1536-1741, z 3707-10239; 21172 tiles as 103 rectangles. + +[[area]] +name = "Arcane" +area_id = "area.arcane" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1679, 3707], + [1689, 3707], + [1689, 3841], + [1679, 3841], +] + +[[area.polygons]] +vertices = [ + [1678, 3708], + [1678, 3851], +] + +[[area.polygons]] +vertices = [ + [1690, 3708], + [1701, 3708], + [1701, 3823], + [1690, 3823], +] + +[[area.polygons]] +vertices = [ + [1715, 3708], + [1733, 3708], + [1733, 3807], + [1715, 3807], +] + +[[area.polygons]] +vertices = [ + [1677, 3709], + [1677, 3852], +] + +[[area.polygons]] +vertices = [ + [1702, 3709], + [1702, 3822], +] + +[[area.polygons]] +vertices = [ + [1734, 3709], + [1734, 3806], +] + +[[area.polygons]] +vertices = [ + [1657, 3710], + [1676, 3710], + [1676, 3852], + [1657, 3852], +] + +[[area.polygons]] +vertices = [ + [1703, 3710], + [1703, 3822], +] + +[[area.polygons]] +vertices = [ + [1641, 3711], + [1656, 3711], + [1656, 3855], + [1641, 3855], +] + +[[area.polygons]] +vertices = [ + [1704, 3711], + [1714, 3711], + [1714, 3818], + [1704, 3818], +] + +[[area.polygons]] +vertices = [ + [1640, 3712], + [1640, 3855], +] + +[[area.polygons]] +vertices = [ + [1639, 3713], + [1639, 3855], +] + +[[area.polygons]] +vertices = [ + [1638, 3714], + [1638, 3855], +] + +[[area.polygons]] +vertices = [ + [1637, 3715], + [1637, 3855], +] + +[[area.polygons]] +vertices = [ + [1636, 3716], + [1636, 3855], +] + +[[area.polygons]] +vertices = [ + [1625, 3718], + [1635, 3718], + [1635, 3855], + [1625, 3855], +] + +[[area.polygons]] +vertices = [ + [1624, 3722], + [1624, 3855], +] + +[[area.polygons]] +vertices = [ + [1623, 3723], + [1623, 3855], +] + +[[area.polygons]] +vertices = [ + [1622, 3724], + [1622, 3854], +] + +[[area.polygons]] +vertices = [ + [1735, 3724], + [1741, 3724], + [1741, 3780], + [1735, 3780], +] + +[[area.polygons]] +vertices = [ + [1621, 3725], + [1621, 3853], +] + +[[area.polygons]] +vertices = [ + [1620, 3729], + [1620, 3852], +] + +[[area.polygons]] +vertices = [ + [1619, 3730], + [1619, 3851], +] + +[[area.polygons]] +vertices = [ + [1618, 3731], + [1618, 3850], +] + +[[area.polygons]] +vertices = [ + [1617, 3756], + [1617, 3849], +] + +[[area.polygons]] +vertices = [ + [1616, 3757], + [1616, 3848], +] + +[[area.polygons]] +vertices = [ + [1615, 3758], + [1615, 3849], +] + +[[area.polygons]] +vertices = [ + [1614, 3759], + [1614, 3850], +] + +[[area.polygons]] +vertices = [ + [1613, 3763], + [1613, 3851], +] + +[[area.polygons]] +vertices = [ + [1612, 3764], + [1612, 3851], +] + +[[area.polygons]] +vertices = [ + [1609, 3765], + [1611, 3765], + [1611, 3851], + [1609, 3851], +] + +[[area.polygons]] +vertices = [ + [1608, 3766], + [1608, 3851], +] + +[[area.polygons]] +vertices = [ + [1607, 3767], + [1607, 3851], +] + +[[area.polygons]] +vertices = [ + [1606, 3768], + [1606, 3851], +] + +[[area.polygons]] +vertices = [ + [1605, 3769], + [1605, 3851], +] + +[[area.polygons]] +vertices = [ + [1599, 3770], + [1604, 3770], + [1604, 3852], + [1599, 3852], +] + +[[area.polygons]] +vertices = [ + [1598, 3771], + [1598, 3875], +] + +[[area.polygons]] +vertices = [ + [1597, 3774], + [1597, 3775], +] + +[[area.polygons]] +vertices = [ + [1595, 3779], + [1597, 3779], + [1597, 3875], + [1595, 3875], +] + +[[area.polygons]] +vertices = [ + [1594, 3780], + [1594, 3859], +] + +[[area.polygons]] +vertices = [ + [1592, 3781], + [1593, 3781], + [1593, 3859], + [1592, 3859], +] + +[[area.polygons]] +vertices = [ + [1735, 3781], + [1735, 3792], +] + +[[area.polygons]] +vertices = [ + [1590, 3782], + [1591, 3782], + [1591, 3859], + [1590, 3859], +] + +[[area.polygons]] +vertices = [ + [1588, 3783], + [1589, 3783], + [1589, 3859], + [1588, 3859], +] + +[[area.polygons]] +vertices = [ + [1587, 3784], + [1587, 3859], +] + +[[area.polygons]] +vertices = [ + [1586, 3785], + [1586, 3859], +] + +[[area.polygons]] +vertices = [ + [1736, 3785], + [1736, 3789], +] + +[[area.polygons]] +vertices = [ + [1585, 3797], + [1585, 3859], +] + +[[area.polygons]] +vertices = [ + [1584, 3798], + [1584, 3859], +] + +[[area.polygons]] +vertices = [ + [1574, 3799], + [1583, 3799], + [1583, 3859], + [1574, 3859], +] + +[[area.polygons]] +vertices = [ + [1573, 3800], + [1573, 3859], +] + +[[area.polygons]] +vertices = [ + [1572, 3801], + [1572, 3859], +] + +[[area.polygons]] +vertices = [ + [1571, 3802], + [1571, 3859], +] + +[[area.polygons]] +vertices = [ + [1570, 3803], + [1570, 3859], +] + +[[area.polygons]] +vertices = [ + [1569, 3804], + [1569, 3859], +] + +[[area.polygons]] +vertices = [ + [1568, 3805], + [1568, 3859], +] + +[[area.polygons]] +vertices = [ + [1567, 3806], + [1567, 3859], +] + +[[area.polygons]] +vertices = [ + [1566, 3807], + [1566, 3859], +] + +[[area.polygons]] +vertices = [ + [1565, 3808], + [1565, 3859], +] + +[[area.polygons]] +vertices = [ + [1715, 3808], + [1732, 3808], +] + +[[area.polygons]] +vertices = [ + [1564, 3809], + [1564, 3859], +] + +[[area.polygons]] +vertices = [ + [1715, 3809], + [1731, 3809], +] + +[[area.polygons]] +vertices = [ + [1563, 3810], + [1563, 3859], +] + +[[area.polygons]] +vertices = [ + [1715, 3810], + [1730, 3810], +] + +[[area.polygons]] +vertices = [ + [1562, 3811], + [1562, 3859], +] + +[[area.polygons]] +vertices = [ + [1715, 3811], + [1729, 3811], +] + +[[area.polygons]] +vertices = [ + [1715, 3812], + [1728, 3812], +] + +[[area.polygons]] +vertices = [ + [1715, 3813], + [1727, 3813], +] + +[[area.polygons]] +vertices = [ + [1715, 3814], + [1726, 3814], +] + +[[area.polygons]] +vertices = [ + [1715, 3815], + [1723, 3815], +] + +[[area.polygons]] +vertices = [ + [1715, 3816], + [1720, 3816], +] + +[[area.polygons]] +vertices = [ + [1715, 3817], + [1718, 3817], +] + +[[area.polygons]] +vertices = [[1715, 3818]] + +[[area.polygons]] +vertices = [ + [1704, 3819], + [1712, 3819], +] + +[[area.polygons]] +vertices = [ + [1704, 3820], + [1709, 3820], +] + +[[area.polygons]] +vertices = [ + [1704, 3821], + [1707, 3821], +] + +[[area.polygons]] +vertices = [[1704, 3822]] + +[[area.polygons]] +vertices = [ + [1690, 3824], + [1700, 3824], + [1700, 3841], + [1690, 3841], +] + +[[area.polygons]] +vertices = [ + [1561, 3829], + [1561, 3859], +] + +[[area.polygons]] +vertices = [ + [1560, 3830], + [1560, 3859], +] + +[[area.polygons]] +vertices = [ + [1559, 3831], + [1559, 3859], +] + +[[area.polygons]] +vertices = [ + [1558, 3832], + [1558, 3859], +] + +[[area.polygons]] +vertices = [ + [1557, 3833], + [1557, 3859], +] + +[[area.polygons]] +vertices = [ + [1556, 3834], + [1556, 3859], +] + +[[area.polygons]] +vertices = [ + [1555, 3835], + [1555, 3859], +] + +[[area.polygons]] +vertices = [ + [1679, 3842], + [1688, 3842], +] + +[[area.polygons]] +vertices = [ + [1698, 3842], + [1700, 3842], +] + +[[area.polygons]] +vertices = [ + [1679, 3843], + [1687, 3843], +] + +[[area.polygons]] +vertices = [[1700, 3843]] + +[[area.polygons]] +vertices = [ + [1679, 3844], + [1686, 3844], +] + +[[area.polygons]] +vertices = [ + [1679, 3845], + [1685, 3845], +] + +[[area.polygons]] +vertices = [ + [1679, 3846], + [1684, 3846], +] + +[[area.polygons]] +vertices = [ + [1679, 3847], + [1683, 3847], +] + +[[area.polygons]] +vertices = [ + [1679, 3848], + [1682, 3848], +] + +[[area.polygons]] +vertices = [ + [1679, 3849], + [1681, 3849], +] + +[[area.polygons]] +vertices = [ + [1679, 3850], + [1680, 3850], +] + +[[area.polygons]] +vertices = [ + [1599, 3853], + [1603, 3853], +] + +[[area.polygons]] +vertices = [ + [1657, 3853], + [1668, 3853], +] + +[[area.polygons]] +vertices = [ + [1599, 3854], + [1602, 3854], + [1602, 3875], + [1599, 3875], +] + +[[area.polygons]] +vertices = [ + [1657, 3854], + [1667, 3854], +] + +[[area.polygons]] +vertices = [ + [1657, 3855], + [1666, 3855], +] + +[[area.polygons]] +vertices = [ + [1536, 10208], + [1567, 10208], + [1567, 10239], + [1536, 10239], +] diff --git a/.data/raw-cache/map/area/ardougne_sewers.toml b/.data/raw-cache/map/area/ardougne_sewers.toml new file mode 100644 index 000000000..0d56c4346 --- /dev/null +++ b/.data/raw-cache/map/area/ardougne_sewers.toml @@ -0,0 +1,105 @@ +# Both halves of the sewers, Elena's prison and the Carnillean mansion cellar. +# +# Regenerated as rectangles from the OSRS wiki music map: 4265 tiles moved to +# neighbouring music areas and 4096 tiles were added. + +[[area]] +name = "Ardougne Sewers" +area_id = "area.ardougne_sewers" +levels = [0] + +[[area.polygons]] +vertices = [ + [2496, 9600], + [2559, 9600], + [2559, 9693], + [2496, 9693], +] + +[[area.polygons]] +vertices = [ + [2560, 9664], + [2623, 9664], + [2623, 9703], + [2560, 9703], +] + +[[area.polygons]] +vertices = [ + [2496, 9694], + [2537, 9694], + [2537, 9727], + [2496, 9727], +] + +[[area.polygons]] +vertices = [ + [2549, 9694], + [2559, 9694], + [2559, 9727], + [2549, 9727], +] + +[[area.polygons]] +vertices = [ + [2542, 9698], + [2548, 9698], + [2548, 9727], + [2542, 9727], +] + +[[area.polygons]] +vertices = [ + [2538, 9700], + [2541, 9700], + [2541, 9727], + [2538, 9727], +] + +[[area.polygons]] +vertices = [ + [2560, 9704], + [2568, 9704], + [2568, 9706], + [2560, 9706], +] + +[[area.polygons]] +vertices = [ + [2577, 9704], + [2623, 9704], + [2623, 9727], + [2577, 9727], +] + +[[area.polygons]] +vertices = [ + [2560, 9707], + [2563, 9707], + [2563, 9727], + [2560, 9727], +] + +[[area.polygons]] +vertices = [ + [2566, 9710], + [2568, 9710], + [2568, 9727], + [2566, 9727], +] + +[[area.polygons]] +vertices = [ + [2564, 9713], + [2565, 9713], + [2565, 9727], + [2564, 9727], +] + +[[area.polygons]] +vertices = [ + [2569, 9716], + [2576, 9716], + [2576, 9727], + [2569, 9727], +] diff --git a/.data/raw-cache/map/area/are_you_not_entertained.toml b/.data/raw-cache/map/area/are_you_not_entertained.toml new file mode 100644 index 000000000..beebfc284 --- /dev/null +++ b/.data/raw-cache/map/area/are_you_not_entertained.toml @@ -0,0 +1,176 @@ +# Are You Not Entertained? music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Are You Not Entertained?. +# Bounds x 1807-1841, z 3089-3124; 1038 tiles as 25 rectangles. + +[[area]] +name = "Are You Not Entertained?" +area_id = "area.are_you_not_entertained" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1819, 3089], + [1822, 3089], + [1822, 3124], + [1819, 3124], +] + +[[area.polygons]] +vertices = [ + [1827, 3089], + [1830, 3089], + [1830, 3123], + [1827, 3123], +] + +[[area.polygons]] +vertices = [ + [1815, 3090], + [1818, 3090], + [1818, 3122], + [1815, 3122], +] + +[[area.polygons]] +vertices = [ + [1831, 3090], + [1833, 3090], + [1833, 3123], + [1831, 3123], +] + +[[area.polygons]] +vertices = [ + [1814, 3091], + [1814, 3122], +] + +[[area.polygons]] +vertices = [ + [1823, 3091], + [1826, 3091], + [1826, 3122], + [1823, 3122], +] + +[[area.polygons]] +vertices = [ + [1834, 3091], + [1835, 3091], + [1835, 3122], + [1834, 3122], +] + +[[area.polygons]] +vertices = [ + [1812, 3092], + [1813, 3092], + [1813, 3121], + [1812, 3121], +] + +[[area.polygons]] +vertices = [ + [1836, 3092], + [1836, 3121], +] + +[[area.polygons]] +vertices = [ + [1811, 3093], + [1811, 3120], +] + +[[area.polygons]] +vertices = [ + [1837, 3093], + [1837, 3120], +] + +[[area.polygons]] +vertices = [ + [1810, 3094], + [1810, 3119], +] + +[[area.polygons]] +vertices = [ + [1838, 3094], + [1838, 3119], +] + +[[area.polygons]] +vertices = [ + [1839, 3095], + [1839, 3118], +] + +[[area.polygons]] +vertices = [ + [1809, 3096], + [1809, 3117], +] + +[[area.polygons]] +vertices = [ + [1840, 3096], + [1840, 3102], +] + +[[area.polygons]] +vertices = [ + [1808, 3098], + [1808, 3104], +] + +[[area.polygons]] +vertices = [ + [1841, 3098], + [1841, 3102], +] + +[[area.polygons]] +vertices = [ + [1807, 3101], + [1807, 3104], +] + +[[area.polygons]] +vertices = [ + [1807, 3109], + [1808, 3109], + [1808, 3112], + [1807, 3112], +] + +[[area.polygons]] +vertices = [ + [1840, 3111], + [1841, 3111], + [1841, 3115], + [1840, 3115], +] + +[[area.polygons]] +vertices = [ + [1808, 3113], + [1808, 3115], +] + +[[area.polygons]] +vertices = [ + [1840, 3116], + [1840, 3117], +] + +[[area.polygons]] +vertices = [ + [1816, 3123], + [1818, 3123], +] + +[[area.polygons]] +vertices = [ + [1827, 3124], + [1829, 3124], +] diff --git a/.data/raw-cache/map/area/armadyl_alliance.toml b/.data/raw-cache/map/area/armadyl_alliance.toml new file mode 100644 index 000000000..56fbc3027 --- /dev/null +++ b/.data/raw-cache/map/area/armadyl_alliance.toml @@ -0,0 +1,32 @@ +# Armadyl Alliance music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Armadyl Alliance. +# Bounds x 2816-2879, z 5248-5311; 3136 tiles as 3 rectangles. + +[[area]] +name = "Armadyl Alliance" +area_id = "area.armadyl_alliance" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 5248], + [2879, 5248], + [2879, 5279], + [2816, 5279], +] + +[[area.polygons]] +vertices = [ + [2816, 5280], + [2855, 5280], + [2855, 5287], + [2816, 5287], +] + +[[area.polygons]] +vertices = [ + [2816, 5288], + [2847, 5288], + [2847, 5311], + [2816, 5311], +] diff --git a/.data/raw-cache/map/area/armageddon.toml b/.data/raw-cache/map/area/armageddon.toml new file mode 100644 index 000000000..c5e5d3362 --- /dev/null +++ b/.data/raw-cache/map/area/armageddon.toml @@ -0,0 +1,40 @@ +# Armageddon music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Armageddon. +# Bounds x 2848-3071, z 5280-10175; 7936 tiles as 4 rectangles. + +[[area]] +name = "Armageddon" +area_id = "area.armageddon" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2856, 5280], + [2903, 5280], + [2903, 5343], + [2856, 5343], +] + +[[area.polygons]] +vertices = [ + [2848, 5288], + [2855, 5288], + [2855, 5335], + [2848, 5335], +] + +[[area.polygons]] +vertices = [ + [2904, 5288], + [2911, 5288], + [2911, 5335], + [2904, 5335], +] + +[[area.polygons]] +vertices = [ + [3008, 10112], + [3071, 10112], + [3071, 10175], + [3008, 10175], +] diff --git a/.data/raw-cache/map/area/artistry.toml b/.data/raw-cache/map/area/artistry.toml new file mode 100644 index 000000000..07eea1b4f --- /dev/null +++ b/.data/raw-cache/map/area/artistry.toml @@ -0,0 +1,16 @@ +# Artistry music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Artistry. +# Bounds x 1984-2047, z 4736-4799; 4096 tiles as 1 rectangles. + +[[area]] +name = "Artistry" +area_id = "area.artistry" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1984, 4736], + [2047, 4736], + [2047, 4799], + [1984, 4799], +] diff --git a/.data/raw-cache/map/area/ascent.toml b/.data/raw-cache/map/area/ascent.toml new file mode 100644 index 000000000..294dd5492 --- /dev/null +++ b/.data/raw-cache/map/area/ascent.toml @@ -0,0 +1,635 @@ +# Ascent music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Ascent. +# Bounds x 1194-1302, z 3477-3567; 6015 tiles as 101 rectangles. + +[[area]] +name = "Ascent" +area_id = "area.ascent" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1226, 3477], + [1226, 3510], +] + +[[area.polygons]] +vertices = [ + [1225, 3478], + [1225, 3510], +] + +[[area.polygons]] +vertices = [ + [1227, 3478], + [1227, 3510], +] + +[[area.polygons]] +vertices = [ + [1224, 3479], + [1224, 3510], +] + +[[area.polygons]] +vertices = [ + [1228, 3479], + [1228, 3511], +] + +[[area.polygons]] +vertices = [ + [1223, 3480], + [1223, 3510], +] + +[[area.polygons]] +vertices = [ + [1229, 3480], + [1229, 3512], +] + +[[area.polygons]] +vertices = [ + [1222, 3481], + [1222, 3510], +] + +[[area.polygons]] +vertices = [ + [1230, 3481], + [1230, 3513], +] + +[[area.polygons]] +vertices = [ + [1221, 3482], + [1221, 3510], +] + +[[area.polygons]] +vertices = [ + [1231, 3482], + [1231, 3544], +] + +[[area.polygons]] +vertices = [ + [1212, 3483], + [1220, 3483], + [1220, 3494], + [1212, 3494], +] + +[[area.polygons]] +vertices = [ + [1232, 3483], + [1232, 3544], +] + +[[area.polygons]] +vertices = [ + [1211, 3484], + [1211, 3494], +] + +[[area.polygons]] +vertices = [ + [1233, 3484], + [1233, 3544], +] + +[[area.polygons]] +vertices = [ + [1210, 3485], + [1210, 3494], +] + +[[area.polygons]] +vertices = [ + [1234, 3485], + [1234, 3544], +] + +[[area.polygons]] +vertices = [ + [1209, 3486], + [1209, 3494], +] + +[[area.polygons]] +vertices = [ + [1235, 3486], + [1235, 3544], +] + +[[area.polygons]] +vertices = [ + [1208, 3487], + [1208, 3494], +] + +[[area.polygons]] +vertices = [ + [1236, 3487], + [1236, 3544], +] + +[[area.polygons]] +vertices = [ + [1244, 3487], + [1254, 3487], + [1254, 3547], + [1244, 3547], +] + +[[area.polygons]] +vertices = [ + [1207, 3488], + [1207, 3494], +] + +[[area.polygons]] +vertices = [ + [1237, 3488], + [1237, 3544], +] + +[[area.polygons]] +vertices = [ + [1243, 3488], + [1243, 3546], +] + +[[area.polygons]] +vertices = [ + [1255, 3488], + [1257, 3488], + [1257, 3549], + [1255, 3549], +] + +[[area.polygons]] +vertices = [ + [1206, 3489], + [1206, 3494], +] + +[[area.polygons]] +vertices = [ + [1238, 3489], + [1238, 3544], +] + +[[area.polygons]] +vertices = [ + [1242, 3489], + [1242, 3545], +] + +[[area.polygons]] +vertices = [ + [1258, 3489], + [1259, 3489], + [1259, 3549], + [1258, 3549], +] + +[[area.polygons]] +vertices = [ + [1267, 3489], + [1284, 3489], + [1284, 3563], + [1267, 3563], +] + +[[area.polygons]] +vertices = [ + [1205, 3490], + [1205, 3494], +] + +[[area.polygons]] +vertices = [ + [1239, 3490], + [1239, 3544], +] + +[[area.polygons]] +vertices = [ + [1241, 3490], + [1241, 3544], +] + +[[area.polygons]] +vertices = [ + [1260, 3490], + [1266, 3490], + [1266, 3551], + [1260, 3551], +] + +[[area.polygons]] +vertices = [ + [1240, 3491], + [1240, 3544], +] + +[[area.polygons]] +vertices = [ + [1214, 3495], + [1220, 3495], + [1220, 3510], + [1214, 3510], +] + +[[area.polygons]] +vertices = [ + [1285, 3497], + [1285, 3567], +] + +[[area.polygons]] +vertices = [ + [1286, 3498], + [1286, 3567], +] + +[[area.polygons]] +vertices = [ + [1287, 3499], + [1287, 3567], +] + +[[area.polygons]] +vertices = [ + [1288, 3500], + [1293, 3500], + [1293, 3515], + [1288, 3515], +] + +[[area.polygons]] +vertices = [ + [1294, 3501], + [1294, 3515], +] + +[[area.polygons]] +vertices = [ + [1295, 3502], + [1295, 3515], +] + +[[area.polygons]] +vertices = [ + [1296, 3503], + [1296, 3515], +] + +[[area.polygons]] +vertices = [ + [1207, 3504], + [1213, 3504], + [1213, 3507], + [1207, 3507], +] + +[[area.polygons]] +vertices = [ + [1297, 3504], + [1297, 3515], +] + +[[area.polygons]] +vertices = [ + [1206, 3505], + [1206, 3506], +] + +[[area.polygons]] +vertices = [ + [1298, 3505], + [1298, 3515], +] + +[[area.polygons]] +vertices = [ + [1299, 3506], + [1299, 3515], +] + +[[area.polygons]] +vertices = [ + [1300, 3507], + [1300, 3515], +] + +[[area.polygons]] +vertices = [ + [1208, 3508], + [1213, 3508], +] + +[[area.polygons]] +vertices = [ + [1301, 3508], + [1301, 3515], +] + +[[area.polygons]] +vertices = [ + [1209, 3509], + [1213, 3509], +] + +[[area.polygons]] +vertices = [ + [1302, 3509], + [1302, 3515], +] + +[[area.polygons]] +vertices = [ + [1210, 3510], + [1213, 3510], +] + +[[area.polygons]] +vertices = [ + [1230, 3516], + [1230, 3544], +] + +[[area.polygons]] +vertices = [ + [1288, 3516], + [1289, 3516], + [1289, 3567], + [1288, 3567], +] + +[[area.polygons]] +vertices = [ + [1229, 3517], + [1229, 3544], +] + +[[area.polygons]] +vertices = [ + [1228, 3518], + [1228, 3544], +] + +[[area.polygons]] +vertices = [ + [1227, 3519], + [1227, 3545], +] + +[[area.polygons]] +vertices = [ + [1226, 3520], + [1226, 3546], +] + +[[area.polygons]] +vertices = [ + [1225, 3521], + [1225, 3547], +] + +[[area.polygons]] +vertices = [ + [1213, 3522], + [1224, 3522], + [1224, 3548], + [1213, 3548], +] + +[[area.polygons]] +vertices = [ + [1212, 3523], + [1212, 3567], +] + +[[area.polygons]] +vertices = [ + [1211, 3524], + [1211, 3567], +] + +[[area.polygons]] +vertices = [ + [1210, 3525], + [1210, 3567], +] + +[[area.polygons]] +vertices = [ + [1209, 3526], + [1209, 3567], +] + +[[area.polygons]] +vertices = [ + [1208, 3527], + [1208, 3567], +] + +[[area.polygons]] +vertices = [ + [1207, 3528], + [1207, 3567], +] + +[[area.polygons]] +vertices = [ + [1206, 3529], + [1206, 3567], +] + +[[area.polygons]] +vertices = [ + [1205, 3530], + [1205, 3567], +] + +[[area.polygons]] +vertices = [ + [1204, 3531], + [1204, 3567], +] + +[[area.polygons]] +vertices = [ + [1203, 3532], + [1203, 3567], +] + +[[area.polygons]] +vertices = [ + [1202, 3533], + [1202, 3567], +] + +[[area.polygons]] +vertices = [ + [1201, 3534], + [1201, 3567], +] + +[[area.polygons]] +vertices = [ + [1200, 3535], + [1200, 3567], +] + +[[area.polygons]] +vertices = [ + [1199, 3536], + [1199, 3567], +] + +[[area.polygons]] +vertices = [ + [1245, 3548], + [1254, 3548], +] + +[[area.polygons]] +vertices = [ + [1213, 3549], + [1223, 3549], +] + +[[area.polygons]] +vertices = [ + [1246, 3549], + [1254, 3549], +] + +[[area.polygons]] +vertices = [ + [1198, 3550], + [1198, 3567], +] + +[[area.polygons]] +vertices = [ + [1213, 3550], + [1222, 3550], +] + +[[area.polygons]] +vertices = [[1259, 3550]] + +[[area.polygons]] +vertices = [ + [1197, 3551], + [1197, 3567], +] + +[[area.polygons]] +vertices = [ + [1213, 3551], + [1221, 3551], +] + +[[area.polygons]] +vertices = [ + [1196, 3552], + [1196, 3567], +] + +[[area.polygons]] +vertices = [ + [1213, 3552], + [1220, 3552], +] + +[[area.polygons]] +vertices = [ + [1261, 3552], + [1266, 3552], +] + +[[area.polygons]] +vertices = [ + [1195, 3553], + [1195, 3567], +] + +[[area.polygons]] +vertices = [ + [1213, 3553], + [1219, 3553], +] + +[[area.polygons]] +vertices = [ + [1262, 3553], + [1266, 3553], +] + +[[area.polygons]] +vertices = [ + [1194, 3554], + [1194, 3567], +] + +[[area.polygons]] +vertices = [ + [1213, 3554], + [1218, 3554], + [1218, 3567], + [1213, 3567], +] + +[[area.polygons]] +vertices = [ + [1263, 3554], + [1266, 3554], +] + +[[area.polygons]] +vertices = [ + [1264, 3555], + [1266, 3555], +] + +[[area.polygons]] +vertices = [ + [1265, 3556], + [1266, 3556], +] + +[[area.polygons]] +vertices = [ + [1266, 3557], + [1266, 3562], +] + +[[area.polygons]] +vertices = [ + [1268, 3564], + [1284, 3564], +] + +[[area.polygons]] +vertices = [ + [1269, 3565], + [1284, 3565], +] + +[[area.polygons]] +vertices = [ + [1270, 3566], + [1284, 3566], +] + +[[area.polygons]] +vertices = [ + [1271, 3567], + [1284, 3567], +] diff --git a/.data/raw-cache/map/area/asgarnian_ice_dungeon.toml b/.data/raw-cache/map/area/asgarnian_ice_dungeon.toml index bd0a9be57..c9ce3bb49 100644 --- a/.data/raw-cache/map/area/asgarnian_ice_dungeon.toml +++ b/.data/raw-cache/map/area/asgarnian_ice_dungeon.toml @@ -18,3 +18,13 @@ vertices = [ [3030, 9599], [2983, 9599], ] + +# Extension traced from the OSRS wiki music map (2496 tiles). + +[[area.polygons]] +vertices = [ + [2944, 9536], + [2982, 9536], + [2982, 9599], + [2944, 9599], +] diff --git a/.data/raw-cache/map/area/assault_and_battery.toml b/.data/raw-cache/map/area/assault_and_battery.toml new file mode 100644 index 000000000..5c261e6da --- /dev/null +++ b/.data/raw-cache/map/area/assault_and_battery.toml @@ -0,0 +1,16 @@ +# Assault and Battery music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Assault and Battery. +# Bounds x 1856-1919, z 5440-5503; 4096 tiles as 1 rectangles. + +[[area]] +name = "Assault and Battery" +area_id = "area.assault_and_battery" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 5440], + [1919, 5440], + [1919, 5503], + [1856, 5503], +] diff --git a/.data/raw-cache/map/area/attack_1.toml b/.data/raw-cache/map/area/attack_1.toml new file mode 100644 index 000000000..86bb68f17 --- /dev/null +++ b/.data/raw-cache/map/area/attack_1.toml @@ -0,0 +1,520 @@ +# Attack 1 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Attack 1. +# Bounds x 2432-2565, z 3196-3305; 6219 tiles as 82 rectangles. + +[[area]] +name = "Attack 1" +area_id = "area.attack_1" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2508, 3196], + [2510, 3196], + [2510, 3279], + [2508, 3279], +] + +[[area.polygons]] +vertices = [ + [2524, 3196], + [2547, 3196], + [2547, 3253], + [2524, 3253], +] + +[[area.polygons]] +vertices = [ + [2507, 3197], + [2507, 3279], +] + +[[area.polygons]] +vertices = [ + [2523, 3197], + [2523, 3264], +] + +[[area.polygons]] +vertices = [ + [2506, 3198], + [2506, 3279], +] + +[[area.polygons]] +vertices = [ + [2511, 3198], + [2512, 3198], + [2512, 3264], + [2511, 3264], +] + +[[area.polygons]] +vertices = [ + [2522, 3198], + [2522, 3264], +] + +[[area.polygons]] +vertices = [ + [2504, 3199], + [2505, 3199], + [2505, 3279], + [2504, 3279], +] + +[[area.polygons]] +vertices = [ + [2513, 3199], + [2513, 3264], +] + +[[area.polygons]] +vertices = [ + [2521, 3199], + [2521, 3264], +] + +[[area.polygons]] +vertices = [ + [2503, 3200], + [2503, 3279], +] + +[[area.polygons]] +vertices = [ + [2514, 3200], + [2520, 3200], + [2520, 3264], + [2514, 3264], +] + +[[area.polygons]] +vertices = [ + [2548, 3200], + [2551, 3200], + [2551, 3249], + [2548, 3249], +] + +[[area.polygons]] +vertices = [ + [2502, 3201], + [2502, 3279], +] + +[[area.polygons]] +vertices = [ + [2552, 3201], + [2552, 3248], +] + +[[area.polygons]] +vertices = [ + [2501, 3202], + [2501, 3279], +] + +[[area.polygons]] +vertices = [ + [2553, 3202], + [2553, 3247], +] + +[[area.polygons]] +vertices = [ + [2554, 3203], + [2554, 3247], +] + +[[area.polygons]] +vertices = [ + [2500, 3204], + [2500, 3279], +] + +[[area.polygons]] +vertices = [ + [2555, 3204], + [2559, 3204], + [2559, 3245], + [2555, 3245], +] + +[[area.polygons]] +vertices = [ + [2499, 3205], + [2499, 3279], +] + +[[area.polygons]] +vertices = [ + [2498, 3206], + [2498, 3279], +] + +[[area.polygons]] +vertices = [ + [2497, 3207], + [2497, 3279], +] + +[[area.polygons]] +vertices = [ + [2496, 3208], + [2496, 3279], +] + +[[area.polygons]] +vertices = [ + [2494, 3209], + [2495, 3209], + [2495, 3279], + [2494, 3279], +] + +[[area.polygons]] +vertices = [ + [2493, 3210], + [2493, 3279], +] + +[[area.polygons]] +vertices = [ + [2560, 3215], + [2560, 3244], +] + +[[area.polygons]] +vertices = [ + [2561, 3216], + [2561, 3244], +] + +[[area.polygons]] +vertices = [ + [2562, 3217], + [2562, 3243], +] + +[[area.polygons]] +vertices = [ + [2563, 3218], + [2563, 3243], +] + +[[area.polygons]] +vertices = [ + [2564, 3219], + [2564, 3231], +] + +[[area.polygons]] +vertices = [ + [2565, 3221], + [2565, 3225], +] + +[[area.polygons]] +vertices = [ + [2488, 3242], + [2492, 3242], + [2492, 3279], + [2488, 3279], +] + +[[area.polygons]] +vertices = [ + [2555, 3246], + [2557, 3246], +] + +[[area.polygons]] +vertices = [[2555, 3247]] + +[[area.polygons]] +vertices = [ + [2548, 3250], + [2550, 3250], +] + +[[area.polygons]] +vertices = [ + [2548, 3251], + [2549, 3251], +] + +[[area.polygons]] +vertices = [[2548, 3252]] + +[[area.polygons]] +vertices = [ + [2484, 3254], + [2487, 3254], + [2487, 3279], + [2484, 3279], +] + +[[area.polygons]] +vertices = [ + [2524, 3254], + [2546, 3254], +] + +[[area.polygons]] +vertices = [ + [2483, 3255], + [2483, 3279], +] + +[[area.polygons]] +vertices = [ + [2524, 3255], + [2545, 3255], +] + +[[area.polygons]] +vertices = [ + [2482, 3256], + [2482, 3279], +] + +[[area.polygons]] +vertices = [ + [2524, 3256], + [2544, 3256], +] + +[[area.polygons]] +vertices = [ + [2481, 3257], + [2481, 3279], +] + +[[area.polygons]] +vertices = [ + [2524, 3257], + [2543, 3257], +] + +[[area.polygons]] +vertices = [ + [2480, 3258], + [2480, 3279], +] + +[[area.polygons]] +vertices = [ + [2524, 3258], + [2542, 3258], +] + +[[area.polygons]] +vertices = [ + [2479, 3259], + [2479, 3279], +] + +[[area.polygons]] +vertices = [ + [2524, 3259], + [2541, 3259], +] + +[[area.polygons]] +vertices = [ + [2478, 3260], + [2478, 3279], +] + +[[area.polygons]] +vertices = [ + [2524, 3260], + [2540, 3260], +] + +[[area.polygons]] +vertices = [ + [2477, 3261], + [2477, 3279], +] + +[[area.polygons]] +vertices = [ + [2524, 3261], + [2538, 3261], +] + +[[area.polygons]] +vertices = [ + [2457, 3262], + [2476, 3262], + [2476, 3279], + [2457, 3279], +] + +[[area.polygons]] +vertices = [ + [2524, 3262], + [2537, 3262], + [2537, 3264], + [2524, 3264], +] + +[[area.polygons]] +vertices = [ + [2456, 3263], + [2456, 3305], +] + +[[area.polygons]] +vertices = [ + [2455, 3264], + [2455, 3305], +] + +[[area.polygons]] +vertices = [ + [2454, 3265], + [2454, 3305], +] + +[[area.polygons]] +vertices = [ + [2453, 3266], + [2453, 3305], +] + +[[area.polygons]] +vertices = [ + [2452, 3267], + [2452, 3305], +] + +[[area.polygons]] +vertices = [ + [2451, 3268], + [2451, 3305], +] + +[[area.polygons]] +vertices = [ + [2450, 3269], + [2450, 3305], +] + +[[area.polygons]] +vertices = [ + [2449, 3270], + [2449, 3305], +] + +[[area.polygons]] +vertices = [ + [2448, 3271], + [2448, 3305], +] + +[[area.polygons]] +vertices = [ + [2447, 3272], + [2447, 3305], +] + +[[area.polygons]] +vertices = [ + [2446, 3273], + [2446, 3305], +] + +[[area.polygons]] +vertices = [ + [2445, 3274], + [2445, 3305], +] + +[[area.polygons]] +vertices = [ + [2444, 3275], + [2444, 3305], +] + +[[area.polygons]] +vertices = [ + [2443, 3276], + [2443, 3305], +] + +[[area.polygons]] +vertices = [ + [2442, 3277], + [2442, 3305], +] + +[[area.polygons]] +vertices = [ + [2441, 3278], + [2441, 3305], +] + +[[area.polygons]] +vertices = [ + [2440, 3279], + [2440, 3305], +] + +[[area.polygons]] +vertices = [ + [2439, 3280], + [2439, 3305], +] + +[[area.polygons]] +vertices = [ + [2457, 3280], + [2460, 3280], + [2460, 3305], + [2457, 3305], +] + +[[area.polygons]] +vertices = [ + [2438, 3281], + [2438, 3305], +] + +[[area.polygons]] +vertices = [ + [2437, 3282], + [2437, 3305], +] + +[[area.polygons]] +vertices = [ + [2436, 3283], + [2436, 3305], +] + +[[area.polygons]] +vertices = [ + [2435, 3284], + [2435, 3305], +] + +[[area.polygons]] +vertices = [ + [2434, 3285], + [2434, 3305], +] + +[[area.polygons]] +vertices = [ + [2433, 3286], + [2433, 3305], +] + +[[area.polygons]] +vertices = [ + [2432, 3287], + [2432, 3305], +] diff --git a/.data/raw-cache/map/area/attack_2.toml b/.data/raw-cache/map/area/attack_2.toml new file mode 100644 index 000000000..5130cec8f --- /dev/null +++ b/.data/raw-cache/map/area/attack_2.toml @@ -0,0 +1,16 @@ +# Attack 2 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Attack 2. +# Bounds x 2846-2879, z 9620-9663; 1496 tiles as 1 rectangles. + +[[area]] +name = "Attack 2" +area_id = "area.attack_2" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2846, 9620], + [2879, 9620], + [2879, 9663], + [2846, 9663], +] diff --git a/.data/raw-cache/map/area/attack_3.toml b/.data/raw-cache/map/area/attack_3.toml new file mode 100644 index 000000000..13244bae4 --- /dev/null +++ b/.data/raw-cache/map/area/attack_3.toml @@ -0,0 +1,16 @@ +# Attack 3 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Attack 3. +# Bounds x 3008-3071, z 10240-10303; 4096 tiles as 1 rectangles. + +[[area]] +name = "Attack 3" +area_id = "area.attack_3" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 10240], + [3071, 10240], + [3071, 10303], + [3008, 10303], +] diff --git a/.data/raw-cache/map/area/attack_4.toml b/.data/raw-cache/map/area/attack_4.toml new file mode 100644 index 000000000..cb7376229 --- /dev/null +++ b/.data/raw-cache/map/area/attack_4.toml @@ -0,0 +1,86 @@ +# Attack 4 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Attack 4. +# Bounds x 2176-2611, z 3151-9727; 4686 tiles as 12 rectangles. + +[[area]] +name = "Attack 4" +area_id = "area.attack_4" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2585, 3151], + [2602, 3151], + [2602, 3172], + [2585, 3172], +] + +[[area.polygons]] +vertices = [ + [2604, 3151], + [2604, 3170], +] + +[[area.polygons]] +vertices = [ + [2584, 3152], + [2584, 3171], +] + +[[area.polygons]] +vertices = [ + [2603, 3152], + [2603, 3171], +] + +[[area.polygons]] +vertices = [ + [2605, 3152], + [2605, 3169], +] + +[[area.polygons]] +vertices = [ + [2583, 3153], + [2583, 3170], +] + +[[area.polygons]] +vertices = [ + [2606, 3153], + [2606, 3168], +] + +[[area.polygons]] +vertices = [ + [2582, 3154], + [2582, 3169], +] + +[[area.polygons]] +vertices = [ + [2581, 3155], + [2581, 3168], +] + +[[area.polygons]] +vertices = [ + [2607, 3156], + [2607, 3167], +] + +[[area.polygons]] +vertices = [ + [2608, 3158], + [2611, 3158], + [2611, 3167], + [2608, 3167], +] + +[[area.polygons]] +vertices = [ + [2176, 9664], + [2239, 9664], + [2239, 9727], + [2176, 9727], +] diff --git a/.data/raw-cache/map/area/attack_5.toml b/.data/raw-cache/map/area/attack_5.toml new file mode 100644 index 000000000..494402161 --- /dev/null +++ b/.data/raw-cache/map/area/attack_5.toml @@ -0,0 +1,16 @@ +# Attack 5 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Attack 5. +# Bounds x 2240-2303, z 4672-4735; 4096 tiles as 1 rectangles. + +[[area]] +name = "Attack 5" +area_id = "area.attack_5" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2240, 4672], + [2303, 4672], + [2303, 4735], + [2240, 4735], +] diff --git a/.data/raw-cache/map/area/attack_6.toml b/.data/raw-cache/map/area/attack_6.toml new file mode 100644 index 000000000..5edef4322 --- /dev/null +++ b/.data/raw-cache/map/area/attack_6.toml @@ -0,0 +1,16 @@ +# Attack 6 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Attack 6. +# Bounds x 2560-2623, z 9408-9471; 4096 tiles as 1 rectangles. + +[[area]] +name = "Attack 6" +area_id = "area.attack_6" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 9408], + [2623, 9408], + [2623, 9471], + [2560, 9471], +] diff --git a/.data/raw-cache/map/area/auburn_valley.toml b/.data/raw-cache/map/area/auburn_valley.toml new file mode 100644 index 000000000..0244d74b6 --- /dev/null +++ b/.data/raw-cache/map/area/auburn_valley.toml @@ -0,0 +1,1521 @@ +# Auburn Valley music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Spiritbloom; Spirit of the Forest. +# Bounds x 1326-1528, z 3171-3408; 19654 tiles as 244 rectangles. + +[[area]] +name = "Auburn Valley" +area_id = "area.auburn_valley" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1382, 3171], + [1382, 3406], +] + +[[area.polygons]] +vertices = [ + [1381, 3172], + [1381, 3405], +] + +[[area.polygons]] +vertices = [ + [1383, 3172], + [1383, 3406], +] + +[[area.polygons]] +vertices = [ + [1380, 3173], + [1380, 3404], +] + +[[area.polygons]] +vertices = [ + [1379, 3174], + [1379, 3404], +] + +[[area.polygons]] +vertices = [ + [1384, 3174], + [1384, 3406], +] + +[[area.polygons]] +vertices = [ + [1378, 3175], + [1378, 3404], +] + +[[area.polygons]] +vertices = [ + [1385, 3175], + [1385, 3407], +] + +[[area.polygons]] +vertices = [ + [1386, 3176], + [1386, 3407], +] + +[[area.polygons]] +vertices = [ + [1377, 3177], + [1377, 3404], +] + +[[area.polygons]] +vertices = [ + [1387, 3177], + [1387, 3408], +] + +[[area.polygons]] +vertices = [ + [1376, 3178], + [1376, 3403], +] + +[[area.polygons]] +vertices = [ + [1375, 3179], + [1375, 3403], +] + +[[area.polygons]] +vertices = [ + [1388, 3179], + [1389, 3179], + [1389, 3408], + [1388, 3408], +] + +[[area.polygons]] +vertices = [ + [1352, 3180], + [1374, 3180], + [1374, 3208], + [1352, 3208], +] + +[[area.polygons]] +vertices = [ + [1390, 3180], + [1391, 3180], + [1391, 3408], + [1390, 3408], +] + +[[area.polygons]] +vertices = [ + [1351, 3181], + [1351, 3212], +] + +[[area.polygons]] +vertices = [ + [1392, 3181], + [1393, 3181], + [1393, 3407], + [1392, 3407], +] + +[[area.polygons]] +vertices = [ + [1350, 3182], + [1350, 3211], +] + +[[area.polygons]] +vertices = [ + [1394, 3182], + [1394, 3406], +] + +[[area.polygons]] +vertices = [ + [1349, 3183], + [1349, 3210], +] + +[[area.polygons]] +vertices = [ + [1395, 3183], + [1395, 3406], +] + +[[area.polygons]] +vertices = [ + [1348, 3184], + [1348, 3210], +] + +[[area.polygons]] +vertices = [ + [1396, 3184], + [1396, 3406], +] + +[[area.polygons]] +vertices = [ + [1397, 3185], + [1397, 3405], +] + +[[area.polygons]] +vertices = [ + [1398, 3186], + [1398, 3232], +] + +[[area.polygons]] +vertices = [ + [1399, 3188], + [1399, 3228], +] + +[[area.polygons]] +vertices = [ + [1400, 3191], + [1400, 3225], +] + +[[area.polygons]] +vertices = [ + [1401, 3193], + [1401, 3224], +] + +[[area.polygons]] +vertices = [ + [1402, 3195], + [1402, 3222], +] + +[[area.polygons]] +vertices = [ + [1347, 3196], + [1347, 3202], +] + +[[area.polygons]] +vertices = [ + [1403, 3196], + [1403, 3220], +] + +[[area.polygons]] +vertices = [ + [1346, 3197], + [1346, 3201], +] + +[[area.polygons]] +vertices = [ + [1345, 3198], + [1345, 3200], +] + +[[area.polygons]] +vertices = [ + [1404, 3198], + [1404, 3220], +] + +[[area.polygons]] +vertices = [ + [1344, 3199], + [1344, 3200], +] + +[[area.polygons]] +vertices = [ + [1405, 3199], + [1406, 3199], + [1406, 3217], + [1405, 3217], +] + +[[area.polygons]] +vertices = [ + [1407, 3200], + [1407, 3215], +] + +[[area.polygons]] +vertices = [ + [1408, 3201], + [1408, 3212], +] + +[[area.polygons]] +vertices = [ + [1409, 3202], + [1409, 3211], +] + +[[area.polygons]] +vertices = [ + [1410, 3203], + [1410, 3208], +] + +[[area.polygons]] +vertices = [ + [1347, 3205], + [1347, 3209], +] + +[[area.polygons]] +vertices = [ + [1352, 3209], + [1356, 3209], + [1356, 3211], + [1352, 3211], +] + +[[area.polygons]] +vertices = [ + [1359, 3209], + [1374, 3209], +] + +[[area.polygons]] +vertices = [ + [1360, 3210], + [1374, 3210], +] + +[[area.polygons]] +vertices = [ + [1361, 3211], + [1374, 3211], + [1374, 3255], + [1361, 3255], +] + +[[area.polygons]] +vertices = [ + [1352, 3212], + [1355, 3212], +] + +[[area.polygons]] +vertices = [ + [1360, 3213], + [1360, 3253], +] + +[[area.polygons]] +vertices = [ + [1359, 3214], + [1359, 3252], +] + +[[area.polygons]] +vertices = [ + [1358, 3215], + [1358, 3219], +] + +[[area.polygons]] +vertices = [ + [1357, 3216], + [1357, 3217], +] + +[[area.polygons]] +vertices = [ + [1405, 3218], + [1405, 3219], +] + +[[area.polygons]] +vertices = [ + [1358, 3227], + [1358, 3236], +] + +[[area.polygons]] +vertices = [ + [1357, 3228], + [1357, 3233], +] + +[[area.polygons]] +vertices = [ + [1356, 3229], + [1356, 3231], +] + +[[area.polygons]] +vertices = [ + [1358, 3241], + [1358, 3250], +] + +[[area.polygons]] +vertices = [ + [1398, 3242], + [1398, 3404], +] + +[[area.polygons]] +vertices = [ + [1399, 3244], + [1399, 3258], +] + +[[area.polygons]] +vertices = [ + [1357, 3246], + [1357, 3247], +] + +[[area.polygons]] +vertices = [ + [1400, 3246], + [1400, 3257], +] + +[[area.polygons]] +vertices = [ + [1401, 3247], + [1402, 3247], + [1402, 3256], + [1401, 3256], +] + +[[area.polygons]] +vertices = [ + [1403, 3248], + [1404, 3248], + [1404, 3255], + [1403, 3255], +] + +[[area.polygons]] +vertices = [ + [1405, 3249], + [1406, 3249], + [1406, 3254], + [1405, 3254], +] + +[[area.polygons]] +vertices = [ + [1407, 3250], + [1410, 3250], + [1410, 3252], + [1407, 3252], +] + +[[area.polygons]] +vertices = [ + [1407, 3253], + [1409, 3253], +] + +[[area.polygons]] +vertices = [[1407, 3254]] + +[[area.polygons]] +vertices = [[1405, 3255]] + +[[area.polygons]] +vertices = [ + [1363, 3256], + [1374, 3256], +] + +[[area.polygons]] +vertices = [[1403, 3256]] + +[[area.polygons]] +vertices = [ + [1364, 3257], + [1374, 3257], +] + +[[area.polygons]] +vertices = [[1401, 3257]] + +[[area.polygons]] +vertices = [ + [1365, 3258], + [1374, 3258], + [1374, 3260], + [1365, 3260], +] + +[[area.polygons]] +vertices = [ + [1366, 3261], + [1374, 3261], +] + +[[area.polygons]] +vertices = [ + [1367, 3262], + [1374, 3262], +] + +[[area.polygons]] +vertices = [ + [1368, 3263], + [1374, 3263], + [1374, 3399], + [1368, 3399], +] + +[[area.polygons]] +vertices = [ + [1399, 3263], + [1399, 3403], +] + +[[area.polygons]] +vertices = [ + [1367, 3265], + [1367, 3399], +] + +[[area.polygons]] +vertices = [ + [1366, 3266], + [1366, 3398], +] + +[[area.polygons]] +vertices = [ + [1400, 3266], + [1400, 3402], +] + +[[area.polygons]] +vertices = [ + [1363, 3267], + [1365, 3267], + [1365, 3395], + [1363, 3395], +] + +[[area.polygons]] +vertices = [ + [1361, 3268], + [1362, 3268], + [1362, 3393], + [1361, 3393], +] + +[[area.polygons]] +vertices = [ + [1401, 3268], + [1401, 3401], +] + +[[area.polygons]] +vertices = [ + [1360, 3269], + [1360, 3392], +] + +[[area.polygons]] +vertices = [ + [1359, 3270], + [1359, 3391], +] + +[[area.polygons]] +vertices = [ + [1402, 3270], + [1411, 3270], + [1411, 3385], + [1402, 3385], +] + +[[area.polygons]] +vertices = [ + [1358, 3271], + [1358, 3390], +] + +[[area.polygons]] +vertices = [ + [1412, 3271], + [1413, 3271], + [1413, 3383], + [1412, 3383], +] + +[[area.polygons]] +vertices = [ + [1357, 3272], + [1357, 3278], +] + +[[area.polygons]] +vertices = [ + [1414, 3272], + [1415, 3272], + [1415, 3382], + [1414, 3382], +] + +[[area.polygons]] +vertices = [ + [1356, 3273], + [1356, 3277], +] + +[[area.polygons]] +vertices = [ + [1416, 3273], + [1417, 3273], + [1417, 3383], + [1416, 3383], +] + +[[area.polygons]] +vertices = [ + [1355, 3274], + [1355, 3275], +] + +[[area.polygons]] +vertices = [ + [1418, 3274], + [1419, 3274], + [1419, 3385], + [1418, 3385], +] + +[[area.polygons]] +vertices = [ + [1420, 3275], + [1420, 3387], +] + +[[area.polygons]] +vertices = [ + [1357, 3281], + [1357, 3389], +] + +[[area.polygons]] +vertices = [ + [1354, 3282], + [1356, 3282], + [1356, 3386], + [1354, 3386], +] + +[[area.polygons]] +vertices = [ + [1353, 3283], + [1353, 3385], +] + +[[area.polygons]] +vertices = [ + [1352, 3284], + [1352, 3286], +] + +[[area.polygons]] +vertices = [ + [1421, 3286], + [1448, 3286], + [1448, 3362], + [1421, 3362], +] + +[[area.polygons]] +vertices = [ + [1449, 3287], + [1450, 3287], + [1450, 3368], + [1449, 3368], +] + +[[area.polygons]] +vertices = [ + [1451, 3288], + [1452, 3288], + [1452, 3370], + [1451, 3370], +] + +[[area.polygons]] +vertices = [ + [1453, 3289], + [1454, 3289], + [1454, 3329], + [1453, 3329], +] + +[[area.polygons]] +vertices = [ + [1352, 3290], + [1352, 3384], +] + +[[area.polygons]] +vertices = [ + [1455, 3290], + [1456, 3290], + [1456, 3329], + [1455, 3329], +] + +[[area.polygons]] +vertices = [ + [1457, 3291], + [1458, 3291], + [1458, 3329], + [1457, 3329], +] + +[[area.polygons]] +vertices = [ + [1351, 3292], + [1351, 3383], +] + +[[area.polygons]] +vertices = [ + [1459, 3292], + [1459, 3329], +] + +[[area.polygons]] +vertices = [ + [1350, 3293], + [1350, 3382], +] + +[[area.polygons]] +vertices = [ + [1348, 3294], + [1349, 3294], + [1349, 3380], + [1348, 3380], +] + +[[area.polygons]] +vertices = [ + [1347, 3295], + [1347, 3355], +] + +[[area.polygons]] +vertices = [ + [1346, 3296], + [1346, 3354], +] + +[[area.polygons]] +vertices = [ + [1460, 3297], + [1460, 3330], +] + +[[area.polygons]] +vertices = [ + [1461, 3298], + [1461, 3330], +] + +[[area.polygons]] +vertices = [ + [1462, 3299], + [1462, 3330], +] + +[[area.polygons]] +vertices = [ + [1345, 3300], + [1345, 3353], +] + +[[area.polygons]] +vertices = [ + [1463, 3300], + [1463, 3331], +] + +[[area.polygons]] +vertices = [ + [1464, 3301], + [1464, 3331], +] + +[[area.polygons]] +vertices = [ + [1465, 3302], + [1465, 3332], +] + +[[area.polygons]] +vertices = [ + [1466, 3303], + [1466, 3332], +] + +[[area.polygons]] +vertices = [ + [1344, 3304], + [1344, 3305], +] + +[[area.polygons]] +vertices = [ + [1467, 3304], + [1468, 3304], + [1468, 3333], + [1467, 3333], +] + +[[area.polygons]] +vertices = [ + [1469, 3305], + [1470, 3305], + [1470, 3335], + [1469, 3335], +] + +[[area.polygons]] +vertices = [ + [1471, 3306], + [1472, 3306], + [1472, 3336], + [1471, 3336], +] + +[[area.polygons]] +vertices = [ + [1473, 3307], + [1474, 3307], + [1474, 3337], + [1473, 3337], +] + +[[area.polygons]] +vertices = [ + [1475, 3308], + [1476, 3308], + [1476, 3340], + [1475, 3340], +] + +[[area.polygons]] +vertices = [ + [1477, 3309], + [1478, 3309], + [1478, 3341], + [1477, 3341], +] + +[[area.polygons]] +vertices = [ + [1479, 3310], + [1480, 3310], + [1480, 3345], + [1479, 3345], +] + +[[area.polygons]] +vertices = [ + [1481, 3311], + [1482, 3311], + [1482, 3346], + [1481, 3346], +] + +[[area.polygons]] +vertices = [ + [1483, 3312], + [1503, 3312], + [1503, 3348], + [1483, 3348], +] + +[[area.polygons]] +vertices = [ + [1344, 3313], + [1344, 3352], +] + +[[area.polygons]] +vertices = [ + [1504, 3313], + [1504, 3352], +] + +[[area.polygons]] +vertices = [ + [1505, 3314], + [1505, 3353], +] + +[[area.polygons]] +vertices = [ + [1343, 3315], + [1343, 3351], +] + +[[area.polygons]] +vertices = [ + [1506, 3315], + [1506, 3354], +] + +[[area.polygons]] +vertices = [ + [1507, 3316], + [1507, 3354], +] + +[[area.polygons]] +vertices = [ + [1508, 3317], + [1508, 3355], +] + +[[area.polygons]] +vertices = [ + [1342, 3318], + [1342, 3350], +] + +[[area.polygons]] +vertices = [ + [1509, 3318], + [1509, 3355], +] + +[[area.polygons]] +vertices = [ + [1341, 3319], + [1341, 3349], +] + +[[area.polygons]] +vertices = [ + [1510, 3319], + [1511, 3319], + [1511, 3354], + [1510, 3354], +] + +[[area.polygons]] +vertices = [ + [1512, 3320], + [1513, 3320], + [1513, 3353], + [1512, 3353], +] + +[[area.polygons]] +vertices = [ + [1340, 3321], + [1340, 3348], +] + +[[area.polygons]] +vertices = [ + [1514, 3321], + [1515, 3321], + [1515, 3352], + [1514, 3352], +] + +[[area.polygons]] +vertices = [ + [1339, 3322], + [1339, 3347], +] + +[[area.polygons]] +vertices = [ + [1516, 3322], + [1517, 3322], + [1517, 3350], + [1516, 3350], +] + +[[area.polygons]] +vertices = [ + [1338, 3323], + [1338, 3347], +] + +[[area.polygons]] +vertices = [ + [1518, 3323], + [1519, 3323], + [1519, 3346], + [1518, 3346], +] + +[[area.polygons]] +vertices = [ + [1335, 3324], + [1337, 3324], + [1337, 3347], + [1335, 3347], +] + +[[area.polygons]] +vertices = [ + [1520, 3324], + [1521, 3324], + [1521, 3345], + [1520, 3345], +] + +[[area.polygons]] +vertices = [ + [1333, 3325], + [1334, 3325], + [1334, 3347], + [1333, 3347], +] + +[[area.polygons]] +vertices = [ + [1522, 3325], + [1523, 3325], + [1523, 3343], + [1522, 3343], +] + +[[area.polygons]] +vertices = [ + [1332, 3326], + [1332, 3336], +] + +[[area.polygons]] +vertices = [ + [1524, 3326], + [1528, 3326], + [1528, 3335], + [1524, 3335], +] + +[[area.polygons]] +vertices = [ + [1330, 3327], + [1331, 3327], + [1331, 3334], + [1330, 3334], +] + +[[area.polygons]] +vertices = [ + [1329, 3328], + [1329, 3334], +] + +[[area.polygons]] +vertices = [ + [1327, 3329], + [1328, 3329], + [1328, 3333], + [1327, 3333], +] + +[[area.polygons]] +vertices = [ + [1453, 3330], + [1453, 3372], +] + +[[area.polygons]] +vertices = [ + [1326, 3331], + [1326, 3333], +] + +[[area.polygons]] +vertices = [[1328, 3334]] + +[[area.polygons]] +vertices = [ + [1454, 3334], + [1457, 3334], + [1457, 3347], + [1454, 3347], +] + +[[area.polygons]] +vertices = [ + [1468, 3334], + [1468, 3335], +] + +[[area.polygons]] +vertices = [[1331, 3335]] + +[[area.polygons]] +vertices = [ + [1458, 3335], + [1459, 3335], + [1459, 3346], + [1458, 3346], +] + +[[area.polygons]] +vertices = [ + [1460, 3336], + [1460, 3345], +] + +[[area.polygons]] +vertices = [[1470, 3336]] + +[[area.polygons]] +vertices = [ + [1524, 3336], + [1526, 3336], +] + +[[area.polygons]] +vertices = [ + [1524, 3337], + [1525, 3337], + [1525, 3342], + [1524, 3342], +] + +[[area.polygons]] +vertices = [ + [1461, 3338], + [1462, 3338], + [1462, 3343], + [1461, 3343], +] + +[[area.polygons]] +vertices = [ + [1474, 3338], + [1474, 3339], +] + +[[area.polygons]] +vertices = [ + [1463, 3339], + [1463, 3342], +] + +[[area.polygons]] +vertices = [ + [1330, 3340], + [1332, 3340], + [1332, 3347], + [1330, 3347], +] + +[[area.polygons]] +vertices = [ + [1464, 3340], + [1464, 3341], +] + +[[area.polygons]] +vertices = [ + [1526, 3340], + [1526, 3341], +] + +[[area.polygons]] +vertices = [ + [1478, 3342], + [1478, 3344], +] + +[[area.polygons]] +vertices = [[1522, 3344]] + +[[area.polygons]] +vertices = [[1480, 3346]] + +[[area.polygons]] +vertices = [[1458, 3347]] + +[[area.polygons]] +vertices = [[1482, 3347]] + +[[area.polygons]] +vertices = [[1518, 3347]] + +[[area.polygons]] +vertices = [ + [1454, 3348], + [1456, 3348], +] + +[[area.polygons]] +vertices = [ + [1454, 3349], + [1454, 3373], +] + +[[area.polygons]] +vertices = [ + [1484, 3349], + [1503, 3349], +] + +[[area.polygons]] +vertices = [ + [1485, 3350], + [1503, 3350], +] + +[[area.polygons]] +vertices = [ + [1486, 3351], + [1497, 3351], +] + +[[area.polygons]] +vertices = [ + [1501, 3351], + [1503, 3351], +] + +[[area.polygons]] +vertices = [[1516, 3351]] + +[[area.polygons]] +vertices = [ + [1455, 3352], + [1455, 3373], +] + +[[area.polygons]] +vertices = [ + [1457, 3352], + [1458, 3352], + [1458, 3357], + [1457, 3357], +] + +[[area.polygons]] +vertices = [ + [1487, 3352], + [1497, 3352], +] + +[[area.polygons]] +vertices = [[1503, 3352]] + +[[area.polygons]] +vertices = [ + [1456, 3353], + [1456, 3373], +] + +[[area.polygons]] +vertices = [ + [1459, 3353], + [1459, 3356], +] + +[[area.polygons]] +vertices = [ + [1491, 3353], + [1496, 3353], +] + +[[area.polygons]] +vertices = [ + [1460, 3354], + [1460, 3355], +] + +[[area.polygons]] +vertices = [ + [1492, 3354], + [1495, 3354], +] + +[[area.polygons]] +vertices = [ + [1457, 3360], + [1457, 3372], +] + +[[area.polygons]] +vertices = [ + [1458, 3361], + [1458, 3371], +] + +[[area.polygons]] +vertices = [ + [1421, 3363], + [1437, 3363], +] + +[[area.polygons]] +vertices = [ + [1443, 3363], + [1448, 3363], +] + +[[area.polygons]] +vertices = [ + [1459, 3363], + [1459, 3369], +] + +[[area.polygons]] +vertices = [ + [1421, 3364], + [1435, 3364], +] + +[[area.polygons]] +vertices = [ + [1446, 3364], + [1448, 3364], +] + +[[area.polygons]] +vertices = [ + [1460, 3364], + [1460, 3369], +] + +[[area.polygons]] +vertices = [ + [1421, 3365], + [1433, 3365], + [1433, 3373], + [1421, 3373], +] + +[[area.polygons]] +vertices = [ + [1447, 3365], + [1448, 3365], +] + +[[area.polygons]] +vertices = [ + [1461, 3365], + [1461, 3368], +] + +[[area.polygons]] +vertices = [[1448, 3366]] + +[[area.polygons]] +vertices = [ + [1434, 3367], + [1434, 3375], +] + +[[area.polygons]] +vertices = [ + [1437, 3368], + [1438, 3368], + [1438, 3373], + [1437, 3373], +] + +[[area.polygons]] +vertices = [ + [1435, 3369], + [1436, 3369], + [1436, 3375], + [1435, 3375], +] + +[[area.polygons]] +vertices = [ + [1439, 3369], + [1439, 3372], +] + +[[area.polygons]] +vertices = [[1450, 3369]] + +[[area.polygons]] +vertices = [[1452, 3371]] + +[[area.polygons]] +vertices = [ + [1421, 3374], + [1429, 3374], +] + +[[area.polygons]] +vertices = [ + [1432, 3374], + [1433, 3374], +] + +[[area.polygons]] +vertices = [[1437, 3374]] + +[[area.polygons]] +vertices = [ + [1421, 3375], + [1427, 3375], + [1427, 3380], + [1421, 3380], +] + +[[area.polygons]] +vertices = [[1433, 3375]] + +[[area.polygons]] +vertices = [ + [1428, 3377], + [1429, 3377], + [1429, 3379], + [1428, 3379], +] + +[[area.polygons]] +vertices = [[1430, 3378]] + +[[area.polygons]] +vertices = [[1428, 3380]] + +[[area.polygons]] +vertices = [[1349, 3381]] + +[[area.polygons]] +vertices = [ + [1421, 3381], + [1426, 3381], + [1426, 3384], + [1421, 3384], +] + +[[area.polygons]] +vertices = [[1417, 3384]] + +[[area.polygons]] +vertices = [ + [1421, 3385], + [1425, 3385], + [1425, 3387], + [1421, 3387], +] + +[[area.polygons]] +vertices = [ + [1402, 3386], + [1410, 3386], +] + +[[area.polygons]] +vertices = [[1419, 3386]] + +[[area.polygons]] +vertices = [ + [1355, 3387], + [1356, 3387], +] + +[[area.polygons]] +vertices = [ + [1402, 3387], + [1409, 3387], + [1409, 3388], + [1402, 3388], +] + +[[area.polygons]] +vertices = [[1356, 3388]] + +[[area.polygons]] +vertices = [ + [1421, 3388], + [1424, 3388], +] + +[[area.polygons]] +vertices = [ + [1402, 3389], + [1408, 3389], +] + +[[area.polygons]] +vertices = [ + [1402, 3390], + [1407, 3390], +] + +[[area.polygons]] +vertices = [ + [1402, 3391], + [1406, 3391], +] + +[[area.polygons]] +vertices = [ + [1402, 3392], + [1405, 3392], + [1405, 3393], + [1402, 3393], +] + +[[area.polygons]] +vertices = [[1362, 3394]] + +[[area.polygons]] +vertices = [ + [1402, 3394], + [1404, 3394], +] + +[[area.polygons]] +vertices = [ + [1402, 3395], + [1403, 3395], + [1403, 3397], + [1402, 3397], +] + +[[area.polygons]] +vertices = [ + [1364, 3396], + [1365, 3396], +] + +[[area.polygons]] +vertices = [[1365, 3397]] + +[[area.polygons]] +vertices = [ + [1402, 3398], + [1402, 3399], +] + +[[area.polygons]] +vertices = [ + [1369, 3400], + [1374, 3400], +] + +[[area.polygons]] +vertices = [ + [1372, 3401], + [1374, 3401], +] + +[[area.polygons]] +vertices = [ + [1373, 3402], + [1374, 3402], +] diff --git a/.data/raw-cache/map/area/avium_savannah.toml b/.data/raw-cache/map/area/avium_savannah.toml new file mode 100644 index 000000000..e4cbd7e75 --- /dev/null +++ b/.data/raw-cache/map/area/avium_savannah.toml @@ -0,0 +1,3323 @@ +# Avium Savannah music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Undying Light; Creatures of Varlamore; Scorching Horizon. +# Bounds x 1383-1854, z 2881-3209; 68771 tiles as 528 rectangles. + +[[area]] +name = "Avium Savannah" +area_id = "area.avium_savannah" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1652, 2881], + [1656, 2881], + [1656, 3076], + [1652, 3076], +] + +[[area.polygons]] +vertices = [ + [1642, 2882], + [1643, 2882], + [1643, 3081], + [1642, 3081], +] + +[[area.polygons]] +vertices = [ + [1646, 2882], + [1648, 2882], + [1648, 3081], + [1646, 3081], +] + +[[area.polygons]] +vertices = [ + [1650, 2882], + [1651, 2882], + [1651, 3081], + [1650, 3081], +] + +[[area.polygons]] +vertices = [ + [1657, 2882], + [1658, 2882], + [1658, 3074], + [1657, 3074], +] + +[[area.polygons]] +vertices = [ + [1641, 2883], + [1641, 3081], +] + +[[area.polygons]] +vertices = [ + [1644, 2883], + [1645, 2883], + [1645, 3081], + [1644, 3081], +] + +[[area.polygons]] +vertices = [ + [1649, 2883], + [1649, 3081], +] + +[[area.polygons]] +vertices = [ + [1640, 2884], + [1640, 3081], +] + +[[area.polygons]] +vertices = [ + [1639, 2886], + [1639, 3081], +] + +[[area.polygons]] +vertices = [ + [1659, 2886], + [1659, 3073], +] + +[[area.polygons]] +vertices = [ + [1609, 2887], + [1611, 2887], + [1611, 3199], + [1609, 3199], +] + +[[area.polygons]] +vertices = [ + [1626, 2887], + [1632, 2887], + [1632, 3081], + [1626, 3081], +] + +[[area.polygons]] +vertices = [ + [1636, 2887], + [1638, 2887], + [1638, 3081], + [1636, 3081], +] + +[[area.polygons]] +vertices = [ + [1660, 2887], + [1661, 2887], + [1661, 3073], + [1660, 3073], +] + +[[area.polygons]] +vertices = [ + [1608, 2888], + [1608, 3199], +] + +[[area.polygons]] +vertices = [ + [1612, 2888], + [1612, 3199], +] + +[[area.polygons]] +vertices = [ + [1623, 2888], + [1625, 2888], + [1625, 3085], + [1623, 3085], +] + +[[area.polygons]] +vertices = [ + [1633, 2888], + [1635, 2888], + [1635, 3081], + [1633, 3081], +] + +[[area.polygons]] +vertices = [ + [1662, 2888], + [1666, 2888], + [1666, 3073], + [1662, 3073], +] + +[[area.polygons]] +vertices = [ + [1670, 2888], + [1671, 2888], + [1671, 3073], + [1670, 3073], +] + +[[area.polygons]] +vertices = [ + [1607, 2889], + [1607, 3199], +] + +[[area.polygons]] +vertices = [ + [1613, 2889], + [1614, 2889], + [1614, 3127], + [1613, 3127], +] + +[[area.polygons]] +vertices = [ + [1621, 2889], + [1622, 2889], + [1622, 3088], + [1621, 3088], +] + +[[area.polygons]] +vertices = [ + [1667, 2889], + [1669, 2889], + [1669, 3073], + [1667, 3073], +] + +[[area.polygons]] +vertices = [ + [1672, 2889], + [1672, 3073], +] + +[[area.polygons]] +vertices = [ + [1606, 2890], + [1606, 3199], +] + +[[area.polygons]] +vertices = [ + [1615, 2890], + [1620, 2890], + [1620, 3121], + [1615, 3121], +] + +[[area.polygons]] +vertices = [ + [1673, 2890], + [1673, 3073], +] + +[[area.polygons]] +vertices = [ + [1674, 2891], + [1674, 3073], +] + +[[area.polygons]] +vertices = [ + [1675, 2892], + [1677, 2892], + [1677, 3073], + [1675, 3073], +] + +[[area.polygons]] +vertices = [ + [1678, 2893], + [1678, 3073], +] + +[[area.polygons]] +vertices = [ + [1679, 2894], + [1679, 3073], +] + +[[area.polygons]] +vertices = [ + [1605, 2895], + [1605, 3199], +] + +[[area.polygons]] +vertices = [ + [1680, 2895], + [1685, 2895], + [1685, 3073], + [1680, 3073], +] + +[[area.polygons]] +vertices = [ + [1686, 2896], + [1688, 2896], + [1688, 3073], + [1686, 3073], +] + +[[area.polygons]] +vertices = [ + [1604, 2897], + [1604, 3199], +] + +[[area.polygons]] +vertices = [ + [1689, 2897], + [1689, 3073], +] + +[[area.polygons]] +vertices = [ + [1603, 2898], + [1603, 3198], +] + +[[area.polygons]] +vertices = [ + [1690, 2898], + [1690, 3073], +] + +[[area.polygons]] +vertices = [ + [1593, 2899], + [1595, 2899], + [1595, 2920], + [1593, 2920], +] + +[[area.polygons]] +vertices = [ + [1600, 2899], + [1602, 2899], + [1602, 3195], + [1600, 3195], +] + +[[area.polygons]] +vertices = [ + [1691, 2899], + [1692, 2899], + [1692, 3073], + [1691, 3073], +] + +[[area.polygons]] +vertices = [ + [1695, 2899], + [1696, 2899], + [1696, 3073], + [1695, 3073], +] + +[[area.polygons]] +vertices = [ + [1591, 2900], + [1592, 2900], + [1592, 2920], + [1591, 2920], +] + +[[area.polygons]] +vertices = [ + [1596, 2900], + [1599, 2900], + [1599, 2924], + [1596, 2924], +] + +[[area.polygons]] +vertices = [ + [1693, 2900], + [1694, 2900], + [1694, 3073], + [1693, 3073], +] + +[[area.polygons]] +vertices = [ + [1697, 2900], + [1698, 2900], + [1698, 2905], + [1697, 2905], +] + +[[area.polygons]] +vertices = [ + [1590, 2901], + [1590, 2907], +] + +[[area.polygons]] +vertices = [ + [1699, 2901], + [1699, 2905], +] + +[[area.polygons]] +vertices = [ + [1589, 2902], + [1589, 2907], +] + +[[area.polygons]] +vertices = [ + [1587, 2903], + [1588, 2903], + [1588, 2906], + [1587, 2906], +] + +[[area.polygons]] +vertices = [ + [1700, 2903], + [1700, 2904], +] + +[[area.polygons]] +vertices = [ + [1697, 2906], + [1697, 3073], +] + +[[area.polygons]] +vertices = [[1588, 2907]] + +[[area.polygons]] +vertices = [[1698, 2908]] + +[[area.polygons]] +vertices = [ + [1590, 2910], + [1590, 2920], +] + +[[area.polygons]] +vertices = [ + [1589, 2912], + [1589, 2919], +] + +[[area.polygons]] +vertices = [ + [1698, 2912], + [1698, 3073], +] + +[[area.polygons]] +vertices = [ + [1587, 2913], + [1588, 2913], + [1588, 2919], + [1587, 2919], +] + +[[area.polygons]] +vertices = [ + [1699, 2913], + [1699, 3073], +] + +[[area.polygons]] +vertices = [ + [1712, 2913], + [1718, 2913], + [1718, 3073], + [1712, 3073], +] + +[[area.polygons]] +vertices = [ + [1586, 2914], + [1586, 2920], +] + +[[area.polygons]] +vertices = [ + [1711, 2914], + [1711, 3073], +] + +[[area.polygons]] +vertices = [ + [1719, 2914], + [1720, 2914], + [1720, 3078], + [1719, 3078], +] + +[[area.polygons]] +vertices = [ + [1585, 2915], + [1585, 2920], +] + +[[area.polygons]] +vertices = [ + [1700, 2915], + [1701, 2915], + [1701, 3073], + [1700, 3073], +] + +[[area.polygons]] +vertices = [ + [1704, 2915], + [1706, 2915], + [1706, 3073], + [1704, 3073], +] + +[[area.polygons]] +vertices = [ + [1710, 2915], + [1710, 3073], +] + +[[area.polygons]] +vertices = [ + [1721, 2915], + [1721, 3080], +] + +[[area.polygons]] +vertices = [ + [1584, 2916], + [1584, 2920], +] + +[[area.polygons]] +vertices = [ + [1702, 2916], + [1703, 2916], + [1703, 3073], + [1702, 3073], +] + +[[area.polygons]] +vertices = [ + [1707, 2916], + [1709, 2916], + [1709, 3073], + [1707, 3073], +] + +[[area.polygons]] +vertices = [ + [1722, 2916], + [1722, 3081], +] + +[[area.polygons]] +vertices = [ + [1738, 2916], + [1740, 2916], + [1740, 3081], + [1738, 3081], +] + +[[area.polygons]] +vertices = [ + [1723, 2917], + [1723, 3081], +] + +[[area.polygons]] +vertices = [ + [1737, 2917], + [1737, 3081], +] + +[[area.polygons]] +vertices = [ + [1741, 2917], + [1741, 3081], +] + +[[area.polygons]] +vertices = [ + [1724, 2918], + [1724, 3081], +] + +[[area.polygons]] +vertices = [ + [1735, 2918], + [1736, 2918], + [1736, 3081], + [1735, 3081], +] + +[[area.polygons]] +vertices = [ + [1742, 2918], + [1742, 3081], +] + +[[area.polygons]] +vertices = [ + [1751, 2918], + [1753, 2918], + [1753, 3081], + [1751, 3081], +] + +[[area.polygons]] +vertices = [ + [1725, 2919], + [1725, 3081], +] + +[[area.polygons]] +vertices = [ + [1733, 2919], + [1734, 2919], + [1734, 3081], + [1733, 3081], +] + +[[area.polygons]] +vertices = [ + [1747, 2919], + [1748, 2919], + [1748, 3081], + [1747, 3081], +] + +[[area.polygons]] +vertices = [ + [1750, 2919], + [1750, 3081], +] + +[[area.polygons]] +vertices = [ + [1754, 2919], + [1754, 2923], +] + +[[area.polygons]] +vertices = [ + [1726, 2920], + [1732, 2920], + [1732, 3081], + [1726, 3081], +] + +[[area.polygons]] +vertices = [ + [1743, 2920], + [1746, 2920], + [1746, 3081], + [1743, 3081], +] + +[[area.polygons]] +vertices = [ + [1749, 2920], + [1749, 3081], +] + +[[area.polygons]] +vertices = [ + [1755, 2920], + [1755, 2922], +] + +[[area.polygons]] +vertices = [ + [1594, 2921], + [1595, 2921], +] + +[[area.polygons]] +vertices = [[1595, 2922]] + +[[area.polygons]] +vertices = [ + [1597, 2925], + [1599, 2925], + [1599, 2928], + [1597, 2928], +] + +[[area.polygons]] +vertices = [ + [1754, 2928], + [1755, 2928], + [1755, 3081], + [1754, 3081], +] + +[[area.polygons]] +vertices = [ + [1598, 2929], + [1599, 2929], + [1599, 3193], + [1598, 3193], +] + +[[area.polygons]] +vertices = [ + [1756, 2929], + [1756, 3081], +] + +[[area.polygons]] +vertices = [ + [1757, 2930], + [1757, 3081], +] + +[[area.polygons]] +vertices = [ + [1758, 2931], + [1758, 3081], +] + +[[area.polygons]] +vertices = [ + [1597, 2933], + [1597, 3192], +] + +[[area.polygons]] +vertices = [ + [1759, 2933], + [1759, 3081], +] + +[[area.polygons]] +vertices = [ + [1760, 2934], + [1763, 2934], + [1763, 3081], + [1760, 3081], +] + +[[area.polygons]] +vertices = [ + [1596, 2935], + [1596, 3191], +] + +[[area.polygons]] +vertices = [ + [1764, 2935], + [1764, 3081], +] + +[[area.polygons]] +vertices = [ + [1765, 2936], + [1765, 3081], +] + +[[area.polygons]] +vertices = [ + [1771, 2936], + [1773, 2936], + [1773, 2961], + [1771, 2961], +] + +[[area.polygons]] +vertices = [ + [1766, 2937], + [1770, 2937], + [1770, 3035], + [1766, 3035], +] + +[[area.polygons]] +vertices = [ + [1774, 2937], + [1774, 2960], +] + +[[area.polygons]] +vertices = [ + [1775, 2938], + [1775, 2960], +] + +[[area.polygons]] +vertices = [ + [1595, 2939], + [1595, 3190], +] + +[[area.polygons]] +vertices = [ + [1776, 2939], + [1778, 2939], + [1778, 2944], + [1776, 2944], +] + +[[area.polygons]] +vertices = [ + [1594, 2940], + [1594, 3189], +] + +[[area.polygons]] +vertices = [[1779, 2940]] + +[[area.polygons]] +vertices = [ + [1593, 2941], + [1593, 3188], +] + +[[area.polygons]] +vertices = [ + [1592, 2942], + [1592, 3187], +] + +[[area.polygons]] +vertices = [ + [1591, 2943], + [1591, 2950], +] + +[[area.polygons]] +vertices = [ + [1776, 2945], + [1777, 2945], + [1777, 2959], + [1776, 2959], +] + +[[area.polygons]] +vertices = [ + [1590, 2946], + [1590, 2951], +] + +[[area.polygons]] +vertices = [ + [1588, 2947], + [1589, 2947], + [1589, 2951], + [1588, 2951], +] + +[[area.polygons]] +vertices = [ + [1778, 2947], + [1778, 2958], +] + +[[area.polygons]] +vertices = [ + [1587, 2948], + [1587, 2955], +] + +[[area.polygons]] +vertices = [ + [1779, 2948], + [1779, 2955], +] + +[[area.polygons]] +vertices = [ + [1780, 2949], + [1780, 2953], +] + +[[area.polygons]] +vertices = [ + [1562, 2950], + [1565, 2950], + [1565, 2965], + [1562, 2965], +] + +[[area.polygons]] +vertices = [ + [1586, 2950], + [1586, 2952], +] + +[[area.polygons]] +vertices = [ + [1781, 2950], + [1781, 2953], +] + +[[area.polygons]] +vertices = [ + [1558, 2951], + [1561, 2951], + [1561, 3026], + [1558, 3026], +] + +[[area.polygons]] +vertices = [ + [1566, 2951], + [1573, 2951], + [1573, 2955], + [1566, 2955], +] + +[[area.polygons]] +vertices = [ + [1556, 2952], + [1557, 2952], + [1557, 3026], + [1556, 3026], +] + +[[area.polygons]] +vertices = [ + [1574, 2952], + [1574, 2954], +] + +[[area.polygons]] +vertices = [ + [1588, 2952], + [1588, 2954], +] + +[[area.polygons]] +vertices = [ + [1554, 2953], + [1555, 2953], + [1555, 3026], + [1554, 3026], +] + +[[area.polygons]] +vertices = [ + [1553, 2954], + [1553, 3026], +] + +[[area.polygons]] +vertices = [[1591, 2954]] + +[[area.polygons]] +vertices = [ + [1552, 2955], + [1552, 3026], +] + +[[area.polygons]] +vertices = [ + [1551, 2956], + [1551, 3026], +] + +[[area.polygons]] +vertices = [ + [1566, 2956], + [1571, 2956], +] + +[[area.polygons]] +vertices = [ + [1550, 2957], + [1550, 3026], +] + +[[area.polygons]] +vertices = [ + [1566, 2957], + [1570, 2957], + [1570, 2962], + [1566, 2962], +] + +[[area.polygons]] +vertices = [ + [1591, 2957], + [1591, 3186], +] + +[[area.polygons]] +vertices = [ + [1547, 2958], + [1549, 2958], + [1549, 3026], + [1547, 3026], +] + +[[area.polygons]] +vertices = [ + [1590, 2958], + [1590, 3185], +] + +[[area.polygons]] +vertices = [ + [1545, 2959], + [1546, 2959], + [1546, 3028], + [1545, 3028], +] + +[[area.polygons]] +vertices = [ + [1589, 2959], + [1589, 3039], +] + +[[area.polygons]] +vertices = [ + [1587, 2960], + [1588, 2960], + [1588, 3035], + [1587, 3035], +] + +[[area.polygons]] +vertices = [ + [1584, 2961], + [1586, 2961], + [1586, 3035], + [1584, 3035], +] + +[[area.polygons]] +vertices = [ + [1582, 2962], + [1583, 2962], + [1583, 3035], + [1582, 3035], +] + +[[area.polygons]] +vertices = [ + [1771, 2962], + [1772, 2962], +] + +[[area.polygons]] +vertices = [ + [1566, 2963], + [1569, 2963], +] + +[[area.polygons]] +vertices = [ + [1575, 2963], + [1581, 2963], + [1581, 3035], + [1575, 3035], +] + +[[area.polygons]] +vertices = [[1771, 2963]] + +[[area.polygons]] +vertices = [ + [1566, 2964], + [1568, 2964], +] + +[[area.polygons]] +vertices = [ + [1574, 2964], + [1574, 3035], +] + +[[area.polygons]] +vertices = [ + [1566, 2965], + [1567, 2965], +] + +[[area.polygons]] +vertices = [ + [1572, 2965], + [1573, 2965], + [1573, 3033], + [1572, 3033], +] + +[[area.polygons]] +vertices = [ + [1562, 2966], + [1564, 2966], +] + +[[area.polygons]] +vertices = [ + [1570, 2966], + [1571, 2966], + [1571, 3031], + [1570, 3031], +] + +[[area.polygons]] +vertices = [ + [1562, 2967], + [1563, 2967], +] + +[[area.polygons]] +vertices = [ + [1569, 2967], + [1569, 3030], +] + +[[area.polygons]] +vertices = [ + [1562, 2968], + [1562, 2969], +] + +[[area.polygons]] +vertices = [ + [1565, 2968], + [1568, 2968], + [1568, 3026], + [1565, 3026], +] + +[[area.polygons]] +vertices = [ + [1564, 2970], + [1564, 3026], +] + +[[area.polygons]] +vertices = [ + [1771, 2971], + [1771, 3033], +] + +[[area.polygons]] +vertices = [ + [1563, 2972], + [1563, 3026], +] + +[[area.polygons]] +vertices = [ + [1562, 2973], + [1562, 3026], +] + +[[area.polygons]] +vertices = [ + [1772, 2973], + [1772, 3032], +] + +[[area.polygons]] +vertices = [ + [1773, 2977], + [1773, 3032], +] + +[[area.polygons]] +vertices = [ + [1774, 2981], + [1774, 2987], +] + +[[area.polygons]] +vertices = [ + [1775, 2983], + [1775, 2985], +] + +[[area.polygons]] +vertices = [ + [1774, 2992], + [1774, 3033], +] + +[[area.polygons]] +vertices = [ + [1544, 2993], + [1544, 3030], +] + +[[area.polygons]] +vertices = [ + [1775, 2993], + [1775, 3033], +] + +[[area.polygons]] +vertices = [ + [1542, 2994], + [1543, 2994], + [1543, 3031], + [1542, 3031], +] + +[[area.polygons]] +vertices = [ + [1776, 2994], + [1777, 2994], + [1777, 3031], + [1776, 3031], +] + +[[area.polygons]] +vertices = [ + [1780, 2994], + [1781, 2994], + [1781, 3010], + [1780, 3010], +] + +[[area.polygons]] +vertices = [ + [1541, 2995], + [1541, 3033], +] + +[[area.polygons]] +vertices = [ + [1778, 2995], + [1779, 2995], + [1779, 3029], + [1778, 3029], +] + +[[area.polygons]] +vertices = [ + [1782, 2995], + [1782, 3011], +] + +[[area.polygons]] +vertices = [ + [1497, 2996], + [1498, 2996], + [1498, 3202], + [1497, 3202], +] + +[[area.polygons]] +vertices = [ + [1540, 2996], + [1540, 3034], +] + +[[area.polygons]] +vertices = [ + [1783, 2996], + [1783, 3012], +] + +[[area.polygons]] +vertices = [ + [1491, 2997], + [1496, 2997], + [1496, 3185], + [1491, 3185], +] + +[[area.polygons]] +vertices = [ + [1499, 2997], + [1499, 3204], +] + +[[area.polygons]] +vertices = [ + [1539, 2997], + [1539, 3035], +] + +[[area.polygons]] +vertices = [ + [1784, 2997], + [1784, 3011], +] + +[[area.polygons]] +vertices = [ + [1490, 2998], + [1490, 3184], +] + +[[area.polygons]] +vertices = [ + [1500, 2998], + [1501, 2998], + [1501, 3205], + [1500, 3205], +] + +[[area.polygons]] +vertices = [ + [1538, 2998], + [1538, 3188], +] + +[[area.polygons]] +vertices = [ + [1785, 2998], + [1785, 3008], +] + +[[area.polygons]] +vertices = [ + [1489, 2999], + [1489, 3183], +] + +[[area.polygons]] +vertices = [ + [1502, 2999], + [1502, 3207], +] + +[[area.polygons]] +vertices = [ + [1537, 2999], + [1537, 3188], +] + +[[area.polygons]] +vertices = [ + [1786, 2999], + [1786, 3007], +] + +[[area.polygons]] +vertices = [ + [1488, 3000], + [1488, 3135], +] + +[[area.polygons]] +vertices = [ + [1503, 3000], + [1504, 3000], + [1504, 3208], + [1503, 3208], +] + +[[area.polygons]] +vertices = [ + [1536, 3000], + [1536, 3188], +] + +[[area.polygons]] +vertices = [ + [1787, 3000], + [1787, 3005], +] + +[[area.polygons]] +vertices = [ + [1487, 3001], + [1487, 3004], +] + +[[area.polygons]] +vertices = [ + [1505, 3001], + [1505, 3209], +] + +[[area.polygons]] +vertices = [ + [1535, 3001], + [1535, 3188], +] + +[[area.polygons]] +vertices = [ + [1506, 3002], + [1507, 3002], + [1507, 3209], + [1506, 3209], +] + +[[area.polygons]] +vertices = [ + [1534, 3002], + [1534, 3188], +] + +[[area.polygons]] +vertices = [ + [1508, 3003], + [1508, 3209], +] + +[[area.polygons]] +vertices = [ + [1533, 3003], + [1533, 3188], +] + +[[area.polygons]] +vertices = [ + [1509, 3004], + [1510, 3004], + [1510, 3209], + [1509, 3209], +] + +[[area.polygons]] +vertices = [ + [1532, 3004], + [1532, 3199], +] + +[[area.polygons]] +vertices = [ + [1511, 3005], + [1511, 3209], +] + +[[area.polygons]] +vertices = [ + [1531, 3005], + [1531, 3200], +] + +[[area.polygons]] +vertices = [ + [1487, 3006], + [1487, 3135], +] + +[[area.polygons]] +vertices = [ + [1512, 3006], + [1530, 3006], + [1530, 3201], + [1512, 3201], +] + +[[area.polygons]] +vertices = [ + [1483, 3007], + [1486, 3007], + [1486, 3135], + [1483, 3135], +] + +[[area.polygons]] +vertices = [ + [1481, 3008], + [1482, 3008], + [1482, 3135], + [1481, 3135], +] + +[[area.polygons]] +vertices = [ + [1479, 3009], + [1480, 3009], + [1480, 3135], + [1479, 3135], +] + +[[area.polygons]] +vertices = [ + [1478, 3010], + [1478, 3015], +] + +[[area.polygons]] +vertices = [ + [1780, 3011], + [1780, 3013], +] + +[[area.polygons]] +vertices = [ + [1785, 3014], + [1785, 3027], +] + +[[area.polygons]] +vertices = [ + [1781, 3015], + [1784, 3015], + [1784, 3026], + [1781, 3026], +] + +[[area.polygons]] +vertices = [ + [1786, 3015], + [1786, 3028], +] + +[[area.polygons]] +vertices = [ + [1453, 3016], + [1454, 3016], + [1454, 3122], + [1453, 3122], +] + +[[area.polygons]] +vertices = [ + [1780, 3016], + [1780, 3028], +] + +[[area.polygons]] +vertices = [ + [1450, 3017], + [1452, 3017], + [1452, 3121], + [1450, 3121], +] + +[[area.polygons]] +vertices = [ + [1455, 3017], + [1455, 3124], +] + +[[area.polygons]] +vertices = [ + [1787, 3017], + [1788, 3017], + [1788, 3019], + [1787, 3019], +] + +[[area.polygons]] +vertices = [ + [1456, 3018], + [1458, 3018], + [1458, 3125], + [1456, 3125], +] + +[[area.polygons]] +vertices = [ + [1478, 3018], + [1478, 3135], +] + +[[area.polygons]] +vertices = [ + [1789, 3018], + [1789, 3020], +] + +[[area.polygons]] +vertices = [ + [1449, 3019], + [1449, 3121], +] + +[[area.polygons]] +vertices = [ + [1459, 3019], + [1459, 3021], +] + +[[area.polygons]] +vertices = [ + [1477, 3019], + [1477, 3135], +] + +[[area.polygons]] +vertices = [[1790, 3019]] + +[[area.polygons]] +vertices = [ + [1448, 3020], + [1448, 3121], +] + +[[area.polygons]] +vertices = [[1788, 3020]] + +[[area.polygons]] +vertices = [ + [1447, 3022], + [1447, 3121], +] + +[[area.polygons]] +vertices = [ + [1470, 3022], + [1471, 3022], + [1471, 3135], + [1470, 3135], +] + +[[area.polygons]] +vertices = [ + [1476, 3022], + [1476, 3135], +] + +[[area.polygons]] +vertices = [ + [1787, 3022], + [1788, 3022], + [1788, 3027], + [1787, 3027], +] + +[[area.polygons]] +vertices = [ + [1459, 3023], + [1459, 3128], +] + +[[area.polygons]] +vertices = [ + [1469, 3023], + [1469, 3135], +] + +[[area.polygons]] +vertices = [ + [1472, 3023], + [1473, 3023], + [1473, 3135], + [1472, 3135], +] + +[[area.polygons]] +vertices = [[1789, 3023]] + +[[area.polygons]] +vertices = [ + [1446, 3024], + [1446, 3130], +] + +[[area.polygons]] +vertices = [ + [1474, 3024], + [1475, 3024], + [1475, 3135], + [1474, 3135], +] + +[[area.polygons]] +vertices = [ + [1445, 3025], + [1445, 3130], +] + +[[area.polygons]] +vertices = [ + [1460, 3025], + [1460, 3129], +] + +[[area.polygons]] +vertices = [ + [1468, 3025], + [1468, 3135], +] + +[[area.polygons]] +vertices = [ + [1443, 3026], + [1444, 3026], + [1444, 3130], + [1443, 3130], +] + +[[area.polygons]] +vertices = [ + [1461, 3026], + [1463, 3026], + [1463, 3130], + [1461, 3130], +] + +[[area.polygons]] +vertices = [ + [1467, 3026], + [1467, 3135], +] + +[[area.polygons]] +vertices = [ + [1442, 3027], + [1442, 3130], +] + +[[area.polygons]] +vertices = [ + [1464, 3027], + [1466, 3027], + [1466, 3133], + [1464, 3133], +] + +[[area.polygons]] +vertices = [[1547, 3027]] + +[[area.polygons]] +vertices = [ + [1566, 3027], + [1568, 3027], +] + +[[area.polygons]] +vertices = [ + [1567, 3028], + [1568, 3028], +] + +[[area.polygons]] +vertices = [[1787, 3028]] + +[[area.polygons]] +vertices = [ + [1441, 3029], + [1441, 3031], +] + +[[area.polygons]] +vertices = [[1545, 3029]] + +[[area.polygons]] +vertices = [[1568, 3029]] + +[[area.polygons]] +vertices = [[1778, 3030]] + +[[area.polygons]] +vertices = [[1542, 3032]] + +[[area.polygons]] +vertices = [[1571, 3032]] + +[[area.polygons]] +vertices = [[1776, 3032]] + +[[area.polygons]] +vertices = [ + [1441, 3033], + [1441, 3130], +] + +[[area.polygons]] +vertices = [[1573, 3034]] + +[[area.polygons]] +vertices = [ + [1440, 3035], + [1440, 3130], +] + +[[area.polygons]] +vertices = [ + [1439, 3036], + [1439, 3130], +] + +[[area.polygons]] +vertices = [ + [1588, 3036], + [1588, 3039], +] + +[[area.polygons]] +vertices = [ + [1766, 3036], + [1769, 3036], +] + +[[area.polygons]] +vertices = [ + [1438, 3037], + [1438, 3130], +] + +[[area.polygons]] +vertices = [ + [1766, 3037], + [1767, 3037], + [1767, 3038], + [1766, 3038], +] + +[[area.polygons]] +vertices = [ + [1437, 3038], + [1437, 3130], +] + +[[area.polygons]] +vertices = [[1766, 3039]] + +[[area.polygons]] +vertices = [ + [1770, 3039], + [1771, 3039], + [1771, 3081], + [1770, 3081], +] + +[[area.polygons]] +vertices = [ + [1766, 3041], + [1766, 3081], +] + +[[area.polygons]] +vertices = [ + [1767, 3042], + [1769, 3042], + [1769, 3081], + [1767, 3081], +] + +[[area.polygons]] +vertices = [ + [1436, 3043], + [1436, 3130], +] + +[[area.polygons]] +vertices = [ + [1435, 3045], + [1435, 3130], +] + +[[area.polygons]] +vertices = [ + [1434, 3046], + [1434, 3051], +] + +[[area.polygons]] +vertices = [ + [1783, 3047], + [1788, 3047], + [1788, 3049], + [1783, 3049], +] + +[[area.polygons]] +vertices = [ + [1796, 3047], + [1796, 3073], +] + +[[area.polygons]] +vertices = [ + [1772, 3048], + [1777, 3048], + [1777, 3049], + [1772, 3049], +] + +[[area.polygons]] +vertices = [ + [1782, 3048], + [1782, 3078], +] + +[[area.polygons]] +vertices = [ + [1794, 3048], + [1795, 3048], + [1795, 3073], + [1794, 3073], +] + +[[area.polygons]] +vertices = [ + [1797, 3048], + [1797, 3073], +] + +[[area.polygons]] +vertices = [ + [1781, 3049], + [1781, 3079], +] + +[[area.polygons]] +vertices = [ + [1792, 3049], + [1793, 3049], + [1793, 3073], + [1792, 3073], +] + +[[area.polygons]] +vertices = [ + [1798, 3049], + [1798, 3073], +] + +[[area.polygons]] +vertices = [ + [1780, 3050], + [1780, 3080], +] + +[[area.polygons]] +vertices = [ + [1783, 3050], + [1787, 3050], + [1787, 3073], + [1783, 3073], +] + +[[area.polygons]] +vertices = [ + [1791, 3050], + [1791, 3073], +] + +[[area.polygons]] +vertices = [ + [1799, 3050], + [1803, 3050], + [1803, 3073], + [1799, 3073], +] + +[[area.polygons]] +vertices = [ + [1779, 3051], + [1779, 3081], +] + +[[area.polygons]] +vertices = [ + [1788, 3051], + [1790, 3051], + [1790, 3073], + [1788, 3073], +] + +[[area.polygons]] +vertices = [ + [1804, 3051], + [1804, 3073], +] + +[[area.polygons]] +vertices = [ + [1589, 3052], + [1589, 3184], +] + +[[area.polygons]] +vertices = [ + [1772, 3052], + [1778, 3052], + [1778, 3081], + [1772, 3081], +] + +[[area.polygons]] +vertices = [ + [1805, 3052], + [1805, 3073], +] + +[[area.polygons]] +vertices = [ + [1811, 3053], + [1815, 3053], + [1815, 3071], + [1811, 3071], +] + +[[area.polygons]] +vertices = [ + [1806, 3054], + [1806, 3073], +] + +[[area.polygons]] +vertices = [ + [1809, 3054], + [1810, 3054], + [1810, 3073], + [1809, 3073], +] + +[[area.polygons]] +vertices = [ + [1816, 3054], + [1817, 3054], + [1817, 3071], + [1816, 3071], +] + +[[area.polygons]] +vertices = [ + [1807, 3055], + [1808, 3055], + [1808, 3073], + [1807, 3073], +] + +[[area.polygons]] +vertices = [ + [1818, 3055], + [1819, 3055], + [1819, 3071], + [1818, 3071], +] + +[[area.polygons]] +vertices = [ + [1826, 3055], + [1827, 3055], + [1827, 3071], + [1826, 3071], +] + +[[area.polygons]] +vertices = [ + [1583, 3056], + [1588, 3056], + [1588, 3179], + [1583, 3179], +] + +[[area.polygons]] +vertices = [ + [1820, 3056], + [1820, 3071], +] + +[[area.polygons]] +vertices = [ + [1823, 3056], + [1825, 3056], + [1825, 3071], + [1823, 3071], +] + +[[area.polygons]] +vertices = [ + [1828, 3056], + [1828, 3071], +] + +[[area.polygons]] +vertices = [ + [1832, 3056], + [1832, 3071], +] + +[[area.polygons]] +vertices = [ + [1434, 3057], + [1434, 3130], +] + +[[area.polygons]] +vertices = [ + [1582, 3057], + [1582, 3179], +] + +[[area.polygons]] +vertices = [ + [1821, 3057], + [1822, 3057], + [1822, 3071], + [1821, 3071], +] + +[[area.polygons]] +vertices = [ + [1829, 3057], + [1831, 3057], + [1831, 3071], + [1829, 3071], +] + +[[area.polygons]] +vertices = [ + [1833, 3057], + [1833, 3071], +] + +[[area.polygons]] +vertices = [ + [1539, 3058], + [1539, 3188], +] + +[[area.polygons]] +vertices = [ + [1581, 3058], + [1581, 3179], +] + +[[area.polygons]] +vertices = [ + [1834, 3058], + [1834, 3071], +] + +[[area.polygons]] +vertices = [ + [1540, 3059], + [1540, 3188], +] + +[[area.polygons]] +vertices = [ + [1572, 3059], + [1580, 3059], + [1580, 3179], + [1572, 3179], +] + +[[area.polygons]] +vertices = [ + [1433, 3060], + [1433, 3130], +] + +[[area.polygons]] +vertices = [ + [1541, 3060], + [1541, 3188], +] + +[[area.polygons]] +vertices = [ + [1571, 3060], + [1571, 3179], +] + +[[area.polygons]] +vertices = [ + [1835, 3060], + [1835, 3071], +] + +[[area.polygons]] +vertices = [ + [1432, 3061], + [1432, 3130], +] + +[[area.polygons]] +vertices = [ + [1542, 3061], + [1542, 3188], +] + +[[area.polygons]] +vertices = [ + [1570, 3061], + [1570, 3179], +] + +[[area.polygons]] +vertices = [ + [1839, 3061], + [1839, 3072], +] + +[[area.polygons]] +vertices = [ + [1543, 3062], + [1543, 3188], +] + +[[area.polygons]] +vertices = [ + [1569, 3062], + [1569, 3179], +] + +[[area.polygons]] +vertices = [ + [1836, 3062], + [1838, 3062], + [1838, 3071], + [1836, 3071], +] + +[[area.polygons]] +vertices = [ + [1840, 3062], + [1841, 3062], + [1841, 3073], + [1840, 3073], +] + +[[area.polygons]] +vertices = [ + [1431, 3063], + [1431, 3130], +] + +[[area.polygons]] +vertices = [ + [1544, 3063], + [1544, 3188], +] + +[[area.polygons]] +vertices = [ + [1568, 3063], + [1568, 3179], +] + +[[area.polygons]] +vertices = [ + [1842, 3063], + [1842, 3073], +] + +[[area.polygons]] +vertices = [ + [1545, 3064], + [1545, 3188], +] + +[[area.polygons]] +vertices = [ + [1567, 3064], + [1567, 3180], +] + +[[area.polygons]] +vertices = [ + [1843, 3064], + [1844, 3064], + [1844, 3073], + [1843, 3073], +] + +[[area.polygons]] +vertices = [ + [1546, 3065], + [1566, 3065], + [1566, 3181], + [1546, 3181], +] + +[[area.polygons]] +vertices = [ + [1845, 3065], + [1845, 3073], +] + +[[area.polygons]] +vertices = [ + [1430, 3066], + [1430, 3130], +] + +[[area.polygons]] +vertices = [ + [1846, 3066], + [1846, 3073], +] + +[[area.polygons]] +vertices = [ + [1429, 3067], + [1429, 3130], +] + +[[area.polygons]] +vertices = [ + [1847, 3067], + [1849, 3067], + [1849, 3073], + [1847, 3073], +] + +[[area.polygons]] +vertices = [ + [1850, 3068], + [1851, 3068], + [1851, 3073], + [1850, 3073], +] + +[[area.polygons]] +vertices = [ + [1428, 3069], + [1428, 3130], +] + +[[area.polygons]] +vertices = [ + [1852, 3069], + [1852, 3073], +] + +[[area.polygons]] +vertices = [ + [1427, 3070], + [1427, 3130], +] + +[[area.polygons]] +vertices = [ + [1426, 3071], + [1426, 3130], +] + +[[area.polygons]] +vertices = [[1811, 3072]] + +[[area.polygons]] +vertices = [ + [1853, 3072], + [1853, 3073], +] + +[[area.polygons]] +vertices = [ + [1425, 3073], + [1425, 3130], +] + +[[area.polygons]] +vertices = [[1854, 3073]] + +[[area.polygons]] +vertices = [ + [1424, 3074], + [1424, 3122], +] + +[[area.polygons]] +vertices = [ + [1715, 3074], + [1718, 3074], +] + +[[area.polygons]] +vertices = [ + [1783, 3074], + [1786, 3074], +] + +[[area.polygons]] +vertices = [ + [1423, 3075], + [1423, 3122], +] + +[[area.polygons]] +vertices = [[1657, 3075]] + +[[area.polygons]] +vertices = [ + [1716, 3075], + [1718, 3075], +] + +[[area.polygons]] +vertices = [ + [1783, 3075], + [1785, 3075], +] + +[[area.polygons]] +vertices = [ + [1421, 3076], + [1422, 3076], + [1422, 3122], + [1421, 3122], +] + +[[area.polygons]] +vertices = [ + [1717, 3076], + [1718, 3076], +] + +[[area.polygons]] +vertices = [ + [1783, 3076], + [1784, 3076], +] + +[[area.polygons]] +vertices = [ + [1420, 3077], + [1420, 3122], +] + +[[area.polygons]] +vertices = [ + [1652, 3077], + [1655, 3077], +] + +[[area.polygons]] +vertices = [[1718, 3077]] + +[[area.polygons]] +vertices = [[1783, 3077]] + +[[area.polygons]] +vertices = [ + [1419, 3078], + [1419, 3122], +] + +[[area.polygons]] +vertices = [ + [1652, 3078], + [1654, 3078], +] + +[[area.polygons]] +vertices = [ + [1652, 3079], + [1653, 3079], +] + +[[area.polygons]] +vertices = [[1720, 3079]] + +[[area.polygons]] +vertices = [ + [1418, 3080], + [1418, 3122], +] + +[[area.polygons]] +vertices = [[1652, 3080]] + +[[area.polygons]] +vertices = [ + [1417, 3081], + [1417, 3142], +] + +[[area.polygons]] +vertices = [ + [1416, 3082], + [1416, 3143], +] + +[[area.polygons]] +vertices = [ + [1626, 3082], + [1628, 3082], +] + +[[area.polygons]] +vertices = [ + [1415, 3083], + [1415, 3144], +] + +[[area.polygons]] +vertices = [ + [1626, 3083], + [1627, 3083], +] + +[[area.polygons]] +vertices = [ + [1414, 3084], + [1414, 3145], +] + +[[area.polygons]] +vertices = [[1626, 3084]] + +[[area.polygons]] +vertices = [ + [1412, 3085], + [1413, 3085], + [1413, 3196], + [1412, 3196], +] + +[[area.polygons]] +vertices = [ + [1623, 3086], + [1624, 3086], +] + +[[area.polygons]] +vertices = [[1623, 3087]] + +[[area.polygons]] +vertices = [ + [1411, 3088], + [1411, 3195], +] + +[[area.polygons]] +vertices = [[1621, 3089]] + +[[area.polygons]] +vertices = [ + [1410, 3090], + [1410, 3194], +] + +[[area.polygons]] +vertices = [ + [1408, 3091], + [1409, 3091], + [1409, 3191], + [1408, 3191], +] + +[[area.polygons]] +vertices = [ + [1405, 3092], + [1407, 3092], + [1407, 3184], + [1405, 3184], +] + +[[area.polygons]] +vertices = [ + [1403, 3093], + [1404, 3093], + [1404, 3179], + [1403, 3179], +] + +[[area.polygons]] +vertices = [ + [1402, 3094], + [1402, 3178], +] + +[[area.polygons]] +vertices = [ + [1400, 3095], + [1401, 3095], + [1401, 3176], + [1400, 3176], +] + +[[area.polygons]] +vertices = [ + [1398, 3096], + [1399, 3096], + [1399, 3176], + [1398, 3176], +] + +[[area.polygons]] +vertices = [ + [1396, 3097], + [1397, 3097], + [1397, 3175], + [1396, 3175], +] + +[[area.polygons]] +vertices = [ + [1395, 3098], + [1395, 3174], +] + +[[area.polygons]] +vertices = [ + [1393, 3099], + [1394, 3099], + [1394, 3140], + [1393, 3140], +] + +[[area.polygons]] +vertices = [ + [1391, 3100], + [1392, 3100], + [1392, 3134], + [1391, 3134], +] + +[[area.polygons]] +vertices = [ + [1390, 3101], + [1390, 3132], +] + +[[area.polygons]] +vertices = [ + [1389, 3103], + [1389, 3130], +] + +[[area.polygons]] +vertices = [ + [1388, 3104], + [1388, 3129], +] + +[[area.polygons]] +vertices = [ + [1387, 3106], + [1387, 3128], +] + +[[area.polygons]] +vertices = [ + [1386, 3107], + [1386, 3126], +] + +[[area.polygons]] +vertices = [ + [1385, 3109], + [1385, 3124], +] + +[[area.polygons]] +vertices = [ + [1384, 3111], + [1384, 3120], +] + +[[area.polygons]] +vertices = [ + [1383, 3114], + [1383, 3116], +] + +[[area.polygons]] +vertices = [ + [1615, 3122], + [1619, 3122], +] + +[[area.polygons]] +vertices = [[1454, 3123]] + +[[area.polygons]] +vertices = [ + [1615, 3123], + [1618, 3123], +] + +[[area.polygons]] +vertices = [ + [1615, 3124], + [1617, 3124], +] + +[[area.polygons]] +vertices = [ + [1615, 3125], + [1616, 3125], +] + +[[area.polygons]] +vertices = [ + [1457, 3126], + [1458, 3126], +] + +[[area.polygons]] +vertices = [[1615, 3126]] + +[[area.polygons]] +vertices = [[1458, 3127]] + +[[area.polygons]] +vertices = [[1613, 3128]] + +[[area.polygons]] +vertices = [ + [1462, 3131], + [1463, 3131], +] + +[[area.polygons]] +vertices = [[1463, 3132]] + +[[area.polygons]] +vertices = [ + [1465, 3134], + [1466, 3134], + [1466, 3135], + [1465, 3135], +] + +[[area.polygons]] +vertices = [ + [1392, 3135], + [1392, 3137], +] + +[[area.polygons]] +vertices = [ + [1394, 3141], + [1394, 3174], +] + +[[area.polygons]] +vertices = [ + [1414, 3147], + [1414, 3198], +] + +[[area.polygons]] +vertices = [ + [1415, 3148], + [1415, 3199], +] + +[[area.polygons]] +vertices = [ + [1393, 3149], + [1393, 3173], +] + +[[area.polygons]] +vertices = [ + [1416, 3149], + [1416, 3200], +] + +[[area.polygons]] +vertices = [ + [1417, 3150], + [1417, 3201], +] + +[[area.polygons]] +vertices = [ + [1418, 3151], + [1418, 3202], +] + +[[area.polygons]] +vertices = [ + [1392, 3152], + [1392, 3172], +] + +[[area.polygons]] +vertices = [ + [1419, 3152], + [1419, 3203], +] + +[[area.polygons]] +vertices = [ + [1391, 3153], + [1391, 3171], +] + +[[area.polygons]] +vertices = [ + [1420, 3153], + [1420, 3203], +] + +[[area.polygons]] +vertices = [ + [1421, 3154], + [1421, 3203], +] + +[[area.polygons]] +vertices = [ + [1390, 3155], + [1390, 3169], +] + +[[area.polygons]] +vertices = [ + [1422, 3155], + [1422, 3203], +] + +[[area.polygons]] +vertices = [ + [1423, 3156], + [1423, 3203], +] + +[[area.polygons]] +vertices = [ + [1424, 3157], + [1424, 3203], +] + +[[area.polygons]] +vertices = [ + [1389, 3158], + [1389, 3167], +] + +[[area.polygons]] +vertices = [ + [1388, 3162], + [1388, 3165], +] + +[[area.polygons]] +vertices = [ + [1613, 3167], + [1613, 3199], +] + +[[area.polygons]] +vertices = [ + [1614, 3168], + [1614, 3199], +] + +[[area.polygons]] +vertices = [ + [1615, 3169], + [1615, 3199], +] + +[[area.polygons]] +vertices = [ + [1616, 3170], + [1616, 3199], +] + +[[area.polygons]] +vertices = [ + [1617, 3171], + [1617, 3199], +] + +[[area.polygons]] +vertices = [ + [1618, 3172], + [1618, 3199], +] + +[[area.polygons]] +vertices = [ + [1619, 3173], + [1619, 3199], +] + +[[area.polygons]] +vertices = [ + [1620, 3174], + [1620, 3199], +] + +[[area.polygons]] +vertices = [ + [1621, 3175], + [1621, 3199], +] + +[[area.polygons]] +vertices = [ + [1622, 3176], + [1622, 3199], +] + +[[area.polygons]] +vertices = [[1401, 3177]] + +[[area.polygons]] +vertices = [ + [1623, 3177], + [1623, 3199], +] + +[[area.polygons]] +vertices = [ + [1624, 3178], + [1624, 3199], +] + +[[area.polygons]] +vertices = [ + [1625, 3179], + [1625, 3199], +] + +[[area.polygons]] +vertices = [[1404, 3180]] + +[[area.polygons]] +vertices = [ + [1585, 3180], + [1588, 3180], +] + +[[area.polygons]] +vertices = [ + [1626, 3180], + [1626, 3199], +] + +[[area.polygons]] +vertices = [ + [1586, 3181], + [1588, 3181], +] + +[[area.polygons]] +vertices = [ + [1627, 3181], + [1627, 3199], +] + +[[area.polygons]] +vertices = [ + [1546, 3182], + [1565, 3182], +] + +[[area.polygons]] +vertices = [ + [1587, 3182], + [1588, 3182], +] + +[[area.polygons]] +vertices = [ + [1628, 3182], + [1628, 3199], +] + +[[area.polygons]] +vertices = [ + [1546, 3183], + [1564, 3183], +] + +[[area.polygons]] +vertices = [[1588, 3183]] + +[[area.polygons]] +vertices = [ + [1629, 3183], + [1651, 3183], + [1651, 3199], + [1629, 3199], +] + +[[area.polygons]] +vertices = [ + [1709, 3183], + [1736, 3183], + [1736, 3185], + [1709, 3185], +] + +[[area.polygons]] +vertices = [ + [1546, 3184], + [1563, 3184], +] + +[[area.polygons]] +vertices = [ + [1652, 3184], + [1652, 3199], +] + +[[area.polygons]] +vertices = [ + [1708, 3184], + [1708, 3195], +] + +[[area.polygons]] +vertices = [ + [1737, 3184], + [1737, 3188], +] + +[[area.polygons]] +vertices = [ + [1406, 3185], + [1407, 3185], + [1407, 3186], + [1406, 3186], +] + +[[area.polygons]] +vertices = [ + [1546, 3185], + [1562, 3185], +] + +[[area.polygons]] +vertices = [ + [1653, 3185], + [1653, 3199], +] + +[[area.polygons]] +vertices = [ + [1707, 3185], + [1707, 3196], +] + +[[area.polygons]] +vertices = [ + [1738, 3185], + [1738, 3189], +] + +[[area.polygons]] +vertices = [ + [1492, 3186], + [1496, 3186], +] + +[[area.polygons]] +vertices = [ + [1546, 3186], + [1561, 3186], +] + +[[area.polygons]] +vertices = [ + [1654, 3186], + [1654, 3199], +] + +[[area.polygons]] +vertices = [ + [1706, 3186], + [1706, 3196], +] + +[[area.polygons]] +vertices = [ + [1709, 3186], + [1720, 3186], +] + +[[area.polygons]] +vertices = [ + [1728, 3186], + [1736, 3186], +] + +[[area.polygons]] +vertices = [ + [1739, 3186], + [1739, 3190], +] + +[[area.polygons]] +vertices = [ + [1407, 3187], + [1407, 3189], +] + +[[area.polygons]] +vertices = [ + [1493, 3187], + [1496, 3187], + [1496, 3198], + [1493, 3198], +] + +[[area.polygons]] +vertices = [ + [1546, 3187], + [1560, 3187], +] + +[[area.polygons]] +vertices = [ + [1655, 3187], + [1655, 3199], +] + +[[area.polygons]] +vertices = [ + [1705, 3187], + [1705, 3196], +] + +[[area.polygons]] +vertices = [ + [1709, 3187], + [1715, 3187], +] + +[[area.polygons]] +vertices = [ + [1730, 3187], + [1736, 3187], +] + +[[area.polygons]] +vertices = [ + [1740, 3187], + [1740, 3192], +] + +[[area.polygons]] +vertices = [ + [1546, 3188], + [1559, 3188], +] + +[[area.polygons]] +vertices = [ + [1656, 3188], + [1656, 3199], +] + +[[area.polygons]] +vertices = [ + [1704, 3188], + [1704, 3195], +] + +[[area.polygons]] +vertices = [ + [1709, 3188], + [1712, 3188], +] + +[[area.polygons]] +vertices = [ + [1732, 3188], + [1733, 3188], + [1733, 3189], + [1732, 3189], +] + +[[area.polygons]] +vertices = [ + [1741, 3188], + [1741, 3192], +] + +[[area.polygons]] +vertices = [ + [1657, 3189], + [1657, 3199], +] + +[[area.polygons]] +vertices = [ + [1703, 3189], + [1703, 3194], +] + +[[area.polygons]] +vertices = [ + [1709, 3189], + [1710, 3189], + [1710, 3190], + [1709, 3190], +] + +[[area.polygons]] +vertices = [ + [1742, 3189], + [1742, 3192], +] + +[[area.polygons]] +vertices = [ + [1658, 3190], + [1658, 3199], +] + +[[area.polygons]] +vertices = [ + [1702, 3190], + [1702, 3194], +] + +[[area.polygons]] +vertices = [[1732, 3190]] + +[[area.polygons]] +vertices = [ + [1743, 3190], + [1743, 3192], +] + +[[area.polygons]] +vertices = [ + [1425, 3191], + [1433, 3191], + [1433, 3203], + [1425, 3203], +] + +[[area.polygons]] +vertices = [ + [1659, 3191], + [1701, 3191], + [1701, 3194], + [1659, 3194], +] + +[[area.polygons]] +vertices = [ + [1709, 3191], + [1709, 3192], +] + +[[area.polygons]] +vertices = [ + [1744, 3191], + [1750, 3191], +] + +[[area.polygons]] +vertices = [ + [1409, 3192], + [1409, 3193], +] + +[[area.polygons]] +vertices = [ + [1744, 3192], + [1748, 3192], +] + +[[area.polygons]] +vertices = [[1599, 3194]] + +[[area.polygons]] +vertices = [ + [1659, 3195], + [1699, 3195], +] + +[[area.polygons]] +vertices = [ + [1601, 3196], + [1602, 3196], +] + +[[area.polygons]] +vertices = [ + [1659, 3196], + [1679, 3196], +] + +[[area.polygons]] +vertices = [ + [1692, 3196], + [1697, 3196], +] + +[[area.polygons]] +vertices = [[1413, 3197]] + +[[area.polygons]] +vertices = [[1602, 3197]] + +[[area.polygons]] +vertices = [ + [1659, 3197], + [1677, 3197], +] + +[[area.polygons]] +vertices = [ + [1659, 3198], + [1675, 3198], +] + +[[area.polygons]] +vertices = [ + [1494, 3199], + [1496, 3199], +] + +[[area.polygons]] +vertices = [ + [1659, 3199], + [1673, 3199], +] + +[[area.polygons]] +vertices = [ + [1495, 3200], + [1496, 3200], +] + +[[area.polygons]] +vertices = [[1496, 3201]] + +[[area.polygons]] +vertices = [ + [1512, 3202], + [1529, 3202], +] + +[[area.polygons]] +vertices = [[1498, 3203]] + +[[area.polygons]] +vertices = [ + [1512, 3203], + [1528, 3203], +] + +[[area.polygons]] +vertices = [ + [1512, 3204], + [1527, 3204], +] + +[[area.polygons]] +vertices = [ + [1512, 3205], + [1526, 3205], +] + +[[area.polygons]] +vertices = [[1501, 3206]] + +[[area.polygons]] +vertices = [ + [1512, 3206], + [1525, 3206], +] + +[[area.polygons]] +vertices = [ + [1512, 3207], + [1524, 3207], +] + +[[area.polygons]] +vertices = [ + [1512, 3208], + [1523, 3208], +] + +[[area.polygons]] +vertices = [[1504, 3209]] + +[[area.polygons]] +vertices = [ + [1512, 3209], + [1522, 3209], +] diff --git a/.data/raw-cache/map/area/aye_car_rum_ba.toml b/.data/raw-cache/map/area/aye_car_rum_ba.toml new file mode 100644 index 000000000..683fa8d34 --- /dev/null +++ b/.data/raw-cache/map/area/aye_car_rum_ba.toml @@ -0,0 +1,16 @@ +# Aye Car Rum Ba music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Aye Car Rum Ba. +# Bounds x 2112-2175, z 5056-5119; 4096 tiles as 1 rectangles. + +[[area]] +name = "Aye Car Rum Ba" +area_id = "area.aye_car_rum_ba" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2112, 5056], + [2175, 5056], + [2175, 5119], + [2112, 5119], +] diff --git a/.data/raw-cache/map/area/aztec.toml b/.data/raw-cache/map/area/aztec.toml new file mode 100644 index 000000000..2ee14f65d --- /dev/null +++ b/.data/raw-cache/map/area/aztec.toml @@ -0,0 +1,16 @@ +# Aztec music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Aztec. +# Bounds x 2752-2815, z 9536-9599; 4096 tiles as 1 rectangles. + +[[area]] +name = "Aztec" +area_id = "area.aztec" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 9536], + [2815, 9536], + [2815, 9599], + [2752, 9599], +] diff --git a/.data/raw-cache/map/area/back_to_life.toml b/.data/raw-cache/map/area/back_to_life.toml new file mode 100644 index 000000000..134273980 --- /dev/null +++ b/.data/raw-cache/map/area/back_to_life.toml @@ -0,0 +1,16 @@ +# Back to Life music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Back to Life. +# Bounds x 2784-2815, z 5152-5183; 1024 tiles as 1 rectangles. + +[[area]] +name = "Back to Life" +area_id = "area.back_to_life" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2784, 5152], + [2815, 5152], + [2815, 5183], + [2784, 5183], +] diff --git a/.data/raw-cache/map/area/background.toml b/.data/raw-cache/map/area/background.toml new file mode 100644 index 000000000..480bc7d90 --- /dev/null +++ b/.data/raw-cache/map/area/background.toml @@ -0,0 +1,553 @@ +# Background music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Background. +# Bounds x 1952-2878, z 3328-5023; 4767 tiles as 87 rectangles. + +[[area]] +name = "Background" +area_id = "area.background" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2824, 3328], + [2841, 3328], + [2841, 3332], + [2824, 3332], +] + +[[area.polygons]] +vertices = [ + [2856, 3328], + [2856, 3390], +] + +[[area.polygons]] +vertices = [ + [2815, 3329], + [2816, 3329], + [2816, 3389], + [2815, 3389], +] + +[[area.polygons]] +vertices = [ + [2823, 3329], + [2823, 3331], +] + +[[area.polygons]] +vertices = [ + [2846, 3329], + [2847, 3329], + [2847, 3391], + [2846, 3391], +] + +[[area.polygons]] +vertices = [ + [2854, 3329], + [2855, 3329], + [2855, 3390], + [2854, 3390], +] + +[[area.polygons]] +vertices = [ + [2857, 3329], + [2857, 3390], +] + +[[area.polygons]] +vertices = [ + [2814, 3330], + [2814, 3389], +] + +[[area.polygons]] +vertices = [ + [2817, 3330], + [2817, 3388], +] + +[[area.polygons]] +vertices = [ + [2844, 3330], + [2845, 3330], + [2845, 3391], + [2844, 3391], +] + +[[area.polygons]] +vertices = [ + [2848, 3330], + [2849, 3330], + [2849, 3390], + [2848, 3390], +] + +[[area.polygons]] +vertices = [ + [2853, 3330], + [2853, 3391], +] + +[[area.polygons]] +vertices = [ + [2858, 3330], + [2858, 3390], +] + +[[area.polygons]] +vertices = [ + [2812, 3331], + [2813, 3331], + [2813, 3388], + [2812, 3388], +] + +[[area.polygons]] +vertices = [ + [2818, 3331], + [2819, 3331], + [2819, 3387], + [2818, 3387], +] + +[[area.polygons]] +vertices = [ + [2843, 3331], + [2843, 3392], +] + +[[area.polygons]] +vertices = [ + [2850, 3331], + [2852, 3331], + [2852, 3391], + [2850, 3391], +] + +[[area.polygons]] +vertices = [ + [2859, 3331], + [2859, 3358], +] + +[[area.polygons]] +vertices = [ + [2862, 3331], + [2863, 3331], + [2863, 3343], + [2862, 3343], +] + +[[area.polygons]] +vertices = [ + [2806, 3332], + [2808, 3332], + [2808, 3360], + [2806, 3360], +] + +[[area.polygons]] +vertices = [ + [2811, 3332], + [2811, 3388], +] + +[[area.polygons]] +vertices = [ + [2820, 3332], + [2820, 3387], +] + +[[area.polygons]] +vertices = [[2842, 3332]] + +[[area.polygons]] +vertices = [ + [2860, 3332], + [2861, 3332], + [2861, 3358], + [2860, 3358], +] + +[[area.polygons]] +vertices = [ + [2864, 3332], + [2866, 3332], + [2866, 3341], + [2864, 3341], +] + +[[area.polygons]] +vertices = [ + [2874, 3332], + [2878, 3332], + [2878, 3341], + [2874, 3341], +] + +[[area.polygons]] +vertices = [ + [2805, 3333], + [2805, 3360], +] + +[[area.polygons]] +vertices = [ + [2809, 3333], + [2810, 3333], + [2810, 3362], + [2809, 3362], +] + +[[area.polygons]] +vertices = [ + [2823, 3333], + [2823, 3384], +] + +[[area.polygons]] +vertices = [ + [2834, 3333], + [2834, 3390], +] + +[[area.polygons]] +vertices = [ + [2867, 3333], + [2867, 3340], +] + +[[area.polygons]] +vertices = [ + [2821, 3334], + [2822, 3334], + [2822, 3385], + [2821, 3385], +] + +[[area.polygons]] +vertices = [ + [2824, 3334], + [2824, 3383], +] + +[[area.polygons]] +vertices = [ + [2868, 3334], + [2868, 3339], +] + +[[area.polygons]] +vertices = [ + [2804, 3335], + [2804, 3360], +] + +[[area.polygons]] +vertices = [ + [2825, 3335], + [2833, 3335], + [2833, 3383], + [2825, 3383], +] + +[[area.polygons]] +vertices = [ + [2835, 3335], + [2842, 3335], + [2842, 3390], + [2835, 3390], +] + +[[area.polygons]] +vertices = [ + [2869, 3336], + [2873, 3336], + [2873, 3337], + [2869, 3337], +] + +[[area.polygons]] +vertices = [ + [2803, 3341], + [2803, 3349], +] + +[[area.polygons]] +vertices = [ + [2802, 3342], + [2802, 3348], +] + +[[area.polygons]] +vertices = [[2864, 3342]] + +[[area.polygons]] +vertices = [ + [2862, 3344], + [2862, 3357], +] + +[[area.polygons]] +vertices = [ + [2801, 3345], + [2801, 3346], +] + +[[area.polygons]] +vertices = [ + [2863, 3347], + [2865, 3347], + [2865, 3354], + [2863, 3354], +] + +[[area.polygons]] +vertices = [ + [2866, 3349], + [2866, 3353], +] + +[[area.polygons]] +vertices = [ + [2867, 3351], + [2867, 3352], +] + +[[area.polygons]] +vertices = [ + [2803, 3352], + [2803, 3360], +] + +[[area.polygons]] +vertices = [ + [2802, 3353], + [2802, 3359], +] + +[[area.polygons]] +vertices = [ + [2863, 3355], + [2864, 3355], +] + +[[area.polygons]] +vertices = [[2863, 3356]] + +[[area.polygons]] +vertices = [ + [2801, 3357], + [2801, 3359], +] + +[[area.polygons]] +vertices = [ + [2807, 3361], + [2808, 3361], +] + +[[area.polygons]] +vertices = [ + [2810, 3363], + [2810, 3387], +] + +[[area.polygons]] +vertices = [ + [2862, 3363], + [2864, 3363], + [2864, 3388], + [2862, 3388], +] + +[[area.polygons]] +vertices = [ + [2809, 3364], + [2809, 3387], +] + +[[area.polygons]] +vertices = [ + [2865, 3364], + [2865, 3388], +] + +[[area.polygons]] +vertices = [ + [2861, 3365], + [2861, 3389], +] + +[[area.polygons]] +vertices = [ + [2866, 3365], + [2866, 3376], +] + +[[area.polygons]] +vertices = [ + [2808, 3366], + [2808, 3386], +] + +[[area.polygons]] +vertices = [ + [2859, 3366], + [2860, 3366], + [2860, 3389], + [2859, 3389], +] + +[[area.polygons]] +vertices = [ + [2867, 3366], + [2867, 3375], +] + +[[area.polygons]] +vertices = [ + [2807, 3367], + [2807, 3385], +] + +[[area.polygons]] +vertices = [ + [2868, 3367], + [2868, 3374], +] + +[[area.polygons]] +vertices = [ + [2806, 3369], + [2806, 3385], +] + +[[area.polygons]] +vertices = [ + [2869, 3369], + [2869, 3374], +] + +[[area.polygons]] +vertices = [ + [2870, 3371], + [2870, 3373], +] + +[[area.polygons]] +vertices = [ + [2805, 3373], + [2805, 3383], +] + +[[area.polygons]] +vertices = [ + [2804, 3374], + [2804, 3381], +] + +[[area.polygons]] +vertices = [ + [2803, 3377], + [2803, 3378], +] + +[[area.polygons]] +vertices = [ + [2866, 3380], + [2867, 3380], + [2867, 3387], + [2866, 3387], +] + +[[area.polygons]] +vertices = [ + [2868, 3382], + [2868, 3386], +] + +[[area.polygons]] +vertices = [ + [2826, 3384], + [2833, 3384], +] + +[[area.polygons]] +vertices = [ + [2869, 3384], + [2869, 3385], +] + +[[area.polygons]] +vertices = [ + [2827, 3385], + [2833, 3385], + [2833, 3386], + [2827, 3386], +] + +[[area.polygons]] +vertices = [[2821, 3386]] + +[[area.polygons]] +vertices = [ + [2828, 3387], + [2833, 3387], +] + +[[area.polygons]] +vertices = [[2818, 3388]] + +[[area.polygons]] +vertices = [ + [2829, 3388], + [2833, 3388], +] + +[[area.polygons]] +vertices = [[2866, 3388]] + +[[area.polygons]] +vertices = [[2813, 3389]] + +[[area.polygons]] +vertices = [ + [2831, 3389], + [2833, 3389], +] + +[[area.polygons]] +vertices = [[2833, 3390]] + +[[area.polygons]] +vertices = [[2859, 3390]] + +[[area.polygons]] +vertices = [ + [2837, 3391], + [2842, 3391], +] + +[[area.polygons]] +vertices = [ + [2838, 3392], + [2842, 3392], +] + +[[area.polygons]] +vertices = [ + [2839, 3393], + [2842, 3393], +] + +[[area.polygons]] +vertices = [ + [1952, 4992], + [1983, 4992], + [1983, 5023], + [1952, 5023], +] diff --git a/.data/raw-cache/map/area/bandit_camp.toml b/.data/raw-cache/map/area/bandit_camp.toml new file mode 100644 index 000000000..65b82b276 --- /dev/null +++ b/.data/raw-cache/map/area/bandit_camp.toml @@ -0,0 +1,350 @@ +# Bandit Camp music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Bandit Camp. +# Bounds x 3137-3198, z 2951-3013; 2749 tiles as 56 rectangles. + +[[area]] +name = "Bandit Camp" +area_id = "area.bandit_camp" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3174, 2951], + [3187, 2951], + [3187, 3003], + [3174, 3003], +] + +[[area.polygons]] +vertices = [ + [3173, 2952], + [3173, 3006], +] + +[[area.polygons]] +vertices = [ + [3172, 2953], + [3172, 3006], +] + +[[area.polygons]] +vertices = [ + [3171, 2954], + [3171, 3007], +] + +[[area.polygons]] +vertices = [ + [3188, 2954], + [3188, 3003], +] + +[[area.polygons]] +vertices = [ + [3170, 2955], + [3170, 3007], +] + +[[area.polygons]] +vertices = [ + [3162, 2956], + [3169, 2956], + [3169, 3007], + [3162, 3007], +] + +[[area.polygons]] +vertices = [ + [3189, 2956], + [3189, 3003], +] + +[[area.polygons]] +vertices = [ + [3161, 2957], + [3161, 3010], +] + +[[area.polygons]] +vertices = [ + [3149, 2958], + [3153, 2958], + [3153, 2961], + [3149, 2961], +] + +[[area.polygons]] +vertices = [ + [3160, 2958], + [3160, 3010], +] + +[[area.polygons]] +vertices = [ + [3190, 2958], + [3190, 3003], +] + +[[area.polygons]] +vertices = [ + [3154, 2959], + [3156, 2959], + [3156, 3012], + [3154, 3012], +] + +[[area.polygons]] +vertices = [ + [3159, 2959], + [3159, 3011], +] + +[[area.polygons]] +vertices = [ + [3157, 2960], + [3158, 2960], + [3158, 3011], + [3157, 3011], +] + +[[area.polygons]] +vertices = [ + [3191, 2960], + [3191, 3003], +] + +[[area.polygons]] +vertices = [ + [3150, 2962], + [3153, 2962], + [3153, 2963], + [3150, 2963], +] + +[[area.polygons]] +vertices = [ + [3192, 2962], + [3192, 3002], +] + +[[area.polygons]] +vertices = [ + [3152, 2964], + [3153, 2964], + [3153, 3012], + [3152, 3012], +] + +[[area.polygons]] +vertices = [ + [3193, 2964], + [3193, 3000], +] + +[[area.polygons]] +vertices = [ + [3140, 2966], + [3141, 2966], + [3141, 2973], + [3140, 2973], +] + +[[area.polygons]] +vertices = [ + [3151, 2966], + [3151, 3011], +] + +[[area.polygons]] +vertices = [ + [3194, 2966], + [3194, 2999], +] + +[[area.polygons]] +vertices = [ + [3138, 2967], + [3139, 2967], + [3139, 2970], + [3138, 2970], +] + +[[area.polygons]] +vertices = [ + [3142, 2967], + [3144, 2967], + [3144, 2973], + [3142, 2973], +] + +[[area.polygons]] +vertices = [ + [3150, 2967], + [3150, 3011], +] + +[[area.polygons]] +vertices = [ + [3145, 2968], + [3149, 2968], + [3149, 2976], + [3145, 2976], +] + +[[area.polygons]] +vertices = [ + [3195, 2968], + [3195, 2997], +] + +[[area.polygons]] +vertices = [ + [3196, 2970], + [3196, 2980], +] + +[[area.polygons]] +vertices = [[3139, 2971]] + +[[area.polygons]] +vertices = [ + [3197, 2972], + [3197, 2978], +] + +[[area.polygons]] +vertices = [ + [3143, 2974], + [3144, 2974], +] + +[[area.polygons]] +vertices = [ + [3198, 2974], + [3198, 2977], +] + +[[area.polygons]] +vertices = [[3144, 2975]] + +[[area.polygons]] +vertices = [ + [3146, 2977], + [3149, 2977], +] + +[[area.polygons]] +vertices = [ + [3148, 2978], + [3149, 2978], +] + +[[area.polygons]] +vertices = [ + [3149, 2979], + [3149, 3011], +] + +[[area.polygons]] +vertices = [ + [3148, 2981], + [3148, 3010], +] + +[[area.polygons]] +vertices = [ + [3146, 2982], + [3147, 2982], + [3147, 3009], + [3146, 3009], +] + +[[area.polygons]] +vertices = [ + [3145, 2983], + [3145, 3008], +] + +[[area.polygons]] +vertices = [ + [3144, 2984], + [3144, 3008], +] + +[[area.polygons]] +vertices = [ + [3143, 2987], + [3143, 3008], +] + +[[area.polygons]] +vertices = [ + [3142, 2989], + [3142, 3008], +] + +[[area.polygons]] +vertices = [[3141, 2990]] + +[[area.polygons]] +vertices = [ + [3141, 2995], + [3141, 3007], +] + +[[area.polygons]] +vertices = [ + [3140, 2997], + [3140, 3007], +] + +[[area.polygons]] +vertices = [ + [3139, 3000], + [3139, 3007], +] + +[[area.polygons]] +vertices = [ + [3138, 3002], + [3138, 3007], +] + +[[area.polygons]] +vertices = [ + [3137, 3003], + [3137, 3006], +] + +[[area.polygons]] +vertices = [ + [3174, 3004], + [3180, 3004], +] + +[[area.polygons]] +vertices = [ + [3174, 3005], + [3177, 3005], +] + +[[area.polygons]] +vertices = [[3174, 3006]] + +[[area.polygons]] +vertices = [ + [3162, 3008], + [3168, 3008], +] + +[[area.polygons]] +vertices = [ + [3162, 3009], + [3165, 3009], +] + +[[area.polygons]] +vertices = [[3162, 3010]] + +[[area.polygons]] +vertices = [[3153, 3013]] diff --git a/.data/raw-cache/map/area/bandos_battalion.toml b/.data/raw-cache/map/area/bandos_battalion.toml new file mode 100644 index 000000000..dcaad7c40 --- /dev/null +++ b/.data/raw-cache/map/area/bandos_battalion.toml @@ -0,0 +1,32 @@ +# Bandos Battalion music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Bandos Battalion. +# Bounds x 2816-2879, z 5312-5375; 3136 tiles as 3 rectangles. + +[[area]] +name = "Bandos Battalion" +area_id = "area.bandos_battalion" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 5312], + [2847, 5312], + [2847, 5375], + [2816, 5375], +] + +[[area.polygons]] +vertices = [ + [2848, 5336], + [2855, 5336], + [2855, 5375], + [2848, 5375], +] + +[[area.polygons]] +vertices = [ + [2856, 5344], + [2879, 5344], + [2879, 5375], + [2856, 5375], +] diff --git a/.data/raw-cache/map/area/bane.toml b/.data/raw-cache/map/area/bane.toml new file mode 100644 index 000000000..ebfd2f3d4 --- /dev/null +++ b/.data/raw-cache/map/area/bane.toml @@ -0,0 +1,16 @@ +# Bane music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Bane. +# Bounds x 2972-3007, z 4352-4415; 2304 tiles as 1 rectangles. + +[[area]] +name = "Bane" +area_id = "area.bane" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2972, 4352], + [3007, 4352], + [3007, 4415], + [2972, 4415], +] diff --git a/.data/raw-cache/map/area/barb_wire.toml b/.data/raw-cache/map/area/barb_wire.toml new file mode 100644 index 000000000..9f4f3c1b2 --- /dev/null +++ b/.data/raw-cache/map/area/barb_wire.toml @@ -0,0 +1,24 @@ +# Barb Wire music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Barb Wire. +# Bounds x 1536-1791, z 4864-5375; 12288 tiles as 2 rectangles. + +[[area]] +name = "Barb Wire" +area_id = "area.barb_wire" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1536, 4864], + [1599, 4864], + [1599, 4927], + [1536, 4927], +] + +[[area.polygons]] +vertices = [ + [1728, 5248], + [1791, 5248], + [1791, 5375], + [1728, 5375], +] diff --git a/.data/raw-cache/map/area/barbarian_outpost.toml b/.data/raw-cache/map/area/barbarian_outpost.toml new file mode 100644 index 000000000..9f98097fe --- /dev/null +++ b/.data/raw-cache/map/area/barbarian_outpost.toml @@ -0,0 +1,462 @@ +[[area]] +name = "Barbarian Outpost" +area_id = "area.barbarian_outpost" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2495, 3545], + [2545, 3545], + [2545, 3590], + [2495, 3590], +] + +# Extension traced from the OSRS wiki music map (2049 tiles). + +[[area.polygons]] +vertices = [ + [2537, 3513], + [2540, 3513], + [2540, 3527], + [2537, 3527], +] + +[[area.polygons]] +vertices = [ + [2553, 3513], + [2554, 3513], + [2554, 3521], + [2553, 3521], +] + +[[area.polygons]] +vertices = [ + [2536, 3514], + [2536, 3544], +] + +[[area.polygons]] +vertices = [ + [2541, 3514], + [2543, 3514], + [2543, 3526], + [2541, 3526], +] + +[[area.polygons]] +vertices = [ + [2552, 3514], + [2552, 3524], +] + +[[area.polygons]] +vertices = [ + [2555, 3514], + [2555, 3515], +] + +[[area.polygons]] +vertices = [ + [2535, 3515], + [2535, 3544], +] + +[[area.polygons]] +vertices = [ + [2544, 3515], + [2544, 3525], +] + +[[area.polygons]] +vertices = [ + [2551, 3515], + [2551, 3525], +] + +[[area.polygons]] +vertices = [ + [2545, 3516], + [2546, 3516], + [2546, 3525], + [2545, 3525], +] + +[[area.polygons]] +vertices = [ + [2550, 3516], + [2550, 3526], +] + +[[area.polygons]] +vertices = [ + [2534, 3517], + [2534, 3544], +] + +[[area.polygons]] +vertices = [ + [2547, 3517], + [2549, 3517], + [2549, 3526], + [2547, 3526], +] + +[[area.polygons]] +vertices = [ + [2489, 3520], + [2495, 3520], + [2495, 3531], + [2489, 3531], +] + +[[area.polygons]] +vertices = [[2555, 3520]] + +[[area.polygons]] +vertices = [ + [2533, 3522], + [2533, 3544], +] + +[[area.polygons]] +vertices = [ + [2553, 3522], + [2553, 3523], +] + +[[area.polygons]] +vertices = [ + [2496, 3523], + [2499, 3523], + [2499, 3544], + [2496, 3544], +] + +[[area.polygons]] +vertices = [ + [2500, 3524], + [2503, 3524], + [2503, 3544], + [2500, 3544], +] + +[[area.polygons]] +vertices = [ + [2532, 3524], + [2532, 3544], +] + +[[area.polygons]] +vertices = [ + [2504, 3525], + [2507, 3525], + [2507, 3544], + [2504, 3544], +] + +[[area.polygons]] +vertices = [ + [2488, 3526], + [2488, 3529], +] + +[[area.polygons]] +vertices = [ + [2508, 3526], + [2511, 3526], + [2511, 3544], + [2508, 3544], +] + +[[area.polygons]] +vertices = [ + [2531, 3526], + [2531, 3544], +] + +[[area.polygons]] +vertices = [[2546, 3526]] + +[[area.polygons]] +vertices = [ + [2512, 3527], + [2515, 3527], + [2515, 3544], + [2512, 3544], +] + +[[area.polygons]] +vertices = [ + [2530, 3527], + [2530, 3544], +] + +[[area.polygons]] +vertices = [[2541, 3527]] + +[[area.polygons]] +vertices = [ + [2516, 3528], + [2519, 3528], + [2519, 3544], + [2516, 3544], +] + +[[area.polygons]] +vertices = [ + [2529, 3528], + [2529, 3544], +] + +[[area.polygons]] +vertices = [ + [2537, 3528], + [2539, 3528], + [2539, 3544], + [2537, 3544], +] + +[[area.polygons]] +vertices = [ + [2520, 3529], + [2523, 3529], + [2523, 3544], + [2520, 3544], +] + +[[area.polygons]] +vertices = [ + [2528, 3529], + [2528, 3544], +] + +[[area.polygons]] +vertices = [ + [2524, 3530], + [2527, 3530], + [2527, 3544], + [2524, 3544], +] + +[[area.polygons]] +vertices = [ + [2540, 3531], + [2541, 3531], + [2541, 3544], + [2540, 3544], +] + +[[area.polygons]] +vertices = [ + [2550, 3531], + [2555, 3531], + [2555, 3581], + [2550, 3581], +] + +[[area.polygons]] +vertices = [ + [2491, 3532], + [2495, 3532], +] + +[[area.polygons]] +vertices = [ + [2542, 3532], + [2542, 3544], +] + +[[area.polygons]] +vertices = [ + [2548, 3532], + [2549, 3532], + [2549, 3585], + [2548, 3585], +] + +[[area.polygons]] +vertices = [ + [2556, 3532], + [2556, 3537], +] + +[[area.polygons]] +vertices = [ + [2492, 3533], + [2495, 3533], + [2495, 3538], + [2492, 3538], +] + +[[area.polygons]] +vertices = [ + [2543, 3533], + [2547, 3533], + [2547, 3544], + [2543, 3544], +] + +[[area.polygons]] +vertices = [ + [2557, 3533], + [2557, 3537], +] + +[[area.polygons]] +vertices = [ + [2558, 3534], + [2558, 3536], +] + +[[area.polygons]] +vertices = [ + [2493, 3539], + [2495, 3539], +] + +[[area.polygons]] +vertices = [ + [2494, 3540], + [2495, 3540], +] + +[[area.polygons]] +vertices = [ + [2556, 3540], + [2556, 3580], +] + +[[area.polygons]] +vertices = [[2495, 3541]] + +[[area.polygons]] +vertices = [ + [2546, 3545], + [2547, 3545], + [2547, 3586], + [2546, 3586], +] + +[[area.polygons]] +vertices = [ + [2557, 3547], + [2557, 3576], +] + +[[area.polygons]] +vertices = [ + [2558, 3554], + [2558, 3562], +] + +[[area.polygons]] +vertices = [ + [2550, 3582], + [2554, 3582], +] + +[[area.polygons]] +vertices = [ + [2550, 3583], + [2553, 3583], +] + +[[area.polygons]] +vertices = [ + [2550, 3584], + [2551, 3584], +] + +[[area.polygons]] +vertices = [[2550, 3585]] + +[[area.polygons]] +vertices = [[2546, 3587]] + +[[area.polygons]] +vertices = [ + [2515, 3591], + [2538, 3591], +] + +[[area.polygons]] +vertices = [ + [2515, 3592], + [2537, 3592], +] + +[[area.polygons]] +vertices = [ + [2516, 3593], + [2535, 3593], +] + +[[area.polygons]] +vertices = [ + [2517, 3594], + [2532, 3594], +] + +[[area.polygons]] +vertices = [ + [2520, 3595], + [2529, 3595], +] + +[[area.polygons]] +vertices = [ + [2522, 3596], + [2522, 3606], +] + +[[area.polygons]] +vertices = [ + [2521, 3597], + [2521, 3609], +] + +[[area.polygons]] +vertices = [ + [2520, 3604], + [2520, 3610], +] + +[[area.polygons]] +vertices = [ + [2519, 3606], + [2519, 3611], +] + +[[area.polygons]] +vertices = [ + [2518, 3608], + [2518, 3612], +] + +[[area.polygons]] +vertices = [ + [2517, 3609], + [2517, 3612], +] + +[[area.polygons]] +vertices = [ + [2514, 3610], + [2516, 3610], + [2516, 3612], + [2514, 3612], +] + +[[area.polygons]] +vertices = [ + [2513, 3611], + [2513, 3614], +] + +[[area.polygons]] +vertices = [ + [2514, 3613], + [2515, 3613], + [2515, 3614], + [2514, 3614], +] diff --git a/.data/raw-cache/map/area/barbarian_village.toml b/.data/raw-cache/map/area/barbarian_village.toml new file mode 100644 index 000000000..134be9406 --- /dev/null +++ b/.data/raw-cache/map/area/barbarian_village.toml @@ -0,0 +1,31 @@ +[[area]] +name = "Barbarian Village" +area_id = "area.barbarian_village" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3060, 3395], + [3118, 3395], + [3118, 3450], + [3060, 3450], +] + +# Extension traced from the OSRS wiki music map (179 tiles). + +[[area.polygons]] +vertices = [ + [3068, 3451], + [3102, 3451], + [3102, 3455], + [3068, 3455], +] + +[[area.polygons]] +vertices = [ + [3067, 3453], + [3067, 3455], +] + +[[area.polygons]] +vertices = [[3066, 3455]] diff --git a/.data/raw-cache/map/area/barking_mad.toml b/.data/raw-cache/map/area/barking_mad.toml new file mode 100644 index 000000000..26af161a4 --- /dev/null +++ b/.data/raw-cache/map/area/barking_mad.toml @@ -0,0 +1,16 @@ +# Barking Mad music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Barking Mad. +# Bounds x 3520-3583, z 9856-9919; 4096 tiles as 1 rectangles. + +[[area]] +name = "Barking Mad" +area_id = "area.barking_mad" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3520, 9856], + [3583, 9856], + [3583, 9919], + [3520, 9919], +] diff --git a/.data/raw-cache/map/area/barren_land.toml b/.data/raw-cache/map/area/barren_land.toml new file mode 100644 index 000000000..73fa00bd0 --- /dev/null +++ b/.data/raw-cache/map/area/barren_land.toml @@ -0,0 +1,274 @@ +# Barren Land music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Barren Land. +# Bounds x 2814-2886, z 3911-3972; 1353 tiles as 43 rectangles. + +[[area]] +name = "Barren Land" +area_id = "area.barren_land" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 3911], + [2886, 3911], + [2886, 3916], + [2816, 3916], +] + +[[area.polygons]] +vertices = [ + [2815, 3912], + [2815, 3916], +] + +[[area.polygons]] +vertices = [[2814, 3915]] + +[[area.polygons]] +vertices = [ + [2818, 3917], + [2851, 3917], +] + +[[area.polygons]] +vertices = [ + [2856, 3917], + [2886, 3917], +] + +[[area.polygons]] +vertices = [ + [2819, 3918], + [2848, 3918], +] + +[[area.polygons]] +vertices = [ + [2857, 3918], + [2886, 3918], +] + +[[area.polygons]] +vertices = [ + [2821, 3919], + [2847, 3919], +] + +[[area.polygons]] +vertices = [ + [2862, 3919], + [2872, 3919], +] + +[[area.polygons]] +vertices = [ + [2875, 3919], + [2886, 3919], +] + +[[area.polygons]] +vertices = [ + [2822, 3920], + [2847, 3920], +] + +[[area.polygons]] +vertices = [ + [2866, 3920], + [2870, 3920], +] + +[[area.polygons]] +vertices = [ + [2877, 3920], + [2886, 3920], +] + +[[area.polygons]] +vertices = [ + [2826, 3921], + [2847, 3921], +] + +[[area.polygons]] +vertices = [ + [2882, 3921], + [2885, 3921], +] + +[[area.polygons]] +vertices = [ + [2829, 3922], + [2847, 3922], +] + +[[area.polygons]] +vertices = [ + [2830, 3923], + [2847, 3923], +] + +[[area.polygons]] +vertices = [ + [2831, 3924], + [2847, 3924], +] + +[[area.polygons]] +vertices = [ + [2832, 3925], + [2847, 3925], + [2847, 3932], + [2832, 3932], +] + +[[area.polygons]] +vertices = [[2831, 3930]] + +[[area.polygons]] +vertices = [ + [2848, 3930], + [2849, 3930], + [2849, 3967], + [2848, 3967], +] + +[[area.polygons]] +vertices = [ + [2850, 3931], + [2850, 3938], +] + +[[area.polygons]] +vertices = [ + [2833, 3933], + [2847, 3933], +] + +[[area.polygons]] +vertices = [ + [2834, 3934], + [2847, 3934], + [2847, 3937], + [2834, 3937], +] + +[[area.polygons]] +vertices = [ + [2835, 3938], + [2847, 3938], + [2847, 3939], + [2835, 3939], +] + +[[area.polygons]] +vertices = [ + [2836, 3940], + [2847, 3940], +] + +[[area.polygons]] +vertices = [ + [2837, 3941], + [2847, 3941], + [2847, 3944], + [2837, 3944], +] + +[[area.polygons]] +vertices = [ + [2838, 3945], + [2847, 3945], + [2847, 3946], + [2838, 3946], +] + +[[area.polygons]] +vertices = [ + [2839, 3947], + [2847, 3947], +] + +[[area.polygons]] +vertices = [ + [2840, 3948], + [2847, 3948], + [2847, 3953], + [2840, 3953], +] + +[[area.polygons]] +vertices = [ + [2841, 3954], + [2847, 3954], +] + +[[area.polygons]] +vertices = [ + [2844, 3955], + [2847, 3955], +] + +[[area.polygons]] +vertices = [ + [2845, 3956], + [2847, 3956], +] + +[[area.polygons]] +vertices = [ + [2846, 3957], + [2847, 3957], + [2847, 3964], + [2846, 3964], +] + +[[area.polygons]] +vertices = [ + [2845, 3959], + [2845, 3964], +] + +[[area.polygons]] +vertices = [ + [2844, 3961], + [2844, 3962], +] + +[[area.polygons]] +vertices = [ + [2850, 3961], + [2863, 3961], + [2863, 3968], + [2850, 3968], +] + +[[area.polygons]] +vertices = [[2847, 3965]] + +[[area.polygons]] +vertices = [[2849, 3968]] + +[[area.polygons]] +vertices = [ + [2851, 3969], + [2863, 3969], +] + +[[area.polygons]] +vertices = [ + [2852, 3970], + [2862, 3970], +] + +[[area.polygons]] +vertices = [ + [2859, 3971], + [2862, 3971], +] + +[[area.polygons]] +vertices = [ + [2860, 3972], + [2861, 3972], +] diff --git a/.data/raw-cache/map/area/beetle_juice.toml b/.data/raw-cache/map/area/beetle_juice.toml new file mode 100644 index 000000000..0d655536d --- /dev/null +++ b/.data/raw-cache/map/area/beetle_juice.toml @@ -0,0 +1,16 @@ +# Beetle Juice music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Beetle Juice. +# Bounds x 2240-2303, z 4288-4351; 4096 tiles as 1 rectangles. + +[[area]] +name = "Beetle Juice" +area_id = "area.beetle_juice" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2240, 4288], + [2303, 4288], + [2303, 4351], + [2240, 4351], +] diff --git a/.data/raw-cache/map/area/below_the_conch.toml b/.data/raw-cache/map/area/below_the_conch.toml new file mode 100644 index 000000000..8cc7ce3de --- /dev/null +++ b/.data/raw-cache/map/area/below_the_conch.toml @@ -0,0 +1,24 @@ +# Below the Conch music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Below the Conch. +# Bounds x 3072-3263, z 8832-8895; 8192 tiles as 2 rectangles. + +[[area]] +name = "Below the Conch" +area_id = "area.below_the_conch" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3072, 8832], + [3135, 8832], + [3135, 8895], + [3072, 8895], +] + +[[area.polygons]] +vertices = [ + [3200, 8832], + [3263, 8832], + [3263, 8895], + [3200, 8895], +] diff --git a/.data/raw-cache/map/area/beneath_cursed_sands.toml b/.data/raw-cache/map/area/beneath_cursed_sands.toml new file mode 100644 index 000000000..e6cb73a0e --- /dev/null +++ b/.data/raw-cache/map/area/beneath_cursed_sands.toml @@ -0,0 +1,16 @@ +# Beneath Cursed Sands music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Beneath Cursed Sands. +# Bounds x 3520-3583, z 5120-5183; 4096 tiles as 1 rectangles. + +[[area]] +name = "Beneath Cursed Sands" +area_id = "area.beneath_cursed_sands" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3520, 5120], + [3583, 5120], + [3583, 5183], + [3520, 5183], +] diff --git a/.data/raw-cache/map/area/beneath_the_kingdom.toml b/.data/raw-cache/map/area/beneath_the_kingdom.toml new file mode 100644 index 000000000..4e8a38b69 --- /dev/null +++ b/.data/raw-cache/map/area/beneath_the_kingdom.toml @@ -0,0 +1,32 @@ +# Beneath the Kingdom music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Beneath the Kingdom. +# Bounds x 1408-1791, z 9856-10111; 24576 tiles as 3 rectangles. + +[[area]] +name = "Beneath the Kingdom" +area_id = "area.beneath_the_kingdom" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1408, 9856], + [1471, 9856], + [1471, 9919], + [1408, 9919], +] + +[[area.polygons]] +vertices = [ + [1600, 9984], + [1791, 9984], + [1791, 10047], + [1600, 10047], +] + +[[area.polygons]] +vertices = [ + [1600, 10048], + [1727, 10048], + [1727, 10111], + [1600, 10111], +] diff --git a/.data/raw-cache/map/area/beneath_the_stronghold.toml b/.data/raw-cache/map/area/beneath_the_stronghold.toml new file mode 100644 index 000000000..6a594c8f2 --- /dev/null +++ b/.data/raw-cache/map/area/beneath_the_stronghold.toml @@ -0,0 +1,32 @@ +# Beneath the Stronghold music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Beneath the Stronghold. +# Bounds x 2368-2495, z 9728-9855; 13568 tiles as 3 rectangles. + +[[area]] +name = "Beneath the Stronghold" +area_id = "area.beneath_the_stronghold" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 9728], + [2495, 9728], + [2495, 9791], + [2368, 9791], +] + +[[area.polygons]] +vertices = [ + [2400, 9792], + [2495, 9792], + [2495, 9807], + [2400, 9807], +] + +[[area.polygons]] +vertices = [ + [2416, 9808], + [2495, 9808], + [2495, 9855], + [2416, 9855], +] diff --git a/.data/raw-cache/map/area/beyond.toml b/.data/raw-cache/map/area/beyond.toml new file mode 100644 index 000000000..739f7c507 --- /dev/null +++ b/.data/raw-cache/map/area/beyond.toml @@ -0,0 +1,24 @@ +# Beyond music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Beyond. +# Bounds x 2816-2943, z 9856-9983; 12288 tiles as 2 rectangles. + +[[area]] +name = "Beyond" +area_id = "area.beyond" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 9856], + [2879, 9856], + [2879, 9983], + [2816, 9983], +] + +[[area.polygons]] +vertices = [ + [2880, 9920], + [2943, 9920], + [2943, 9983], + [2880, 9983], +] diff --git a/.data/raw-cache/map/area/beyond_the_meadow.toml b/.data/raw-cache/map/area/beyond_the_meadow.toml new file mode 100644 index 000000000..1bc41b537 --- /dev/null +++ b/.data/raw-cache/map/area/beyond_the_meadow.toml @@ -0,0 +1,663 @@ +# Beyond the Meadow music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Beyond the Meadow. +# Bounds x 1576-1729, z 3491-3646; 16057 tiles as 101 rectangles. + +[[area]] +name = "Beyond the Meadow" +area_id = "area.beyond_the_meadow" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1650, 3491], + [1667, 3491], +] + +[[area.polygons]] +vertices = [ + [1651, 3492], + [1670, 3492], +] + +[[area.polygons]] +vertices = [ + [1652, 3493], + [1674, 3493], +] + +[[area.polygons]] +vertices = [ + [1653, 3494], + [1678, 3494], +] + +[[area.polygons]] +vertices = [ + [1654, 3495], + [1681, 3495], +] + +[[area.polygons]] +vertices = [ + [1655, 3496], + [1709, 3496], +] + +[[area.polygons]] +vertices = [ + [1656, 3497], + [1710, 3497], + [1710, 3501], + [1656, 3501], +] + +[[area.polygons]] +vertices = [ + [1711, 3499], + [1711, 3561], +] + +[[area.polygons]] +vertices = [ + [1712, 3500], + [1712, 3561], +] + +[[area.polygons]] +vertices = [ + [1657, 3502], + [1710, 3502], +] + +[[area.polygons]] +vertices = [ + [1713, 3502], + [1713, 3560], +] + +[[area.polygons]] +vertices = [ + [1658, 3503], + [1710, 3503], + [1710, 3559], + [1658, 3559], +] + +[[area.polygons]] +vertices = [ + [1714, 3504], + [1714, 3559], +] + +[[area.polygons]] +vertices = [ + [1715, 3506], + [1715, 3558], +] + +[[area.polygons]] +vertices = [ + [1657, 3507], + [1657, 3645], +] + +[[area.polygons]] +vertices = [ + [1716, 3507], + [1716, 3558], +] + +[[area.polygons]] +vertices = [ + [1656, 3508], + [1656, 3645], +] + +[[area.polygons]] +vertices = [ + [1655, 3509], + [1655, 3645], +] + +[[area.polygons]] +vertices = [ + [1717, 3509], + [1717, 3557], +] + +[[area.polygons]] +vertices = [ + [1718, 3510], + [1724, 3510], + [1724, 3529], + [1718, 3529], +] + +[[area.polygons]] +vertices = [ + [1601, 3511], + [1606, 3511], + [1606, 3519], + [1601, 3519], +] + +[[area.polygons]] +vertices = [ + [1725, 3511], + [1725, 3527], +] + +[[area.polygons]] +vertices = [ + [1726, 3512], + [1726, 3525], +] + +[[area.polygons]] +vertices = [ + [1727, 3513], + [1727, 3523], +] + +[[area.polygons]] +vertices = [ + [1607, 3514], + [1609, 3514], + [1609, 3645], + [1607, 3645], +] + +[[area.polygons]] +vertices = [ + [1728, 3514], + [1728, 3522], +] + +[[area.polygons]] +vertices = [ + [1610, 3515], + [1610, 3645], +] + +[[area.polygons]] +vertices = [ + [1729, 3515], + [1729, 3520], +] + +[[area.polygons]] +vertices = [ + [1611, 3516], + [1620, 3516], + [1620, 3645], + [1611, 3645], +] + +[[area.polygons]] +vertices = [ + [1654, 3516], + [1654, 3645], +] + +[[area.polygons]] +vertices = [ + [1621, 3517], + [1621, 3645], +] + +[[area.polygons]] +vertices = [ + [1632, 3517], + [1653, 3517], + [1653, 3645], + [1632, 3645], +] + +[[area.polygons]] +vertices = [ + [1622, 3518], + [1622, 3645], +] + +[[area.polygons]] +vertices = [ + [1631, 3518], + [1631, 3645], +] + +[[area.polygons]] +vertices = [ + [1623, 3519], + [1630, 3519], + [1630, 3645], + [1623, 3645], +] + +[[area.polygons]] +vertices = [ + [1602, 3520], + [1606, 3520], +] + +[[area.polygons]] +vertices = [ + [1604, 3521], + [1606, 3521], +] + +[[area.polygons]] +vertices = [ + [1605, 3522], + [1606, 3522], + [1606, 3632], + [1605, 3632], +] + +[[area.polygons]] +vertices = [ + [1718, 3530], + [1723, 3530], + [1723, 3531], + [1718, 3531], +] + +[[area.polygons]] +vertices = [ + [1718, 3532], + [1722, 3532], +] + +[[area.polygons]] +vertices = [ + [1718, 3533], + [1721, 3533], + [1721, 3534], + [1718, 3534], +] + +[[area.polygons]] +vertices = [ + [1718, 3535], + [1720, 3535], + [1720, 3555], + [1718, 3555], +] + +[[area.polygons]] +vertices = [ + [1721, 3547], + [1721, 3554], +] + +[[area.polygons]] +vertices = [ + [1576, 3548], + [1604, 3548], + [1604, 3572], + [1576, 3572], +] + +[[area.polygons]] +vertices = [ + [1718, 3556], + [1719, 3556], +] + +[[area.polygons]] +vertices = [ + [1658, 3560], + [1692, 3560], +] + +[[area.polygons]] +vertices = [ + [1699, 3560], + [1710, 3560], +] + +[[area.polygons]] +vertices = [ + [1658, 3561], + [1691, 3561], + [1691, 3562], + [1658, 3562], +] + +[[area.polygons]] +vertices = [ + [1700, 3561], + [1710, 3561], +] + +[[area.polygons]] +vertices = [ + [1702, 3562], + [1710, 3562], +] + +[[area.polygons]] +vertices = [ + [1658, 3563], + [1690, 3563], +] + +[[area.polygons]] +vertices = [ + [1703, 3563], + [1709, 3563], +] + +[[area.polygons]] +vertices = [ + [1658, 3564], + [1689, 3564], + [1689, 3565], + [1658, 3565], +] + +[[area.polygons]] +vertices = [ + [1658, 3566], + [1688, 3566], + [1688, 3567], + [1658, 3567], +] + +[[area.polygons]] +vertices = [ + [1658, 3568], + [1687, 3568], +] + +[[area.polygons]] +vertices = [ + [1658, 3569], + [1686, 3569], + [1686, 3570], + [1658, 3570], +] + +[[area.polygons]] +vertices = [ + [1658, 3571], + [1685, 3571], + [1685, 3585], + [1658, 3585], +] + +[[area.polygons]] +vertices = [ + [1577, 3573], + [1604, 3573], +] + +[[area.polygons]] +vertices = [ + [1578, 3574], + [1604, 3574], +] + +[[area.polygons]] +vertices = [ + [1579, 3575], + [1604, 3575], +] + +[[area.polygons]] +vertices = [ + [1580, 3576], + [1604, 3576], + [1604, 3577], + [1580, 3577], +] + +[[area.polygons]] +vertices = [ + [1584, 3578], + [1604, 3578], +] + +[[area.polygons]] +vertices = [ + [1592, 3579], + [1604, 3579], +] + +[[area.polygons]] +vertices = [ + [1596, 3580], + [1604, 3580], +] + +[[area.polygons]] +vertices = [ + [1597, 3581], + [1604, 3581], +] + +[[area.polygons]] +vertices = [ + [1598, 3582], + [1604, 3582], +] + +[[area.polygons]] +vertices = [ + [1599, 3583], + [1604, 3583], +] + +[[area.polygons]] +vertices = [ + [1600, 3584], + [1604, 3584], +] + +[[area.polygons]] +vertices = [ + [1601, 3585], + [1604, 3585], +] + +[[area.polygons]] +vertices = [ + [1602, 3586], + [1604, 3586], + [1604, 3587], + [1602, 3587], +] + +[[area.polygons]] +vertices = [ + [1658, 3586], + [1684, 3586], +] + +[[area.polygons]] +vertices = [ + [1658, 3587], + [1683, 3587], + [1683, 3645], + [1658, 3645], +] + +[[area.polygons]] +vertices = [ + [1603, 3588], + [1604, 3588], + [1604, 3591], + [1603, 3591], +] + +[[area.polygons]] +vertices = [ + [1703, 3588], + [1711, 3588], + [1711, 3620], + [1703, 3620], +] + +[[area.polygons]] +vertices = [ + [1701, 3589], + [1702, 3589], + [1702, 3640], + [1701, 3640], +] + +[[area.polygons]] +vertices = [ + [1712, 3589], + [1712, 3619], +] + +[[area.polygons]] +vertices = [ + [1699, 3590], + [1700, 3590], + [1700, 3640], + [1699, 3640], +] + +[[area.polygons]] +vertices = [ + [1713, 3590], + [1713, 3618], +] + +[[area.polygons]] +vertices = [ + [1684, 3591], + [1684, 3645], +] + +[[area.polygons]] +vertices = [ + [1697, 3591], + [1698, 3591], + [1698, 3642], + [1697, 3642], +] + +[[area.polygons]] +vertices = [ + [1714, 3591], + [1714, 3617], +] + +[[area.polygons]] +vertices = [ + [1604, 3592], + [1604, 3595], +] + +[[area.polygons]] +vertices = [ + [1685, 3592], + [1685, 3645], +] + +[[area.polygons]] +vertices = [ + [1695, 3592], + [1696, 3592], + [1696, 3644], + [1695, 3644], +] + +[[area.polygons]] +vertices = [ + [1686, 3593], + [1694, 3593], + [1694, 3645], + [1686, 3645], +] + +[[area.polygons]] +vertices = [ + [1604, 3599], + [1604, 3630], +] + +[[area.polygons]] +vertices = [ + [1603, 3602], + [1603, 3628], +] + +[[area.polygons]] +vertices = [ + [1602, 3604], + [1602, 3626], +] + +[[area.polygons]] +vertices = [ + [1715, 3605], + [1715, 3616], +] + +[[area.polygons]] +vertices = [ + [1601, 3607], + [1601, 3624], +] + +[[area.polygons]] +vertices = [ + [1716, 3607], + [1716, 3615], +] + +[[area.polygons]] +vertices = [ + [1703, 3621], + [1710, 3621], +] + +[[area.polygons]] +vertices = [ + [1703, 3622], + [1709, 3622], +] + +[[area.polygons]] +vertices = [ + [1703, 3623], + [1708, 3623], + [1708, 3638], + [1703, 3638], +] + +[[area.polygons]] +vertices = [ + [1606, 3633], + [1606, 3634], +] + +[[area.polygons]] +vertices = [ + [1703, 3639], + [1707, 3639], +] + +[[area.polygons]] +vertices = [ + [1703, 3640], + [1706, 3640], +] + +[[area.polygons]] +vertices = [[1699, 3641]] + +[[area.polygons]] +vertices = [[1697, 3643]] + +[[area.polygons]] +vertices = [[1695, 3645]] + +[[area.polygons]] +vertices = [ + [1690, 3646], + [1694, 3646], +] diff --git a/.data/raw-cache/map/area/black_of_knight.toml b/.data/raw-cache/map/area/black_of_knight.toml new file mode 100644 index 000000000..f4072e3b2 --- /dev/null +++ b/.data/raw-cache/map/area/black_of_knight.toml @@ -0,0 +1,40 @@ +# Black of Knight music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Black of Knight. +# Bounds x 4096-4159, z 4672-4863; 11936 tiles as 4 rectangles. + +[[area]] +name = "Black of Knight" +area_id = "area.black_of_knight" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [4096, 4672], + [4159, 4672], + [4159, 4841], + [4096, 4841], +] + +[[area.polygons]] +vertices = [ + [4096, 4842], + [4125, 4842], + [4125, 4863], + [4096, 4863], +] + +[[area.polygons]] +vertices = [ + [4148, 4842], + [4159, 4842], + [4159, 4863], + [4148, 4863], +] + +[[area.polygons]] +vertices = [ + [4126, 4858], + [4147, 4858], + [4147, 4863], + [4126, 4863], +] diff --git a/.data/raw-cache/map/area/blistering_barnacles.toml b/.data/raw-cache/map/area/blistering_barnacles.toml new file mode 100644 index 000000000..aa3f7b666 --- /dev/null +++ b/.data/raw-cache/map/area/blistering_barnacles.toml @@ -0,0 +1,16 @@ +# Blistering Barnacles music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Blistering Barnacles. +# Bounds x 2112-2175, z 5120-5183; 4096 tiles as 1 rectangles. + +[[area]] +name = "Blistering Barnacles" +area_id = "area.blistering_barnacles" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2112, 5120], + [2175, 5120], + [2175, 5183], + [2112, 5183], +] diff --git a/.data/raw-cache/map/area/bloodbath.toml b/.data/raw-cache/map/area/bloodbath.toml new file mode 100644 index 000000000..c2c3f7e7d --- /dev/null +++ b/.data/raw-cache/map/area/bloodbath.toml @@ -0,0 +1,232 @@ +# Bloodbath music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Bloodbath. +# Bounds x 3200-3775, z 3239-4863; 21139 tiles as 35 rectangles. + +[[area]] +name = "Bloodbath" +area_id = "area.bloodbath" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3648, 3239], + [3679, 3239], + [3679, 3328], + [3648, 3328], +] + +[[area.polygons]] +vertices = [ + [3680, 3264], + [3775, 3264], + [3775, 3380], + [3680, 3380], +] + +[[area.polygons]] +vertices = [ + [3664, 3329], + [3679, 3329], + [3679, 3332], + [3664, 3332], +] + +[[area.polygons]] +vertices = [ + [3669, 3333], + [3679, 3333], + [3679, 3347], + [3669, 3347], +] + +[[area.polygons]] +vertices = [ + [3672, 3348], + [3679, 3348], + [3679, 3380], + [3672, 3380], +] + +[[area.polygons]] +vertices = [ + [3697, 3381], + [3775, 3381], + [3775, 3388], + [3697, 3388], +] + +[[area.polygons]] +vertices = [ + [3698, 3389], + [3775, 3389], +] + +[[area.polygons]] +vertices = [ + [3699, 3390], + [3775, 3390], +] + +[[area.polygons]] +vertices = [ + [3700, 3391], + [3775, 3391], +] + +[[area.polygons]] +vertices = [ + [3701, 3392], + [3775, 3392], +] + +[[area.polygons]] +vertices = [ + [3702, 3393], + [3775, 3393], +] + +[[area.polygons]] +vertices = [ + [3703, 3394], + [3775, 3394], +] + +[[area.polygons]] +vertices = [ + [3704, 3395], + [3775, 3395], +] + +[[area.polygons]] +vertices = [ + [3705, 3396], + [3775, 3396], +] + +[[area.polygons]] +vertices = [ + [3706, 3397], + [3775, 3397], +] + +[[area.polygons]] +vertices = [ + [3707, 3398], + [3775, 3398], +] + +[[area.polygons]] +vertices = [ + [3708, 3399], + [3775, 3399], +] + +[[area.polygons]] +vertices = [ + [3709, 3400], + [3775, 3400], +] + +[[area.polygons]] +vertices = [ + [3710, 3401], + [3775, 3401], +] + +[[area.polygons]] +vertices = [ + [3711, 3402], + [3775, 3402], +] + +[[area.polygons]] +vertices = [ + [3712, 3403], + [3775, 3403], +] + +[[area.polygons]] +vertices = [ + [3713, 3404], + [3775, 3404], +] + +[[area.polygons]] +vertices = [ + [3714, 3405], + [3775, 3405], +] + +[[area.polygons]] +vertices = [ + [3715, 3406], + [3775, 3406], +] + +[[area.polygons]] +vertices = [ + [3716, 3407], + [3775, 3407], +] + +[[area.polygons]] +vertices = [ + [3717, 3408], + [3775, 3408], +] + +[[area.polygons]] +vertices = [ + [3718, 3409], + [3775, 3409], +] + +[[area.polygons]] +vertices = [ + [3719, 3410], + [3775, 3410], +] + +[[area.polygons]] +vertices = [ + [3720, 3411], + [3775, 3411], +] + +[[area.polygons]] +vertices = [ + [3721, 3412], + [3775, 3412], +] + +[[area.polygons]] +vertices = [ + [3722, 3413], + [3775, 3413], +] + +[[area.polygons]] +vertices = [ + [3723, 3414], + [3775, 3414], +] + +[[area.polygons]] +vertices = [ + [3724, 3415], + [3775, 3415], +] + +[[area.polygons]] +vertices = [ + [3725, 3416], + [3775, 3416], +] + +[[area.polygons]] +vertices = [ + [3200, 4800], + [3263, 4800], + [3263, 4863], + [3200, 4863], +] diff --git a/.data/raw-cache/map/area/body_parts.toml b/.data/raw-cache/map/area/body_parts.toml new file mode 100644 index 000000000..978ccf4c1 --- /dev/null +++ b/.data/raw-cache/map/area/body_parts.toml @@ -0,0 +1,16 @@ +# Body Parts music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Body Parts. +# Bounds x 3456-3583, z 9920-9983; 8192 tiles as 1 rectangles. + +[[area]] +name = "Body Parts" +area_id = "area.body_parts" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3456, 9920], + [3583, 9920], + [3583, 9983], + [3456, 9983], +] diff --git a/.data/raw-cache/map/area/bone_dance.toml b/.data/raw-cache/map/area/bone_dance.toml new file mode 100644 index 000000000..456ee0e22 --- /dev/null +++ b/.data/raw-cache/map/area/bone_dance.toml @@ -0,0 +1,357 @@ +# Bone Dance music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Bone Dance. +# Bounds x 3400-3476, z 3200-3336; 5768 tiles as 58 rectangles. + +[[area]] +name = "Bone Dance" +area_id = "area.bone_dance" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3432, 3200], + [3455, 3200], + [3455, 3323], + [3432, 3323], +] + +[[area.polygons]] +vertices = [ + [3431, 3201], + [3431, 3323], +] + +[[area.polygons]] +vertices = [ + [3456, 3201], + [3456, 3314], +] + +[[area.polygons]] +vertices = [ + [3430, 3202], + [3430, 3323], +] + +[[area.polygons]] +vertices = [ + [3457, 3202], + [3457, 3313], +] + +[[area.polygons]] +vertices = [ + [3429, 3203], + [3429, 3323], +] + +[[area.polygons]] +vertices = [ + [3458, 3203], + [3458, 3312], +] + +[[area.polygons]] +vertices = [ + [3428, 3204], + [3428, 3323], +] + +[[area.polygons]] +vertices = [ + [3459, 3204], + [3459, 3264], +] + +[[area.polygons]] +vertices = [ + [3427, 3205], + [3427, 3323], +] + +[[area.polygons]] +vertices = [ + [3460, 3205], + [3460, 3263], +] + +[[area.polygons]] +vertices = [ + [3461, 3206], + [3461, 3262], +] + +[[area.polygons]] +vertices = [ + [3426, 3207], + [3426, 3323], +] + +[[area.polygons]] +vertices = [ + [3462, 3207], + [3462, 3261], +] + +[[area.polygons]] +vertices = [ + [3425, 3208], + [3425, 3324], +] + +[[area.polygons]] +vertices = [ + [3463, 3208], + [3463, 3260], +] + +[[area.polygons]] +vertices = [ + [3424, 3209], + [3424, 3325], +] + +[[area.polygons]] +vertices = [ + [3464, 3209], + [3464, 3259], +] + +[[area.polygons]] +vertices = [ + [3423, 3210], + [3423, 3326], +] + +[[area.polygons]] +vertices = [ + [3465, 3210], + [3465, 3258], +] + +[[area.polygons]] +vertices = [ + [3466, 3211], + [3466, 3257], +] + +[[area.polygons]] +vertices = [ + [3467, 3212], + [3467, 3256], +] + +[[area.polygons]] +vertices = [ + [3468, 3213], + [3468, 3255], +] + +[[area.polygons]] +vertices = [ + [3469, 3214], + [3469, 3254], +] + +[[area.polygons]] +vertices = [ + [3470, 3215], + [3470, 3253], +] + +[[area.polygons]] +vertices = [ + [3471, 3216], + [3471, 3252], +] + +[[area.polygons]] +vertices = [ + [3472, 3217], + [3472, 3247], +] + +[[area.polygons]] +vertices = [ + [3473, 3218], + [3473, 3227], +] + +[[area.polygons]] +vertices = [[3474, 3219]] + +[[area.polygons]] +vertices = [ + [3474, 3223], + [3474, 3224], +] + +[[area.polygons]] +vertices = [ + [3473, 3234], + [3473, 3246], +] + +[[area.polygons]] +vertices = [ + [3474, 3237], + [3474, 3245], +] + +[[area.polygons]] +vertices = [ + [3475, 3239], + [3475, 3244], +] + +[[area.polygons]] +vertices = [ + [3476, 3241], + [3476, 3243], +] + +[[area.polygons]] +vertices = [ + [3422, 3269], + [3422, 3327], +] + +[[area.polygons]] +vertices = [ + [3421, 3271], + [3421, 3328], +] + +[[area.polygons]] +vertices = [ + [3420, 3273], + [3420, 3329], +] + +[[area.polygons]] +vertices = [ + [3419, 3275], + [3419, 3330], +] + +[[area.polygons]] +vertices = [ + [3418, 3277], + [3418, 3331], +] + +[[area.polygons]] +vertices = [ + [3417, 3279], + [3417, 3299], +] + +[[area.polygons]] +vertices = [ + [3416, 3281], + [3416, 3297], +] + +[[area.polygons]] +vertices = [ + [3415, 3283], + [3415, 3294], +] + +[[area.polygons]] +vertices = [ + [3414, 3285], + [3414, 3292], +] + +[[area.polygons]] +vertices = [ + [3459, 3297], + [3459, 3311], +] + +[[area.polygons]] +vertices = [ + [3460, 3299], + [3460, 3310], +] + +[[area.polygons]] +vertices = [ + [3417, 3313], + [3417, 3332], +] + +[[area.polygons]] +vertices = [ + [3416, 3314], + [3416, 3333], +] + +[[area.polygons]] +vertices = [ + [3415, 3315], + [3415, 3334], +] + +[[area.polygons]] +vertices = [ + [3414, 3316], + [3414, 3335], +] + +[[area.polygons]] +vertices = [ + [3413, 3317], + [3413, 3336], +] + +[[area.polygons]] +vertices = [ + [3412, 3318], + [3412, 3336], +] + +[[area.polygons]] +vertices = [ + [3411, 3319], + [3411, 3336], +] + +[[area.polygons]] +vertices = [ + [3410, 3320], + [3410, 3336], +] + +[[area.polygons]] +vertices = [ + [3402, 3321], + [3409, 3321], + [3409, 3334], + [3402, 3334], +] + +[[area.polygons]] +vertices = [ + [3401, 3322], + [3401, 3333], +] + +[[area.polygons]] +vertices = [ + [3400, 3323], + [3400, 3332], +] + +[[area.polygons]] +vertices = [ + [3403, 3335], + [3409, 3335], +] + +[[area.polygons]] +vertices = [ + [3404, 3336], + [3409, 3336], +] diff --git a/.data/raw-cache/map/area/bone_dry.toml b/.data/raw-cache/map/area/bone_dry.toml new file mode 100644 index 000000000..90439ce04 --- /dev/null +++ b/.data/raw-cache/map/area/bone_dry.toml @@ -0,0 +1,16 @@ +# Bone Dry music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Bone Dry. +# Bounds x 3136-3327, z 9344-9407; 12288 tiles as 1 rectangles. + +[[area]] +name = "Bone Dry" +area_id = "area.bone_dry" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 9344], + [3327, 9344], + [3327, 9407], + [3136, 9407], +] diff --git a/.data/raw-cache/map/area/box_of_delights.toml b/.data/raw-cache/map/area/box_of_delights.toml new file mode 100644 index 000000000..d1f0a75a4 --- /dev/null +++ b/.data/raw-cache/map/area/box_of_delights.toml @@ -0,0 +1,16 @@ +# Box of Delights music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Box of Delights. +# Bounds x 2688-2751, z 4288-4351; 4096 tiles as 1 rectangles. + +[[area]] +name = "Box of Delights" +area_id = "area.box_of_delights" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 4288], + [2751, 4288], + [2751, 4351], + [2688, 4351], +] diff --git a/.data/raw-cache/map/area/brew_hoo_hoo.toml b/.data/raw-cache/map/area/brew_hoo_hoo.toml new file mode 100644 index 000000000..e5aeefbf1 --- /dev/null +++ b/.data/raw-cache/map/area/brew_hoo_hoo.toml @@ -0,0 +1,16 @@ +# Brew Hoo Hoo! music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Brew Hoo Hoo!. +# Bounds x 3648-3711, z 9920-9983; 4096 tiles as 1 rectangles. + +[[area]] +name = "Brew Hoo Hoo!" +area_id = "area.brew_hoo_hoo" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3648, 9920], + [3711, 9920], + [3711, 9983], + [3648, 9983], +] diff --git a/.data/raw-cache/map/area/brimhaven.toml b/.data/raw-cache/map/area/brimhaven.toml new file mode 100644 index 000000000..efd771daa --- /dev/null +++ b/.data/raw-cache/map/area/brimhaven.toml @@ -0,0 +1,1170 @@ +# Brimhaven music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: High Seas; Landlubber; Jolly R. +# Bounds x 2689-2815, z 3137-3254; 7945 tiles as 183 rectangles. + +[[area]] +name = "Brimhaven" +area_id = "area.brimhaven" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2732, 3137], + [2739, 3137], + [2739, 3205], + [2732, 3205], +] + +[[area.polygons]] +vertices = [ + [2729, 3138], + [2731, 3138], + [2731, 3206], + [2729, 3206], +] + +[[area.polygons]] +vertices = [ + [2740, 3138], + [2742, 3138], + [2742, 3207], + [2740, 3207], +] + +[[area.polygons]] +vertices = [ + [2728, 3139], + [2728, 3205], +] + +[[area.polygons]] +vertices = [ + [2743, 3139], + [2744, 3139], + [2744, 3211], + [2743, 3211], +] + +[[area.polygons]] +vertices = [ + [2727, 3140], + [2727, 3206], +] + +[[area.polygons]] +vertices = [ + [2745, 3140], + [2751, 3140], + [2751, 3205], + [2745, 3205], +] + +[[area.polygons]] +vertices = [ + [2752, 3141], + [2752, 3205], +] + +[[area.polygons]] +vertices = [ + [2763, 3141], + [2767, 3141], + [2767, 3158], + [2763, 3158], +] + +[[area.polygons]] +vertices = [ + [2726, 3142], + [2726, 3206], +] + +[[area.polygons]] +vertices = [ + [2753, 3142], + [2753, 3204], +] + +[[area.polygons]] +vertices = [ + [2762, 3142], + [2762, 3217], +] + +[[area.polygons]] +vertices = [ + [2768, 3142], + [2768, 3148], +] + +[[area.polygons]] +vertices = [ + [2754, 3143], + [2754, 3204], +] + +[[area.polygons]] +vertices = [ + [2761, 3143], + [2761, 3216], +] + +[[area.polygons]] +vertices = [ + [2769, 3143], + [2769, 3147], +] + +[[area.polygons]] +vertices = [ + [2755, 3144], + [2760, 3144], + [2760, 3205], + [2755, 3205], +] + +[[area.polygons]] +vertices = [ + [2770, 3144], + [2770, 3146], +] + +[[area.polygons]] +vertices = [ + [2725, 3145], + [2725, 3207], +] + +[[area.polygons]] +vertices = [ + [2799, 3145], + [2800, 3145], + [2800, 3210], + [2799, 3210], +] + +[[area.polygons]] +vertices = [ + [2724, 3146], + [2724, 3230], +] + +[[area.polygons]] +vertices = [ + [2797, 3146], + [2798, 3146], + [2798, 3211], + [2797, 3211], +] + +[[area.polygons]] +vertices = [ + [2801, 3146], + [2801, 3210], +] + +[[area.polygons]] +vertices = [ + [2711, 3147], + [2717, 3147], + [2717, 3167], + [2711, 3167], +] + +[[area.polygons]] +vertices = [ + [2723, 3147], + [2723, 3230], +] + +[[area.polygons]] +vertices = [ + [2796, 3147], + [2796, 3212], +] + +[[area.polygons]] +vertices = [ + [2802, 3147], + [2803, 3147], + [2803, 3210], + [2802, 3210], +] + +[[area.polygons]] +vertices = [ + [2708, 3148], + [2710, 3148], + [2710, 3164], + [2708, 3164], +] + +[[area.polygons]] +vertices = [ + [2718, 3148], + [2722, 3148], + [2722, 3176], + [2718, 3176], +] + +[[area.polygons]] +vertices = [ + [2794, 3148], + [2795, 3148], + [2795, 3212], + [2794, 3212], +] + +[[area.polygons]] +vertices = [ + [2804, 3148], + [2806, 3148], + [2806, 3206], + [2804, 3206], +] + +[[area.polygons]] +vertices = [ + [2707, 3149], + [2707, 3162], +] + +[[area.polygons]] +vertices = [ + [2807, 3149], + [2807, 3205], +] + +[[area.polygons]] +vertices = [ + [2811, 3149], + [2811, 3203], +] + +[[area.polygons]] +vertices = [ + [2793, 3150], + [2793, 3213], +] + +[[area.polygons]] +vertices = [ + [2808, 3150], + [2810, 3150], + [2810, 3204], + [2808, 3204], +] + +[[area.polygons]] +vertices = [ + [2812, 3150], + [2812, 3203], +] + +[[area.polygons]] +vertices = [ + [2768, 3151], + [2768, 3153], +] + +[[area.polygons]] +vertices = [ + [2792, 3151], + [2792, 3214], +] + +[[area.polygons]] +vertices = [ + [2813, 3151], + [2813, 3202], +] + +[[area.polygons]] +vertices = [ + [2706, 3152], + [2706, 3159], +] + +[[area.polygons]] +vertices = [[2769, 3152]] + +[[area.polygons]] +vertices = [ + [2789, 3152], + [2791, 3152], + [2791, 3214], + [2789, 3214], +] + +[[area.polygons]] +vertices = [ + [2814, 3152], + [2815, 3152], + [2815, 3199], + [2814, 3199], +] + +[[area.polygons]] +vertices = [ + [2704, 3153], + [2705, 3153], + [2705, 3156], + [2704, 3156], +] + +[[area.polygons]] +vertices = [ + [2788, 3153], + [2788, 3214], +] + +[[area.polygons]] +vertices = [ + [2787, 3155], + [2787, 3215], +] + +[[area.polygons]] +vertices = [ + [2786, 3156], + [2786, 3216], +] + +[[area.polygons]] +vertices = [ + [2705, 3157], + [2705, 3158], +] + +[[area.polygons]] +vertices = [ + [2785, 3158], + [2785, 3218], +] + +[[area.polygons]] +vertices = [ + [2763, 3159], + [2766, 3159], + [2766, 3160], + [2763, 3160], +] + +[[area.polygons]] +vertices = [ + [2763, 3161], + [2765, 3161], + [2765, 3218], + [2763, 3218], +] + +[[area.polygons]] +vertices = [ + [2766, 3163], + [2766, 3221], +] + +[[area.polygons]] +vertices = [ + [2784, 3163], + [2784, 3219], +] + +[[area.polygons]] +vertices = [ + [2783, 3164], + [2783, 3219], +] + +[[area.polygons]] +vertices = [ + [2709, 3165], + [2710, 3165], +] + +[[area.polygons]] +vertices = [ + [2767, 3165], + [2767, 3222], +] + +[[area.polygons]] +vertices = [ + [2782, 3165], + [2782, 3220], +] + +[[area.polygons]] +vertices = [[2710, 3166]] + +[[area.polygons]] +vertices = [ + [2781, 3166], + [2781, 3220], +] + +[[area.polygons]] +vertices = [ + [2768, 3167], + [2768, 3223], +] + +[[area.polygons]] +vertices = [ + [2780, 3167], + [2780, 3220], +] + +[[area.polygons]] +vertices = [ + [2712, 3168], + [2717, 3168], +] + +[[area.polygons]] +vertices = [ + [2769, 3168], + [2770, 3168], + [2770, 3223], + [2769, 3223], +] + +[[area.polygons]] +vertices = [ + [2779, 3168], + [2779, 3220], +] + +[[area.polygons]] +vertices = [ + [2713, 3169], + [2717, 3169], +] + +[[area.polygons]] +vertices = [ + [2771, 3169], + [2773, 3169], + [2773, 3230], + [2771, 3230], +] + +[[area.polygons]] +vertices = [ + [2778, 3169], + [2778, 3220], +] + +[[area.polygons]] +vertices = [ + [2714, 3170], + [2717, 3170], + [2717, 3171], + [2714, 3171], +] + +[[area.polygons]] +vertices = [ + [2774, 3170], + [2777, 3170], + [2777, 3222], + [2774, 3222], +] + +[[area.polygons]] +vertices = [ + [2715, 3172], + [2717, 3172], + [2717, 3173], + [2715, 3173], +] + +[[area.polygons]] +vertices = [ + [2716, 3174], + [2717, 3174], +] + +[[area.polygons]] +vertices = [[2717, 3175]] + +[[area.polygons]] +vertices = [ + [2719, 3177], + [2722, 3177], +] + +[[area.polygons]] +vertices = [ + [2720, 3178], + [2722, 3178], + [2722, 3179], + [2720, 3179], +] + +[[area.polygons]] +vertices = [ + [2721, 3180], + [2722, 3180], + [2722, 3226], + [2721, 3226], +] + +[[area.polygons]] +vertices = [ + [2720, 3184], + [2720, 3225], +] + +[[area.polygons]] +vertices = [ + [2719, 3186], + [2719, 3225], +] + +[[area.polygons]] +vertices = [ + [2718, 3187], + [2718, 3224], +] + +[[area.polygons]] +vertices = [ + [2716, 3188], + [2717, 3188], + [2717, 3222], + [2716, 3222], +] + +[[area.polygons]] +vertices = [ + [2715, 3189], + [2715, 3221], +] + +[[area.polygons]] +vertices = [ + [2711, 3190], + [2714, 3190], + [2714, 3217], + [2711, 3217], +] + +[[area.polygons]] +vertices = [ + [2707, 3191], + [2710, 3191], + [2710, 3200], + [2707, 3200], +] + +[[area.polygons]] +vertices = [ + [2706, 3192], + [2706, 3200], +] + +[[area.polygons]] +vertices = [ + [2705, 3193], + [2705, 3198], +] + +[[area.polygons]] +vertices = [[2704, 3195]] + +[[area.polygons]] +vertices = [ + [2695, 3200], + [2695, 3218], +] + +[[area.polygons]] +vertices = [[2814, 3200]] + +[[area.polygons]] +vertices = [ + [2693, 3201], + [2694, 3201], + [2694, 3218], + [2693, 3218], +] + +[[area.polygons]] +vertices = [ + [2696, 3201], + [2700, 3201], + [2700, 3213], + [2696, 3213], +] + +[[area.polygons]] +vertices = [ + [2708, 3201], + [2710, 3201], +] + +[[area.polygons]] +vertices = [ + [2691, 3202], + [2692, 3202], + [2692, 3208], + [2691, 3208], +] + +[[area.polygons]] +vertices = [ + [2701, 3202], + [2704, 3202], + [2704, 3209], + [2701, 3209], +] + +[[area.polygons]] +vertices = [ + [2709, 3202], + [2710, 3202], + [2710, 3206], + [2709, 3206], +] + +[[area.polygons]] +vertices = [ + [2690, 3203], + [2690, 3206], +] + +[[area.polygons]] +vertices = [ + [2705, 3203], + [2705, 3206], +] + +[[area.polygons]] +vertices = [ + [2808, 3205], + [2809, 3205], +] + +[[area.polygons]] +vertices = [ + [2732, 3206], + [2734, 3206], + [2734, 3207], + [2732, 3207], +] + +[[area.polygons]] +vertices = [ + [2738, 3206], + [2739, 3206], +] + +[[area.polygons]] +vertices = [ + [2745, 3206], + [2750, 3206], + [2750, 3207], + [2745, 3207], +] + +[[area.polygons]] +vertices = [ + [2756, 3206], + [2760, 3206], +] + +[[area.polygons]] +vertices = [ + [2710, 3207], + [2710, 3217], +] + +[[area.polygons]] +vertices = [ + [2730, 3207], + [2731, 3207], +] + +[[area.polygons]] +vertices = [[2739, 3207]] + +[[area.polygons]] +vertices = [ + [2757, 3207], + [2760, 3207], + [2760, 3209], + [2757, 3209], +] + +[[area.polygons]] +vertices = [ + [2804, 3207], + [2805, 3207], +] + +[[area.polygons]] +vertices = [ + [2709, 3208], + [2709, 3215], +] + +[[area.polygons]] +vertices = [ + [2741, 3208], + [2742, 3208], +] + +[[area.polygons]] +vertices = [ + [2745, 3208], + [2749, 3208], +] + +[[area.polygons]] +vertices = [ + [2804, 3208], + [2804, 3209], +] + +[[area.polygons]] +vertices = [ + [2692, 3209], + [2692, 3218], +] + +[[area.polygons]] +vertices = [ + [2708, 3209], + [2708, 3214], +] + +[[area.polygons]] +vertices = [ + [2742, 3209], + [2742, 3210], +] + +[[area.polygons]] +vertices = [ + [2745, 3209], + [2748, 3209], +] + +[[area.polygons]] +vertices = [ + [2691, 3210], + [2691, 3219], +] + +[[area.polygons]] +vertices = [ + [2701, 3210], + [2703, 3210], +] + +[[area.polygons]] +vertices = [ + [2745, 3210], + [2747, 3210], +] + +[[area.polygons]] +vertices = [ + [2758, 3210], + [2760, 3210], +] + +[[area.polygons]] +vertices = [ + [2690, 3211], + [2690, 3219], +] + +[[area.polygons]] +vertices = [ + [2701, 3211], + [2701, 3212], +] + +[[area.polygons]] +vertices = [ + [2707, 3211], + [2707, 3212], +] + +[[area.polygons]] +vertices = [[2745, 3211]] + +[[area.polygons]] +vertices = [ + [2759, 3211], + [2760, 3211], + [2760, 3212], + [2759, 3212], +] + +[[area.polygons]] +vertices = [ + [2725, 3212], + [2725, 3229], +] + +[[area.polygons]] +vertices = [[2797, 3212]] + +[[area.polygons]] +vertices = [ + [2689, 3213], + [2689, 3214], +] + +[[area.polygons]] +vertices = [ + [2760, 3213], + [2760, 3215], +] + +[[area.polygons]] +vertices = [ + [2696, 3214], + [2699, 3214], + [2699, 3215], + [2696, 3215], +] + +[[area.polygons]] +vertices = [ + [2726, 3214], + [2726, 3229], +] + +[[area.polygons]] +vertices = [ + [2727, 3215], + [2727, 3229], +] + +[[area.polygons]] +vertices = [ + [2696, 3216], + [2698, 3216], +] + +[[area.polygons]] +vertices = [ + [2728, 3216], + [2728, 3230], +] + +[[area.polygons]] +vertices = [ + [2696, 3217], + [2697, 3217], +] + +[[area.polygons]] +vertices = [ + [2729, 3217], + [2733, 3217], + [2733, 3231], + [2729, 3231], +] + +[[area.polygons]] +vertices = [ + [2738, 3217], + [2741, 3217], + [2741, 3242], + [2738, 3242], +] + +[[area.polygons]] +vertices = [ + [2712, 3218], + [2714, 3218], +] + +[[area.polygons]] +vertices = [ + [2734, 3218], + [2737, 3218], + [2737, 3241], + [2734, 3241], +] + +[[area.polygons]] +vertices = [ + [2742, 3218], + [2742, 3245], +] + +[[area.polygons]] +vertices = [[2714, 3219]] + +[[area.polygons]] +vertices = [ + [2743, 3219], + [2743, 3245], +] + +[[area.polygons]] +vertices = [ + [2764, 3219], + [2765, 3219], + [2765, 3220], + [2764, 3220], +] + +[[area.polygons]] +vertices = [ + [2744, 3220], + [2744, 3245], +] + +[[area.polygons]] +vertices = [[2765, 3221]] + +[[area.polygons]] +vertices = [[2717, 3223]] + +[[area.polygons]] +vertices = [ + [2774, 3223], + [2776, 3223], +] + +[[area.polygons]] +vertices = [ + [2745, 3225], + [2745, 3246], +] + +[[area.polygons]] +vertices = [ + [2769, 3225], + [2770, 3225], + [2770, 3228], + [2769, 3228], +] + +[[area.polygons]] +vertices = [ + [2759, 3226], + [2768, 3226], + [2768, 3227], + [2759, 3227], +] + +[[area.polygons]] +vertices = [ + [2722, 3227], + [2722, 3229], +] + +[[area.polygons]] +vertices = [ + [2759, 3228], + [2761, 3228], + [2761, 3234], + [2759, 3234], +] + +[[area.polygons]] +vertices = [ + [2775, 3228], + [2777, 3228], + [2777, 3236], + [2775, 3236], +] + +[[area.polygons]] +vertices = [ + [2746, 3229], + [2746, 3246], +] + +[[area.polygons]] +vertices = [ + [2747, 3230], + [2747, 3247], +] + +[[area.polygons]] +vertices = [ + [2771, 3231], + [2772, 3231], + [2772, 3235], + [2771, 3235], +] + +[[area.polygons]] +vertices = [ + [2730, 3232], + [2733, 3232], +] + +[[area.polygons]] +vertices = [ + [2732, 3233], + [2733, 3233], +] + +[[area.polygons]] +vertices = [[2733, 3234]] + +[[area.polygons]] +vertices = [ + [2763, 3234], + [2764, 3234], + [2764, 3243], + [2763, 3243], +] + +[[area.polygons]] +vertices = [ + [2773, 3234], + [2774, 3234], +] + +[[area.polygons]] +vertices = [ + [2748, 3235], + [2748, 3247], +] + +[[area.polygons]] +vertices = [ + [2759, 3235], + [2760, 3235], + [2760, 3239], + [2759, 3239], +] + +[[area.polygons]] +vertices = [ + [2765, 3235], + [2765, 3242], +] + +[[area.polygons]] +vertices = [ + [2749, 3237], + [2749, 3238], +] + +[[area.polygons]] +vertices = [ + [2775, 3237], + [2776, 3237], +] + +[[area.polygons]] +vertices = [ + [2761, 3238], + [2762, 3238], +] + +[[area.polygons]] +vertices = [ + [2733, 3239], + [2733, 3241], +] + +[[area.polygons]] +vertices = [ + [2749, 3241], + [2749, 3247], +] + +[[area.polygons]] +vertices = [[2737, 3242]] + +[[area.polygons]] +vertices = [ + [2739, 3243], + [2741, 3243], +] + +[[area.polygons]] +vertices = [ + [2750, 3243], + [2750, 3246], +] + +[[area.polygons]] +vertices = [ + [2700, 3244], + [2702, 3244], + [2702, 3252], + [2700, 3252], +] + +[[area.polygons]] +vertices = [ + [2740, 3244], + [2741, 3244], + [2741, 3245], + [2740, 3245], +] + +[[area.polygons]] +vertices = [ + [2699, 3245], + [2699, 3254], +] + +[[area.polygons]] +vertices = [ + [2703, 3245], + [2703, 3251], +] + +[[area.polygons]] +vertices = [ + [2697, 3246], + [2698, 3246], + [2698, 3253], + [2697, 3253], +] + +[[area.polygons]] +vertices = [ + [2704, 3246], + [2704, 3249], +] + +[[area.polygons]] +vertices = [ + [2696, 3247], + [2696, 3252], +] + +[[area.polygons]] +vertices = [ + [2692, 3248], + [2695, 3248], + [2695, 3250], + [2692, 3250], +] + +[[area.polygons]] +vertices = [ + [2694, 3251], + [2695, 3251], +] + +[[area.polygons]] +vertices = [[2695, 3252]] + +[[area.polygons]] +vertices = [ + [2700, 3253], + [2701, 3253], +] + +[[area.polygons]] +vertices = [[2698, 3254]] + +[[area.polygons]] +vertices = [[2700, 3254]] diff --git a/.data/raw-cache/map/area/brimstails_scales.toml b/.data/raw-cache/map/area/brimstails_scales.toml new file mode 100644 index 000000000..b387a3c01 --- /dev/null +++ b/.data/raw-cache/map/area/brimstails_scales.toml @@ -0,0 +1,24 @@ +# Brimstail's Scales music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Brimstail's Scales. +# Bounds x 2368-2415, z 9792-9855; 2816 tiles as 2 rectangles. + +[[area]] +name = "Brimstail's Scales" +area_id = "area.brimstails_scales" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 9792], + [2399, 9792], + [2399, 9855], + [2368, 9855], +] + +[[area.polygons]] +vertices = [ + [2400, 9808], + [2415, 9808], + [2415, 9855], + [2400, 9855], +] diff --git a/.data/raw-cache/map/area/brineratcavern.toml b/.data/raw-cache/map/area/brineratcavern.toml index eca5553d0..8e119a310 100644 --- a/.data/raw-cache/map/area/brineratcavern.toml +++ b/.data/raw-cache/map/area/brineratcavern.toml @@ -1,3 +1,6 @@ +# Regenerated as rectangles from the OSRS wiki music map: 291 tiles moved to +# neighbouring music areas and 97 tiles were added. + [[area]] name = "Brine Rat Cavern" area_id = "area.brineratcavern" @@ -5,8 +8,26 @@ levels = [0, 1, 2, 3] [[area.polygons]] vertices = [ + [2688, 10112], + [2751, 10112], + [2751, 10175], + [2688, 10175], +] + +[[area.polygons]] +vertices = [ + [2687, 10125], + [2687, 10179], +] + +[[area.polygons]] +vertices = [ + [2686, 10147], + [2686, 10179], +] + +[[area.polygons]] +vertices = [ + [2685, 10169], [2685, 10179], - [2753, 10179], - [2749, 10112], - [2688, 10114], ] diff --git a/.data/raw-cache/map/area/bubble_and_squeak.toml b/.data/raw-cache/map/area/bubble_and_squeak.toml new file mode 100644 index 000000000..a2cf5e943 --- /dev/null +++ b/.data/raw-cache/map/area/bubble_and_squeak.toml @@ -0,0 +1,16 @@ +# Bubble and Squeak music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Bubble and Squeak. +# Bounds x 1920-1983, z 4672-4735; 4096 tiles as 1 rectangles. + +[[area]] +name = "Bubble and Squeak" +area_id = "area.bubble_and_squeak" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1920, 4672], + [1983, 4672], + [1983, 4735], + [1920, 4735], +] diff --git a/.data/raw-cache/map/area/burning_desire.toml b/.data/raw-cache/map/area/burning_desire.toml new file mode 100644 index 000000000..7d6bdea8f --- /dev/null +++ b/.data/raw-cache/map/area/burning_desire.toml @@ -0,0 +1,306 @@ +# Burning Desire music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Burning Desire. +# Bounds x 1240-1359, z 3757-3871; 11559 tiles as 48 rectangles. + +[[area]] +name = "Burning Desire" +area_id = "area.burning_desire" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1338, 3757], + [1343, 3757], + [1343, 3871], + [1338, 3871], +] + +[[area.polygons]] +vertices = [ + [1337, 3758], + [1337, 3871], +] + +[[area.polygons]] +vertices = [ + [1336, 3759], + [1336, 3871], +] + +[[area.polygons]] +vertices = [ + [1335, 3760], + [1335, 3871], +] + +[[area.polygons]] +vertices = [ + [1334, 3761], + [1334, 3871], +] + +[[area.polygons]] +vertices = [ + [1333, 3762], + [1333, 3871], +] + +[[area.polygons]] +vertices = [ + [1332, 3763], + [1332, 3871], +] + +[[area.polygons]] +vertices = [ + [1331, 3764], + [1331, 3871], +] + +[[area.polygons]] +vertices = [ + [1330, 3765], + [1330, 3871], +] + +[[area.polygons]] +vertices = [ + [1329, 3766], + [1329, 3871], +] + +[[area.polygons]] +vertices = [ + [1328, 3767], + [1328, 3871], +] + +[[area.polygons]] +vertices = [ + [1303, 3768], + [1313, 3768], + [1313, 3871], + [1303, 3871], +] + +[[area.polygons]] +vertices = [ + [1327, 3768], + [1327, 3871], +] + +[[area.polygons]] +vertices = [ + [1302, 3769], + [1302, 3871], +] + +[[area.polygons]] +vertices = [ + [1314, 3769], + [1314, 3871], +] + +[[area.polygons]] +vertices = [ + [1326, 3769], + [1326, 3871], +] + +[[area.polygons]] +vertices = [ + [1301, 3770], + [1301, 3871], +] + +[[area.polygons]] +vertices = [ + [1315, 3770], + [1315, 3871], +] + +[[area.polygons]] +vertices = [ + [1325, 3770], + [1325, 3871], +] + +[[area.polygons]] +vertices = [ + [1300, 3771], + [1300, 3871], +] + +[[area.polygons]] +vertices = [ + [1316, 3771], + [1316, 3871], +] + +[[area.polygons]] +vertices = [ + [1324, 3771], + [1324, 3871], +] + +[[area.polygons]] +vertices = [ + [1263, 3772], + [1299, 3772], + [1299, 3871], + [1263, 3871], +] + +[[area.polygons]] +vertices = [ + [1317, 3772], + [1323, 3772], + [1323, 3871], + [1317, 3871], +] + +[[area.polygons]] +vertices = [ + [1262, 3773], + [1262, 3871], +] + +[[area.polygons]] +vertices = [ + [1261, 3774], + [1261, 3871], +] + +[[area.polygons]] +vertices = [ + [1260, 3775], + [1260, 3871], +] + +[[area.polygons]] +vertices = [ + [1259, 3776], + [1259, 3871], +] + +[[area.polygons]] +vertices = [ + [1258, 3777], + [1258, 3871], +] + +[[area.polygons]] +vertices = [ + [1257, 3778], + [1257, 3871], +] + +[[area.polygons]] +vertices = [ + [1256, 3779], + [1256, 3871], +] + +[[area.polygons]] +vertices = [ + [1255, 3780], + [1255, 3871], +] + +[[area.polygons]] +vertices = [ + [1254, 3781], + [1254, 3871], +] + +[[area.polygons]] +vertices = [ + [1253, 3782], + [1253, 3871], +] + +[[area.polygons]] +vertices = [ + [1252, 3783], + [1252, 3871], +] + +[[area.polygons]] +vertices = [ + [1251, 3784], + [1251, 3871], +] + +[[area.polygons]] +vertices = [ + [1250, 3785], + [1250, 3871], +] + +[[area.polygons]] +vertices = [ + [1249, 3786], + [1249, 3871], +] + +[[area.polygons]] +vertices = [ + [1248, 3787], + [1248, 3871], +] + +[[area.polygons]] +vertices = [ + [1247, 3788], + [1247, 3871], +] + +[[area.polygons]] +vertices = [ + [1246, 3789], + [1246, 3871], +] + +[[area.polygons]] +vertices = [ + [1245, 3790], + [1245, 3871], +] + +[[area.polygons]] +vertices = [ + [1244, 3791], + [1244, 3871], +] + +[[area.polygons]] +vertices = [ + [1243, 3792], + [1243, 3871], +] + +[[area.polygons]] +vertices = [ + [1242, 3793], + [1242, 3871], +] + +[[area.polygons]] +vertices = [ + [1241, 3794], + [1241, 3871], +] + +[[area.polygons]] +vertices = [ + [1240, 3795], + [1240, 3871], +] + +[[area.polygons]] +vertices = [ + [1344, 3798], + [1359, 3798], + [1359, 3871], + [1344, 3871], +] diff --git a/.data/raw-cache/map/area/burthorpe.toml b/.data/raw-cache/map/area/burthorpe.toml new file mode 100644 index 000000000..d806800f2 --- /dev/null +++ b/.data/raw-cache/map/area/burthorpe.toml @@ -0,0 +1,322 @@ +# Regenerated as rectangles from the OSRS wiki music map: 826 tiles moved to +# neighbouring music areas and 1127 tiles were added. + +[[area]] +name = "Burthorpe" +area_id = "area.burthorpe" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2880, 3505], + [2935, 3505], + [2935, 3576], + [2880, 3576], +] + +[[area.polygons]] +vertices = [ + [2879, 3520], + [2879, 3575], +] + +[[area.polygons]] +vertices = [ + [2878, 3521], + [2878, 3575], +] + +[[area.polygons]] +vertices = [ + [2877, 3522], + [2877, 3575], +] + +[[area.polygons]] +vertices = [ + [2876, 3523], + [2876, 3539], +] + +[[area.polygons]] +vertices = [ + [2936, 3523], + [2937, 3523], + [2937, 3583], + [2936, 3583], +] + +[[area.polygons]] +vertices = [ + [2875, 3524], + [2875, 3536], +] + +[[area.polygons]] +vertices = [ + [2874, 3525], + [2874, 3536], +] + +[[area.polygons]] +vertices = [ + [2873, 3526], + [2873, 3536], +] + +[[area.polygons]] +vertices = [ + [2872, 3527], + [2872, 3535], +] + +[[area.polygons]] +vertices = [ + [2871, 3528], + [2871, 3535], +] + +[[area.polygons]] +vertices = [ + [2870, 3529], + [2870, 3536], +] + +[[area.polygons]] +vertices = [ + [2869, 3530], + [2869, 3536], +] + +[[area.polygons]] +vertices = [ + [2786, 3531], + [2816, 3531], + [2816, 3545], + [2786, 3545], +] + +[[area.polygons]] +vertices = [ + [2868, 3531], + [2868, 3536], +] + +[[area.polygons]] +vertices = [ + [2817, 3532], + [2817, 3545], +] + +[[area.polygons]] +vertices = [ + [2867, 3532], + [2867, 3536], +] + +[[area.polygons]] +vertices = [ + [2818, 3533], + [2818, 3545], +] + +[[area.polygons]] +vertices = [ + [2866, 3533], + [2866, 3535], +] + +[[area.polygons]] +vertices = [ + [2819, 3534], + [2819, 3545], +] + +[[area.polygons]] +vertices = [ + [2865, 3534], + [2865, 3535], +] + +[[area.polygons]] +vertices = [ + [2820, 3535], + [2820, 3545], +] + +[[area.polygons]] +vertices = [ + [2821, 3536], + [2821, 3545], +] + +[[area.polygons]] +vertices = [ + [2822, 3537], + [2822, 3545], +] + +[[area.polygons]] +vertices = [ + [2823, 3538], + [2823, 3545], +] + +[[area.polygons]] +vertices = [ + [2824, 3539], + [2824, 3545], +] + +[[area.polygons]] +vertices = [ + [2825, 3540], + [2825, 3545], +] + +[[area.polygons]] +vertices = [ + [2826, 3541], + [2826, 3545], +] + +[[area.polygons]] +vertices = [ + [2876, 3541], + [2876, 3544], +] + +[[area.polygons]] +vertices = [ + [2827, 3542], + [2827, 3545], +] + +[[area.polygons]] +vertices = [ + [2876, 3548], + [2876, 3551], +] + +[[area.polygons]] +vertices = [ + [2876, 3553], + [2876, 3575], +] + +[[area.polygons]] +vertices = [ + [2847, 3556], + [2864, 3556], + [2864, 3577], + [2847, 3577], +] + +[[area.polygons]] +vertices = [ + [2867, 3556], + [2870, 3556], + [2870, 3576], + [2867, 3576], +] + +[[area.polygons]] +vertices = [ + [2873, 3556], + [2875, 3556], + [2875, 3575], + [2873, 3575], +] + +[[area.polygons]] +vertices = [ + [2865, 3557], + [2866, 3557], + [2866, 3578], + [2865, 3578], +] + +[[area.polygons]] +vertices = [ + [2871, 3557], + [2872, 3557], + [2872, 3575], + [2871, 3575], +] + +[[area.polygons]] +vertices = [[2871, 3576]] + +[[area.polygons]] +vertices = [ + [2867, 3577], + [2869, 3577], +] + +[[area.polygons]] +vertices = [ + [2881, 3577], + [2935, 3577], +] + +[[area.polygons]] +vertices = [ + [2849, 3578], + [2864, 3578], + [2864, 3579], + [2849, 3579], +] + +[[area.polygons]] +vertices = [[2867, 3578]] + +[[area.polygons]] +vertices = [ + [2882, 3578], + [2935, 3578], +] + +[[area.polygons]] +vertices = [[2865, 3579]] + +[[area.polygons]] +vertices = [ + [2883, 3579], + [2935, 3579], +] + +[[area.polygons]] +vertices = [ + [2850, 3580], + [2856, 3580], +] + +[[area.polygons]] +vertices = [ + [2884, 3580], + [2935, 3580], +] + +[[area.polygons]] +vertices = [ + [2850, 3581], + [2855, 3581], +] + +[[area.polygons]] +vertices = [ + [2885, 3581], + [2935, 3581], +] + +[[area.polygons]] +vertices = [ + [2886, 3582], + [2935, 3582], +] + +[[area.polygons]] +vertices = [ + [2887, 3583], + [2935, 3583], +] diff --git a/.data/raw-cache/map/area/business_as_unusual.toml b/.data/raw-cache/map/area/business_as_unusual.toml new file mode 100644 index 000000000..ad7a59ca5 --- /dev/null +++ b/.data/raw-cache/map/area/business_as_unusual.toml @@ -0,0 +1,246 @@ +# Business as Unusual music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Business as Unusual. +# Bounds x 2782-2812, z 2495-2535; 658 tiles as 37 rectangles. + +[[area]] +name = "Business as Unusual" +area_id = "area.business_as_unusual" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2808, 2495], + [2809, 2495], + [2809, 2504], + [2808, 2504], +] + +[[area.polygons]] +vertices = [ + [2810, 2496], + [2810, 2503], +] + +[[area.polygons]] +vertices = [ + [2793, 2498], + [2794, 2498], + [2794, 2530], + [2793, 2530], +] + +[[area.polygons]] +vertices = [ + [2800, 2498], + [2801, 2498], + [2801, 2522], + [2800, 2522], +] + +[[area.polygons]] +vertices = [ + [2795, 2499], + [2795, 2529], +] + +[[area.polygons]] +vertices = [ + [2802, 2499], + [2802, 2522], +] + +[[area.polygons]] +vertices = [ + [2790, 2501], + [2792, 2501], + [2792, 2510], + [2790, 2510], +] + +[[area.polygons]] +vertices = [ + [2796, 2501], + [2799, 2501], + [2799, 2523], + [2796, 2523], +] + +[[area.polygons]] +vertices = [ + [2803, 2501], + [2805, 2501], + [2805, 2523], + [2803, 2523], +] + +[[area.polygons]] +vertices = [ + [2806, 2502], + [2807, 2502], +] + +[[area.polygons]] +vertices = [ + [2806, 2509], + [2808, 2509], + [2808, 2510], + [2806, 2510], +] + +[[area.polygons]] +vertices = [ + [2806, 2511], + [2806, 2513], +] + +[[area.polygons]] +vertices = [ + [2789, 2514], + [2792, 2514], + [2792, 2531], + [2789, 2531], +] + +[[area.polygons]] +vertices = [ + [2787, 2515], + [2788, 2515], + [2788, 2531], + [2787, 2531], +] + +[[area.polygons]] +vertices = [ + [2786, 2516], + [2786, 2531], +] + +[[area.polygons]] +vertices = [ + [2784, 2517], + [2785, 2517], + [2785, 2530], + [2784, 2530], +] + +[[area.polygons]] +vertices = [ + [2783, 2520], + [2783, 2527], +] + +[[area.polygons]] +vertices = [ + [2806, 2520], + [2806, 2523], +] + +[[area.polygons]] +vertices = [ + [2782, 2521], + [2782, 2523], +] + +[[area.polygons]] +vertices = [ + [2807, 2521], + [2809, 2521], + [2809, 2522], + [2807, 2522], +] + +[[area.polygons]] +vertices = [ + [2796, 2524], + [2798, 2524], + [2798, 2529], + [2796, 2529], +] + +[[area.polygons]] +vertices = [ + [2808, 2526], + [2809, 2526], + [2809, 2533], + [2808, 2533], +] + +[[area.polygons]] +vertices = [ + [2799, 2527], + [2799, 2530], +] + +[[area.polygons]] +vertices = [ + [2810, 2527], + [2810, 2532], +] + +[[area.polygons]] +vertices = [ + [2800, 2528], + [2800, 2531], +] + +[[area.polygons]] +vertices = [ + [2807, 2528], + [2807, 2535], +] + +[[area.polygons]] +vertices = [ + [2811, 2528], + [2812, 2528], + [2812, 2530], + [2811, 2530], +] + +[[area.polygons]] +vertices = [ + [2801, 2529], + [2801, 2532], +] + +[[area.polygons]] +vertices = [ + [2806, 2529], + [2806, 2533], +] + +[[area.polygons]] +vertices = [ + [2802, 2530], + [2802, 2532], +] + +[[area.polygons]] +vertices = [ + [2805, 2530], + [2805, 2532], +] + +[[area.polygons]] +vertices = [[2785, 2531]] + +[[area.polygons]] +vertices = [ + [2803, 2531], + [2804, 2531], +] + +[[area.polygons]] +vertices = [[2811, 2531]] + +[[area.polygons]] +vertices = [ + [2789, 2532], + [2791, 2532], +] + +[[area.polygons]] +vertices = [[2803, 2532]] + +[[area.polygons]] +vertices = [[2808, 2534]] diff --git a/.data/raw-cache/map/area/cabin_fever.toml b/.data/raw-cache/map/area/cabin_fever.toml new file mode 100644 index 000000000..2d420660f --- /dev/null +++ b/.data/raw-cache/map/area/cabin_fever.toml @@ -0,0 +1,16 @@ +# Cabin Fever music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Cabin Fever. +# Bounds x 1792-1855, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Cabin Fever" +area_id = "area.cabin_fever" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1792, 4800], + [1855, 4800], + [1855, 4863], + [1792, 4863], +] diff --git a/.data/raw-cache/map/area/call_of_the_tlati.toml b/.data/raw-cache/map/area/call_of_the_tlati.toml new file mode 100644 index 000000000..1e80067ce --- /dev/null +++ b/.data/raw-cache/map/area/call_of_the_tlati.toml @@ -0,0 +1,1391 @@ +# Call of the Tlati music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Call of the Tlati. +# Bounds x 1216-1386, z 2982-9535; 30059 tiles as 218 rectangles. + +[[area]] +name = "Call of the Tlati" +area_id = "area.call_of_the_tlati" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1252, 2982], + [1253, 2982], + [1253, 3187], + [1252, 3187], +] + +[[area.polygons]] +vertices = [ + [1268, 2982], + [1268, 3185], +] + +[[area.polygons]] +vertices = [ + [1254, 2983], + [1256, 2983], + [1256, 3184], + [1254, 3184], +] + +[[area.polygons]] +vertices = [ + [1259, 2983], + [1260, 2983], + [1260, 3185], + [1259, 3185], +] + +[[area.polygons]] +vertices = [ + [1266, 2983], + [1267, 2983], + [1267, 3183], + [1266, 3183], +] + +[[area.polygons]] +vertices = [ + [1251, 2984], + [1251, 3187], +] + +[[area.polygons]] +vertices = [ + [1257, 2984], + [1258, 2984], + [1258, 3184], + [1257, 3184], +] + +[[area.polygons]] +vertices = [ + [1261, 2984], + [1262, 2984], + [1262, 3186], + [1261, 3186], +] + +[[area.polygons]] +vertices = [ + [1265, 2984], + [1265, 3183], +] + +[[area.polygons]] +vertices = [ + [1269, 2984], + [1269, 3185], +] + +[[area.polygons]] +vertices = [ + [1246, 2985], + [1247, 2985], + [1247, 3092], + [1246, 3092], +] + +[[area.polygons]] +vertices = [ + [1250, 2985], + [1250, 3115], +] + +[[area.polygons]] +vertices = [ + [1263, 2985], + [1264, 2985], + [1264, 3184], + [1263, 3184], +] + +[[area.polygons]] +vertices = [ + [1245, 2986], + [1245, 3091], +] + +[[area.polygons]] +vertices = [ + [1248, 2986], + [1249, 2986], + [1249, 3114], + [1248, 3114], +] + +[[area.polygons]] +vertices = [ + [1270, 2986], + [1270, 3184], +] + +[[area.polygons]] +vertices = [ + [1244, 2987], + [1244, 3090], +] + +[[area.polygons]] +vertices = [ + [1271, 2987], + [1272, 2987], + [1272, 3182], + [1271, 3182], +] + +[[area.polygons]] +vertices = [ + [1243, 2988], + [1243, 3006], +] + +[[area.polygons]] +vertices = [ + [1273, 2988], + [1275, 2988], + [1275, 2990], + [1273, 2990], +] + +[[area.polygons]] +vertices = [ + [1242, 2989], + [1242, 2996], +] + +[[area.polygons]] +vertices = [ + [1241, 2990], + [1241, 2995], +] + +[[area.polygons]] +vertices = [ + [1240, 2991], + [1240, 2994], +] + +[[area.polygons]] +vertices = [ + [1273, 2991], + [1274, 2991], + [1274, 3182], + [1273, 3182], +] + +[[area.polygons]] +vertices = [ + [1239, 2992], + [1239, 2994], +] + +[[area.polygons]] +vertices = [ + [1275, 2992], + [1275, 3183], +] + +[[area.polygons]] +vertices = [ + [1238, 2993], + [1238, 2994], +] + +[[area.polygons]] +vertices = [ + [1276, 2993], + [1276, 3183], +] + +[[area.polygons]] +vertices = [ + [1277, 2994], + [1277, 3183], +] + +[[area.polygons]] +vertices = [ + [1278, 2996], + [1278, 3182], +] + +[[area.polygons]] +vertices = [ + [1279, 2997], + [1280, 2997], + [1280, 3178], + [1279, 3178], +] + +[[area.polygons]] +vertices = [ + [1300, 2997], + [1301, 2997], + [1301, 3182], + [1300, 3182], +] + +[[area.polygons]] +vertices = [ + [1281, 2998], + [1282, 2998], + [1282, 3177], + [1281, 3177], +] + +[[area.polygons]] +vertices = [ + [1302, 2998], + [1302, 3183], +] + +[[area.polygons]] +vertices = [ + [1283, 2999], + [1283, 3177], +] + +[[area.polygons]] +vertices = [ + [1298, 2999], + [1299, 2999], + [1299, 3182], + [1298, 3182], +] + +[[area.polygons]] +vertices = [ + [1242, 3000], + [1242, 3004], +] + +[[area.polygons]] +vertices = [ + [1296, 3000], + [1297, 3000], + [1297, 3183], + [1296, 3183], +] + +[[area.polygons]] +vertices = [ + [1303, 3000], + [1303, 3183], +] + +[[area.polygons]] +vertices = [ + [1305, 3000], + [1305, 3187], +] + +[[area.polygons]] +vertices = [ + [1241, 3001], + [1241, 3002], +] + +[[area.polygons]] +vertices = [ + [1284, 3001], + [1284, 3177], +] + +[[area.polygons]] +vertices = [ + [1295, 3001], + [1295, 3184], +] + +[[area.polygons]] +vertices = [ + [1304, 3001], + [1304, 3184], +] + +[[area.polygons]] +vertices = [ + [1306, 3001], + [1307, 3001], + [1307, 3187], + [1306, 3187], +] + +[[area.polygons]] +vertices = [ + [1285, 3002], + [1285, 3177], +] + +[[area.polygons]] +vertices = [ + [1294, 3002], + [1294, 3184], +] + +[[area.polygons]] +vertices = [ + [1308, 3002], + [1308, 3187], +] + +[[area.polygons]] +vertices = [ + [1291, 3003], + [1293, 3003], + [1293, 3181], + [1291, 3181], +] + +[[area.polygons]] +vertices = [ + [1309, 3003], + [1311, 3003], + [1311, 3186], + [1309, 3186], +] + +[[area.polygons]] +vertices = [ + [1288, 3004], + [1290, 3004], + [1290, 3179], + [1288, 3179], +] + +[[area.polygons]] +vertices = [ + [1312, 3004], + [1312, 3185], +] + +[[area.polygons]] +vertices = [ + [1286, 3005], + [1287, 3005], + [1287, 3177], + [1286, 3177], +] + +[[area.polygons]] +vertices = [ + [1313, 3006], + [1313, 3185], +] + +[[area.polygons]] +vertices = [ + [1314, 3007], + [1314, 3185], +] + +[[area.polygons]] +vertices = [ + [1315, 3008], + [1315, 3184], +] + +[[area.polygons]] +vertices = [ + [1235, 3009], + [1235, 3075], +] + +[[area.polygons]] +vertices = [[1243, 3009]] + +[[area.polygons]] +vertices = [ + [1316, 3009], + [1316, 3184], +] + +[[area.polygons]] +vertices = [ + [1327, 3009], + [1327, 3190], +] + +[[area.polygons]] +vertices = [ + [1231, 3010], + [1232, 3010], + [1232, 3075], + [1231, 3075], +] + +[[area.polygons]] +vertices = [ + [1234, 3010], + [1234, 3075], +] + +[[area.polygons]] +vertices = [ + [1236, 3010], + [1239, 3010], + [1239, 3076], + [1236, 3076], +] + +[[area.polygons]] +vertices = [ + [1317, 3010], + [1319, 3010], + [1319, 3185], + [1317, 3185], +] + +[[area.polygons]] +vertices = [ + [1326, 3010], + [1326, 3189], +] + +[[area.polygons]] +vertices = [ + [1328, 3010], + [1328, 3192], +] + +[[area.polygons]] +vertices = [ + [1338, 3010], + [1341, 3010], + [1341, 3194], + [1338, 3194], +] + +[[area.polygons]] +vertices = [ + [1229, 3011], + [1230, 3011], + [1230, 3014], + [1229, 3014], +] + +[[area.polygons]] +vertices = [ + [1233, 3011], + [1233, 3075], +] + +[[area.polygons]] +vertices = [ + [1240, 3011], + [1241, 3011], + [1241, 3086], + [1240, 3086], +] + +[[area.polygons]] +vertices = [ + [1243, 3011], + [1243, 3089], +] + +[[area.polygons]] +vertices = [ + [1320, 3011], + [1321, 3011], + [1321, 3187], + [1320, 3187], +] + +[[area.polygons]] +vertices = [ + [1325, 3011], + [1325, 3189], +] + +[[area.polygons]] +vertices = [ + [1329, 3011], + [1330, 3011], + [1330, 3193], + [1329, 3193], +] + +[[area.polygons]] +vertices = [ + [1332, 3011], + [1337, 3011], + [1337, 3194], + [1332, 3194], +] + +[[area.polygons]] +vertices = [ + [1342, 3011], + [1342, 3194], +] + +[[area.polygons]] +vertices = [ + [1228, 3012], + [1228, 3013], +] + +[[area.polygons]] +vertices = [ + [1242, 3012], + [1242, 3088], +] + +[[area.polygons]] +vertices = [ + [1322, 3012], + [1324, 3012], + [1324, 3189], + [1322, 3189], +] + +[[area.polygons]] +vertices = [ + [1331, 3012], + [1331, 3194], +] + +[[area.polygons]] +vertices = [ + [1343, 3012], + [1344, 3012], + [1344, 3194], + [1343, 3194], +] + +[[area.polygons]] +vertices = [ + [1345, 3013], + [1345, 3197], +] + +[[area.polygons]] +vertices = [ + [1346, 3014], + [1346, 3020], +] + +[[area.polygons]] +vertices = [ + [1230, 3015], + [1230, 3072], +] + +[[area.polygons]] +vertices = [ + [1229, 3016], + [1229, 3069], +] + +[[area.polygons]] +vertices = [ + [1347, 3016], + [1347, 3019], +] + +[[area.polygons]] +vertices = [ + [1228, 3018], + [1228, 3069], +] + +[[area.polygons]] +vertices = [[1348, 3018]] + +[[area.polygons]] +vertices = [ + [1224, 3019], + [1227, 3019], + [1227, 3068], + [1224, 3068], +] + +[[area.polygons]] +vertices = [ + [1223, 3020], + [1223, 3023], +] + +[[area.polygons]] +vertices = [ + [1222, 3021], + [1222, 3022], +] + +[[area.polygons]] +vertices = [ + [1223, 3026], + [1223, 3027], +] + +[[area.polygons]] +vertices = [ + [1222, 3029], + [1223, 3029], + [1223, 3069], + [1222, 3069], +] + +[[area.polygons]] +vertices = [ + [1221, 3030], + [1221, 3032], +] + +[[area.polygons]] +vertices = [ + [1221, 3034], + [1221, 3056], +] + +[[area.polygons]] +vertices = [ + [1220, 3035], + [1220, 3055], +] + +[[area.polygons]] +vertices = [ + [1219, 3038], + [1219, 3053], +] + +[[area.polygons]] +vertices = [ + [1218, 3039], + [1218, 3051], +] + +[[area.polygons]] +vertices = [ + [1217, 3043], + [1217, 3045], +] + +[[area.polygons]] +vertices = [ + [1220, 3059], + [1221, 3059], + [1221, 3061], + [1220, 3061], +] + +[[area.polygons]] +vertices = [ + [1221, 3062], + [1221, 3068], +] + +[[area.polygons]] +vertices = [ + [1220, 3064], + [1220, 3066], +] + +[[area.polygons]] +vertices = [ + [1224, 3069], + [1225, 3069], +] + +[[area.polygons]] +vertices = [[1230, 3074]] + +[[area.polygons]] +vertices = [ + [1237, 3077], + [1239, 3077], +] + +[[area.polygons]] +vertices = [ + [1238, 3078], + [1239, 3078], + [1239, 3079], + [1238, 3079], +] + +[[area.polygons]] +vertices = [ + [1346, 3078], + [1386, 3078], +] + +[[area.polygons]] +vertices = [ + [1346, 3079], + [1385, 3079], +] + +[[area.polygons]] +vertices = [ + [1239, 3080], + [1239, 3085], +] + +[[area.polygons]] +vertices = [ + [1346, 3080], + [1384, 3080], + [1384, 3081], + [1346, 3081], +] + +[[area.polygons]] +vertices = [ + [1346, 3082], + [1383, 3082], + [1383, 3083], + [1346, 3083], +] + +[[area.polygons]] +vertices = [ + [1346, 3084], + [1382, 3084], +] + +[[area.polygons]] +vertices = [ + [1346, 3085], + [1381, 3085], +] + +[[area.polygons]] +vertices = [ + [1346, 3086], + [1379, 3086], +] + +[[area.polygons]] +vertices = [[1241, 3087]] + +[[area.polygons]] +vertices = [ + [1346, 3087], + [1378, 3087], +] + +[[area.polygons]] +vertices = [ + [1346, 3088], + [1377, 3088], +] + +[[area.polygons]] +vertices = [ + [1346, 3089], + [1376, 3089], + [1376, 3090], + [1346, 3090], +] + +[[area.polygons]] +vertices = [ + [1346, 3091], + [1375, 3091], +] + +[[area.polygons]] +vertices = [ + [1346, 3092], + [1374, 3092], +] + +[[area.polygons]] +vertices = [ + [1247, 3093], + [1247, 3114], +] + +[[area.polygons]] +vertices = [ + [1346, 3093], + [1373, 3093], +] + +[[area.polygons]] +vertices = [ + [1346, 3094], + [1372, 3094], + [1372, 3095], + [1346, 3095], +] + +[[area.polygons]] +vertices = [ + [1346, 3096], + [1371, 3096], + [1371, 3097], + [1346, 3097], +] + +[[area.polygons]] +vertices = [ + [1346, 3098], + [1370, 3098], + [1370, 3100], + [1346, 3100], +] + +[[area.polygons]] +vertices = [ + [1346, 3101], + [1369, 3101], + [1369, 3106], + [1346, 3106], +] + +[[area.polygons]] +vertices = [ + [1346, 3107], + [1368, 3107], + [1368, 3108], + [1346, 3108], +] + +[[area.polygons]] +vertices = [ + [1346, 3109], + [1367, 3109], + [1367, 3110], + [1346, 3110], +] + +[[area.polygons]] +vertices = [ + [1346, 3111], + [1366, 3111], + [1366, 3113], + [1346, 3113], +] + +[[area.polygons]] +vertices = [ + [1346, 3114], + [1365, 3114], + [1365, 3116], + [1346, 3116], +] + +[[area.polygons]] +vertices = [ + [1346, 3117], + [1364, 3117], + [1364, 3179], + [1346, 3179], +] + +[[area.polygons]] +vertices = [ + [1365, 3121], + [1365, 3179], +] + +[[area.polygons]] +vertices = [ + [1366, 3122], + [1366, 3179], +] + +[[area.polygons]] +vertices = [ + [1367, 3123], + [1367, 3179], +] + +[[area.polygons]] +vertices = [ + [1368, 3124], + [1371, 3124], + [1371, 3179], + [1368, 3179], +] + +[[area.polygons]] +vertices = [ + [1372, 3125], + [1372, 3179], +] + +[[area.polygons]] +vertices = [ + [1373, 3126], + [1373, 3179], +] + +[[area.polygons]] +vertices = [ + [1374, 3128], + [1374, 3179], +] + +[[area.polygons]] +vertices = [ + [1375, 3130], + [1375, 3178], +] + +[[area.polygons]] +vertices = [ + [1250, 3133], + [1250, 3186], +] + +[[area.polygons]] +vertices = [ + [1376, 3133], + [1376, 3177], +] + +[[area.polygons]] +vertices = [ + [1377, 3134], + [1377, 3176], +] + +[[area.polygons]] +vertices = [ + [1249, 3135], + [1249, 3186], +] + +[[area.polygons]] +vertices = [ + [1248, 3136], + [1248, 3185], +] + +[[area.polygons]] +vertices = [ + [1378, 3136], + [1378, 3174], +] + +[[area.polygons]] +vertices = [ + [1247, 3137], + [1247, 3185], +] + +[[area.polygons]] +vertices = [ + [1379, 3137], + [1379, 3173], +] + +[[area.polygons]] +vertices = [ + [1216, 3138], + [1234, 3138], + [1234, 3139], + [1216, 3139], +] + +[[area.polygons]] +vertices = [ + [1246, 3138], + [1246, 3185], +] + +[[area.polygons]] +vertices = [ + [1380, 3138], + [1380, 3159], +] + +[[area.polygons]] +vertices = [ + [1235, 3139], + [1235, 3188], +] + +[[area.polygons]] +vertices = [ + [1244, 3139], + [1245, 3139], + [1245, 3186], + [1244, 3186], +] + +[[area.polygons]] +vertices = [ + [1217, 3140], + [1234, 3140], +] + +[[area.polygons]] +vertices = [ + [1236, 3140], + [1243, 3140], + [1243, 3188], + [1236, 3188], +] + +[[area.polygons]] +vertices = [ + [1381, 3140], + [1381, 3155], +] + +[[area.polygons]] +vertices = [ + [1218, 3141], + [1234, 3141], +] + +[[area.polygons]] +vertices = [ + [1219, 3142], + [1234, 3142], + [1234, 3144], + [1219, 3144], +] + +[[area.polygons]] +vertices = [ + [1382, 3142], + [1382, 3153], +] + +[[area.polygons]] +vertices = [ + [1220, 3145], + [1234, 3145], + [1234, 3182], + [1220, 3182], +] + +[[area.polygons]] +vertices = [ + [1219, 3150], + [1219, 3181], +] + +[[area.polygons]] +vertices = [ + [1217, 3151], + [1218, 3151], + [1218, 3161], + [1217, 3161], +] + +[[area.polygons]] +vertices = [ + [1216, 3152], + [1216, 3157], +] + +[[area.polygons]] +vertices = [ + [1218, 3162], + [1218, 3180], +] + +[[area.polygons]] +vertices = [ + [1380, 3165], + [1380, 3172], +] + +[[area.polygons]] +vertices = [ + [1217, 3167], + [1217, 3175], +] + +[[area.polygons]] +vertices = [ + [1381, 3168], + [1381, 3171], +] + +[[area.polygons]] +vertices = [[1216, 3169]] + +[[area.polygons]] +vertices = [[1382, 3170]] + +[[area.polygons]] +vertices = [ + [1217, 3177], + [1217, 3180], +] + +[[area.polygons]] +vertices = [ + [1216, 3178], + [1216, 3180], +] + +[[area.polygons]] +vertices = [[1281, 3178]] + +[[area.polygons]] +vertices = [[1287, 3178]] + +[[area.polygons]] +vertices = [[1279, 3179]] + +[[area.polygons]] +vertices = [ + [1289, 3180], + [1290, 3180], +] + +[[area.polygons]] +vertices = [ + [1346, 3180], + [1351, 3180], +] + +[[area.polygons]] +vertices = [[1290, 3181]] + +[[area.polygons]] +vertices = [ + [1346, 3181], + [1350, 3181], +] + +[[area.polygons]] +vertices = [ + [1346, 3182], + [1349, 3182], +] + +[[area.polygons]] +vertices = [ + [1221, 3183], + [1234, 3183], + [1234, 3185], + [1221, 3185], +] + +[[area.polygons]] +vertices = [[1271, 3183]] + +[[area.polygons]] +vertices = [ + [1293, 3183], + [1293, 3184], +] + +[[area.polygons]] +vertices = [ + [1346, 3183], + [1348, 3183], +] + +[[area.polygons]] +vertices = [[1267, 3184]] + +[[area.polygons]] +vertices = [ + [1346, 3184], + [1347, 3184], + [1347, 3195], + [1346, 3195], +] + +[[area.polygons]] +vertices = [ + [1254, 3185], + [1254, 3186], +] + +[[area.polygons]] +vertices = [[1263, 3185]] + +[[area.polygons]] +vertices = [ + [1222, 3186], + [1234, 3186], + [1234, 3189], + [1222, 3189], +] + +[[area.polygons]] +vertices = [ + [1304, 3186], + [1304, 3188], +] + +[[area.polygons]] +vertices = [[1319, 3186]] + +[[area.polygons]] +vertices = [ + [1244, 3187], + [1244, 3188], +] + +[[area.polygons]] +vertices = [ + [1301, 3187], + [1303, 3187], + [1303, 3188], + [1301, 3188], +] + +[[area.polygons]] +vertices = [[1309, 3187]] + +[[area.polygons]] +vertices = [ + [1221, 3188], + [1221, 3199], +] + +[[area.polygons]] +vertices = [[1300, 3188]] + +[[area.polygons]] +vertices = [[1321, 3188]] + +[[area.polygons]] +vertices = [ + [1237, 3189], + [1243, 3189], +] + +[[area.polygons]] +vertices = [ + [1301, 3189], + [1302, 3189], +] + +[[area.polygons]] +vertices = [ + [1219, 3190], + [1220, 3190], + [1220, 3198], + [1219, 3198], +] + +[[area.polygons]] +vertices = [ + [1222, 3190], + [1233, 3190], + [1233, 3192], + [1222, 3192], +] + +[[area.polygons]] +vertices = [ + [1218, 3192], + [1218, 3197], +] + +[[area.polygons]] +vertices = [[1234, 3192]] + +[[area.polygons]] +vertices = [ + [1222, 3193], + [1228, 3193], + [1228, 3194], + [1222, 3194], +] + +[[area.polygons]] +vertices = [ + [1232, 3193], + [1233, 3193], +] + +[[area.polygons]] +vertices = [ + [1222, 3195], + [1227, 3195], + [1227, 3196], + [1222, 3196], +] + +[[area.polygons]] +vertices = [ + [1337, 3195], + [1340, 3195], +] + +[[area.polygons]] +vertices = [[1344, 3195]] + +[[area.polygons]] +vertices = [[1346, 3196]] + +[[area.polygons]] +vertices = [ + [1222, 3197], + [1226, 3197], +] + +[[area.polygons]] +vertices = [ + [1344, 3197], + [1344, 3198], +] + +[[area.polygons]] +vertices = [ + [1222, 3198], + [1225, 3198], +] + +[[area.polygons]] +vertices = [ + [1222, 3199], + [1223, 3199], +] + +[[area.polygons]] +vertices = [ + [1300, 9344], + [1343, 9344], + [1343, 9357], + [1300, 9357], +] + +[[area.polygons]] +vertices = [ + [1300, 9358], + [1337, 9358], + [1337, 9359], + [1300, 9359], +] + +[[area.polygons]] +vertices = [ + [1300, 9360], + [1320, 9360], + [1320, 9362], + [1300, 9362], +] + +[[area.polygons]] +vertices = [ + [1309, 9363], + [1314, 9363], + [1314, 9375], + [1309, 9375], +] + +[[area.polygons]] +vertices = [ + [1309, 9376], + [1313, 9376], +] + +[[area.polygons]] +vertices = [[1312, 9377]] + +[[area.polygons]] +vertices = [ + [1216, 9472], + [1279, 9472], + [1279, 9535], + [1216, 9535], +] diff --git a/.data/raw-cache/map/area/camelot.toml b/.data/raw-cache/map/area/camelot.toml new file mode 100644 index 000000000..2af4efd07 --- /dev/null +++ b/.data/raw-cache/map/area/camelot.toml @@ -0,0 +1,49 @@ +# Regenerated as rectangles from the OSRS wiki music map: 21 tiles moved to +# neighbouring music areas and 80 tiles were added. + +[[area]] +name = "Camelot" +area_id = "area.camelot" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2740, 3470], + [2790, 3470], + [2790, 3494], + [2740, 3494], +] + +[[area.polygons]] +vertices = [ + [2740, 3495], + [2789, 3495], +] + +[[area.polygons]] +vertices = [ + [2740, 3496], + [2788, 3496], + [2788, 3515], + [2740, 3515], +] + +[[area.polygons]] +vertices = [ + [2789, 3505], + [2789, 3515], +] + +[[area.polygons]] +vertices = [ + [2790, 3507], + [2790, 3515], +] + +[[area.polygons]] +vertices = [ + [2744, 3516], + [2783, 3516], + [2783, 3517], + [2744, 3517], +] diff --git a/.data/raw-cache/map/area/cannonballin.toml b/.data/raw-cache/map/area/cannonballin.toml new file mode 100644 index 000000000..47123884c --- /dev/null +++ b/.data/raw-cache/map/area/cannonballin.toml @@ -0,0 +1,169 @@ +# Cannonballin' music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Cannonballin'. +# Bounds x 2843-2871, z 2315-2343; 495 tiles as 26 rectangles. + +[[area]] +name = "Cannonballin'" +area_id = "area.cannonballin" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2860, 2315], + [2865, 2315], + [2865, 2331], + [2860, 2331], +] + +[[area.polygons]] +vertices = [ + [2851, 2316], + [2859, 2316], + [2859, 2336], + [2851, 2336], +] + +[[area.polygons]] +vertices = [ + [2866, 2316], + [2868, 2316], + [2868, 2329], + [2866, 2329], +] + +[[area.polygons]] +vertices = [ + [2845, 2317], + [2850, 2317], + [2850, 2322], + [2845, 2322], +] + +[[area.polygons]] +vertices = [ + [2869, 2317], + [2869, 2328], +] + +[[area.polygons]] +vertices = [ + [2844, 2318], + [2844, 2322], +] + +[[area.polygons]] +vertices = [ + [2870, 2318], + [2870, 2326], +] + +[[area.polygons]] +vertices = [[2843, 2320]] + +[[area.polygons]] +vertices = [ + [2871, 2320], + [2871, 2324], +] + +[[area.polygons]] +vertices = [ + [2846, 2323], + [2850, 2323], +] + +[[area.polygons]] +vertices = [ + [2847, 2324], + [2850, 2324], +] + +[[area.polygons]] +vertices = [ + [2848, 2325], + [2850, 2325], +] + +[[area.polygons]] +vertices = [ + [2849, 2326], + [2850, 2326], + [2850, 2333], + [2849, 2333], +] + +[[area.polygons]] +vertices = [ + [2866, 2330], + [2867, 2330], +] + +[[area.polygons]] +vertices = [[2866, 2331]] + +[[area.polygons]] +vertices = [ + [2860, 2332], + [2864, 2332], + [2864, 2333], + [2860, 2333], +] + +[[area.polygons]] +vertices = [[2850, 2334]] + +[[area.polygons]] +vertices = [ + [2860, 2334], + [2863, 2334], +] + +[[area.polygons]] +vertices = [ + [2860, 2335], + [2862, 2335], +] + +[[area.polygons]] +vertices = [ + [2860, 2336], + [2861, 2336], +] + +[[area.polygons]] +vertices = [ + [2852, 2337], + [2860, 2337], +] + +[[area.polygons]] +vertices = [ + [2853, 2338], + [2860, 2338], +] + +[[area.polygons]] +vertices = [ + [2854, 2339], + [2860, 2339], +] + +[[area.polygons]] +vertices = [ + [2854, 2340], + [2859, 2340], + [2859, 2341], + [2854, 2341], +] + +[[area.polygons]] +vertices = [ + [2855, 2342], + [2859, 2342], +] + +[[area.polygons]] +vertices = [ + [2856, 2343], + [2857, 2343], +] diff --git a/.data/raw-cache/map/area/castle_wars.toml b/.data/raw-cache/map/area/castle_wars.toml new file mode 100644 index 000000000..342076867 --- /dev/null +++ b/.data/raw-cache/map/area/castle_wars.toml @@ -0,0 +1,103 @@ +# Castle Wars above ground: the arena map square with both castles and the lobby building with +# the team portals (2436,3082-3096). Music area for "Castle Wars". The waiting rooms and tunnels +# underneath are castle_wars_underground.toml. +# The arena. +# The lobby. +# +# Regenerated as rectangles from the OSRS wiki music map: 630 tiles moved to +# neighbouring music areas. + +[[area]] +name = "Castle Wars" +area_id = "area.castle_wars" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2379, 3072], + [2431, 3072], + [2431, 3119], + [2379, 3119], +] + +[[area.polygons]] +vertices = [ + [2378, 3073], + [2378, 3135], +] + +[[area.polygons]] +vertices = [ + [2377, 3074], + [2377, 3135], +] + +[[area.polygons]] +vertices = [ + [2376, 3075], + [2376, 3135], +] + +[[area.polygons]] +vertices = [ + [2375, 3076], + [2375, 3135], +] + +[[area.polygons]] +vertices = [ + [2374, 3077], + [2374, 3135], +] + +[[area.polygons]] +vertices = [ + [2373, 3078], + [2373, 3135], +] + +[[area.polygons]] +vertices = [ + [2368, 3079], + [2368, 3135], +] + +[[area.polygons]] +vertices = [ + [2372, 3079], + [2372, 3135], +] + +[[area.polygons]] +vertices = [ + [2369, 3080], + [2371, 3080], + [2371, 3135], + [2369, 3135], +] + +[[area.polygons]] +vertices = [ + [2379, 3120], + [2430, 3120], +] + +[[area.polygons]] +vertices = [ + [2379, 3121], + [2429, 3121], +] + +[[area.polygons]] +vertices = [ + [2379, 3122], + [2428, 3122], +] + +[[area.polygons]] +vertices = [ + [2379, 3123], + [2427, 3123], + [2427, 3135], + [2379, 3135], +] diff --git a/.data/raw-cache/map/area/castle_wars_underground.toml b/.data/raw-cache/map/area/castle_wars_underground.toml new file mode 100644 index 000000000..ee636a70c --- /dev/null +++ b/.data/raw-cache/map/area/castle_wars_underground.toml @@ -0,0 +1,25 @@ +# Underneath Castle Wars: both team waiting rooms (portals at 2374,9486 and 2424,9529) and the +# tunnels beneath the arena. Music area for "Ready for Battle". Starts at z 9475 so it stays clear +# of the Smoke Devil Dungeon polygon (yanille_underground.toml). +[[area]] +name = "Castle Wars (underground)" +area_id = "area.castle_wars_underground" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 9475], + [2431, 9475], + [2431, 9535], + [2368, 9535], +] + +# Extension traced from the OSRS wiki music map (192 tiles). + +[[area.polygons]] +vertices = [ + [2368, 9472], + [2431, 9472], + [2431, 9474], + [2368, 9474], +] diff --git a/.data/raw-cache/map/area/catacombs_and_tombs.toml b/.data/raw-cache/map/area/catacombs_and_tombs.toml new file mode 100644 index 000000000..6b9d769ea --- /dev/null +++ b/.data/raw-cache/map/area/catacombs_and_tombs.toml @@ -0,0 +1,16 @@ +# Catacombs and Tombs music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Catacombs and Tombs. +# Bounds x 3712-3775, z 4352-4415; 4096 tiles as 1 rectangles. + +[[area]] +name = "Catacombs and Tombs" +area_id = "area.catacombs_and_tombs" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3712, 4352], + [3775, 4352], + [3775, 4415], + [3712, 4415], +] diff --git a/.data/raw-cache/map/area/catch_me_if_you_can.toml b/.data/raw-cache/map/area/catch_me_if_you_can.toml new file mode 100644 index 000000000..164d8fea2 --- /dev/null +++ b/.data/raw-cache/map/area/catch_me_if_you_can.toml @@ -0,0 +1,16 @@ +# Catch Me If You Can music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Catch Me If You Can. +# Bounds x 2624-2687, z 9600-9663; 4096 tiles as 1 rectangles. + +[[area]] +name = "Catch Me If You Can" +area_id = "area.catch_me_if_you_can" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2624, 9600], + [2687, 9600], + [2687, 9663], + [2624, 9663], +] diff --git a/.data/raw-cache/map/area/catherby.toml b/.data/raw-cache/map/area/catherby.toml new file mode 100644 index 000000000..7f8537570 --- /dev/null +++ b/.data/raw-cache/map/area/catherby.toml @@ -0,0 +1,446 @@ +# Regenerated as rectangles from the OSRS wiki music map: 311 tiles moved to +# neighbouring music areas and 1767 tiles were added. + +[[area]] +name = "Catherby" +area_id = "area.catherby" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2790, 3410], + [2870, 3410], + [2870, 3437], + [2790, 3437], +] + +[[area.polygons]] +vertices = [ + [2787, 3417], + [2789, 3417], + [2789, 3419], + [2787, 3419], +] + +[[area.polygons]] +vertices = [ + [2766, 3421], + [2766, 3439], +] + +[[area.polygons]] +vertices = [ + [2780, 3421], + [2780, 3439], +] + +[[area.polygons]] +vertices = [ + [2765, 3422], + [2765, 3439], +] + +[[area.polygons]] +vertices = [ + [2767, 3422], + [2767, 3439], +] + +[[area.polygons]] +vertices = [ + [2779, 3422], + [2779, 3439], +] + +[[area.polygons]] +vertices = [ + [2781, 3422], + [2781, 3439], +] + +[[area.polygons]] +vertices = [ + [2768, 3423], + [2768, 3439], +] + +[[area.polygons]] +vertices = [ + [2778, 3423], + [2778, 3439], +] + +[[area.polygons]] +vertices = [ + [2782, 3423], + [2782, 3439], +] + +[[area.polygons]] +vertices = [ + [2764, 3424], + [2764, 3439], +] + +[[area.polygons]] +vertices = [ + [2769, 3424], + [2769, 3439], +] + +[[area.polygons]] +vertices = [ + [2773, 3424], + [2773, 3439], +] + +[[area.polygons]] +vertices = [ + [2777, 3424], + [2777, 3439], +] + +[[area.polygons]] +vertices = [ + [2783, 3424], + [2784, 3424], + [2784, 3439], + [2783, 3439], +] + +[[area.polygons]] +vertices = [ + [2763, 3425], + [2763, 3439], +] + +[[area.polygons]] +vertices = [ + [2770, 3425], + [2772, 3425], + [2772, 3439], + [2770, 3439], +] + +[[area.polygons]] +vertices = [ + [2774, 3425], + [2776, 3425], + [2776, 3439], + [2774, 3439], +] + +[[area.polygons]] +vertices = [ + [2785, 3425], + [2786, 3425], + [2786, 3439], + [2785, 3439], +] + +[[area.polygons]] +vertices = [ + [2762, 3426], + [2762, 3439], +] + +[[area.polygons]] +vertices = [ + [2787, 3426], + [2788, 3426], + [2788, 3439], + [2787, 3439], +] + +[[area.polygons]] +vertices = [ + [2761, 3427], + [2761, 3439], +] + +[[area.polygons]] +vertices = [ + [2789, 3427], + [2789, 3439], +] + +[[area.polygons]] +vertices = [ + [2760, 3428], + [2760, 3439], +] + +[[area.polygons]] +vertices = [ + [2758, 3429], + [2759, 3429], + [2759, 3439], + [2758, 3439], +] + +[[area.polygons]] +vertices = [ + [2755, 3436], + [2757, 3436], + [2757, 3439], + [2755, 3439], +] + +[[area.polygons]] +vertices = [ + [2752, 3437], + [2754, 3437], + [2754, 3439], + [2752, 3439], +] + +[[area.polygons]] +vertices = [ + [2790, 3438], + [2858, 3438], +] + +[[area.polygons]] +vertices = [ + [2860, 3438], + [2870, 3438], + [2870, 3460], + [2860, 3460], +] + +[[area.polygons]] +vertices = [ + [2790, 3439], + [2857, 3439], +] + +[[area.polygons]] +vertices = [ + [2790, 3440], + [2856, 3440], +] + +[[area.polygons]] +vertices = [ + [2790, 3441], + [2855, 3441], +] + +[[area.polygons]] +vertices = [ + [2790, 3442], + [2852, 3442], +] + +[[area.polygons]] +vertices = [ + [2790, 3443], + [2851, 3443], +] + +[[area.polygons]] +vertices = [ + [2790, 3444], + [2850, 3444], +] + +[[area.polygons]] +vertices = [ + [2790, 3445], + [2848, 3445], +] + +[[area.polygons]] +vertices = [ + [2790, 3446], + [2846, 3446], +] + +[[area.polygons]] +vertices = [ + [2790, 3447], + [2840, 3447], +] + +[[area.polygons]] +vertices = [ + [2790, 3448], + [2839, 3448], +] + +[[area.polygons]] +vertices = [ + [2790, 3449], + [2838, 3449], + [2838, 3460], + [2790, 3460], +] + +[[area.polygons]] +vertices = [ + [2839, 3451], + [2839, 3462], +] + +[[area.polygons]] +vertices = [ + [2840, 3452], + [2840, 3461], +] + +[[area.polygons]] +vertices = [ + [2841, 3453], + [2841, 3460], +] + +[[area.polygons]] +vertices = [ + [2842, 3454], + [2842, 3459], +] + +[[area.polygons]] +vertices = [ + [2843, 3455], + [2843, 3458], +] + +[[area.polygons]] +vertices = [[2844, 3456]] + +[[area.polygons]] +vertices = [ + [2791, 3461], + [2838, 3461], + [2838, 3462], + [2791, 3462], +] + +[[area.polygons]] +vertices = [ + [2791, 3463], + [2837, 3463], +] + +[[area.polygons]] +vertices = [ + [2791, 3464], + [2835, 3464], +] + +[[area.polygons]] +vertices = [ + [2791, 3465], + [2833, 3465], + [2833, 3475], + [2791, 3475], +] + +[[area.polygons]] +vertices = [ + [2791, 3476], + [2832, 3476], +] + +[[area.polygons]] +vertices = [ + [2795, 3477], + [2832, 3477], +] + +[[area.polygons]] +vertices = [ + [2795, 3478], + [2831, 3478], + [2831, 3479], + [2795, 3479], +] + +[[area.polygons]] +vertices = [ + [2795, 3480], + [2830, 3480], + [2830, 3481], + [2795, 3481], +] + +[[area.polygons]] +vertices = [ + [2796, 3482], + [2830, 3482], +] + +[[area.polygons]] +vertices = [ + [2797, 3483], + [2830, 3483], +] + +[[area.polygons]] +vertices = [ + [2799, 3484], + [2830, 3484], +] + +[[area.polygons]] +vertices = [ + [2800, 3485], + [2830, 3485], + [2830, 3487], + [2800, 3487], +] + +[[area.polygons]] +vertices = [ + [2800, 3488], + [2829, 3488], +] + +[[area.polygons]] +vertices = [ + [2800, 3489], + [2828, 3489], +] + +[[area.polygons]] +vertices = [ + [2800, 3490], + [2827, 3490], +] + +[[area.polygons]] +vertices = [ + [2808, 3491], + [2826, 3491], +] + +[[area.polygons]] +vertices = [ + [2810, 3492], + [2825, 3492], +] + +[[area.polygons]] +vertices = [ + [2812, 3493], + [2824, 3493], +] + +[[area.polygons]] +vertices = [ + [2813, 3494], + [2823, 3494], +] + +[[area.polygons]] +vertices = [ + [2814, 3495], + [2821, 3495], +] + +[[area.polygons]] +vertices = [ + [2815, 3496], + [2819, 3496], +] diff --git a/.data/raw-cache/map/area/cave_background.toml b/.data/raw-cache/map/area/cave_background.toml new file mode 100644 index 000000000..ea226cde2 --- /dev/null +++ b/.data/raw-cache/map/area/cave_background.toml @@ -0,0 +1,32 @@ +# Cave Background music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Cave Background. +# Bounds x 2944-3071, z 9664-9855; 16896 tiles as 3 rectangles. + +[[area]] +name = "Cave Background" +area_id = "area.cave_background" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3000, 9664], + [3071, 9664], + [3071, 9727], + [3000, 9727], +] + +[[area.polygons]] +vertices = [ + [3008, 9728], + [3071, 9728], + [3071, 9855], + [3008, 9855], +] + +[[area.polygons]] +vertices = [ + [2944, 9792], + [3007, 9792], + [3007, 9855], + [2944, 9855], +] diff --git a/.data/raw-cache/map/area/cave_of_beasts.toml b/.data/raw-cache/map/area/cave_of_beasts.toml new file mode 100644 index 000000000..16ca11e25 --- /dev/null +++ b/.data/raw-cache/map/area/cave_of_beasts.toml @@ -0,0 +1,16 @@ +# Cave of Beasts music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Cave of Beasts. +# Bounds x 2752-2815, z 10048-10111; 4096 tiles as 1 rectangles. + +[[area]] +name = "Cave of Beasts" +area_id = "area.cave_of_beasts" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 10048], + [2815, 10048], + [2815, 10111], + [2752, 10111], +] diff --git a/.data/raw-cache/map/area/cave_of_the_goblins.toml b/.data/raw-cache/map/area/cave_of_the_goblins.toml new file mode 100644 index 000000000..d98ae8e3b --- /dev/null +++ b/.data/raw-cache/map/area/cave_of_the_goblins.toml @@ -0,0 +1,24 @@ +# Cave of the Goblins music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Cave of the Goblins. +# Bounds x 2496-3263, z 5504-9599; 12288 tiles as 2 rectangles. + +[[area]] +name = "Cave of the Goblins" +area_id = "area.cave_of_the_goblins" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2496, 5504], + [2527, 5504], + [2527, 5631], + [2496, 5631], +] + +[[area.polygons]] +vertices = [ + [3136, 9536], + [3263, 9536], + [3263, 9599], + [3136, 9599], +] diff --git a/.data/raw-cache/map/area/cavern.toml b/.data/raw-cache/map/area/cavern.toml new file mode 100644 index 000000000..e25c6e19f --- /dev/null +++ b/.data/raw-cache/map/area/cavern.toml @@ -0,0 +1,16 @@ +# Cavern music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Cavern. +# Bounds x 3008-3071, z 10304-10367; 4096 tiles as 1 rectangles. + +[[area]] +name = "Cavern" +area_id = "area.cavern" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 10304], + [3071, 10304], + [3071, 10367], + [3008, 10367], +] diff --git a/.data/raw-cache/map/area/cellar_song.toml b/.data/raw-cache/map/area/cellar_song.toml new file mode 100644 index 000000000..e2e114ad6 --- /dev/null +++ b/.data/raw-cache/map/area/cellar_song.toml @@ -0,0 +1,16 @@ +# Cellar Song music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Cellar Song. +# Bounds x 3136-3199, z 9792-9855; 4096 tiles as 1 rectangles. + +[[area]] +name = "Cellar Song" +area_id = "area.cellar_song" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 9792], + [3199, 9792], + [3199, 9855], + [3136, 9855], +] diff --git a/.data/raw-cache/map/area/chain_of_command.toml b/.data/raw-cache/map/area/chain_of_command.toml new file mode 100644 index 000000000..db6cde175 --- /dev/null +++ b/.data/raw-cache/map/area/chain_of_command.toml @@ -0,0 +1,16 @@ +# Chain of Command music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Chain of Command. +# Bounds x 2624-2687, z 9728-9919; 12288 tiles as 1 rectangles. + +[[area]] +name = "Chain of Command" +area_id = "area.chain_of_command" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2624, 9728], + [2687, 9728], + [2687, 9919], + [2624, 9919], +] diff --git a/.data/raw-cache/map/area/chamber.toml b/.data/raw-cache/map/area/chamber.toml new file mode 100644 index 000000000..fec9e5745 --- /dev/null +++ b/.data/raw-cache/map/area/chamber.toml @@ -0,0 +1,24 @@ +# Chamber music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Chamber. +# Bounds x 2688-2815, z 4416-4543; 8192 tiles as 2 rectangles. + +[[area]] +name = "Chamber" +area_id = "area.chamber" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 4416], + [2751, 4416], + [2751, 4479], + [2688, 4479], +] + +[[area.polygons]] +vertices = [ + [2752, 4480], + [2815, 4480], + [2815, 4543], + [2752, 4543], +] diff --git a/.data/raw-cache/map/area/chickened_out.toml b/.data/raw-cache/map/area/chickened_out.toml new file mode 100644 index 000000000..686ceaff7 --- /dev/null +++ b/.data/raw-cache/map/area/chickened_out.toml @@ -0,0 +1,16 @@ +# Chickened Out music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Chickened Out. +# Bounds x 2432-2495, z 4352-4415; 4096 tiles as 1 rectangles. + +[[area]] +name = "Chickened Out" +area_id = "area.chickened_out" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2432, 4352], + [2495, 4352], + [2495, 4415], + [2432, 4415], +] diff --git a/.data/raw-cache/map/area/city_of_the_dead.toml b/.data/raw-cache/map/area/city_of_the_dead.toml new file mode 100644 index 000000000..6b1d533a9 --- /dev/null +++ b/.data/raw-cache/map/area/city_of_the_dead.toml @@ -0,0 +1,16 @@ +# City of the Dead music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: City of the Dead. +# Bounds x 3264-3322, z 2748-2809; 3658 tiles as 1 rectangles. + +[[area]] +name = "City of the Dead" +area_id = "area.city_of_the_dead" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 2748], + [3322, 2748], + [3322, 2809], + [3264, 2809], +] diff --git a/.data/raw-cache/map/area/civitas_illa_fortis.toml b/.data/raw-cache/map/area/civitas_illa_fortis.toml new file mode 100644 index 000000000..36e711c22 --- /dev/null +++ b/.data/raw-cache/map/area/civitas_illa_fortis.toml @@ -0,0 +1,1052 @@ +# Civitas illa Fortis music music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The City of Sun; Prospering Fortune. +# Bounds x 1613-1860, z 3072-3190; 21589 tiles as 167 rectangles. + +[[area]] +name = "Civitas illa Fortis music" +area_id = "area.civitas_illa_fortis" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1812, 3072], + [1838, 3072], + [1838, 3088], + [1812, 3088], +] + +[[area.polygons]] +vertices = [ + [1811, 3073], + [1811, 3092], +] + +[[area.polygons]] +vertices = [ + [1839, 3073], + [1839, 3094], +] + +[[area.polygons]] +vertices = [ + [1659, 3074], + [1714, 3074], + [1714, 3182], + [1659, 3182], +] + +[[area.polygons]] +vertices = [ + [1787, 3074], + [1810, 3074], + [1810, 3093], + [1787, 3093], +] + +[[area.polygons]] +vertices = [ + [1840, 3074], + [1856, 3074], + [1856, 3095], + [1840, 3095], +] + +[[area.polygons]] +vertices = [ + [1658, 3075], + [1658, 3189], +] + +[[area.polygons]] +vertices = [ + [1715, 3075], + [1715, 3182], +] + +[[area.polygons]] +vertices = [ + [1786, 3075], + [1786, 3162], +] + +[[area.polygons]] +vertices = [ + [1857, 3075], + [1857, 3133], +] + +[[area.polygons]] +vertices = [ + [1657, 3076], + [1657, 3188], +] + +[[area.polygons]] +vertices = [ + [1716, 3076], + [1716, 3182], +] + +[[area.polygons]] +vertices = [ + [1785, 3076], + [1785, 3161], +] + +[[area.polygons]] +vertices = [ + [1858, 3076], + [1858, 3133], +] + +[[area.polygons]] +vertices = [ + [1656, 3077], + [1656, 3187], +] + +[[area.polygons]] +vertices = [ + [1717, 3077], + [1717, 3182], +] + +[[area.polygons]] +vertices = [ + [1784, 3077], + [1784, 3161], +] + +[[area.polygons]] +vertices = [ + [1859, 3077], + [1859, 3132], +] + +[[area.polygons]] +vertices = [ + [1655, 3078], + [1655, 3186], +] + +[[area.polygons]] +vertices = [ + [1718, 3078], + [1718, 3182], +] + +[[area.polygons]] +vertices = [ + [1783, 3078], + [1783, 3162], +] + +[[area.polygons]] +vertices = [ + [1654, 3079], + [1654, 3185], +] + +[[area.polygons]] +vertices = [ + [1719, 3079], + [1719, 3182], +] + +[[area.polygons]] +vertices = [ + [1782, 3079], + [1782, 3163], +] + +[[area.polygons]] +vertices = [ + [1653, 3080], + [1653, 3184], +] + +[[area.polygons]] +vertices = [ + [1720, 3080], + [1720, 3182], +] + +[[area.polygons]] +vertices = [ + [1781, 3080], + [1781, 3169], +] + +[[area.polygons]] +vertices = [ + [1652, 3081], + [1652, 3183], +] + +[[area.polygons]] +vertices = [ + [1721, 3081], + [1721, 3182], +] + +[[area.polygons]] +vertices = [ + [1780, 3081], + [1780, 3170], +] + +[[area.polygons]] +vertices = [ + [1629, 3082], + [1651, 3082], + [1651, 3182], + [1629, 3182], +] + +[[area.polygons]] +vertices = [ + [1722, 3082], + [1779, 3082], + [1779, 3122], + [1722, 3122], +] + +[[area.polygons]] +vertices = [ + [1860, 3082], + [1860, 3128], +] + +[[area.polygons]] +vertices = [ + [1628, 3083], + [1628, 3181], +] + +[[area.polygons]] +vertices = [ + [1627, 3084], + [1627, 3180], +] + +[[area.polygons]] +vertices = [ + [1626, 3085], + [1626, 3179], +] + +[[area.polygons]] +vertices = [ + [1625, 3086], + [1625, 3178], +] + +[[area.polygons]] +vertices = [ + [1624, 3087], + [1624, 3177], +] + +[[area.polygons]] +vertices = [ + [1623, 3088], + [1623, 3176], +] + +[[area.polygons]] +vertices = [ + [1622, 3089], + [1622, 3175], +] + +[[area.polygons]] +vertices = [ + [1812, 3089], + [1818, 3089], +] + +[[area.polygons]] +vertices = [ + [1823, 3089], + [1826, 3089], + [1826, 3090], + [1823, 3090], +] + +[[area.polygons]] +vertices = [ + [1831, 3089], + [1838, 3089], +] + +[[area.polygons]] +vertices = [ + [1621, 3090], + [1621, 3174], +] + +[[area.polygons]] +vertices = [ + [1812, 3090], + [1814, 3090], +] + +[[area.polygons]] +vertices = [ + [1834, 3090], + [1838, 3090], +] + +[[area.polygons]] +vertices = [ + [1812, 3091], + [1813, 3091], +] + +[[area.polygons]] +vertices = [ + [1836, 3091], + [1838, 3091], +] + +[[area.polygons]] +vertices = [ + [1837, 3092], + [1838, 3092], +] + +[[area.polygons]] +vertices = [[1838, 3093]] + +[[area.polygons]] +vertices = [ + [1787, 3094], + [1809, 3094], + [1809, 3095], + [1787, 3095], +] + +[[area.polygons]] +vertices = [ + [1787, 3096], + [1808, 3096], + [1808, 3097], + [1787, 3097], +] + +[[area.polygons]] +vertices = [ + [1841, 3096], + [1856, 3096], + [1856, 3097], + [1841, 3097], +] + +[[area.polygons]] +vertices = [ + [1787, 3098], + [1807, 3098], + [1807, 3100], + [1787, 3100], +] + +[[area.polygons]] +vertices = [ + [1842, 3098], + [1856, 3098], + [1856, 3134], + [1842, 3134], +] + +[[area.polygons]] +vertices = [ + [1787, 3101], + [1806, 3101], + [1806, 3153], + [1787, 3153], +] + +[[area.polygons]] +vertices = [ + [1840, 3103], + [1841, 3103], + [1841, 3110], + [1840, 3110], +] + +[[area.polygons]] +vertices = [ + [1807, 3105], + [1808, 3105], + [1808, 3108], + [1807, 3108], +] + +[[area.polygons]] +vertices = [ + [1807, 3113], + [1807, 3155], +] + +[[area.polygons]] +vertices = [ + [1808, 3116], + [1808, 3155], +] + +[[area.polygons]] +vertices = [ + [1841, 3116], + [1841, 3146], +] + +[[area.polygons]] +vertices = [ + [1809, 3118], + [1809, 3154], +] + +[[area.polygons]] +vertices = [ + [1840, 3118], + [1840, 3145], +] + +[[area.polygons]] +vertices = [ + [1839, 3119], + [1839, 3145], +] + +[[area.polygons]] +vertices = [ + [1810, 3120], + [1810, 3154], +] + +[[area.polygons]] +vertices = [ + [1838, 3120], + [1838, 3146], +] + +[[area.polygons]] +vertices = [ + [1811, 3121], + [1811, 3155], +] + +[[area.polygons]] +vertices = [ + [1837, 3121], + [1837, 3146], +] + +[[area.polygons]] +vertices = [ + [1620, 3122], + [1620, 3173], +] + +[[area.polygons]] +vertices = [ + [1812, 3122], + [1813, 3122], + [1813, 3155], + [1812, 3155], +] + +[[area.polygons]] +vertices = [ + [1836, 3122], + [1836, 3146], +] + +[[area.polygons]] +vertices = [ + [1619, 3123], + [1619, 3172], +] + +[[area.polygons]] +vertices = [ + [1722, 3123], + [1747, 3123], +] + +[[area.polygons]] +vertices = [ + [1771, 3123], + [1779, 3123], +] + +[[area.polygons]] +vertices = [ + [1814, 3123], + [1815, 3123], + [1815, 3154], + [1814, 3154], +] + +[[area.polygons]] +vertices = [ + [1823, 3123], + [1826, 3123], + [1826, 3150], + [1823, 3150], +] + +[[area.polygons]] +vertices = [ + [1834, 3123], + [1835, 3123], + [1835, 3146], + [1834, 3146], +] + +[[area.polygons]] +vertices = [ + [1618, 3124], + [1618, 3171], +] + +[[area.polygons]] +vertices = [ + [1722, 3124], + [1746, 3124], +] + +[[area.polygons]] +vertices = [ + [1772, 3124], + [1779, 3124], +] + +[[area.polygons]] +vertices = [ + [1816, 3124], + [1818, 3124], + [1818, 3152], + [1816, 3152], +] + +[[area.polygons]] +vertices = [ + [1830, 3124], + [1833, 3124], + [1833, 3148], + [1830, 3148], +] + +[[area.polygons]] +vertices = [ + [1617, 3125], + [1617, 3170], +] + +[[area.polygons]] +vertices = [ + [1722, 3125], + [1745, 3125], +] + +[[area.polygons]] +vertices = [ + [1773, 3125], + [1779, 3125], +] + +[[area.polygons]] +vertices = [ + [1819, 3125], + [1822, 3125], + [1822, 3151], + [1819, 3151], +] + +[[area.polygons]] +vertices = [ + [1827, 3125], + [1829, 3125], + [1829, 3151], + [1827, 3151], +] + +[[area.polygons]] +vertices = [ + [1616, 3126], + [1616, 3169], +] + +[[area.polygons]] +vertices = [ + [1722, 3126], + [1744, 3126], +] + +[[area.polygons]] +vertices = [ + [1774, 3126], + [1779, 3126], +] + +[[area.polygons]] +vertices = [ + [1615, 3127], + [1615, 3168], +] + +[[area.polygons]] +vertices = [ + [1722, 3127], + [1743, 3127], + [1743, 3182], + [1722, 3182], +] + +[[area.polygons]] +vertices = [ + [1775, 3127], + [1779, 3127], +] + +[[area.polygons]] +vertices = [ + [1614, 3128], + [1614, 3167], +] + +[[area.polygons]] +vertices = [ + [1776, 3128], + [1779, 3128], + [1779, 3185], + [1776, 3185], +] + +[[area.polygons]] +vertices = [ + [1613, 3129], + [1613, 3166], +] + +[[area.polygons]] +vertices = [ + [1747, 3132], + [1748, 3132], + [1748, 3141], + [1747, 3141], +] + +[[area.polygons]] +vertices = [ + [1749, 3133], + [1749, 3140], +] + +[[area.polygons]] +vertices = [ + [1842, 3135], + [1855, 3135], + [1855, 3138], + [1842, 3138], +] + +[[area.polygons]] +vertices = [ + [1744, 3136], + [1746, 3136], +] + +[[area.polygons]] +vertices = [ + [1842, 3139], + [1854, 3139], + [1854, 3140], + [1842, 3140], +] + +[[area.polygons]] +vertices = [ + [1842, 3141], + [1853, 3141], +] + +[[area.polygons]] +vertices = [ + [1842, 3142], + [1852, 3142], +] + +[[area.polygons]] +vertices = [ + [1842, 3143], + [1850, 3143], + [1850, 3145], + [1842, 3145], +] + +[[area.polygons]] +vertices = [ + [1842, 3146], + [1849, 3146], +] + +[[area.polygons]] +vertices = [[1834, 3147]] + +[[area.polygons]] +vertices = [ + [1845, 3147], + [1847, 3147], +] + +[[area.polygons]] +vertices = [ + [1775, 3149], + [1775, 3189], +] + +[[area.polygons]] +vertices = [ + [1830, 3149], + [1832, 3149], +] + +[[area.polygons]] +vertices = [ + [1744, 3150], + [1744, 3190], +] + +[[area.polygons]] +vertices = [ + [1774, 3150], + [1774, 3190], +] + +[[area.polygons]] +vertices = [ + [1830, 3150], + [1831, 3150], +] + +[[area.polygons]] +vertices = [ + [1745, 3151], + [1745, 3190], +] + +[[area.polygons]] +vertices = [ + [1773, 3151], + [1773, 3190], +] + +[[area.polygons]] +vertices = [[1823, 3151]] + +[[area.polygons]] +vertices = [[1830, 3151]] + +[[area.polygons]] +vertices = [ + [1746, 3152], + [1746, 3190], +] + +[[area.polygons]] +vertices = [ + [1772, 3152], + [1772, 3190], +] + +[[area.polygons]] +vertices = [ + [1819, 3152], + [1820, 3152], +] + +[[area.polygons]] +vertices = [ + [1747, 3153], + [1747, 3190], +] + +[[area.polygons]] +vertices = [ + [1771, 3153], + [1771, 3190], +] + +[[area.polygons]] +vertices = [ + [1816, 3153], + [1817, 3153], +] + +[[area.polygons]] +vertices = [ + [1748, 3154], + [1748, 3190], +] + +[[area.polygons]] +vertices = [ + [1770, 3154], + [1770, 3190], +] + +[[area.polygons]] +vertices = [ + [1787, 3154], + [1801, 3154], +] + +[[area.polygons]] +vertices = [[1806, 3154]] + +[[area.polygons]] +vertices = [[1816, 3154]] + +[[area.polygons]] +vertices = [ + [1749, 3155], + [1749, 3190], +] + +[[area.polygons]] +vertices = [ + [1769, 3155], + [1769, 3190], +] + +[[area.polygons]] +vertices = [ + [1787, 3155], + [1791, 3155], + [1791, 3157], + [1787, 3157], +] + +[[area.polygons]] +vertices = [ + [1795, 3155], + [1799, 3155], +] + +[[area.polygons]] +vertices = [[1814, 3155]] + +[[area.polygons]] +vertices = [ + [1750, 3156], + [1754, 3156], + [1754, 3162], + [1750, 3162], +] + +[[area.polygons]] +vertices = [ + [1765, 3156], + [1768, 3156], + [1768, 3162], + [1765, 3162], +] + +[[area.polygons]] +vertices = [ + [1787, 3158], + [1790, 3158], + [1790, 3159], + [1787, 3159], +] + +[[area.polygons]] +vertices = [ + [1787, 3160], + [1789, 3160], +] + +[[area.polygons]] +vertices = [ + [1787, 3161], + [1788, 3161], +] + +[[area.polygons]] +vertices = [[1787, 3162]] + +[[area.polygons]] +vertices = [ + [1750, 3163], + [1753, 3163], + [1753, 3190], + [1750, 3190], +] + +[[area.polygons]] +vertices = [ + [1766, 3163], + [1768, 3163], + [1768, 3190], + [1766, 3190], +] + +[[area.polygons]] +vertices = [ + [1783, 3172], + [1786, 3172], + [1786, 3183], + [1783, 3183], +] + +[[area.polygons]] +vertices = [ + [1780, 3173], + [1780, 3184], +] + +[[area.polygons]] +vertices = [ + [1782, 3173], + [1782, 3183], +] + +[[area.polygons]] +vertices = [ + [1787, 3173], + [1787, 3183], +] + +[[area.polygons]] +vertices = [ + [1781, 3174], + [1781, 3183], +] + +[[area.polygons]] +vertices = [ + [1788, 3174], + [1788, 3183], +] + +[[area.polygons]] +vertices = [ + [1789, 3175], + [1789, 3183], +] + +[[area.polygons]] +vertices = [ + [1790, 3176], + [1790, 3180], +] + +[[area.polygons]] +vertices = [ + [1791, 3177], + [1791, 3178], +] + +[[area.polygons]] +vertices = [ + [1659, 3183], + [1708, 3183], +] + +[[area.polygons]] +vertices = [ + [1737, 3183], + [1743, 3183], +] + +[[area.polygons]] +vertices = [ + [1659, 3184], + [1707, 3184], +] + +[[area.polygons]] +vertices = [ + [1738, 3184], + [1743, 3184], +] + +[[area.polygons]] +vertices = [ + [1659, 3185], + [1706, 3185], +] + +[[area.polygons]] +vertices = [ + [1739, 3185], + [1743, 3185], +] + +[[area.polygons]] +vertices = [ + [1659, 3186], + [1705, 3186], +] + +[[area.polygons]] +vertices = [ + [1740, 3186], + [1743, 3186], +] + +[[area.polygons]] +vertices = [ + [1776, 3186], + [1778, 3186], +] + +[[area.polygons]] +vertices = [ + [1659, 3187], + [1704, 3187], +] + +[[area.polygons]] +vertices = [ + [1741, 3187], + [1743, 3187], +] + +[[area.polygons]] +vertices = [ + [1776, 3187], + [1777, 3187], +] + +[[area.polygons]] +vertices = [ + [1659, 3188], + [1703, 3188], +] + +[[area.polygons]] +vertices = [ + [1742, 3188], + [1743, 3188], +] + +[[area.polygons]] +vertices = [[1776, 3188]] + +[[area.polygons]] +vertices = [ + [1659, 3189], + [1702, 3189], +] + +[[area.polygons]] +vertices = [[1743, 3189]] + +[[area.polygons]] +vertices = [ + [1659, 3190], + [1701, 3190], +] diff --git a/.data/raw-cache/map/area/clan_wars.toml b/.data/raw-cache/map/area/clan_wars.toml new file mode 100644 index 000000000..12584d2ba --- /dev/null +++ b/.data/raw-cache/map/area/clan_wars.toml @@ -0,0 +1,24 @@ +# Clan Wars music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Clan Wars. +# Bounds x 3136-3583, z 4672-5119; 167936 tiles as 2 rectangles. + +[[area]] +name = "Clan Wars" +area_id = "area.clan_wars" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 4672], + [3583, 4672], + [3583, 5119], + [3264, 5119], +] + +[[area.polygons]] +vertices = [ + [3136, 4928], + [3263, 4928], + [3263, 5119], + [3136, 5119], +] diff --git a/.data/raw-cache/map/area/clanliness.toml b/.data/raw-cache/map/area/clanliness.toml new file mode 100644 index 000000000..491c2e3ff --- /dev/null +++ b/.data/raw-cache/map/area/clanliness.toml @@ -0,0 +1,16 @@ +# Clanliness music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Clanliness. +# Bounds x 1728-1791, z 5440-5503; 4096 tiles as 1 rectangles. + +[[area]] +name = "Clanliness" +area_id = "area.clanliness" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1728, 5440], + [1791, 5440], + [1791, 5503], + [1728, 5503], +] diff --git a/.data/raw-cache/map/area/claustrophobia.toml b/.data/raw-cache/map/area/claustrophobia.toml new file mode 100644 index 000000000..8cf0a7309 --- /dev/null +++ b/.data/raw-cache/map/area/claustrophobia.toml @@ -0,0 +1,16 @@ +# Claustrophobia music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Claustrophobia. +# Bounds x 2304-2431, z 4928-4991; 8192 tiles as 1 rectangles. + +[[area]] +name = "Claustrophobia" +area_id = "area.claustrophobia" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2304, 4928], + [2431, 4928], + [2431, 4991], + [2304, 4991], +] diff --git a/.data/raw-cache/map/area/coil.toml b/.data/raw-cache/map/area/coil.toml new file mode 100644 index 000000000..2ed408496 --- /dev/null +++ b/.data/raw-cache/map/area/coil.toml @@ -0,0 +1,16 @@ +# Coil music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Coil. +# Bounds x 2256-2279, z 3066-3079; 336 tiles as 1 rectangles. + +[[area]] +name = "Coil" +area_id = "area.coil" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2256, 3066], + [2279, 3066], + [2279, 3079], + [2256, 3079], +] diff --git a/.data/raw-cache/map/area/colossus_of_the_deep.toml b/.data/raw-cache/map/area/colossus_of_the_deep.toml new file mode 100644 index 000000000..596c0c653 --- /dev/null +++ b/.data/raw-cache/map/area/colossus_of_the_deep.toml @@ -0,0 +1,16 @@ +# Colossus of the Deep music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Colossus of the Deep. +# Bounds x 2048-2111, z 6336-6399; 4096 tiles as 1 rectangles. + +[[area]] +name = "Colossus of the Deep" +area_id = "area.colossus_of_the_deep" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2048, 6336], + [2111, 6336], + [2111, 6399], + [2048, 6399], +] diff --git a/.data/raw-cache/map/area/competition.toml b/.data/raw-cache/map/area/competition.toml new file mode 100644 index 000000000..fd49dd0bf --- /dev/null +++ b/.data/raw-cache/map/area/competition.toml @@ -0,0 +1,16 @@ +# Competition music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Competition. +# Bounds x 2176-2239, z 4928-4991; 4096 tiles as 1 rectangles. + +[[area]] +name = "Competition" +area_id = "area.competition" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2176, 4928], + [2239, 4928], + [2239, 4991], + [2176, 4991], +] diff --git a/.data/raw-cache/map/area/complication.toml b/.data/raw-cache/map/area/complication.toml new file mode 100644 index 000000000..c19beec34 --- /dev/null +++ b/.data/raw-cache/map/area/complication.toml @@ -0,0 +1,16 @@ +# Complication music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Complication. +# Bounds x 2240-2303, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Complication" +area_id = "area.complication" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2240, 4800], + [2303, 4800], + [2303, 4863], + [2240, 4863], +] diff --git a/.data/raw-cache/map/area/conspiracy.toml b/.data/raw-cache/map/area/conspiracy.toml new file mode 100644 index 000000000..f9e87279d --- /dev/null +++ b/.data/raw-cache/map/area/conspiracy.toml @@ -0,0 +1,16 @@ +# Conspiracy music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Conspiracy. +# Bounds x 3584-3599, z 9664-9727; 1024 tiles as 1 rectangles. + +[[area]] +name = "Conspiracy" +area_id = "area.conspiracy" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3584, 9664], + [3599, 9664], + [3599, 9727], + [3584, 9727], +] diff --git a/.data/raw-cache/map/area/corporal_punishment.toml b/.data/raw-cache/map/area/corporal_punishment.toml new file mode 100644 index 000000000..ff76d6180 --- /dev/null +++ b/.data/raw-cache/map/area/corporal_punishment.toml @@ -0,0 +1,16 @@ +# Corporal Punishment music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Corporal Punishment. +# Bounds x 3136-3199, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Corporal Punishment" +area_id = "area.corporal_punishment" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 4800], + [3199, 4800], + [3199, 4863], + [3136, 4863], +] diff --git a/.data/raw-cache/map/area/corridors_of_power.toml b/.data/raw-cache/map/area/corridors_of_power.toml new file mode 100644 index 000000000..2b7f08951 --- /dev/null +++ b/.data/raw-cache/map/area/corridors_of_power.toml @@ -0,0 +1,24 @@ +# Corridors of Power music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Corridors of Power. +# Bounds x 2560-2623, z 10240-10303; 3584 tiles as 2 rectangles. + +[[area]] +name = "Corridors of Power" +area_id = "area.corridors_of_power" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 10240], + [2623, 10240], + [2623, 10271], + [2560, 10271], +] + +[[area.polygons]] +vertices = [ + [2560, 10272], + [2607, 10272], + [2607, 10303], + [2560, 10303], +] diff --git a/.data/raw-cache/map/area/country_jig.toml b/.data/raw-cache/map/area/country_jig.toml new file mode 100644 index 000000000..80ba191c4 --- /dev/null +++ b/.data/raw-cache/map/area/country_jig.toml @@ -0,0 +1,1880 @@ +# Country Jig music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Country Jig. +# Bounds x 1669-1919, z 3402-9983; 24264 tiles as 296 rectangles. + +[[area]] +name = "Country Jig" +area_id = "area.country_jig" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1784, 3402], + [1789, 3402], + [1789, 3411], + [1784, 3411], +] + +[[area.polygons]] +vertices = [ + [1782, 3403], + [1783, 3403], + [1783, 3412], + [1782, 3412], +] + +[[area.polygons]] +vertices = [ + [1790, 3403], + [1790, 3410], +] + +[[area.polygons]] +vertices = [ + [1781, 3404], + [1781, 3413], +] + +[[area.polygons]] +vertices = [ + [1791, 3404], + [1793, 3404], + [1793, 3410], + [1791, 3410], +] + +[[area.polygons]] +vertices = [ + [1779, 3405], + [1780, 3405], + [1780, 3415], + [1779, 3415], +] + +[[area.polygons]] +vertices = [ + [1794, 3405], + [1794, 3412], +] + +[[area.polygons]] +vertices = [ + [1749, 3406], + [1753, 3406], + [1753, 3428], + [1749, 3428], +] + +[[area.polygons]] +vertices = [ + [1766, 3406], + [1770, 3406], + [1770, 3426], + [1766, 3426], +] + +[[area.polygons]] +vertices = [ + [1795, 3406], + [1795, 3413], +] + +[[area.polygons]] +vertices = [ + [1747, 3407], + [1748, 3407], + [1748, 3414], + [1747, 3414], +] + +[[area.polygons]] +vertices = [ + [1754, 3407], + [1754, 3438], +] + +[[area.polygons]] +vertices = [ + [1764, 3407], + [1765, 3407], + [1765, 3429], + [1764, 3429], +] + +[[area.polygons]] +vertices = [ + [1771, 3407], + [1771, 3426], +] + +[[area.polygons]] +vertices = [ + [1778, 3407], + [1778, 3442], +] + +[[area.polygons]] +vertices = [ + [1796, 3407], + [1796, 3414], +] + +[[area.polygons]] +vertices = [ + [1755, 3408], + [1755, 3440], +] + +[[area.polygons]] +vertices = [ + [1763, 3408], + [1763, 3447], +] + +[[area.polygons]] +vertices = [ + [1772, 3408], + [1772, 3427], +] + +[[area.polygons]] +vertices = [ + [1777, 3408], + [1777, 3440], +] + +[[area.polygons]] +vertices = [ + [1797, 3408], + [1797, 3412], +] + +[[area.polygons]] +vertices = [ + [1746, 3409], + [1746, 3412], +] + +[[area.polygons]] +vertices = [ + [1756, 3409], + [1757, 3409], + [1757, 3441], + [1756, 3441], +] + +[[area.polygons]] +vertices = [ + [1762, 3409], + [1762, 3447], +] + +[[area.polygons]] +vertices = [ + [1773, 3409], + [1774, 3409], + [1774, 3428], + [1773, 3428], +] + +[[area.polygons]] +vertices = [ + [1776, 3409], + [1776, 3435], +] + +[[area.polygons]] +vertices = [ + [1758, 3410], + [1761, 3410], + [1761, 3443], + [1758, 3443], +] + +[[area.polygons]] +vertices = [ + [1775, 3410], + [1775, 3431], +] + +[[area.polygons]] +vertices = [[1793, 3411]] + +[[area.polygons]] +vertices = [ + [1784, 3412], + [1785, 3412], +] + +[[area.polygons]] +vertices = [ + [1787, 3412], + [1788, 3412], +] + +[[area.polygons]] +vertices = [[1782, 3413]] + +[[area.polygons]] +vertices = [[1748, 3415]] + +[[area.polygons]] +vertices = [ + [1779, 3416], + [1779, 3443], +] + +[[area.polygons]] +vertices = [ + [1780, 3419], + [1780, 3442], +] + +[[area.polygons]] +vertices = [ + [1748, 3422], + [1748, 3426], +] + +[[area.polygons]] +vertices = [ + [1781, 3422], + [1781, 3440], +] + +[[area.polygons]] +vertices = [ + [1766, 3427], + [1768, 3427], +] + +[[area.polygons]] +vertices = [ + [1782, 3427], + [1782, 3435], +] + +[[area.polygons]] +vertices = [[1766, 3428]] + +[[area.polygons]] +vertices = [ + [1750, 3429], + [1753, 3429], +] + +[[area.polygons]] +vertices = [[1774, 3429]] + +[[area.polygons]] +vertices = [ + [1751, 3430], + [1753, 3430], +] + +[[area.polygons]] +vertices = [ + [1764, 3430], + [1764, 3431], +] + +[[area.polygons]] +vertices = [ + [1752, 3431], + [1753, 3431], + [1753, 3434], + [1752, 3434], +] + +[[area.polygons]] +vertices = [ + [1753, 3435], + [1753, 3436], +] + +[[area.polygons]] +vertices = [ + [1806, 3436], + [1807, 3436], + [1807, 3572], + [1806, 3572], +] + +[[area.polygons]] +vertices = [ + [1804, 3437], + [1805, 3437], + [1805, 3572], + [1804, 3572], +] + +[[area.polygons]] +vertices = [ + [1808, 3437], + [1808, 3588], +] + +[[area.polygons]] +vertices = [ + [1814, 3437], + [1815, 3437], + [1815, 3593], + [1814, 3593], +] + +[[area.polygons]] +vertices = [ + [1764, 3438], + [1764, 3448], +] + +[[area.polygons]] +vertices = [ + [1803, 3438], + [1803, 3570], +] + +[[area.polygons]] +vertices = [ + [1809, 3438], + [1810, 3438], + [1810, 3589], + [1809, 3589], +] + +[[area.polygons]] +vertices = [ + [1813, 3438], + [1813, 3593], +] + +[[area.polygons]] +vertices = [ + [1816, 3438], + [1816, 3440], +] + +[[area.polygons]] +vertices = [ + [1801, 3439], + [1802, 3439], + [1802, 3570], + [1801, 3570], +] + +[[area.polygons]] +vertices = [ + [1811, 3439], + [1812, 3439], + [1812, 3591], + [1811, 3591], +] + +[[area.polygons]] +vertices = [ + [1799, 3440], + [1800, 3440], + [1800, 3447], + [1799, 3447], +] + +[[area.polygons]] +vertices = [ + [1765, 3441], + [1765, 3448], +] + +[[area.polygons]] +vertices = [ + [1797, 3441], + [1798, 3441], + [1798, 3446], + [1797, 3446], +] + +[[area.polygons]] +vertices = [[1757, 3442]] + +[[area.polygons]] +vertices = [ + [1766, 3443], + [1766, 3449], +] + +[[area.polygons]] +vertices = [ + [1796, 3443], + [1796, 3444], +] + +[[area.polygons]] +vertices = [ + [1827, 3443], + [1829, 3443], + [1829, 3595], + [1827, 3595], +] + +[[area.polygons]] +vertices = [ + [1759, 3444], + [1761, 3444], +] + +[[area.polygons]] +vertices = [ + [1825, 3444], + [1826, 3444], + [1826, 3591], + [1825, 3591], +] + +[[area.polygons]] +vertices = [ + [1830, 3444], + [1830, 3617], +] + +[[area.polygons]] +vertices = [ + [1760, 3445], + [1761, 3445], +] + +[[area.polygons]] +vertices = [ + [1767, 3445], + [1767, 3449], +] + +[[area.polygons]] +vertices = [ + [1816, 3445], + [1816, 3591], +] + +[[area.polygons]] +vertices = [ + [1831, 3445], + [1831, 3630], +] + +[[area.polygons]] +vertices = [[1761, 3446]] + +[[area.polygons]] +vertices = [ + [1768, 3446], + [1770, 3446], + [1770, 3448], + [1768, 3448], +] + +[[area.polygons]] +vertices = [ + [1824, 3446], + [1824, 3590], +] + +[[area.polygons]] +vertices = [ + [1771, 3447], + [1773, 3447], +] + +[[area.polygons]] +vertices = [ + [1817, 3447], + [1818, 3447], + [1818, 3590], + [1817, 3590], +] + +[[area.polygons]] +vertices = [ + [1832, 3447], + [1834, 3447], + [1834, 3632], + [1832, 3632], +] + +[[area.polygons]] +vertices = [ + [1771, 3448], + [1772, 3448], +] + +[[area.polygons]] +vertices = [ + [1800, 3448], + [1800, 3449], +] + +[[area.polygons]] +vertices = [ + [1819, 3448], + [1823, 3448], + [1823, 3589], + [1819, 3589], +] + +[[area.polygons]] +vertices = [ + [1835, 3448], + [1838, 3448], + [1838, 3604], + [1835, 3604], +] + +[[area.polygons]] +vertices = [ + [1768, 3449], + [1769, 3449], +] + +[[area.polygons]] +vertices = [ + [1839, 3449], + [1839, 3603], +] + +[[area.polygons]] +vertices = [ + [1840, 3450], + [1841, 3450], + [1841, 3592], + [1840, 3592], +] + +[[area.polygons]] +vertices = [ + [1842, 3451], + [1842, 3591], +] + +[[area.polygons]] +vertices = [ + [1726, 3453], + [1727, 3453], + [1727, 3511], + [1726, 3511], +] + +[[area.polygons]] +vertices = [ + [1800, 3453], + [1800, 3457], +] + +[[area.polygons]] +vertices = [ + [1843, 3453], + [1843, 3588], +] + +[[area.polygons]] +vertices = [[1799, 3455]] + +[[area.polygons]] +vertices = [ + [1844, 3455], + [1844, 3587], +] + +[[area.polygons]] +vertices = [ + [1845, 3456], + [1845, 3586], +] + +[[area.polygons]] +vertices = [ + [1781, 3457], + [1786, 3457], + [1786, 3560], + [1781, 3560], +] + +[[area.polygons]] +vertices = [ + [1846, 3457], + [1847, 3457], + [1847, 3464], + [1846, 3464], +] + +[[area.polygons]] +vertices = [ + [1715, 3458], + [1718, 3458], + [1718, 3505], + [1715, 3505], +] + +[[area.polygons]] +vertices = [ + [1780, 3458], + [1780, 3542], +] + +[[area.polygons]] +vertices = [ + [1787, 3458], + [1787, 3560], +] + +[[area.polygons]] +vertices = [ + [1848, 3458], + [1848, 3463], +] + +[[area.polygons]] +vertices = [ + [1713, 3459], + [1714, 3459], + [1714, 3501], + [1713, 3501], +] + +[[area.polygons]] +vertices = [ + [1719, 3459], + [1725, 3459], + [1725, 3509], + [1719, 3509], +] + +[[area.polygons]] +vertices = [ + [1728, 3459], + [1728, 3513], +] + +[[area.polygons]] +vertices = [ + [1772, 3459], + [1773, 3459], + [1773, 3542], + [1772, 3542], +] + +[[area.polygons]] +vertices = [ + [1779, 3459], + [1779, 3542], +] + +[[area.polygons]] +vertices = [ + [1788, 3459], + [1788, 3561], +] + +[[area.polygons]] +vertices = [ + [1800, 3459], + [1800, 3570], +] + +[[area.polygons]] +vertices = [ + [1849, 3459], + [1849, 3462], +] + +[[area.polygons]] +vertices = [ + [1697, 3460], + [1700, 3460], + [1700, 3495], + [1697, 3495], +] + +[[area.polygons]] +vertices = [ + [1712, 3460], + [1712, 3499], +] + +[[area.polygons]] +vertices = [ + [1729, 3460], + [1729, 3514], +] + +[[area.polygons]] +vertices = [ + [1774, 3460], + [1774, 3542], +] + +[[area.polygons]] +vertices = [ + [1778, 3460], + [1778, 3542], +] + +[[area.polygons]] +vertices = [ + [1789, 3460], + [1790, 3460], + [1790, 3566], + [1789, 3566], +] + +[[area.polygons]] +vertices = [ + [1796, 3460], + [1799, 3460], + [1799, 3568], + [1796, 3568], +] + +[[area.polygons]] +vertices = [ + [1850, 3460], + [1850, 3461], +] + +[[area.polygons]] +vertices = [ + [1695, 3461], + [1696, 3461], + [1696, 3495], + [1695, 3495], +] + +[[area.polygons]] +vertices = [ + [1701, 3461], + [1702, 3461], + [1702, 3495], + [1701, 3495], +] + +[[area.polygons]] +vertices = [ + [1711, 3461], + [1711, 3498], +] + +[[area.polygons]] +vertices = [ + [1730, 3461], + [1731, 3461], + [1731, 3544], + [1730, 3544], +] + +[[area.polygons]] +vertices = [ + [1771, 3461], + [1771, 3542], +] + +[[area.polygons]] +vertices = [ + [1775, 3461], + [1777, 3461], + [1777, 3542], + [1775, 3542], +] + +[[area.polygons]] +vertices = [ + [1791, 3461], + [1795, 3461], + [1795, 3566], + [1791, 3566], +] + +[[area.polygons]] +vertices = [ + [1694, 3462], + [1694, 3495], +] + +[[area.polygons]] +vertices = [ + [1703, 3462], + [1703, 3495], +] + +[[area.polygons]] +vertices = [ + [1706, 3462], + [1710, 3462], + [1710, 3495], + [1706, 3495], +] + +[[area.polygons]] +vertices = [ + [1732, 3462], + [1733, 3462], + [1733, 3545], + [1732, 3545], +] + +[[area.polygons]] +vertices = [ + [1704, 3463], + [1705, 3463], + [1705, 3495], + [1704, 3495], +] + +[[area.polygons]] +vertices = [ + [1734, 3463], + [1734, 3544], +] + +[[area.polygons]] +vertices = [ + [1748, 3463], + [1748, 3542], +] + +[[area.polygons]] +vertices = [ + [1764, 3463], + [1767, 3463], + [1767, 3542], + [1764, 3542], +] + +[[area.polygons]] +vertices = [ + [1770, 3463], + [1770, 3542], +] + +[[area.polygons]] +vertices = [ + [1693, 3464], + [1693, 3495], +] + +[[area.polygons]] +vertices = [ + [1735, 3464], + [1735, 3544], +] + +[[area.polygons]] +vertices = [ + [1749, 3464], + [1749, 3542], +] + +[[area.polygons]] +vertices = [ + [1762, 3464], + [1763, 3464], + [1763, 3542], + [1762, 3542], +] + +[[area.polygons]] +vertices = [ + [1768, 3464], + [1769, 3464], + [1769, 3542], + [1768, 3542], +] + +[[area.polygons]] +vertices = [ + [1692, 3465], + [1692, 3495], +] + +[[area.polygons]] +vertices = [ + [1736, 3465], + [1736, 3544], +] + +[[area.polygons]] +vertices = [ + [1747, 3465], + [1747, 3542], +] + +[[area.polygons]] +vertices = [ + [1750, 3465], + [1750, 3542], +] + +[[area.polygons]] +vertices = [ + [1761, 3465], + [1761, 3545], +] + +[[area.polygons]] +vertices = [ + [1846, 3465], + [1846, 3532], +] + +[[area.polygons]] +vertices = [ + [1737, 3466], + [1737, 3544], +] + +[[area.polygons]] +vertices = [ + [1746, 3466], + [1746, 3543], +] + +[[area.polygons]] +vertices = [ + [1751, 3466], + [1751, 3542], +] + +[[area.polygons]] +vertices = [ + [1759, 3466], + [1760, 3466], + [1760, 3545], + [1759, 3545], +] + +[[area.polygons]] +vertices = [ + [1854, 3466], + [1858, 3466], + [1858, 3522], + [1854, 3522], +] + +[[area.polygons]] +vertices = [ + [1691, 3467], + [1691, 3495], +] + +[[area.polygons]] +vertices = [ + [1738, 3467], + [1739, 3467], + [1739, 3544], + [1738, 3544], +] + +[[area.polygons]] +vertices = [ + [1745, 3467], + [1745, 3544], +] + +[[area.polygons]] +vertices = [ + [1752, 3467], + [1753, 3467], + [1753, 3542], + [1752, 3542], +] + +[[area.polygons]] +vertices = [ + [1756, 3467], + [1758, 3467], + [1758, 3544], + [1756, 3544], +] + +[[area.polygons]] +vertices = [ + [1859, 3467], + [1859, 3522], +] + +[[area.polygons]] +vertices = [ + [1689, 3468], + [1690, 3468], + [1690, 3495], + [1689, 3495], +] + +[[area.polygons]] +vertices = [ + [1740, 3468], + [1741, 3468], + [1741, 3544], + [1740, 3544], +] + +[[area.polygons]] +vertices = [ + [1744, 3468], + [1744, 3544], +] + +[[area.polygons]] +vertices = [ + [1754, 3468], + [1755, 3468], + [1755, 3543], + [1754, 3543], +] + +[[area.polygons]] +vertices = [ + [1847, 3468], + [1848, 3468], + [1848, 3530], + [1847, 3530], +] + +[[area.polygons]] +vertices = [ + [1853, 3468], + [1853, 3527], +] + +[[area.polygons]] +vertices = [ + [1860, 3468], + [1862, 3468], + [1862, 3521], + [1860, 3521], +] + +[[area.polygons]] +vertices = [ + [1742, 3469], + [1743, 3469], + [1743, 3544], + [1742, 3544], +] + +[[area.polygons]] +vertices = [ + [1849, 3469], + [1852, 3469], + [1852, 3528], + [1849, 3528], +] + +[[area.polygons]] +vertices = [ + [1863, 3469], + [1864, 3469], + [1864, 3520], + [1863, 3520], +] + +[[area.polygons]] +vertices = [ + [1671, 3470], + [1677, 3470], + [1677, 3481], + [1671, 3481], +] + +[[area.polygons]] +vertices = [ + [1688, 3470], + [1688, 3495], +] + +[[area.polygons]] +vertices = [ + [1865, 3470], + [1869, 3470], + [1869, 3503], + [1865, 3503], +] + +[[area.polygons]] +vertices = [ + [1670, 3471], + [1670, 3479], +] + +[[area.polygons]] +vertices = [ + [1678, 3471], + [1680, 3471], + [1680, 3491], + [1678, 3491], +] + +[[area.polygons]] +vertices = [ + [1687, 3471], + [1687, 3495], +] + +[[area.polygons]] +vertices = [ + [1870, 3471], + [1870, 3510], +] + +[[area.polygons]] +vertices = [ + [1669, 3472], + [1669, 3477], +] + +[[area.polygons]] +vertices = [ + [1681, 3472], + [1682, 3472], + [1682, 3493], + [1681, 3493], +] + +[[area.polygons]] +vertices = [ + [1686, 3472], + [1686, 3495], +] + +[[area.polygons]] +vertices = [ + [1871, 3472], + [1874, 3472], + [1874, 3511], + [1871, 3511], +] + +[[area.polygons]] +vertices = [ + [1683, 3473], + [1685, 3473], + [1685, 3493], + [1683, 3493], +] + +[[area.polygons]] +vertices = [ + [1875, 3473], + [1875, 3517], +] + +[[area.polygons]] +vertices = [ + [1876, 3475], + [1877, 3475], + [1877, 3518], + [1876, 3518], +] + +[[area.polygons]] +vertices = [ + [1878, 3476], + [1879, 3476], + [1879, 3487], + [1878, 3487], +] + +[[area.polygons]] +vertices = [ + [1880, 3477], + [1881, 3477], + [1881, 3485], + [1880, 3485], +] + +[[area.polygons]] +vertices = [ + [1882, 3478], + [1882, 3484], +] + +[[area.polygons]] +vertices = [ + [1883, 3479], + [1883, 3483], +] + +[[area.polygons]] +vertices = [ + [1884, 3480], + [1884, 3482], +] + +[[area.polygons]] +vertices = [ + [1672, 3482], + [1677, 3482], +] + +[[area.polygons]] +vertices = [ + [1674, 3483], + [1677, 3483], +] + +[[area.polygons]] +vertices = [ + [1675, 3484], + [1677, 3484], +] + +[[area.polygons]] +vertices = [ + [1676, 3485], + [1677, 3485], +] + +[[area.polygons]] +vertices = [[1677, 3486]] + +[[area.polygons]] +vertices = [[1880, 3486]] + +[[area.polygons]] +vertices = [ + [1677, 3488], + [1677, 3491], +] + +[[area.polygons]] +vertices = [ + [1878, 3488], + [1878, 3518], +] + +[[area.polygons]] +vertices = [ + [1679, 3492], + [1680, 3492], +] + +[[area.polygons]] +vertices = [ + [1879, 3492], + [1879, 3517], +] + +[[area.polygons]] +vertices = [ + [1684, 3494], + [1685, 3494], +] + +[[area.polygons]] +vertices = [ + [1880, 3494], + [1880, 3515], +] + +[[area.polygons]] +vertices = [ + [1881, 3495], + [1881, 3513], +] + +[[area.polygons]] +vertices = [[1710, 3496]] + +[[area.polygons]] +vertices = [ + [1882, 3496], + [1882, 3511], +] + +[[area.polygons]] +vertices = [ + [1714, 3502], + [1714, 3503], +] + +[[area.polygons]] +vertices = [ + [1883, 3503], + [1883, 3510], +] + +[[area.polygons]] +vertices = [[1865, 3504]] + +[[area.polygons]] +vertices = [[1869, 3504]] + +[[area.polygons]] +vertices = [ + [1884, 3505], + [1884, 3508], +] + +[[area.polygons]] +vertices = [ + [1716, 3506], + [1718, 3506], +] + +[[area.polygons]] +vertices = [ + [1717, 3507], + [1718, 3507], + [1718, 3508], + [1717, 3508], +] + +[[area.polygons]] +vertices = [ + [1869, 3508], + [1869, 3509], +] + +[[area.polygons]] +vertices = [[1718, 3509]] + +[[area.polygons]] +vertices = [[1725, 3510]] + +[[area.polygons]] +vertices = [ + [1865, 3511], + [1865, 3519], +] + +[[area.polygons]] +vertices = [[1727, 3512]] + +[[area.polygons]] +vertices = [ + [1872, 3512], + [1874, 3512], +] + +[[area.polygons]] +vertices = [ + [1873, 3513], + [1874, 3513], + [1874, 3514], + [1873, 3514], +] + +[[area.polygons]] +vertices = [ + [1866, 3514], + [1866, 3518], +] + +[[area.polygons]] +vertices = [ + [1867, 3515], + [1867, 3517], +] + +[[area.polygons]] +vertices = [ + [1874, 3515], + [1874, 3516], +] + +[[area.polygons]] +vertices = [ + [1729, 3521], + [1729, 3543], +] + +[[area.polygons]] +vertices = [ + [1860, 3522], + [1861, 3522], +] + +[[area.polygons]] +vertices = [ + [1728, 3523], + [1728, 3541], +] + +[[area.polygons]] +vertices = [ + [1854, 3523], + [1857, 3523], +] + +[[area.polygons]] +vertices = [ + [1727, 3524], + [1727, 3541], +] + +[[area.polygons]] +vertices = [ + [1854, 3524], + [1855, 3524], +] + +[[area.polygons]] +vertices = [[1854, 3525]] + +[[area.polygons]] +vertices = [ + [1726, 3526], + [1726, 3541], +] + +[[area.polygons]] +vertices = [ + [1725, 3528], + [1725, 3541], +] + +[[area.polygons]] +vertices = [ + [1724, 3530], + [1724, 3541], +] + +[[area.polygons]] +vertices = [[1847, 3531]] + +[[area.polygons]] +vertices = [ + [1723, 3532], + [1723, 3541], +] + +[[area.polygons]] +vertices = [ + [1722, 3533], + [1722, 3541], +] + +[[area.polygons]] +vertices = [ + [1859, 3533], + [1862, 3533], + [1862, 3564], + [1859, 3564], +] + +[[area.polygons]] +vertices = [ + [1857, 3534], + [1858, 3534], + [1858, 3569], + [1857, 3569], +] + +[[area.polygons]] +vertices = [ + [1863, 3534], + [1863, 3564], +] + +[[area.polygons]] +vertices = [ + [1721, 3535], + [1721, 3541], +] + +[[area.polygons]] +vertices = [ + [1856, 3535], + [1856, 3569], +] + +[[area.polygons]] +vertices = [ + [1864, 3535], + [1864, 3565], +] + +[[area.polygons]] +vertices = [ + [1853, 3536], + [1855, 3536], + [1855, 3569], + [1853, 3569], +] + +[[area.polygons]] +vertices = [ + [1865, 3536], + [1865, 3567], +] + +[[area.polygons]] +vertices = [ + [1852, 3537], + [1852, 3541], +] + +[[area.polygons]] +vertices = [ + [1866, 3537], + [1866, 3567], +] + +[[area.polygons]] +vertices = [ + [1851, 3538], + [1851, 3539], +] + +[[area.polygons]] +vertices = [ + [1846, 3539], + [1846, 3586], +] + +[[area.polygons]] +vertices = [ + [1847, 3541], + [1847, 3585], +] + +[[area.polygons]] +vertices = [ + [1867, 3541], + [1870, 3541], + [1870, 3568], + [1867, 3568], +] + +[[area.polygons]] +vertices = [ + [1848, 3542], + [1848, 3585], +] + +[[area.polygons]] +vertices = [ + [1871, 3542], + [1872, 3542], + [1872, 3566], + [1871, 3566], +] + +[[area.polygons]] +vertices = [ + [1849, 3543], + [1849, 3584], +] + +[[area.polygons]] +vertices = [ + [1873, 3543], + [1875, 3543], + [1875, 3564], + [1873, 3564], +] + +[[area.polygons]] +vertices = [[1755, 3544]] + +[[area.polygons]] +vertices = [ + [1876, 3544], + [1876, 3561], +] + +[[area.polygons]] +vertices = [[1731, 3545]] + +[[area.polygons]] +vertices = [ + [1757, 3545], + [1758, 3545], +] + +[[area.polygons]] +vertices = [ + [1850, 3545], + [1852, 3545], + [1852, 3572], + [1850, 3572], +] + +[[area.polygons]] +vertices = [ + [1877, 3547], + [1878, 3547], + [1878, 3561], + [1877, 3561], +] + +[[area.polygons]] +vertices = [ + [1879, 3548], + [1883, 3548], + [1883, 3551], + [1879, 3551], +] + +[[area.polygons]] +vertices = [ + [1884, 3549], + [1884, 3550], +] + +[[area.polygons]] +vertices = [ + [1879, 3552], + [1882, 3552], + [1882, 3553], + [1879, 3553], +] + +[[area.polygons]] +vertices = [ + [1879, 3554], + [1881, 3554], + [1881, 3558], + [1879, 3558], +] + +[[area.polygons]] +vertices = [ + [1777, 3558], + [1780, 3558], + [1780, 3560], + [1777, 3560], +] + +[[area.polygons]] +vertices = [ + [1879, 3559], + [1880, 3559], +] + +[[area.polygons]] +vertices = [[1879, 3560]] + +[[area.polygons]] +vertices = [ + [1859, 3565], + [1859, 3568], +] + +[[area.polygons]] +vertices = [ + [1873, 3565], + [1874, 3565], +] + +[[area.polygons]] +vertices = [ + [1794, 3567], + [1795, 3567], +] + +[[area.polygons]] +vertices = [[1871, 3567]] + +[[area.polygons]] +vertices = [ + [1798, 3569], + [1799, 3569], +] + +[[area.polygons]] +vertices = [ + [1853, 3570], + [1854, 3570], +] + +[[area.polygons]] +vertices = [ + [1807, 3573], + [1807, 3587], +] + +[[area.polygons]] +vertices = [ + [1850, 3573], + [1851, 3573], + [1851, 3581], + [1850, 3581], +] + +[[area.polygons]] +vertices = [ + [1806, 3576], + [1806, 3586], +] + +[[area.polygons]] +vertices = [ + [1852, 3576], + [1852, 3580], +] + +[[area.polygons]] +vertices = [ + [1853, 3577], + [1853, 3580], +] + +[[area.polygons]] +vertices = [ + [1805, 3580], + [1805, 3585], +] + +[[area.polygons]] +vertices = [ + [1804, 3581], + [1804, 3584], +] + +[[area.polygons]] +vertices = [[1850, 3582]] + +[[area.polygons]] +vertices = [[1810, 3590]] + +[[area.polygons]] +vertices = [[1817, 3591]] + +[[area.polygons]] +vertices = [[1812, 3592]] + +[[area.polygons]] +vertices = [ + [1826, 3592], + [1826, 3593], +] + +[[area.polygons]] +vertices = [[1840, 3593]] + +[[area.polygons]] +vertices = [[1814, 3594]] + +[[area.polygons]] +vertices = [ + [1828, 3596], + [1829, 3596], + [1829, 3597], + [1828, 3597], +] + +[[area.polygons]] +vertices = [ + [1840, 3597], + [1840, 3602], +] + +[[area.polygons]] +vertices = [ + [1829, 3598], + [1829, 3609], +] + +[[area.polygons]] +vertices = [ + [1828, 3604], + [1828, 3606], +] + +[[area.polygons]] +vertices = [ + [1835, 3605], + [1837, 3605], + [1837, 3642], + [1835, 3642], +] + +[[area.polygons]] +vertices = [ + [1838, 3610], + [1838, 3645], +] + +[[area.polygons]] +vertices = [ + [1839, 3611], + [1839, 3645], +] + +[[area.polygons]] +vertices = [ + [1829, 3612], + [1829, 3615], +] + +[[area.polygons]] +vertices = [ + [1840, 3614], + [1840, 3645], +] + +[[area.polygons]] +vertices = [ + [1841, 3617], + [1841, 3621], +] + +[[area.polygons]] +vertices = [ + [1830, 3626], + [1830, 3628], +] + +[[area.polygons]] +vertices = [ + [1841, 3630], + [1841, 3645], +] + +[[area.polygons]] +vertices = [ + [1842, 3632], + [1842, 3644], +] + +[[area.polygons]] +vertices = [ + [1833, 3633], + [1834, 3633], +] + +[[area.polygons]] +vertices = [ + [1834, 3634], + [1834, 3635], +] + +[[area.polygons]] +vertices = [ + [1843, 3635], + [1843, 3643], +] + +[[area.polygons]] +vertices = [ + [1834, 3638], + [1834, 3640], +] + +[[area.polygons]] +vertices = [ + [1844, 3640], + [1844, 3642], +] + +[[area.polygons]] +vertices = [ + [1836, 3643], + [1837, 3643], + [1837, 3644], + [1836, 3644], +] + +[[area.polygons]] +vertices = [ + [1856, 9912], + [1919, 9912], + [1919, 9983], + [1856, 9983], +] diff --git a/.data/raw-cache/map/area/creature_cruelty.toml b/.data/raw-cache/map/area/creature_cruelty.toml new file mode 100644 index 000000000..fb6c6de31 --- /dev/null +++ b/.data/raw-cache/map/area/creature_cruelty.toml @@ -0,0 +1,34 @@ +# Creature Cruelty music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Creature Cruelty. +# Bounds x 2645-2653, z 3214-3222; 73 tiles as 4 rectangles. + +[[area]] +name = "Creature Cruelty" +area_id = "area.creature_cruelty" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2646, 3214], + [2651, 3214], + [2651, 3222], + [2646, 3222], +] + +[[area.polygons]] +vertices = [ + [2645, 3215], + [2645, 3221], +] + +[[area.polygons]] +vertices = [ + [2652, 3215], + [2652, 3221], +] + +[[area.polygons]] +vertices = [ + [2653, 3216], + [2653, 3220], +] diff --git a/.data/raw-cache/map/area/creeping_vines.toml b/.data/raw-cache/map/area/creeping_vines.toml new file mode 100644 index 000000000..6b377087f --- /dev/null +++ b/.data/raw-cache/map/area/creeping_vines.toml @@ -0,0 +1,16 @@ +# Creeping Vines music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Creeping Vines. +# Bounds x 1216-1279, z 10048-10111; 4096 tiles as 1 rectangles. + +[[area]] +name = "Creeping Vines" +area_id = "area.creeping_vines" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1216, 10048], + [1279, 10048], + [1279, 10111], + [1216, 10111], +] diff --git a/.data/raw-cache/map/area/crystal_castle.toml b/.data/raw-cache/map/area/crystal_castle.toml new file mode 100644 index 000000000..fb99bcd7a --- /dev/null +++ b/.data/raw-cache/map/area/crystal_castle.toml @@ -0,0 +1,829 @@ +# Crystal Castle music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Crystal Castle. +# Bounds x 2152-2331, z 3230-3372; 7101 tiles as 132 rectangles. + +[[area]] +name = "Crystal Castle" +area_id = "area.crystal_castle" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2227, 3230], + [2231, 3230], + [2231, 3270], + [2227, 3270], +] + +[[area.polygons]] +vertices = [ + [2226, 3231], + [2226, 3270], +] + +[[area.polygons]] +vertices = [ + [2232, 3231], + [2232, 3270], +] + +[[area.polygons]] +vertices = [ + [2189, 3232], + [2196, 3232], + [2196, 3276], + [2189, 3276], +] + +[[area.polygons]] +vertices = [ + [2225, 3232], + [2225, 3270], +] + +[[area.polygons]] +vertices = [ + [2233, 3232], + [2233, 3270], +] + +[[area.polygons]] +vertices = [ + [2188, 3233], + [2188, 3284], +] + +[[area.polygons]] +vertices = [ + [2197, 3233], + [2197, 3275], +] + +[[area.polygons]] +vertices = [ + [2234, 3233], + [2234, 3270], +] + +[[area.polygons]] +vertices = [ + [2187, 3234], + [2187, 3285], +] + +[[area.polygons]] +vertices = [ + [2198, 3234], + [2198, 3274], +] + +[[area.polygons]] +vertices = [ + [2235, 3234], + [2235, 3270], +] + +[[area.polygons]] +vertices = [ + [2186, 3235], + [2186, 3286], +] + +[[area.polygons]] +vertices = [ + [2199, 3235], + [2199, 3273], +] + +[[area.polygons]] +vertices = [ + [2236, 3235], + [2236, 3270], +] + +[[area.polygons]] +vertices = [ + [2185, 3236], + [2185, 3287], +] + +[[area.polygons]] +vertices = [ + [2237, 3236], + [2237, 3270], +] + +[[area.polygons]] +vertices = [ + [2184, 3237], + [2184, 3287], +] + +[[area.polygons]] +vertices = [ + [2207, 3237], + [2212, 3237], + [2212, 3269], + [2207, 3269], +] + +[[area.polygons]] +vertices = [ + [2238, 3237], + [2238, 3270], +] + +[[area.polygons]] +vertices = [ + [2183, 3238], + [2183, 3287], +] + +[[area.polygons]] +vertices = [ + [2205, 3238], + [2206, 3238], + [2206, 3267], + [2205, 3267], +] + +[[area.polygons]] +vertices = [ + [2239, 3238], + [2241, 3238], + [2241, 3270], + [2239, 3270], +] + +[[area.polygons]] +vertices = [ + [2182, 3239], + [2182, 3251], +] + +[[area.polygons]] +vertices = [ + [2200, 3239], + [2200, 3269], +] + +[[area.polygons]] +vertices = [ + [2203, 3239], + [2204, 3239], + [2204, 3267], + [2203, 3267], +] + +[[area.polygons]] +vertices = [ + [2242, 3239], + [2242, 3270], +] + +[[area.polygons]] +vertices = [ + [2181, 3240], + [2181, 3249], +] + +[[area.polygons]] +vertices = [ + [2201, 3240], + [2202, 3240], + [2202, 3267], + [2201, 3267], +] + +[[area.polygons]] +vertices = [ + [2224, 3240], + [2224, 3270], +] + +[[area.polygons]] +vertices = [ + [2243, 3240], + [2243, 3270], +] + +[[area.polygons]] +vertices = [ + [2223, 3241], + [2223, 3270], +] + +[[area.polygons]] +vertices = [ + [2244, 3241], + [2244, 3270], +] + +[[area.polygons]] +vertices = [ + [2222, 3242], + [2222, 3270], +] + +[[area.polygons]] +vertices = [ + [2245, 3242], + [2245, 3270], +] + +[[area.polygons]] +vertices = [ + [2221, 3243], + [2221, 3270], +] + +[[area.polygons]] +vertices = [ + [2246, 3243], + [2246, 3270], +] + +[[area.polygons]] +vertices = [ + [2220, 3244], + [2220, 3270], +] + +[[area.polygons]] +vertices = [ + [2247, 3244], + [2247, 3270], +] + +[[area.polygons]] +vertices = [ + [2219, 3245], + [2219, 3270], +] + +[[area.polygons]] +vertices = [ + [2248, 3245], + [2261, 3245], + [2261, 3270], + [2248, 3270], +] + +[[area.polygons]] +vertices = [ + [2213, 3246], + [2218, 3246], + [2218, 3270], + [2213, 3270], +] + +[[area.polygons]] +vertices = [ + [2262, 3253], + [2305, 3253], + [2305, 3267], + [2262, 3267], +] + +[[area.polygons]] +vertices = [ + [2182, 3256], + [2182, 3287], +] + +[[area.polygons]] +vertices = [ + [2181, 3257], + [2181, 3288], +] + +[[area.polygons]] +vertices = [ + [2180, 3258], + [2180, 3289], +] + +[[area.polygons]] +vertices = [ + [2179, 3259], + [2179, 3372], +] + +[[area.polygons]] +vertices = [ + [2172, 3260], + [2178, 3260], + [2178, 3372], + [2172, 3372], +] + +[[area.polygons]] +vertices = [ + [2171, 3261], + [2171, 3300], +] + +[[area.polygons]] +vertices = [ + [2170, 3262], + [2170, 3297], +] + +[[area.polygons]] +vertices = [ + [2306, 3262], + [2331, 3262], + [2331, 3271], + [2306, 3271], +] + +[[area.polygons]] +vertices = [ + [2168, 3263], + [2169, 3263], + [2169, 3293], + [2168, 3293], +] + +[[area.polygons]] +vertices = [ + [2167, 3264], + [2167, 3290], +] + +[[area.polygons]] +vertices = [ + [2166, 3265], + [2166, 3288], +] + +[[area.polygons]] +vertices = [[2201, 3268]] + +[[area.polygons]] +vertices = [[2206, 3268]] + +[[area.polygons]] +vertices = [ + [2262, 3268], + [2272, 3268], +] + +[[area.polygons]] +vertices = [ + [2277, 3268], + [2305, 3268], +] + +[[area.polygons]] +vertices = [ + [2262, 3269], + [2271, 3269], +] + +[[area.polygons]] +vertices = [ + [2278, 3269], + [2305, 3269], +] + +[[area.polygons]] +vertices = [ + [2165, 3270], + [2165, 3283], +] + +[[area.polygons]] +vertices = [ + [2208, 3270], + [2212, 3270], +] + +[[area.polygons]] +vertices = [ + [2262, 3270], + [2270, 3270], +] + +[[area.polygons]] +vertices = [ + [2279, 3270], + [2305, 3270], +] + +[[area.polygons]] +vertices = [ + [2280, 3271], + [2305, 3271], + [2305, 3274], + [2280, 3274], +] + +[[area.polygons]] +vertices = [ + [2164, 3272], + [2164, 3278], +] + +[[area.polygons]] +vertices = [ + [2306, 3272], + [2330, 3272], +] + +[[area.polygons]] +vertices = [ + [2306, 3273], + [2329, 3273], +] + +[[area.polygons]] +vertices = [ + [2163, 3274], + [2163, 3276], +] + +[[area.polygons]] +vertices = [ + [2306, 3274], + [2328, 3274], +] + +[[area.polygons]] +vertices = [ + [2281, 3275], + [2327, 3275], +] + +[[area.polygons]] +vertices = [ + [2282, 3276], + [2327, 3276], +] + +[[area.polygons]] +vertices = [ + [2189, 3277], + [2195, 3277], +] + +[[area.polygons]] +vertices = [ + [2283, 3277], + [2327, 3277], +] + +[[area.polygons]] +vertices = [ + [2189, 3278], + [2194, 3278], +] + +[[area.polygons]] +vertices = [ + [2284, 3278], + [2327, 3278], +] + +[[area.polygons]] +vertices = [ + [2189, 3279], + [2193, 3279], +] + +[[area.polygons]] +vertices = [ + [2285, 3279], + [2327, 3279], +] + +[[area.polygons]] +vertices = [ + [2189, 3280], + [2192, 3280], +] + +[[area.polygons]] +vertices = [ + [2286, 3280], + [2327, 3280], +] + +[[area.polygons]] +vertices = [ + [2189, 3281], + [2191, 3281], +] + +[[area.polygons]] +vertices = [ + [2287, 3281], + [2327, 3281], +] + +[[area.polygons]] +vertices = [ + [2189, 3282], + [2190, 3282], +] + +[[area.polygons]] +vertices = [ + [2288, 3282], + [2327, 3282], +] + +[[area.polygons]] +vertices = [[2189, 3283]] + +[[area.polygons]] +vertices = [ + [2289, 3283], + [2327, 3283], +] + +[[area.polygons]] +vertices = [ + [2290, 3284], + [2327, 3284], +] + +[[area.polygons]] +vertices = [ + [2291, 3285], + [2327, 3285], +] + +[[area.polygons]] +vertices = [ + [2292, 3286], + [2327, 3286], +] + +[[area.polygons]] +vertices = [ + [2293, 3287], + [2327, 3287], +] + +[[area.polygons]] +vertices = [ + [2297, 3288], + [2327, 3288], +] + +[[area.polygons]] +vertices = [ + [2298, 3289], + [2327, 3289], +] + +[[area.polygons]] +vertices = [ + [2299, 3290], + [2327, 3290], +] + +[[area.polygons]] +vertices = [ + [2300, 3291], + [2327, 3291], + [2327, 3304], + [2300, 3304], +] + +[[area.polygons]] +vertices = [ + [2169, 3294], + [2169, 3296], +] + +[[area.polygons]] +vertices = [ + [2299, 3294], + [2299, 3320], +] + +[[area.polygons]] +vertices = [ + [2180, 3295], + [2180, 3360], +] + +[[area.polygons]] +vertices = [ + [2298, 3295], + [2298, 3320], +] + +[[area.polygons]] +vertices = [ + [2181, 3296], + [2181, 3359], +] + +[[area.polygons]] +vertices = [ + [2297, 3296], + [2297, 3320], +] + +[[area.polygons]] +vertices = [ + [2182, 3297], + [2182, 3358], +] + +[[area.polygons]] +vertices = [ + [2171, 3304], + [2171, 3372], +] + +[[area.polygons]] +vertices = [ + [2300, 3305], + [2315, 3305], +] + +[[area.polygons]] +vertices = [ + [2170, 3306], + [2170, 3319], +] + +[[area.polygons]] +vertices = [ + [2300, 3306], + [2314, 3306], +] + +[[area.polygons]] +vertices = [ + [2169, 3307], + [2169, 3318], +] + +[[area.polygons]] +vertices = [ + [2300, 3307], + [2313, 3307], +] + +[[area.polygons]] +vertices = [ + [2300, 3308], + [2312, 3308], + [2312, 3320], + [2300, 3320], +] + +[[area.polygons]] +vertices = [ + [2168, 3309], + [2168, 3315], +] + +[[area.polygons]] +vertices = [ + [2167, 3311], + [2167, 3313], +] + +[[area.polygons]] +vertices = [ + [2170, 3322], + [2170, 3372], +] + +[[area.polygons]] +vertices = [ + [2168, 3323], + [2169, 3323], + [2169, 3372], + [2168, 3372], +] + +[[area.polygons]] +vertices = [ + [2165, 3325], + [2167, 3325], + [2167, 3326], + [2165, 3326], +] + +[[area.polygons]] +vertices = [ + [2157, 3329], + [2167, 3329], + [2167, 3330], + [2157, 3330], +] + +[[area.polygons]] +vertices = [ + [2157, 3331], + [2157, 3335], +] + +[[area.polygons]] +vertices = [ + [2167, 3332], + [2167, 3372], +] + +[[area.polygons]] +vertices = [ + [2152, 3333], + [2156, 3333], + [2156, 3335], + [2152, 3335], +] + +[[area.polygons]] +vertices = [ + [2158, 3333], + [2160, 3333], + [2160, 3335], + [2158, 3335], +] + +[[area.polygons]] +vertices = [[2161, 3334]] + +[[area.polygons]] +vertices = [ + [2166, 3334], + [2166, 3335], +] + +[[area.polygons]] +vertices = [ + [2166, 3340], + [2166, 3345], +] + +[[area.polygons]] +vertices = [ + [2165, 3342], + [2165, 3343], +] + +[[area.polygons]] +vertices = [ + [2166, 3348], + [2166, 3355], +] + +[[area.polygons]] +vertices = [ + [2165, 3349], + [2165, 3354], +] + +[[area.polygons]] +vertices = [ + [2164, 3351], + [2164, 3352], +] + +[[area.polygons]] +vertices = [ + [2166, 3358], + [2166, 3367], +] + +[[area.polygons]] +vertices = [ + [2165, 3360], + [2165, 3365], +] + +[[area.polygons]] +vertices = [ + [2161, 3361], + [2164, 3361], + [2164, 3363], + [2161, 3363], +] + +[[area.polygons]] +vertices = [ + [2159, 3362], + [2160, 3362], +] + +[[area.polygons]] +vertices = [[2160, 3363]] + +[[area.polygons]] +vertices = [ + [2163, 3364], + [2164, 3364], +] + +[[area.polygons]] +vertices = [ + [2166, 3369], + [2166, 3372], +] diff --git a/.data/raw-cache/map/area/crystal_sword.toml b/.data/raw-cache/map/area/crystal_sword.toml new file mode 100644 index 000000000..8dc7ee664 --- /dev/null +++ b/.data/raw-cache/map/area/crystal_sword.toml @@ -0,0 +1,16 @@ +# Crystal Sword music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Crystal Sword. +# Bounds x 2624-2687, z 9664-9727; 4096 tiles as 1 rectangles. + +[[area]] +name = "Crystal Sword" +area_id = "area.crystal_sword" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2624, 9664], + [2687, 9664], + [2687, 9727], + [2624, 9727], +] diff --git a/.data/raw-cache/map/area/cursed.toml b/.data/raw-cache/map/area/cursed.toml new file mode 100644 index 000000000..d3fad9aa6 --- /dev/null +++ b/.data/raw-cache/map/area/cursed.toml @@ -0,0 +1,32 @@ +# Cursed music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Cursed. +# Bounds x 2304-3327, z 4800-12607; 16384 tiles as 3 rectangles. + +[[area]] +name = "Cursed" +area_id = "area.cursed" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2304, 4800], + [2367, 4800], + [2367, 4863], + [2304, 4863], +] + +[[area.polygons]] +vertices = [ + [2368, 9664], + [2495, 9664], + [2495, 9727], + [2368, 9727], +] + +[[area.polygons]] +vertices = [ + [3264, 12544], + [3327, 12544], + [3327, 12607], + [3264, 12607], +] diff --git a/.data/raw-cache/map/area/dagannoth_dawn.toml b/.data/raw-cache/map/area/dagannoth_dawn.toml new file mode 100644 index 000000000..45d052f97 --- /dev/null +++ b/.data/raw-cache/map/area/dagannoth_dawn.toml @@ -0,0 +1,24 @@ +# Dagannoth Dawn music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dagannoth Dawn. +# Bounds x 1792-1983, z 4352-4415; 8192 tiles as 2 rectangles. + +[[area]] +name = "Dagannoth Dawn" +area_id = "area.dagannoth_dawn" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1792, 4352], + [1855, 4352], + [1855, 4415], + [1792, 4415], +] + +[[area.polygons]] +vertices = [ + [1920, 4352], + [1983, 4352], + [1983, 4415], + [1920, 4415], +] diff --git a/.data/raw-cache/map/area/dance_of_death.toml b/.data/raw-cache/map/area/dance_of_death.toml new file mode 100644 index 000000000..dd408f034 --- /dev/null +++ b/.data/raw-cache/map/area/dance_of_death.toml @@ -0,0 +1,16 @@ +# Dance of Death music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dance of Death. +# Bounds x 2304-2367, z 5184-5247; 4096 tiles as 1 rectangles. + +[[area]] +name = "Dance of Death" +area_id = "area.dance_of_death" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2304, 5184], + [2367, 5184], + [2367, 5247], + [2304, 5247], +] diff --git a/.data/raw-cache/map/area/dance_of_the_undead.toml b/.data/raw-cache/map/area/dance_of_the_undead.toml new file mode 100644 index 000000000..02f702fef --- /dev/null +++ b/.data/raw-cache/map/area/dance_of_the_undead.toml @@ -0,0 +1,270 @@ +# Dance of the Undead music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dance of the Undead. +# Bounds x 3514-3584, z 3257-3341; 4153 tiles as 38 rectangles. + +[[area]] +name = "Dance of the Undead" +area_id = "area.dance_of_the_undead" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3530, 3257], + [3530, 3328], +] + +[[area.polygons]] +vertices = [ + [3529, 3258], + [3529, 3328], +] + +[[area.polygons]] +vertices = [ + [3528, 3259], + [3528, 3328], +] + +[[area.polygons]] +vertices = [ + [3527, 3260], + [3527, 3272], +] + +[[area.polygons]] +vertices = [ + [3531, 3260], + [3532, 3260], + [3532, 3328], + [3531, 3328], +] + +[[area.polygons]] +vertices = [ + [3526, 3261], + [3526, 3270], +] + +[[area.polygons]] +vertices = [ + [3533, 3261], + [3535, 3261], + [3535, 3328], + [3533, 3328], +] + +[[area.polygons]] +vertices = [ + [3525, 3262], + [3525, 3268], +] + +[[area.polygons]] +vertices = [ + [3536, 3262], + [3540, 3262], + [3540, 3328], + [3536, 3328], +] + +[[area.polygons]] +vertices = [ + [3545, 3262], + [3548, 3262], + [3548, 3328], + [3545, 3328], +] + +[[area.polygons]] +vertices = [ + [3556, 3262], + [3558, 3262], + [3558, 3328], + [3556, 3328], +] + +[[area.polygons]] +vertices = [ + [3563, 3262], + [3564, 3262], + [3564, 3328], + [3563, 3328], +] + +[[area.polygons]] +vertices = [ + [3524, 3263], + [3524, 3266], +] + +[[area.polygons]] +vertices = [ + [3541, 3263], + [3544, 3263], + [3544, 3328], + [3541, 3328], +] + +[[area.polygons]] +vertices = [ + [3549, 3263], + [3550, 3263], + [3550, 3328], + [3549, 3328], +] + +[[area.polygons]] +vertices = [ + [3554, 3263], + [3555, 3263], + [3555, 3328], + [3554, 3328], +] + +[[area.polygons]] +vertices = [ + [3559, 3263], + [3562, 3263], + [3562, 3328], + [3559, 3328], +] + +[[area.polygons]] +vertices = [ + [3565, 3263], + [3566, 3263], + [3566, 3328], + [3565, 3328], +] + +[[area.polygons]] +vertices = [ + [3570, 3263], + [3572, 3263], + [3572, 3328], + [3570, 3328], +] + +[[area.polygons]] +vertices = [ + [3579, 3263], + [3580, 3263], + [3580, 3328], + [3579, 3328], +] + +[[area.polygons]] +vertices = [ + [3551, 3264], + [3553, 3264], + [3553, 3328], + [3551, 3328], +] + +[[area.polygons]] +vertices = [ + [3567, 3264], + [3569, 3264], + [3569, 3328], + [3567, 3328], +] + +[[area.polygons]] +vertices = [ + [3573, 3264], + [3578, 3264], + [3578, 3328], + [3573, 3328], +] + +[[area.polygons]] +vertices = [ + [3581, 3264], + [3584, 3264], + [3584, 3328], + [3581, 3328], +] + +[[area.polygons]] +vertices = [ + [3527, 3288], + [3527, 3328], +] + +[[area.polygons]] +vertices = [ + [3526, 3290], + [3526, 3328], +] + +[[area.polygons]] +vertices = [ + [3525, 3292], + [3525, 3328], +] + +[[area.polygons]] +vertices = [ + [3524, 3294], + [3524, 3328], +] + +[[area.polygons]] +vertices = [ + [3523, 3314], + [3523, 3328], +] + +[[area.polygons]] +vertices = [ + [3522, 3315], + [3522, 3328], +] + +[[area.polygons]] +vertices = [ + [3521, 3316], + [3521, 3329], +] + +[[area.polygons]] +vertices = [ + [3520, 3317], + [3520, 3330], +] + +[[area.polygons]] +vertices = [ + [3519, 3318], + [3519, 3336], +] + +[[area.polygons]] +vertices = [ + [3518, 3319], + [3518, 3340], +] + +[[area.polygons]] +vertices = [ + [3517, 3320], + [3517, 3341], +] + +[[area.polygons]] +vertices = [ + [3516, 3321], + [3516, 3341], +] + +[[area.polygons]] +vertices = [ + [3515, 3322], + [3515, 3341], +] + +[[area.polygons]] +vertices = [ + [3514, 3323], + [3514, 3341], +] diff --git a/.data/raw-cache/map/area/dangerous_logic.toml b/.data/raw-cache/map/area/dangerous_logic.toml new file mode 100644 index 000000000..c99887a41 --- /dev/null +++ b/.data/raw-cache/map/area/dangerous_logic.toml @@ -0,0 +1,16 @@ +# Dangerous Logic music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dangerous Logic. +# Bounds x 4115-4149, z 4932-4985; 1890 tiles as 1 rectangles. + +[[area]] +name = "Dangerous Logic" +area_id = "area.dangerous_logic" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [4115, 4932], + [4149, 4932], + [4149, 4985], + [4115, 4985], +] diff --git a/.data/raw-cache/map/area/dangerous_road.toml b/.data/raw-cache/map/area/dangerous_road.toml new file mode 100644 index 000000000..a6e70bb84 --- /dev/null +++ b/.data/raw-cache/map/area/dangerous_road.toml @@ -0,0 +1,24 @@ +# Dangerous Road music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dangerous Road. +# Bounds x 2816-2879, z 9536-9663; 6696 tiles as 2 rectangles. + +[[area]] +name = "Dangerous Road" +area_id = "area.dangerous_road" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 9536], + [2879, 9536], + [2879, 9619], + [2816, 9619], +] + +[[area.polygons]] +vertices = [ + [2816, 9620], + [2845, 9620], + [2845, 9663], + [2816, 9663], +] diff --git a/.data/raw-cache/map/area/dangerous_way.toml b/.data/raw-cache/map/area/dangerous_way.toml new file mode 100644 index 000000000..4e64bd6c9 --- /dev/null +++ b/.data/raw-cache/map/area/dangerous_way.toml @@ -0,0 +1,16 @@ +# Dangerous Way music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dangerous Way. +# Bounds x 3520-3583, z 9664-9727; 4096 tiles as 1 rectangles. + +[[area]] +name = "Dangerous Way" +area_id = "area.dangerous_way" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3520, 9664], + [3583, 9664], + [3583, 9727], + [3520, 9727], +] diff --git a/.data/raw-cache/map/area/darkly_altared.toml b/.data/raw-cache/map/area/darkly_altared.toml new file mode 100644 index 000000000..8eb4633c3 --- /dev/null +++ b/.data/raw-cache/map/area/darkly_altared.toml @@ -0,0 +1,16 @@ +# Darkly Altared music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Darkly Altared. +# Bounds x 2240-2303, z 5632-5695; 4096 tiles as 1 rectangles. + +[[area]] +name = "Darkly Altared" +area_id = "area.darkly_altared" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2240, 5632], + [2303, 5632], + [2303, 5695], + [2240, 5695], +] diff --git a/.data/raw-cache/map/area/darkmeyer_music.toml b/.data/raw-cache/map/area/darkmeyer_music.toml new file mode 100644 index 000000000..3ce9998fe --- /dev/null +++ b/.data/raw-cache/map/area/darkmeyer_music.toml @@ -0,0 +1,208 @@ +# Darkmeyer music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Darkmeyer. +# Bounds x 3588-3668, z 3314-3397; 4993 tiles as 30 rectangles. + +[[area]] +name = "Darkmeyer" +area_id = "area.darkmeyer_music" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3594, 3314], + [3614, 3314], + [3614, 3397], + [3594, 3397], +] + +[[area.polygons]] +vertices = [ + [3615, 3332], + [3660, 3332], + [3660, 3353], + [3615, 3353], +] + +[[area.polygons]] +vertices = [ + [3588, 3333], + [3593, 3333], + [3593, 3349], + [3588, 3349], +] + +[[area.polygons]] +vertices = [ + [3661, 3336], + [3665, 3336], + [3665, 3382], + [3661, 3382], +] + +[[area.polygons]] +vertices = [ + [3590, 3350], + [3593, 3350], + [3593, 3383], + [3590, 3383], +] + +[[area.polygons]] +vertices = [ + [3666, 3351], + [3668, 3351], + [3668, 3382], + [3666, 3382], +] + +[[area.polygons]] +vertices = [ + [3615, 3354], + [3624, 3354], +] + +[[area.polygons]] +vertices = [ + [3632, 3354], + [3660, 3354], +] + +[[area.polygons]] +vertices = [ + [3615, 3355], + [3623, 3355], +] + +[[area.polygons]] +vertices = [ + [3633, 3355], + [3660, 3355], +] + +[[area.polygons]] +vertices = [ + [3615, 3356], + [3622, 3356], +] + +[[area.polygons]] +vertices = [ + [3634, 3356], + [3660, 3356], +] + +[[area.polygons]] +vertices = [ + [3615, 3357], + [3621, 3357], +] + +[[area.polygons]] +vertices = [ + [3635, 3357], + [3660, 3357], +] + +[[area.polygons]] +vertices = [ + [3615, 3358], + [3620, 3358], +] + +[[area.polygons]] +vertices = [ + [3636, 3358], + [3660, 3358], +] + +[[area.polygons]] +vertices = [ + [3637, 3359], + [3660, 3359], + [3660, 3390], + [3637, 3390], +] + +[[area.polygons]] +vertices = [ + [3615, 3365], + [3620, 3365], + [3620, 3397], + [3615, 3397], +] + +[[area.polygons]] +vertices = [ + [3636, 3365], + [3636, 3390], +] + +[[area.polygons]] +vertices = [ + [3621, 3366], + [3621, 3397], +] + +[[area.polygons]] +vertices = [ + [3635, 3366], + [3635, 3390], +] + +[[area.polygons]] +vertices = [ + [3588, 3367], + [3589, 3367], + [3589, 3383], + [3588, 3383], +] + +[[area.polygons]] +vertices = [ + [3622, 3367], + [3622, 3397], +] + +[[area.polygons]] +vertices = [ + [3634, 3367], + [3634, 3397], +] + +[[area.polygons]] +vertices = [ + [3623, 3368], + [3623, 3397], +] + +[[area.polygons]] +vertices = [ + [3633, 3368], + [3633, 3397], +] + +[[area.polygons]] +vertices = [ + [3624, 3369], + [3624, 3397], +] + +[[area.polygons]] +vertices = [ + [3632, 3369], + [3632, 3397], +] + +[[area.polygons]] +vertices = [ + [3625, 3370], + [3631, 3370], + [3631, 3397], + [3625, 3397], +] + +[[area.polygons]] +vertices = [ + [3593, 3384], + [3593, 3397], +] diff --git a/.data/raw-cache/map/area/darkness_in_the_depths.toml b/.data/raw-cache/map/area/darkness_in_the_depths.toml new file mode 100644 index 000000000..715041b42 --- /dev/null +++ b/.data/raw-cache/map/area/darkness_in_the_depths.toml @@ -0,0 +1,16 @@ +# Darkness in the Depths music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Darkness in the Depths. +# Bounds x 1408-1471, z 10048-10111; 4096 tiles as 1 rectangles. + +[[area]] +name = "Darkness in the Depths" +area_id = "area.darkness_in_the_depths" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1408, 10048], + [1471, 10048], + [1471, 10111], + [1408, 10111], +] diff --git a/.data/raw-cache/map/area/davy_jones_locker.toml b/.data/raw-cache/map/area/davy_jones_locker.toml new file mode 100644 index 000000000..062cd0d00 --- /dev/null +++ b/.data/raw-cache/map/area/davy_jones_locker.toml @@ -0,0 +1,16 @@ +# Davy Jones' Locker music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Davy Jones' Locker. +# Bounds x 2944-3007, z 9472-9535; 4096 tiles as 1 rectangles. + +[[area]] +name = "Davy Jones' Locker" +area_id = "area.davy_jones_locker" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2944, 9472], + [3007, 9472], + [3007, 9535], + [2944, 9535], +] diff --git a/.data/raw-cache/map/area/dead_quiet.toml b/.data/raw-cache/map/area/dead_quiet.toml new file mode 100644 index 000000000..de03861f7 --- /dev/null +++ b/.data/raw-cache/map/area/dead_quiet.toml @@ -0,0 +1,56 @@ +# Dead Quiet music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dead Quiet. +# Bounds x 1984-2431, z 4992-5055; 9216 tiles as 6 rectangles. + +[[area]] +name = "Dead Quiet" +area_id = "area.dead_quiet" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2048, 4992], + [2079, 4992], + [2079, 5023], + [2048, 5023], +] + +[[area.polygons]] +vertices = [ + [2368, 4992], + [2431, 4992], + [2431, 5055], + [2368, 5055], +] + +[[area.polygons]] +vertices = [ + [1984, 5024], + [2015, 5024], + [2015, 5055], + [1984, 5055], +] + +[[area.polygons]] +vertices = [ + [2176, 5024], + [2207, 5024], + [2207, 5055], + [2176, 5055], +] + +[[area.polygons]] +vertices = [ + [2240, 5024], + [2271, 5024], + [2271, 5055], + [2240, 5055], +] + +[[area.polygons]] +vertices = [ + [2304, 5024], + [2335, 5024], + [2335, 5055], + [2304, 5055], +] diff --git a/.data/raw-cache/map/area/dead_quiet_2.toml b/.data/raw-cache/map/area/dead_quiet_2.toml new file mode 100644 index 000000000..078da6031 --- /dev/null +++ b/.data/raw-cache/map/area/dead_quiet_2.toml @@ -0,0 +1,577 @@ +# Dead Quiet music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Stagnant; Waterlogged; Dead Quiet. +# Bounds x 3395-3583, z 3324-3469; 19665 tiles as 92 rectangles. + +[[area]] +name = "Dead Quiet" +area_id = "area.dead_quiet_2" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3426, 3324], + [3455, 3324], + [3455, 3330], + [3426, 3330], +] + +[[area.polygons]] +vertices = [ + [3489, 3324], + [3513, 3324], + [3513, 3461], + [3489, 3461], +] + +[[area.polygons]] +vertices = [ + [3425, 3325], + [3425, 3457], +] + +[[area.polygons]] +vertices = [ + [3487, 3325], + [3488, 3325], + [3488, 3461], + [3487, 3461], +] + +[[area.polygons]] +vertices = [ + [3424, 3326], + [3424, 3458], +] + +[[area.polygons]] +vertices = [ + [3485, 3326], + [3486, 3326], + [3486, 3461], + [3485, 3461], +] + +[[area.polygons]] +vertices = [ + [3423, 3327], + [3423, 3458], +] + +[[area.polygons]] +vertices = [ + [3483, 3327], + [3484, 3327], + [3484, 3462], + [3483, 3462], +] + +[[area.polygons]] +vertices = [ + [3422, 3328], + [3422, 3458], +] + +[[area.polygons]] +vertices = [ + [3456, 3328], + [3482, 3328], + [3482, 3457], + [3456, 3457], +] + +[[area.polygons]] +vertices = [ + [3421, 3329], + [3421, 3457], +] + +[[area.polygons]] +vertices = [ + [3420, 3330], + [3420, 3456], +] + +[[area.polygons]] +vertices = [ + [3419, 3331], + [3419, 3456], +] + +[[area.polygons]] +vertices = [ + [3426, 3331], + [3434, 3331], +] + +[[area.polygons]] +vertices = [ + [3447, 3331], + [3455, 3331], +] + +[[area.polygons]] +vertices = [ + [3418, 3332], + [3418, 3456], +] + +[[area.polygons]] +vertices = [ + [3426, 3332], + [3433, 3332], +] + +[[area.polygons]] +vertices = [ + [3448, 3332], + [3455, 3332], +] + +[[area.polygons]] +vertices = [ + [3417, 3333], + [3417, 3455], +] + +[[area.polygons]] +vertices = [ + [3426, 3333], + [3432, 3333], +] + +[[area.polygons]] +vertices = [ + [3449, 3333], + [3455, 3333], +] + +[[area.polygons]] +vertices = [ + [3416, 3334], + [3416, 3455], +] + +[[area.polygons]] +vertices = [ + [3426, 3334], + [3431, 3334], + [3431, 3455], + [3426, 3455], +] + +[[area.polygons]] +vertices = [ + [3450, 3334], + [3455, 3334], +] + +[[area.polygons]] +vertices = [ + [3415, 3335], + [3415, 3455], +] + +[[area.polygons]] +vertices = [ + [3451, 3335], + [3455, 3335], + [3455, 3455], + [3451, 3455], +] + +[[area.polygons]] +vertices = [ + [3414, 3336], + [3414, 3455], +] + +[[area.polygons]] +vertices = [ + [3413, 3337], + [3413, 3454], +] + +[[area.polygons]] +vertices = [ + [3412, 3338], + [3412, 3453], +] + +[[area.polygons]] +vertices = [ + [3411, 3339], + [3411, 3452], +] + +[[area.polygons]] +vertices = [ + [3410, 3340], + [3410, 3451], +] + +[[area.polygons]] +vertices = [ + [3450, 3340], + [3450, 3456], +] + +[[area.polygons]] +vertices = [ + [3409, 3341], + [3409, 3450], +] + +[[area.polygons]] +vertices = [ + [3432, 3341], + [3432, 3455], +] + +[[area.polygons]] +vertices = [ + [3449, 3341], + [3449, 3456], +] + +[[area.polygons]] +vertices = [ + [3408, 3342], + [3408, 3449], +] + +[[area.polygons]] +vertices = [ + [3433, 3342], + [3433, 3455], +] + +[[area.polygons]] +vertices = [ + [3448, 3342], + [3448, 3456], +] + +[[area.polygons]] +vertices = [ + [3514, 3342], + [3519, 3342], + [3519, 3456], + [3514, 3456], +] + +[[area.polygons]] +vertices = [ + [3407, 3343], + [3407, 3448], +] + +[[area.polygons]] +vertices = [ + [3434, 3343], + [3434, 3455], +] + +[[area.polygons]] +vertices = [ + [3447, 3343], + [3447, 3455], +] + +[[area.polygons]] +vertices = [ + [3406, 3344], + [3406, 3447], +] + +[[area.polygons]] +vertices = [ + [3435, 3344], + [3435, 3455], +] + +[[area.polygons]] +vertices = [ + [3445, 3344], + [3446, 3344], + [3446, 3455], + [3445, 3455], +] + +[[area.polygons]] +vertices = [ + [3405, 3345], + [3405, 3446], +] + +[[area.polygons]] +vertices = [ + [3436, 3345], + [3436, 3456], +] + +[[area.polygons]] +vertices = [ + [3443, 3345], + [3444, 3345], + [3444, 3457], + [3443, 3457], +] + +[[area.polygons]] +vertices = [ + [3404, 3346], + [3404, 3445], +] + +[[area.polygons]] +vertices = [ + [3437, 3346], + [3442, 3346], + [3442, 3456], + [3437, 3456], +] + +[[area.polygons]] +vertices = [ + [3403, 3347], + [3403, 3444], +] + +[[area.polygons]] +vertices = [ + [3402, 3348], + [3402, 3443], +] + +[[area.polygons]] +vertices = [ + [3401, 3349], + [3401, 3394], +] + +[[area.polygons]] +vertices = [ + [3400, 3350], + [3400, 3393], +] + +[[area.polygons]] +vertices = [ + [3399, 3351], + [3399, 3392], +] + +[[area.polygons]] +vertices = [ + [3398, 3352], + [3398, 3391], +] + +[[area.polygons]] +vertices = [ + [3397, 3353], + [3397, 3390], +] + +[[area.polygons]] +vertices = [ + [3396, 3354], + [3396, 3389], +] + +[[area.polygons]] +vertices = [ + [3395, 3355], + [3395, 3388], +] + +[[area.polygons]] +vertices = [ + [3520, 3392], + [3583, 3392], + [3583, 3453], + [3520, 3453], +] + +[[area.polygons]] +vertices = [ + [3520, 3454], + [3526, 3454], +] + +[[area.polygons]] +vertices = [ + [3552, 3454], + [3583, 3454], +] + +[[area.polygons]] +vertices = [ + [3520, 3455], + [3525, 3455], +] + +[[area.polygons]] +vertices = [ + [3553, 3455], + [3583, 3455], +] + +[[area.polygons]] +vertices = [ + [3426, 3456], + [3430, 3456], +] + +[[area.polygons]] +vertices = [[3445, 3456]] + +[[area.polygons]] +vertices = [ + [3451, 3456], + [3452, 3456], +] + +[[area.polygons]] +vertices = [[3455, 3456]] + +[[area.polygons]] +vertices = [ + [3520, 3456], + [3524, 3456], +] + +[[area.polygons]] +vertices = [ + [3554, 3456], + [3583, 3456], +] + +[[area.polygons]] +vertices = [ + [3441, 3457], + [3442, 3457], +] + +[[area.polygons]] +vertices = [ + [3514, 3457], + [3518, 3457], +] + +[[area.polygons]] +vertices = [ + [3457, 3458], + [3482, 3458], +] + +[[area.polygons]] +vertices = [ + [3514, 3458], + [3517, 3458], +] + +[[area.polygons]] +vertices = [ + [3458, 3459], + [3482, 3459], +] + +[[area.polygons]] +vertices = [ + [3514, 3459], + [3516, 3459], +] + +[[area.polygons]] +vertices = [ + [3459, 3460], + [3482, 3460], +] + +[[area.polygons]] +vertices = [ + [3514, 3460], + [3515, 3460], +] + +[[area.polygons]] +vertices = [ + [3460, 3461], + [3482, 3461], +] + +[[area.polygons]] +vertices = [[3514, 3461]] + +[[area.polygons]] +vertices = [ + [3461, 3462], + [3482, 3462], +] + +[[area.polygons]] +vertices = [ + [3503, 3462], + [3513, 3462], +] + +[[area.polygons]] +vertices = [ + [3462, 3463], + [3483, 3463], +] + +[[area.polygons]] +vertices = [ + [3504, 3463], + [3512, 3463], +] + +[[area.polygons]] +vertices = [ + [3463, 3464], + [3482, 3464], +] + +[[area.polygons]] +vertices = [ + [3505, 3464], + [3511, 3464], +] + +[[area.polygons]] +vertices = [ + [3464, 3465], + [3481, 3465], +] + +[[area.polygons]] +vertices = [ + [3508, 3465], + [3510, 3465], +] + +[[area.polygons]] +vertices = [ + [3465, 3466], + [3480, 3466], +] + +[[area.polygons]] +vertices = [ + [3466, 3467], + [3479, 3467], +] + +[[area.polygons]] +vertices = [ + [3467, 3468], + [3478, 3468], +] + +[[area.polygons]] +vertices = [ + [3468, 3469], + [3477, 3469], +] diff --git a/.data/raw-cache/map/area/deadlands.toml b/.data/raw-cache/map/area/deadlands.toml new file mode 100644 index 000000000..91fe88c5e --- /dev/null +++ b/.data/raw-cache/map/area/deadlands.toml @@ -0,0 +1,512 @@ +# Deadlands music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Deadlands. +# Bounds x 3511-3719, z 3381-3520; 16679 tiles as 82 rectangles. + +[[area]] +name = "Deadlands" +area_id = "area.deadlands" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3672, 3381], + [3696, 3381], + [3696, 3453], + [3672, 3453], +] + +[[area.polygons]] +vertices = [ + [3664, 3386], + [3671, 3386], + [3671, 3453], + [3664, 3453], +] + +[[area.polygons]] +vertices = [ + [3697, 3389], + [3697, 3453], +] + +[[area.polygons]] +vertices = [ + [3698, 3390], + [3698, 3453], +] + +[[area.polygons]] +vertices = [ + [3699, 3391], + [3699, 3453], +] + +[[area.polygons]] +vertices = [ + [3700, 3392], + [3700, 3453], +] + +[[area.polygons]] +vertices = [ + [3701, 3393], + [3701, 3454], +] + +[[area.polygons]] +vertices = [ + [3638, 3394], + [3663, 3394], + [3663, 3454], + [3638, 3454], +] + +[[area.polygons]] +vertices = [ + [3702, 3394], + [3702, 3454], +] + +[[area.polygons]] +vertices = [ + [3703, 3395], + [3703, 3454], +] + +[[area.polygons]] +vertices = [ + [3704, 3396], + [3704, 3454], +] + +[[area.polygons]] +vertices = [ + [3705, 3397], + [3705, 3454], +] + +[[area.polygons]] +vertices = [ + [3706, 3398], + [3706, 3454], +] + +[[area.polygons]] +vertices = [ + [3707, 3399], + [3707, 3454], +] + +[[area.polygons]] +vertices = [ + [3708, 3400], + [3708, 3454], +] + +[[area.polygons]] +vertices = [ + [3584, 3401], + [3637, 3401], + [3637, 3511], + [3584, 3511], +] + +[[area.polygons]] +vertices = [ + [3709, 3401], + [3709, 3454], +] + +[[area.polygons]] +vertices = [ + [3710, 3402], + [3710, 3454], +] + +[[area.polygons]] +vertices = [ + [3711, 3403], + [3711, 3454], +] + +[[area.polygons]] +vertices = [ + [3712, 3404], + [3712, 3454], +] + +[[area.polygons]] +vertices = [ + [3713, 3405], + [3713, 3454], +] + +[[area.polygons]] +vertices = [ + [3714, 3406], + [3714, 3454], +] + +[[area.polygons]] +vertices = [ + [3715, 3407], + [3715, 3454], +] + +[[area.polygons]] +vertices = [ + [3716, 3408], + [3716, 3454], +] + +[[area.polygons]] +vertices = [ + [3717, 3409], + [3717, 3454], +] + +[[area.polygons]] +vertices = [ + [3718, 3410], + [3718, 3454], +] + +[[area.polygons]] +vertices = [ + [3719, 3411], + [3719, 3454], +] + +[[area.polygons]] +vertices = [ + [3527, 3454], + [3551, 3454], + [3551, 3517], + [3527, 3517], +] + +[[area.polygons]] +vertices = [[3664, 3454]] + +[[area.polygons]] +vertices = [ + [3674, 3454], + [3693, 3454], +] + +[[area.polygons]] +vertices = [ + [3526, 3455], + [3526, 3516], +] + +[[area.polygons]] +vertices = [ + [3552, 3455], + [3552, 3519], +] + +[[area.polygons]] +vertices = [ + [3638, 3455], + [3660, 3455], +] + +[[area.polygons]] +vertices = [ + [3675, 3455], + [3692, 3455], +] + +[[area.polygons]] +vertices = [ + [3525, 3456], + [3525, 3515], +] + +[[area.polygons]] +vertices = [ + [3553, 3456], + [3553, 3519], +] + +[[area.polygons]] +vertices = [ + [3638, 3456], + [3659, 3456], +] + +[[area.polygons]] +vertices = [ + [3519, 3457], + [3524, 3457], + [3524, 3509], + [3519, 3509], +] + +[[area.polygons]] +vertices = [ + [3554, 3457], + [3583, 3457], + [3583, 3518], + [3554, 3518], +] + +[[area.polygons]] +vertices = [ + [3638, 3457], + [3650, 3457], +] + +[[area.polygons]] +vertices = [ + [3518, 3458], + [3518, 3473], +] + +[[area.polygons]] +vertices = [ + [3638, 3458], + [3649, 3458], + [3649, 3503], + [3638, 3503], +] + +[[area.polygons]] +vertices = [ + [3517, 3459], + [3517, 3472], +] + +[[area.polygons]] +vertices = [ + [3516, 3460], + [3516, 3471], +] + +[[area.polygons]] +vertices = [ + [3515, 3461], + [3515, 3470], +] + +[[area.polygons]] +vertices = [ + [3514, 3462], + [3514, 3469], +] + +[[area.polygons]] +vertices = [ + [3513, 3463], + [3513, 3468], +] + +[[area.polygons]] +vertices = [ + [3512, 3464], + [3512, 3467], +] + +[[area.polygons]] +vertices = [ + [3511, 3465], + [3511, 3466], +] + +[[area.polygons]] +vertices = [ + [3650, 3472], + [3650, 3503], +] + +[[area.polygons]] +vertices = [ + [3651, 3473], + [3651, 3503], +] + +[[area.polygons]] +vertices = [ + [3652, 3474], + [3652, 3503], +] + +[[area.polygons]] +vertices = [ + [3518, 3484], + [3518, 3508], +] + +[[area.polygons]] +vertices = [ + [3517, 3485], + [3517, 3507], +] + +[[area.polygons]] +vertices = [ + [3516, 3486], + [3516, 3507], +] + +[[area.polygons]] +vertices = [ + [3515, 3487], + [3515, 3507], +] + +[[area.polygons]] +vertices = [ + [3514, 3488], + [3514, 3507], +] + +[[area.polygons]] +vertices = [ + [3638, 3504], + [3647, 3504], + [3647, 3517], + [3638, 3517], +] + +[[area.polygons]] +vertices = [ + [3520, 3510], + [3524, 3510], +] + +[[area.polygons]] +vertices = [ + [3521, 3511], + [3524, 3511], +] + +[[area.polygons]] +vertices = [ + [3522, 3512], + [3524, 3512], +] + +[[area.polygons]] +vertices = [ + [3584, 3512], + [3600, 3512], +] + +[[area.polygons]] +vertices = [ + [3606, 3512], + [3637, 3512], +] + +[[area.polygons]] +vertices = [ + [3523, 3513], + [3524, 3513], +] + +[[area.polygons]] +vertices = [ + [3584, 3513], + [3599, 3513], +] + +[[area.polygons]] +vertices = [ + [3607, 3513], + [3637, 3513], +] + +[[area.polygons]] +vertices = [[3524, 3514]] + +[[area.polygons]] +vertices = [ + [3584, 3514], + [3598, 3514], +] + +[[area.polygons]] +vertices = [ + [3608, 3514], + [3637, 3514], +] + +[[area.polygons]] +vertices = [ + [3584, 3515], + [3597, 3515], +] + +[[area.polygons]] +vertices = [ + [3609, 3515], + [3637, 3515], +] + +[[area.polygons]] +vertices = [ + [3584, 3516], + [3596, 3516], +] + +[[area.polygons]] +vertices = [ + [3610, 3516], + [3637, 3516], +] + +[[area.polygons]] +vertices = [ + [3584, 3517], + [3595, 3517], +] + +[[area.polygons]] +vertices = [ + [3611, 3517], + [3637, 3517], +] + +[[area.polygons]] +vertices = [ + [3528, 3518], + [3551, 3518], +] + +[[area.polygons]] +vertices = [ + [3584, 3518], + [3594, 3518], +] + +[[area.polygons]] +vertices = [ + [3612, 3518], + [3625, 3518], +] + +[[area.polygons]] +vertices = [ + [3529, 3519], + [3551, 3519], +] + +[[area.polygons]] +vertices = [ + [3554, 3519], + [3558, 3519], +] + +[[area.polygons]] +vertices = [ + [3613, 3519], + [3623, 3519], +] + +[[area.polygons]] +vertices = [ + [3614, 3520], + [3621, 3520], +] diff --git a/.data/raw-cache/map/area/deep_down.toml b/.data/raw-cache/map/area/deep_down.toml new file mode 100644 index 000000000..f364c3f71 --- /dev/null +++ b/.data/raw-cache/map/area/deep_down.toml @@ -0,0 +1,24 @@ +# Deep Down music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Deep Down. +# Bounds x 2688-2815, z 4480-4607; 12288 tiles as 2 rectangles. + +[[area]] +name = "Deep Down" +area_id = "area.deep_down" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 4480], + [2751, 4480], + [2751, 4607], + [2688, 4607], +] + +[[area.polygons]] +vertices = [ + [2752, 4544], + [2815, 4544], + [2815, 4607], + [2752, 4607], +] diff --git a/.data/raw-cache/map/area/demon_slayer.toml b/.data/raw-cache/map/area/demon_slayer.toml new file mode 100644 index 000000000..9405ba8e2 --- /dev/null +++ b/.data/raw-cache/map/area/demon_slayer.toml @@ -0,0 +1,14 @@ +# The stone circle south of Varrock where Delrith is summoned. Walking in here with Silverlight +# during Demon Slayer moves the player into a private copy of the circle for the fight. +[[area]] +name = "Demon Slayer stone circle" +area_id = "area.demon_slayer_stone_circle" +levels = [0] + +[[area.polygons]] +vertices = [ + [3220, 3361], + [3236, 3361], + [3236, 3378], + [3220, 3378], +] diff --git a/.data/raw-cache/map/area/devils_may_care.toml b/.data/raw-cache/map/area/devils_may_care.toml new file mode 100644 index 000000000..63bcac377 --- /dev/null +++ b/.data/raw-cache/map/area/devils_may_care.toml @@ -0,0 +1,16 @@ +# Devils May Care music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Devils May Care. +# Bounds x 2304-2431, z 9408-9471; 8192 tiles as 1 rectangles. + +[[area]] +name = "Devils May Care" +area_id = "area.devils_may_care" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2304, 9408], + [2431, 9408], + [2431, 9471], + [2304, 9471], +] diff --git a/.data/raw-cache/map/area/digsite.toml b/.data/raw-cache/map/area/digsite.toml new file mode 100644 index 000000000..ed3fbc3a0 --- /dev/null +++ b/.data/raw-cache/map/area/digsite.toml @@ -0,0 +1,248 @@ +# Digsite music music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lullaby; Venture. +# Bounds x 3296-3401, z 3329-3457; 11660 tiles as 37 rectangles. + +[[area]] +name = "Digsite music" +area_id = "area.digsite" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3312, 3329], + [3315, 3329], + [3315, 3352], + [3312, 3352], +] + +[[area.polygons]] +vertices = [ + [3316, 3330], + [3318, 3330], + [3318, 3457], + [3316, 3457], +] + +[[area.polygons]] +vertices = [ + [3322, 3330], + [3379, 3330], + [3379, 3457], + [3322, 3457], +] + +[[area.polygons]] +vertices = [ + [3319, 3331], + [3321, 3331], + [3321, 3457], + [3319, 3457], +] + +[[area.polygons]] +vertices = [ + [3313, 3353], + [3315, 3353], + [3315, 3457], + [3313, 3457], +] + +[[area.polygons]] +vertices = [ + [3312, 3355], + [3312, 3457], +] + +[[area.polygons]] +vertices = [ + [3311, 3359], + [3311, 3455], +] + +[[area.polygons]] +vertices = [ + [3304, 3360], + [3310, 3360], + [3310, 3362], + [3304, 3362], +] + +[[area.polygons]] +vertices = [ + [3305, 3363], + [3310, 3363], + [3310, 3455], + [3305, 3455], +] + +[[area.polygons]] +vertices = [ + [3304, 3365], + [3304, 3455], +] + +[[area.polygons]] +vertices = [ + [3380, 3374], + [3380, 3457], +] + +[[area.polygons]] +vertices = [ + [3381, 3375], + [3381, 3457], +] + +[[area.polygons]] +vertices = [ + [3382, 3376], + [3382, 3457], +] + +[[area.polygons]] +vertices = [ + [3383, 3377], + [3383, 3457], +] + +[[area.polygons]] +vertices = [ + [3384, 3378], + [3384, 3457], +] + +[[area.polygons]] +vertices = [ + [3385, 3379], + [3385, 3457], +] + +[[area.polygons]] +vertices = [ + [3386, 3380], + [3386, 3457], +] + +[[area.polygons]] +vertices = [ + [3387, 3381], + [3387, 3457], +] + +[[area.polygons]] +vertices = [ + [3388, 3382], + [3388, 3457], +] + +[[area.polygons]] +vertices = [ + [3303, 3383], + [3303, 3455], +] + +[[area.polygons]] +vertices = [ + [3389, 3383], + [3389, 3457], +] + +[[area.polygons]] +vertices = [ + [3296, 3384], + [3302, 3384], + [3302, 3437], + [3296, 3437], +] + +[[area.polygons]] +vertices = [ + [3390, 3384], + [3390, 3457], +] + +[[area.polygons]] +vertices = [ + [3391, 3385], + [3391, 3457], +] + +[[area.polygons]] +vertices = [ + [3392, 3386], + [3392, 3457], +] + +[[area.polygons]] +vertices = [ + [3393, 3387], + [3393, 3457], +] + +[[area.polygons]] +vertices = [ + [3394, 3388], + [3394, 3457], +] + +[[area.polygons]] +vertices = [ + [3395, 3389], + [3395, 3457], +] + +[[area.polygons]] +vertices = [ + [3396, 3390], + [3396, 3457], +] + +[[area.polygons]] +vertices = [ + [3397, 3391], + [3397, 3457], +] + +[[area.polygons]] +vertices = [ + [3398, 3392], + [3398, 3457], +] + +[[area.polygons]] +vertices = [ + [3399, 3393], + [3399, 3457], +] + +[[area.polygons]] +vertices = [ + [3400, 3394], + [3400, 3457], +] + +[[area.polygons]] +vertices = [ + [3401, 3395], + [3401, 3457], +] + +[[area.polygons]] +vertices = [ + [3297, 3438], + [3302, 3438], + [3302, 3455], + [3297, 3455], +] + +[[area.polygons]] +vertices = [ + [3296, 3440], + [3296, 3448], +] + +[[area.polygons]] +vertices = [ + [3296, 3451], + [3296, 3455], +] diff --git a/.data/raw-cache/map/area/dimension_x.toml b/.data/raw-cache/map/area/dimension_x.toml new file mode 100644 index 000000000..48674dc11 --- /dev/null +++ b/.data/raw-cache/map/area/dimension_x.toml @@ -0,0 +1,16 @@ +# Dimension X music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dimension X. +# Bounds x 3008-3071, z 5312-5375; 4096 tiles as 1 rectangles. + +[[area]] +name = "Dimension X" +area_id = "area.dimension_x" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 5312], + [3071, 5312], + [3071, 5375], + [3008, 5375], +] diff --git a/.data/raw-cache/map/area/distant_land.toml b/.data/raw-cache/map/area/distant_land.toml new file mode 100644 index 000000000..75c1f2099 --- /dev/null +++ b/.data/raw-cache/map/area/distant_land.toml @@ -0,0 +1,392 @@ +# Distant Land music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Distant Land. +# Bounds x 2048-3584, z 3160-5055; 14941 tiles as 62 rectangles. + +[[area]] +name = "Distant Land" +area_id = "area.distant_land" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3495, 3160], + [3584, 3160], + [3584, 3243], + [3495, 3243], +] + +[[area.polygons]] +vertices = [ + [3494, 3161], + [3494, 3244], +] + +[[area.polygons]] +vertices = [ + [3493, 3162], + [3493, 3244], +] + +[[area.polygons]] +vertices = [ + [3492, 3163], + [3492, 3244], +] + +[[area.polygons]] +vertices = [ + [3491, 3164], + [3491, 3244], +] + +[[area.polygons]] +vertices = [ + [3490, 3165], + [3490, 3244], +] + +[[area.polygons]] +vertices = [ + [3489, 3166], + [3489, 3244], +] + +[[area.polygons]] +vertices = [ + [3488, 3167], + [3488, 3241], +] + +[[area.polygons]] +vertices = [ + [3487, 3168], + [3487, 3242], +] + +[[area.polygons]] +vertices = [ + [3486, 3169], + [3486, 3243], +] + +[[area.polygons]] +vertices = [ + [3485, 3170], + [3485, 3243], +] + +[[area.polygons]] +vertices = [ + [3484, 3171], + [3484, 3243], +] + +[[area.polygons]] +vertices = [ + [3483, 3172], + [3483, 3243], +] + +[[area.polygons]] +vertices = [ + [3482, 3173], + [3482, 3243], +] + +[[area.polygons]] +vertices = [ + [3481, 3174], + [3481, 3244], +] + +[[area.polygons]] +vertices = [ + [3480, 3175], + [3480, 3243], +] + +[[area.polygons]] +vertices = [ + [3479, 3176], + [3479, 3243], +] + +[[area.polygons]] +vertices = [ + [3478, 3177], + [3478, 3242], +] + +[[area.polygons]] +vertices = [ + [3477, 3178], + [3477, 3242], +] + +[[area.polygons]] +vertices = [ + [3476, 3179], + [3476, 3240], +] + +[[area.polygons]] +vertices = [ + [3475, 3180], + [3475, 3238], +] + +[[area.polygons]] +vertices = [ + [3474, 3181], + [3474, 3218], +] + +[[area.polygons]] +vertices = [ + [3473, 3182], + [3473, 3217], +] + +[[area.polygons]] +vertices = [ + [3472, 3183], + [3472, 3216], +] + +[[area.polygons]] +vertices = [ + [3471, 3184], + [3471, 3215], +] + +[[area.polygons]] +vertices = [ + [3470, 3185], + [3470, 3214], +] + +[[area.polygons]] +vertices = [ + [3469, 3186], + [3469, 3213], +] + +[[area.polygons]] +vertices = [ + [3468, 3187], + [3468, 3212], +] + +[[area.polygons]] +vertices = [ + [3467, 3188], + [3467, 3211], +] + +[[area.polygons]] +vertices = [ + [3466, 3189], + [3466, 3210], +] + +[[area.polygons]] +vertices = [ + [3465, 3190], + [3465, 3209], +] + +[[area.polygons]] +vertices = [ + [3464, 3191], + [3464, 3208], +] + +[[area.polygons]] +vertices = [ + [3463, 3192], + [3463, 3207], +] + +[[area.polygons]] +vertices = [ + [3462, 3193], + [3462, 3206], +] + +[[area.polygons]] +vertices = [ + [3461, 3194], + [3461, 3205], +] + +[[area.polygons]] +vertices = [ + [3460, 3195], + [3460, 3204], +] + +[[area.polygons]] +vertices = [ + [3459, 3196], + [3459, 3203], +] + +[[area.polygons]] +vertices = [ + [3458, 3197], + [3458, 3202], +] + +[[area.polygons]] +vertices = [ + [3457, 3198], + [3457, 3201], +] + +[[area.polygons]] +vertices = [ + [3456, 3199], + [3456, 3200], +] + +[[area.polygons]] +vertices = [ + [3474, 3220], + [3474, 3222], +] + +[[area.polygons]] +vertices = [ + [3474, 3225], + [3474, 3236], +] + +[[area.polygons]] +vertices = [ + [3473, 3228], + [3473, 3233], +] + +[[area.polygons]] +vertices = [ + [3500, 3244], + [3511, 3244], +] + +[[area.polygons]] +vertices = [ + [3519, 3244], + [3584, 3244], +] + +[[area.polygons]] +vertices = [ + [3505, 3245], + [3506, 3245], +] + +[[area.polygons]] +vertices = [ + [3521, 3245], + [3584, 3245], + [3584, 3254], + [3521, 3254], +] + +[[area.polygons]] +vertices = [ + [3520, 3248], + [3520, 3251], +] + +[[area.polygons]] +vertices = [ + [3522, 3255], + [3584, 3255], + [3584, 3256], + [3522, 3256], +] + +[[area.polygons]] +vertices = [ + [3531, 3257], + [3584, 3257], + [3584, 3259], + [3531, 3259], +] + +[[area.polygons]] +vertices = [ + [3533, 3260], + [3584, 3260], +] + +[[area.polygons]] +vertices = [ + [3536, 3261], + [3584, 3261], +] + +[[area.polygons]] +vertices = [ + [3541, 3262], + [3544, 3262], +] + +[[area.polygons]] +vertices = [ + [3549, 3262], + [3555, 3262], +] + +[[area.polygons]] +vertices = [ + [3559, 3262], + [3562, 3262], +] + +[[area.polygons]] +vertices = [ + [3565, 3262], + [3584, 3262], +] + +[[area.polygons]] +vertices = [ + [3551, 3263], + [3553, 3263], +] + +[[area.polygons]] +vertices = [ + [3567, 3263], + [3569, 3263], +] + +[[area.polygons]] +vertices = [ + [3573, 3263], + [3578, 3263], +] + +[[area.polygons]] +vertices = [ + [3581, 3263], + [3584, 3263], +] + +[[area.polygons]] +vertices = [ + [2112, 4992], + [2175, 4992], + [2175, 5055], + [2112, 5055], +] + +[[area.polygons]] +vertices = [ + [2048, 5024], + [2055, 5024], + [2055, 5055], + [2048, 5055], +] diff --git a/.data/raw-cache/map/area/distillery_hilarity.toml b/.data/raw-cache/map/area/distillery_hilarity.toml new file mode 100644 index 000000000..999e04aa0 --- /dev/null +++ b/.data/raw-cache/map/area/distillery_hilarity.toml @@ -0,0 +1,76 @@ +# Distillery Hilarity music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Distillery Hilarity. +# Bounds x 3790-3835, z 3008-3031; 995 tiles as 11 rectangles. + +[[area]] +name = "Distillery Hilarity" +area_id = "area.distillery_hilarity" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3790, 3008], + [3835, 3008], + [3835, 3021], + [3790, 3021], +] + +[[area.polygons]] +vertices = [ + [3791, 3022], + [3834, 3022], +] + +[[area.polygons]] +vertices = [ + [3792, 3023], + [3833, 3023], +] + +[[area.polygons]] +vertices = [ + [3793, 3024], + [3832, 3024], +] + +[[area.polygons]] +vertices = [ + [3794, 3025], + [3831, 3025], +] + +[[area.polygons]] +vertices = [ + [3795, 3026], + [3830, 3026], +] + +[[area.polygons]] +vertices = [ + [3796, 3027], + [3829, 3027], +] + +[[area.polygons]] +vertices = [ + [3797, 3028], + [3828, 3028], +] + +[[area.polygons]] +vertices = [ + [3798, 3029], + [3827, 3029], +] + +[[area.polygons]] +vertices = [ + [3799, 3030], + [3826, 3030], +] + +[[area.polygons]] +vertices = [ + [3799, 3031], + [3825, 3031], +] diff --git a/.data/raw-cache/map/area/dogs_of_war.toml b/.data/raw-cache/map/area/dogs_of_war.toml new file mode 100644 index 000000000..d42cb155c --- /dev/null +++ b/.data/raw-cache/map/area/dogs_of_war.toml @@ -0,0 +1,16 @@ +# Dogs of War music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dogs of War. +# Bounds x 1856-1919, z 5184-5247; 4096 tiles as 1 rectangles. + +[[area]] +name = "Dogs of War" +area_id = "area.dogs_of_war" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 5184], + [1919, 5184], + [1919, 5247], + [1856, 5247], +] diff --git a/.data/raw-cache/map/area/doorways.toml b/.data/raw-cache/map/area/doorways.toml new file mode 100644 index 000000000..de60fed93 --- /dev/null +++ b/.data/raw-cache/map/area/doorways.toml @@ -0,0 +1,2106 @@ +# Doorways music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Doorways. +# Bounds x 2084-2343, z 2767-3005; 28724 tiles as 332 rectangles. + +[[area]] +name = "Doorways" +area_id = "area.doorways" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2216, 2767], + [2217, 2767], + [2217, 2827], + [2216, 2827], +] + +[[area.polygons]] +vertices = [ + [2232, 2767], + [2232, 2829], +] + +[[area.polygons]] +vertices = [ + [2211, 2768], + [2215, 2768], + [2215, 2829], + [2211, 2829], +] + +[[area.polygons]] +vertices = [ + [2218, 2768], + [2231, 2768], + [2231, 2826], + [2218, 2826], +] + +[[area.polygons]] +vertices = [ + [2233, 2768], + [2233, 2830], +] + +[[area.polygons]] +vertices = [ + [2208, 2769], + [2210, 2769], + [2210, 2831], + [2208, 2831], +] + +[[area.polygons]] +vertices = [ + [2234, 2769], + [2234, 2831], +] + +[[area.polygons]] +vertices = [ + [2205, 2770], + [2207, 2770], + [2207, 2829], + [2205, 2829], +] + +[[area.polygons]] +vertices = [ + [2203, 2771], + [2204, 2771], + [2204, 2827], + [2203, 2827], +] + +[[area.polygons]] +vertices = [ + [2235, 2771], + [2235, 2879], +] + +[[area.polygons]] +vertices = [ + [2201, 2772], + [2202, 2772], + [2202, 2826], + [2201, 2826], +] + +[[area.polygons]] +vertices = [ + [2198, 2773], + [2200, 2773], + [2200, 2826], + [2198, 2826], +] + +[[area.polygons]] +vertices = [ + [2236, 2773], + [2236, 2879], +] + +[[area.polygons]] +vertices = [ + [2191, 2774], + [2197, 2774], + [2197, 2826], + [2191, 2826], +] + +[[area.polygons]] +vertices = [ + [2180, 2775], + [2190, 2775], + [2190, 2826], + [2180, 2826], +] + +[[area.polygons]] +vertices = [ + [2237, 2775], + [2237, 2879], +] + +[[area.polygons]] +vertices = [ + [2177, 2776], + [2179, 2776], + [2179, 2883], + [2177, 2883], +] + +[[area.polygons]] +vertices = [ + [2174, 2777], + [2176, 2777], + [2176, 2885], + [2174, 2885], +] + +[[area.polygons]] +vertices = [ + [2238, 2777], + [2242, 2777], + [2242, 2879], + [2238, 2879], +] + +[[area.polygons]] +vertices = [ + [2173, 2778], + [2173, 2886], +] + +[[area.polygons]] +vertices = [ + [2243, 2778], + [2243, 2879], +] + +[[area.polygons]] +vertices = [ + [2171, 2779], + [2172, 2779], + [2172, 2887], + [2171, 2887], +] + +[[area.polygons]] +vertices = [ + [2244, 2779], + [2245, 2779], + [2245, 2879], + [2244, 2879], +] + +[[area.polygons]] +vertices = [ + [2170, 2780], + [2170, 2888], +] + +[[area.polygons]] +vertices = [ + [2246, 2780], + [2248, 2780], + [2248, 2879], + [2246, 2879], +] + +[[area.polygons]] +vertices = [ + [2168, 2781], + [2169, 2781], + [2169, 2888], + [2168, 2888], +] + +[[area.polygons]] +vertices = [ + [2249, 2781], + [2251, 2781], + [2251, 2879], + [2249, 2879], +] + +[[area.polygons]] +vertices = [ + [2293, 2781], + [2295, 2781], + [2295, 2788], + [2293, 2788], +] + +[[area.polygons]] +vertices = [ + [2165, 2782], + [2167, 2782], + [2167, 2888], + [2165, 2888], +] + +[[area.polygons]] +vertices = [ + [2252, 2782], + [2253, 2782], + [2253, 2879], + [2252, 2879], +] + +[[area.polygons]] +vertices = [ + [2284, 2782], + [2285, 2782], + [2285, 2808], + [2284, 2808], +] + +[[area.polygons]] +vertices = [ + [2291, 2782], + [2292, 2782], + [2292, 2790], + [2291, 2790], +] + +[[area.polygons]] +vertices = [ + [2296, 2782], + [2296, 2788], +] + +[[area.polygons]] +vertices = [ + [2160, 2783], + [2164, 2783], + [2164, 2888], + [2160, 2888], +] + +[[area.polygons]] +vertices = [ + [2254, 2783], + [2254, 2879], +] + +[[area.polygons]] +vertices = [ + [2282, 2783], + [2283, 2783], + [2283, 2817], + [2282, 2817], +] + +[[area.polygons]] +vertices = [ + [2286, 2783], + [2290, 2783], + [2290, 2804], + [2286, 2804], +] + +[[area.polygons]] +vertices = [ + [2297, 2783], + [2297, 2788], +] + +[[area.polygons]] +vertices = [ + [2159, 2784], + [2159, 2888], +] + +[[area.polygons]] +vertices = [ + [2255, 2784], + [2256, 2784], + [2256, 2879], + [2255, 2879], +] + +[[area.polygons]] +vertices = [ + [2281, 2784], + [2281, 2833], +] + +[[area.polygons]] +vertices = [ + [2298, 2784], + [2298, 2787], +] + +[[area.polygons]] +vertices = [ + [2158, 2785], + [2158, 2888], +] + +[[area.polygons]] +vertices = [ + [2257, 2785], + [2257, 2879], +] + +[[area.polygons]] +vertices = [ + [2279, 2785], + [2280, 2785], + [2280, 2834], + [2279, 2834], +] + +[[area.polygons]] +vertices = [ + [2299, 2785], + [2299, 2786], +] + +[[area.polygons]] +vertices = [ + [2157, 2786], + [2157, 2888], +] + +[[area.polygons]] +vertices = [ + [2258, 2786], + [2259, 2786], + [2259, 2879], + [2258, 2879], +] + +[[area.polygons]] +vertices = [ + [2265, 2786], + [2270, 2786], + [2270, 2887], + [2265, 2887], +] + +[[area.polygons]] +vertices = [ + [2277, 2786], + [2278, 2786], + [2278, 2883], + [2277, 2883], +] + +[[area.polygons]] +vertices = [ + [2156, 2787], + [2156, 2888], +] + +[[area.polygons]] +vertices = [ + [2260, 2787], + [2264, 2787], + [2264, 2889], + [2260, 2889], +] + +[[area.polygons]] +vertices = [ + [2271, 2787], + [2276, 2787], + [2276, 2884], + [2271, 2884], +] + +[[area.polygons]] +vertices = [ + [2155, 2788], + [2155, 2888], +] + +[[area.polygons]] +vertices = [[2293, 2789]] + +[[area.polygons]] +vertices = [ + [2154, 2790], + [2154, 2888], +] + +[[area.polygons]] +vertices = [ + [2291, 2791], + [2291, 2797], +] + +[[area.polygons]] +vertices = [ + [2153, 2792], + [2153, 2888], +] + +[[area.polygons]] +vertices = [ + [2152, 2794], + [2152, 2888], +] + +[[area.polygons]] +vertices = [ + [2151, 2796], + [2151, 2888], +] + +[[area.polygons]] +vertices = [ + [2150, 2797], + [2150, 2888], +] + +[[area.polygons]] +vertices = [ + [2149, 2798], + [2149, 2888], +] + +[[area.polygons]] +vertices = [ + [2147, 2799], + [2148, 2799], + [2148, 2815], + [2147, 2815], +] + +[[area.polygons]] +vertices = [ + [2145, 2800], + [2146, 2800], + [2146, 2813], + [2145, 2813], +] + +[[area.polygons]] +vertices = [ + [2291, 2800], + [2291, 2801], +] + +[[area.polygons]] +vertices = [ + [2143, 2801], + [2144, 2801], + [2144, 2812], + [2143, 2812], +] + +[[area.polygons]] +vertices = [ + [2142, 2802], + [2142, 2812], +] + +[[area.polygons]] +vertices = [ + [2141, 2803], + [2141, 2811], +] + +[[area.polygons]] +vertices = [ + [2140, 2805], + [2140, 2808], +] + +[[area.polygons]] +vertices = [ + [2286, 2805], + [2289, 2805], +] + +[[area.polygons]] +vertices = [ + [2286, 2806], + [2288, 2806], +] + +[[area.polygons]] +vertices = [[2286, 2807]] + +[[area.polygons]] +vertices = [ + [2284, 2809], + [2284, 2815], +] + +[[area.polygons]] +vertices = [[2146, 2814]] + +[[area.polygons]] +vertices = [ + [2148, 2816], + [2148, 2888], +] + +[[area.polygons]] +vertices = [ + [2147, 2818], + [2147, 2888], +] + +[[area.polygons]] +vertices = [ + [2282, 2818], + [2282, 2832], +] + +[[area.polygons]] +vertices = [ + [2146, 2822], + [2146, 2888], +] + +[[area.polygons]] +vertices = [ + [2283, 2822], + [2283, 2831], +] + +[[area.polygons]] +vertices = [ + [2142, 2823], + [2145, 2823], + [2145, 2884], + [2142, 2884], +] + +[[area.polygons]] +vertices = [ + [2141, 2824], + [2141, 2883], +] + +[[area.polygons]] +vertices = [ + [2140, 2825], + [2140, 2882], +] + +[[area.polygons]] +vertices = [ + [2284, 2825], + [2284, 2827], +] + +[[area.polygons]] +vertices = [ + [2139, 2826], + [2139, 2881], +] + +[[area.polygons]] +vertices = [ + [2136, 2827], + [2138, 2827], + [2138, 2879], + [2136, 2879], +] + +[[area.polygons]] +vertices = [ + [2180, 2827], + [2184, 2827], +] + +[[area.polygons]] +vertices = [ + [2230, 2827], + [2231, 2827], +] + +[[area.polygons]] +vertices = [ + [2135, 2828], + [2135, 2879], +] + +[[area.polygons]] +vertices = [ + [2180, 2828], + [2183, 2828], +] + +[[area.polygons]] +vertices = [[2204, 2828]] + +[[area.polygons]] +vertices = [[2216, 2828]] + +[[area.polygons]] +vertices = [[2231, 2828]] + +[[area.polygons]] +vertices = [ + [2134, 2829], + [2134, 2879], +] + +[[area.polygons]] +vertices = [ + [2180, 2829], + [2182, 2829], +] + +[[area.polygons]] +vertices = [ + [2180, 2830], + [2181, 2830], +] + +[[area.polygons]] +vertices = [ + [2206, 2830], + [2207, 2830], +] + +[[area.polygons]] +vertices = [ + [2211, 2830], + [2214, 2830], +] + +[[area.polygons]] +vertices = [ + [2180, 2831], + [2180, 2883], +] + +[[area.polygons]] +vertices = [[2207, 2831]] + +[[area.polygons]] +vertices = [ + [2211, 2831], + [2213, 2831], +] + +[[area.polygons]] +vertices = [ + [2133, 2833], + [2133, 2879], +] + +[[area.polygons]] +vertices = [ + [2132, 2834], + [2132, 2879], +] + +[[area.polygons]] +vertices = [ + [2118, 2835], + [2125, 2835], + [2125, 2879], + [2118, 2879], +] + +[[area.polygons]] +vertices = [ + [2130, 2835], + [2131, 2835], + [2131, 2879], + [2130, 2879], +] + +[[area.polygons]] +vertices = [ + [2279, 2835], + [2279, 2883], +] + +[[area.polygons]] +vertices = [ + [2116, 2836], + [2117, 2836], + [2117, 2880], + [2116, 2880], +] + +[[area.polygons]] +vertices = [ + [2126, 2836], + [2129, 2836], + [2129, 2879], + [2126, 2879], +] + +[[area.polygons]] +vertices = [ + [2115, 2837], + [2115, 2882], +] + +[[area.polygons]] +vertices = [ + [2114, 2839], + [2114, 2883], +] + +[[area.polygons]] +vertices = [ + [2280, 2841], + [2280, 2883], +] + +[[area.polygons]] +vertices = [[2113, 2842]] + +[[area.polygons]] +vertices = [ + [2281, 2842], + [2281, 2882], +] + +[[area.polygons]] +vertices = [ + [2282, 2843], + [2282, 2882], +] + +[[area.polygons]] +vertices = [ + [2283, 2844], + [2283, 2881], +] + +[[area.polygons]] +vertices = [ + [2284, 2845], + [2286, 2845], + [2286, 2881], + [2284, 2881], +] + +[[area.polygons]] +vertices = [ + [2113, 2846], + [2113, 2968], +] + +[[area.polygons]] +vertices = [ + [2287, 2846], + [2287, 2881], +] + +[[area.polygons]] +vertices = [ + [2105, 2847], + [2112, 2847], + [2112, 2963], + [2105, 2963], +] + +[[area.polygons]] +vertices = [ + [2103, 2848], + [2104, 2848], + [2104, 2962], + [2103, 2962], +] + +[[area.polygons]] +vertices = [ + [2102, 2849], + [2102, 2962], +] + +[[area.polygons]] +vertices = [ + [2101, 2850], + [2101, 2961], +] + +[[area.polygons]] +vertices = [ + [2288, 2850], + [2288, 2881], +] + +[[area.polygons]] +vertices = [ + [2100, 2851], + [2100, 2961], +] + +[[area.polygons]] +vertices = [ + [2289, 2851], + [2289, 2881], +] + +[[area.polygons]] +vertices = [ + [2097, 2852], + [2099, 2852], + [2099, 2959], + [2097, 2959], +] + +[[area.polygons]] +vertices = [ + [2290, 2852], + [2290, 2881], +] + +[[area.polygons]] +vertices = [ + [2094, 2853], + [2096, 2853], + [2096, 2912], + [2094, 2912], +] + +[[area.polygons]] +vertices = [ + [2291, 2853], + [2292, 2853], + [2292, 2881], + [2291, 2881], +] + +[[area.polygons]] +vertices = [ + [2092, 2854], + [2093, 2854], + [2093, 2908], + [2092, 2908], +] + +[[area.polygons]] +vertices = [ + [2181, 2854], + [2181, 2882], +] + +[[area.polygons]] +vertices = [ + [2234, 2854], + [2234, 2879], +] + +[[area.polygons]] +vertices = [ + [2293, 2854], + [2295, 2854], + [2295, 2881], + [2293, 2881], +] + +[[area.polygons]] +vertices = [ + [2091, 2855], + [2091, 2907], +] + +[[area.polygons]] +vertices = [ + [2182, 2855], + [2182, 2882], +] + +[[area.polygons]] +vertices = [ + [2233, 2855], + [2233, 2879], +] + +[[area.polygons]] +vertices = [ + [2296, 2855], + [2297, 2855], + [2297, 2881], + [2296, 2881], +] + +[[area.polygons]] +vertices = [ + [2090, 2856], + [2090, 2906], +] + +[[area.polygons]] +vertices = [ + [2298, 2856], + [2300, 2856], + [2300, 2910], + [2298, 2910], +] + +[[area.polygons]] +vertices = [ + [2089, 2857], + [2089, 2880], +] + +[[area.polygons]] +vertices = [ + [2183, 2857], + [2183, 2881], +] + +[[area.polygons]] +vertices = [ + [2232, 2857], + [2232, 2879], +] + +[[area.polygons]] +vertices = [ + [2301, 2857], + [2301, 2916], +] + +[[area.polygons]] +vertices = [ + [2088, 2858], + [2088, 2878], +] + +[[area.polygons]] +vertices = [ + [2231, 2858], + [2231, 2879], +] + +[[area.polygons]] +vertices = [ + [2302, 2858], + [2302, 2918], +] + +[[area.polygons]] +vertices = [ + [2086, 2859], + [2087, 2859], + [2087, 2871], + [2086, 2871], +] + +[[area.polygons]] +vertices = [ + [2184, 2859], + [2184, 2881], +] + +[[area.polygons]] +vertices = [ + [2303, 2859], + [2307, 2859], + [2307, 2920], + [2303, 2920], +] + +[[area.polygons]] +vertices = [ + [2085, 2860], + [2085, 2870], +] + +[[area.polygons]] +vertices = [ + [2185, 2860], + [2185, 2880], +] + +[[area.polygons]] +vertices = [ + [2230, 2860], + [2230, 2879], +] + +[[area.polygons]] +vertices = [ + [2308, 2860], + [2309, 2860], + [2309, 2968], + [2308, 2968], +] + +[[area.polygons]] +vertices = [ + [2310, 2861], + [2310, 2967], +] + +[[area.polygons]] +vertices = [ + [2084, 2862], + [2084, 2866], +] + +[[area.polygons]] +vertices = [ + [2186, 2862], + [2186, 2880], +] + +[[area.polygons]] +vertices = [ + [2229, 2862], + [2229, 2879], +] + +[[area.polygons]] +vertices = [ + [2311, 2862], + [2311, 2966], +] + +[[area.polygons]] +vertices = [ + [2187, 2863], + [2187, 2879], +] + +[[area.polygons]] +vertices = [ + [2228, 2863], + [2228, 2879], +] + +[[area.polygons]] +vertices = [ + [2312, 2863], + [2312, 2966], +] + +[[area.polygons]] +vertices = [ + [2313, 2865], + [2313, 2965], +] + +[[area.polygons]] +vertices = [ + [2320, 2865], + [2326, 2865], + [2326, 2942], + [2320, 2942], +] + +[[area.polygons]] +vertices = [ + [2314, 2866], + [2319, 2866], + [2319, 2963], + [2314, 2963], +] + +[[area.polygons]] +vertices = [ + [2327, 2866], + [2327, 2877], +] + +[[area.polygons]] +vertices = [ + [2328, 2867], + [2328, 2876], +] + +[[area.polygons]] +vertices = [ + [2329, 2868], + [2329, 2875], +] + +[[area.polygons]] +vertices = [ + [2087, 2872], + [2087, 2873], +] + +[[area.polygons]] +vertices = [ + [2330, 2872], + [2330, 2874], +] + +[[area.polygons]] +vertices = [[2138, 2880]] + +[[area.polygons]] +vertices = [[2116, 2881]] + +[[area.polygons]] +vertices = [ + [2327, 2881], + [2328, 2881], + [2328, 2935], + [2327, 2935], +] + +[[area.polygons]] +vertices = [ + [2329, 2882], + [2329, 2934], +] + +[[area.polygons]] +vertices = [ + [2330, 2883], + [2330, 2934], +] + +[[area.polygons]] +vertices = [ + [2177, 2884], + [2178, 2884], +] + +[[area.polygons]] +vertices = [[2277, 2884]] + +[[area.polygons]] +vertices = [ + [2331, 2884], + [2331, 2933], +] + +[[area.polygons]] +vertices = [ + [2143, 2885], + [2145, 2885], +] + +[[area.polygons]] +vertices = [ + [2271, 2885], + [2275, 2885], +] + +[[area.polygons]] +vertices = [ + [2332, 2885], + [2332, 2933], +] + +[[area.polygons]] +vertices = [ + [2089, 2886], + [2089, 2905], +] + +[[area.polygons]] +vertices = [ + [2144, 2886], + [2145, 2886], +] + +[[area.polygons]] +vertices = [[2174, 2886]] + +[[area.polygons]] +vertices = [ + [2271, 2886], + [2273, 2886], +] + +[[area.polygons]] +vertices = [ + [2088, 2887], + [2088, 2903], +] + +[[area.polygons]] +vertices = [[2145, 2887]] + +[[area.polygons]] +vertices = [[2271, 2887]] + +[[area.polygons]] +vertices = [ + [2333, 2887], + [2333, 2932], +] + +[[area.polygons]] +vertices = [ + [2265, 2888], + [2268, 2888], +] + +[[area.polygons]] +vertices = [ + [2087, 2889], + [2087, 2898], +] + +[[area.polygons]] +vertices = [ + [2265, 2889], + [2266, 2889], +] + +[[area.polygons]] +vertices = [ + [2334, 2889], + [2334, 2894], +] + +[[area.polygons]] +vertices = [ + [2086, 2892], + [2086, 2897], +] + +[[area.polygons]] +vertices = [ + [2334, 2900], + [2334, 2931], +] + +[[area.polygons]] +vertices = [ + [2335, 2901], + [2335, 2931], +] + +[[area.polygons]] +vertices = [ + [2336, 2903], + [2336, 2920], +] + +[[area.polygons]] +vertices = [ + [2337, 2906], + [2337, 2918], +] + +[[area.polygons]] +vertices = [ + [2338, 2908], + [2338, 2918], +] + +[[area.polygons]] +vertices = [ + [2093, 2909], + [2093, 2910], +] + +[[area.polygons]] +vertices = [ + [2339, 2909], + [2339, 2918], +] + +[[area.polygons]] +vertices = [ + [2340, 2910], + [2341, 2910], + [2341, 2915], + [2340, 2915], +] + +[[area.polygons]] +vertices = [ + [2299, 2911], + [2300, 2911], + [2300, 2912], + [2299, 2912], +] + +[[area.polygons]] +vertices = [ + [2342, 2911], + [2342, 2914], +] + +[[area.polygons]] +vertices = [ + [2343, 2912], + [2343, 2913], +] + +[[area.polygons]] +vertices = [ + [2095, 2913], + [2096, 2913], + [2096, 2916], + [2095, 2916], +] + +[[area.polygons]] +vertices = [ + [2300, 2913], + [2300, 2914], +] + +[[area.polygons]] +vertices = [ + [2340, 2916], + [2340, 2917], +] + +[[area.polygons]] +vertices = [ + [2096, 2917], + [2096, 2918], +] + +[[area.polygons]] +vertices = [ + [2304, 2921], + [2307, 2921], + [2307, 2968], + [2304, 2968], +] + +[[area.polygons]] +vertices = [ + [2096, 2923], + [2096, 2958], +] + +[[area.polygons]] +vertices = [ + [2095, 2925], + [2095, 2942], +] + +[[area.polygons]] +vertices = [ + [2336, 2926], + [2336, 2929], +] + +[[area.polygons]] +vertices = [ + [2094, 2927], + [2094, 2941], +] + +[[area.polygons]] +vertices = [ + [2093, 2929], + [2093, 2940], +] + +[[area.polygons]] +vertices = [ + [2092, 2933], + [2092, 2939], +] + +[[area.polygons]] +vertices = [[2091, 2935]] + +[[area.polygons]] +vertices = [ + [2327, 2936], + [2327, 2939], +] + +[[area.polygons]] +vertices = [ + [2245, 2937], + [2267, 2937], + [2267, 2979], + [2245, 2979], +] + +[[area.polygons]] +vertices = [ + [2243, 2938], + [2244, 2938], + [2244, 2981], + [2243, 2981], +] + +[[area.polygons]] +vertices = [ + [2268, 2938], + [2268, 2984], +] + +[[area.polygons]] +vertices = [ + [2241, 2939], + [2242, 2939], + [2242, 2982], + [2241, 2982], +] + +[[area.polygons]] +vertices = [ + [2269, 2939], + [2269, 2984], +] + +[[area.polygons]] +vertices = [ + [2114, 2940], + [2156, 2940], + [2156, 2969], + [2114, 2969], +] + +[[area.polygons]] +vertices = [ + [2239, 2940], + [2240, 2940], + [2240, 2982], + [2239, 2982], +] + +[[area.polygons]] +vertices = [ + [2270, 2940], + [2270, 2983], +] + +[[area.polygons]] +vertices = [ + [2157, 2941], + [2158, 2941], + [2158, 2986], + [2157, 2986], +] + +[[area.polygons]] +vertices = [ + [2237, 2941], + [2238, 2941], + [2238, 2983], + [2237, 2983], +] + +[[area.polygons]] +vertices = [ + [2271, 2941], + [2271, 2982], +] + +[[area.polygons]] +vertices = [ + [2159, 2942], + [2160, 2942], + [2160, 2986], + [2159, 2986], +] + +[[area.polygons]] +vertices = [ + [2235, 2942], + [2236, 2942], + [2236, 2983], + [2235, 2983], +] + +[[area.polygons]] +vertices = [ + [2272, 2942], + [2272, 2981], +] + +[[area.polygons]] +vertices = [ + [2161, 2943], + [2162, 2943], + [2162, 2986], + [2161, 2986], +] + +[[area.polygons]] +vertices = [ + [2233, 2943], + [2234, 2943], + [2234, 2983], + [2233, 2983], +] + +[[area.polygons]] +vertices = [ + [2273, 2943], + [2273, 2978], +] + +[[area.polygons]] +vertices = [ + [2320, 2943], + [2325, 2943], + [2325, 2944], + [2320, 2944], +] + +[[area.polygons]] +vertices = [ + [2163, 2944], + [2232, 2944], + [2232, 2984], + [2163, 2984], +] + +[[area.polygons]] +vertices = [ + [2274, 2944], + [2303, 2944], + [2303, 2968], + [2274, 2968], +] + +[[area.polygons]] +vertices = [ + [2320, 2945], + [2324, 2945], +] + +[[area.polygons]] +vertices = [ + [2095, 2946], + [2095, 2949], +] + +[[area.polygons]] +vertices = [ + [2320, 2946], + [2323, 2946], +] + +[[area.polygons]] +vertices = [[2094, 2947]] + +[[area.polygons]] +vertices = [ + [2320, 2947], + [2321, 2947], +] + +[[area.polygons]] +vertices = [ + [2320, 2948], + [2320, 2962], +] + +[[area.polygons]] +vertices = [ + [2321, 2951], + [2321, 2962], +] + +[[area.polygons]] +vertices = [ + [2095, 2952], + [2095, 2957], +] + +[[area.polygons]] +vertices = [ + [2322, 2952], + [2322, 2961], +] + +[[area.polygons]] +vertices = [ + [2094, 2953], + [2094, 2956], +] + +[[area.polygons]] +vertices = [[2093, 2954]] + +[[area.polygons]] +vertices = [ + [2323, 2955], + [2323, 2959], +] + +[[area.polygons]] +vertices = [ + [2098, 2960], + [2099, 2960], +] + +[[area.polygons]] +vertices = [ + [2106, 2964], + [2112, 2964], +] + +[[area.polygons]] +vertices = [ + [2314, 2964], + [2317, 2964], +] + +[[area.polygons]] +vertices = [ + [2108, 2965], + [2112, 2965], +] + +[[area.polygons]] +vertices = [ + [2109, 2966], + [2112, 2966], + [2112, 2967], + [2109, 2967], +] + +[[area.polygons]] +vertices = [ + [2111, 2968], + [2112, 2968], +] + +[[area.polygons]] +vertices = [ + [2274, 2969], + [2302, 2969], +] + +[[area.polygons]] +vertices = [ + [2116, 2970], + [2156, 2970], +] + +[[area.polygons]] +vertices = [ + [2274, 2970], + [2293, 2970], +] + +[[area.polygons]] +vertices = [ + [2295, 2970], + [2300, 2970], +] + +[[area.polygons]] +vertices = [ + [2117, 2971], + [2156, 2971], +] + +[[area.polygons]] +vertices = [ + [2274, 2971], + [2291, 2971], +] + +[[area.polygons]] +vertices = [ + [2119, 2972], + [2156, 2972], +] + +[[area.polygons]] +vertices = [ + [2274, 2972], + [2289, 2972], +] + +[[area.polygons]] +vertices = [ + [2124, 2973], + [2156, 2973], +] + +[[area.polygons]] +vertices = [ + [2274, 2973], + [2283, 2973], +] + +[[area.polygons]] +vertices = [ + [2285, 2973], + [2288, 2973], +] + +[[area.polygons]] +vertices = [ + [2126, 2974], + [2156, 2974], +] + +[[area.polygons]] +vertices = [ + [2274, 2974], + [2281, 2974], +] + +[[area.polygons]] +vertices = [ + [2129, 2975], + [2156, 2975], +] + +[[area.polygons]] +vertices = [ + [2274, 2975], + [2279, 2975], +] + +[[area.polygons]] +vertices = [ + [2131, 2976], + [2156, 2976], +] + +[[area.polygons]] +vertices = [[2274, 2976]] + +[[area.polygons]] +vertices = [ + [2132, 2977], + [2156, 2977], +] + +[[area.polygons]] +vertices = [ + [2133, 2978], + [2156, 2978], +] + +[[area.polygons]] +vertices = [ + [2134, 2979], + [2156, 2979], +] + +[[area.polygons]] +vertices = [ + [2134, 2980], + [2142, 2980], +] + +[[area.polygons]] +vertices = [ + [2147, 2980], + [2156, 2980], +] + +[[area.polygons]] +vertices = [ + [2245, 2980], + [2247, 2980], +] + +[[area.polygons]] +vertices = [ + [2251, 2980], + [2267, 2980], +] + +[[area.polygons]] +vertices = [ + [2134, 2981], + [2135, 2981], + [2135, 3005], + [2134, 3005], +] + +[[area.polygons]] +vertices = [ + [2148, 2981], + [2156, 2981], + [2156, 2982], + [2148, 2982], +] + +[[area.polygons]] +vertices = [[2245, 2981]] + +[[area.polygons]] +vertices = [ + [2253, 2981], + [2267, 2981], +] + +[[area.polygons]] +vertices = [ + [2258, 2982], + [2267, 2982], +] + +[[area.polygons]] +vertices = [ + [2149, 2983], + [2156, 2983], + [2156, 2984], + [2149, 2984], +] + +[[area.polygons]] +vertices = [ + [2260, 2983], + [2267, 2983], +] + +[[area.polygons]] +vertices = [[2233, 2984]] + +[[area.polygons]] +vertices = [ + [2261, 2984], + [2267, 2984], +] + +[[area.polygons]] +vertices = [ + [2150, 2985], + [2156, 2985], +] + +[[area.polygons]] +vertices = [ + [2163, 2985], + [2227, 2985], +] + +[[area.polygons]] +vertices = [ + [2151, 2986], + [2156, 2986], +] + +[[area.polygons]] +vertices = [ + [2163, 2986], + [2164, 2986], +] + +[[area.polygons]] +vertices = [ + [2168, 2986], + [2225, 2986], +] + +[[area.polygons]] +vertices = [ + [2127, 2987], + [2128, 2987], + [2128, 3003], + [2127, 3003], +] + +[[area.polygons]] +vertices = [ + [2137, 2987], + [2137, 3005], +] + +[[area.polygons]] +vertices = [ + [2152, 2987], + [2156, 2987], +] + +[[area.polygons]] +vertices = [ + [2169, 2987], + [2223, 2987], +] + +[[area.polygons]] +vertices = [ + [2126, 2988], + [2126, 3003], +] + +[[area.polygons]] +vertices = [ + [2129, 2988], + [2133, 2988], + [2133, 3004], + [2129, 3004], +] + +[[area.polygons]] +vertices = [ + [2136, 2988], + [2136, 3005], +] + +[[area.polygons]] +vertices = [ + [2138, 2988], + [2139, 2988], + [2139, 3004], + [2138, 3004], +] + +[[area.polygons]] +vertices = [ + [2154, 2988], + [2155, 2988], +] + +[[area.polygons]] +vertices = [ + [2170, 2988], + [2223, 2988], +] + +[[area.polygons]] +vertices = [ + [2124, 2989], + [2125, 2989], + [2125, 3001], + [2124, 3001], +] + +[[area.polygons]] +vertices = [ + [2140, 2989], + [2140, 3003], +] + +[[area.polygons]] +vertices = [ + [2171, 2989], + [2222, 2989], +] + +[[area.polygons]] +vertices = [ + [2141, 2990], + [2142, 2990], + [2142, 3003], + [2141, 3003], +] + +[[area.polygons]] +vertices = [ + [2173, 2990], + [2221, 2990], +] + +[[area.polygons]] +vertices = [ + [2143, 2991], + [2144, 2991], + [2144, 3003], + [2143, 3003], +] + +[[area.polygons]] +vertices = [ + [2179, 2991], + [2221, 2991], +] + +[[area.polygons]] +vertices = [ + [2145, 2992], + [2145, 3003], +] + +[[area.polygons]] +vertices = [ + [2180, 2992], + [2220, 2992], +] + +[[area.polygons]] +vertices = [ + [2146, 2993], + [2146, 3003], +] + +[[area.polygons]] +vertices = [ + [2181, 2993], + [2220, 2993], +] + +[[area.polygons]] +vertices = [ + [2182, 2994], + [2219, 2994], +] + +[[area.polygons]] +vertices = [ + [2182, 2995], + [2218, 2995], +] + +[[area.polygons]] +vertices = [ + [2147, 2996], + [2147, 3003], +] + +[[area.polygons]] +vertices = [ + [2183, 2996], + [2217, 2996], +] + +[[area.polygons]] +vertices = [ + [2123, 2997], + [2123, 2998], +] + +[[area.polygons]] +vertices = [ + [2148, 2997], + [2148, 3002], +] + +[[area.polygons]] +vertices = [ + [2184, 2997], + [2216, 2997], +] + +[[area.polygons]] +vertices = [ + [2149, 2998], + [2149, 3001], +] + +[[area.polygons]] +vertices = [ + [2185, 2998], + [2214, 2998], +] + +[[area.polygons]] +vertices = [ + [2150, 2999], + [2150, 3000], +] + +[[area.polygons]] +vertices = [ + [2186, 2999], + [2213, 2999], +] + +[[area.polygons]] +vertices = [ + [2186, 3000], + [2212, 3000], +] + +[[area.polygons]] +vertices = [ + [2188, 3001], + [2211, 3001], +] + +[[area.polygons]] +vertices = [[2125, 3002]] + +[[area.polygons]] +vertices = [ + [2191, 3002], + [2210, 3002], +] + +[[area.polygons]] +vertices = [ + [2195, 3003], + [2209, 3003], +] + +[[area.polygons]] +vertices = [[2128, 3004]] + +[[area.polygons]] +vertices = [ + [2203, 3004], + [2208, 3004], +] + +[[area.polygons]] +vertices = [ + [2132, 3005], + [2133, 3005], +] diff --git a/.data/raw-cache/map/area/dorgeshuun_city.toml b/.data/raw-cache/map/area/dorgeshuun_city.toml new file mode 100644 index 000000000..15ab0d729 --- /dev/null +++ b/.data/raw-cache/map/area/dorgeshuun_city.toml @@ -0,0 +1,16 @@ +# Dorgeshuun City music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dorgeshuun City. +# Bounds x 2688-2751, z 5248-5375; 8192 tiles as 1 rectangles. + +[[area]] +name = "Dorgeshuun City" +area_id = "area.dorgeshuun_city" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 5248], + [2751, 5248], + [2751, 5375], + [2688, 5375], +] diff --git a/.data/raw-cache/map/area/dorgeshuun_deep.toml b/.data/raw-cache/map/area/dorgeshuun_deep.toml new file mode 100644 index 000000000..da6d1ba39 --- /dev/null +++ b/.data/raw-cache/map/area/dorgeshuun_deep.toml @@ -0,0 +1,16 @@ +# Dorgeshuun Deep music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dorgeshuun Deep. +# Bounds x 2688-2751, z 5184-5247; 4096 tiles as 1 rectangles. + +[[area]] +name = "Dorgeshuun Deep" +area_id = "area.dorgeshuun_deep" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 5184], + [2751, 5184], + [2751, 5247], + [2688, 5247], +] diff --git a/.data/raw-cache/map/area/down_and_out.toml b/.data/raw-cache/map/area/down_and_out.toml new file mode 100644 index 000000000..81f84e262 --- /dev/null +++ b/.data/raw-cache/map/area/down_and_out.toml @@ -0,0 +1,16 @@ +# Down and Out music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Down and Out. +# Bounds x 2336-2367, z 10352-10367; 512 tiles as 1 rectangles. + +[[area]] +name = "Down and Out" +area_id = "area.down_and_out" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2336, 10352], + [2367, 10352], + [2367, 10367], + [2336, 10367], +] diff --git a/.data/raw-cache/map/area/down_below.toml b/.data/raw-cache/map/area/down_below.toml new file mode 100644 index 000000000..675e88623 --- /dev/null +++ b/.data/raw-cache/map/area/down_below.toml @@ -0,0 +1,16 @@ +# Down Below music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Down Below. +# Bounds x 3072-3135, z 9600-9727; 8192 tiles as 1 rectangles. + +[[area]] +name = "Down Below" +area_id = "area.down_below" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3072, 9600], + [3135, 9600], + [3135, 9727], + [3072, 9727], +] diff --git a/.data/raw-cache/map/area/down_by_the_docks.toml b/.data/raw-cache/map/area/down_by_the_docks.toml new file mode 100644 index 000000000..e2c96269f --- /dev/null +++ b/.data/raw-cache/map/area/down_by_the_docks.toml @@ -0,0 +1,575 @@ +# Down by the Docks music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Down by the Docks. +# Bounds x 1736-1853, z 3673-3813; 10288 tiles as 87 rectangles. + +[[area]] +name = "Down by the Docks" +area_id = "area.down_by_the_docks" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1805, 3673], + [1808, 3673], + [1808, 3759], + [1805, 3759], +] + +[[area.polygons]] +vertices = [ + [1819, 3673], + [1822, 3673], + [1822, 3691], + [1819, 3691], +] + +[[area.polygons]] +vertices = [ + [1811, 3674], + [1812, 3674], + [1812, 3683], + [1811, 3683], +] + +[[area.polygons]] +vertices = [ + [1813, 3675], + [1813, 3682], +] + +[[area.polygons]] +vertices = [ + [1809, 3679], + [1810, 3679], +] + +[[area.polygons]] +vertices = [ + [1788, 3688], + [1804, 3688], + [1804, 3691], + [1788, 3691], +] + +[[area.polygons]] +vertices = [ + [1809, 3688], + [1818, 3688], + [1818, 3691], + [1809, 3691], +] + +[[area.polygons]] +vertices = [ + [1823, 3688], + [1848, 3688], + [1848, 3691], + [1823, 3691], +] + +[[area.polygons]] +vertices = [ + [1790, 3692], + [1804, 3692], + [1804, 3797], + [1790, 3797], +] + +[[area.polygons]] +vertices = [ + [1824, 3692], + [1824, 3696], +] + +[[area.polygons]] +vertices = [ + [1819, 3694], + [1823, 3694], + [1823, 3696], + [1819, 3696], +] + +[[area.polygons]] +vertices = [ + [1825, 3694], + [1827, 3694], + [1827, 3696], + [1825, 3696], +] + +[[area.polygons]] +vertices = [[1828, 3695]] + +[[area.polygons]] +vertices = [ + [1789, 3699], + [1789, 3797], +] + +[[area.polygons]] +vertices = [ + [1788, 3700], + [1788, 3796], +] + +[[area.polygons]] +vertices = [ + [1787, 3701], + [1787, 3796], +] + +[[area.polygons]] +vertices = [ + [1786, 3702], + [1786, 3796], +] + +[[area.polygons]] +vertices = [ + [1742, 3703], + [1768, 3703], + [1768, 3784], + [1742, 3784], +] + +[[area.polygons]] +vertices = [ + [1775, 3703], + [1785, 3703], + [1785, 3796], + [1775, 3796], +] + +[[area.polygons]] +vertices = [ + [1769, 3704], + [1774, 3704], + [1774, 3797], + [1769, 3797], +] + +[[area.polygons]] +vertices = [ + [1824, 3713], + [1837, 3713], + [1837, 3726], + [1824, 3726], +] + +[[area.polygons]] +vertices = [ + [1809, 3714], + [1823, 3714], + [1823, 3749], + [1809, 3749], +] + +[[area.polygons]] +vertices = [ + [1843, 3720], + [1851, 3720], + [1851, 3728], + [1843, 3728], +] + +[[area.polygons]] +vertices = [ + [1838, 3723], + [1842, 3723], + [1842, 3725], + [1838, 3725], +] + +[[area.polygons]] +vertices = [ + [1824, 3727], + [1836, 3727], + [1836, 3749], + [1824, 3749], +] + +[[area.polygons]] +vertices = [ + [1837, 3734], + [1853, 3734], + [1853, 3761], + [1837, 3761], +] + +[[area.polygons]] +vertices = [ + [1809, 3750], + [1819, 3750], + [1819, 3761], + [1809, 3761], +] + +[[area.polygons]] +vertices = [ + [1830, 3750], + [1836, 3750], + [1836, 3761], + [1830, 3761], +] + +[[area.polygons]] +vertices = [ + [1820, 3751], + [1820, 3760], +] + +[[area.polygons]] +vertices = [ + [1821, 3752], + [1821, 3755], +] + +[[area.polygons]] +vertices = [ + [1822, 3753], + [1822, 3754], +] + +[[area.polygons]] +vertices = [ + [1805, 3760], + [1805, 3802], +] + +[[area.polygons]] +vertices = [ + [1807, 3760], + [1808, 3760], + [1808, 3763], + [1807, 3763], +] + +[[area.polygons]] +vertices = [ + [1809, 3762], + [1818, 3762], +] + +[[area.polygons]] +vertices = [ + [1809, 3763], + [1817, 3763], +] + +[[area.polygons]] +vertices = [ + [1830, 3768], + [1830, 3805], +] + +[[area.polygons]] +vertices = [ + [1806, 3769], + [1810, 3769], + [1810, 3802], + [1806, 3802], +] + +[[area.polygons]] +vertices = [ + [1818, 3769], + [1823, 3769], + [1823, 3800], + [1818, 3800], +] + +[[area.polygons]] +vertices = [ + [1829, 3769], + [1829, 3804], +] + +[[area.polygons]] +vertices = [ + [1831, 3769], + [1831, 3807], +] + +[[area.polygons]] +vertices = [ + [1811, 3770], + [1814, 3770], + [1814, 3799], + [1811, 3799], +] + +[[area.polygons]] +vertices = [ + [1817, 3770], + [1817, 3800], +] + +[[area.polygons]] +vertices = [ + [1824, 3770], + [1824, 3801], +] + +[[area.polygons]] +vertices = [ + [1828, 3770], + [1828, 3803], +] + +[[area.polygons]] +vertices = [ + [1832, 3770], + [1833, 3770], + [1833, 3808], + [1832, 3808], +] + +[[area.polygons]] +vertices = [ + [1815, 3771], + [1816, 3771], + [1816, 3799], + [1815, 3799], +] + +[[area.polygons]] +vertices = [ + [1825, 3771], + [1827, 3771], + [1827, 3801], + [1825, 3801], +] + +[[area.polygons]] +vertices = [ + [1834, 3771], + [1834, 3809], +] + +[[area.polygons]] +vertices = [ + [1835, 3772], + [1836, 3772], + [1836, 3810], + [1835, 3810], +] + +[[area.polygons]] +vertices = [ + [1837, 3773], + [1837, 3777], +] + +[[area.polygons]] +vertices = [ + [1838, 3775], + [1838, 3777], +] + +[[area.polygons]] +vertices = [[1839, 3776]] + +[[area.polygons]] +vertices = [ + [1837, 3780], + [1847, 3780], + [1847, 3793], + [1837, 3793], +] + +[[area.polygons]] +vertices = [ + [1736, 3781], + [1741, 3781], + [1741, 3784], + [1736, 3784], +] + +[[area.polygons]] +vertices = [ + [1848, 3781], + [1851, 3781], + [1851, 3791], + [1848, 3791], +] + +[[area.polygons]] +vertices = [ + [1743, 3785], + [1768, 3785], + [1768, 3795], + [1743, 3795], +] + +[[area.polygons]] +vertices = [ + [1742, 3787], + [1742, 3797], +] + +[[area.polygons]] +vertices = [ + [1741, 3792], + [1741, 3796], +] + +[[area.polygons]] +vertices = [[1848, 3792]] + +[[area.polygons]] +vertices = [[1740, 3794]] + +[[area.polygons]] +vertices = [ + [1837, 3794], + [1844, 3794], + [1844, 3811], + [1837, 3811], +] + +[[area.polygons]] +vertices = [ + [1743, 3796], + [1758, 3796], +] + +[[area.polygons]] +vertices = [ + [1767, 3796], + [1768, 3796], +] + +[[area.polygons]] +vertices = [ + [1743, 3797], + [1756, 3797], +] + +[[area.polygons]] +vertices = [[1768, 3797]] + +[[area.polygons]] +vertices = [ + [1775, 3797], + [1783, 3797], +] + +[[area.polygons]] +vertices = [ + [1743, 3798], + [1754, 3798], +] + +[[area.polygons]] +vertices = [ + [1774, 3798], + [1781, 3798], +] + +[[area.polygons]] +vertices = [ + [1795, 3798], + [1804, 3798], +] + +[[area.polygons]] +vertices = [ + [1743, 3799], + [1753, 3799], +] + +[[area.polygons]] +vertices = [ + [1796, 3799], + [1804, 3799], +] + +[[area.polygons]] +vertices = [ + [1744, 3800], + [1752, 3800], +] + +[[area.polygons]] +vertices = [ + [1798, 3800], + [1804, 3800], +] + +[[area.polygons]] +vertices = [ + [1811, 3800], + [1813, 3800], +] + +[[area.polygons]] +vertices = [ + [1746, 3801], + [1751, 3801], +] + +[[area.polygons]] +vertices = [ + [1802, 3801], + [1804, 3801], +] + +[[area.polygons]] +vertices = [[1811, 3801]] + +[[area.polygons]] +vertices = [ + [1819, 3801], + [1823, 3801], +] + +[[area.polygons]] +vertices = [ + [1845, 3801], + [1845, 3813], +] + +[[area.polygons]] +vertices = [ + [1826, 3802], + [1827, 3802], +] + +[[area.polygons]] +vertices = [ + [1846, 3803], + [1846, 3813], +] + +[[area.polygons]] +vertices = [ + [1847, 3806], + [1847, 3813], +] + +[[area.polygons]] +vertices = [ + [1848, 3808], + [1848, 3813], +] + +[[area.polygons]] +vertices = [[1833, 3809]] + +[[area.polygons]] +vertices = [ + [1849, 3811], + [1849, 3812], +] + +[[area.polygons]] +vertices = [ + [1839, 3812], + [1844, 3812], +] + +[[area.polygons]] +vertices = [ + [1843, 3813], + [1844, 3813], +] diff --git a/.data/raw-cache/map/area/down_to_earth.toml b/.data/raw-cache/map/area/down_to_earth.toml new file mode 100644 index 000000000..4fa559d67 --- /dev/null +++ b/.data/raw-cache/map/area/down_to_earth.toml @@ -0,0 +1,16 @@ +# Down to Earth music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Down to Earth. +# Bounds x 2624-2687, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Down to Earth" +area_id = "area.down_to_earth" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2624, 4800], + [2687, 4800], + [2687, 4863], + [2624, 4863], +] diff --git a/.data/raw-cache/map/area/dragontooth_island.toml b/.data/raw-cache/map/area/dragontooth_island.toml new file mode 100644 index 000000000..93d3c5204 --- /dev/null +++ b/.data/raw-cache/map/area/dragontooth_island.toml @@ -0,0 +1,16 @@ +# Dragontooth Island music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dragontooth Island. +# Bounds x 3776-3839, z 3520-3583; 4096 tiles as 1 rectangles. + +[[area]] +name = "Dragontooth Island" +area_id = "area.dragontooth_island" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3776, 3520], + [3839, 3520], + [3839, 3583], + [3776, 3583], +] diff --git a/.data/raw-cache/map/area/draynor_manor.toml b/.data/raw-cache/map/area/draynor_manor.toml new file mode 100644 index 000000000..fd41eb997 --- /dev/null +++ b/.data/raw-cache/map/area/draynor_manor.toml @@ -0,0 +1,138 @@ +[[area]] +name = "Draynor Manor" +area_id = "area.draynor_manor" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3090, 3320], + [3130, 3320], + [3130, 3375], + [3090, 3375], +] + +# Extension traced from the OSRS wiki music map (634 tiles). + +[[area.polygons]] +vertices = [ + [3085, 3331], + [3089, 3331], + [3089, 3348], + [3085, 3348], +] + +[[area.polygons]] +vertices = [ + [3084, 3332], + [3084, 3337], +] + +[[area.polygons]] +vertices = [ + [3084, 3340], + [3084, 3347], +] + +[[area.polygons]] +vertices = [ + [3086, 3349], + [3089, 3349], + [3089, 3385], + [3086, 3385], +] + +[[area.polygons]] +vertices = [ + [3085, 3353], + [3085, 3384], +] + +[[area.polygons]] +vertices = [ + [3084, 3358], + [3084, 3364], +] + +[[area.polygons]] +vertices = [ + [3084, 3370], + [3084, 3378], +] + +[[area.polygons]] +vertices = [ + [3083, 3371], + [3083, 3377], +] + +[[area.polygons]] +vertices = [ + [3090, 3376], + [3125, 3376], +] + +[[area.polygons]] +vertices = [ + [3090, 3377], + [3124, 3377], +] + +[[area.polygons]] +vertices = [ + [3090, 3378], + [3123, 3378], +] + +[[area.polygons]] +vertices = [ + [3090, 3379], + [3122, 3379], +] + +[[area.polygons]] +vertices = [ + [3090, 3380], + [3121, 3380], +] + +[[area.polygons]] +vertices = [ + [3090, 3381], + [3120, 3381], +] + +[[area.polygons]] +vertices = [ + [3090, 3382], + [3119, 3382], +] + +[[area.polygons]] +vertices = [ + [3090, 3383], + [3118, 3383], +] + +[[area.polygons]] +vertices = [ + [3090, 3384], + [3117, 3384], +] + +[[area.polygons]] +vertices = [ + [3090, 3385], + [3106, 3385], +] + +[[area.polygons]] +vertices = [ + [3087, 3386], + [3101, 3386], +] + +[[area.polygons]] +vertices = [ + [3091, 3387], + [3097, 3387], +] diff --git a/.data/raw-cache/map/area/draynor_village.toml b/.data/raw-cache/map/area/draynor_village.toml new file mode 100644 index 000000000..c83670aad --- /dev/null +++ b/.data/raw-cache/map/area/draynor_village.toml @@ -0,0 +1,544 @@ +[[area]] +name = "Draynor Village" +area_id = "area.draynor_village" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3070, 3222], + [3130, 3222], + [3130, 3280], + [3070, 3280], +] + +# Extension traced from the OSRS wiki music map (6047 tiles). + +[[area.polygons]] +vertices = [ + [3112, 3200], + [3115, 3200], + [3115, 3221], + [3112, 3221], +] + +[[area.polygons]] +vertices = [ + [3132, 3203], + [3132, 3387], +] + +[[area.polygons]] +vertices = [ + [3130, 3204], + [3131, 3204], + [3131, 3221], + [3130, 3221], +] + +[[area.polygons]] +vertices = [ + [3133, 3204], + [3133, 3387], +] + +[[area.polygons]] +vertices = [ + [3119, 3205], + [3124, 3205], + [3124, 3221], + [3119, 3221], +] + +[[area.polygons]] +vertices = [ + [3129, 3205], + [3129, 3221], +] + +[[area.polygons]] +vertices = [ + [3134, 3205], + [3134, 3387], +] + +[[area.polygons]] +vertices = [ + [3111, 3206], + [3111, 3221], +] + +[[area.polygons]] +vertices = [ + [3116, 3206], + [3118, 3206], + [3118, 3221], + [3116, 3221], +] + +[[area.polygons]] +vertices = [ + [3125, 3206], + [3128, 3206], + [3128, 3221], + [3125, 3221], +] + +[[area.polygons]] +vertices = [ + [3135, 3206], + [3135, 3371], +] + +[[area.polygons]] +vertices = [ + [3110, 3207], + [3110, 3221], +] + +[[area.polygons]] +vertices = [ + [3136, 3207], + [3136, 3291], +] + +[[area.polygons]] +vertices = [ + [3102, 3208], + [3103, 3208], + [3103, 3221], + [3102, 3221], +] + +[[area.polygons]] +vertices = [ + [3107, 3208], + [3109, 3208], + [3109, 3221], + [3107, 3221], +] + +[[area.polygons]] +vertices = [ + [3137, 3208], + [3137, 3290], +] + +[[area.polygons]] +vertices = [ + [3098, 3209], + [3101, 3209], + [3101, 3221], + [3098, 3221], +] + +[[area.polygons]] +vertices = [ + [3104, 3209], + [3106, 3209], + [3106, 3221], + [3104, 3221], +] + +[[area.polygons]] +vertices = [ + [3138, 3209], + [3138, 3268], +] + +[[area.polygons]] +vertices = [ + [3096, 3210], + [3097, 3210], + [3097, 3221], + [3096, 3221], +] + +[[area.polygons]] +vertices = [ + [3139, 3210], + [3139, 3267], +] + +[[area.polygons]] +vertices = [ + [3094, 3211], + [3095, 3211], + [3095, 3221], + [3094, 3221], +] + +[[area.polygons]] +vertices = [ + [3140, 3211], + [3140, 3267], +] + +[[area.polygons]] +vertices = [ + [3093, 3212], + [3093, 3221], +] + +[[area.polygons]] +vertices = [ + [3141, 3212], + [3141, 3267], +] + +[[area.polygons]] +vertices = [ + [3091, 3213], + [3092, 3213], + [3092, 3221], + [3091, 3221], +] + +[[area.polygons]] +vertices = [ + [3142, 3213], + [3142, 3266], +] + +[[area.polygons]] +vertices = [ + [3090, 3214], + [3090, 3221], +] + +[[area.polygons]] +vertices = [ + [3143, 3214], + [3143, 3266], +] + +[[area.polygons]] +vertices = [ + [3089, 3215], + [3089, 3221], +] + +[[area.polygons]] +vertices = [ + [3144, 3215], + [3144, 3266], +] + +[[area.polygons]] +vertices = [ + [3145, 3216], + [3145, 3266], +] + +[[area.polygons]] +vertices = [ + [3088, 3217], + [3088, 3221], +] + +[[area.polygons]] +vertices = [ + [3087, 3219], + [3087, 3221], +] + +[[area.polygons]] +vertices = [ + [3086, 3220], + [3086, 3221], +] + +[[area.polygons]] +vertices = [ + [3131, 3222], + [3131, 3387], +] + +[[area.polygons]] +vertices = [ + [3069, 3244], + [3069, 3272], +] + +[[area.polygons]] +vertices = [ + [3068, 3245], + [3068, 3267], +] + +[[area.polygons]] +vertices = [ + [3067, 3246], + [3067, 3256], +] + +[[area.polygons]] +vertices = [ + [3066, 3248], + [3066, 3255], +] + +[[area.polygons]] +vertices = [ + [3065, 3249], + [3065, 3254], +] + +[[area.polygons]] +vertices = [ + [3138, 3276], + [3138, 3279], +] + +[[area.polygons]] +vertices = [ + [3070, 3281], + [3130, 3281], + [3130, 3319], + [3070, 3319], +] + +[[area.polygons]] +vertices = [ + [3069, 3285], + [3069, 3310], +] + +[[area.polygons]] +vertices = [ + [3138, 3287], + [3138, 3289], +] + +[[area.polygons]] +vertices = [ + [3068, 3296], + [3068, 3309], +] + +[[area.polygons]] +vertices = [ + [3136, 3299], + [3136, 3371], +] + +[[area.polygons]] +vertices = [ + [3137, 3300], + [3137, 3371], +] + +[[area.polygons]] +vertices = [ + [3138, 3307], + [3141, 3307], + [3141, 3371], + [3138, 3371], +] + +[[area.polygons]] +vertices = [ + [3142, 3308], + [3142, 3371], +] + +[[area.polygons]] +vertices = [ + [3143, 3309], + [3143, 3371], +] + +[[area.polygons]] +vertices = [ + [3144, 3310], + [3144, 3371], +] + +[[area.polygons]] +vertices = [ + [3145, 3311], + [3145, 3371], +] + +[[area.polygons]] +vertices = [ + [3146, 3312], + [3146, 3371], +] + +[[area.polygons]] +vertices = [ + [3147, 3313], + [3147, 3371], +] + +[[area.polygons]] +vertices = [ + [3069, 3314], + [3069, 3323], +] + +[[area.polygons]] +vertices = [ + [3148, 3314], + [3148, 3371], +] + +[[area.polygons]] +vertices = [ + [3149, 3315], + [3149, 3371], +] + +[[area.polygons]] +vertices = [ + [3150, 3316], + [3150, 3371], +] + +[[area.polygons]] +vertices = [ + [3151, 3317], + [3151, 3356], +] + +[[area.polygons]] +vertices = [ + [3152, 3318], + [3152, 3321], +] + +[[area.polygons]] +vertices = [ + [3070, 3320], + [3089, 3320], + [3089, 3327], + [3070, 3327], +] + +[[area.polygons]] +vertices = [ + [3152, 3327], + [3152, 3337], +] + +[[area.polygons]] +vertices = [ + [3071, 3328], + [3089, 3328], +] + +[[area.polygons]] +vertices = [ + [3072, 3329], + [3089, 3329], +] + +[[area.polygons]] +vertices = [ + [3073, 3330], + [3089, 3330], +] + +[[area.polygons]] +vertices = [ + [3074, 3331], + [3084, 3331], +] + +[[area.polygons]] +vertices = [ + [3153, 3331], + [3153, 3333], +] + +[[area.polygons]] +vertices = [ + [3075, 3332], + [3083, 3332], +] + +[[area.polygons]] +vertices = [ + [3152, 3344], + [3152, 3355], +] + +[[area.polygons]] +vertices = [ + [3153, 3347], + [3153, 3354], +] + +[[area.polygons]] +vertices = [ + [3154, 3348], + [3154, 3353], +] + +[[area.polygons]] +vertices = [ + [3155, 3349], + [3155, 3352], +] + +[[area.polygons]] +vertices = [ + [3156, 3350], + [3156, 3351], +] + +[[area.polygons]] +vertices = [ + [3126, 3376], + [3130, 3376], + [3130, 3387], + [3126, 3387], +] + +[[area.polygons]] +vertices = [ + [3125, 3377], + [3125, 3387], +] + +[[area.polygons]] +vertices = [ + [3124, 3378], + [3124, 3387], +] + +[[area.polygons]] +vertices = [ + [3123, 3379], + [3123, 3387], +] + +[[area.polygons]] +vertices = [ + [3122, 3380], + [3122, 3387], +] + +[[area.polygons]] +vertices = [ + [3121, 3381], + [3121, 3387], +] + +[[area.polygons]] +vertices = [ + [3120, 3382], + [3120, 3388], +] + +[[area.polygons]] +vertices = [ + [3119, 3383], + [3119, 3389], +] + +[[area.polygons]] +vertices = [ + [3118, 3384], + [3118, 3390], +] diff --git a/.data/raw-cache/map/area/dreamstate.toml b/.data/raw-cache/map/area/dreamstate.toml new file mode 100644 index 000000000..12ce55995 --- /dev/null +++ b/.data/raw-cache/map/area/dreamstate.toml @@ -0,0 +1,40 @@ +# Dreamstate music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dreamstate. +# Bounds x 1728-1791, z 5056-5119; 3072 tiles as 4 rectangles. + +[[area]] +name = "Dreamstate" +area_id = "area.dreamstate" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1728, 5056], + [1791, 5056], + [1791, 5071], + [1728, 5071], +] + +[[area.polygons]] +vertices = [ + [1728, 5072], + [1743, 5072], + [1743, 5119], + [1728, 5119], +] + +[[area.polygons]] +vertices = [ + [1776, 5072], + [1791, 5072], + [1791, 5119], + [1776, 5119], +] + +[[area.polygons]] +vertices = [ + [1744, 5104], + [1775, 5104], + [1775, 5119], + [1744, 5119], +] diff --git a/.data/raw-cache/map/area/dunes_of_eternity.toml b/.data/raw-cache/map/area/dunes_of_eternity.toml new file mode 100644 index 000000000..c30f16f0d --- /dev/null +++ b/.data/raw-cache/map/area/dunes_of_eternity.toml @@ -0,0 +1,677 @@ +# Dunes of Eternity music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dunes of Eternity. +# Bounds x 3277-3400, z 2675-2760; 6774 tiles as 104 rectangles. + +[[area]] +name = "Dunes of Eternity" +area_id = "area.dunes_of_eternity" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3362, 2675], + [3369, 2675], + [3369, 2745], + [3362, 2745], +] + +[[area.polygons]] +vertices = [ + [3355, 2676], + [3361, 2676], + [3361, 2751], + [3355, 2751], +] + +[[area.polygons]] +vertices = [ + [3370, 2676], + [3373, 2676], + [3373, 2739], + [3370, 2739], +] + +[[area.polygons]] +vertices = [ + [3352, 2677], + [3354, 2677], + [3354, 2760], + [3352, 2760], +] + +[[area.polygons]] +vertices = [ + [3374, 2677], + [3381, 2677], + [3381, 2732], + [3374, 2732], +] + +[[area.polygons]] +vertices = [ + [3351, 2678], + [3351, 2760], +] + +[[area.polygons]] +vertices = [ + [3382, 2678], + [3383, 2678], + [3383, 2730], + [3382, 2730], +] + +[[area.polygons]] +vertices = [ + [3346, 2679], + [3350, 2679], + [3350, 2755], + [3346, 2755], +] + +[[area.polygons]] +vertices = [ + [3384, 2679], + [3385, 2679], + [3385, 2728], + [3384, 2728], +] + +[[area.polygons]] +vertices = [ + [3344, 2680], + [3345, 2680], + [3345, 2755], + [3344, 2755], +] + +[[area.polygons]] +vertices = [ + [3386, 2680], + [3389, 2680], + [3389, 2724], + [3386, 2724], +] + +[[area.polygons]] +vertices = [ + [3341, 2681], + [3343, 2681], + [3343, 2755], + [3341, 2755], +] + +[[area.polygons]] +vertices = [ + [3390, 2681], + [3391, 2681], + [3391, 2722], + [3390, 2722], +] + +[[area.polygons]] +vertices = [ + [3339, 2682], + [3340, 2682], + [3340, 2755], + [3339, 2755], +] + +[[area.polygons]] +vertices = [ + [3392, 2682], + [3394, 2682], + [3394, 2714], + [3392, 2714], +] + +[[area.polygons]] +vertices = [ + [3338, 2683], + [3338, 2755], +] + +[[area.polygons]] +vertices = [ + [3395, 2683], + [3396, 2683], + [3396, 2709], + [3395, 2709], +] + +[[area.polygons]] +vertices = [ + [3337, 2684], + [3337, 2755], +] + +[[area.polygons]] +vertices = [ + [3397, 2684], + [3398, 2684], + [3398, 2691], + [3397, 2691], +] + +[[area.polygons]] +vertices = [ + [3335, 2685], + [3336, 2685], + [3336, 2755], + [3335, 2755], +] + +[[area.polygons]] +vertices = [ + [3399, 2685], + [3399, 2689], +] + +[[area.polygons]] +vertices = [ + [3333, 2686], + [3334, 2686], + [3334, 2755], + [3333, 2755], +] + +[[area.polygons]] +vertices = [ + [3331, 2687], + [3332, 2687], + [3332, 2755], + [3331, 2755], +] + +[[area.polygons]] +vertices = [ + [3400, 2687], + [3400, 2688], +] + +[[area.polygons]] +vertices = [ + [3329, 2688], + [3330, 2688], + [3330, 2755], + [3329, 2755], +] + +[[area.polygons]] +vertices = [ + [3328, 2690], + [3328, 2755], +] + +[[area.polygons]] +vertices = [ + [3327, 2692], + [3327, 2755], +] + +[[area.polygons]] +vertices = [ + [3397, 2692], + [3397, 2704], +] + +[[area.polygons]] +vertices = [ + [3326, 2693], + [3326, 2755], +] + +[[area.polygons]] +vertices = [ + [3398, 2693], + [3398, 2700], +] + +[[area.polygons]] +vertices = [ + [3325, 2694], + [3325, 2755], +] + +[[area.polygons]] +vertices = [ + [3324, 2695], + [3324, 2755], +] + +[[area.polygons]] +vertices = [ + [3323, 2696], + [3323, 2755], +] + +[[area.polygons]] +vertices = [ + [3316, 2697], + [3322, 2697], + [3322, 2747], + [3316, 2747], +] + +[[area.polygons]] +vertices = [ + [3287, 2698], + [3287, 2703], +] + +[[area.polygons]] +vertices = [ + [3313, 2698], + [3315, 2698], + [3315, 2747], + [3313, 2747], +] + +[[area.polygons]] +vertices = [ + [3286, 2699], + [3286, 2706], +] + +[[area.polygons]] +vertices = [ + [3288, 2699], + [3288, 2701], +] + +[[area.polygons]] +vertices = [ + [3295, 2699], + [3297, 2699], + [3297, 2701], + [3295, 2701], +] + +[[area.polygons]] +vertices = [ + [3310, 2699], + [3312, 2699], + [3312, 2747], + [3310, 2747], +] + +[[area.polygons]] +vertices = [ + [3285, 2700], + [3285, 2707], +] + +[[area.polygons]] +vertices = [ + [3289, 2700], + [3294, 2700], +] + +[[area.polygons]] +vertices = [ + [3308, 2700], + [3309, 2700], + [3309, 2747], + [3308, 2747], +] + +[[area.polygons]] +vertices = [ + [3284, 2701], + [3284, 2708], +] + +[[area.polygons]] +vertices = [ + [3294, 2701], + [3294, 2704], +] + +[[area.polygons]] +vertices = [ + [3307, 2701], + [3307, 2747], +] + +[[area.polygons]] +vertices = [ + [3295, 2702], + [3296, 2702], + [3296, 2703], + [3295, 2703], +] + +[[area.polygons]] +vertices = [ + [3304, 2702], + [3306, 2702], + [3306, 2747], + [3304, 2747], +] + +[[area.polygons]] +vertices = [ + [3283, 2703], + [3283, 2709], +] + +[[area.polygons]] +vertices = [[3293, 2703]] + +[[area.polygons]] +vertices = [ + [3303, 2703], + [3303, 2747], +] + +[[area.polygons]] +vertices = [ + [3282, 2704], + [3282, 2709], +] + +[[area.polygons]] +vertices = [ + [3295, 2704], + [3295, 2747], +] + +[[area.polygons]] +vertices = [ + [3302, 2704], + [3302, 2747], +] + +[[area.polygons]] +vertices = [ + [3300, 2705], + [3301, 2705], + [3301, 2747], + [3300, 2747], +] + +[[area.polygons]] +vertices = [ + [3298, 2706], + [3299, 2706], + [3299, 2747], + [3298, 2747], +] + +[[area.polygons]] +vertices = [[3281, 2707]] + +[[area.polygons]] +vertices = [ + [3297, 2707], + [3297, 2747], +] + +[[area.polygons]] +vertices = [ + [3294, 2708], + [3294, 2747], +] + +[[area.polygons]] +vertices = [ + [3296, 2708], + [3296, 2747], +] + +[[area.polygons]] +vertices = [ + [3293, 2710], + [3293, 2747], +] + +[[area.polygons]] +vertices = [ + [3395, 2710], + [3395, 2712], +] + +[[area.polygons]] +vertices = [ + [3292, 2711], + [3292, 2747], +] + +[[area.polygons]] +vertices = [ + [3291, 2714], + [3291, 2747], +] + +[[area.polygons]] +vertices = [ + [3290, 2715], + [3290, 2747], +] + +[[area.polygons]] +vertices = [ + [3392, 2715], + [3393, 2715], + [3393, 2716], + [3392, 2716], +] + +[[area.polygons]] +vertices = [ + [3289, 2717], + [3289, 2747], +] + +[[area.polygons]] +vertices = [ + [3392, 2717], + [3392, 2719], +] + +[[area.polygons]] +vertices = [ + [3288, 2718], + [3288, 2747], +] + +[[area.polygons]] +vertices = [ + [3287, 2721], + [3287, 2747], +] + +[[area.polygons]] +vertices = [ + [3286, 2722], + [3286, 2747], +] + +[[area.polygons]] +vertices = [ + [3285, 2723], + [3285, 2747], +] + +[[area.polygons]] +vertices = [[3390, 2723]] + +[[area.polygons]] +vertices = [ + [3284, 2725], + [3284, 2747], +] + +[[area.polygons]] +vertices = [ + [3386, 2725], + [3388, 2725], +] + +[[area.polygons]] +vertices = [ + [3386, 2726], + [3387, 2726], +] + +[[area.polygons]] +vertices = [[3386, 2727]] + +[[area.polygons]] +vertices = [ + [3283, 2728], + [3283, 2747], +] + +[[area.polygons]] +vertices = [ + [3281, 2729], + [3282, 2729], + [3282, 2747], + [3281, 2747], +] + +[[area.polygons]] +vertices = [[3384, 2729]] + +[[area.polygons]] +vertices = [ + [3280, 2730], + [3280, 2747], +] + +[[area.polygons]] +vertices = [[3382, 2731]] + +[[area.polygons]] +vertices = [ + [3279, 2732], + [3279, 2747], +] + +[[area.polygons]] +vertices = [ + [3374, 2733], + [3380, 2733], +] + +[[area.polygons]] +vertices = [ + [3278, 2734], + [3278, 2744], +] + +[[area.polygons]] +vertices = [ + [3374, 2734], + [3379, 2734], +] + +[[area.polygons]] +vertices = [ + [3374, 2735], + [3377, 2735], +] + +[[area.polygons]] +vertices = [ + [3277, 2736], + [3277, 2740], +] + +[[area.polygons]] +vertices = [ + [3374, 2736], + [3375, 2736], +] + +[[area.polygons]] +vertices = [[3374, 2737]] + +[[area.polygons]] +vertices = [ + [3370, 2740], + [3372, 2740], + [3372, 2741], + [3370, 2741], +] + +[[area.polygons]] +vertices = [ + [3370, 2742], + [3371, 2742], + [3371, 2743], + [3370, 2743], +] + +[[area.polygons]] +vertices = [[3370, 2744]] + +[[area.polygons]] +vertices = [ + [3362, 2746], + [3368, 2746], +] + +[[area.polygons]] +vertices = [ + [3362, 2747], + [3367, 2747], +] + +[[area.polygons]] +vertices = [ + [3362, 2748], + [3366, 2748], +] + +[[area.polygons]] +vertices = [ + [3362, 2749], + [3365, 2749], +] + +[[area.polygons]] +vertices = [ + [3362, 2750], + [3364, 2750], +] + +[[area.polygons]] +vertices = [[3362, 2751]] + +[[area.polygons]] +vertices = [ + [3355, 2752], + [3359, 2752], + [3359, 2753], + [3355, 2753], +] + +[[area.polygons]] +vertices = [ + [3355, 2754], + [3358, 2754], + [3358, 2760], + [3355, 2760], +] + +[[area.polygons]] +vertices = [ + [3359, 2755], + [3359, 2760], +] + +[[area.polygons]] +vertices = [ + [3348, 2756], + [3350, 2756], + [3350, 2760], + [3348, 2760], +] + +[[area.polygons]] +vertices = [ + [3360, 2757], + [3360, 2760], +] diff --git a/.data/raw-cache/map/area/dusk_in_yubiusk.toml b/.data/raw-cache/map/area/dusk_in_yubiusk.toml new file mode 100644 index 000000000..21473e030 --- /dev/null +++ b/.data/raw-cache/map/area/dusk_in_yubiusk.toml @@ -0,0 +1,16 @@ +# Dusk in Yu'biusk music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dusk in Yu'biusk. +# Bounds x 3456-3647, z 4288-4479; 36864 tiles as 1 rectangles. + +[[area]] +name = "Dusk in Yu'biusk" +area_id = "area.dusk_in_yubiusk" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3456, 4288], + [3647, 4288], + [3647, 4479], + [3456, 4479], +] diff --git a/.data/raw-cache/map/area/dwarf_theme.toml b/.data/raw-cache/map/area/dwarf_theme.toml new file mode 100644 index 000000000..9437ce97c --- /dev/null +++ b/.data/raw-cache/map/area/dwarf_theme.toml @@ -0,0 +1,184 @@ +# Dwarf Theme music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dwarf Theme. +# Bounds x 2988-3030, z 3439-3466; 892 tiles as 29 rectangles. + +[[area]] +name = "Dwarf Theme" +area_id = "area.dwarf_theme" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2998, 3439], + [3016, 3439], + [3016, 3461], + [2998, 3461], +] + +[[area.polygons]] +vertices = [ + [2997, 3440], + [2997, 3461], +] + +[[area.polygons]] +vertices = [ + [3017, 3440], + [3017, 3465], +] + +[[area.polygons]] +vertices = [ + [2996, 3441], + [2996, 3461], +] + +[[area.polygons]] +vertices = [ + [3018, 3441], + [3018, 3465], +] + +[[area.polygons]] +vertices = [ + [2995, 3442], + [2995, 3461], +] + +[[area.polygons]] +vertices = [ + [3019, 3442], + [3019, 3465], +] + +[[area.polygons]] +vertices = [ + [2994, 3443], + [2994, 3461], +] + +[[area.polygons]] +vertices = [ + [3020, 3443], + [3020, 3465], +] + +[[area.polygons]] +vertices = [ + [2993, 3444], + [2993, 3461], +] + +[[area.polygons]] +vertices = [ + [3021, 3444], + [3021, 3466], +] + +[[area.polygons]] +vertices = [ + [2992, 3445], + [2992, 3460], +] + +[[area.polygons]] +vertices = [ + [3022, 3445], + [3022, 3466], +] + +[[area.polygons]] +vertices = [ + [2991, 3446], + [2991, 3459], +] + +[[area.polygons]] +vertices = [ + [3023, 3446], + [3023, 3466], +] + +[[area.polygons]] +vertices = [ + [2990, 3447], + [2990, 3458], +] + +[[area.polygons]] +vertices = [ + [3024, 3447], + [3024, 3466], +] + +[[area.polygons]] +vertices = [ + [2989, 3448], + [2989, 3457], +] + +[[area.polygons]] +vertices = [ + [3025, 3448], + [3025, 3466], +] + +[[area.polygons]] +vertices = [ + [2988, 3449], + [2988, 3457], +] + +[[area.polygons]] +vertices = [ + [3026, 3449], + [3026, 3465], +] + +[[area.polygons]] +vertices = [ + [3027, 3450], + [3027, 3464], +] + +[[area.polygons]] +vertices = [ + [3028, 3451], + [3028, 3463], +] + +[[area.polygons]] +vertices = [ + [3029, 3452], + [3029, 3462], +] + +[[area.polygons]] +vertices = [ + [3030, 3453], + [3030, 3461], +] + +[[area.polygons]] +vertices = [ + [3009, 3462], + [3016, 3462], +] + +[[area.polygons]] +vertices = [ + [3010, 3463], + [3016, 3463], +] + +[[area.polygons]] +vertices = [ + [3011, 3464], + [3016, 3464], +] + +[[area.polygons]] +vertices = [ + [3012, 3465], + [3016, 3465], +] diff --git a/.data/raw-cache/map/area/dwarven_domain.toml b/.data/raw-cache/map/area/dwarven_domain.toml new file mode 100644 index 000000000..4249564aa --- /dev/null +++ b/.data/raw-cache/map/area/dwarven_domain.toml @@ -0,0 +1,738 @@ +# Dwarven Domain music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dwarven Domain. +# Bounds x 1406-1597, z 3712-3859; 16309 tiles as 111 rectangles. + +[[area]] +name = "Dwarven Domain" +area_id = "area.dwarven_domain" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1533, 3712], + [1539, 3712], + [1539, 3859], + [1533, 3859], +] + +[[area.polygons]] +vertices = [ + [1532, 3713], + [1532, 3859], +] + +[[area.polygons]] +vertices = [ + [1531, 3714], + [1531, 3859], +] + +[[area.polygons]] +vertices = [ + [1540, 3714], + [1540, 3859], +] + +[[area.polygons]] +vertices = [ + [1530, 3715], + [1530, 3859], +] + +[[area.polygons]] +vertices = [ + [1529, 3716], + [1529, 3859], +] + +[[area.polygons]] +vertices = [ + [1528, 3717], + [1528, 3859], +] + +[[area.polygons]] +vertices = [ + [1541, 3717], + [1541, 3859], +] + +[[area.polygons]] +vertices = [ + [1527, 3718], + [1527, 3839], +] + +[[area.polygons]] +vertices = [ + [1526, 3719], + [1526, 3839], +] + +[[area.polygons]] +vertices = [ + [1542, 3719], + [1542, 3859], +] + +[[area.polygons]] +vertices = [ + [1525, 3720], + [1525, 3839], +] + +[[area.polygons]] +vertices = [ + [1524, 3721], + [1524, 3839], +] + +[[area.polygons]] +vertices = [ + [1543, 3721], + [1543, 3859], +] + +[[area.polygons]] +vertices = [ + [1523, 3722], + [1523, 3839], +] + +[[area.polygons]] +vertices = [ + [1521, 3723], + [1522, 3723], + [1522, 3839], + [1521, 3839], +] + +[[area.polygons]] +vertices = [ + [1518, 3724], + [1520, 3724], + [1520, 3839], + [1518, 3839], +] + +[[area.polygons]] +vertices = [ + [1544, 3724], + [1544, 3859], +] + +[[area.polygons]] +vertices = [ + [1516, 3725], + [1517, 3725], + [1517, 3839], + [1516, 3839], +] + +[[area.polygons]] +vertices = [ + [1514, 3726], + [1515, 3726], + [1515, 3839], + [1514, 3839], +] + +[[area.polygons]] +vertices = [ + [1545, 3726], + [1545, 3859], +] + +[[area.polygons]] +vertices = [ + [1511, 3727], + [1513, 3727], + [1513, 3839], + [1511, 3839], +] + +[[area.polygons]] +vertices = [ + [1406, 3728], + [1429, 3728], + [1429, 3739], + [1406, 3739], +] + +[[area.polygons]] +vertices = [ + [1509, 3728], + [1510, 3728], + [1510, 3839], + [1509, 3839], +] + +[[area.polygons]] +vertices = [ + [1546, 3728], + [1546, 3859], +] + +[[area.polygons]] +vertices = [ + [1430, 3729], + [1430, 3753], +] + +[[area.polygons]] +vertices = [ + [1507, 3729], + [1508, 3729], + [1508, 3839], + [1507, 3839], +] + +[[area.polygons]] +vertices = [ + [1547, 3729], + [1547, 3859], +] + +[[area.polygons]] +vertices = [ + [1431, 3730], + [1431, 3753], +] + +[[area.polygons]] +vertices = [ + [1505, 3730], + [1506, 3730], + [1506, 3839], + [1505, 3839], +] + +[[area.polygons]] +vertices = [ + [1548, 3730], + [1548, 3859], +] + +[[area.polygons]] +vertices = [ + [1432, 3731], + [1432, 3753], +] + +[[area.polygons]] +vertices = [ + [1475, 3731], + [1486, 3731], + [1486, 3839], + [1475, 3839], +] + +[[area.polygons]] +vertices = [ + [1502, 3731], + [1504, 3731], + [1504, 3839], + [1502, 3839], +] + +[[area.polygons]] +vertices = [ + [1433, 3732], + [1457, 3732], + [1457, 3753], + [1433, 3753], +] + +[[area.polygons]] +vertices = [ + [1474, 3732], + [1474, 3839], +] + +[[area.polygons]] +vertices = [ + [1487, 3732], + [1488, 3732], + [1488, 3839], + [1487, 3839], +] + +[[area.polygons]] +vertices = [ + [1500, 3732], + [1501, 3732], + [1501, 3839], + [1500, 3839], +] + +[[area.polygons]] +vertices = [ + [1458, 3733], + [1458, 3844], +] + +[[area.polygons]] +vertices = [ + [1473, 3733], + [1473, 3839], +] + +[[area.polygons]] +vertices = [ + [1489, 3733], + [1499, 3733], + [1499, 3839], + [1489, 3839], +] + +[[area.polygons]] +vertices = [ + [1459, 3734], + [1459, 3844], +] + +[[area.polygons]] +vertices = [ + [1472, 3734], + [1472, 3839], +] + +[[area.polygons]] +vertices = [ + [1460, 3735], + [1460, 3844], +] + +[[area.polygons]] +vertices = [ + [1471, 3735], + [1471, 3839], +] + +[[area.polygons]] +vertices = [ + [1461, 3736], + [1470, 3736], + [1470, 3839], + [1461, 3839], +] + +[[area.polygons]] +vertices = [ + [1549, 3743], + [1569, 3743], + [1569, 3803], + [1549, 3803], +] + +[[area.polygons]] +vertices = [ + [1570, 3744], + [1571, 3744], + [1571, 3801], + [1570, 3801], +] + +[[area.polygons]] +vertices = [ + [1572, 3745], + [1573, 3745], + [1573, 3799], + [1572, 3799], +] + +[[area.polygons]] +vertices = [ + [1574, 3746], + [1575, 3746], + [1575, 3798], + [1574, 3798], +] + +[[area.polygons]] +vertices = [ + [1576, 3747], + [1578, 3747], + [1578, 3798], + [1576, 3798], +] + +[[area.polygons]] +vertices = [ + [1579, 3748], + [1580, 3748], + [1580, 3798], + [1579, 3798], +] + +[[area.polygons]] +vertices = [ + [1581, 3749], + [1582, 3749], + [1582, 3798], + [1581, 3798], +] + +[[area.polygons]] +vertices = [ + [1583, 3750], + [1583, 3798], +] + +[[area.polygons]] +vertices = [ + [1438, 3754], + [1457, 3754], + [1457, 3806], + [1438, 3806], +] + +[[area.polygons]] +vertices = [ + [1431, 3758], + [1437, 3758], + [1437, 3762], + [1431, 3762], +] + +[[area.polygons]] +vertices = [ + [1432, 3763], + [1437, 3763], +] + +[[area.polygons]] +vertices = [ + [1433, 3764], + [1437, 3764], +] + +[[area.polygons]] +vertices = [ + [1434, 3765], + [1437, 3765], + [1437, 3806], + [1434, 3806], +] + +[[area.polygons]] +vertices = [ + [1433, 3767], + [1433, 3806], +] + +[[area.polygons]] +vertices = [ + [1584, 3770], + [1585, 3770], + [1585, 3796], + [1584, 3796], +] + +[[area.polygons]] +vertices = [ + [1586, 3771], + [1588, 3771], + [1588, 3782], + [1586, 3782], +] + +[[area.polygons]] +vertices = [ + [1589, 3772], + [1592, 3772], + [1592, 3780], + [1589, 3780], +] + +[[area.polygons]] +vertices = [ + [1593, 3773], + [1595, 3773], + [1595, 3778], + [1593, 3778], +] + +[[area.polygons]] +vertices = [ + [1596, 3774], + [1596, 3778], +] + +[[area.polygons]] +vertices = [ + [1597, 3776], + [1597, 3778], +] + +[[area.polygons]] +vertices = [ + [1593, 3779], + [1594, 3779], +] + +[[area.polygons]] +vertices = [ + [1432, 3780], + [1432, 3806], +] + +[[area.polygons]] +vertices = [[1593, 3780]] + +[[area.polygons]] +vertices = [ + [1431, 3781], + [1431, 3806], +] + +[[area.polygons]] +vertices = [ + [1589, 3781], + [1591, 3781], +] + +[[area.polygons]] +vertices = [ + [1430, 3782], + [1430, 3803], +] + +[[area.polygons]] +vertices = [[1589, 3782]] + +[[area.polygons]] +vertices = [ + [1429, 3783], + [1429, 3803], +] + +[[area.polygons]] +vertices = [ + [1586, 3783], + [1587, 3783], +] + +[[area.polygons]] +vertices = [ + [1428, 3784], + [1428, 3803], +] + +[[area.polygons]] +vertices = [[1586, 3784]] + +[[area.polygons]] +vertices = [ + [1427, 3785], + [1427, 3803], +] + +[[area.polygons]] +vertices = [ + [1426, 3786], + [1426, 3803], +] + +[[area.polygons]] +vertices = [ + [1425, 3787], + [1425, 3803], +] + +[[area.polygons]] +vertices = [ + [1424, 3788], + [1424, 3803], +] + +[[area.polygons]] +vertices = [ + [1423, 3789], + [1423, 3803], +] + +[[area.polygons]] +vertices = [ + [1422, 3790], + [1422, 3803], +] + +[[area.polygons]] +vertices = [ + [1421, 3791], + [1421, 3803], +] + +[[area.polygons]] +vertices = [ + [1420, 3792], + [1420, 3803], +] + +[[area.polygons]] +vertices = [[1584, 3797]] + +[[area.polygons]] +vertices = [[1572, 3800]] + +[[area.polygons]] +vertices = [[1570, 3802]] + +[[area.polygons]] +vertices = [ + [1549, 3804], + [1568, 3804], +] + +[[area.polygons]] +vertices = [ + [1549, 3805], + [1567, 3805], +] + +[[area.polygons]] +vertices = [ + [1549, 3806], + [1566, 3806], +] + +[[area.polygons]] +vertices = [ + [1440, 3807], + [1457, 3807], + [1457, 3811], + [1440, 3811], +] + +[[area.polygons]] +vertices = [ + [1549, 3807], + [1565, 3807], +] + +[[area.polygons]] +vertices = [ + [1549, 3808], + [1564, 3808], +] + +[[area.polygons]] +vertices = [ + [1549, 3809], + [1563, 3809], +] + +[[area.polygons]] +vertices = [ + [1549, 3810], + [1562, 3810], +] + +[[area.polygons]] +vertices = [ + [1549, 3811], + [1561, 3811], + [1561, 3828], + [1549, 3828], +] + +[[area.polygons]] +vertices = [ + [1443, 3812], + [1457, 3812], + [1457, 3817], + [1443, 3817], +] + +[[area.polygons]] +vertices = [ + [1445, 3818], + [1457, 3818], + [1457, 3822], + [1445, 3822], +] + +[[area.polygons]] +vertices = [ + [1446, 3823], + [1457, 3823], + [1457, 3825], + [1446, 3825], +] + +[[area.polygons]] +vertices = [ + [1448, 3826], + [1457, 3826], + [1457, 3830], + [1448, 3830], +] + +[[area.polygons]] +vertices = [ + [1549, 3829], + [1560, 3829], +] + +[[area.polygons]] +vertices = [ + [1549, 3830], + [1559, 3830], +] + +[[area.polygons]] +vertices = [ + [1453, 3831], + [1457, 3831], + [1457, 3835], + [1453, 3835], +] + +[[area.polygons]] +vertices = [ + [1549, 3831], + [1558, 3831], +] + +[[area.polygons]] +vertices = [ + [1549, 3832], + [1557, 3832], +] + +[[area.polygons]] +vertices = [ + [1549, 3833], + [1556, 3833], +] + +[[area.polygons]] +vertices = [ + [1549, 3834], + [1555, 3834], +] + +[[area.polygons]] +vertices = [ + [1549, 3835], + [1554, 3835], + [1554, 3859], + [1549, 3859], +] + +[[area.polygons]] +vertices = [ + [1456, 3836], + [1457, 3836], + [1457, 3844], + [1456, 3844], +] + +[[area.polygons]] +vertices = [ + [1461, 3840], + [1469, 3840], + [1469, 3844], + [1461, 3844], +] diff --git a/.data/raw-cache/map/area/dynasty.toml b/.data/raw-cache/map/area/dynasty.toml new file mode 100644 index 000000000..69037b0e4 --- /dev/null +++ b/.data/raw-cache/map/area/dynasty.toml @@ -0,0 +1,391 @@ +# Dynasty music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Dynasty. +# Bounds x 3319-3382, z 2928-3020; 4000 tiles as 62 rectangles. + +[[area]] +name = "Dynasty" +area_id = "area.dynasty" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3335, 2928], + [3349, 2928], + [3349, 3005], + [3335, 3005], +] + +[[area.polygons]] +vertices = [ + [3334, 2929], + [3334, 3003], +] + +[[area.polygons]] +vertices = [ + [3350, 2929], + [3350, 3020], +] + +[[area.polygons]] +vertices = [ + [3333, 2930], + [3333, 3001], +] + +[[area.polygons]] +vertices = [ + [3351, 2930], + [3351, 3020], +] + +[[area.polygons]] +vertices = [ + [3332, 2931], + [3332, 2980], +] + +[[area.polygons]] +vertices = [ + [3352, 2931], + [3352, 3020], +] + +[[area.polygons]] +vertices = [ + [3331, 2932], + [3331, 2979], +] + +[[area.polygons]] +vertices = [ + [3353, 2932], + [3353, 3020], +] + +[[area.polygons]] +vertices = [ + [3330, 2933], + [3330, 2978], +] + +[[area.polygons]] +vertices = [ + [3354, 2933], + [3354, 3020], +] + +[[area.polygons]] +vertices = [ + [3329, 2934], + [3329, 2977], +] + +[[area.polygons]] +vertices = [ + [3355, 2934], + [3355, 3020], +] + +[[area.polygons]] +vertices = [ + [3328, 2935], + [3328, 2976], +] + +[[area.polygons]] +vertices = [ + [3356, 2935], + [3356, 3020], +] + +[[area.polygons]] +vertices = [ + [3327, 2936], + [3327, 2975], +] + +[[area.polygons]] +vertices = [ + [3357, 2936], + [3357, 3020], +] + +[[area.polygons]] +vertices = [ + [3326, 2937], + [3326, 2974], +] + +[[area.polygons]] +vertices = [ + [3358, 2937], + [3358, 3020], +] + +[[area.polygons]] +vertices = [ + [3325, 2938], + [3325, 2973], +] + +[[area.polygons]] +vertices = [ + [3359, 2938], + [3359, 3020], +] + +[[area.polygons]] +vertices = [ + [3324, 2939], + [3324, 2972], +] + +[[area.polygons]] +vertices = [ + [3323, 2940], + [3323, 2971], +] + +[[area.polygons]] +vertices = [ + [3322, 2941], + [3322, 2970], +] + +[[area.polygons]] +vertices = [ + [3321, 2942], + [3321, 2969], +] + +[[area.polygons]] +vertices = [ + [3320, 2943], + [3320, 2968], +] + +[[area.polygons]] +vertices = [ + [3319, 2944], + [3319, 2967], +] + +[[area.polygons]] +vertices = [ + [3360, 2947], + [3360, 3020], +] + +[[area.polygons]] +vertices = [ + [3361, 2948], + [3361, 3020], +] + +[[area.polygons]] +vertices = [ + [3362, 2949], + [3362, 3020], +] + +[[area.polygons]] +vertices = [ + [3363, 2950], + [3363, 3020], +] + +[[area.polygons]] +vertices = [ + [3364, 2951], + [3365, 2951], + [3365, 3017], + [3364, 3017], +] + +[[area.polygons]] +vertices = [ + [3366, 2952], + [3366, 3015], +] + +[[area.polygons]] +vertices = [ + [3367, 2953], + [3367, 3013], +] + +[[area.polygons]] +vertices = [ + [3368, 2954], + [3368, 3011], +] + +[[area.polygons]] +vertices = [ + [3369, 2955], + [3369, 3009], +] + +[[area.polygons]] +vertices = [ + [3370, 2956], + [3371, 2956], + [3371, 3005], + [3370, 3005], +] + +[[area.polygons]] +vertices = [ + [3372, 2957], + [3372, 3003], +] + +[[area.polygons]] +vertices = [ + [3373, 2958], + [3373, 3001], +] + +[[area.polygons]] +vertices = [ + [3374, 2959], + [3374, 2999], +] + +[[area.polygons]] +vertices = [ + [3375, 2960], + [3379, 2960], + [3379, 2993], + [3375, 2993], +] + +[[area.polygons]] +vertices = [ + [3380, 2961], + [3380, 2990], +] + +[[area.polygons]] +vertices = [ + [3381, 2964], + [3381, 2987], +] + +[[area.polygons]] +vertices = [ + [3382, 2967], + [3382, 2984], +] + +[[area.polygons]] +vertices = [ + [3375, 2994], + [3378, 2994], +] + +[[area.polygons]] +vertices = [ + [3375, 2995], + [3377, 2995], +] + +[[area.polygons]] +vertices = [ + [3375, 2996], + [3376, 2996], +] + +[[area.polygons]] +vertices = [[3375, 2997]] + +[[area.polygons]] +vertices = [ + [3336, 3006], + [3349, 3006], + [3349, 3007], + [3336, 3007], +] + +[[area.polygons]] +vertices = [ + [3370, 3006], + [3370, 3007], +] + +[[area.polygons]] +vertices = [ + [3337, 3008], + [3349, 3008], + [3349, 3009], + [3337, 3009], +] + +[[area.polygons]] +vertices = [ + [3338, 3010], + [3349, 3010], + [3349, 3011], + [3338, 3011], +] + +[[area.polygons]] +vertices = [ + [3339, 3012], + [3349, 3012], +] + +[[area.polygons]] +vertices = [ + [3340, 3013], + [3349, 3013], +] + +[[area.polygons]] +vertices = [ + [3341, 3014], + [3349, 3014], +] + +[[area.polygons]] +vertices = [ + [3342, 3015], + [3349, 3015], +] + +[[area.polygons]] +vertices = [ + [3343, 3016], + [3349, 3016], +] + +[[area.polygons]] +vertices = [ + [3344, 3017], + [3349, 3017], +] + +[[area.polygons]] +vertices = [ + [3345, 3018], + [3349, 3018], +] + +[[area.polygons]] +vertices = [ + [3364, 3018], + [3364, 3019], +] + +[[area.polygons]] +vertices = [ + [3346, 3019], + [3349, 3019], +] + +[[area.polygons]] +vertices = [ + [3348, 3020], + [3349, 3020], +] diff --git a/.data/raw-cache/map/area/eagles_peak.toml b/.data/raw-cache/map/area/eagles_peak.toml new file mode 100644 index 000000000..4dd5b171c --- /dev/null +++ b/.data/raw-cache/map/area/eagles_peak.toml @@ -0,0 +1,334 @@ +# Eagles' Peak music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Eagles' Peak. +# Bounds x 2300-2380, z 3435-3519; 4203 tiles as 50 rectangles. + +[[area]] +name = "Eagles' Peak" +area_id = "area.eagles_peak" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2348, 3435], + [2370, 3435], + [2370, 3486], + [2348, 3486], +] + +[[area.polygons]] +vertices = [ + [2371, 3444], + [2371, 3485], +] + +[[area.polygons]] +vertices = [ + [2372, 3445], + [2372, 3485], +] + +[[area.polygons]] +vertices = [ + [2373, 3446], + [2373, 3480], +] + +[[area.polygons]] +vertices = [ + [2374, 3447], + [2374, 3478], +] + +[[area.polygons]] +vertices = [ + [2375, 3459], + [2375, 3477], +] + +[[area.polygons]] +vertices = [ + [2376, 3460], + [2376, 3476], +] + +[[area.polygons]] +vertices = [ + [2377, 3461], + [2377, 3475], +] + +[[area.polygons]] +vertices = [ + [2378, 3462], + [2378, 3474], +] + +[[area.polygons]] +vertices = [ + [2379, 3463], + [2379, 3473], +] + +[[area.polygons]] +vertices = [ + [2335, 3464], + [2347, 3464], + [2347, 3519], + [2335, 3519], +] + +[[area.polygons]] +vertices = [ + [2380, 3464], + [2380, 3472], +] + +[[area.polygons]] +vertices = [ + [2333, 3465], + [2334, 3465], + [2334, 3519], + [2333, 3519], +] + +[[area.polygons]] +vertices = [ + [2331, 3466], + [2332, 3466], + [2332, 3519], + [2331, 3519], +] + +[[area.polygons]] +vertices = [ + [2329, 3467], + [2330, 3467], + [2330, 3519], + [2329, 3519], +] + +[[area.polygons]] +vertices = [ + [2327, 3468], + [2328, 3468], + [2328, 3519], + [2327, 3519], +] + +[[area.polygons]] +vertices = [ + [2325, 3469], + [2326, 3469], + [2326, 3519], + [2325, 3519], +] + +[[area.polygons]] +vertices = [ + [2323, 3470], + [2324, 3470], + [2324, 3519], + [2323, 3519], +] + +[[area.polygons]] +vertices = [ + [2321, 3471], + [2322, 3471], + [2322, 3519], + [2321, 3519], +] + +[[area.polygons]] +vertices = [ + [2320, 3472], + [2320, 3519], +] + +[[area.polygons]] +vertices = [ + [2319, 3473], + [2319, 3519], +] + +[[area.polygons]] +vertices = [ + [2318, 3475], + [2318, 3519], +] + +[[area.polygons]] +vertices = [ + [2317, 3477], + [2317, 3519], +] + +[[area.polygons]] +vertices = [ + [2316, 3479], + [2316, 3519], +] + +[[area.polygons]] +vertices = [ + [2315, 3481], + [2315, 3519], +] + +[[area.polygons]] +vertices = [ + [2314, 3483], + [2314, 3519], +] + +[[area.polygons]] +vertices = [ + [2313, 3485], + [2313, 3519], +] + +[[area.polygons]] +vertices = [ + [2312, 3487], + [2312, 3519], +] + +[[area.polygons]] +vertices = [ + [2348, 3487], + [2368, 3487], +] + +[[area.polygons]] +vertices = [ + [2311, 3488], + [2311, 3519], +] + +[[area.polygons]] +vertices = [ + [2348, 3488], + [2367, 3488], + [2367, 3498], + [2348, 3498], +] + +[[area.polygons]] +vertices = [ + [2310, 3489], + [2310, 3519], +] + +[[area.polygons]] +vertices = [ + [2309, 3490], + [2309, 3519], +] + +[[area.polygons]] +vertices = [ + [2308, 3491], + [2308, 3519], +] + +[[area.polygons]] +vertices = [ + [2307, 3492], + [2307, 3519], +] + +[[area.polygons]] +vertices = [ + [2306, 3493], + [2306, 3519], +] + +[[area.polygons]] +vertices = [ + [2368, 3493], + [2368, 3497], +] + +[[area.polygons]] +vertices = [ + [2305, 3494], + [2305, 3519], +] + +[[area.polygons]] +vertices = [ + [2369, 3494], + [2369, 3496], +] + +[[area.polygons]] +vertices = [ + [2304, 3495], + [2304, 3519], +] + +[[area.polygons]] +vertices = [ + [2303, 3496], + [2303, 3519], +] + +[[area.polygons]] +vertices = [ + [2302, 3497], + [2302, 3519], +] + +[[area.polygons]] +vertices = [ + [2301, 3498], + [2301, 3519], +] + +[[area.polygons]] +vertices = [ + [2300, 3499], + [2300, 3519], +] + +[[area.polygons]] +vertices = [ + [2348, 3499], + [2366, 3499], + [2366, 3505], + [2348, 3505], +] + +[[area.polygons]] +vertices = [ + [2348, 3506], + [2365, 3506], + [2365, 3516], + [2348, 3516], +] + +[[area.polygons]] +vertices = [ + [2366, 3509], + [2366, 3511], +] + +[[area.polygons]] +vertices = [ + [2366, 3514], + [2366, 3515], +] + +[[area.polygons]] +vertices = [ + [2348, 3517], + [2363, 3517], +] + +[[area.polygons]] +vertices = [ + [2348, 3518], + [2362, 3518], + [2362, 3519], + [2348, 3519], +] diff --git a/.data/raw-cache/map/area/east_ardougne.toml b/.data/raw-cache/map/area/east_ardougne.toml new file mode 100644 index 000000000..f8fad00dd --- /dev/null +++ b/.data/raw-cache/map/area/east_ardougne.toml @@ -0,0 +1,172 @@ +# Inside the city walls: castle, market, zoo, docks and the north gate road. +# +# Regenerated as rectangles from the OSRS wiki music map: 670 tiles moved to +# neighbouring music areas. + +[[area]] +name = "East Ardougne" +area_id = "area.east_ardougne" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2559, 3263], + [2690, 3263], + [2690, 3264], + [2559, 3264], +] + +[[area.polygons]] +vertices = [ + [2559, 3265], + [2688, 3265], + [2688, 3268], + [2559, 3268], +] + +[[area.polygons]] +vertices = [ + [2559, 3269], + [2687, 3269], + [2687, 3287], + [2559, 3287], +] + +[[area.polygons]] +vertices = [ + [2688, 3272], + [2688, 3273], +] + +[[area.polygons]] +vertices = [ + [2559, 3288], + [2686, 3288], +] + +[[area.polygons]] +vertices = [ + [2559, 3289], + [2685, 3289], + [2685, 3329], + [2559, 3329], +] + +[[area.polygons]] +vertices = [ + [2686, 3298], + [2686, 3328], +] + +[[area.polygons]] +vertices = [ + [2687, 3299], + [2687, 3310], +] + +[[area.polygons]] +vertices = [ + [2687, 3314], + [2687, 3327], +] + +[[area.polygons]] +vertices = [ + [2559, 3330], + [2684, 3330], +] + +[[area.polygons]] +vertices = [ + [2559, 3331], + [2683, 3331], +] + +[[area.polygons]] +vertices = [ + [2559, 3332], + [2682, 3332], +] + +[[area.polygons]] +vertices = [ + [2559, 3333], + [2681, 3333], +] + +[[area.polygons]] +vertices = [ + [2559, 3334], + [2672, 3334], +] + +[[area.polygons]] +vertices = [ + [2559, 3335], + [2671, 3335], +] + +[[area.polygons]] +vertices = [ + [2559, 3336], + [2670, 3336], +] + +[[area.polygons]] +vertices = [ + [2560, 3337], + [2669, 3337], +] + +[[area.polygons]] +vertices = [ + [2560, 3338], + [2668, 3338], +] + +[[area.polygons]] +vertices = [ + [2561, 3339], + [2667, 3339], +] + +[[area.polygons]] +vertices = [ + [2562, 3340], + [2622, 3340], +] + +[[area.polygons]] +vertices = [ + [2563, 3341], + [2622, 3341], +] + +[[area.polygons]] +vertices = [ + [2598, 3342], + [2622, 3342], + [2622, 3349], + [2598, 3349], +] + +[[area.polygons]] +vertices = [ + [2597, 3345], + [2597, 3349], +] + +[[area.polygons]] +vertices = [ + [2596, 3347], + [2596, 3349], +] + +[[area.polygons]] +vertices = [ + [2595, 3348], + [2595, 3349], +] + +[[area.polygons]] +vertices = [[2594, 3349]] diff --git a/.data/raw-cache/map/area/east_ardougne_music.toml b/.data/raw-cache/map/area/east_ardougne_music.toml new file mode 100644 index 000000000..d376a17cd --- /dev/null +++ b/.data/raw-cache/map/area/east_ardougne_music.toml @@ -0,0 +1,141 @@ +# East Ardougne music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Baroque; The Tower; Knightly. +# Bounds x 2509-2576, z 9694-9774; 590 tiles as 20 rectangles. + +[[area]] +name = "East Ardougne" +area_id = "area.east_ardougne_music" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2538, 9694], + [2548, 9694], + [2548, 9697], + [2538, 9697], +] + +[[area.polygons]] +vertices = [ + [2538, 9698], + [2541, 9698], + [2541, 9699], + [2538, 9699], +] + +[[area.polygons]] +vertices = [ + [2569, 9704], + [2576, 9704], + [2576, 9715], + [2569, 9715], +] + +[[area.polygons]] +vertices = [ + [2564, 9707], + [2568, 9707], + [2568, 9709], + [2564, 9709], +] + +[[area.polygons]] +vertices = [ + [2564, 9710], + [2565, 9710], + [2565, 9712], + [2564, 9712], +] + +[[area.polygons]] +vertices = [ + [2512, 9738], + [2515, 9738], + [2515, 9766], + [2512, 9766], +] + +[[area.polygons]] +vertices = [ + [2511, 9739], + [2511, 9765], +] + +[[area.polygons]] +vertices = [ + [2516, 9739], + [2516, 9770], +] + +[[area.polygons]] +vertices = [ + [2510, 9740], + [2510, 9764], +] + +[[area.polygons]] +vertices = [ + [2517, 9740], + [2517, 9771], +] + +[[area.polygons]] +vertices = [ + [2509, 9741], + [2509, 9763], +] + +[[area.polygons]] +vertices = [ + [2518, 9741], + [2518, 9772], +] + +[[area.polygons]] +vertices = [ + [2519, 9742], + [2519, 9773], +] + +[[area.polygons]] +vertices = [ + [2520, 9763], + [2522, 9763], + [2522, 9774], + [2520, 9774], +] + +[[area.polygons]] +vertices = [ + [2523, 9764], + [2526, 9764], + [2526, 9774], + [2523, 9774], +] + +[[area.polygons]] +vertices = [ + [2527, 9765], + [2527, 9773], +] + +[[area.polygons]] +vertices = [ + [2528, 9766], + [2528, 9772], +] + +[[area.polygons]] +vertices = [ + [2513, 9767], + [2515, 9767], +] + +[[area.polygons]] +vertices = [ + [2514, 9768], + [2515, 9768], +] + +[[area.polygons]] +vertices = [[2515, 9769]] diff --git a/.data/raw-cache/map/area/edgeville.toml b/.data/raw-cache/map/area/edgeville.toml new file mode 100644 index 000000000..09c5528e6 --- /dev/null +++ b/.data/raw-cache/map/area/edgeville.toml @@ -0,0 +1,103 @@ +[[area]] +name = "Edgeville" +area_id = "area.edgeville" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3070, 3465], + [3130, 3465], + [3130, 3523], + [3070, 3523], +] + +# Extension traced from the OSRS wiki music map (895 tiles). + +[[area.polygons]] +vertices = [ + [3066, 3456], + [3102, 3456], + [3102, 3464], + [3066, 3464], +] + +[[area.polygons]] +vertices = [ + [3108, 3462], + [3119, 3462], + [3119, 3464], + [3108, 3464], +] + +[[area.polygons]] +vertices = [ + [3107, 3463], + [3107, 3464], +] + +[[area.polygons]] +vertices = [ + [3120, 3463], + [3120, 3464], +] + +[[area.polygons]] +vertices = [ + [3103, 3464], + [3106, 3464], +] + +[[area.polygons]] +vertices = [[3121, 3464]] + +[[area.polygons]] +vertices = [ + [3066, 3465], + [3069, 3465], + [3069, 3522], + [3066, 3522], +] + +[[area.polygons]] +vertices = [ + [3131, 3465], + [3131, 3522], +] + +[[area.polygons]] +vertices = [ + [3132, 3466], + [3132, 3522], +] + +[[area.polygons]] +vertices = [ + [3133, 3467], + [3133, 3522], +] + +[[area.polygons]] +vertices = [ + [3134, 3468], + [3134, 3522], +] + +[[area.polygons]] +vertices = [ + [3135, 3516], + [3141, 3516], + [3141, 3522], + [3135, 3522], +] + +[[area.polygons]] +vertices = [ + [3142, 3517], + [3142, 3522], +] + +[[area.polygons]] +vertices = [ + [3065, 3519], + [3065, 3522], +] diff --git a/.data/raw-cache/map/area/elder_wisdom.toml b/.data/raw-cache/map/area/elder_wisdom.toml new file mode 100644 index 000000000..c22f7390f --- /dev/null +++ b/.data/raw-cache/map/area/elder_wisdom.toml @@ -0,0 +1,189 @@ +# Elder Wisdom music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Elder Wisdom. +# Bounds x 3340-3371, z 2184-2217; 682 tiles as 29 rectangles. + +[[area]] +name = "Elder Wisdom" +area_id = "area.elder_wisdom" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3340, 2184], + [3341, 2184], + [3341, 2189], + [3340, 2189], +] + +[[area.polygons]] +vertices = [ + [3342, 2185], + [3342, 2191], +] + +[[area.polygons]] +vertices = [ + [3343, 2186], + [3346, 2186], + [3346, 2193], + [3343, 2193], +] + +[[area.polygons]] +vertices = [ + [3347, 2187], + [3348, 2187], + [3348, 2212], + [3347, 2212], +] + +[[area.polygons]] +vertices = [ + [3349, 2188], + [3349, 2213], +] + +[[area.polygons]] +vertices = [[3341, 2190]] + +[[area.polygons]] +vertices = [ + [3350, 2190], + [3351, 2190], + [3351, 2213], + [3350, 2213], +] + +[[area.polygons]] +vertices = [ + [3352, 2191], + [3352, 2215], +] + +[[area.polygons]] +vertices = [ + [3361, 2191], + [3363, 2191], + [3363, 2216], + [3361, 2216], +] + +[[area.polygons]] +vertices = [ + [3353, 2192], + [3353, 2216], +] + +[[area.polygons]] +vertices = [ + [3357, 2192], + [3360, 2192], + [3360, 2215], + [3357, 2215], +] + +[[area.polygons]] +vertices = [ + [3354, 2193], + [3356, 2193], + [3356, 2216], + [3354, 2216], +] + +[[area.polygons]] +vertices = [ + [3364, 2193], + [3364, 2217], +] + +[[area.polygons]] +vertices = [ + [3344, 2194], + [3346, 2194], +] + +[[area.polygons]] +vertices = [ + [3365, 2194], + [3365, 2217], +] + +[[area.polygons]] +vertices = [ + [3345, 2195], + [3346, 2195], + [3346, 2209], + [3345, 2209], +] + +[[area.polygons]] +vertices = [ + [3366, 2195], + [3366, 2216], +] + +[[area.polygons]] +vertices = [ + [3367, 2196], + [3367, 2216], +] + +[[area.polygons]] +vertices = [ + [3368, 2197], + [3368, 2214], +] + +[[area.polygons]] +vertices = [ + [3344, 2198], + [3344, 2208], +] + +[[area.polygons]] +vertices = [ + [3369, 2198], + [3369, 2213], +] + +[[area.polygons]] +vertices = [ + [3343, 2200], + [3343, 2207], +] + +[[area.polygons]] +vertices = [ + [3370, 2201], + [3370, 2211], +] + +[[area.polygons]] +vertices = [ + [3342, 2203], + [3342, 2207], +] + +[[area.polygons]] +vertices = [ + [3371, 2203], + [3371, 2210], +] + +[[area.polygons]] +vertices = [ + [3346, 2210], + [3346, 2211], +] + +[[area.polygons]] +vertices = [[3348, 2213]] + +[[area.polygons]] +vertices = [[3351, 2214]] + +[[area.polygons]] +vertices = [ + [3362, 2217], + [3363, 2217], +] diff --git a/.data/raw-cache/map/area/elven_seed.toml b/.data/raw-cache/map/area/elven_seed.toml new file mode 100644 index 000000000..d5e0540fc --- /dev/null +++ b/.data/raw-cache/map/area/elven_seed.toml @@ -0,0 +1,141 @@ +# Elven Seed music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Elven Seed. +# Bounds x 2184-2220, z 3392-3450; 1764 tiles as 20 rectangles. + +[[area]] +name = "Elven Seed" +area_id = "area.elven_seed" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2184, 3392], + [2217, 3392], + [2217, 3423], + [2184, 3423], +] + +[[area.polygons]] +vertices = [ + [2218, 3408], + [2220, 3408], + [2220, 3423], + [2218, 3423], +] + +[[area.polygons]] +vertices = [ + [2184, 3424], + [2214, 3424], +] + +[[area.polygons]] +vertices = [ + [2216, 3424], + [2219, 3424], +] + +[[area.polygons]] +vertices = [ + [2184, 3425], + [2213, 3425], + [2213, 3429], + [2184, 3429], +] + +[[area.polygons]] +vertices = [ + [2214, 3427], + [2214, 3428], +] + +[[area.polygons]] +vertices = [ + [2184, 3430], + [2212, 3430], + [2212, 3431], + [2184, 3431], +] + +[[area.polygons]] +vertices = [ + [2184, 3432], + [2211, 3432], + [2211, 3434], + [2184, 3434], +] + +[[area.polygons]] +vertices = [ + [2184, 3435], + [2210, 3435], + [2210, 3436], + [2184, 3436], +] + +[[area.polygons]] +vertices = [ + [2184, 3437], + [2209, 3437], + [2209, 3441], + [2184, 3441], +] + +[[area.polygons]] +vertices = [ + [2210, 3438], + [2210, 3439], +] + +[[area.polygons]] +vertices = [[2211, 3439]] + +[[area.polygons]] +vertices = [ + [2184, 3442], + [2208, 3442], +] + +[[area.polygons]] +vertices = [ + [2184, 3443], + [2200, 3443], +] + +[[area.polygons]] +vertices = [ + [2205, 3443], + [2207, 3443], +] + +[[area.polygons]] +vertices = [ + [2184, 3444], + [2197, 3444], +] + +[[area.polygons]] +vertices = [ + [2184, 3445], + [2194, 3445], +] + +[[area.polygons]] +vertices = [ + [2184, 3446], + [2192, 3446], + [2192, 3448], + [2184, 3448], +] + +[[area.polygons]] +vertices = [ + [2184, 3449], + [2191, 3449], +] + +[[area.polygons]] +vertices = [ + [2184, 3450], + [2190, 3450], +] diff --git a/.data/raw-cache/map/area/emirs_arena.toml b/.data/raw-cache/map/area/emirs_arena.toml new file mode 100644 index 000000000..c6326d199 --- /dev/null +++ b/.data/raw-cache/map/area/emirs_arena.toml @@ -0,0 +1,73 @@ +[[area]] +name = "Emir's Arena" +area_id = "area.emirs_arena" +levels = [0, 1] + +# The whole complex north-east of Al Kharid: the welcome centre and ring of dueling landing on +# the west side, the four walled combat areas with their spectator walkways, and the hospital, +# chapel and bank along the north. +[[area.polygons]] +vertices = [ + [3308, 3200], + [3392, 3200], + [3392, 3292], + [3308, 3292], +] + +# Same footprint again under a second id for the music table: the music plugin registers its own +# enter/exit handlers per music area, and the arena plugin already owns area.emirs_arena. +[[area]] +name = "Emir's Arena (music)" +area_id = "area.emirs_arena_music" +levels = [0, 1] + +[[area.polygons]] +vertices = [ + [3308, 3200], + [3392, 3200], + [3392, 3292], + [3308, 3292], +] + +# Extension traced from the OSRS wiki music map (2066 tiles). + +[[area.polygons]] +vertices = [ + [3328, 3196], + [3330, 3196], + [3330, 3199], + [3328, 3199], +] + +[[area.polygons]] +vertices = [ + [3326, 3197], + [3327, 3197], + [3327, 3199], + [3326, 3199], +] + +[[area.polygons]] +vertices = [ + [3331, 3197], + [3331, 3199], +] + +[[area.polygons]] +vertices = [ + [3332, 3198], + [3333, 3198], + [3333, 3199], + [3332, 3199], +] + +[[area.polygons]] +vertices = [[3334, 3199]] + +[[area.polygons]] +vertices = [ + [3393, 3200], + [3422, 3200], + [3422, 3267], + [3393, 3267], +] diff --git a/.data/raw-cache/map/area/emissaries_of_twilight.toml b/.data/raw-cache/map/area/emissaries_of_twilight.toml new file mode 100644 index 000000000..b0c842a1c --- /dev/null +++ b/.data/raw-cache/map/area/emissaries_of_twilight.toml @@ -0,0 +1,401 @@ +# Emissaries of Twilight music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Emissaries of Twilight. +# Bounds x 1627-1716, z 3208-3282; 3945 tiles as 62 rectangles. + +[[area]] +name = "Emissaries of Twilight" +area_id = "area.emissaries_of_twilight" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1635, 3208], + [1648, 3208], + [1648, 3241], + [1635, 3241], +] + +[[area.polygons]] +vertices = [ + [1634, 3209], + [1634, 3241], +] + +[[area.polygons]] +vertices = [ + [1649, 3209], + [1649, 3269], +] + +[[area.polygons]] +vertices = [ + [1627, 3210], + [1633, 3210], + [1633, 3221], + [1627, 3221], +] + +[[area.polygons]] +vertices = [ + [1650, 3210], + [1650, 3271], +] + +[[area.polygons]] +vertices = [ + [1651, 3211], + [1652, 3211], + [1652, 3272], + [1651, 3272], +] + +[[area.polygons]] +vertices = [ + [1653, 3212], + [1653, 3275], +] + +[[area.polygons]] +vertices = [ + [1654, 3213], + [1654, 3277], +] + +[[area.polygons]] +vertices = [ + [1655, 3214], + [1655, 3278], +] + +[[area.polygons]] +vertices = [ + [1656, 3215], + [1657, 3215], + [1657, 3280], + [1656, 3280], +] + +[[area.polygons]] +vertices = [ + [1658, 3216], + [1658, 3282], +] + +[[area.polygons]] +vertices = [ + [1659, 3217], + [1659, 3282], +] + +[[area.polygons]] +vertices = [ + [1660, 3218], + [1671, 3218], + [1671, 3272], + [1660, 3272], +] + +[[area.polygons]] +vertices = [ + [1628, 3222], + [1633, 3222], + [1633, 3225], + [1628, 3225], +] + +[[area.polygons]] +vertices = [ + [1629, 3226], + [1633, 3226], + [1633, 3228], + [1629, 3228], +] + +[[area.polygons]] +vertices = [ + [1672, 3226], + [1698, 3226], + [1698, 3271], + [1672, 3271], +] + +[[area.polygons]] +vertices = [ + [1630, 3229], + [1633, 3229], + [1633, 3232], + [1630, 3232], +] + +[[area.polygons]] +vertices = [ + [1631, 3233], + [1633, 3233], + [1633, 3236], + [1631, 3236], +] + +[[area.polygons]] +vertices = [ + [1699, 3235], + [1699, 3279], +] + +[[area.polygons]] +vertices = [ + [1700, 3236], + [1700, 3279], +] + +[[area.polygons]] +vertices = [ + [1632, 3237], + [1633, 3237], + [1633, 3239], + [1632, 3239], +] + +[[area.polygons]] +vertices = [ + [1701, 3237], + [1701, 3279], +] + +[[area.polygons]] +vertices = [ + [1702, 3238], + [1702, 3278], +] + +[[area.polygons]] +vertices = [ + [1703, 3239], + [1703, 3277], +] + +[[area.polygons]] +vertices = [ + [1633, 3240], + [1633, 3241], +] + +[[area.polygons]] +vertices = [ + [1638, 3242], + [1648, 3242], +] + +[[area.polygons]] +vertices = [ + [1639, 3243], + [1648, 3243], +] + +[[area.polygons]] +vertices = [ + [1642, 3244], + [1648, 3244], +] + +[[area.polygons]] +vertices = [ + [1704, 3244], + [1704, 3276], +] + +[[area.polygons]] +vertices = [ + [1643, 3245], + [1648, 3245], +] + +[[area.polygons]] +vertices = [ + [1705, 3245], + [1705, 3275], +] + +[[area.polygons]] +vertices = [ + [1644, 3246], + [1648, 3246], +] + +[[area.polygons]] +vertices = [ + [1706, 3246], + [1706, 3274], +] + +[[area.polygons]] +vertices = [ + [1645, 3247], + [1648, 3247], +] + +[[area.polygons]] +vertices = [ + [1707, 3247], + [1707, 3273], +] + +[[area.polygons]] +vertices = [ + [1646, 3248], + [1648, 3248], +] + +[[area.polygons]] +vertices = [ + [1708, 3248], + [1708, 3272], +] + +[[area.polygons]] +vertices = [ + [1647, 3249], + [1648, 3249], +] + +[[area.polygons]] +vertices = [ + [1648, 3250], + [1648, 3268], +] + +[[area.polygons]] +vertices = [ + [1709, 3253], + [1709, 3271], +] + +[[area.polygons]] +vertices = [ + [1710, 3254], + [1710, 3271], +] + +[[area.polygons]] +vertices = [ + [1711, 3255], + [1711, 3271], +] + +[[area.polygons]] +vertices = [ + [1712, 3256], + [1712, 3271], +] + +[[area.polygons]] +vertices = [ + [1713, 3264], + [1713, 3271], +] + +[[area.polygons]] +vertices = [ + [1714, 3265], + [1714, 3271], +] + +[[area.polygons]] +vertices = [ + [1715, 3266], + [1715, 3271], +] + +[[area.polygons]] +vertices = [ + [1716, 3267], + [1716, 3271], +] + +[[area.polygons]] +vertices = [ + [1683, 3272], + [1698, 3272], +] + +[[area.polygons]] +vertices = [ + [1652, 3273], + [1652, 3274], +] + +[[area.polygons]] +vertices = [ + [1660, 3273], + [1669, 3273], +] + +[[area.polygons]] +vertices = [ + [1684, 3273], + [1698, 3273], +] + +[[area.polygons]] +vertices = [ + [1660, 3274], + [1667, 3274], +] + +[[area.polygons]] +vertices = [ + [1685, 3274], + [1698, 3274], +] + +[[area.polygons]] +vertices = [ + [1660, 3275], + [1665, 3275], +] + +[[area.polygons]] +vertices = [ + [1686, 3275], + [1698, 3275], +] + +[[area.polygons]] +vertices = [ + [1660, 3276], + [1663, 3276], +] + +[[area.polygons]] +vertices = [ + [1687, 3276], + [1698, 3276], +] + +[[area.polygons]] +vertices = [ + [1660, 3277], + [1662, 3277], + [1662, 3282], + [1660, 3282], +] + +[[area.polygons]] +vertices = [ + [1688, 3277], + [1698, 3277], +] + +[[area.polygons]] +vertices = [ + [1689, 3278], + [1698, 3278], +] + +[[area.polygons]] +vertices = [ + [1690, 3279], + [1698, 3279], +] + +[[area.polygons]] +vertices = [[1657, 3281]] diff --git a/.data/raw-cache/map/area/emotion.toml b/.data/raw-cache/map/area/emotion.toml new file mode 100644 index 000000000..dd4abfa72 --- /dev/null +++ b/.data/raw-cache/map/area/emotion.toml @@ -0,0 +1,216 @@ +# Emotion music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Emotion. +# Bounds x 2496-2815, z 3136-9599; 11616 tiles as 30 rectangles. + +[[area]] +name = "Emotion" +area_id = "area.emotion" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2496, 3136], + [2514, 3136], + [2514, 3144], + [2496, 3144], +] + +[[area.polygons]] +vertices = [ + [2540, 3136], + [2550, 3136], + [2550, 3195], + [2540, 3195], +] + +[[area.polygons]] +vertices = [ + [2538, 3137], + [2539, 3137], + [2539, 3195], + [2538, 3195], +] + +[[area.polygons]] +vertices = [ + [2551, 3137], + [2552, 3137], + [2552, 3199], + [2551, 3199], +] + +[[area.polygons]] +vertices = [ + [2536, 3138], + [2537, 3138], + [2537, 3195], + [2536, 3195], +] + +[[area.polygons]] +vertices = [ + [2553, 3138], + [2554, 3138], + [2554, 3199], + [2553, 3199], +] + +[[area.polygons]] +vertices = [ + [2535, 3139], + [2535, 3195], +] + +[[area.polygons]] +vertices = [ + [2555, 3139], + [2555, 3199], +] + +[[area.polygons]] +vertices = [ + [2515, 3140], + [2520, 3140], + [2520, 3199], + [2515, 3199], +] + +[[area.polygons]] +vertices = [ + [2530, 3140], + [2534, 3140], + [2534, 3195], + [2530, 3195], +] + +[[area.polygons]] +vertices = [ + [2521, 3141], + [2529, 3141], + [2529, 3195], + [2521, 3195], +] + +[[area.polygons]] +vertices = [ + [2500, 3145], + [2514, 3145], + [2514, 3189], + [2500, 3189], +] + +[[area.polygons]] +vertices = [ + [2498, 3157], + [2499, 3157], + [2499, 3173], + [2498, 3173], +] + +[[area.polygons]] +vertices = [ + [2497, 3159], + [2497, 3172], +] + +[[area.polygons]] +vertices = [ + [2496, 3160], + [2496, 3171], +] + +[[area.polygons]] +vertices = [ + [2501, 3190], + [2514, 3190], +] + +[[area.polygons]] +vertices = [ + [2502, 3191], + [2514, 3191], +] + +[[area.polygons]] +vertices = [ + [2556, 3191], + [2558, 3191], + [2558, 3199], + [2556, 3199], +] + +[[area.polygons]] +vertices = [ + [2503, 3192], + [2514, 3192], +] + +[[area.polygons]] +vertices = [ + [2506, 3193], + [2514, 3193], + [2514, 3194], + [2506, 3194], +] + +[[area.polygons]] +vertices = [ + [2507, 3195], + [2514, 3195], +] + +[[area.polygons]] +vertices = [ + [2511, 3196], + [2514, 3196], + [2514, 3197], + [2511, 3197], +] + +[[area.polygons]] +vertices = [ + [2521, 3196], + [2523, 3196], +] + +[[area.polygons]] +vertices = [ + [2548, 3196], + [2550, 3196], + [2550, 3199], + [2548, 3199], +] + +[[area.polygons]] +vertices = [ + [2521, 3197], + [2522, 3197], +] + +[[area.polygons]] +vertices = [ + [2513, 3198], + [2514, 3198], +] + +[[area.polygons]] +vertices = [[2521, 3198]] + +[[area.polygons]] +vertices = [[2514, 3199]] + +[[area.polygons]] +vertices = [ + [2752, 4672], + [2815, 4672], + [2815, 4735], + [2752, 4735], +] + +[[area.polygons]] +vertices = [ + [2496, 9536], + [2559, 9536], + [2559, 9599], + [2496, 9599], +] diff --git a/.data/raw-cache/map/area/escape.toml b/.data/raw-cache/map/area/escape.toml new file mode 100644 index 000000000..7ef967a26 --- /dev/null +++ b/.data/raw-cache/map/area/escape.toml @@ -0,0 +1,16 @@ +# Escape music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Escape. +# Bounds x 2688-2751, z 9664-9727; 4096 tiles as 1 rectangles. + +[[area]] +name = "Escape" +area_id = "area.escape" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 9664], + [2751, 9664], + [2751, 9727], + [2688, 9727], +] diff --git a/.data/raw-cache/map/area/espionage.toml b/.data/raw-cache/map/area/espionage.toml new file mode 100644 index 000000000..c43758a4d --- /dev/null +++ b/.data/raw-cache/map/area/espionage.toml @@ -0,0 +1,16 @@ +# Espionage music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Espionage. +# Bounds x 2624-2687, z 10368-10431; 4096 tiles as 1 rectangles. + +[[area]] +name = "Espionage" +area_id = "area.espionage" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2624, 10368], + [2687, 10368], + [2687, 10431], + [2624, 10431], +] diff --git a/.data/raw-cache/map/area/etceteria.toml b/.data/raw-cache/map/area/etceteria.toml new file mode 100644 index 000000000..7177b5663 --- /dev/null +++ b/.data/raw-cache/map/area/etceteria.toml @@ -0,0 +1,328 @@ +# Etceteria music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Etceteria. +# Bounds x 2575-2625, z 3841-3903; 1963 tiles as 49 rectangles. + +[[area]] +name = "Etceteria" +area_id = "area.etceteria" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2612, 3841], + [2613, 3841], + [2613, 3846], + [2612, 3846], +] + +[[area.polygons]] +vertices = [ + [2617, 3841], + [2618, 3841], + [2618, 3903], + [2617, 3903], +] + +[[area.polygons]] +vertices = [ + [2607, 3845], + [2611, 3845], + [2611, 3846], + [2607, 3846], +] + +[[area.polygons]] +vertices = [ + [2614, 3845], + [2616, 3845], + [2616, 3903], + [2614, 3903], +] + +[[area.polygons]] +vertices = [ + [2613, 3847], + [2613, 3903], +] + +[[area.polygons]] +vertices = [ + [2619, 3847], + [2620, 3847], + [2620, 3855], + [2619, 3855], +] + +[[area.polygons]] +vertices = [ + [2612, 3848], + [2612, 3903], +] + +[[area.polygons]] +vertices = [ + [2621, 3848], + [2621, 3854], +] + +[[area.polygons]] +vertices = [ + [2611, 3849], + [2611, 3902], +] + +[[area.polygons]] +vertices = [ + [2604, 3850], + [2610, 3850], + [2610, 3898], + [2604, 3898], +] + +[[area.polygons]] +vertices = [ + [2603, 3851], + [2603, 3898], +] + +[[area.polygons]] +vertices = [ + [2622, 3851], + [2622, 3853], +] + +[[area.polygons]] +vertices = [ + [2602, 3852], + [2602, 3897], +] + +[[area.polygons]] +vertices = [ + [2601, 3853], + [2601, 3897], +] + +[[area.polygons]] +vertices = [ + [2600, 3854], + [2600, 3897], +] + +[[area.polygons]] +vertices = [ + [2584, 3855], + [2585, 3855], + [2585, 3871], + [2584, 3871], +] + +[[area.polygons]] +vertices = [ + [2599, 3855], + [2599, 3897], +] + +[[area.polygons]] +vertices = [ + [2577, 3856], + [2583, 3856], + [2583, 3869], + [2577, 3869], +] + +[[area.polygons]] +vertices = [ + [2586, 3856], + [2586, 3887], +] + +[[area.polygons]] +vertices = [ + [2598, 3856], + [2598, 3897], +] + +[[area.polygons]] +vertices = [ + [2619, 3856], + [2619, 3902], +] + +[[area.polygons]] +vertices = [ + [2575, 3857], + [2576, 3857], + [2576, 3865], + [2575, 3865], +] + +[[area.polygons]] +vertices = [ + [2587, 3857], + [2587, 3887], +] + +[[area.polygons]] +vertices = [ + [2597, 3857], + [2597, 3897], +] + +[[area.polygons]] +vertices = [ + [2588, 3858], + [2588, 3888], +] + +[[area.polygons]] +vertices = [ + [2594, 3858], + [2596, 3858], + [2596, 3896], + [2594, 3896], +] + +[[area.polygons]] +vertices = [ + [2589, 3859], + [2593, 3859], + [2593, 3888], + [2589, 3888], +] + +[[area.polygons]] +vertices = [ + [2620, 3860], + [2620, 3902], +] + +[[area.polygons]] +vertices = [ + [2621, 3863], + [2621, 3901], +] + +[[area.polygons]] +vertices = [ + [2622, 3864], + [2622, 3882], +] + +[[area.polygons]] +vertices = [ + [2623, 3865], + [2623, 3880], +] + +[[area.polygons]] +vertices = [ + [2576, 3866], + [2576, 3867], +] + +[[area.polygons]] +vertices = [ + [2624, 3866], + [2624, 3879], +] + +[[area.polygons]] +vertices = [ + [2581, 3870], + [2583, 3870], +] + +[[area.polygons]] +vertices = [ + [2625, 3870], + [2625, 3875], +] + +[[area.polygons]] +vertices = [ + [2585, 3872], + [2585, 3887], +] + +[[area.polygons]] +vertices = [ + [2584, 3874], + [2584, 3886], +] + +[[area.polygons]] +vertices = [ + [2583, 3877], + [2583, 3884], +] + +[[area.polygons]] +vertices = [ + [2580, 3879], + [2582, 3879], + [2582, 3880], + [2580, 3880], +] + +[[area.polygons]] +vertices = [ + [2590, 3889], + [2593, 3889], +] + +[[area.polygons]] +vertices = [ + [2591, 3890], + [2593, 3890], + [2593, 3892], + [2591, 3892], +] + +[[area.polygons]] +vertices = [ + [2622, 3890], + [2622, 3899], +] + +[[area.polygons]] +vertices = [ + [2623, 3892], + [2623, 3898], +] + +[[area.polygons]] +vertices = [ + [2592, 3893], + [2593, 3893], +] + +[[area.polygons]] +vertices = [ + [2593, 3894], + [2593, 3895], +] + +[[area.polygons]] +vertices = [ + [2624, 3894], + [2624, 3897], +] + +[[area.polygons]] +vertices = [ + [2606, 3899], + [2610, 3899], +] + +[[area.polygons]] +vertices = [ + [2608, 3900], + [2610, 3900], +] + +[[area.polygons]] +vertices = [ + [2609, 3901], + [2610, 3901], +] diff --git a/.data/raw-cache/map/area/everywhere.toml b/.data/raw-cache/map/area/everywhere.toml new file mode 100644 index 000000000..42ea6f753 --- /dev/null +++ b/.data/raw-cache/map/area/everywhere.toml @@ -0,0 +1,711 @@ +# Everywhere music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Everywhere. +# Bounds x 2121-2311, z 3368-3455; 10012 tiles as 114 rectangles. + +[[area]] +name = "Everywhere" +area_id = "area.everywhere" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2296, 3368], + [2311, 3368], + [2311, 3455], + [2296, 3455], +] + +[[area.polygons]] +vertices = [ + [2295, 3369], + [2295, 3455], +] + +[[area.polygons]] +vertices = [ + [2294, 3370], + [2294, 3455], +] + +[[area.polygons]] +vertices = [ + [2293, 3371], + [2293, 3455], +] + +[[area.polygons]] +vertices = [ + [2292, 3372], + [2292, 3455], +] + +[[area.polygons]] +vertices = [ + [2166, 3373], + [2175, 3373], + [2175, 3446], + [2166, 3446], +] + +[[area.polygons]] +vertices = [ + [2291, 3373], + [2291, 3455], +] + +[[area.polygons]] +vertices = [ + [2176, 3374], + [2176, 3448], +] + +[[area.polygons]] +vertices = [ + [2290, 3374], + [2290, 3455], +] + +[[area.polygons]] +vertices = [ + [2177, 3375], + [2177, 3448], +] + +[[area.polygons]] +vertices = [ + [2289, 3375], + [2289, 3455], +] + +[[area.polygons]] +vertices = [ + [2165, 3376], + [2165, 3450], +] + +[[area.polygons]] +vertices = [ + [2178, 3376], + [2178, 3448], +] + +[[area.polygons]] +vertices = [ + [2288, 3376], + [2288, 3455], +] + +[[area.polygons]] +vertices = [ + [2164, 3377], + [2164, 3451], +] + +[[area.polygons]] +vertices = [ + [2179, 3377], + [2179, 3449], +] + +[[area.polygons]] +vertices = [ + [2287, 3377], + [2287, 3455], +] + +[[area.polygons]] +vertices = [ + [2163, 3378], + [2163, 3451], +] + +[[area.polygons]] +vertices = [ + [2180, 3378], + [2180, 3450], +] + +[[area.polygons]] +vertices = [ + [2286, 3378], + [2286, 3455], +] + +[[area.polygons]] +vertices = [ + [2162, 3379], + [2162, 3451], +] + +[[area.polygons]] +vertices = [ + [2181, 3379], + [2181, 3450], +] + +[[area.polygons]] +vertices = [ + [2285, 3379], + [2285, 3453], +] + +[[area.polygons]] +vertices = [ + [2161, 3380], + [2161, 3451], +] + +[[area.polygons]] +vertices = [ + [2182, 3380], + [2182, 3450], +] + +[[area.polygons]] +vertices = [ + [2284, 3380], + [2284, 3452], +] + +[[area.polygons]] +vertices = [ + [2160, 3381], + [2160, 3451], +] + +[[area.polygons]] +vertices = [ + [2183, 3381], + [2183, 3450], +] + +[[area.polygons]] +vertices = [ + [2283, 3381], + [2283, 3451], +] + +[[area.polygons]] +vertices = [ + [2159, 3382], + [2159, 3451], +] + +[[area.polygons]] +vertices = [ + [2184, 3382], + [2184, 3391], +] + +[[area.polygons]] +vertices = [ + [2282, 3382], + [2282, 3451], +] + +[[area.polygons]] +vertices = [ + [2158, 3383], + [2158, 3451], +] + +[[area.polygons]] +vertices = [ + [2185, 3383], + [2185, 3391], +] + +[[area.polygons]] +vertices = [ + [2281, 3383], + [2281, 3451], +] + +[[area.polygons]] +vertices = [ + [2157, 3384], + [2157, 3450], +] + +[[area.polygons]] +vertices = [ + [2186, 3384], + [2186, 3391], +] + +[[area.polygons]] +vertices = [ + [2280, 3384], + [2280, 3451], +] + +[[area.polygons]] +vertices = [ + [2156, 3385], + [2156, 3450], +] + +[[area.polygons]] +vertices = [ + [2187, 3385], + [2187, 3391], +] + +[[area.polygons]] +vertices = [ + [2218, 3385], + [2270, 3385], + [2270, 3407], + [2218, 3407], +] + +[[area.polygons]] +vertices = [ + [2279, 3385], + [2279, 3451], +] + +[[area.polygons]] +vertices = [ + [2155, 3386], + [2155, 3451], +] + +[[area.polygons]] +vertices = [ + [2188, 3386], + [2188, 3391], +] + +[[area.polygons]] +vertices = [ + [2271, 3386], + [2271, 3452], +] + +[[area.polygons]] +vertices = [ + [2278, 3386], + [2278, 3451], +] + +[[area.polygons]] +vertices = [ + [2154, 3387], + [2154, 3451], +] + +[[area.polygons]] +vertices = [ + [2189, 3387], + [2189, 3391], +] + +[[area.polygons]] +vertices = [ + [2272, 3387], + [2272, 3452], +] + +[[area.polygons]] +vertices = [ + [2277, 3387], + [2277, 3451], +] + +[[area.polygons]] +vertices = [ + [2152, 3388], + [2153, 3388], + [2153, 3451], + [2152, 3451], +] + +[[area.polygons]] +vertices = [ + [2190, 3388], + [2190, 3391], +] + +[[area.polygons]] +vertices = [ + [2273, 3388], + [2276, 3388], + [2276, 3451], + [2273, 3451], +] + +[[area.polygons]] +vertices = [ + [2148, 3389], + [2151, 3389], + [2151, 3449], + [2148, 3449], +] + +[[area.polygons]] +vertices = [ + [2191, 3389], + [2191, 3391], +] + +[[area.polygons]] +vertices = [ + [2146, 3390], + [2147, 3390], + [2147, 3449], + [2146, 3449], +] + +[[area.polygons]] +vertices = [ + [2192, 3390], + [2192, 3391], +] + +[[area.polygons]] +vertices = [ + [2144, 3391], + [2145, 3391], + [2145, 3449], + [2144, 3449], +] + +[[area.polygons]] +vertices = [[2193, 3391]] + +[[area.polygons]] +vertices = [ + [2143, 3392], + [2143, 3449], +] + +[[area.polygons]] +vertices = [ + [2142, 3393], + [2142, 3449], +] + +[[area.polygons]] +vertices = [ + [2141, 3394], + [2141, 3449], +] + +[[area.polygons]] +vertices = [ + [2127, 3395], + [2130, 3395], + [2130, 3438], + [2127, 3438], +] + +[[area.polygons]] +vertices = [ + [2140, 3395], + [2140, 3449], +] + +[[area.polygons]] +vertices = [ + [2126, 3396], + [2126, 3399], +] + +[[area.polygons]] +vertices = [ + [2131, 3396], + [2131, 3439], +] + +[[area.polygons]] +vertices = [ + [2139, 3396], + [2139, 3448], +] + +[[area.polygons]] +vertices = [ + [2132, 3397], + [2132, 3441], +] + +[[area.polygons]] +vertices = [ + [2138, 3397], + [2138, 3447], +] + +[[area.polygons]] +vertices = [ + [2133, 3398], + [2133, 3443], +] + +[[area.polygons]] +vertices = [ + [2137, 3398], + [2137, 3446], +] + +[[area.polygons]] +vertices = [ + [2135, 3399], + [2136, 3399], + [2136, 3445], + [2135, 3445], +] + +[[area.polygons]] +vertices = [ + [2134, 3400], + [2134, 3444], +] + +[[area.polygons]] +vertices = [ + [2126, 3401], + [2126, 3438], +] + +[[area.polygons]] +vertices = [ + [2125, 3402], + [2125, 3437], +] + +[[area.polygons]] +vertices = [ + [2124, 3403], + [2124, 3409], +] + +[[area.polygons]] +vertices = [ + [2123, 3404], + [2123, 3408], +] + +[[area.polygons]] +vertices = [ + [2122, 3405], + [2122, 3408], +] + +[[area.polygons]] +vertices = [ + [2121, 3406], + [2121, 3408], +] + +[[area.polygons]] +vertices = [ + [2221, 3408], + [2270, 3408], + [2270, 3421], + [2221, 3421], +] + +[[area.polygons]] +vertices = [ + [2124, 3412], + [2124, 3415], +] + +[[area.polygons]] +vertices = [ + [2124, 3419], + [2124, 3437], +] + +[[area.polygons]] +vertices = [ + [2123, 3421], + [2123, 3425], +] + +[[area.polygons]] +vertices = [ + [2221, 3422], + [2223, 3422], +] + +[[area.polygons]] +vertices = [ + [2226, 3422], + [2227, 3422], + [2227, 3429], + [2226, 3429], +] + +[[area.polygons]] +vertices = [ + [2230, 3422], + [2270, 3422], +] + +[[area.polygons]] +vertices = [ + [2233, 3423], + [2270, 3423], +] + +[[area.polygons]] +vertices = [ + [2234, 3424], + [2270, 3424], +] + +[[area.polygons]] +vertices = [ + [2235, 3425], + [2270, 3425], +] + +[[area.polygons]] +vertices = [ + [2236, 3426], + [2270, 3426], + [2270, 3427], + [2236, 3427], +] + +[[area.polygons]] +vertices = [ + [2123, 3428], + [2123, 3434], +] + +[[area.polygons]] +vertices = [ + [2237, 3428], + [2270, 3428], + [2270, 3445], + [2237, 3445], +] + +[[area.polygons]] +vertices = [ + [2122, 3430], + [2122, 3432], +] + +[[area.polygons]] +vertices = [ + [2236, 3430], + [2236, 3444], +] + +[[area.polygons]] +vertices = [ + [2235, 3435], + [2235, 3442], +] + +[[area.polygons]] +vertices = [ + [2234, 3437], + [2234, 3439], +] + +[[area.polygons]] +vertices = [ + [2238, 3446], + [2270, 3446], +] + +[[area.polygons]] +vertices = [ + [2166, 3447], + [2173, 3447], +] + +[[area.polygons]] +vertices = [[2175, 3447]] + +[[area.polygons]] +vertices = [ + [2240, 3447], + [2270, 3447], +] + +[[area.polygons]] +vertices = [ + [2166, 3448], + [2171, 3448], +] + +[[area.polygons]] +vertices = [ + [2243, 3448], + [2251, 3448], +] + +[[area.polygons]] +vertices = [ + [2253, 3448], + [2270, 3448], +] + +[[area.polygons]] +vertices = [ + [2166, 3449], + [2169, 3449], +] + +[[area.polygons]] +vertices = [ + [2245, 3449], + [2248, 3449], +] + +[[area.polygons]] +vertices = [ + [2255, 3449], + [2270, 3449], +] + +[[area.polygons]] +vertices = [ + [2149, 3450], + [2151, 3450], +] + +[[area.polygons]] +vertices = [ + [2166, 3450], + [2167, 3450], +] + +[[area.polygons]] +vertices = [ + [2257, 3450], + [2270, 3450], +] + +[[area.polygons]] +vertices = [[2151, 3451]] + +[[area.polygons]] +vertices = [ + [2260, 3451], + [2270, 3451], +] + +[[area.polygons]] +vertices = [ + [2262, 3452], + [2270, 3452], +] + +[[area.polygons]] +vertices = [ + [2273, 3452], + [2274, 3452], +] + +[[area.polygons]] +vertices = [ + [2264, 3453], + [2266, 3453], +] diff --git a/.data/raw-cache/map/area/evil_bobs_island.toml b/.data/raw-cache/map/area/evil_bobs_island.toml new file mode 100644 index 000000000..d7def4de4 --- /dev/null +++ b/.data/raw-cache/map/area/evil_bobs_island.toml @@ -0,0 +1,16 @@ +# Evil Bob's Island music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Evil Bob's Island. +# Bounds x 2496-2559, z 4736-4799; 4096 tiles as 1 rectangles. + +[[area]] +name = "Evil Bob's Island" +area_id = "area.evil_bobs_island" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2496, 4736], + [2559, 4736], + [2559, 4799], + [2496, 4799], +] diff --git a/.data/raw-cache/map/area/expecting.toml b/.data/raw-cache/map/area/expecting.toml new file mode 100644 index 000000000..fd0b107d0 --- /dev/null +++ b/.data/raw-cache/map/area/expecting.toml @@ -0,0 +1,174 @@ +# Expecting music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Expecting. +# Bounds x 2432-2595, z 3211-9759; 7066 tiles as 26 rectangles. + +[[area]] +name = "Expecting" +area_id = "area.expecting" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2486, 3211], + [2492, 3211], + [2492, 3241], + [2486, 3241], +] + +[[area.polygons]] +vertices = [ + [2485, 3212], + [2485, 3253], +] + +[[area.polygons]] +vertices = [ + [2484, 3213], + [2484, 3253], +] + +[[area.polygons]] +vertices = [ + [2483, 3214], + [2483, 3254], +] + +[[area.polygons]] +vertices = [ + [2482, 3215], + [2482, 3255], +] + +[[area.polygons]] +vertices = [ + [2481, 3216], + [2481, 3256], +] + +[[area.polygons]] +vertices = [ + [2450, 3217], + [2480, 3217], + [2480, 3249], + [2450, 3249], +] + +[[area.polygons]] +vertices = [ + [2449, 3218], + [2449, 3248], +] + +[[area.polygons]] +vertices = [ + [2448, 3219], + [2448, 3247], +] + +[[area.polygons]] +vertices = [ + [2447, 3220], + [2447, 3246], +] + +[[area.polygons]] +vertices = [ + [2446, 3221], + [2446, 3245], +] + +[[area.polygons]] +vertices = [ + [2486, 3242], + [2487, 3242], + [2487, 3253], + [2486, 3253], +] + +[[area.polygons]] +vertices = [ + [2451, 3250], + [2480, 3250], +] + +[[area.polygons]] +vertices = [ + [2452, 3251], + [2480, 3251], +] + +[[area.polygons]] +vertices = [ + [2453, 3252], + [2480, 3252], +] + +[[area.polygons]] +vertices = [ + [2454, 3253], + [2480, 3253], +] + +[[area.polygons]] +vertices = [ + [2461, 3254], + [2480, 3254], +] + +[[area.polygons]] +vertices = [ + [2462, 3255], + [2480, 3255], +] + +[[area.polygons]] +vertices = [ + [2463, 3256], + [2480, 3256], +] + +[[area.polygons]] +vertices = [ + [2464, 3257], + [2480, 3257], +] + +[[area.polygons]] +vertices = [ + [2465, 3258], + [2479, 3258], +] + +[[area.polygons]] +vertices = [ + [2466, 3259], + [2478, 3259], +] + +[[area.polygons]] +vertices = [ + [2467, 3260], + [2477, 3260], +] + +[[area.polygons]] +vertices = [ + [2468, 3261], + [2476, 3261], +] + +[[area.polygons]] +vertices = [ + [2432, 9600], + [2495, 9600], + [2495, 9663], + [2432, 9663], +] + +[[area.polygons]] +vertices = [ + [2560, 9728], + [2595, 9728], + [2595, 9759], + [2560, 9759], +] diff --git a/.data/raw-cache/map/area/expedition.toml b/.data/raw-cache/map/area/expedition.toml new file mode 100644 index 000000000..1685706b7 --- /dev/null +++ b/.data/raw-cache/map/area/expedition.toml @@ -0,0 +1,24 @@ +# Expedition music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Expedition. +# Bounds x 2304-2943, z 9344-10047; 8192 tiles as 2 rectangles. + +[[area]] +name = "Expedition" +area_id = "area.expedition" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2304, 9344], + [2367, 9344], + [2367, 9407], + [2304, 9407], +] + +[[area.polygons]] +vertices = [ + [2880, 9984], + [2943, 9984], + [2943, 10047], + [2880, 10047], +] diff --git a/.data/raw-cache/map/area/exposed.toml b/.data/raw-cache/map/area/exposed.toml new file mode 100644 index 000000000..5b312ca09 --- /dev/null +++ b/.data/raw-cache/map/area/exposed.toml @@ -0,0 +1,393 @@ +# Exposed music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Exposed. +# Bounds x 2452-2547, z 2518-4026; 972 tiles as 60 rectangles. + +[[area]] +name = "Exposed" +area_id = "area.exposed" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2528, 2518], + [2531, 2518], + [2531, 2524], + [2528, 2524], +] + +[[area.polygons]] +vertices = [ + [2526, 2519], + [2527, 2519], + [2527, 2535], + [2526, 2535], +] + +[[area.polygons]] +vertices = [ + [2532, 2519], + [2532, 2523], +] + +[[area.polygons]] +vertices = [ + [2533, 2520], + [2535, 2520], + [2535, 2521], + [2533, 2521], +] + +[[area.polygons]] +vertices = [ + [2525, 2521], + [2525, 2531], +] + +[[area.polygons]] +vertices = [ + [2533, 2522], + [2534, 2522], + [2534, 2523], + [2533, 2523], +] + +[[area.polygons]] +vertices = [ + [2523, 2525], + [2524, 2525], + [2524, 2528], + [2523, 2528], +] + +[[area.polygons]] +vertices = [ + [2528, 2525], + [2530, 2525], + [2530, 2535], + [2528, 2535], +] + +[[area.polygons]] +vertices = [ + [2524, 2529], + [2524, 2530], +] + +[[area.polygons]] +vertices = [ + [2531, 2529], + [2531, 2545], +] + +[[area.polygons]] +vertices = [ + [2532, 2530], + [2532, 2544], +] + +[[area.polygons]] +vertices = [ + [2533, 2531], + [2533, 2543], +] + +[[area.polygons]] +vertices = [ + [2534, 2533], + [2534, 2543], +] + +[[area.polygons]] +vertices = [ + [2535, 2534], + [2536, 2534], + [2536, 2543], + [2535, 2543], +] + +[[area.polygons]] +vertices = [ + [2537, 2535], + [2537, 2543], +] + +[[area.polygons]] +vertices = [ + [2529, 2536], + [2530, 2536], +] + +[[area.polygons]] +vertices = [ + [2538, 2536], + [2542, 2536], + [2542, 2544], + [2538, 2544], +] + +[[area.polygons]] +vertices = [ + [2530, 2537], + [2530, 2545], +] + +[[area.polygons]] +vertices = [ + [2543, 2537], + [2544, 2537], + [2544, 2546], + [2543, 2546], +] + +[[area.polygons]] +vertices = [ + [2545, 2538], + [2545, 2545], +] + +[[area.polygons]] +vertices = [ + [2529, 2539], + [2529, 2544], +] + +[[area.polygons]] +vertices = [ + [2546, 2540], + [2546, 2544], +] + +[[area.polygons]] +vertices = [ + [2528, 2541], + [2528, 2544], +] + +[[area.polygons]] +vertices = [ + [2547, 2542], + [2547, 2543], +] + +[[area.polygons]] +vertices = [ + [2540, 2545], + [2542, 2545], +] + +[[area.polygons]] +vertices = [ + [2541, 2546], + [2542, 2546], +] + +[[area.polygons]] +vertices = [ + [2475, 3993], + [2476, 3993], + [2476, 3997], + [2475, 3997], +] + +[[area.polygons]] +vertices = [ + [2471, 3994], + [2474, 3994], + [2474, 4019], + [2471, 4019], +] + +[[area.polygons]] +vertices = [ + [2477, 3994], + [2477, 3996], +] + +[[area.polygons]] +vertices = [ + [2470, 3995], + [2470, 4020], +] + +[[area.polygons]] +vertices = [ + [2468, 3996], + [2469, 3996], + [2469, 4021], + [2468, 4021], +] + +[[area.polygons]] +vertices = [ + [2467, 3998], + [2467, 4023], +] + +[[area.polygons]] +vertices = [[2475, 3998]] + +[[area.polygons]] +vertices = [ + [2460, 4000], + [2460, 4025], +] + +[[area.polygons]] +vertices = [ + [2459, 4001], + [2459, 4022], +] + +[[area.polygons]] +vertices = [ + [2461, 4001], + [2461, 4026], +] + +[[area.polygons]] +vertices = [ + [2466, 4001], + [2466, 4023], +] + +[[area.polygons]] +vertices = [ + [2475, 4001], + [2475, 4018], +] + +[[area.polygons]] +vertices = [ + [2458, 4002], + [2458, 4021], +] + +[[area.polygons]] +vertices = [ + [2462, 4002], + [2462, 4026], +] + +[[area.polygons]] +vertices = [ + [2465, 4002], + [2465, 4025], +] + +[[area.polygons]] +vertices = [ + [2476, 4002], + [2477, 4002], + [2477, 4017], + [2476, 4017], +] + +[[area.polygons]] +vertices = [ + [2457, 4003], + [2457, 4019], +] + +[[area.polygons]] +vertices = [ + [2463, 4003], + [2464, 4003], + [2464, 4026], + [2463, 4026], +] + +[[area.polygons]] +vertices = [ + [2478, 4003], + [2478, 4017], +] + +[[area.polygons]] +vertices = [ + [2455, 4004], + [2456, 4004], + [2456, 4018], + [2455, 4018], +] + +[[area.polygons]] +vertices = [ + [2479, 4004], + [2479, 4016], +] + +[[area.polygons]] +vertices = [ + [2480, 4005], + [2480, 4016], +] + +[[area.polygons]] +vertices = [ + [2453, 4007], + [2454, 4007], + [2454, 4015], + [2453, 4015], +] + +[[area.polygons]] +vertices = [ + [2481, 4007], + [2481, 4017], +] + +[[area.polygons]] +vertices = [ + [2452, 4008], + [2452, 4014], +] + +[[area.polygons]] +vertices = [ + [2482, 4009], + [2482, 4018], +] + +[[area.polygons]] +vertices = [ + [2483, 4010], + [2483, 4018], +] + +[[area.polygons]] +vertices = [ + [2484, 4011], + [2484, 4018], +] + +[[area.polygons]] +vertices = [ + [2485, 4015], + [2486, 4015], + [2486, 4017], + [2485, 4017], +] + +[[area.polygons]] +vertices = [ + [2454, 4016], + [2454, 4017], +] + +[[area.polygons]] +vertices = [ + [2487, 4016], + [2487, 4017], +] + +[[area.polygons]] +vertices = [[2485, 4018]] + +[[area.polygons]] +vertices = [ + [2471, 4020], + [2472, 4020], +] + +[[area.polygons]] +vertices = [[2468, 4022]] diff --git a/.data/raw-cache/map/area/falador.toml b/.data/raw-cache/map/area/falador.toml new file mode 100644 index 000000000..660d89219 --- /dev/null +++ b/.data/raw-cache/map/area/falador.toml @@ -0,0 +1,32 @@ +[[area]] +name = "Falador" +area_id = "area.falador" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2935, 3320], + [3062, 3320], + [3062, 3400], + [2935, 3400], +] + +# Extension traced from the OSRS wiki music map (54 tiles). + +[[area.polygons]] +vertices = [ + [3063, 3367], + [3063, 3386], +] + +[[area.polygons]] +vertices = [ + [3064, 3368], + [3064, 3385], +] + +[[area.polygons]] +vertices = [ + [3065, 3369], + [3065, 3384], +] diff --git a/.data/raw-cache/map/area/falador_north.toml b/.data/raw-cache/map/area/falador_north.toml new file mode 100644 index 000000000..310143680 --- /dev/null +++ b/.data/raw-cache/map/area/falador_north.toml @@ -0,0 +1,500 @@ +# Regenerated as rectangles from the OSRS wiki music map: 40 tiles moved to +# neighbouring music areas and 6007 tiles were added. + +[[area]] +name = "Falador (north of the walls)" +area_id = "area.falador_north" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3063, 3328], + [3070, 3328], + [3070, 3366], + [3063, 3366], +] + +[[area.polygons]] +vertices = [ + [3071, 3329], + [3071, 3394], +] + +[[area.polygons]] +vertices = [ + [3072, 3330], + [3072, 3394], +] + +[[area.polygons]] +vertices = [ + [3073, 3331], + [3073, 3394], +] + +[[area.polygons]] +vertices = [ + [3074, 3332], + [3074, 3394], +] + +[[area.polygons]] +vertices = [ + [3075, 3333], + [3083, 3333], + [3083, 3370], + [3075, 3370], +] + +[[area.polygons]] +vertices = [ + [3084, 3338], + [3084, 3339], +] + +[[area.polygons]] +vertices = [ + [3084, 3348], + [3084, 3357], +] + +[[area.polygons]] +vertices = [ + [3085, 3349], + [3085, 3352], +] + +[[area.polygons]] +vertices = [ + [3084, 3365], + [3084, 3369], +] + +[[area.polygons]] +vertices = [ + [3064, 3367], + [3070, 3367], +] + +[[area.polygons]] +vertices = [ + [3065, 3368], + [3070, 3368], +] + +[[area.polygons]] +vertices = [ + [3066, 3369], + [3070, 3369], + [3070, 3394], + [3066, 3394], +] + +[[area.polygons]] +vertices = [ + [3075, 3371], + [3082, 3371], + [3082, 3394], + [3075, 3394], +] + +[[area.polygons]] +vertices = [ + [3083, 3378], + [3083, 3394], +] + +[[area.polygons]] +vertices = [ + [3084, 3379], + [3084, 3394], +] + +[[area.polygons]] +vertices = [ + [3065, 3385], + [3065, 3394], +] + +[[area.polygons]] +vertices = [ + [3085, 3385], + [3085, 3394], +] + +[[area.polygons]] +vertices = [ + [3107, 3385], + [3117, 3385], + [3117, 3391], + [3107, 3391], +] + +[[area.polygons]] +vertices = [ + [3064, 3386], + [3064, 3394], +] + +[[area.polygons]] +vertices = [ + [3086, 3386], + [3086, 3394], +] + +[[area.polygons]] +vertices = [ + [3102, 3386], + [3106, 3386], + [3106, 3394], + [3102, 3394], +] + +[[area.polygons]] +vertices = [ + [3063, 3387], + [3063, 3394], +] + +[[area.polygons]] +vertices = [ + [3087, 3387], + [3090, 3387], + [3090, 3394], + [3087, 3394], +] + +[[area.polygons]] +vertices = [ + [3098, 3387], + [3101, 3387], + [3101, 3394], + [3098, 3394], +] + +[[area.polygons]] +vertices = [ + [3091, 3388], + [3097, 3388], + [3097, 3394], + [3091, 3394], +] + +[[area.polygons]] +vertices = [ + [3107, 3392], + [3116, 3392], +] + +[[area.polygons]] +vertices = [ + [3107, 3393], + [3115, 3393], +] + +[[area.polygons]] +vertices = [ + [3107, 3394], + [3114, 3394], +] + +[[area.polygons]] +vertices = [ + [2935, 3401], + [3062, 3401], + [3062, 3438], + [2935, 3438], +] + +[[area.polygons]] +vertices = [ + [2935, 3439], + [2997, 3439], +] + +[[area.polygons]] +vertices = [ + [3017, 3439], + [3062, 3439], +] + +[[area.polygons]] +vertices = [ + [2935, 3440], + [2996, 3440], +] + +[[area.polygons]] +vertices = [ + [3018, 3440], + [3062, 3440], +] + +[[area.polygons]] +vertices = [ + [2944, 3441], + [2995, 3441], +] + +[[area.polygons]] +vertices = [ + [3019, 3441], + [3059, 3441], +] + +[[area.polygons]] +vertices = [ + [2944, 3442], + [2994, 3442], +] + +[[area.polygons]] +vertices = [ + [3020, 3442], + [3059, 3442], +] + +[[area.polygons]] +vertices = [ + [2944, 3443], + [2993, 3443], +] + +[[area.polygons]] +vertices = [ + [3021, 3443], + [3059, 3443], +] + +[[area.polygons]] +vertices = [ + [2943, 3444], + [2992, 3444], +] + +[[area.polygons]] +vertices = [ + [3022, 3444], + [3059, 3444], +] + +[[area.polygons]] +vertices = [ + [2942, 3445], + [2984, 3445], + [2984, 3456], + [2942, 3456], +] + +[[area.polygons]] +vertices = [ + [3041, 3445], + [3059, 3445], + [3059, 3522], + [3041, 3522], +] + +[[area.polygons]] +vertices = [ + [2941, 3446], + [2941, 3479], +] + +[[area.polygons]] +vertices = [ + [2940, 3447], + [2940, 3479], +] + +[[area.polygons]] +vertices = [ + [2937, 3448], + [2939, 3448], + [2939, 3454], + [2937, 3454], +] + +[[area.polygons]] +vertices = [ + [2936, 3449], + [2936, 3454], +] + +[[area.polygons]] +vertices = [ + [3060, 3451], + [3067, 3451], + [3067, 3452], + [3060, 3452], +] + +[[area.polygons]] +vertices = [ + [3060, 3453], + [3066, 3453], + [3066, 3454], + [3060, 3454], +] + +[[area.polygons]] +vertices = [[2939, 3455]] + +[[area.polygons]] +vertices = [ + [3060, 3455], + [3065, 3455], + [3065, 3518], + [3060, 3518], +] + +[[area.polygons]] +vertices = [ + [2942, 3457], + [2983, 3457], +] + +[[area.polygons]] +vertices = [ + [2942, 3458], + [2982, 3458], +] + +[[area.polygons]] +vertices = [ + [2942, 3459], + [2981, 3459], +] + +[[area.polygons]] +vertices = [ + [2942, 3460], + [2980, 3460], +] + +[[area.polygons]] +vertices = [ + [2942, 3461], + [2979, 3461], +] + +[[area.polygons]] +vertices = [ + [2942, 3462], + [2978, 3462], + [2978, 3478], + [2942, 3478], +] + +[[area.polygons]] +vertices = [ + [2939, 3472], + [2939, 3522], +] + +[[area.polygons]] +vertices = [ + [2938, 3473], + [2938, 3522], +] + +[[area.polygons]] +vertices = [ + [2937, 3474], + [2937, 3505], +] + +[[area.polygons]] +vertices = [ + [2979, 3476], + [2979, 3522], +] + +[[area.polygons]] +vertices = [ + [2980, 3477], + [2980, 3522], +] + +[[area.polygons]] +vertices = [ + [2981, 3478], + [2981, 3522], +] + +[[area.polygons]] +vertices = [ + [2942, 3479], + [2948, 3479], +] + +[[area.polygons]] +vertices = [ + [2959, 3479], + [2978, 3479], +] + +[[area.polygons]] +vertices = [ + [2982, 3479], + [2982, 3522], +] + +[[area.polygons]] +vertices = [ + [2976, 3480], + [2978, 3480], + [2978, 3522], + [2976, 3522], +] + +[[area.polygons]] +vertices = [ + [2983, 3480], + [2983, 3522], +] + +[[area.polygons]] +vertices = [ + [2984, 3481], + [2984, 3522], +] + +[[area.polygons]] +vertices = [ + [2936, 3493], + [2936, 3504], +] + +[[area.polygons]] +vertices = [ + [2935, 3494], + [2935, 3503], +] + +[[area.polygons]] +vertices = [ + [2937, 3507], + [2937, 3522], +] + +[[area.polygons]] +vertices = [ + [2936, 3508], + [2936, 3522], +] + +[[area.polygons]] +vertices = [ + [3060, 3519], + [3064, 3519], + [3064, 3522], + [3060, 3522], +] + +[[area.polygons]] +vertices = [ + [2940, 3521], + [2975, 3521], + [2975, 3522], + [2940, 3522], +] diff --git a/.data/raw-cache/map/area/falador_south.toml b/.data/raw-cache/map/area/falador_south.toml new file mode 100644 index 000000000..84acf276e --- /dev/null +++ b/.data/raw-cache/map/area/falador_south.toml @@ -0,0 +1,533 @@ +[[area]] +name = "Falador (south of the walls)" +area_id = "area.falador_south" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2935, 3268], + [3062, 3268], + [3062, 3319], + [2935, 3319], +] + +# Extension traced from the OSRS wiki music map (5887 tiles). + +[[area.polygons]] +vertices = [ + [2916, 3254], + [2920, 3254], + [2920, 3255], + [2916, 3255], +] + +[[area.polygons]] +vertices = [ + [2917, 3256], + [2921, 3256], + [2921, 3257], + [2917, 3257], +] + +[[area.polygons]] +vertices = [ + [2922, 3257], + [2922, 3390], +] + +[[area.polygons]] +vertices = [ + [2941, 3257], + [2944, 3257], + [2944, 3267], + [2941, 3267], +] + +[[area.polygons]] +vertices = [ + [2918, 3258], + [2921, 3258], +] + +[[area.polygons]] +vertices = [ + [2923, 3258], + [2940, 3258], + [2940, 3267], + [2923, 3267], +] + +[[area.polygons]] +vertices = [ + [2957, 3258], + [2990, 3258], + [2990, 3267], + [2957, 3267], +] + +[[area.polygons]] +vertices = [ + [2919, 3259], + [2921, 3259], + [2921, 3260], + [2919, 3260], +] + +[[area.polygons]] +vertices = [ + [2956, 3260], + [2956, 3267], +] + +[[area.polygons]] +vertices = [ + [2920, 3261], + [2921, 3261], +] + +[[area.polygons]] +vertices = [ + [2955, 3261], + [2955, 3267], +] + +[[area.polygons]] +vertices = [ + [2991, 3261], + [3005, 3261], + [3005, 3267], + [2991, 3267], +] + +[[area.polygons]] +vertices = [[2921, 3262]] + +[[area.polygons]] +vertices = [ + [2945, 3262], + [2945, 3267], +] + +[[area.polygons]] +vertices = [ + [2954, 3262], + [2954, 3267], +] + +[[area.polygons]] +vertices = [ + [3006, 3262], + [3006, 3267], +] + +[[area.polygons]] +vertices = [ + [2946, 3263], + [2953, 3263], + [2953, 3267], + [2946, 3267], +] + +[[area.polygons]] +vertices = [ + [2919, 3264], + [2921, 3264], + [2921, 3390], + [2919, 3390], +] + +[[area.polygons]] +vertices = [ + [3007, 3264], + [3067, 3264], + [3067, 3267], + [3007, 3267], +] + +[[area.polygons]] +vertices = [ + [2918, 3265], + [2918, 3390], +] + +[[area.polygons]] +vertices = [ + [2917, 3266], + [2917, 3390], +] + +[[area.polygons]] +vertices = [ + [2916, 3267], + [2916, 3390], +] + +[[area.polygons]] +vertices = [ + [2915, 3268], + [2915, 3390], +] + +[[area.polygons]] +vertices = [ + [2923, 3268], + [2934, 3268], + [2934, 3390], + [2923, 3390], +] + +[[area.polygons]] +vertices = [ + [3063, 3268], + [3068, 3268], + [3068, 3295], + [3063, 3295], +] + +[[area.polygons]] +vertices = [ + [2914, 3269], + [2914, 3390], +] + +[[area.polygons]] +vertices = [ + [3069, 3273], + [3069, 3284], +] + +[[area.polygons]] +vertices = [ + [2913, 3274], + [2913, 3390], +] + +[[area.polygons]] +vertices = [ + [2912, 3276], + [2912, 3390], +] + +[[area.polygons]] +vertices = [ + [2911, 3278], + [2911, 3390], +] + +[[area.polygons]] +vertices = [ + [2910, 3279], + [2910, 3390], +] + +[[area.polygons]] +vertices = [ + [2909, 3280], + [2909, 3390], +] + +[[area.polygons]] +vertices = [ + [2908, 3281], + [2908, 3315], +] + +[[area.polygons]] +vertices = [ + [2907, 3284], + [2907, 3299], +] + +[[area.polygons]] +vertices = [ + [2906, 3286], + [2906, 3299], +] + +[[area.polygons]] +vertices = [ + [2905, 3288], + [2905, 3299], +] + +[[area.polygons]] +vertices = [ + [2904, 3289], + [2904, 3299], +] + +[[area.polygons]] +vertices = [ + [2903, 3292], + [2903, 3297], +] + +[[area.polygons]] +vertices = [ + [3063, 3296], + [3067, 3296], + [3067, 3327], + [3063, 3327], +] + +[[area.polygons]] +vertices = [ + [2907, 3302], + [2907, 3314], +] + +[[area.polygons]] +vertices = [ + [2906, 3304], + [2906, 3305], +] + +[[area.polygons]] +vertices = [ + [2906, 3309], + [2906, 3313], +] + +[[area.polygons]] +vertices = [ + [3068, 3310], + [3068, 3327], +] + +[[area.polygons]] +vertices = [ + [3069, 3311], + [3069, 3313], +] + +[[area.polygons]] +vertices = [ + [2908, 3318], + [2908, 3390], +] + +[[area.polygons]] +vertices = [ + [2907, 3319], + [2907, 3389], +] + +[[area.polygons]] +vertices = [ + [2906, 3320], + [2906, 3389], +] + +[[area.polygons]] +vertices = [ + [2895, 3321], + [2899, 3321], + [2899, 3329], + [2895, 3329], +] + +[[area.polygons]] +vertices = [ + [2904, 3321], + [2905, 3321], + [2905, 3388], + [2904, 3388], +] + +[[area.polygons]] +vertices = [ + [2892, 3322], + [2894, 3322], + [2894, 3326], + [2892, 3326], +] + +[[area.polygons]] +vertices = [ + [2900, 3322], + [2900, 3387], +] + +[[area.polygons]] +vertices = [ + [2903, 3322], + [2903, 3388], +] + +[[area.polygons]] +vertices = [ + [2891, 3323], + [2891, 3326], +] + +[[area.polygons]] +vertices = [ + [2901, 3323], + [2902, 3323], + [2902, 3387], + [2901, 3387], +] + +[[area.polygons]] +vertices = [ + [3069, 3324], + [3069, 3327], +] + +[[area.polygons]] +vertices = [ + [2893, 3327], + [2894, 3327], +] + +[[area.polygons]] +vertices = [[2894, 3328]] + +[[area.polygons]] +vertices = [ + [2898, 3330], + [2899, 3330], +] + +[[area.polygons]] +vertices = [[2899, 3331]] + +[[area.polygons]] +vertices = [ + [2899, 3339], + [2899, 3387], +] + +[[area.polygons]] +vertices = [ + [2898, 3340], + [2898, 3386], +] + +[[area.polygons]] +vertices = [ + [2895, 3341], + [2897, 3341], + [2897, 3385], + [2895, 3385], +] + +[[area.polygons]] +vertices = [ + [2894, 3343], + [2894, 3385], +] + +[[area.polygons]] +vertices = [ + [2893, 3344], + [2893, 3385], +] + +[[area.polygons]] +vertices = [ + [2887, 3345], + [2892, 3345], + [2892, 3355], + [2887, 3355], +] + +[[area.polygons]] +vertices = [ + [2885, 3346], + [2886, 3346], + [2886, 3354], + [2885, 3354], +] + +[[area.polygons]] +vertices = [ + [2884, 3347], + [2884, 3354], +] + +[[area.polygons]] +vertices = [ + [2883, 3349], + [2883, 3353], +] + +[[area.polygons]] +vertices = [[2886, 3355]] + +[[area.polygons]] +vertices = [ + [2888, 3356], + [2892, 3356], +] + +[[area.polygons]] +vertices = [ + [2889, 3357], + [2892, 3357], + [2892, 3371], + [2889, 3371], +] + +[[area.polygons]] +vertices = [ + [2888, 3361], + [2888, 3371], +] + +[[area.polygons]] +vertices = [ + [2887, 3366], + [2887, 3370], +] + +[[area.polygons]] +vertices = [ + [2890, 3372], + [2892, 3372], +] + +[[area.polygons]] +vertices = [ + [2891, 3373], + [2892, 3373], +] + +[[area.polygons]] +vertices = [ + [2892, 3374], + [2892, 3384], +] + +[[area.polygons]] +vertices = [ + [2891, 3380], + [2891, 3384], +] + +[[area.polygons]] +vertices = [ + [2890, 3381], + [2890, 3384], +] + +[[area.polygons]] +vertices = [ + [2889, 3382], + [2889, 3383], +] + +[[area.polygons]] +vertices = [[2888, 3383]] + +[[area.polygons]] +vertices = [ + [2896, 3386], + [2897, 3386], +] + +[[area.polygons]] +vertices = [[2902, 3388]] + +[[area.polygons]] +vertices = [[2905, 3389]] diff --git a/.data/raw-cache/map/area/fanfare_2.toml b/.data/raw-cache/map/area/fanfare_2.toml new file mode 100644 index 000000000..03c8be96a --- /dev/null +++ b/.data/raw-cache/map/area/fanfare_2.toml @@ -0,0 +1,321 @@ +# Fanfare 2 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Fanfare 2. +# Bounds x 2945-3004, z 3015-3066; 1792 tiles as 48 rectangles. + +[[area]] +name = "Fanfare 2" +area_id = "area.fanfare_2" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2955, 3015], + [2958, 3015], + [2958, 3064], + [2955, 3064], +] + +[[area.polygons]] +vertices = [ + [2954, 3016], + [2954, 3064], +] + +[[area.polygons]] +vertices = [ + [2959, 3016], + [2959, 3066], +] + +[[area.polygons]] +vertices = [ + [2951, 3017], + [2953, 3017], + [2953, 3064], + [2951, 3064], +] + +[[area.polygons]] +vertices = [ + [2950, 3018], + [2950, 3065], +] + +[[area.polygons]] +vertices = [ + [2960, 3018], + [2960, 3066], +] + +[[area.polygons]] +vertices = [ + [2961, 3019], + [2961, 3066], +] + +[[area.polygons]] +vertices = [ + [2949, 3020], + [2949, 3065], +] + +[[area.polygons]] +vertices = [ + [2962, 3020], + [2963, 3020], + [2963, 3066], + [2962, 3066], +] + +[[area.polygons]] +vertices = [ + [2948, 3021], + [2948, 3060], +] + +[[area.polygons]] +vertices = [ + [2964, 3021], + [2966, 3021], + [2966, 3063], + [2964, 3063], +] + +[[area.polygons]] +vertices = [ + [2967, 3022], + [2970, 3022], +] + +[[area.polygons]] +vertices = [ + [2967, 3023], + [2969, 3023], + [2969, 3025], + [2967, 3025], +] + +[[area.polygons]] +vertices = [ + [2967, 3026], + [2968, 3026], + [2968, 3062], + [2967, 3062], +] + +[[area.polygons]] +vertices = [ + [2996, 3027], + [2997, 3027], + [2997, 3036], + [2996, 3036], +] + +[[area.polygons]] +vertices = [ + [2947, 3028], + [2947, 3052], +] + +[[area.polygons]] +vertices = [ + [2969, 3028], + [2969, 3063], +] + +[[area.polygons]] +vertices = [ + [2998, 3028], + [2998, 3035], +] + +[[area.polygons]] +vertices = [ + [2946, 3029], + [2946, 3048], +] + +[[area.polygons]] +vertices = [ + [2970, 3029], + [2970, 3063], +] + +[[area.polygons]] +vertices = [ + [2971, 3030], + [2971, 3064], +] + +[[area.polygons]] +vertices = [ + [2977, 3030], + [2978, 3030], + [2978, 3035], + [2977, 3035], +] + +[[area.polygons]] +vertices = [ + [2972, 3031], + [2972, 3065], +] + +[[area.polygons]] +vertices = [ + [2976, 3031], + [2976, 3036], +] + +[[area.polygons]] +vertices = [ + [2979, 3031], + [2980, 3031], + [2980, 3034], + [2979, 3034], +] + +[[area.polygons]] +vertices = [ + [3001, 3031], + [3002, 3031], + [3002, 3049], + [3001, 3049], +] + +[[area.polygons]] +vertices = [ + [2973, 3032], + [2975, 3032], + [2975, 3037], + [2973, 3037], +] + +[[area.polygons]] +vertices = [ + [2981, 3032], + [2981, 3035], +] + +[[area.polygons]] +vertices = [ + [2999, 3032], + [3000, 3032], +] + +[[area.polygons]] +vertices = [ + [2945, 3033], + [2945, 3047], +] + +[[area.polygons]] +vertices = [ + [2982, 3033], + [2982, 3035], +] + +[[area.polygons]] +vertices = [[2983, 3034]] + +[[area.polygons]] +vertices = [ + [3000, 3036], + [3000, 3044], +] + +[[area.polygons]] +vertices = [ + [2973, 3038], + [2974, 3038], +] + +[[area.polygons]] +vertices = [ + [2973, 3039], + [2973, 3066], +] + +[[area.polygons]] +vertices = [ + [2974, 3040], + [2974, 3066], +] + +[[area.polygons]] +vertices = [ + [2975, 3041], + [2975, 3065], +] + +[[area.polygons]] +vertices = [ + [2976, 3042], + [2976, 3064], +] + +[[area.polygons]] +vertices = [ + [2977, 3043], + [2982, 3043], + [2982, 3064], + [2977, 3064], +] + +[[area.polygons]] +vertices = [ + [2998, 3043], + [2999, 3043], + [2999, 3044], + [2998, 3044], +] + +[[area.polygons]] +vertices = [ + [2983, 3048], + [3000, 3048], + [3000, 3049], + [2983, 3049], +] + +[[area.polygons]] +vertices = [ + [3003, 3048], + [3004, 3048], + [3004, 3049], + [3003, 3049], +] + +[[area.polygons]] +vertices = [ + [2983, 3050], + [2996, 3050], + [2996, 3062], + [2983, 3062], +] + +[[area.polygons]] +vertices = [ + [2947, 3054], + [2947, 3059], +] + +[[area.polygons]] +vertices = [ + [2997, 3061], + [2998, 3061], + [2998, 3062], + [2997, 3062], +] + +[[area.polygons]] +vertices = [ + [2964, 3064], + [2965, 3064], +] + +[[area.polygons]] +vertices = [[2958, 3065]] + +[[area.polygons]] +vertices = [[2964, 3065]] diff --git a/.data/raw-cache/map/area/fanfare_3.toml b/.data/raw-cache/map/area/fanfare_3.toml new file mode 100644 index 000000000..f1457c07e --- /dev/null +++ b/.data/raw-cache/map/area/fanfare_3.toml @@ -0,0 +1,738 @@ +# Fanfare 3 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Fanfare 3. +# Bounds x 2552-2685, z 3136-3209; 5638 tiles as 116 rectangles. + +[[area]] +name = "Fanfare 3" +area_id = "area.fanfare_3" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2558, 3136], + [2590, 3136], + [2590, 3150], + [2558, 3150], +] + +[[area.polygons]] +vertices = [ + [2557, 3137], + [2557, 3190], +] + +[[area.polygons]] +vertices = [ + [2591, 3137], + [2591, 3150], +] + +[[area.polygons]] +vertices = [ + [2556, 3138], + [2556, 3190], +] + +[[area.polygons]] +vertices = [ + [2592, 3138], + [2592, 3150], +] + +[[area.polygons]] +vertices = [ + [2593, 3139], + [2619, 3139], + [2619, 3150], + [2593, 3150], +] + +[[area.polygons]] +vertices = [ + [2670, 3139], + [2678, 3139], + [2678, 3141], + [2670, 3141], +] + +[[area.polygons]] +vertices = [ + [2650, 3140], + [2651, 3140], + [2651, 3146], + [2650, 3146], +] + +[[area.polygons]] +vertices = [[2679, 3140]] + +[[area.polygons]] +vertices = [ + [2649, 3141], + [2649, 3146], +] + +[[area.polygons]] +vertices = [ + [2652, 3141], + [2652, 3174], +] + +[[area.polygons]] +vertices = [ + [2648, 3142], + [2648, 3146], +] + +[[area.polygons]] +vertices = [ + [2674, 3142], + [2674, 3151], +] + +[[area.polygons]] +vertices = [ + [2661, 3143], + [2663, 3143], + [2663, 3164], + [2661, 3164], +] + +[[area.polygons]] +vertices = [[2647, 3144]] + +[[area.polygons]] +vertices = [ + [2659, 3144], + [2660, 3144], + [2660, 3166], + [2659, 3166], +] + +[[area.polygons]] +vertices = [ + [2664, 3144], + [2664, 3164], +] + +[[area.polygons]] +vertices = [ + [2673, 3144], + [2673, 3151], +] + +[[area.polygons]] +vertices = [ + [2675, 3144], + [2675, 3145], +] + +[[area.polygons]] +vertices = [ + [2658, 3145], + [2658, 3168], +] + +[[area.polygons]] +vertices = [ + [2665, 3145], + [2665, 3168], +] + +[[area.polygons]] +vertices = [ + [2657, 3146], + [2657, 3169], +] + +[[area.polygons]] +vertices = [[2651, 3147]] + +[[area.polygons]] +vertices = [ + [2653, 3147], + [2656, 3147], + [2656, 3170], + [2653, 3170], +] + +[[area.polygons]] +vertices = [ + [2666, 3147], + [2666, 3153], +] + +[[area.polygons]] +vertices = [ + [2639, 3148], + [2641, 3148], + [2641, 3177], + [2639, 3177], +] + +[[area.polygons]] +vertices = [[2638, 3149]] + +[[area.polygons]] +vertices = [ + [2642, 3149], + [2642, 3151], +] + +[[area.polygons]] +vertices = [ + [2651, 3149], + [2651, 3172], +] + +[[area.polygons]] +vertices = [ + [2667, 3149], + [2672, 3149], + [2672, 3151], + [2667, 3151], +] + +[[area.polygons]] +vertices = [ + [2650, 3150], + [2650, 3171], +] + +[[area.polygons]] +vertices = [ + [2675, 3150], + [2680, 3150], + [2680, 3151], + [2675, 3151], +] + +[[area.polygons]] +vertices = [ + [2558, 3151], + [2584, 3151], +] + +[[area.polygons]] +vertices = [[2603, 3151]] + +[[area.polygons]] +vertices = [ + [2605, 3151], + [2618, 3151], +] + +[[area.polygons]] +vertices = [ + [2649, 3151], + [2649, 3169], +] + +[[area.polygons]] +vertices = [ + [2558, 3152], + [2583, 3152], +] + +[[area.polygons]] +vertices = [ + [2606, 3152], + [2617, 3152], +] + +[[area.polygons]] +vertices = [ + [2647, 3152], + [2648, 3152], + [2648, 3166], + [2647, 3166], +] + +[[area.polygons]] +vertices = [ + [2676, 3152], + [2680, 3152], + [2680, 3154], + [2676, 3154], +] + +[[area.polygons]] +vertices = [ + [2558, 3153], + [2582, 3153], +] + +[[area.polygons]] +vertices = [ + [2607, 3153], + [2617, 3153], + [2617, 3155], + [2607, 3155], +] + +[[area.polygons]] +vertices = [ + [2645, 3153], + [2646, 3153], + [2646, 3164], + [2645, 3164], +] + +[[area.polygons]] +vertices = [ + [2558, 3154], + [2581, 3154], +] + +[[area.polygons]] +vertices = [ + [2638, 3154], + [2638, 3178], +] + +[[area.polygons]] +vertices = [ + [2642, 3154], + [2642, 3176], +] + +[[area.polygons]] +vertices = [ + [2644, 3154], + [2644, 3164], +] + +[[area.polygons]] +vertices = [ + [2558, 3155], + [2580, 3155], + [2580, 3190], + [2558, 3190], +] + +[[area.polygons]] +vertices = [ + [2618, 3155], + [2618, 3204], +] + +[[area.polygons]] +vertices = [ + [2637, 3155], + [2637, 3179], +] + +[[area.polygons]] +vertices = [ + [2643, 3155], + [2643, 3164], +] + +[[area.polygons]] +vertices = [ + [2608, 3156], + [2617, 3156], + [2617, 3157], + [2608, 3157], +] + +[[area.polygons]] +vertices = [ + [2619, 3156], + [2619, 3205], +] + +[[area.polygons]] +vertices = [ + [2635, 3156], + [2636, 3156], + [2636, 3180], + [2635, 3180], +] + +[[area.polygons]] +vertices = [ + [2633, 3157], + [2634, 3157], + [2634, 3182], + [2633, 3182], +] + +[[area.polygons]] +vertices = [ + [2612, 3158], + [2617, 3158], + [2617, 3203], + [2612, 3203], +] + +[[area.polygons]] +vertices = [ + [2632, 3158], + [2632, 3206], +] + +[[area.polygons]] +vertices = [ + [2631, 3159], + [2631, 3206], +] + +[[area.polygons]] +vertices = [ + [2630, 3160], + [2630, 3206], +] + +[[area.polygons]] +vertices = [ + [2666, 3160], + [2668, 3160], + [2668, 3168], + [2666, 3168], +] + +[[area.polygons]] +vertices = [ + [2620, 3161], + [2629, 3161], + [2629, 3206], + [2620, 3206], +] + +[[area.polygons]] +vertices = [ + [2669, 3161], + [2685, 3161], + [2685, 3162], + [2669, 3162], +] + +[[area.polygons]] +vertices = [ + [2669, 3163], + [2676, 3163], + [2676, 3170], + [2669, 3170], +] + +[[area.polygons]] +vertices = [ + [2683, 3163], + [2685, 3163], + [2685, 3164], + [2683, 3164], +] + +[[area.polygons]] +vertices = [[2646, 3165]] + +[[area.polygons]] +vertices = [ + [2661, 3165], + [2662, 3165], +] + +[[area.polygons]] +vertices = [ + [2643, 3166], + [2643, 3176], +] + +[[area.polygons]] +vertices = [ + [2644, 3167], + [2644, 3176], +] + +[[area.polygons]] +vertices = [[2648, 3167]] + +[[area.polygons]] +vertices = [ + [2607, 3168], + [2611, 3168], + [2611, 3205], + [2607, 3205], +] + +[[area.polygons]] +vertices = [ + [2645, 3168], + [2645, 3175], +] + +[[area.polygons]] +vertices = [ + [2581, 3169], + [2581, 3205], +] + +[[area.polygons]] +vertices = [ + [2606, 3169], + [2606, 3206], +] + +[[area.polygons]] +vertices = [ + [2582, 3170], + [2582, 3205], +] + +[[area.polygons]] +vertices = [ + [2605, 3170], + [2605, 3206], +] + +[[area.polygons]] +vertices = [ + [2646, 3170], + [2646, 3174], +] + +[[area.polygons]] +vertices = [ + [2583, 3171], + [2583, 3205], +] + +[[area.polygons]] +vertices = [ + [2604, 3171], + [2604, 3206], +] + +[[area.polygons]] +vertices = [ + [2653, 3171], + [2654, 3171], + [2654, 3179], + [2653, 3179], +] + +[[area.polygons]] +vertices = [ + [2669, 3171], + [2673, 3171], + [2673, 3181], + [2669, 3181], +] + +[[area.polygons]] +vertices = [ + [2676, 3171], + [2676, 3174], +] + +[[area.polygons]] +vertices = [ + [2584, 3172], + [2584, 3205], +] + +[[area.polygons]] +vertices = [ + [2603, 3172], + [2603, 3206], +] + +[[area.polygons]] +vertices = [ + [2585, 3173], + [2602, 3173], + [2602, 3203], + [2585, 3203], +] + +[[area.polygons]] +vertices = [ + [2655, 3173], + [2655, 3175], +] + +[[area.polygons]] +vertices = [ + [2669, 3182], + [2672, 3182], +] + +[[area.polygons]] +vertices = [ + [2633, 3183], + [2633, 3206], +] + +[[area.polygons]] +vertices = [ + [2670, 3183], + [2671, 3183], +] + +[[area.polygons]] +vertices = [ + [2559, 3191], + [2580, 3191], + [2580, 3203], + [2559, 3203], +] + +[[area.polygons]] +vertices = [ + [2634, 3191], + [2634, 3206], +] + +[[area.polygons]] +vertices = [ + [2635, 3192], + [2635, 3207], +] + +[[area.polygons]] +vertices = [ + [2636, 3193], + [2636, 3208], +] + +[[area.polygons]] +vertices = [ + [2637, 3195], + [2638, 3195], + [2638, 3209], + [2637, 3209], +] + +[[area.polygons]] +vertices = [ + [2639, 3196], + [2639, 3209], +] + +[[area.polygons]] +vertices = [ + [2640, 3197], + [2640, 3209], +] + +[[area.polygons]] +vertices = [ + [2641, 3198], + [2641, 3209], +] + +[[area.polygons]] +vertices = [ + [2642, 3199], + [2642, 3209], +] + +[[area.polygons]] +vertices = [ + [2552, 3200], + [2558, 3200], +] + +[[area.polygons]] +vertices = [ + [2643, 3200], + [2643, 3209], +] + +[[area.polygons]] +vertices = [ + [2553, 3201], + [2558, 3201], +] + +[[area.polygons]] +vertices = [ + [2644, 3201], + [2644, 3209], +] + +[[area.polygons]] +vertices = [ + [2554, 3202], + [2558, 3202], +] + +[[area.polygons]] +vertices = [ + [2555, 3203], + [2558, 3203], +] + +[[area.polygons]] +vertices = [ + [2645, 3203], + [2645, 3208], +] + +[[area.polygons]] +vertices = [ + [2578, 3204], + [2580, 3204], + [2580, 3205], + [2578, 3205], +] + +[[area.polygons]] +vertices = [ + [2585, 3204], + [2593, 3204], +] + +[[area.polygons]] +vertices = [ + [2599, 3204], + [2602, 3204], +] + +[[area.polygons]] +vertices = [[2612, 3204]] + +[[area.polygons]] +vertices = [ + [2585, 3205], + [2592, 3205], +] + +[[area.polygons]] +vertices = [ + [2600, 3205], + [2602, 3205], +] + +[[area.polygons]] +vertices = [ + [2646, 3205], + [2647, 3205], + [2647, 3207], + [2646, 3207], +] + +[[area.polygons]] +vertices = [ + [2589, 3206], + [2591, 3206], +] + +[[area.polygons]] +vertices = [ + [2601, 3206], + [2602, 3206], +] + +[[area.polygons]] +vertices = [ + [2607, 3206], + [2610, 3206], +] + +[[area.polygons]] +vertices = [[2648, 3206]] + +[[area.polygons]] +vertices = [[2590, 3207]] diff --git a/.data/raw-cache/map/area/fangs_for_the_memory.toml b/.data/raw-cache/map/area/fangs_for_the_memory.toml new file mode 100644 index 000000000..152e43776 --- /dev/null +++ b/.data/raw-cache/map/area/fangs_for_the_memory.toml @@ -0,0 +1,16 @@ +# Fangs for the Memory music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Fangs for the Memory. +# Bounds x 2816-2879, z 4544-4607; 4096 tiles as 1 rectangles. + +[[area]] +name = "Fangs for the Memory" +area_id = "area.fangs_for_the_memory" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 4544], + [2879, 4544], + [2879, 4607], + [2816, 4607], +] diff --git a/.data/raw-cache/map/area/far_away.toml b/.data/raw-cache/map/area/far_away.toml new file mode 100644 index 000000000..29e3973f9 --- /dev/null +++ b/.data/raw-cache/map/area/far_away.toml @@ -0,0 +1,16 @@ +# Far Away music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Far Away. +# Bounds x 2306-2363, z 3144-3199; 3248 tiles as 1 rectangles. + +[[area]] +name = "Far Away" +area_id = "area.far_away" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2306, 3144], + [2363, 3144], + [2363, 3199], + [2306, 3199], +] diff --git a/.data/raw-cache/map/area/farming_guild.toml b/.data/raw-cache/map/area/farming_guild.toml new file mode 100644 index 000000000..f61e1e739 --- /dev/null +++ b/.data/raw-cache/map/area/farming_guild.toml @@ -0,0 +1,320 @@ +# Farming Guild music music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: A Farmer's Grind; Grow Grow Grow; Hoe Down. +# Bounds x 1220-1274, z 3720-3765; 1663 tiles as 47 rectangles. + +[[area]] +name = "Farming Guild music" +area_id = "area.farming_guild" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1228, 3720], + [1237, 3720], + [1237, 3739], + [1228, 3739], +] + +[[area.polygons]] +vertices = [ + [1260, 3720], + [1269, 3720], + [1269, 3739], + [1260, 3739], +] + +[[area.polygons]] +vertices = [ + [1227, 3721], + [1227, 3739], +] + +[[area.polygons]] +vertices = [ + [1238, 3721], + [1238, 3738], +] + +[[area.polygons]] +vertices = [ + [1259, 3721], + [1259, 3739], +] + +[[area.polygons]] +vertices = [ + [1270, 3721], + [1270, 3738], +] + +[[area.polygons]] +vertices = [ + [1226, 3722], + [1226, 3738], +] + +[[area.polygons]] +vertices = [ + [1239, 3722], + [1239, 3737], +] + +[[area.polygons]] +vertices = [ + [1258, 3722], + [1258, 3738], +] + +[[area.polygons]] +vertices = [ + [1271, 3722], + [1271, 3737], +] + +[[area.polygons]] +vertices = [ + [1225, 3723], + [1225, 3737], +] + +[[area.polygons]] +vertices = [ + [1240, 3723], + [1240, 3736], +] + +[[area.polygons]] +vertices = [ + [1245, 3723], + [1252, 3723], + [1252, 3743], + [1245, 3743], +] + +[[area.polygons]] +vertices = [ + [1257, 3723], + [1257, 3737], +] + +[[area.polygons]] +vertices = [ + [1272, 3723], + [1272, 3736], +] + +[[area.polygons]] +vertices = [ + [1224, 3724], + [1224, 3736], +] + +[[area.polygons]] +vertices = [ + [1241, 3724], + [1241, 3735], +] + +[[area.polygons]] +vertices = [ + [1256, 3724], + [1256, 3736], +] + +[[area.polygons]] +vertices = [ + [1273, 3724], + [1273, 3735], +] + +[[area.polygons]] +vertices = [ + [1223, 3725], + [1223, 3735], +] + +[[area.polygons]] +vertices = [ + [1242, 3725], + [1242, 3734], +] + +[[area.polygons]] +vertices = [ + [1255, 3725], + [1255, 3735], +] + +[[area.polygons]] +vertices = [ + [1274, 3725], + [1274, 3734], +] + +[[area.polygons]] +vertices = [ + [1243, 3728], + [1244, 3728], + [1244, 3731], + [1243, 3731], +] + +[[area.polygons]] +vertices = [ + [1253, 3728], + [1254, 3728], + [1254, 3731], + [1253, 3731], +] + +[[area.polygons]] +vertices = [ + [1242, 3738], + [1244, 3738], + [1244, 3743], + [1242, 3743], +] + +[[area.polygons]] +vertices = [ + [1253, 3738], + [1255, 3738], + [1255, 3743], + [1253, 3743], +] + +[[area.polygons]] +vertices = [ + [1263, 3740], + [1266, 3740], + [1266, 3751], + [1263, 3751], +] + +[[area.polygons]] +vertices = [ + [1226, 3744], + [1231, 3744], + [1231, 3765], + [1226, 3765], +] + +[[area.polygons]] +vertices = [ + [1247, 3744], + [1250, 3744], + [1250, 3763], + [1247, 3763], +] + +[[area.polygons]] +vertices = [ + [1262, 3744], + [1262, 3751], +] + +[[area.polygons]] +vertices = [ + [1267, 3744], + [1267, 3751], +] + +[[area.polygons]] +vertices = [ + [1261, 3745], + [1261, 3751], +] + +[[area.polygons]] +vertices = [ + [1268, 3745], + [1268, 3750], +] + +[[area.polygons]] +vertices = [ + [1240, 3746], + [1246, 3746], + [1246, 3762], + [1240, 3762], +] + +[[area.polygons]] +vertices = [ + [1251, 3746], + [1255, 3746], + [1255, 3763], + [1251, 3763], +] + +[[area.polygons]] +vertices = [ + [1222, 3747], + [1225, 3747], + [1225, 3762], + [1222, 3762], +] + +[[area.polygons]] +vertices = [ + [1232, 3747], + [1235, 3747], + [1235, 3762], + [1232, 3762], +] + +[[area.polygons]] +vertices = [ + [1239, 3747], + [1239, 3761], +] + +[[area.polygons]] +vertices = [ + [1256, 3747], + [1256, 3762], +] + +[[area.polygons]] +vertices = [ + [1221, 3748], + [1221, 3761], +] + +[[area.polygons]] +vertices = [ + [1236, 3748], + [1236, 3761], +] + +[[area.polygons]] +vertices = [ + [1257, 3748], + [1257, 3761], +] + +[[area.polygons]] +vertices = [ + [1220, 3749], + [1220, 3760], +] + +[[area.polygons]] +vertices = [ + [1237, 3749], + [1238, 3749], + [1238, 3760], + [1237, 3760], +] + +[[area.polygons]] +vertices = [ + [1258, 3749], + [1258, 3760], +] + +[[area.polygons]] +vertices = [ + [1241, 3763], + [1246, 3763], +] diff --git a/.data/raw-cache/map/area/fe_fi_fo_fum.toml b/.data/raw-cache/map/area/fe_fi_fo_fum.toml new file mode 100644 index 000000000..bf43a6e84 --- /dev/null +++ b/.data/raw-cache/map/area/fe_fi_fo_fum.toml @@ -0,0 +1,16 @@ +# Fe Fi Fo Fum music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Fe Fi Fo Fum. +# Bounds x 2112-2175, z 5504-5567; 4096 tiles as 1 rectangles. + +[[area]] +name = "Fe Fi Fo Fum" +area_id = "area.fe_fi_fo_fum" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2112, 5504], + [2175, 5504], + [2175, 5567], + [2112, 5567], +] diff --git a/.data/raw-cache/map/area/fear_and_loathing.toml b/.data/raw-cache/map/area/fear_and_loathing.toml new file mode 100644 index 000000000..8ef5795f3 --- /dev/null +++ b/.data/raw-cache/map/area/fear_and_loathing.toml @@ -0,0 +1,32 @@ +# Fear and Loathing music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Fear and Loathing. +# Bounds x 3040-3327, z 5216-9855; 2003 tiles as 3 rectangles. + +[[area]] +name = "Fear and Loathing" +area_id = "area.fear_and_loathing" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3040, 5216], + [3071, 5216], + [3071, 5247], + [3040, 5247], +] + +[[area.polygons]] +vertices = [ + [3296, 9824], + [3327, 9824], + [3327, 9852], + [3296, 9852], +] + +[[area.polygons]] +vertices = [ + [3311, 9853], + [3327, 9853], + [3327, 9855], + [3311, 9855], +] diff --git a/.data/raw-cache/map/area/feldip_hills.toml b/.data/raw-cache/map/area/feldip_hills.toml new file mode 100644 index 000000000..b382381eb --- /dev/null +++ b/.data/raw-cache/map/area/feldip_hills.toml @@ -0,0 +1,1638 @@ +# Feldip Hills music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Chompy Hunt; Grumpy; Jungle Hunt. +# Bounds x 1920-2659, z 2831-9401; 40246 tiles as 259 rectangles. + +[[area]] +name = "Feldip Hills" +area_id = "area.feldip_hills" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2532, 2831], + [2532, 3007], +] + +[[area.polygons]] +vertices = [ + [2527, 2832], + [2531, 2832], + [2531, 3007], + [2527, 3007], +] + +[[area.polygons]] +vertices = [ + [2533, 2832], + [2534, 2832], + [2534, 3007], + [2533, 3007], +] + +[[area.polygons]] +vertices = [ + [2525, 2833], + [2526, 2833], + [2526, 3007], + [2525, 3007], +] + +[[area.polygons]] +vertices = [ + [2535, 2833], + [2535, 3007], +] + +[[area.polygons]] +vertices = [ + [2523, 2834], + [2524, 2834], + [2524, 3007], + [2523, 3007], +] + +[[area.polygons]] +vertices = [ + [2522, 2835], + [2522, 3007], +] + +[[area.polygons]] +vertices = [ + [2504, 2836], + [2505, 2836], + [2505, 3007], + [2504, 3007], +] + +[[area.polygons]] +vertices = [ + [2521, 2836], + [2521, 3007], +] + +[[area.polygons]] +vertices = [ + [2536, 2836], + [2536, 3007], +] + +[[area.polygons]] +vertices = [ + [2501, 2837], + [2503, 2837], + [2503, 3007], + [2501, 3007], +] + +[[area.polygons]] +vertices = [ + [2506, 2837], + [2506, 3007], +] + +[[area.polygons]] +vertices = [ + [2520, 2837], + [2520, 3007], +] + +[[area.polygons]] +vertices = [ + [2494, 2838], + [2495, 2838], + [2495, 3018], + [2494, 3018], +] + +[[area.polygons]] +vertices = [ + [2499, 2838], + [2500, 2838], + [2500, 3007], + [2499, 3007], +] + +[[area.polygons]] +vertices = [ + [2507, 2838], + [2507, 3007], +] + +[[area.polygons]] +vertices = [ + [2512, 2838], + [2513, 2838], + [2513, 3007], + [2512, 3007], +] + +[[area.polygons]] +vertices = [ + [2518, 2838], + [2519, 2838], + [2519, 3007], + [2518, 3007], +] + +[[area.polygons]] +vertices = [ + [2537, 2838], + [2537, 3007], +] + +[[area.polygons]] +vertices = [ + [2491, 2839], + [2493, 2839], + [2493, 3018], + [2491, 3018], +] + +[[area.polygons]] +vertices = [ + [2496, 2839], + [2496, 3007], +] + +[[area.polygons]] +vertices = [ + [2498, 2839], + [2498, 3007], +] + +[[area.polygons]] +vertices = [ + [2508, 2839], + [2511, 2839], + [2511, 3007], + [2508, 3007], +] + +[[area.polygons]] +vertices = [ + [2514, 2839], + [2517, 2839], + [2517, 3007], + [2514, 3007], +] + +[[area.polygons]] +vertices = [ + [2538, 2839], + [2538, 3007], +] + +[[area.polygons]] +vertices = [ + [2489, 2840], + [2490, 2840], + [2490, 3018], + [2489, 3018], +] + +[[area.polygons]] +vertices = [ + [2497, 2840], + [2497, 3007], +] + +[[area.polygons]] +vertices = [ + [2539, 2840], + [2539, 3007], +] + +[[area.polygons]] +vertices = [ + [2487, 2841], + [2488, 2841], + [2488, 3018], + [2487, 3018], +] + +[[area.polygons]] +vertices = [ + [2540, 2841], + [2540, 2858], +] + +[[area.polygons]] +vertices = [ + [2486, 2843], + [2486, 2954], +] + +[[area.polygons]] +vertices = [ + [2541, 2843], + [2541, 2857], +] + +[[area.polygons]] +vertices = [ + [2542, 2844], + [2547, 2844], + [2547, 2850], + [2542, 2850], +] + +[[area.polygons]] +vertices = [ + [2485, 2845], + [2485, 2952], +] + +[[area.polygons]] +vertices = [ + [2484, 2847], + [2484, 2950], +] + +[[area.polygons]] +vertices = [ + [2483, 2848], + [2483, 2948], +] + +[[area.polygons]] +vertices = [ + [2482, 2849], + [2482, 2947], +] + +[[area.polygons]] +vertices = [ + [2479, 2850], + [2481, 2850], + [2481, 2945], + [2479, 2945], +] + +[[area.polygons]] +vertices = [ + [2477, 2851], + [2478, 2851], + [2478, 2916], + [2477, 2916], +] + +[[area.polygons]] +vertices = [ + [2542, 2851], + [2546, 2851], + [2546, 2852], + [2542, 2852], +] + +[[area.polygons]] +vertices = [ + [2476, 2852], + [2476, 2914], +] + +[[area.polygons]] +vertices = [ + [2475, 2853], + [2475, 2913], +] + +[[area.polygons]] +vertices = [ + [2542, 2853], + [2545, 2853], +] + +[[area.polygons]] +vertices = [ + [2542, 2854], + [2544, 2854], +] + +[[area.polygons]] +vertices = [ + [2474, 2855], + [2474, 2912], +] + +[[area.polygons]] +vertices = [ + [2542, 2855], + [2543, 2855], +] + +[[area.polygons]] +vertices = [[2542, 2856]] + +[[area.polygons]] +vertices = [ + [2473, 2857], + [2473, 2863], +] + +[[area.polygons]] +vertices = [ + [2455, 2862], + [2459, 2862], + [2459, 2871], + [2455, 2871], +] + +[[area.polygons]] +vertices = [ + [2454, 2863], + [2454, 2873], +] + +[[area.polygons]] +vertices = [ + [2460, 2863], + [2461, 2863], + [2461, 2870], + [2460, 2870], +] + +[[area.polygons]] +vertices = [ + [2453, 2864], + [2453, 2872], +] + +[[area.polygons]] +vertices = [ + [2462, 2864], + [2463, 2864], + [2463, 2870], + [2462, 2870], +] + +[[area.polygons]] +vertices = [ + [2540, 2864], + [2540, 3007], +] + +[[area.polygons]] +vertices = [ + [2464, 2865], + [2464, 2869], +] + +[[area.polygons]] +vertices = [ + [2452, 2866], + [2452, 2870], +] + +[[area.polygons]] +vertices = [ + [2541, 2866], + [2541, 3007], +] + +[[area.polygons]] +vertices = [ + [2465, 2867], + [2473, 2867], +] + +[[area.polygons]] +vertices = [ + [2542, 2868], + [2542, 3007], +] + +[[area.polygons]] +vertices = [ + [2543, 2870], + [2543, 3007], +] + +[[area.polygons]] +vertices = [ + [2594, 2870], + [2599, 2870], + [2599, 3009], + [2594, 3009], +] + +[[area.polygons]] +vertices = [ + [2473, 2871], + [2473, 2912], +] + +[[area.polygons]] +vertices = [ + [2592, 2871], + [2593, 2871], + [2593, 3009], + [2592, 3009], +] + +[[area.polygons]] +vertices = [ + [2600, 2871], + [2600, 2909], +] + +[[area.polygons]] +vertices = [ + [2455, 2872], + [2457, 2872], +] + +[[area.polygons]] +vertices = [ + [2544, 2872], + [2544, 3007], +] + +[[area.polygons]] +vertices = [ + [2590, 2872], + [2591, 2872], + [2591, 3011], + [2590, 3011], +] + +[[area.polygons]] +vertices = [ + [2601, 2872], + [2601, 2909], +] + +[[area.polygons]] +vertices = [[2455, 2873]] + +[[area.polygons]] +vertices = [ + [2545, 2873], + [2589, 2873], + [2589, 3007], + [2545, 3007], +] + +[[area.polygons]] +vertices = [ + [2602, 2873], + [2604, 2873], + [2604, 2907], + [2602, 2907], +] + +[[area.polygons]] +vertices = [ + [2472, 2874], + [2472, 2912], +] + +[[area.polygons]] +vertices = [ + [2605, 2874], + [2607, 2874], + [2607, 2907], + [2605, 2907], +] + +[[area.polygons]] +vertices = [ + [2608, 2875], + [2610, 2875], + [2610, 2907], + [2608, 2907], +] + +[[area.polygons]] +vertices = [ + [2471, 2876], + [2471, 2911], +] + +[[area.polygons]] +vertices = [ + [2611, 2876], + [2611, 2910], +] + +[[area.polygons]] +vertices = [ + [2470, 2877], + [2470, 2910], +] + +[[area.polygons]] +vertices = [ + [2612, 2877], + [2612, 2911], +] + +[[area.polygons]] +vertices = [ + [2613, 2878], + [2613, 2880], +] + +[[area.polygons]] +vertices = [ + [2469, 2879], + [2469, 2908], +] + +[[area.polygons]] +vertices = [ + [2613, 2882], + [2613, 2897], +] + +[[area.polygons]] +vertices = [ + [2468, 2883], + [2468, 2904], +] + +[[area.polygons]] +vertices = [ + [2614, 2883], + [2614, 2896], +] + +[[area.polygons]] +vertices = [ + [2467, 2885], + [2467, 2903], +] + +[[area.polygons]] +vertices = [ + [2615, 2885], + [2615, 2895], +] + +[[area.polygons]] +vertices = [ + [2466, 2886], + [2466, 2901], +] + +[[area.polygons]] +vertices = [ + [2616, 2886], + [2616, 2893], +] + +[[area.polygons]] +vertices = [ + [2464, 2887], + [2465, 2887], + [2465, 2898], + [2464, 2898], +] + +[[area.polygons]] +vertices = [ + [2617, 2887], + [2618, 2887], + [2618, 2890], + [2617, 2890], +] + +[[area.polygons]] +vertices = [ + [2619, 2888], + [2619, 2889], +] + +[[area.polygons]] +vertices = [ + [2463, 2889], + [2463, 2896], +] + +[[area.polygons]] +vertices = [ + [2454, 2891], + [2462, 2891], + [2462, 2892], + [2454, 2892], +] + +[[area.polygons]] +vertices = [ + [2617, 2891], + [2617, 2892], +] + +[[area.polygons]] +vertices = [[2465, 2899]] + +[[area.polygons]] +vertices = [ + [2613, 2901], + [2613, 2912], +] + +[[area.polygons]] +vertices = [ + [2614, 2902], + [2617, 2902], + [2617, 2905], + [2614, 2905], +] + +[[area.polygons]] +vertices = [ + [2618, 2903], + [2618, 2904], +] + +[[area.polygons]] +vertices = [ + [2614, 2906], + [2616, 2906], +] + +[[area.polygons]] +vertices = [ + [2614, 2907], + [2614, 2912], +] + +[[area.polygons]] +vertices = [ + [2602, 2908], + [2603, 2908], +] + +[[area.polygons]] +vertices = [ + [2609, 2908], + [2610, 2908], +] + +[[area.polygons]] +vertices = [[2602, 2909]] + +[[area.polygons]] +vertices = [[2610, 2909]] + +[[area.polygons]] +vertices = [ + [2615, 2909], + [2617, 2909], + [2617, 2912], + [2615, 2912], +] + +[[area.polygons]] +vertices = [ + [2618, 2910], + [2618, 2913], +] + +[[area.polygons]] +vertices = [ + [2605, 2911], + [2607, 2911], + [2607, 3013], + [2605, 3013], +] + +[[area.polygons]] +vertices = [ + [2604, 2912], + [2604, 3012], +] + +[[area.polygons]] +vertices = [ + [2608, 2912], + [2609, 2912], + [2609, 3012], + [2608, 3012], +] + +[[area.polygons]] +vertices = [ + [2600, 2913], + [2603, 2913], + [2603, 3011], + [2600, 3011], +] + +[[area.polygons]] +vertices = [ + [2610, 2913], + [2610, 3011], +] + +[[area.polygons]] +vertices = [ + [2619, 2913], + [2619, 2914], +] + +[[area.polygons]] +vertices = [ + [2611, 2915], + [2612, 2915], + [2612, 3012], + [2611, 3012], +] + +[[area.polygons]] +vertices = [ + [2613, 2916], + [2613, 3013], +] + +[[area.polygons]] +vertices = [ + [2478, 2917], + [2478, 2944], +] + +[[area.polygons]] +vertices = [ + [2614, 2918], + [2615, 2918], + [2615, 2919], + [2614, 2919], +] + +[[area.polygons]] +vertices = [ + [2614, 2920], + [2614, 2921], +] + +[[area.polygons]] +vertices = [ + [2616, 2921], + [2616, 2922], +] + +[[area.polygons]] +vertices = [[2617, 2922]] + +[[area.polygons]] +vertices = [ + [2614, 2923], + [2614, 3013], +] + +[[area.polygons]] +vertices = [ + [2618, 2923], + [2618, 2924], +] + +[[area.polygons]] +vertices = [ + [2615, 2924], + [2617, 2924], +] + +[[area.polygons]] +vertices = [ + [2477, 2925], + [2477, 2944], +] + +[[area.polygons]] +vertices = [ + [2615, 2925], + [2616, 2925], +] + +[[area.polygons]] +vertices = [ + [2615, 2926], + [2615, 3013], +] + +[[area.polygons]] +vertices = [ + [2476, 2927], + [2476, 2943], +] + +[[area.polygons]] +vertices = [ + [2616, 2927], + [2616, 2932], +] + +[[area.polygons]] +vertices = [ + [2475, 2928], + [2475, 2942], +] + +[[area.polygons]] +vertices = [ + [2617, 2929], + [2617, 2931], +] + +[[area.polygons]] +vertices = [ + [2474, 2930], + [2474, 2942], +] + +[[area.polygons]] +vertices = [[2618, 2930]] + +[[area.polygons]] +vertices = [ + [2473, 2931], + [2473, 2941], +] + +[[area.polygons]] +vertices = [ + [2472, 2933], + [2472, 2937], +] + +[[area.polygons]] +vertices = [ + [2616, 2936], + [2616, 3013], +] + +[[area.polygons]] +vertices = [ + [2617, 2937], + [2617, 3013], +] + +[[area.polygons]] +vertices = [ + [2618, 2938], + [2618, 3012], +] + +[[area.polygons]] +vertices = [ + [2619, 2940], + [2620, 2940], + [2620, 3011], + [2619, 3011], +] + +[[area.polygons]] +vertices = [ + [2621, 2941], + [2621, 3010], +] + +[[area.polygons]] +vertices = [ + [2622, 2943], + [2622, 3009], +] + +[[area.polygons]] +vertices = [ + [2623, 2944], + [2626, 2944], + [2626, 3008], + [2623, 3008], +] + +[[area.polygons]] +vertices = [ + [2627, 2945], + [2627, 3008], +] + +[[area.polygons]] +vertices = [ + [2480, 2946], + [2481, 2946], +] + +[[area.polygons]] +vertices = [ + [2628, 2946], + [2628, 3008], +] + +[[area.polygons]] +vertices = [ + [2636, 2946], + [2639, 2946], + [2639, 2972], + [2636, 2972], +] + +[[area.polygons]] +vertices = [[2481, 2947]] + +[[area.polygons]] +vertices = [ + [2634, 2947], + [2635, 2947], + [2635, 3009], + [2634, 3009], +] + +[[area.polygons]] +vertices = [ + [2640, 2947], + [2640, 2969], +] + +[[area.polygons]] +vertices = [ + [2629, 2948], + [2629, 3008], +] + +[[area.polygons]] +vertices = [ + [2641, 2948], + [2641, 2949], +] + +[[area.polygons]] +vertices = [ + [2630, 2950], + [2630, 3008], +] + +[[area.polygons]] +vertices = [ + [2631, 2951], + [2631, 3008], +] + +[[area.polygons]] +vertices = [ + [2641, 2951], + [2641, 2967], +] + +[[area.polygons]] +vertices = [ + [2646, 2951], + [2649, 2951], + [2649, 2968], + [2646, 2968], +] + +[[area.polygons]] +vertices = [ + [2632, 2952], + [2633, 2952], + [2633, 3008], + [2632, 3008], +] + +[[area.polygons]] +vertices = [ + [2642, 2952], + [2645, 2952], + [2645, 2966], + [2642, 2966], +] + +[[area.polygons]] +vertices = [ + [2650, 2952], + [2650, 2969], +] + +[[area.polygons]] +vertices = [ + [2651, 2953], + [2651, 2969], +] + +[[area.polygons]] +vertices = [ + [2652, 2954], + [2652, 2968], +] + +[[area.polygons]] +vertices = [ + [2653, 2956], + [2653, 2966], +] + +[[area.polygons]] +vertices = [ + [2486, 2958], + [2486, 3019], +] + +[[area.polygons]] +vertices = [ + [2654, 2959], + [2654, 2965], +] + +[[area.polygons]] +vertices = [ + [2485, 2961], + [2485, 3020], +] + +[[area.polygons]] +vertices = [ + [2484, 2963], + [2484, 2997], +] + +[[area.polygons]] +vertices = [ + [2483, 2965], + [2483, 2994], +] + +[[area.polygons]] +vertices = [ + [2480, 2966], + [2482, 2966], + [2482, 2978], + [2480, 2978], +] + +[[area.polygons]] +vertices = [[2642, 2967]] + +[[area.polygons]] +vertices = [[2645, 2967]] + +[[area.polygons]] +vertices = [ + [2479, 2968], + [2479, 2977], +] + +[[area.polygons]] +vertices = [ + [2478, 2969], + [2478, 2976], +] + +[[area.polygons]] +vertices = [ + [2477, 2971], + [2477, 2976], +] + +[[area.polygons]] +vertices = [ + [2476, 2973], + [2476, 2976], +] + +[[area.polygons]] +vertices = [ + [2636, 2973], + [2638, 2973], +] + +[[area.polygons]] +vertices = [ + [2636, 2974], + [2637, 2974], + [2637, 3009], + [2636, 3009], +] + +[[area.polygons]] +vertices = [ + [2638, 2977], + [2638, 3009], +] + +[[area.polygons]] +vertices = [[2482, 2979]] + +[[area.polygons]] +vertices = [ + [2639, 2979], + [2639, 3009], +] + +[[area.polygons]] +vertices = [ + [2640, 2980], + [2640, 3008], +] + +[[area.polygons]] +vertices = [ + [2641, 2981], + [2641, 3008], +] + +[[area.polygons]] +vertices = [ + [2642, 2982], + [2642, 3007], +] + +[[area.polygons]] +vertices = [ + [2482, 2983], + [2482, 2993], +] + +[[area.polygons]] +vertices = [ + [2481, 2984], + [2481, 2990], +] + +[[area.polygons]] +vertices = [ + [2643, 2984], + [2643, 3006], +] + +[[area.polygons]] +vertices = [ + [2644, 2985], + [2644, 3005], +] + +[[area.polygons]] +vertices = [ + [2480, 2986], + [2480, 2989], +] + +[[area.polygons]] +vertices = [ + [2645, 2986], + [2645, 3004], +] + +[[area.polygons]] +vertices = [ + [2646, 2987], + [2646, 3003], +] + +[[area.polygons]] +vertices = [ + [2647, 2988], + [2647, 3002], +] + +[[area.polygons]] +vertices = [ + [2648, 2989], + [2648, 3001], +] + +[[area.polygons]] +vertices = [ + [2649, 2994], + [2649, 3000], +] + +[[area.polygons]] +vertices = [ + [2650, 2996], + [2650, 2999], +] + +[[area.polygons]] +vertices = [ + [2484, 3000], + [2484, 3021], +] + +[[area.polygons]] +vertices = [ + [2483, 3003], + [2483, 3022], +] + +[[area.polygons]] +vertices = [ + [2482, 3004], + [2482, 3023], +] + +[[area.polygons]] +vertices = [ + [2481, 3005], + [2481, 3024], +] + +[[area.polygons]] +vertices = [ + [2480, 3006], + [2480, 3025], +] + +[[area.polygons]] +vertices = [ + [2479, 3007], + [2479, 3026], +] + +[[area.polygons]] +vertices = [ + [2477, 3008], + [2478, 3008], + [2478, 3027], + [2477, 3027], +] + +[[area.polygons]] +vertices = [ + [2560, 3008], + [2589, 3008], + [2589, 3009], + [2560, 3009], +] + +[[area.polygons]] +vertices = [ + [2476, 3009], + [2476, 3029], +] + +[[area.polygons]] +vertices = [ + [2474, 3010], + [2475, 3010], + [2475, 3030], + [2474, 3030], +] + +[[area.polygons]] +vertices = [ + [2560, 3010], + [2573, 3010], +] + +[[area.polygons]] +vertices = [ + [2579, 3010], + [2589, 3010], +] + +[[area.polygons]] +vertices = [[2592, 3010]] + +[[area.polygons]] +vertices = [ + [2598, 3010], + [2599, 3010], +] + +[[area.polygons]] +vertices = [ + [2560, 3011], + [2572, 3011], + [2572, 3012], + [2560, 3012], +] + +[[area.polygons]] +vertices = [ + [2580, 3011], + [2589, 3011], +] + +[[area.polygons]] +vertices = [[2599, 3011]] + +[[area.polygons]] +vertices = [ + [2473, 3012], + [2473, 3013], +] + +[[area.polygons]] +vertices = [ + [2583, 3012], + [2589, 3012], +] + +[[area.polygons]] +vertices = [[2603, 3012]] + +[[area.polygons]] +vertices = [[2619, 3012]] + +[[area.polygons]] +vertices = [ + [2560, 3013], + [2571, 3013], +] + +[[area.polygons]] +vertices = [ + [2584, 3013], + [2587, 3013], +] + +[[area.polygons]] +vertices = [[2608, 3013]] + +[[area.polygons]] +vertices = [[2612, 3013]] + +[[area.polygons]] +vertices = [ + [2560, 3014], + [2570, 3014], + [2570, 3015], + [2560, 3015], +] + +[[area.polygons]] +vertices = [ + [2575, 3014], + [2578, 3014], + [2578, 3036], + [2575, 3036], +] + +[[area.polygons]] +vertices = [ + [2574, 3015], + [2574, 3041], +] + +[[area.polygons]] +vertices = [ + [2579, 3015], + [2579, 3035], +] + +[[area.polygons]] +vertices = [ + [2560, 3016], + [2569, 3016], +] + +[[area.polygons]] +vertices = [ + [2572, 3016], + [2573, 3016], + [2573, 3042], + [2572, 3042], +] + +[[area.polygons]] +vertices = [ + [2560, 3017], + [2568, 3017], +] + +[[area.polygons]] +vertices = [ + [2462, 3018], + [2462, 3030], +] + +[[area.polygons]] +vertices = [ + [2467, 3018], + [2467, 3030], +] + +[[area.polygons]] +vertices = [ + [2560, 3018], + [2566, 3018], + [2566, 3019], + [2560, 3019], +] + +[[area.polygons]] +vertices = [ + [2571, 3018], + [2571, 3042], +] + +[[area.polygons]] +vertices = [ + [2580, 3018], + [2580, 3034], +] + +[[area.polygons]] +vertices = [ + [2461, 3019], + [2461, 3030], +] + +[[area.polygons]] +vertices = [ + [2463, 3019], + [2466, 3019], + [2466, 3030], + [2463, 3030], +] + +[[area.polygons]] +vertices = [ + [2468, 3019], + [2468, 3030], +] + +[[area.polygons]] +vertices = [ + [2473, 3019], + [2473, 3030], +] + +[[area.polygons]] +vertices = [ + [2568, 3019], + [2570, 3019], + [2570, 3040], + [2568, 3040], +] + +[[area.polygons]] +vertices = [ + [2581, 3019], + [2581, 3033], +] + +[[area.polygons]] +vertices = [ + [2460, 3020], + [2460, 3030], +] + +[[area.polygons]] +vertices = [ + [2469, 3020], + [2472, 3020], + [2472, 3030], + [2469, 3030], +] + +[[area.polygons]] +vertices = [ + [2560, 3020], + [2565, 3020], + [2565, 3040], + [2560, 3040], +] + +[[area.polygons]] +vertices = [ + [2582, 3020], + [2582, 3032], +] + +[[area.polygons]] +vertices = [ + [2459, 3021], + [2459, 3030], +] + +[[area.polygons]] +vertices = [ + [2566, 3021], + [2567, 3021], + [2567, 3040], + [2566, 3040], +] + +[[area.polygons]] +vertices = [ + [2583, 3021], + [2583, 3031], +] + +[[area.polygons]] +vertices = [ + [2458, 3022], + [2458, 3030], +] + +[[area.polygons]] +vertices = [ + [2457, 3023], + [2457, 3030], +] + +[[area.polygons]] +vertices = [ + [2452, 3024], + [2452, 3029], +] + +[[area.polygons]] +vertices = [ + [2455, 3024], + [2456, 3024], + [2456, 3030], + [2455, 3030], +] + +[[area.polygons]] +vertices = [ + [2451, 3025], + [2451, 3028], +] + +[[area.polygons]] +vertices = [ + [2453, 3025], + [2454, 3025], + [2454, 3030], + [2453, 3030], +] + +[[area.polygons]] +vertices = [ + [2584, 3025], + [2584, 3030], +] + +[[area.polygons]] +vertices = [ + [2450, 3026], + [2450, 3027], +] + +[[area.polygons]] +vertices = [ + [2585, 3026], + [2585, 3028], +] + +[[area.polygons]] +vertices = [[2477, 3028]] + +[[area.polygons]] +vertices = [ + [2575, 3037], + [2577, 3037], + [2577, 3038], + [2575, 3038], +] + +[[area.polygons]] +vertices = [ + [2575, 3039], + [2576, 3039], +] + +[[area.polygons]] +vertices = [[2575, 3040]] + +[[area.polygons]] +vertices = [ + [2560, 3041], + [2564, 3041], +] + +[[area.polygons]] +vertices = [[2570, 3041]] + +[[area.polygons]] +vertices = [ + [2560, 3042], + [2563, 3042], +] + +[[area.polygons]] +vertices = [ + [1968, 8960], + [2111, 8960], + [2111, 9023], + [1968, 9023], +] + +[[area.polygons]] +vertices = [ + [1920, 9024], + [1983, 9024], + [1983, 9087], + [1920, 9087], +] + +[[area.polygons]] +vertices = [ + [2506, 9284], + [2535, 9284], + [2535, 9331], + [2506, 9331], +] + +[[area.polygons]] +vertices = [ + [2632, 9376], + [2659, 9376], + [2659, 9401], + [2632, 9401], +] diff --git a/.data/raw-cache/map/area/fenkenstrains_refrain.toml b/.data/raw-cache/map/area/fenkenstrains_refrain.toml new file mode 100644 index 000000000..9360af074 --- /dev/null +++ b/.data/raw-cache/map/area/fenkenstrains_refrain.toml @@ -0,0 +1,211 @@ +# Fenkenstrain's Refrain music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Fenkenstrain's Refrain. +# Bounds x 3535-3562, z 3536-3567; 555 tiles as 31 rectangles. + +[[area]] +name = "Fenkenstrain's Refrain" +area_id = "area.fenkenstrains_refrain" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3547, 3536], + [3549, 3536], + [3549, 3542], + [3547, 3542], +] + +[[area.polygons]] +vertices = [ + [3546, 3537], + [3546, 3541], +] + +[[area.polygons]] +vertices = [ + [3550, 3537], + [3550, 3541], +] + +[[area.polygons]] +vertices = [ + [3551, 3538], + [3551, 3540], +] + +[[area.polygons]] +vertices = [ + [3539, 3540], + [3541, 3540], + [3541, 3545], + [3539, 3545], +] + +[[area.polygons]] +vertices = [ + [3555, 3540], + [3557, 3540], + [3557, 3561], + [3555, 3561], +] + +[[area.polygons]] +vertices = [ + [3538, 3541], + [3538, 3544], +] + +[[area.polygons]] +vertices = [ + [3542, 3541], + [3542, 3561], +] + +[[area.polygons]] +vertices = [ + [3554, 3541], + [3554, 3563], +] + +[[area.polygons]] +vertices = [ + [3558, 3541], + [3558, 3544], +] + +[[area.polygons]] +vertices = [ + [3543, 3542], + [3543, 3564], +] + +[[area.polygons]] +vertices = [ + [3559, 3542], + [3559, 3543], +] + +[[area.polygons]] +vertices = [ + [3548, 3543], + [3549, 3543], + [3549, 3567], + [3548, 3567], +] + +[[area.polygons]] +vertices = [ + [3547, 3544], + [3547, 3567], +] + +[[area.polygons]] +vertices = [ + [3553, 3544], + [3553, 3564], +] + +[[area.polygons]] +vertices = [ + [3544, 3545], + [3546, 3545], + [3546, 3565], + [3544, 3565], +] + +[[area.polygons]] +vertices = [ + [3550, 3545], + [3552, 3545], + [3552, 3566], + [3550, 3566], +] + +[[area.polygons]] +vertices = [ + [3540, 3546], + [3541, 3546], + [3541, 3561], + [3540, 3561], +] + +[[area.polygons]] +vertices = [ + [3536, 3549], + [3539, 3549], + [3539, 3557], + [3536, 3557], +] + +[[area.polygons]] +vertices = [ + [3558, 3549], + [3560, 3549], + [3560, 3557], + [3558, 3557], +] + +[[area.polygons]] +vertices = [ + [3561, 3550], + [3561, 3557], +] + +[[area.polygons]] +vertices = [ + [3535, 3552], + [3535, 3557], +] + +[[area.polygons]] +vertices = [ + [3562, 3553], + [3562, 3556], +] + +[[area.polygons]] +vertices = [ + [3539, 3558], + [3539, 3562], +] + +[[area.polygons]] +vertices = [ + [3558, 3558], + [3558, 3561], +] + +[[area.polygons]] +vertices = [ + [3538, 3559], + [3538, 3561], +] + +[[area.polygons]] +vertices = [ + [3559, 3560], + [3559, 3561], +] + +[[area.polygons]] +vertices = [[3540, 3562]] + +[[area.polygons]] +vertices = [ + [3556, 3562], + [3557, 3562], +] + +[[area.polygons]] +vertices = [ + [3545, 3566], + [3546, 3566], + [3546, 3567], + [3545, 3567], +] + +[[area.polygons]] +vertices = [ + [3550, 3567], + [3551, 3567], +] diff --git a/.data/raw-cache/map/area/find_my_way.toml b/.data/raw-cache/map/area/find_my_way.toml new file mode 100644 index 000000000..0ee30004b --- /dev/null +++ b/.data/raw-cache/map/area/find_my_way.toml @@ -0,0 +1,16 @@ +# Find My Way music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Find My Way. +# Bounds x 2688-2815, z 9088-9151; 8192 tiles as 1 rectangles. + +[[area]] +name = "Find My Way" +area_id = "area.find_my_way" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 9088], + [2815, 9088], + [2815, 9151], + [2688, 9151], +] diff --git a/.data/raw-cache/map/area/fire_and_brimstone.toml b/.data/raw-cache/map/area/fire_and_brimstone.toml new file mode 100644 index 000000000..3bb17a550 --- /dev/null +++ b/.data/raw-cache/map/area/fire_and_brimstone.toml @@ -0,0 +1,16 @@ +# Fire and Brimstone music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Fire and Brimstone. +# Bounds x 2368-2431, z 5120-5183; 4096 tiles as 1 rectangles. + +[[area]] +name = "Fire and Brimstone" +area_id = "area.fire_and_brimstone" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 5120], + [2431, 5120], + [2431, 5183], + [2368, 5183], +] diff --git a/.data/raw-cache/map/area/fire_in_the_deep.toml b/.data/raw-cache/map/area/fire_in_the_deep.toml new file mode 100644 index 000000000..9963be320 --- /dev/null +++ b/.data/raw-cache/map/area/fire_in_the_deep.toml @@ -0,0 +1,16 @@ +# Fire in the Deep music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Fire in the Deep. +# Bounds x 3200-3263, z 5696-5759; 4096 tiles as 1 rectangles. + +[[area]] +name = "Fire in the Deep" +area_id = "area.fire_in_the_deep" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3200, 5696], + [3263, 5696], + [3263, 5759], + [3200, 5759], +] diff --git a/.data/raw-cache/map/area/fishing_guild.toml b/.data/raw-cache/map/area/fishing_guild.toml index f4df994da..90d4051af 100644 --- a/.data/raw-cache/map/area/fishing_guild.toml +++ b/.data/raw-cache/map/area/fishing_guild.toml @@ -1,3 +1,6 @@ +# Regenerated as rectangles from the OSRS wiki music map: 4 tiles moved to +# neighbouring music areas and 14552 tiles were added. + [[area]] name = "Fishing Guild" area_id = "area.fishing_guild" @@ -5,40 +8,725 @@ levels = [0] [[area.polygons]] vertices = [ - [2594, 3423], - [2590, 3424], - [2579, 3424], - [2578, 3416], - [2580, 3415], - [2584, 3414], - [2585, 3409], - [2587, 3408], - [2587, 3405], - [2589, 3404], - [2590, 3399], - [2592, 3396], - [2593, 3394], - [2601, 3393], - [2604, 3393], - [2615, 3393], - [2619, 3393], - [2620, 3394], - [2622, 3396], - [2623, 3397], - [2624, 3399], - [2624, 3402], - [2623, 3403], - [2622, 3404], - [2619, 3408], - [2619, 3411], - [2619, 3413], - [2619, 3417], - [2617, 3418], - [2613, 3421], - [2609, 3423], - [2605, 3426], - [2601, 3428], - [2599, 3428], - [2597, 3430], - [2595, 3426], + [2593, 3350], + [2622, 3350], + [2622, 3451], + [2593, 3451], +] + +[[area.polygons]] +vertices = [ + [2592, 3352], + [2592, 3507], +] + +[[area.polygons]] +vertices = [ + [2591, 3354], + [2591, 3508], +] + +[[area.polygons]] +vertices = [ + [2623, 3355], + [2623, 3450], +] + +[[area.polygons]] +vertices = [ + [2590, 3356], + [2590, 3508], +] + +[[area.polygons]] +vertices = [ + [2589, 3358], + [2589, 3508], +] + +[[area.polygons]] +vertices = [ + [2588, 3360], + [2588, 3508], +] + +[[area.polygons]] +vertices = [ + [2573, 3361], + [2576, 3361], + [2576, 3508], + [2573, 3508], +] + +[[area.polygons]] +vertices = [ + [2587, 3361], + [2587, 3508], +] + +[[area.polygons]] +vertices = [ + [2571, 3362], + [2572, 3362], + [2572, 3507], + [2571, 3507], +] + +[[area.polygons]] +vertices = [ + [2577, 3362], + [2578, 3362], + [2578, 3508], + [2577, 3508], +] + +[[area.polygons]] +vertices = [ + [2586, 3362], + [2586, 3508], +] + +[[area.polygons]] +vertices = [ + [2570, 3363], + [2570, 3506], +] + +[[area.polygons]] +vertices = [ + [2579, 3363], + [2585, 3363], + [2585, 3508], + [2579, 3508], +] + +[[area.polygons]] +vertices = [ + [2569, 3365], + [2569, 3506], +] + +[[area.polygons]] +vertices = [ + [2568, 3367], + [2568, 3506], +] + +[[area.polygons]] +vertices = [ + [2567, 3369], + [2567, 3506], +] + +[[area.polygons]] +vertices = [ + [2566, 3371], + [2566, 3506], +] + +[[area.polygons]] +vertices = [ + [2624, 3371], + [2624, 3398], +] + +[[area.polygons]] +vertices = [ + [2632, 3371], + [2634, 3371], +] + +[[area.polygons]] +vertices = [ + [2625, 3372], + [2633, 3372], +] + +[[area.polygons]] +vertices = [ + [2565, 3373], + [2565, 3506], +] + +[[area.polygons]] +vertices = [ + [2625, 3373], + [2632, 3373], +] + +[[area.polygons]] +vertices = [ + [2564, 3374], + [2564, 3506], +] + +[[area.polygons]] +vertices = [ + [2625, 3374], + [2631, 3374], + [2631, 3375], + [2625, 3375], +] + +[[area.polygons]] +vertices = [ + [2562, 3375], + [2563, 3375], + [2563, 3507], + [2562, 3507], +] + +[[area.polygons]] +vertices = [ + [2560, 3376], + [2561, 3376], + [2561, 3509], + [2560, 3509], +] + +[[area.polygons]] +vertices = [ + [2625, 3376], + [2627, 3376], + [2627, 3397], + [2625, 3397], +] + +[[area.polygons]] +vertices = [ + [2559, 3377], + [2559, 3511], +] + +[[area.polygons]] +vertices = [ + [2558, 3378], + [2558, 3511], +] + +[[area.polygons]] +vertices = [ + [2557, 3379], + [2557, 3511], +] + +[[area.polygons]] +vertices = [ + [2556, 3380], + [2556, 3511], +] + +[[area.polygons]] +vertices = [ + [2555, 3381], + [2555, 3511], +] + +[[area.polygons]] +vertices = [ + [2554, 3382], + [2554, 3511], +] + +[[area.polygons]] +vertices = [ + [2628, 3382], + [2629, 3382], + [2629, 3383], + [2628, 3383], +] + +[[area.polygons]] +vertices = [ + [2553, 3383], + [2553, 3511], +] + +[[area.polygons]] +vertices = [ + [2552, 3384], + [2552, 3511], +] + +[[area.polygons]] +vertices = [ + [2628, 3384], + [2628, 3397], +] + +[[area.polygons]] +vertices = [ + [2551, 3385], + [2551, 3511], +] + +[[area.polygons]] +vertices = [ + [2550, 3386], + [2550, 3511], +] + +[[area.polygons]] +vertices = [ + [2549, 3387], + [2549, 3511], +] + +[[area.polygons]] +vertices = [ + [2548, 3388], + [2548, 3511], +] + +[[area.polygons]] +vertices = [ + [2629, 3388], + [2629, 3396], +] + +[[area.polygons]] +vertices = [ + [2547, 3389], + [2547, 3511], +] + +[[area.polygons]] +vertices = [ + [2630, 3389], + [2630, 3394], +] + +[[area.polygons]] +vertices = [ + [2546, 3390], + [2546, 3511], +] + +[[area.polygons]] +vertices = [ + [2631, 3390], + [2631, 3393], +] + +[[area.polygons]] +vertices = [ + [2545, 3391], + [2545, 3511], +] + +[[area.polygons]] +vertices = [ + [2544, 3392], + [2544, 3511], +] + +[[area.polygons]] +vertices = [ + [2543, 3393], + [2543, 3511], +] + +[[area.polygons]] +vertices = [ + [2542, 3394], + [2542, 3511], +] + +[[area.polygons]] +vertices = [ + [2540, 3395], + [2541, 3395], + [2541, 3511], + [2540, 3511], +] + +[[area.polygons]] +vertices = [ + [2538, 3396], + [2539, 3396], + [2539, 3511], + [2538, 3511], +] + +[[area.polygons]] +vertices = [ + [2536, 3397], + [2537, 3397], + [2537, 3404], + [2536, 3404], +] + +[[area.polygons]] +vertices = [ + [2535, 3398], + [2535, 3403], +] + +[[area.polygons]] +vertices = [ + [2625, 3398], + [2626, 3398], +] + +[[area.polygons]] +vertices = [ + [2537, 3405], + [2537, 3511], +] + +[[area.polygons]] +vertices = [ + [2536, 3406], + [2536, 3512], +] + +[[area.polygons]] +vertices = [ + [2535, 3407], + [2535, 3514], +] + +[[area.polygons]] +vertices = [ + [2534, 3408], + [2534, 3516], +] + +[[area.polygons]] +vertices = [ + [2533, 3409], + [2533, 3521], +] + +[[area.polygons]] +vertices = [ + [2532, 3410], + [2532, 3523], +] + +[[area.polygons]] +vertices = [ + [2530, 3411], + [2531, 3411], + [2531, 3525], + [2530, 3525], +] + +[[area.polygons]] +vertices = [ + [2521, 3412], + [2529, 3412], + [2529, 3527], + [2521, 3527], +] + +[[area.polygons]] +vertices = [ + [2520, 3413], + [2520, 3528], +] + +[[area.polygons]] +vertices = [ + [2519, 3414], + [2519, 3527], +] + +[[area.polygons]] +vertices = [ + [2518, 3415], + [2518, 3527], +] + +[[area.polygons]] +vertices = [ + [2517, 3416], + [2517, 3527], +] + +[[area.polygons]] +vertices = [ + [2516, 3417], + [2516, 3527], +] + +[[area.polygons]] +vertices = [ + [2515, 3418], + [2515, 3526], +] + +[[area.polygons]] +vertices = [ + [2514, 3419], + [2514, 3526], +] + +[[area.polygons]] +vertices = [ + [2513, 3420], + [2513, 3526], +] + +[[area.polygons]] +vertices = [ + [2511, 3421], + [2512, 3421], + [2512, 3525], + [2511, 3525], +] + +[[area.polygons]] +vertices = [ + [2505, 3422], + [2510, 3422], + [2510, 3524], + [2505, 3524], +] + +[[area.polygons]] +vertices = [ + [2504, 3423], + [2504, 3524], +] + +[[area.polygons]] +vertices = [ + [2503, 3424], + [2503, 3523], +] + +[[area.polygons]] +vertices = [ + [2502, 3425], + [2502, 3523], +] + +[[area.polygons]] +vertices = [ + [2499, 3440], + [2501, 3440], + [2501, 3522], + [2499, 3522], +] + +[[area.polygons]] +vertices = [ + [2593, 3452], + [2621, 3452], +] + +[[area.polygons]] +vertices = [ + [2593, 3453], + [2620, 3453], +] + +[[area.polygons]] +vertices = [ + [2593, 3454], + [2619, 3454], +] + +[[area.polygons]] +vertices = [ + [2593, 3455], + [2618, 3455], +] + +[[area.polygons]] +vertices = [ + [2593, 3456], + [2617, 3456], +] + +[[area.polygons]] +vertices = [ + [2593, 3457], + [2616, 3457], +] + +[[area.polygons]] +vertices = [ + [2593, 3458], + [2615, 3458], +] + +[[area.polygons]] +vertices = [ + [2593, 3459], + [2614, 3459], +] + +[[area.polygons]] +vertices = [ + [2593, 3460], + [2613, 3460], +] + +[[area.polygons]] +vertices = [ + [2593, 3461], + [2612, 3461], +] + +[[area.polygons]] +vertices = [ + [2593, 3462], + [2611, 3462], +] + +[[area.polygons]] +vertices = [ + [2593, 3463], + [2610, 3463], +] + +[[area.polygons]] +vertices = [ + [2593, 3464], + [2609, 3464], +] + +[[area.polygons]] +vertices = [ + [2593, 3465], + [2608, 3465], +] + +[[area.polygons]] +vertices = [ + [2593, 3466], + [2607, 3466], +] + +[[area.polygons]] +vertices = [ + [2593, 3467], + [2606, 3467], +] + +[[area.polygons]] +vertices = [ + [2593, 3468], + [2605, 3468], +] + +[[area.polygons]] +vertices = [ + [2593, 3469], + [2604, 3469], +] + +[[area.polygons]] +vertices = [ + [2593, 3470], + [2603, 3470], +] + +[[area.polygons]] +vertices = [ + [2593, 3471], + [2602, 3471], +] + +[[area.polygons]] +vertices = [ + [2593, 3472], + [2601, 3472], +] + +[[area.polygons]] +vertices = [ + [2593, 3473], + [2600, 3473], +] + +[[area.polygons]] +vertices = [ + [2593, 3474], + [2599, 3474], + [2599, 3498], + [2593, 3498], +] + +[[area.polygons]] +vertices = [ + [2593, 3499], + [2598, 3499], + [2598, 3500], + [2593, 3500], +] + +[[area.polygons]] +vertices = [ + [2593, 3501], + [2597, 3501], + [2597, 3502], + [2593, 3502], +] + +[[area.polygons]] +vertices = [ + [2593, 3503], + [2596, 3503], +] + +[[area.polygons]] +vertices = [ + [2593, 3504], + [2595, 3504], +] + +[[area.polygons]] +vertices = [ + [2593, 3505], + [2594, 3505], +] + +[[area.polygons]] +vertices = [[2593, 3506]] + +[[area.polygons]] +vertices = [[2562, 3508]] + +[[area.polygons]] +vertices = [[2560, 3510]] + +[[area.polygons]] +vertices = [ + [2496, 3520], + [2498, 3520], + [2498, 3522], + [2496, 3522], +] + +[[area.polygons]] +vertices = [ + [2500, 3523], + [2501, 3523], +] + +[[area.polygons]] +vertices = [ + [2508, 3525], + [2510, 3525], +] + +[[area.polygons]] +vertices = [[2512, 3526]] + +[[area.polygons]] +vertices = [[2530, 3526]] + +[[area.polygons]] +vertices = [ + [2521, 3528], + [2528, 3528], +] + +[[area.polygons]] +vertices = [ + [2524, 3529], + [2527, 3529], ] diff --git a/.data/raw-cache/map/area/food_for_thought.toml b/.data/raw-cache/map/area/food_for_thought.toml new file mode 100644 index 000000000..3c0567a73 --- /dev/null +++ b/.data/raw-cache/map/area/food_for_thought.toml @@ -0,0 +1,16 @@ +# Food for Thought music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Food for Thought. +# Bounds x 1984-2047, z 5184-5247; 4096 tiles as 1 rectangles. + +[[area]] +name = "Food for Thought" +area_id = "area.food_for_thought" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1984, 5184], + [2047, 5184], + [2047, 5247], + [1984, 5247], +] diff --git a/.data/raw-cache/map/area/forever.toml b/.data/raw-cache/map/area/forever.toml new file mode 100644 index 000000000..366c53609 --- /dev/null +++ b/.data/raw-cache/map/area/forever.toml @@ -0,0 +1,16 @@ +# Forever music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Forever. +# Bounds x 3072-3135, z 9792-10047; 16384 tiles as 1 rectangles. + +[[area]] +name = "Forever" +area_id = "area.forever" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3072, 9792], + [3135, 9792], + [3135, 10047], + [3072, 10047], +] diff --git a/.data/raw-cache/map/area/forgettable_melody.toml b/.data/raw-cache/map/area/forgettable_melody.toml new file mode 100644 index 000000000..f257cab1e --- /dev/null +++ b/.data/raw-cache/map/area/forgettable_melody.toml @@ -0,0 +1,16 @@ +# Forgettable Melody music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Forgettable Melody (plus right_on_track from wiki polygons sharing this footprint). +# Bounds x 1856-1919, z 4928-4991; 4096 tiles as 1 rectangles. + +[[area]] +name = "Forgettable Melody" +area_id = "area.forgettable_melody" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 4928], + [1919, 4928], + [1919, 4991], + [1856, 4991], +] diff --git a/.data/raw-cache/map/area/fossilised.toml b/.data/raw-cache/map/area/fossilised.toml new file mode 100644 index 000000000..ecd700454 --- /dev/null +++ b/.data/raw-cache/map/area/fossilised.toml @@ -0,0 +1,16 @@ +# Fossilised music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Fossilised. +# Bounds x 3584-3647, z 10176-10303; 8192 tiles as 1 rectangles. + +[[area]] +name = "Fossilised" +area_id = "area.fossilised" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3584, 10176], + [3647, 10176], + [3647, 10303], + [3584, 10303], +] diff --git a/.data/raw-cache/map/area/fremennik_province.toml b/.data/raw-cache/map/area/fremennik_province.toml new file mode 100644 index 000000000..b103d7ba5 --- /dev/null +++ b/.data/raw-cache/map/area/fremennik_province.toml @@ -0,0 +1,997 @@ +# Fremennik Province music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Legend; Saga; Borderland. +# Bounds x 2595-2802, z 3576-3742; 16821 tiles as 153 rectangles. + +[[area]] +name = "Fremennik Province" +area_id = "area.fremennik_province" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2769, 3576], + [2802, 3576], + [2802, 3647], + [2769, 3647], +] + +[[area.polygons]] +vertices = [ + [2768, 3577], + [2768, 3651], +] + +[[area.polygons]] +vertices = [ + [2767, 3578], + [2767, 3652], +] + +[[area.polygons]] +vertices = [ + [2766, 3579], + [2766, 3653], +] + +[[area.polygons]] +vertices = [ + [2765, 3580], + [2765, 3654], +] + +[[area.polygons]] +vertices = [ + [2764, 3581], + [2764, 3655], +] + +[[area.polygons]] +vertices = [ + [2763, 3582], + [2763, 3656], +] + +[[area.polygons]] +vertices = [ + [2762, 3583], + [2762, 3657], +] + +[[area.polygons]] +vertices = [ + [2761, 3584], + [2761, 3658], +] + +[[area.polygons]] +vertices = [ + [2760, 3585], + [2760, 3658], +] + +[[area.polygons]] +vertices = [ + [2759, 3586], + [2759, 3671], +] + +[[area.polygons]] +vertices = [ + [2743, 3587], + [2745, 3587], + [2745, 3742], + [2743, 3742], +] + +[[area.polygons]] +vertices = [ + [2758, 3587], + [2758, 3672], +] + +[[area.polygons]] +vertices = [ + [2742, 3588], + [2742, 3742], +] + +[[area.polygons]] +vertices = [ + [2746, 3588], + [2748, 3588], + [2748, 3742], + [2746, 3742], +] + +[[area.polygons]] +vertices = [ + [2757, 3588], + [2757, 3673], +] + +[[area.polygons]] +vertices = [ + [2741, 3589], + [2741, 3742], +] + +[[area.polygons]] +vertices = [ + [2749, 3589], + [2750, 3589], + [2750, 3742], + [2749, 3742], +] + +[[area.polygons]] +vertices = [ + [2756, 3589], + [2756, 3674], +] + +[[area.polygons]] +vertices = [ + [2740, 3590], + [2740, 3742], +] + +[[area.polygons]] +vertices = [ + [2751, 3590], + [2755, 3590], + [2755, 3675], + [2751, 3675], +] + +[[area.polygons]] +vertices = [ + [2734, 3591], + [2739, 3591], + [2739, 3737], + [2734, 3737], +] + +[[area.polygons]] +vertices = [ + [2731, 3592], + [2733, 3592], + [2733, 3736], + [2731, 3736], +] + +[[area.polygons]] +vertices = [ + [2641, 3593], + [2646, 3593], + [2646, 3595], + [2641, 3595], +] + +[[area.polygons]] +vertices = [ + [2728, 3593], + [2730, 3593], + [2730, 3738], + [2728, 3738], +] + +[[area.polygons]] +vertices = [ + [2647, 3594], + [2648, 3594], + [2648, 3643], + [2647, 3643], +] + +[[area.polygons]] +vertices = [ + [2725, 3594], + [2727, 3594], + [2727, 3731], + [2725, 3731], +] + +[[area.polygons]] +vertices = [ + [2649, 3595], + [2650, 3595], + [2650, 3643], + [2649, 3643], +] + +[[area.polygons]] +vertices = [ + [2717, 3595], + [2724, 3595], + [2724, 3731], + [2717, 3731], +] + +[[area.polygons]] +vertices = [ + [2642, 3596], + [2646, 3596], + [2646, 3643], + [2642, 3643], +] + +[[area.polygons]] +vertices = [ + [2651, 3596], + [2655, 3596], + [2655, 3643], + [2651, 3643], +] + +[[area.polygons]] +vertices = [ + [2711, 3596], + [2716, 3596], + [2716, 3733], + [2711, 3733], +] + +[[area.polygons]] +vertices = [ + [2626, 3597], + [2626, 3646], +] + +[[area.polygons]] +vertices = [ + [2656, 3597], + [2657, 3597], + [2657, 3643], + [2656, 3643], +] + +[[area.polygons]] +vertices = [ + [2668, 3597], + [2670, 3597], + [2670, 3643], + [2668, 3643], +] + +[[area.polygons]] +vertices = [ + [2705, 3597], + [2710, 3597], + [2710, 3729], + [2705, 3729], +] + +[[area.polygons]] +vertices = [ + [2637, 3598], + [2637, 3644], +] + +[[area.polygons]] +vertices = [ + [2658, 3598], + [2659, 3598], + [2659, 3644], + [2658, 3644], +] + +[[area.polygons]] +vertices = [ + [2666, 3598], + [2667, 3598], + [2667, 3644], + [2666, 3644], +] + +[[area.polygons]] +vertices = [ + [2671, 3598], + [2672, 3598], + [2672, 3643], + [2671, 3643], +] + +[[area.polygons]] +vertices = [ + [2699, 3598], + [2704, 3598], + [2704, 3730], + [2699, 3730], +] + +[[area.polygons]] +vertices = [ + [2636, 3599], + [2636, 3644], +] + +[[area.polygons]] +vertices = [ + [2638, 3599], + [2638, 3644], +] + +[[area.polygons]] +vertices = [ + [2641, 3599], + [2641, 3644], +] + +[[area.polygons]] +vertices = [ + [2660, 3599], + [2661, 3599], + [2661, 3644], + [2660, 3644], +] + +[[area.polygons]] +vertices = [ + [2664, 3599], + [2665, 3599], + [2665, 3644], + [2664, 3644], +] + +[[area.polygons]] +vertices = [ + [2673, 3599], + [2674, 3599], + [2674, 3644], + [2673, 3644], +] + +[[area.polygons]] +vertices = [ + [2686, 3599], + [2698, 3599], + [2698, 3645], + [2686, 3645], +] + +[[area.polygons]] +vertices = [ + [2631, 3600], + [2631, 3645], +] + +[[area.polygons]] +vertices = [ + [2639, 3600], + [2640, 3600], + [2640, 3644], + [2639, 3644], +] + +[[area.polygons]] +vertices = [ + [2662, 3600], + [2663, 3600], + [2663, 3644], + [2662, 3644], +] + +[[area.polygons]] +vertices = [ + [2675, 3600], + [2676, 3600], + [2676, 3644], + [2675, 3644], +] + +[[area.polygons]] +vertices = [ + [2685, 3600], + [2685, 3644], +] + +[[area.polygons]] +vertices = [ + [2618, 3601], + [2619, 3601], + [2619, 3650], + [2618, 3650], +] + +[[area.polygons]] +vertices = [ + [2621, 3601], + [2622, 3601], + [2622, 3648], + [2621, 3648], +] + +[[area.polygons]] +vertices = [ + [2630, 3601], + [2630, 3645], +] + +[[area.polygons]] +vertices = [ + [2632, 3601], + [2633, 3601], + [2633, 3645], + [2632, 3645], +] + +[[area.polygons]] +vertices = [ + [2635, 3601], + [2635, 3645], +] + +[[area.polygons]] +vertices = [ + [2677, 3601], + [2678, 3601], + [2678, 3644], + [2677, 3644], +] + +[[area.polygons]] +vertices = [ + [2684, 3601], + [2684, 3644], +] + +[[area.polygons]] +vertices = [ + [2616, 3602], + [2617, 3602], + [2617, 3651], + [2616, 3651], +] + +[[area.polygons]] +vertices = [ + [2620, 3602], + [2620, 3649], +] + +[[area.polygons]] +vertices = [ + [2623, 3602], + [2623, 3647], +] + +[[area.polygons]] +vertices = [ + [2629, 3602], + [2629, 3645], +] + +[[area.polygons]] +vertices = [ + [2634, 3602], + [2634, 3645], +] + +[[area.polygons]] +vertices = [ + [2679, 3602], + [2679, 3644], +] + +[[area.polygons]] +vertices = [ + [2683, 3602], + [2683, 3644], +] + +[[area.polygons]] +vertices = [ + [2624, 3603], + [2624, 3647], +] + +[[area.polygons]] +vertices = [ + [2628, 3603], + [2628, 3645], +] + +[[area.polygons]] +vertices = [ + [2680, 3603], + [2680, 3644], +] + +[[area.polygons]] +vertices = [ + [2682, 3603], + [2682, 3644], +] + +[[area.polygons]] +vertices = [ + [2605, 3604], + [2606, 3604], + [2606, 3654], + [2605, 3654], +] + +[[area.polygons]] +vertices = [ + [2615, 3604], + [2615, 3652], +] + +[[area.polygons]] +vertices = [ + [2625, 3604], + [2625, 3646], +] + +[[area.polygons]] +vertices = [ + [2627, 3604], + [2627, 3645], +] + +[[area.polygons]] +vertices = [ + [2681, 3604], + [2681, 3644], +] + +[[area.polygons]] +vertices = [ + [2604, 3605], + [2604, 3654], +] + +[[area.polygons]] +vertices = [ + [2607, 3605], + [2607, 3606], +] + +[[area.polygons]] +vertices = [ + [2614, 3605], + [2614, 3652], +] + +[[area.polygons]] +vertices = [ + [2601, 3606], + [2603, 3606], + [2603, 3611], + [2601, 3611], +] + +[[area.polygons]] +vertices = [ + [2612, 3606], + [2613, 3606], + [2613, 3652], + [2612, 3652], +] + +[[area.polygons]] +vertices = [ + [2599, 3607], + [2600, 3607], + [2600, 3612], + [2599, 3612], +] + +[[area.polygons]] +vertices = [ + [2610, 3607], + [2611, 3607], + [2611, 3652], + [2610, 3652], +] + +[[area.polygons]] +vertices = [ + [2598, 3608], + [2598, 3612], +] + +[[area.polygons]] +vertices = [ + [2608, 3608], + [2609, 3608], + [2609, 3652], + [2608, 3652], +] + +[[area.polygons]] +vertices = [ + [2607, 3609], + [2607, 3653], +] + +[[area.polygons]] +vertices = [[2597, 3611]] + +[[area.polygons]] +vertices = [ + [2603, 3612], + [2603, 3654], +] + +[[area.polygons]] +vertices = [ + [2602, 3613], + [2602, 3654], +] + +[[area.polygons]] +vertices = [ + [2601, 3614], + [2601, 3653], +] + +[[area.polygons]] +vertices = [ + [2600, 3615], + [2600, 3651], +] + +[[area.polygons]] +vertices = [ + [2599, 3617], + [2599, 3649], +] + +[[area.polygons]] +vertices = [ + [2598, 3618], + [2598, 3638], +] + +[[area.polygons]] +vertices = [ + [2597, 3620], + [2597, 3637], +] + +[[area.polygons]] +vertices = [ + [2596, 3622], + [2596, 3631], +] + +[[area.polygons]] +vertices = [ + [2595, 3624], + [2595, 3626], +] + +[[area.polygons]] +vertices = [ + [2642, 3644], + [2645, 3644], +] + +[[area.polygons]] +vertices = [ + [2650, 3644], + [2652, 3644], +] + +[[area.polygons]] +vertices = [[2657, 3644]] + +[[area.polygons]] +vertices = [ + [2668, 3644], + [2669, 3644], +] + +[[area.polygons]] +vertices = [ + [2688, 3646], + [2698, 3646], +] + +[[area.polygons]] +vertices = [ + [2690, 3647], + [2698, 3647], +] + +[[area.polygons]] +vertices = [ + [2692, 3648], + [2698, 3648], +] + +[[area.polygons]] +vertices = [ + [2769, 3648], + [2771, 3648], +] + +[[area.polygons]] +vertices = [ + [2693, 3649], + [2698, 3649], + [2698, 3650], + [2693, 3650], +] + +[[area.polygons]] +vertices = [ + [2769, 3649], + [2770, 3649], +] + +[[area.polygons]] +vertices = [[2769, 3650]] + +[[area.polygons]] +vertices = [ + [2694, 3651], + [2698, 3651], + [2698, 3728], + [2694, 3728], +] + +[[area.polygons]] +vertices = [[2608, 3653]] + +[[area.polygons]] +vertices = [ + [2693, 3654], + [2693, 3684], +] + +[[area.polygons]] +vertices = [ + [2692, 3656], + [2692, 3682], +] + +[[area.polygons]] +vertices = [ + [2691, 3658], + [2691, 3665], +] + +[[area.polygons]] +vertices = [ + [2690, 3660], + [2690, 3663], +] + +[[area.polygons]] +vertices = [ + [2760, 3662], + [2760, 3671], +] + +[[area.polygons]] +vertices = [ + [2761, 3663], + [2761, 3671], +] + +[[area.polygons]] +vertices = [ + [2762, 3664], + [2767, 3664], + [2767, 3669], + [2762, 3669], +] + +[[area.polygons]] +vertices = [ + [2768, 3665], + [2768, 3668], +] + +[[area.polygons]] +vertices = [ + [2762, 3670], + [2766, 3670], +] + +[[area.polygons]] +vertices = [ + [2691, 3671], + [2691, 3680], +] + +[[area.polygons]] +vertices = [ + [2762, 3671], + [2765, 3671], +] + +[[area.polygons]] +vertices = [ + [2690, 3673], + [2690, 3678], +] + +[[area.polygons]] +vertices = [ + [2751, 3676], + [2754, 3676], +] + +[[area.polygons]] +vertices = [ + [2751, 3677], + [2753, 3677], +] + +[[area.polygons]] +vertices = [ + [2751, 3678], + [2752, 3678], +] + +[[area.polygons]] +vertices = [ + [2751, 3679], + [2751, 3742], +] + +[[area.polygons]] +vertices = [ + [2693, 3691], + [2693, 3702], +] + +[[area.polygons]] +vertices = [ + [2692, 3693], + [2692, 3700], +] + +[[area.polygons]] +vertices = [ + [2691, 3695], + [2691, 3698], +] + +[[area.polygons]] +vertices = [ + [2693, 3707], + [2693, 3727], +] + +[[area.polygons]] +vertices = [ + [2692, 3709], + [2692, 3727], +] + +[[area.polygons]] +vertices = [ + [2691, 3716], + [2691, 3719], +] + +[[area.polygons]] +vertices = [ + [2697, 3729], + [2698, 3729], +] + +[[area.polygons]] +vertices = [[2698, 3730]] + +[[area.polygons]] +vertices = [ + [2705, 3730], + [2707, 3730], +] + +[[area.polygons]] +vertices = [ + [2709, 3730], + [2710, 3730], +] + +[[area.polygons]] +vertices = [ + [2699, 3731], + [2702, 3731], +] + +[[area.polygons]] +vertices = [ + [2706, 3731], + [2707, 3731], + [2707, 3736], + [2706, 3736], +] + +[[area.polygons]] +vertices = [[2710, 3731]] + +[[area.polygons]] +vertices = [ + [2700, 3732], + [2701, 3732], +] + +[[area.polygons]] +vertices = [ + [2717, 3732], + [2721, 3732], +] + +[[area.polygons]] +vertices = [ + [2726, 3732], + [2727, 3732], + [2727, 3733], + [2726, 3733], +] + +[[area.polygons]] +vertices = [ + [2717, 3733], + [2719, 3733], +] + +[[area.polygons]] +vertices = [ + [2713, 3734], + [2715, 3734], +] + +[[area.polygons]] +vertices = [ + [2717, 3734], + [2718, 3734], +] + +[[area.polygons]] +vertices = [ + [2727, 3734], + [2727, 3735], +] + +[[area.polygons]] +vertices = [ + [2731, 3737], + [2731, 3742], +] + +[[area.polygons]] +vertices = [ + [2736, 3738], + [2739, 3738], +] + +[[area.polygons]] +vertices = [ + [2729, 3739], + [2730, 3739], +] + +[[area.polygons]] +vertices = [ + [2737, 3739], + [2739, 3739], +] + +[[area.polygons]] +vertices = [ + [2730, 3740], + [2730, 3742], +] + +[[area.polygons]] +vertices = [ + [2738, 3740], + [2739, 3740], +] + +[[area.polygons]] +vertices = [ + [2732, 3741], + [2732, 3742], +] + +[[area.polygons]] +vertices = [[2739, 3741]] diff --git a/.data/raw-cache/map/area/frogland.toml b/.data/raw-cache/map/area/frogland.toml new file mode 100644 index 000000000..7dcf235cd --- /dev/null +++ b/.data/raw-cache/map/area/frogland.toml @@ -0,0 +1,16 @@ +# Frogland music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Frogland. +# Bounds x 2432-2495, z 4736-4799; 4096 tiles as 1 rectangles. + +[[area]] +name = "Frogland" +area_id = "area.frogland" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2432, 4736], + [2495, 4736], + [2495, 4799], + [2432, 4799], +] diff --git a/.data/raw-cache/map/area/frostbite.toml b/.data/raw-cache/map/area/frostbite.toml new file mode 100644 index 000000000..fb1c063aa --- /dev/null +++ b/.data/raw-cache/map/area/frostbite.toml @@ -0,0 +1,16 @@ +# Frostbite music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Frostbite. +# Bounds x 2816-2879, z 3776-3839; 4096 tiles as 1 rectangles. + +[[area]] +name = "Frostbite" +area_id = "area.frostbite" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 3776], + [2879, 3776], + [2879, 3839], + [2816, 3839], +] diff --git a/.data/raw-cache/map/area/fruits_de_mer.toml b/.data/raw-cache/map/area/fruits_de_mer.toml new file mode 100644 index 000000000..085f376c2 --- /dev/null +++ b/.data/raw-cache/map/area/fruits_de_mer.toml @@ -0,0 +1,311 @@ +# Fruits de Mer music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Fruits de Mer. +# Bounds x 2323-2801, z 2259-3325; 979 tiles as 47 rectangles. + +[[area]] +name = "Fruits de Mer" +area_id = "area.fruits_de_mer" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2334, 2259], + [2337, 2259], + [2337, 2281], + [2334, 2281], +] + +[[area.polygons]] +vertices = [ + [2331, 2260], + [2333, 2260], + [2333, 2281], + [2331, 2281], +] + +[[area.polygons]] +vertices = [ + [2338, 2260], + [2338, 2281], +] + +[[area.polygons]] +vertices = [ + [2330, 2261], + [2330, 2280], +] + +[[area.polygons]] +vertices = [ + [2339, 2261], + [2339, 2281], +] + +[[area.polygons]] +vertices = [ + [2329, 2262], + [2329, 2279], +] + +[[area.polygons]] +vertices = [ + [2340, 2262], + [2340, 2280], +] + +[[area.polygons]] +vertices = [ + [2328, 2263], + [2328, 2279], +] + +[[area.polygons]] +vertices = [ + [2341, 2263], + [2342, 2263], + [2342, 2278], + [2341, 2278], +] + +[[area.polygons]] +vertices = [ + [2343, 2264], + [2343, 2276], +] + +[[area.polygons]] +vertices = [ + [2327, 2265], + [2327, 2278], +] + +[[area.polygons]] +vertices = [ + [2326, 2266], + [2326, 2277], +] + +[[area.polygons]] +vertices = [ + [2344, 2266], + [2344, 2273], +] + +[[area.polygons]] +vertices = [ + [2325, 2267], + [2325, 2276], +] + +[[area.polygons]] +vertices = [ + [2324, 2268], + [2324, 2275], +] + +[[area.polygons]] +vertices = [ + [2345, 2268], + [2345, 2271], +] + +[[area.polygons]] +vertices = [ + [2323, 2271], + [2323, 2273], +] + +[[area.polygons]] +vertices = [[2341, 2279]] + +[[area.polygons]] +vertices = [ + [2335, 2282], + [2336, 2282], +] + +[[area.polygons]] +vertices = [ + [2761, 3272], + [2767, 3272], + [2767, 3279], + [2761, 3279], +] + +[[area.polygons]] +vertices = [ + [2768, 3273], + [2783, 3273], + [2783, 3279], + [2768, 3279], +] + +[[area.polygons]] +vertices = [ + [2784, 3274], + [2793, 3274], + [2793, 3275], + [2784, 3275], +] + +[[area.polygons]] +vertices = [ + [2794, 3275], + [2794, 3281], +] + +[[area.polygons]] +vertices = [ + [2784, 3276], + [2787, 3276], + [2787, 3278], + [2784, 3278], +] + +[[area.polygons]] +vertices = [ + [2792, 3276], + [2793, 3276], + [2793, 3282], + [2792, 3282], +] + +[[area.polygons]] +vertices = [ + [2784, 3279], + [2786, 3279], +] + +[[area.polygons]] +vertices = [ + [2762, 3280], + [2777, 3280], + [2777, 3290], + [2762, 3290], +] + +[[area.polygons]] +vertices = [ + [2778, 3284], + [2780, 3284], + [2780, 3291], + [2778, 3291], +] + +[[area.polygons]] +vertices = [ + [2781, 3285], + [2786, 3285], + [2786, 3288], + [2781, 3288], +] + +[[area.polygons]] +vertices = [ + [2761, 3286], + [2761, 3289], +] + +[[area.polygons]] +vertices = [ + [2787, 3286], + [2787, 3287], +] + +[[area.polygons]] +vertices = [ + [2781, 3289], + [2784, 3289], +] + +[[area.polygons]] +vertices = [ + [2781, 3290], + [2783, 3290], +] + +[[area.polygons]] +vertices = [ + [2763, 3291], + [2777, 3291], +] + +[[area.polygons]] +vertices = [ + [2781, 3291], + [2782, 3291], +] + +[[area.polygons]] +vertices = [ + [2764, 3292], + [2774, 3292], +] + +[[area.polygons]] +vertices = [ + [2795, 3313], + [2796, 3313], + [2796, 3323], + [2795, 3323], +] + +[[area.polygons]] +vertices = [ + [2793, 3314], + [2794, 3314], + [2794, 3325], + [2793, 3325], +] + +[[area.polygons]] +vertices = [ + [2797, 3314], + [2797, 3323], +] + +[[area.polygons]] +vertices = [ + [2790, 3317], + [2791, 3317], + [2791, 3324], + [2790, 3324], +] + +[[area.polygons]] +vertices = [ + [2789, 3318], + [2789, 3322], +] + +[[area.polygons]] +vertices = [ + [2792, 3318], + [2792, 3325], +] + +[[area.polygons]] +vertices = [ + [2798, 3318], + [2801, 3318], + [2801, 3321], + [2798, 3321], +] + +[[area.polygons]] +vertices = [ + [2788, 3319], + [2788, 3320], +] + +[[area.polygons]] +vertices = [ + [2798, 3322], + [2800, 3322], +] + +[[area.polygons]] +vertices = [[2795, 3324]] + +[[area.polygons]] +vertices = [[2791, 3325]] diff --git a/.data/raw-cache/map/area/gaol.toml b/.data/raw-cache/map/area/gaol.toml new file mode 100644 index 000000000..075701368 --- /dev/null +++ b/.data/raw-cache/map/area/gaol.toml @@ -0,0 +1,146 @@ +# Gaol music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Gaol. +# Bounds x 2496-2559, z 3008-9471; 7464 tiles as 22 rectangles. + +[[area]] +name = "Gaol" +area_id = "area.gaol" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2496, 3008], + [2559, 3008], + [2559, 3050], + [2496, 3050], +] + +[[area.polygons]] +vertices = [ + [2496, 3051], + [2537, 3051], +] + +[[area.polygons]] +vertices = [ + [2543, 3051], + [2559, 3051], +] + +[[area.polygons]] +vertices = [ + [2496, 3052], + [2536, 3052], +] + +[[area.polygons]] +vertices = [ + [2544, 3052], + [2559, 3052], +] + +[[area.polygons]] +vertices = [ + [2496, 3053], + [2535, 3053], +] + +[[area.polygons]] +vertices = [ + [2545, 3053], + [2559, 3053], +] + +[[area.polygons]] +vertices = [ + [2496, 3054], + [2534, 3054], +] + +[[area.polygons]] +vertices = [ + [2547, 3054], + [2559, 3054], +] + +[[area.polygons]] +vertices = [ + [2496, 3055], + [2533, 3055], +] + +[[area.polygons]] +vertices = [ + [2548, 3055], + [2559, 3055], +] + +[[area.polygons]] +vertices = [ + [2496, 3056], + [2532, 3056], +] + +[[area.polygons]] +vertices = [ + [2551, 3056], + [2555, 3056], +] + +[[area.polygons]] +vertices = [ + [2496, 3057], + [2531, 3057], +] + +[[area.polygons]] +vertices = [ + [2552, 3057], + [2554, 3057], +] + +[[area.polygons]] +vertices = [ + [2496, 3058], + [2530, 3058], + [2530, 3060], + [2496, 3060], +] + +[[area.polygons]] +vertices = [ + [2496, 3061], + [2529, 3061], +] + +[[area.polygons]] +vertices = [ + [2496, 3062], + [2528, 3062], +] + +[[area.polygons]] +vertices = [ + [2496, 3063], + [2527, 3063], +] + +[[area.polygons]] +vertices = [ + [2496, 3064], + [2525, 3064], +] + +[[area.polygons]] +vertices = [ + [2496, 3065], + [2523, 3065], +] + +[[area.polygons]] +vertices = [ + [2496, 9408], + [2559, 9408], + [2559, 9471], + [2496, 9471], +] diff --git a/.data/raw-cache/map/area/garden_of_autumn.toml b/.data/raw-cache/map/area/garden_of_autumn.toml new file mode 100644 index 000000000..6c5d61823 --- /dev/null +++ b/.data/raw-cache/map/area/garden_of_autumn.toml @@ -0,0 +1,16 @@ +# Garden of Autumn music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Garden of Autumn. +# Bounds x 2897-2918, z 5447-5462; 352 tiles as 1 rectangles. + +[[area]] +name = "Garden of Autumn" +area_id = "area.garden_of_autumn" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2897, 5447], + [2918, 5447], + [2918, 5462], + [2897, 5462], +] diff --git a/.data/raw-cache/map/area/garden_of_spring.toml b/.data/raw-cache/map/area/garden_of_spring.toml new file mode 100644 index 000000000..7825678b7 --- /dev/null +++ b/.data/raw-cache/map/area/garden_of_spring.toml @@ -0,0 +1,16 @@ +# Garden of Spring music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Garden of Spring. +# Bounds x 2921-2936, z 5457-5478; 352 tiles as 1 rectangles. + +[[area]] +name = "Garden of Spring" +area_id = "area.garden_of_spring" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2921, 5457], + [2936, 5457], + [2936, 5478], + [2921, 5478], +] diff --git a/.data/raw-cache/map/area/garden_of_summer.toml b/.data/raw-cache/map/area/garden_of_summer.toml new file mode 100644 index 000000000..73d4e9ac3 --- /dev/null +++ b/.data/raw-cache/map/area/garden_of_summer.toml @@ -0,0 +1,16 @@ +# Garden of Summer music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Garden of Summer. +# Bounds x 2905-2926, z 5481-5496; 352 tiles as 1 rectangles. + +[[area]] +name = "Garden of Summer" +area_id = "area.garden_of_summer" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2905, 5481], + [2926, 5481], + [2926, 5496], + [2905, 5496], +] diff --git a/.data/raw-cache/map/area/garden_of_winter.toml b/.data/raw-cache/map/area/garden_of_winter.toml new file mode 100644 index 000000000..7ce5a90e5 --- /dev/null +++ b/.data/raw-cache/map/area/garden_of_winter.toml @@ -0,0 +1,16 @@ +# Garden of Winter music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Garden of Winter. +# Bounds x 2887-2902, z 5465-5486; 352 tiles as 1 rectangles. + +[[area]] +name = "Garden of Winter" +area_id = "area.garden_of_winter" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2887, 5465], + [2902, 5465], + [2902, 5486], + [2887, 5486], +] diff --git a/.data/raw-cache/map/area/getting_down_to_business.toml b/.data/raw-cache/map/area/getting_down_to_business.toml new file mode 100644 index 000000000..f0a383dab --- /dev/null +++ b/.data/raw-cache/map/area/getting_down_to_business.toml @@ -0,0 +1,895 @@ +# Getting Down to Business music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Getting Down to Business. +# Bounds x 1184-1361, z 3690-3795; 10391 tiles as 140 rectangles. + +[[area]] +name = "Getting Down to Business" +area_id = "area.getting_down_to_business" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1280, 3690], + [1300, 3690], + [1300, 3754], + [1280, 3754], +] + +[[area.polygons]] +vertices = [ + [1301, 3691], + [1301, 3769], +] + +[[area.polygons]] +vertices = [ + [1302, 3692], + [1302, 3768], +] + +[[area.polygons]] +vertices = [ + [1303, 3693], + [1303, 3767], +] + +[[area.polygons]] +vertices = [ + [1304, 3694], + [1304, 3767], +] + +[[area.polygons]] +vertices = [ + [1305, 3695], + [1319, 3695], + [1319, 3767], + [1305, 3767], +] + +[[area.polygons]] +vertices = [ + [1320, 3696], + [1320, 3771], +] + +[[area.polygons]] +vertices = [ + [1321, 3697], + [1321, 3771], +] + +[[area.polygons]] +vertices = [ + [1322, 3698], + [1324, 3698], + [1324, 3770], + [1322, 3770], +] + +[[area.polygons]] +vertices = [ + [1325, 3699], + [1325, 3769], +] + +[[area.polygons]] +vertices = [ + [1326, 3700], + [1326, 3768], +] + +[[area.polygons]] +vertices = [ + [1327, 3701], + [1327, 3767], +] + +[[area.polygons]] +vertices = [ + [1328, 3702], + [1329, 3702], + [1329, 3765], + [1328, 3765], +] + +[[area.polygons]] +vertices = [ + [1330, 3705], + [1335, 3705], + [1335, 3759], + [1330, 3759], +] + +[[area.polygons]] +vertices = [ + [1336, 3706], + [1336, 3758], +] + +[[area.polygons]] +vertices = [ + [1337, 3707], + [1338, 3707], + [1338, 3756], + [1337, 3756], +] + +[[area.polygons]] +vertices = [ + [1339, 3709], + [1339, 3756], +] + +[[area.polygons]] +vertices = [ + [1340, 3710], + [1340, 3756], +] + +[[area.polygons]] +vertices = [ + [1184, 3712], + [1279, 3712], + [1279, 3719], + [1184, 3719], +] + +[[area.polygons]] +vertices = [ + [1341, 3714], + [1341, 3756], +] + +[[area.polygons]] +vertices = [ + [1342, 3715], + [1342, 3756], +] + +[[area.polygons]] +vertices = [ + [1343, 3716], + [1345, 3716], + [1345, 3756], + [1343, 3756], +] + +[[area.polygons]] +vertices = [ + [1346, 3718], + [1348, 3718], + [1348, 3756], + [1346, 3756], +] + +[[area.polygons]] +vertices = [ + [1184, 3720], + [1227, 3720], +] + +[[area.polygons]] +vertices = [ + [1238, 3720], + [1259, 3720], +] + +[[area.polygons]] +vertices = [ + [1270, 3720], + [1279, 3720], +] + +[[area.polygons]] +vertices = [ + [1184, 3721], + [1226, 3721], +] + +[[area.polygons]] +vertices = [ + [1239, 3721], + [1258, 3721], +] + +[[area.polygons]] +vertices = [ + [1271, 3721], + [1279, 3721], +] + +[[area.polygons]] +vertices = [ + [1184, 3722], + [1225, 3722], +] + +[[area.polygons]] +vertices = [ + [1240, 3722], + [1257, 3722], +] + +[[area.polygons]] +vertices = [ + [1272, 3722], + [1279, 3722], +] + +[[area.polygons]] +vertices = [ + [1184, 3723], + [1224, 3723], +] + +[[area.polygons]] +vertices = [ + [1241, 3723], + [1244, 3723], +] + +[[area.polygons]] +vertices = [ + [1253, 3723], + [1256, 3723], +] + +[[area.polygons]] +vertices = [ + [1273, 3723], + [1279, 3723], +] + +[[area.polygons]] +vertices = [ + [1184, 3724], + [1223, 3724], +] + +[[area.polygons]] +vertices = [ + [1242, 3724], + [1244, 3724], +] + +[[area.polygons]] +vertices = [ + [1253, 3724], + [1255, 3724], +] + +[[area.polygons]] +vertices = [ + [1274, 3724], + [1279, 3724], +] + +[[area.polygons]] +vertices = [ + [1349, 3724], + [1353, 3724], + [1353, 3756], + [1349, 3756], +] + +[[area.polygons]] +vertices = [ + [1184, 3725], + [1222, 3725], + [1222, 3746], + [1184, 3746], +] + +[[area.polygons]] +vertices = [ + [1243, 3725], + [1244, 3725], + [1244, 3727], + [1243, 3727], +] + +[[area.polygons]] +vertices = [ + [1253, 3725], + [1254, 3725], + [1254, 3727], + [1253, 3727], +] + +[[area.polygons]] +vertices = [ + [1275, 3725], + [1279, 3725], + [1279, 3754], + [1275, 3754], +] + +[[area.polygons]] +vertices = [ + [1354, 3725], + [1357, 3725], + [1357, 3744], + [1354, 3744], +] + +[[area.polygons]] +vertices = [ + [1358, 3727], + [1358, 3744], +] + +[[area.polygons]] +vertices = [ + [1359, 3730], + [1359, 3744], +] + +[[area.polygons]] +vertices = [ + [1243, 3732], + [1244, 3732], + [1244, 3737], + [1243, 3737], +] + +[[area.polygons]] +vertices = [ + [1253, 3732], + [1254, 3732], + [1254, 3737], + [1253, 3737], +] + +[[area.polygons]] +vertices = [ + [1360, 3734], + [1360, 3741], +] + +[[area.polygons]] +vertices = [ + [1242, 3735], + [1242, 3737], +] + +[[area.polygons]] +vertices = [ + [1274, 3735], + [1274, 3754], +] + +[[area.polygons]] +vertices = [ + [1361, 3735], + [1361, 3738], +] + +[[area.polygons]] +vertices = [ + [1223, 3736], + [1223, 3746], +] + +[[area.polygons]] +vertices = [ + [1241, 3736], + [1241, 3745], +] + +[[area.polygons]] +vertices = [ + [1255, 3736], + [1255, 3737], +] + +[[area.polygons]] +vertices = [ + [1273, 3736], + [1273, 3754], +] + +[[area.polygons]] +vertices = [ + [1224, 3737], + [1224, 3746], +] + +[[area.polygons]] +vertices = [ + [1240, 3737], + [1240, 3745], +] + +[[area.polygons]] +vertices = [ + [1256, 3737], + [1256, 3746], +] + +[[area.polygons]] +vertices = [ + [1272, 3737], + [1272, 3755], +] + +[[area.polygons]] +vertices = [ + [1225, 3738], + [1225, 3746], +] + +[[area.polygons]] +vertices = [ + [1239, 3738], + [1239, 3746], +] + +[[area.polygons]] +vertices = [ + [1257, 3738], + [1257, 3747], +] + +[[area.polygons]] +vertices = [ + [1271, 3738], + [1271, 3756], +] + +[[area.polygons]] +vertices = [ + [1226, 3739], + [1226, 3743], +] + +[[area.polygons]] +vertices = [ + [1238, 3739], + [1238, 3748], +] + +[[area.polygons]] +vertices = [ + [1258, 3739], + [1258, 3748], +] + +[[area.polygons]] +vertices = [ + [1270, 3739], + [1270, 3757], +] + +[[area.polygons]] +vertices = [ + [1227, 3740], + [1237, 3740], + [1237, 3743], + [1227, 3743], +] + +[[area.polygons]] +vertices = [ + [1259, 3740], + [1262, 3740], + [1262, 3743], + [1259, 3743], +] + +[[area.polygons]] +vertices = [ + [1267, 3740], + [1269, 3740], + [1269, 3743], + [1267, 3743], +] + +[[area.polygons]] +vertices = [ + [1232, 3744], + [1237, 3744], + [1237, 3746], + [1232, 3746], +] + +[[area.polygons]] +vertices = [ + [1242, 3744], + [1246, 3744], + [1246, 3745], + [1242, 3745], +] + +[[area.polygons]] +vertices = [ + [1251, 3744], + [1255, 3744], + [1255, 3745], + [1251, 3745], +] + +[[area.polygons]] +vertices = [ + [1259, 3744], + [1261, 3744], +] + +[[area.polygons]] +vertices = [ + [1268, 3744], + [1269, 3744], +] + +[[area.polygons]] +vertices = [ + [1259, 3745], + [1260, 3745], + [1260, 3774], + [1259, 3774], +] + +[[area.polygons]] +vertices = [ + [1269, 3745], + [1269, 3758], +] + +[[area.polygons]] +vertices = [ + [1354, 3745], + [1356, 3745], + [1356, 3748], + [1354, 3748], +] + +[[area.polygons]] +vertices = [ + [1184, 3747], + [1221, 3747], +] + +[[area.polygons]] +vertices = [ + [1236, 3747], + [1237, 3747], +] + +[[area.polygons]] +vertices = [ + [1184, 3748], + [1220, 3748], +] + +[[area.polygons]] +vertices = [[1237, 3748]] + +[[area.polygons]] +vertices = [ + [1184, 3749], + [1219, 3749], + [1219, 3795], + [1184, 3795], +] + +[[area.polygons]] +vertices = [ + [1354, 3749], + [1355, 3749], +] + +[[area.polygons]] +vertices = [ + [1354, 3750], + [1354, 3756], +] + +[[area.polygons]] +vertices = [ + [1268, 3751], + [1268, 3759], +] + +[[area.polygons]] +vertices = [ + [1261, 3752], + [1267, 3752], + [1267, 3760], + [1261, 3760], +] + +[[area.polygons]] +vertices = [ + [1355, 3752], + [1355, 3756], +] + +[[area.polygons]] +vertices = [ + [1289, 3755], + [1300, 3755], +] + +[[area.polygons]] +vertices = [ + [1290, 3756], + [1300, 3756], +] + +[[area.polygons]] +vertices = [ + [1291, 3757], + [1300, 3757], +] + +[[area.polygons]] +vertices = [[1337, 3757]] + +[[area.polygons]] +vertices = [ + [1292, 3758], + [1300, 3758], + [1300, 3770], + [1292, 3770], +] + +[[area.polygons]] +vertices = [ + [1330, 3760], + [1334, 3760], +] + +[[area.polygons]] +vertices = [ + [1220, 3761], + [1220, 3795], +] + +[[area.polygons]] +vertices = [ + [1237, 3761], + [1238, 3761], + [1238, 3795], + [1237, 3795], +] + +[[area.polygons]] +vertices = [ + [1258, 3761], + [1258, 3776], +] + +[[area.polygons]] +vertices = [ + [1261, 3761], + [1266, 3761], +] + +[[area.polygons]] +vertices = [ + [1330, 3761], + [1333, 3761], +] + +[[area.polygons]] +vertices = [ + [1221, 3762], + [1221, 3795], +] + +[[area.polygons]] +vertices = [ + [1236, 3762], + [1236, 3795], +] + +[[area.polygons]] +vertices = [ + [1239, 3762], + [1239, 3795], +] + +[[area.polygons]] +vertices = [ + [1257, 3762], + [1257, 3777], +] + +[[area.polygons]] +vertices = [ + [1261, 3762], + [1265, 3762], + [1265, 3771], + [1261, 3771], +] + +[[area.polygons]] +vertices = [ + [1330, 3762], + [1332, 3762], +] + +[[area.polygons]] +vertices = [ + [1222, 3763], + [1225, 3763], + [1225, 3795], + [1222, 3795], +] + +[[area.polygons]] +vertices = [ + [1232, 3763], + [1235, 3763], + [1235, 3795], + [1232, 3795], +] + +[[area.polygons]] +vertices = [ + [1240, 3763], + [1240, 3794], +] + +[[area.polygons]] +vertices = [ + [1256, 3763], + [1256, 3778], +] + +[[area.polygons]] +vertices = [ + [1330, 3763], + [1331, 3763], +] + +[[area.polygons]] +vertices = [ + [1241, 3764], + [1255, 3764], + [1255, 3779], + [1241, 3779], +] + +[[area.polygons]] +vertices = [[1330, 3764]] + +[[area.polygons]] +vertices = [ + [1226, 3766], + [1231, 3766], + [1231, 3795], + [1226, 3795], +] + +[[area.polygons]] +vertices = [[1328, 3766]] + +[[area.polygons]] +vertices = [ + [1314, 3768], + [1319, 3768], +] + +[[area.polygons]] +vertices = [ + [1315, 3769], + [1319, 3769], +] + +[[area.polygons]] +vertices = [ + [1316, 3770], + [1319, 3770], +] + +[[area.polygons]] +vertices = [ + [1292, 3771], + [1299, 3771], +] + +[[area.polygons]] +vertices = [ + [1317, 3771], + [1319, 3771], +] + +[[area.polygons]] +vertices = [ + [1322, 3771], + [1323, 3771], +] + +[[area.polygons]] +vertices = [ + [1261, 3772], + [1262, 3772], +] + +[[area.polygons]] +vertices = [[1261, 3773]] + +[[area.polygons]] +vertices = [[1259, 3775]] + +[[area.polygons]] +vertices = [ + [1241, 3780], + [1254, 3780], +] + +[[area.polygons]] +vertices = [ + [1241, 3781], + [1253, 3781], +] + +[[area.polygons]] +vertices = [ + [1241, 3782], + [1252, 3782], +] + +[[area.polygons]] +vertices = [ + [1241, 3783], + [1251, 3783], +] + +[[area.polygons]] +vertices = [ + [1241, 3784], + [1250, 3784], +] + +[[area.polygons]] +vertices = [ + [1241, 3785], + [1249, 3785], +] + +[[area.polygons]] +vertices = [ + [1241, 3786], + [1248, 3786], +] + +[[area.polygons]] +vertices = [ + [1241, 3787], + [1247, 3787], +] + +[[area.polygons]] +vertices = [ + [1241, 3788], + [1246, 3788], +] + +[[area.polygons]] +vertices = [ + [1241, 3789], + [1245, 3789], +] + +[[area.polygons]] +vertices = [ + [1241, 3790], + [1244, 3790], +] + +[[area.polygons]] +vertices = [ + [1241, 3791], + [1243, 3791], +] + +[[area.polygons]] +vertices = [ + [1241, 3792], + [1242, 3792], +] + +[[area.polygons]] +vertices = [[1241, 3793]] diff --git a/.data/raw-cache/map/area/gill_bill.toml b/.data/raw-cache/map/area/gill_bill.toml new file mode 100644 index 000000000..302d4406f --- /dev/null +++ b/.data/raw-cache/map/area/gill_bill.toml @@ -0,0 +1,16 @@ +# Gill Bill music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Gill Bill. +# Bounds x 1352-1383, z 3624-3647; 768 tiles as 1 rectangles. + +[[area]] +name = "Gill Bill" +area_id = "area.gill_bill" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1352, 3624], + [1383, 3624], + [1383, 3647], + [1352, 3647], +] diff --git a/.data/raw-cache/map/area/gnome_ball_field.toml b/.data/raw-cache/map/area/gnome_ball_field.toml new file mode 100644 index 000000000..52b36ac95 --- /dev/null +++ b/.data/raw-cache/map/area/gnome_ball_field.toml @@ -0,0 +1,15 @@ +# The Gnome Ball pitch inside its gates in the north-west of the Tree Gnome Stronghold (west gate +# at 2383,3488, goal at 2404,3488). Music area for "Gnomeball"; the stronghold polygon leaves this +# rectangle out. +[[area]] +name = "Gnome Ball field" +area_id = "area.gnome_ball_field" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2384, 3479], + [2405, 3479], + [2405, 3497], + [2384, 3497], +] diff --git a/.data/raw-cache/map/area/goblin_game.toml b/.data/raw-cache/map/area/goblin_game.toml new file mode 100644 index 000000000..bb08eb251 --- /dev/null +++ b/.data/raw-cache/map/area/goblin_game.toml @@ -0,0 +1,16 @@ +# Goblin Game music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Goblin Game. +# Bounds x 2560-2623, z 9792-9855; 4096 tiles as 1 rectangles. + +[[area]] +name = "Goblin Game" +area_id = "area.goblin_game" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 9792], + [2623, 9792], + [2623, 9855], + [2560, 9855], +] diff --git a/.data/raw-cache/map/area/goblin_village.toml b/.data/raw-cache/map/area/goblin_village.toml new file mode 100644 index 000000000..cc401c506 --- /dev/null +++ b/.data/raw-cache/map/area/goblin_village.toml @@ -0,0 +1,12 @@ +[[area]] +name = "Goblin Village" +area_id = "area.goblin_village" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2940, 3480], + [2975, 3480], + [2975, 3520], + [2940, 3520], +] diff --git a/.data/raw-cache/map/area/grand_exchange.toml b/.data/raw-cache/map/area/grand_exchange.toml new file mode 100644 index 000000000..21914bc5b --- /dev/null +++ b/.data/raw-cache/map/area/grand_exchange.toml @@ -0,0 +1,14 @@ +[[area]] +name = "Grand Exchange" +area_id = "area.grand_exchange" +levels = [0, 1, 2, 3] + +# The exchange building, its plaza and the ring of walls around it in north-west Varrock. The +# Varrock area polygon leaves this rectangle out so the music plugin only ever sees one area. +[[area.polygons]] +vertices = [ + [3143, 3462], + [3187, 3462], + [3187, 3510], + [3143, 3510], +] diff --git a/.data/raw-cache/map/area/grotto.toml b/.data/raw-cache/map/area/grotto.toml new file mode 100644 index 000000000..60db081bb --- /dev/null +++ b/.data/raw-cache/map/area/grotto.toml @@ -0,0 +1,16 @@ +# Grotto music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Grotto. +# Bounds x 3392-3455, z 9728-9791; 4096 tiles as 1 rectangles. + +[[area]] +name = "Grotto" +area_id = "area.grotto" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3392, 9728], + [3455, 9728], + [3455, 9791], + [3392, 9791], +] diff --git a/.data/raw-cache/map/area/guardians_of_the_rift.toml b/.data/raw-cache/map/area/guardians_of_the_rift.toml new file mode 100644 index 000000000..3085ba443 --- /dev/null +++ b/.data/raw-cache/map/area/guardians_of_the_rift.toml @@ -0,0 +1,16 @@ +# Guardians of the Rift music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Guardians of the Rift. +# Bounds x 3520-3711, z 9483-9535; 10176 tiles as 1 rectangles. + +[[area]] +name = "Guardians of the Rift" +area_id = "area.guardians_of_the_rift" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3520, 9483], + [3711, 9483], + [3711, 9535], + [3520, 9535], +] diff --git a/.data/raw-cache/map/area/h_a_m_and_seek.toml b/.data/raw-cache/map/area/h_a_m_and_seek.toml new file mode 100644 index 000000000..afdb3bca5 --- /dev/null +++ b/.data/raw-cache/map/area/h_a_m_and_seek.toml @@ -0,0 +1,16 @@ +# H.A.M. and Seek music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: H.A.M. and Seek. +# Bounds x 2368-2431, z 5504-5567; 4096 tiles as 1 rectangles. + +[[area]] +name = "H.A.M. and Seek" +area_id = "area.h_a_m_and_seek" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 5504], + [2431, 5504], + [2431, 5567], + [2368, 5567], +] diff --git a/.data/raw-cache/map/area/h_a_m_attack.toml b/.data/raw-cache/map/area/h_a_m_attack.toml new file mode 100644 index 000000000..77dccd5e3 --- /dev/null +++ b/.data/raw-cache/map/area/h_a_m_attack.toml @@ -0,0 +1,16 @@ +# H.A.M. Attack music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: H.A.M. Attack. +# Bounds x 2432-2495, z 5376-5439; 4096 tiles as 1 rectangles. + +[[area]] +name = "H.A.M. Attack" +area_id = "area.h_a_m_attack" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2432, 5376], + [2495, 5376], + [2495, 5439], + [2432, 5439], +] diff --git a/.data/raw-cache/map/area/h_a_m_fisted.toml b/.data/raw-cache/map/area/h_a_m_fisted.toml new file mode 100644 index 000000000..8342f621e --- /dev/null +++ b/.data/raw-cache/map/area/h_a_m_fisted.toml @@ -0,0 +1,16 @@ +# H.A.M. Fisted music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: H.A.M. Fisted. +# Bounds x 2560-2623, z 5184-5247; 4096 tiles as 1 rectangles. + +[[area]] +name = "H.A.M. Fisted" +area_id = "area.h_a_m_fisted" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 5184], + [2623, 5184], + [2623, 5247], + [2560, 5247], +] diff --git a/.data/raw-cache/map/area/harmony.toml b/.data/raw-cache/map/area/harmony.toml new file mode 100644 index 000000000..07270bb5c --- /dev/null +++ b/.data/raw-cache/map/area/harmony.toml @@ -0,0 +1,16 @@ +# Harmony music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Harmony (plus chef_surprise from wiki polygons sharing this footprint). +# Bounds x 1856-1919, z 5312-5375; 4096 tiles as 1 rectangles. + +[[area]] +name = "Harmony" +area_id = "area.harmony" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 5312], + [1919, 5312], + [1919, 5375], + [1856, 5375], +] diff --git a/.data/raw-cache/map/area/harmony_2.toml b/.data/raw-cache/map/area/harmony_2.toml new file mode 100644 index 000000000..7c8853b7f --- /dev/null +++ b/.data/raw-cache/map/area/harmony_2.toml @@ -0,0 +1,24 @@ +# Harmony 2 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Harmony 2. +# Bounds x 3200-3263, z 9600-9727; 5376 tiles as 2 rectangles. + +[[area]] +name = "Harmony 2" +area_id = "area.harmony_2" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3200, 9600], + [3219, 9600], + [3219, 9727], + [3200, 9727], +] + +[[area.polygons]] +vertices = [ + [3220, 9664], + [3263, 9664], + [3263, 9727], + [3220, 9727], +] diff --git a/.data/raw-cache/map/area/haunted_mine.toml b/.data/raw-cache/map/area/haunted_mine.toml new file mode 100644 index 000000000..4f950a385 --- /dev/null +++ b/.data/raw-cache/map/area/haunted_mine.toml @@ -0,0 +1,16 @@ +# Haunted Mine music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Haunted Mine. +# Bounds x 2752-2815, z 4416-4479; 4096 tiles as 1 rectangles. + +[[area]] +name = "Haunted Mine" +area_id = "area.haunted_mine" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 4416], + [2815, 4416], + [2815, 4479], + [2752, 4479], +] diff --git a/.data/raw-cache/map/area/have_a_blast.toml b/.data/raw-cache/map/area/have_a_blast.toml new file mode 100644 index 000000000..6db031036 --- /dev/null +++ b/.data/raw-cache/map/area/have_a_blast.toml @@ -0,0 +1,16 @@ +# Have a Blast music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Have a Blast. +# Bounds x 1920-1983, z 4928-4991; 4096 tiles as 1 rectangles. + +[[area]] +name = "Have a Blast" +area_id = "area.have_a_blast" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1920, 4928], + [1983, 4928], + [1983, 4991], + [1920, 4991], +] diff --git a/.data/raw-cache/map/area/have_an_ice_day.toml b/.data/raw-cache/map/area/have_an_ice_day.toml new file mode 100644 index 000000000..3ee6612e9 --- /dev/null +++ b/.data/raw-cache/map/area/have_an_ice_day.toml @@ -0,0 +1,609 @@ +# Have an Ice Day music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Have an Ice Day. +# Bounds x 2624-2680, z 3978-4088; 3771 tiles as 99 rectangles. + +[[area]] +name = "Have an Ice Day" +area_id = "area.have_an_ice_day" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2664, 3978], + [2668, 3978], + [2668, 4024], + [2664, 4024], +] + +[[area.polygons]] +vertices = [ + [2663, 3979], + [2663, 4040], +] + +[[area.polygons]] +vertices = [ + [2669, 3979], + [2669, 4024], +] + +[[area.polygons]] +vertices = [ + [2672, 3979], + [2674, 3979], + [2674, 4003], + [2672, 4003], +] + +[[area.polygons]] +vertices = [ + [2670, 3980], + [2671, 3980], + [2671, 4024], + [2670, 4024], +] + +[[area.polygons]] +vertices = [ + [2675, 3980], + [2675, 4001], +] + +[[area.polygons]] +vertices = [ + [2676, 3981], + [2676, 4000], +] + +[[area.polygons]] +vertices = [ + [2677, 3982], + [2677, 3997], +] + +[[area.polygons]] +vertices = [ + [2662, 3983], + [2662, 4041], +] + +[[area.polygons]] +vertices = [ + [2678, 3983], + [2678, 3996], +] + +[[area.polygons]] +vertices = [ + [2679, 3984], + [2679, 3995], +] + +[[area.polygons]] +vertices = [ + [2661, 3986], + [2661, 4042], +] + +[[area.polygons]] +vertices = [ + [2680, 3986], + [2680, 3994], +] + +[[area.polygons]] +vertices = [ + [2660, 3987], + [2660, 4088], +] + +[[area.polygons]] +vertices = [ + [2657, 3988], + [2659, 3988], + [2659, 4087], + [2657, 4087], +] + +[[area.polygons]] +vertices = [ + [2656, 3989], + [2656, 4086], +] + +[[area.polygons]] +vertices = [ + [2655, 3990], + [2655, 4085], +] + +[[area.polygons]] +vertices = [ + [2651, 3991], + [2654, 3991], + [2654, 4083], + [2651, 4083], +] + +[[area.polygons]] +vertices = [ + [2646, 3992], + [2650, 3992], + [2650, 4048], + [2646, 4048], +] + +[[area.polygons]] +vertices = [ + [2645, 3993], + [2645, 4048], +] + +[[area.polygons]] +vertices = [ + [2644, 3994], + [2644, 4049], +] + +[[area.polygons]] +vertices = [ + [2643, 3995], + [2643, 4051], +] + +[[area.polygons]] +vertices = [ + [2642, 3996], + [2642, 4055], +] + +[[area.polygons]] +vertices = [ + [2641, 4000], + [2641, 4056], +] + +[[area.polygons]] +vertices = [ + [2635, 4002], + [2637, 4002], + [2637, 4052], + [2635, 4052], +] + +[[area.polygons]] +vertices = [ + [2640, 4002], + [2640, 4057], +] + +[[area.polygons]] +vertices = [ + [2634, 4003], + [2634, 4051], +] + +[[area.polygons]] +vertices = [ + [2638, 4003], + [2639, 4003], + [2639, 4056], + [2638, 4056], +] + +[[area.polygons]] +vertices = [ + [2672, 4004], + [2673, 4004], + [2673, 4006], + [2672, 4006], +] + +[[area.polygons]] +vertices = [ + [2633, 4005], + [2633, 4051], +] + +[[area.polygons]] +vertices = [ + [2632, 4006], + [2632, 4048], +] + +[[area.polygons]] +vertices = [ + [2630, 4007], + [2631, 4007], + [2631, 4046], + [2630, 4046], +] + +[[area.polygons]] +vertices = [ + [2672, 4007], + [2672, 4023], +] + +[[area.polygons]] +vertices = [ + [2629, 4008], + [2629, 4019], +] + +[[area.polygons]] +vertices = [ + [2628, 4009], + [2628, 4019], +] + +[[area.polygons]] +vertices = [ + [2673, 4009], + [2673, 4022], +] + +[[area.polygons]] +vertices = [ + [2674, 4010], + [2675, 4010], + [2675, 4021], + [2674, 4021], +] + +[[area.polygons]] +vertices = [ + [2627, 4011], + [2627, 4018], +] + +[[area.polygons]] +vertices = [ + [2676, 4011], + [2676, 4020], +] + +[[area.polygons]] +vertices = [ + [2677, 4012], + [2677, 4019], +] + +[[area.polygons]] +vertices = [ + [2626, 4013], + [2626, 4018], +] + +[[area.polygons]] +vertices = [ + [2678, 4014], + [2678, 4017], +] + +[[area.polygons]] +vertices = [ + [2629, 4021], + [2629, 4045], +] + +[[area.polygons]] +vertices = [ + [2628, 4022], + [2628, 4045], +] + +[[area.polygons]] +vertices = [ + [2627, 4023], + [2627, 4026], +] + +[[area.polygons]] +vertices = [ + [2664, 4025], + [2666, 4025], +] + +[[area.polygons]] +vertices = [ + [2664, 4026], + [2665, 4026], + [2665, 4027], + [2664, 4027], +] + +[[area.polygons]] +vertices = [ + [2627, 4028], + [2627, 4045], +] + +[[area.polygons]] +vertices = [ + [2664, 4028], + [2664, 4039], +] + +[[area.polygons]] +vertices = [ + [2626, 4029], + [2626, 4044], +] + +[[area.polygons]] +vertices = [ + [2625, 4030], + [2625, 4036], +] + +[[area.polygons]] +vertices = [ + [2624, 4031], + [2624, 4035], +] + +[[area.polygons]] +vertices = [ + [2665, 4035], + [2666, 4035], + [2666, 4037], + [2665, 4037], +] + +[[area.polygons]] +vertices = [[2667, 4036]] + +[[area.polygons]] +vertices = [[2665, 4038]] + +[[area.polygons]] +vertices = [ + [2625, 4039], + [2625, 4043], +] + +[[area.polygons]] +vertices = [ + [2624, 4041], + [2624, 4042], +] + +[[area.polygons]] +vertices = [[2631, 4047]] + +[[area.polygons]] +vertices = [ + [2661, 4048], + [2661, 4088], +] + +[[area.polygons]] +vertices = [ + [2662, 4049], + [2662, 4088], +] + +[[area.polygons]] +vertices = [ + [2649, 4050], + [2650, 4050], + [2650, 4083], + [2649, 4083], +] + +[[area.polygons]] +vertices = [ + [2663, 4050], + [2663, 4087], +] + +[[area.polygons]] +vertices = [ + [2664, 4051], + [2664, 4087], +] + +[[area.polygons]] +vertices = [ + [2648, 4052], + [2648, 4085], +] + +[[area.polygons]] +vertices = [ + [2636, 4053], + [2637, 4053], + [2637, 4055], + [2636, 4055], +] + +[[area.polygons]] +vertices = [ + [2665, 4053], + [2665, 4086], +] + +[[area.polygons]] +vertices = [ + [2647, 4054], + [2647, 4086], +] + +[[area.polygons]] +vertices = [ + [2666, 4054], + [2666, 4082], +] + +[[area.polygons]] +vertices = [ + [2667, 4055], + [2667, 4081], +] + +[[area.polygons]] +vertices = [[2637, 4056]] + +[[area.polygons]] +vertices = [ + [2668, 4056], + [2669, 4056], + [2669, 4058], + [2668, 4058], +] + +[[area.polygons]] +vertices = [[2639, 4057]] + +[[area.polygons]] +vertices = [[2670, 4057]] + +[[area.polygons]] +vertices = [ + [2646, 4058], + [2646, 4086], +] + +[[area.polygons]] +vertices = [[2668, 4059]] + +[[area.polygons]] +vertices = [ + [2645, 4060], + [2645, 4087], +] + +[[area.polygons]] +vertices = [ + [2644, 4062], + [2644, 4087], +] + +[[area.polygons]] +vertices = [ + [2668, 4062], + [2668, 4080], +] + +[[area.polygons]] +vertices = [ + [2642, 4063], + [2643, 4063], + [2643, 4087], + [2642, 4087], +] + +[[area.polygons]] +vertices = [ + [2669, 4063], + [2669, 4068], +] + +[[area.polygons]] +vertices = [ + [2636, 4064], + [2638, 4064], + [2638, 4080], + [2636, 4080], +] + +[[area.polygons]] +vertices = [ + [2641, 4064], + [2641, 4087], +] + +[[area.polygons]] +vertices = [ + [2635, 4065], + [2635, 4078], +] + +[[area.polygons]] +vertices = [ + [2639, 4065], + [2640, 4065], + [2640, 4086], + [2639, 4086], +] + +[[area.polygons]] +vertices = [ + [2670, 4065], + [2670, 4067], +] + +[[area.polygons]] +vertices = [ + [2634, 4066], + [2634, 4069], +] + +[[area.polygons]] +vertices = [[2671, 4066]] + +[[area.polygons]] +vertices = [ + [2633, 4067], + [2633, 4068], +] + +[[area.polygons]] +vertices = [ + [2669, 4071], + [2669, 4074], +] + +[[area.polygons]] +vertices = [ + [2634, 4072], + [2634, 4076], +] + +[[area.polygons]] +vertices = [ + [2670, 4072], + [2670, 4073], +] + +[[area.polygons]] +vertices = [ + [2669, 4077], + [2669, 4079], +] + +[[area.polygons]] +vertices = [ + [2637, 4081], + [2638, 4081], + [2638, 4082], + [2637, 4082], +] + +[[area.polygons]] +vertices = [ + [2638, 4083], + [2638, 4084], +] + +[[area.polygons]] +vertices = [[2649, 4084]] + +[[area.polygons]] +vertices = [ + [2653, 4084], + [2654, 4084], +] + +[[area.polygons]] +vertices = [[2654, 4085]] + +[[area.polygons]] +vertices = [[2666, 4085]] + +[[area.polygons]] +vertices = [ + [2658, 4088], + [2659, 4088], +] diff --git a/.data/raw-cache/map/area/head_to_head.toml b/.data/raw-cache/map/area/head_to_head.toml new file mode 100644 index 000000000..2647f524c --- /dev/null +++ b/.data/raw-cache/map/area/head_to_head.toml @@ -0,0 +1,16 @@ +# Head to Head music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Head to Head. +# Bounds x 1856-1919, z 5120-5183; 4096 tiles as 1 rectangles. + +[[area]] +name = "Head to Head" +area_id = "area.head_to_head" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 5120], + [1919, 5120], + [1919, 5183], + [1856, 5183], +] diff --git a/.data/raw-cache/map/area/heart_and_mind.toml b/.data/raw-cache/map/area/heart_and_mind.toml new file mode 100644 index 000000000..ebef9d4de --- /dev/null +++ b/.data/raw-cache/map/area/heart_and_mind.toml @@ -0,0 +1,16 @@ +# Heart and Mind music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Heart and Mind. +# Bounds x 2496-2559, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Heart and Mind" +area_id = "area.heart_and_mind" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2496, 4800], + [2559, 4800], + [2559, 4863], + [2496, 4863], +] diff --git a/.data/raw-cache/map/area/hells_bells.toml b/.data/raw-cache/map/area/hells_bells.toml new file mode 100644 index 000000000..7af57df2f --- /dev/null +++ b/.data/raw-cache/map/area/hells_bells.toml @@ -0,0 +1,16 @@ +# Hells Bells music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Hells Bells. +# Bounds x 2752-2815, z 3712-3834; 7872 tiles as 1 rectangles. + +[[area]] +name = "Hells Bells" +area_id = "area.hells_bells" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 3712], + [2815, 3712], + [2815, 3834], + [2752, 3834], +] diff --git a/.data/raw-cache/map/area/hemenster.toml b/.data/raw-cache/map/area/hemenster.toml new file mode 100644 index 000000000..bfeabfed0 --- /dev/null +++ b/.data/raw-cache/map/area/hemenster.toml @@ -0,0 +1,792 @@ +# Hemenster music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lasting; Wonderous; Magical Journey. +# Bounds x 2611-2780, z 3299-3469; 16720 tiles as 125 rectangles. + +[[area]] +name = "Hemenster" +area_id = "area.hemenster" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 3299], + [2703, 3299], + [2703, 3455], + [2688, 3455], +] + +[[area.polygons]] +vertices = [ + [2704, 3300], + [2710, 3300], + [2710, 3455], + [2704, 3455], +] + +[[area.polygons]] +vertices = [ + [2711, 3303], + [2711, 3455], +] + +[[area.polygons]] +vertices = [ + [2712, 3304], + [2712, 3454], +] + +[[area.polygons]] +vertices = [ + [2713, 3305], + [2713, 3453], +] + +[[area.polygons]] +vertices = [ + [2714, 3306], + [2714, 3452], +] + +[[area.polygons]] +vertices = [ + [2715, 3307], + [2715, 3451], +] + +[[area.polygons]] +vertices = [ + [2716, 3308], + [2716, 3450], +] + +[[area.polygons]] +vertices = [ + [2717, 3310], + [2717, 3449], +] + +[[area.polygons]] +vertices = [ + [2687, 3311], + [2687, 3313], +] + +[[area.polygons]] +vertices = [ + [2718, 3311], + [2718, 3446], +] + +[[area.polygons]] +vertices = [ + [2719, 3312], + [2720, 3312], + [2720, 3361], + [2719, 3361], +] + +[[area.polygons]] +vertices = [[2721, 3313]] + +[[area.polygons]] +vertices = [ + [2721, 3318], + [2721, 3361], +] + +[[area.polygons]] +vertices = [ + [2722, 3320], + [2723, 3320], + [2723, 3361], + [2722, 3361], +] + +[[area.polygons]] +vertices = [ + [2724, 3321], + [2724, 3361], +] + +[[area.polygons]] +vertices = [ + [2725, 3323], + [2725, 3361], +] + +[[area.polygons]] +vertices = [ + [2726, 3324], + [2727, 3324], + [2727, 3349], + [2726, 3349], +] + +[[area.polygons]] +vertices = [ + [2728, 3325], + [2730, 3325], + [2730, 3349], + [2728, 3349], +] + +[[area.polygons]] +vertices = [ + [2744, 3325], + [2745, 3325], + [2745, 3360], + [2744, 3360], +] + +[[area.polygons]] +vertices = [ + [2731, 3326], + [2736, 3326], + [2736, 3349], + [2731, 3349], +] + +[[area.polygons]] +vertices = [ + [2741, 3326], + [2743, 3326], + [2743, 3435], + [2741, 3435], +] + +[[area.polygons]] +vertices = [ + [2746, 3326], + [2746, 3356], +] + +[[area.polygons]] +vertices = [ + [2737, 3327], + [2740, 3327], + [2740, 3362], + [2737, 3362], +] + +[[area.polygons]] +vertices = [ + [2687, 3328], + [2687, 3458], +] + +[[area.polygons]] +vertices = [ + [2686, 3329], + [2686, 3458], +] + +[[area.polygons]] +vertices = [ + [2747, 3329], + [2747, 3354], +] + +[[area.polygons]] +vertices = [ + [2685, 3330], + [2685, 3458], +] + +[[area.polygons]] +vertices = [ + [2684, 3331], + [2684, 3458], +] + +[[area.polygons]] +vertices = [ + [2683, 3332], + [2683, 3458], +] + +[[area.polygons]] +vertices = [ + [2682, 3333], + [2682, 3459], +] + +[[area.polygons]] +vertices = [ + [2673, 3334], + [2681, 3334], + [2681, 3461], + [2673, 3461], +] + +[[area.polygons]] +vertices = [ + [2672, 3335], + [2672, 3467], +] + +[[area.polygons]] +vertices = [ + [2671, 3336], + [2671, 3467], +] + +[[area.polygons]] +vertices = [ + [2748, 3336], + [2748, 3344], +] + +[[area.polygons]] +vertices = [ + [2670, 3337], + [2670, 3467], +] + +[[area.polygons]] +vertices = [ + [2669, 3338], + [2669, 3467], +] + +[[area.polygons]] +vertices = [ + [2668, 3339], + [2668, 3466], +] + +[[area.polygons]] +vertices = [ + [2749, 3339], + [2749, 3342], +] + +[[area.polygons]] +vertices = [ + [2623, 3340], + [2667, 3340], + [2667, 3354], + [2623, 3354], +] + +[[area.polygons]] +vertices = [ + [2748, 3346], + [2748, 3353], +] + +[[area.polygons]] +vertices = [ + [2749, 3349], + [2749, 3352], +] + +[[area.polygons]] +vertices = [ + [2732, 3350], + [2736, 3350], + [2736, 3361], + [2732, 3361], +] + +[[area.polygons]] +vertices = [ + [2624, 3355], + [2667, 3355], + [2667, 3370], + [2624, 3370], +] + +[[area.polygons]] +vertices = [ + [2744, 3361], + [2744, 3435], +] + +[[area.polygons]] +vertices = [[2719, 3362]] + +[[area.polygons]] +vertices = [ + [2738, 3363], + [2740, 3363], +] + +[[area.polygons]] +vertices = [ + [2739, 3364], + [2740, 3364], + [2740, 3436], + [2739, 3436], +] + +[[area.polygons]] +vertices = [ + [2745, 3366], + [2745, 3435], +] + +[[area.polygons]] +vertices = [ + [2746, 3367], + [2746, 3436], +] + +[[area.polygons]] +vertices = [ + [2747, 3369], + [2747, 3436], +] + +[[area.polygons]] +vertices = [ + [2625, 3371], + [2631, 3371], +] + +[[area.polygons]] +vertices = [ + [2635, 3371], + [2667, 3371], + [2667, 3466], + [2635, 3466], +] + +[[area.polygons]] +vertices = [ + [2634, 3372], + [2634, 3467], +] + +[[area.polygons]] +vertices = [ + [2633, 3373], + [2633, 3467], +] + +[[area.polygons]] +vertices = [ + [2632, 3374], + [2632, 3467], +] + +[[area.polygons]] +vertices = [ + [2748, 3374], + [2748, 3436], +] + +[[area.polygons]] +vertices = [ + [2749, 3375], + [2749, 3436], +] + +[[area.polygons]] +vertices = [ + [2628, 3376], + [2631, 3376], + [2631, 3381], + [2628, 3381], +] + +[[area.polygons]] +vertices = [ + [2750, 3378], + [2750, 3381], +] + +[[area.polygons]] +vertices = [ + [2630, 3382], + [2631, 3382], + [2631, 3388], + [2630, 3388], +] + +[[area.polygons]] +vertices = [ + [2629, 3384], + [2629, 3387], +] + +[[area.polygons]] +vertices = [ + [2738, 3385], + [2738, 3436], +] + +[[area.polygons]] +vertices = [ + [2750, 3385], + [2750, 3436], +] + +[[area.polygons]] +vertices = [ + [2719, 3386], + [2719, 3445], +] + +[[area.polygons]] +vertices = [ + [2737, 3386], + [2737, 3437], +] + +[[area.polygons]] +vertices = [ + [2751, 3386], + [2751, 3436], +] + +[[area.polygons]] +vertices = [ + [2720, 3387], + [2736, 3387], + [2736, 3438], + [2720, 3438], +] + +[[area.polygons]] +vertices = [[2631, 3389]] + +[[area.polygons]] +vertices = [ + [2752, 3392], + [2757, 3392], + [2757, 3435], + [2752, 3435], +] + +[[area.polygons]] +vertices = [ + [2758, 3393], + [2758, 3428], +] + +[[area.polygons]] +vertices = [ + [2766, 3393], + [2774, 3393], + [2774, 3410], + [2766, 3410], +] + +[[area.polygons]] +vertices = [ + [2631, 3394], + [2631, 3467], +] + +[[area.polygons]] +vertices = [ + [2764, 3394], + [2765, 3394], + [2765, 3409], + [2764, 3409], +] + +[[area.polygons]] +vertices = [ + [2775, 3394], + [2775, 3410], +] + +[[area.polygons]] +vertices = [ + [2630, 3395], + [2630, 3467], +] + +[[area.polygons]] +vertices = [ + [2763, 3395], + [2763, 3408], +] + +[[area.polygons]] +vertices = [ + [2776, 3395], + [2776, 3409], +] + +[[area.polygons]] +vertices = [ + [2759, 3396], + [2762, 3396], + [2762, 3407], + [2759, 3407], +] + +[[area.polygons]] +vertices = [ + [2777, 3396], + [2777, 3407], +] + +[[area.polygons]] +vertices = [ + [2629, 3397], + [2629, 3468], +] + +[[area.polygons]] +vertices = [ + [2627, 3398], + [2628, 3398], + [2628, 3469], + [2627, 3469], +] + +[[area.polygons]] +vertices = [ + [2624, 3399], + [2626, 3399], + [2626, 3468], + [2624, 3468], +] + +[[area.polygons]] +vertices = [ + [2778, 3399], + [2780, 3399], + [2780, 3402], + [2778, 3402], +] + +[[area.polygons]] +vertices = [ + [2761, 3408], + [2762, 3408], +] + +[[area.polygons]] +vertices = [ + [2759, 3409], + [2759, 3428], +] + +[[area.polygons]] +vertices = [[2765, 3410]] + +[[area.polygons]] +vertices = [ + [2760, 3411], + [2760, 3427], +] + +[[area.polygons]] +vertices = [ + [2766, 3411], + [2773, 3411], +] + +[[area.polygons]] +vertices = [ + [2767, 3412], + [2771, 3412], +] + +[[area.polygons]] +vertices = [ + [2761, 3414], + [2761, 3426], +] + +[[area.polygons]] +vertices = [ + [2762, 3420], + [2762, 3425], +] + +[[area.polygons]] +vertices = [ + [2763, 3421], + [2763, 3424], +] + +[[area.polygons]] +vertices = [ + [2764, 3422], + [2764, 3423], +] + +[[area.polygons]] +vertices = [ + [2752, 3436], + [2754, 3436], +] + +[[area.polygons]] +vertices = [ + [2720, 3439], + [2733, 3439], +] + +[[area.polygons]] +vertices = [ + [2720, 3440], + [2731, 3440], +] + +[[area.polygons]] +vertices = [ + [2720, 3441], + [2729, 3441], +] + +[[area.polygons]] +vertices = [ + [2720, 3442], + [2726, 3442], +] + +[[area.polygons]] +vertices = [ + [2720, 3443], + [2725, 3443], +] + +[[area.polygons]] +vertices = [ + [2720, 3444], + [2724, 3444], +] + +[[area.polygons]] +vertices = [ + [2623, 3451], + [2623, 3467], +] + +[[area.polygons]] +vertices = [ + [2622, 3452], + [2622, 3467], +] + +[[area.polygons]] +vertices = [ + [2621, 3453], + [2621, 3466], +] + +[[area.polygons]] +vertices = [ + [2620, 3454], + [2620, 3464], +] + +[[area.polygons]] +vertices = [ + [2619, 3455], + [2619, 3463], +] + +[[area.polygons]] +vertices = [ + [2618, 3456], + [2618, 3463], +] + +[[area.polygons]] +vertices = [[2688, 3456]] + +[[area.polygons]] +vertices = [ + [2695, 3456], + [2700, 3456], +] + +[[area.polygons]] +vertices = [ + [2704, 3456], + [2707, 3456], +] + +[[area.polygons]] +vertices = [ + [2617, 3457], + [2617, 3463], +] + +[[area.polygons]] +vertices = [ + [2616, 3458], + [2616, 3463], +] + +[[area.polygons]] +vertices = [ + [2615, 3459], + [2615, 3463], +] + +[[area.polygons]] +vertices = [ + [2614, 3460], + [2614, 3463], +] + +[[area.polygons]] +vertices = [ + [2613, 3461], + [2613, 3463], +] + +[[area.polygons]] +vertices = [ + [2612, 3462], + [2612, 3463], +] + +[[area.polygons]] +vertices = [ + [2673, 3462], + [2680, 3462], +] + +[[area.polygons]] +vertices = [[2611, 3463]] + +[[area.polygons]] +vertices = [ + [2673, 3463], + [2675, 3463], +] + +[[area.polygons]] +vertices = [ + [2673, 3464], + [2674, 3464], +] + +[[area.polygons]] +vertices = [ + [2673, 3465], + [2673, 3466], +] + +[[area.polygons]] +vertices = [ + [2635, 3467], + [2661, 3467], +] + +[[area.polygons]] +vertices = [ + [2641, 3468], + [2660, 3468], +] + +[[area.polygons]] +vertices = [ + [2625, 3469], + [2626, 3469], +] + +[[area.polygons]] +vertices = [ + [2646, 3469], + [2655, 3469], +] diff --git a/.data/raw-cache/map/area/hermit.toml b/.data/raw-cache/map/area/hermit.toml new file mode 100644 index 000000000..1384fad29 --- /dev/null +++ b/.data/raw-cache/map/area/hermit.toml @@ -0,0 +1,16 @@ +# Hermit music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Hermit. +# Bounds x 2240-2303, z 4736-4799; 4096 tiles as 1 rectangles. + +[[area]] +name = "Hermit" +area_id = "area.hermit" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2240, 4736], + [2303, 4736], + [2303, 4799], + [2240, 4799], +] diff --git a/.data/raw-cache/map/area/home_sweet_home.toml b/.data/raw-cache/map/area/home_sweet_home.toml new file mode 100644 index 000000000..4e106ebfe --- /dev/null +++ b/.data/raw-cache/map/area/home_sweet_home.toml @@ -0,0 +1,16 @@ +# Home Sweet Home music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Home Sweet Home. +# Bounds x 1792-2047, z 5696-5823; 32768 tiles as 1 rectangles. + +[[area]] +name = "Home Sweet Home" +area_id = "area.home_sweet_home" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1792, 5696], + [2047, 5696], + [2047, 5823], + [1792, 5823], +] diff --git a/.data/raw-cache/map/area/horizon.toml b/.data/raw-cache/map/area/horizon.toml new file mode 100644 index 000000000..b1316761b --- /dev/null +++ b/.data/raw-cache/map/area/horizon.toml @@ -0,0 +1,16 @@ +# Horizon music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Horizon. +# Bounds x 1920-1945, z 5030-5055; 676 tiles as 1 rectangles. + +[[area]] +name = "Horizon" +area_id = "area.horizon" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1920, 5030], + [1945, 5030], + [1945, 5055], + [1920, 5055], +] diff --git a/.data/raw-cache/map/area/iban.toml b/.data/raw-cache/map/area/iban.toml new file mode 100644 index 000000000..fe1b6ea3a --- /dev/null +++ b/.data/raw-cache/map/area/iban.toml @@ -0,0 +1,16 @@ +# Iban music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Iban. +# Bounds x 2112-2175, z 4544-4735; 12288 tiles as 1 rectangles. + +[[area]] +name = "Iban" +area_id = "area.iban" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2112, 4544], + [2175, 4544], + [2175, 4735], + [2112, 4735], +] diff --git a/.data/raw-cache/map/area/ice_and_fire.toml b/.data/raw-cache/map/area/ice_and_fire.toml new file mode 100644 index 000000000..b85d5a041 --- /dev/null +++ b/.data/raw-cache/map/area/ice_and_fire.toml @@ -0,0 +1,16 @@ +# Ice and Fire music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Ice and Fire. +# Bounds x 1600-1663, z 3968-4031; 4096 tiles as 1 rectangles. + +[[area]] +name = "Ice and Fire" +area_id = "area.ice_and_fire" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1600, 3968], + [1663, 3968], + [1663, 4031], + [1600, 4031], +] diff --git a/.data/raw-cache/map/area/ice_melody.toml b/.data/raw-cache/map/area/ice_melody.toml new file mode 100644 index 000000000..b3e0ec08c --- /dev/null +++ b/.data/raw-cache/map/area/ice_melody.toml @@ -0,0 +1,577 @@ +# Ice Melody music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Ice Melody. +# Bounds x 2789-2879, z 3428-3541; 5531 tiles as 94 rectangles. + +[[area]] +name = "Ice Melody" +area_id = "area.ice_melody" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2869, 3428], + [2871, 3428], + [2871, 3527], + [2869, 3527], +] + +[[area.polygons]] +vertices = [ + [2868, 3429], + [2868, 3530], +] + +[[area.polygons]] +vertices = [ + [2872, 3430], + [2875, 3430], + [2875, 3466], + [2872, 3466], +] + +[[area.polygons]] +vertices = [ + [2867, 3431], + [2867, 3531], +] + +[[area.polygons]] +vertices = [ + [2876, 3431], + [2876, 3464], +] + +[[area.polygons]] +vertices = [ + [2866, 3432], + [2866, 3532], +] + +[[area.polygons]] +vertices = [ + [2877, 3432], + [2877, 3462], +] + +[[area.polygons]] +vertices = [ + [2865, 3433], + [2865, 3533], +] + +[[area.polygons]] +vertices = [ + [2864, 3434], + [2864, 3532], +] + +[[area.polygons]] +vertices = [ + [2863, 3435], + [2863, 3531], +] + +[[area.polygons]] +vertices = [ + [2862, 3436], + [2862, 3530], +] + +[[area.polygons]] +vertices = [ + [2861, 3437], + [2861, 3529], +] + +[[area.polygons]] +vertices = [ + [2859, 3438], + [2860, 3438], + [2860, 3527], + [2859, 3527], +] + +[[area.polygons]] +vertices = [ + [2858, 3439], + [2858, 3527], +] + +[[area.polygons]] +vertices = [ + [2857, 3440], + [2857, 3527], +] + +[[area.polygons]] +vertices = [ + [2856, 3441], + [2856, 3527], +] + +[[area.polygons]] +vertices = [ + [2853, 3442], + [2855, 3442], + [2855, 3527], + [2853, 3527], +] + +[[area.polygons]] +vertices = [ + [2852, 3443], + [2852, 3527], +] + +[[area.polygons]] +vertices = [ + [2851, 3444], + [2851, 3528], +] + +[[area.polygons]] +vertices = [ + [2849, 3445], + [2850, 3445], + [2850, 3528], + [2849, 3528], +] + +[[area.polygons]] +vertices = [ + [2847, 3446], + [2848, 3446], + [2848, 3529], + [2847, 3529], +] + +[[area.polygons]] +vertices = [ + [2841, 3447], + [2846, 3447], + [2846, 3452], + [2841, 3452], +] + +[[area.polygons]] +vertices = [ + [2840, 3448], + [2840, 3451], +] + +[[area.polygons]] +vertices = [ + [2839, 3449], + [2839, 3450], +] + +[[area.polygons]] +vertices = [ + [2842, 3453], + [2846, 3453], +] + +[[area.polygons]] +vertices = [ + [2843, 3454], + [2846, 3454], +] + +[[area.polygons]] +vertices = [ + [2844, 3455], + [2846, 3455], +] + +[[area.polygons]] +vertices = [ + [2845, 3456], + [2846, 3456], + [2846, 3530], + [2845, 3530], +] + +[[area.polygons]] +vertices = [ + [2844, 3457], + [2844, 3531], +] + +[[area.polygons]] +vertices = [ + [2843, 3459], + [2843, 3531], +] + +[[area.polygons]] +vertices = [ + [2842, 3460], + [2842, 3531], +] + +[[area.polygons]] +vertices = [ + [2841, 3461], + [2841, 3532], +] + +[[area.polygons]] +vertices = [ + [2840, 3462], + [2840, 3533], +] + +[[area.polygons]] +vertices = [ + [2838, 3463], + [2839, 3463], + [2839, 3534], + [2838, 3534], +] + +[[area.polygons]] +vertices = [ + [2836, 3464], + [2837, 3464], + [2837, 3536], + [2836, 3536], +] + +[[area.polygons]] +vertices = [ + [2834, 3465], + [2835, 3465], + [2835, 3537], + [2834, 3537], +] + +[[area.polygons]] +vertices = [ + [2872, 3467], + [2874, 3467], + [2874, 3468], + [2872, 3468], +] + +[[area.polygons]] +vertices = [ + [2872, 3469], + [2873, 3469], + [2873, 3470], + [2872, 3470], +] + +[[area.polygons]] +vertices = [ + [2872, 3471], + [2872, 3472], +] + +[[area.polygons]] +vertices = [ + [2833, 3476], + [2833, 3538], +] + +[[area.polygons]] +vertices = [ + [2832, 3478], + [2832, 3539], +] + +[[area.polygons]] +vertices = [ + [2831, 3480], + [2831, 3539], +] + +[[area.polygons]] +vertices = [ + [2872, 3485], + [2872, 3526], +] + +[[area.polygons]] +vertices = [ + [2873, 3487], + [2873, 3525], +] + +[[area.polygons]] +vertices = [ + [2830, 3488], + [2830, 3540], +] + +[[area.polygons]] +vertices = [ + [2829, 3489], + [2829, 3540], +] + +[[area.polygons]] +vertices = [ + [2874, 3489], + [2874, 3524], +] + +[[area.polygons]] +vertices = [ + [2828, 3490], + [2828, 3541], +] + +[[area.polygons]] +vertices = [ + [2794, 3491], + [2807, 3491], + [2807, 3521], + [2794, 3521], +] + +[[area.polygons]] +vertices = [ + [2827, 3491], + [2827, 3541], +] + +[[area.polygons]] +vertices = [ + [2875, 3491], + [2875, 3523], +] + +[[area.polygons]] +vertices = [ + [2793, 3492], + [2793, 3520], +] + +[[area.polygons]] +vertices = [ + [2808, 3492], + [2809, 3492], + [2809, 3530], + [2808, 3530], +] + +[[area.polygons]] +vertices = [ + [2826, 3492], + [2826, 3540], +] + +[[area.polygons]] +vertices = [ + [2792, 3493], + [2792, 3519], +] + +[[area.polygons]] +vertices = [ + [2810, 3493], + [2811, 3493], + [2811, 3530], + [2810, 3530], +] + +[[area.polygons]] +vertices = [ + [2825, 3493], + [2825, 3539], +] + +[[area.polygons]] +vertices = [ + [2876, 3493], + [2876, 3522], +] + +[[area.polygons]] +vertices = [ + [2791, 3494], + [2791, 3508], +] + +[[area.polygons]] +vertices = [ + [2812, 3494], + [2812, 3530], +] + +[[area.polygons]] +vertices = [ + [2824, 3494], + [2824, 3538], +] + +[[area.polygons]] +vertices = [ + [2790, 3495], + [2790, 3506], +] + +[[area.polygons]] +vertices = [ + [2813, 3495], + [2813, 3530], +] + +[[area.polygons]] +vertices = [ + [2822, 3495], + [2823, 3495], + [2823, 3536], + [2822, 3536], +] + +[[area.polygons]] +vertices = [ + [2877, 3495], + [2877, 3521], +] + +[[area.polygons]] +vertices = [ + [2789, 3496], + [2789, 3504], +] + +[[area.polygons]] +vertices = [ + [2814, 3496], + [2814, 3530], +] + +[[area.polygons]] +vertices = [ + [2820, 3496], + [2821, 3496], + [2821, 3534], + [2820, 3534], +] + +[[area.polygons]] +vertices = [ + [2815, 3497], + [2819, 3497], + [2819, 3530], + [2815, 3530], +] + +[[area.polygons]] +vertices = [ + [2878, 3497], + [2878, 3520], +] + +[[area.polygons]] +vertices = [ + [2879, 3499], + [2879, 3519], +] + +[[area.polygons]] +vertices = [ + [2795, 3522], + [2807, 3522], +] + +[[area.polygons]] +vertices = [ + [2796, 3523], + [2807, 3523], +] + +[[area.polygons]] +vertices = [ + [2797, 3524], + [2807, 3524], +] + +[[area.polygons]] +vertices = [ + [2798, 3525], + [2807, 3525], +] + +[[area.polygons]] +vertices = [ + [2799, 3526], + [2807, 3526], +] + +[[area.polygons]] +vertices = [ + [2800, 3527], + [2807, 3527], +] + +[[area.polygons]] +vertices = [ + [2801, 3528], + [2807, 3528], +] + +[[area.polygons]] +vertices = [[2860, 3528]] + +[[area.polygons]] +vertices = [ + [2869, 3528], + [2870, 3528], +] + +[[area.polygons]] +vertices = [ + [2802, 3529], + [2807, 3529], +] + +[[area.polygons]] +vertices = [[2849, 3529]] + +[[area.polygons]] +vertices = [[2869, 3529]] + +[[area.polygons]] +vertices = [ + [2803, 3530], + [2807, 3530], +] + +[[area.polygons]] +vertices = [[2847, 3530]] + +[[area.polygons]] +vertices = [ + [2817, 3531], + [2819, 3531], +] + +[[area.polygons]] +vertices = [[2845, 3531]] + +[[area.polygons]] +vertices = [ + [2818, 3532], + [2819, 3532], +] + +[[area.polygons]] +vertices = [[2819, 3533]] + +[[area.polygons]] +vertices = [[2821, 3535]] + +[[area.polygons]] +vertices = [[2838, 3535]] + +[[area.polygons]] +vertices = [[2823, 3537]] + +[[area.polygons]] +vertices = [[2836, 3537]] + +[[area.polygons]] +vertices = [[2834, 3538]] diff --git a/.data/raw-cache/map/area/ice_mountain.toml b/.data/raw-cache/map/area/ice_mountain.toml new file mode 100644 index 000000000..4a629244a --- /dev/null +++ b/.data/raw-cache/map/area/ice_mountain.toml @@ -0,0 +1,483 @@ +# Regenerated as rectangles from the OSRS wiki music map: 934 tiles moved to +# neighbouring music areas and 693 tiles were added. + +[[area]] +name = "Ice Mountain" +area_id = "area.ice_mountain" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2985, 3445], + [2991, 3445], +] + +[[area.polygons]] +vertices = [ + [3023, 3445], + [3040, 3445], +] + +[[area.polygons]] +vertices = [ + [2985, 3446], + [2990, 3446], +] + +[[area.polygons]] +vertices = [ + [3024, 3446], + [3040, 3446], +] + +[[area.polygons]] +vertices = [ + [2985, 3447], + [2989, 3447], +] + +[[area.polygons]] +vertices = [ + [3025, 3447], + [3040, 3447], +] + +[[area.polygons]] +vertices = [ + [2985, 3448], + [2988, 3448], +] + +[[area.polygons]] +vertices = [ + [3026, 3448], + [3040, 3448], +] + +[[area.polygons]] +vertices = [ + [2985, 3449], + [2987, 3449], + [2987, 3523], + [2985, 3523], +] + +[[area.polygons]] +vertices = [ + [3027, 3449], + [3040, 3449], +] + +[[area.polygons]] +vertices = [ + [3028, 3450], + [3040, 3450], +] + +[[area.polygons]] +vertices = [ + [3029, 3451], + [3040, 3451], +] + +[[area.polygons]] +vertices = [ + [3030, 3452], + [3040, 3452], +] + +[[area.polygons]] +vertices = [ + [3031, 3453], + [3040, 3453], + [3040, 3504], + [3031, 3504], +] + +[[area.polygons]] +vertices = [ + [2984, 3457], + [2984, 3480], +] + +[[area.polygons]] +vertices = [ + [2983, 3458], + [2983, 3479], +] + +[[area.polygons]] +vertices = [ + [2988, 3458], + [2989, 3458], + [2989, 3523], + [2988, 3523], +] + +[[area.polygons]] +vertices = [ + [2982, 3459], + [2982, 3478], +] + +[[area.polygons]] +vertices = [ + [2990, 3459], + [2990, 3523], +] + +[[area.polygons]] +vertices = [ + [2981, 3460], + [2981, 3477], +] + +[[area.polygons]] +vertices = [ + [2991, 3460], + [2991, 3523], +] + +[[area.polygons]] +vertices = [ + [2980, 3461], + [2980, 3476], +] + +[[area.polygons]] +vertices = [ + [2992, 3461], + [2992, 3523], +] + +[[area.polygons]] +vertices = [ + [2979, 3462], + [2979, 3475], +] + +[[area.polygons]] +vertices = [ + [2993, 3462], + [3008, 3462], + [3008, 3512], + [2993, 3512], +] + +[[area.polygons]] +vertices = [ + [3030, 3462], + [3030, 3503], +] + +[[area.polygons]] +vertices = [ + [3009, 3463], + [3009, 3512], +] + +[[area.polygons]] +vertices = [ + [3029, 3463], + [3029, 3503], +] + +[[area.polygons]] +vertices = [ + [3010, 3464], + [3010, 3512], +] + +[[area.polygons]] +vertices = [ + [3028, 3464], + [3028, 3503], +] + +[[area.polygons]] +vertices = [ + [3011, 3465], + [3011, 3512], +] + +[[area.polygons]] +vertices = [ + [3027, 3465], + [3027, 3503], +] + +[[area.polygons]] +vertices = [ + [3012, 3466], + [3020, 3466], + [3020, 3512], + [3012, 3512], +] + +[[area.polygons]] +vertices = [ + [3026, 3466], + [3026, 3505], +] + +[[area.polygons]] +vertices = [ + [3021, 3467], + [3025, 3467], + [3025, 3505], + [3021, 3505], +] + +[[area.polygons]] +vertices = [ + [3032, 3505], + [3040, 3505], +] + +[[area.polygons]] +vertices = [ + [3021, 3506], + [3024, 3506], + [3024, 3509], + [3021, 3509], +] + +[[area.polygons]] +vertices = [ + [3033, 3506], + [3040, 3506], + [3040, 3523], + [3033, 3523], +] + +[[area.polygons]] +vertices = [ + [3032, 3508], + [3032, 3527], +] + +[[area.polygons]] +vertices = [ + [3031, 3509], + [3031, 3528], +] + +[[area.polygons]] +vertices = [ + [2993, 3513], + [3007, 3513], + [3007, 3523], + [2993, 3523], +] + +[[area.polygons]] +vertices = [ + [3012, 3513], + [3018, 3513], +] + +[[area.polygons]] +vertices = [ + [3014, 3514], + [3017, 3514], +] + +[[area.polygons]] +vertices = [ + [3013, 3517], + [3014, 3517], + [3014, 3543], + [3013, 3543], +] + +[[area.polygons]] +vertices = [ + [3017, 3517], + [3017, 3543], +] + +[[area.polygons]] +vertices = [ + [3030, 3517], + [3030, 3529], +] + +[[area.polygons]] +vertices = [ + [3012, 3518], + [3012, 3543], +] + +[[area.polygons]] +vertices = [ + [3018, 3518], + [3018, 3543], +] + +[[area.polygons]] +vertices = [ + [3029, 3518], + [3029, 3530], +] + +[[area.polygons]] +vertices = [ + [3008, 3519], + [3011, 3519], + [3011, 3543], + [3008, 3543], +] + +[[area.polygons]] +vertices = [ + [3019, 3519], + [3028, 3519], + [3028, 3536], + [3019, 3536], +] + +[[area.polygons]] +vertices = [ + [3015, 3520], + [3016, 3520], + [3016, 3543], + [3015, 3543], +] + +[[area.polygons]] +vertices = [ + [2994, 3524], + [3007, 3524], +] + +[[area.polygons]] +vertices = [ + [3033, 3524], + [3035, 3524], +] + +[[area.polygons]] +vertices = [ + [2995, 3525], + [3007, 3525], +] + +[[area.polygons]] +vertices = [ + [3033, 3525], + [3034, 3525], +] + +[[area.polygons]] +vertices = [ + [2996, 3526], + [3007, 3526], +] + +[[area.polygons]] +vertices = [[3033, 3526]] + +[[area.polygons]] +vertices = [ + [2997, 3527], + [3007, 3527], +] + +[[area.polygons]] +vertices = [ + [2998, 3528], + [3007, 3528], + [3007, 3531], + [2998, 3531], +] + +[[area.polygons]] +vertices = [ + [2999, 3532], + [3007, 3532], + [3007, 3535], + [2999, 3535], +] + +[[area.polygons]] +vertices = [ + [3001, 3536], + [3007, 3536], +] + +[[area.polygons]] +vertices = [ + [3002, 3537], + [3007, 3537], +] + +[[area.polygons]] +vertices = [ + [3019, 3537], + [3027, 3537], +] + +[[area.polygons]] +vertices = [ + [3003, 3538], + [3007, 3538], + [3007, 3539], + [3003, 3539], +] + +[[area.polygons]] +vertices = [ + [3019, 3538], + [3026, 3538], +] + +[[area.polygons]] +vertices = [ + [3019, 3539], + [3025, 3539], +] + +[[area.polygons]] +vertices = [ + [3004, 3540], + [3007, 3540], +] + +[[area.polygons]] +vertices = [ + [3019, 3540], + [3024, 3540], +] + +[[area.polygons]] +vertices = [ + [3005, 3541], + [3007, 3541], +] + +[[area.polygons]] +vertices = [ + [3019, 3541], + [3023, 3541], +] + +[[area.polygons]] +vertices = [ + [3006, 3542], + [3007, 3542], +] + +[[area.polygons]] +vertices = [ + [3019, 3542], + [3022, 3542], +] + +[[area.polygons]] +vertices = [[3007, 3543]] + +[[area.polygons]] +vertices = [ + [3019, 3543], + [3021, 3543], +] diff --git a/.data/raw-cache/map/area/impetuous.toml b/.data/raw-cache/map/area/impetuous.toml new file mode 100644 index 000000000..ea1acd3a3 --- /dev/null +++ b/.data/raw-cache/map/area/impetuous.toml @@ -0,0 +1,16 @@ +# Impetuous music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Impetuous. +# Bounds x 2560-2623, z 4288-4351; 4096 tiles as 1 rectangles. + +[[area]] +name = "Impetuous" +area_id = "area.impetuous" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 4288], + [2623, 4288], + [2623, 4351], + [2560, 4351], +] diff --git a/.data/raw-cache/map/area/in_between.toml b/.data/raw-cache/map/area/in_between.toml new file mode 100644 index 000000000..10335e745 --- /dev/null +++ b/.data/raw-cache/map/area/in_between.toml @@ -0,0 +1,16 @@ +# In Between music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: In Between. +# Bounds x 2496-2623, z 4928-4991; 8192 tiles as 1 rectangles. + +[[area]] +name = "In Between" +area_id = "area.in_between" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2496, 4928], + [2623, 4928], + [2623, 4991], + [2496, 4991], +] diff --git a/.data/raw-cache/map/area/in_the_brine.toml b/.data/raw-cache/map/area/in_the_brine.toml new file mode 100644 index 000000000..16e2ff2f5 --- /dev/null +++ b/.data/raw-cache/map/area/in_the_brine.toml @@ -0,0 +1,177 @@ +# In the Brine music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: In the Brine. +# Bounds x 1860-1914, z 3270-3322; 1519 tiles as 22 rectangles. + +[[area]] +name = "In the Brine" +area_id = "area.in_the_brine" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1881, 3270], + [1894, 3270], + [1894, 3279], + [1881, 3279], +] + +[[area.polygons]] +vertices = [ + [1892, 3280], + [1894, 3280], + [1894, 3287], + [1892, 3287], +] + +[[area.polygons]] +vertices = [ + [1899, 3280], + [1912, 3280], + [1912, 3304], + [1899, 3304], +] + +[[area.polygons]] +vertices = [ + [1886, 3285], + [1891, 3285], + [1891, 3287], + [1886, 3287], +] + +[[area.polygons]] +vertices = [ + [1860, 3286], + [1873, 3286], + [1873, 3299], + [1860, 3299], +] + +[[area.polygons]] +vertices = [ + [1888, 3288], + [1890, 3288], + [1890, 3303], + [1888, 3303], +] + +[[area.polygons]] +vertices = [ + [1881, 3291], + [1887, 3291], + [1887, 3303], + [1881, 3303], +] + +[[area.polygons]] +vertices = [ + [1891, 3291], + [1894, 3291], + [1894, 3303], + [1891, 3303], +] + +[[area.polygons]] +vertices = [ + [1874, 3292], + [1880, 3292], + [1880, 3295], + [1874, 3295], +] + +[[area.polygons]] +vertices = [ + [1895, 3292], + [1898, 3292], + [1898, 3294], + [1895, 3294], +] + +[[area.polygons]] +vertices = [ + [1870, 3300], + [1872, 3300], + [1872, 3311], + [1870, 3311], +] + +[[area.polygons]] +vertices = [ + [1882, 3304], + [1885, 3304], + [1885, 3320], + [1882, 3320], +] + +[[area.polygons]] +vertices = [ + [1907, 3305], + [1910, 3305], + [1910, 3322], + [1907, 3322], +] + +[[area.polygons]] +vertices = [ + [1862, 3306], + [1869, 3306], + [1869, 3308], + [1862, 3308], +] + +[[area.polygons]] +vertices = [ + [1878, 3309], + [1881, 3309], + [1881, 3320], + [1878, 3320], +] + +[[area.polygons]] +vertices = [ + [1886, 3309], + [1898, 3309], + [1898, 3320], + [1886, 3320], +] + +[[area.polygons]] +vertices = [ + [1902, 3309], + [1906, 3309], + [1906, 3322], + [1902, 3322], +] + +[[area.polygons]] +vertices = [ + [1911, 3309], + [1914, 3309], + [1914, 3322], + [1911, 3322], +] + +[[area.polygons]] +vertices = [ + [1871, 3312], + [1871, 3316], +] + +[[area.polygons]] +vertices = [ + [1866, 3314], + [1870, 3314], + [1870, 3316], + [1866, 3316], +] + +[[area.polygons]] +vertices = [ + [1872, 3314], + [1874, 3314], + [1874, 3316], + [1872, 3316], +] + +[[area.polygons]] +vertices = [[1875, 3315]] diff --git a/.data/raw-cache/map/area/in_the_clink.toml b/.data/raw-cache/map/area/in_the_clink.toml new file mode 100644 index 000000000..c25f505ba --- /dev/null +++ b/.data/raw-cache/map/area/in_the_clink.toml @@ -0,0 +1,16 @@ +# In the Clink music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: In the Clink. +# Bounds x 2048-2111, z 4416-4479; 4096 tiles as 1 rectangles. + +[[area]] +name = "In the Clink" +area_id = "area.in_the_clink" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2048, 4416], + [2111, 4416], + [2111, 4479], + [2048, 4479], +] diff --git a/.data/raw-cache/map/area/in_the_manor.toml b/.data/raw-cache/map/area/in_the_manor.toml new file mode 100644 index 000000000..91754c33a --- /dev/null +++ b/.data/raw-cache/map/area/in_the_manor.toml @@ -0,0 +1,410 @@ +# In the Manor music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: In the Manor. +# Bounds x 2528-2621, z 3037-3074; 2112 tiles as 62 rectangles. + +[[area]] +name = "In the Manor" +area_id = "area.in_the_manor" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2580, 3037], + [2583, 3037], + [2583, 3070], + [2580, 3070], +] + +[[area.polygons]] +vertices = [ + [2579, 3038], + [2579, 3072], +] + +[[area.polygons]] +vertices = [ + [2584, 3038], + [2584, 3070], +] + +[[area.polygons]] +vertices = [ + [2578, 3039], + [2578, 3072], +] + +[[area.polygons]] +vertices = [ + [2585, 3039], + [2585, 3070], +] + +[[area.polygons]] +vertices = [ + [2577, 3040], + [2577, 3072], +] + +[[area.polygons]] +vertices = [ + [2565, 3041], + [2569, 3041], + [2569, 3072], + [2565, 3072], +] + +[[area.polygons]] +vertices = [ + [2576, 3041], + [2576, 3072], +] + +[[area.polygons]] +vertices = [ + [2564, 3042], + [2564, 3072], +] + +[[area.polygons]] +vertices = [ + [2570, 3042], + [2570, 3072], +] + +[[area.polygons]] +vertices = [ + [2575, 3042], + [2575, 3072], +] + +[[area.polygons]] +vertices = [ + [2586, 3042], + [2588, 3042], + [2588, 3070], + [2586, 3070], +] + +[[area.polygons]] +vertices = [ + [2560, 3043], + [2563, 3043], + [2563, 3072], + [2560, 3072], +] + +[[area.polygons]] +vertices = [ + [2571, 3043], + [2574, 3043], + [2574, 3072], + [2571, 3072], +] + +[[area.polygons]] +vertices = [ + [2589, 3043], + [2589, 3070], +] + +[[area.polygons]] +vertices = [ + [2590, 3045], + [2591, 3045], + [2591, 3071], + [2590, 3071], +] + +[[area.polygons]] +vertices = [ + [2592, 3046], + [2592, 3072], +] + +[[area.polygons]] +vertices = [ + [2593, 3047], + [2594, 3047], + [2594, 3072], + [2593, 3072], +] + +[[area.polygons]] +vertices = [ + [2600, 3047], + [2600, 3072], +] + +[[area.polygons]] +vertices = [ + [2595, 3048], + [2599, 3048], + [2599, 3072], + [2595, 3072], +] + +[[area.polygons]] +vertices = [ + [2601, 3048], + [2602, 3048], + [2602, 3072], + [2601, 3072], +] + +[[area.polygons]] +vertices = [ + [2603, 3049], + [2604, 3049], + [2604, 3072], + [2603, 3072], +] + +[[area.polygons]] +vertices = [ + [2605, 3050], + [2605, 3072], +] + +[[area.polygons]] +vertices = [ + [2538, 3051], + [2542, 3051], + [2542, 3072], + [2538, 3072], +] + +[[area.polygons]] +vertices = [ + [2537, 3052], + [2537, 3074], +] + +[[area.polygons]] +vertices = [ + [2543, 3052], + [2543, 3072], +] + +[[area.polygons]] +vertices = [ + [2606, 3052], + [2607, 3052], + [2607, 3072], + [2606, 3072], +] + +[[area.polygons]] +vertices = [ + [2536, 3053], + [2536, 3074], +] + +[[area.polygons]] +vertices = [ + [2544, 3053], + [2544, 3072], +] + +[[area.polygons]] +vertices = [ + [2608, 3053], + [2608, 3072], +] + +[[area.polygons]] +vertices = [ + [2535, 3054], + [2535, 3073], +] + +[[area.polygons]] +vertices = [ + [2545, 3054], + [2546, 3054], + [2546, 3072], + [2545, 3072], +] + +[[area.polygons]] +vertices = [ + [2609, 3054], + [2609, 3072], +] + +[[area.polygons]] +vertices = [ + [2534, 3055], + [2534, 3072], +] + +[[area.polygons]] +vertices = [ + [2547, 3055], + [2547, 3072], +] + +[[area.polygons]] +vertices = [ + [2610, 3055], + [2611, 3055], + [2611, 3072], + [2610, 3072], +] + +[[area.polygons]] +vertices = [ + [2533, 3056], + [2533, 3071], +] + +[[area.polygons]] +vertices = [ + [2548, 3056], + [2550, 3056], + [2550, 3072], + [2548, 3072], +] + +[[area.polygons]] +vertices = [ + [2556, 3056], + [2559, 3056], + [2559, 3072], + [2556, 3072], +] + +[[area.polygons]] +vertices = [ + [2612, 3056], + [2612, 3057], +] + +[[area.polygons]] +vertices = [ + [2532, 3057], + [2532, 3070], +] + +[[area.polygons]] +vertices = [ + [2551, 3057], + [2551, 3072], +] + +[[area.polygons]] +vertices = [ + [2555, 3057], + [2555, 3072], +] + +[[area.polygons]] +vertices = [ + [2531, 3058], + [2531, 3069], +] + +[[area.polygons]] +vertices = [ + [2552, 3058], + [2554, 3058], + [2554, 3072], + [2552, 3072], +] + +[[area.polygons]] +vertices = [ + [2612, 3059], + [2612, 3072], +] + +[[area.polygons]] +vertices = [ + [2530, 3061], + [2530, 3068], +] + +[[area.polygons]] +vertices = [ + [2613, 3061], + [2613, 3072], +] + +[[area.polygons]] +vertices = [ + [2529, 3062], + [2529, 3067], +] + +[[area.polygons]] +vertices = [ + [2614, 3062], + [2614, 3072], +] + +[[area.polygons]] +vertices = [ + [2528, 3063], + [2528, 3066], +] + +[[area.polygons]] +vertices = [ + [2615, 3063], + [2616, 3063], + [2616, 3072], + [2615, 3072], +] + +[[area.polygons]] +vertices = [ + [2617, 3064], + [2617, 3072], +] + +[[area.polygons]] +vertices = [ + [2618, 3065], + [2618, 3072], +] + +[[area.polygons]] +vertices = [ + [2619, 3066], + [2619, 3072], +] + +[[area.polygons]] +vertices = [ + [2620, 3068], + [2620, 3072], +] + +[[area.polygons]] +vertices = [ + [2621, 3069], + [2621, 3072], +] + +[[area.polygons]] +vertices = [ + [2580, 3071], + [2581, 3071], +] + +[[area.polygons]] +vertices = [[2580, 3072]] + +[[area.polygons]] +vertices = [[2591, 3072]] + +[[area.polygons]] +vertices = [ + [2538, 3073], + [2540, 3073], +] + +[[area.polygons]] +vertices = [ + [2538, 3074], + [2539, 3074], +] diff --git a/.data/raw-cache/map/area/in_the_pits.toml b/.data/raw-cache/map/area/in_the_pits.toml new file mode 100644 index 000000000..3c0dcb522 --- /dev/null +++ b/.data/raw-cache/map/area/in_the_pits.toml @@ -0,0 +1,264 @@ +# In the Pits music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: In the Pits. +# Bounds x 2432-2943, z 5120-9599; 7314 tiles as 39 rectangles. + +[[area]] +name = "In the Pits" +area_id = "area.in_the_pits" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2456, 5120], + [2457, 5120], + [2457, 5183], + [2456, 5183], +] + +[[area.polygons]] +vertices = [ + [2435, 5121], + [2436, 5121], + [2436, 5183], + [2435, 5183], +] + +[[area.polygons]] +vertices = [ + [2449, 5121], + [2451, 5121], + [2451, 5183], + [2449, 5183], +] + +[[area.polygons]] +vertices = [ + [2455, 5121], + [2455, 5183], +] + +[[area.polygons]] +vertices = [ + [2458, 5121], + [2459, 5121], + [2459, 5183], + [2458, 5183], +] + +[[area.polygons]] +vertices = [ + [2434, 5122], + [2434, 5183], +] + +[[area.polygons]] +vertices = [ + [2437, 5122], + [2437, 5183], +] + +[[area.polygons]] +vertices = [ + [2447, 5122], + [2448, 5122], + [2448, 5183], + [2447, 5183], +] + +[[area.polygons]] +vertices = [ + [2452, 5122], + [2454, 5122], + [2454, 5183], + [2452, 5183], +] + +[[area.polygons]] +vertices = [ + [2460, 5122], + [2464, 5122], + [2464, 5183], + [2460, 5183], +] + +[[area.polygons]] +vertices = [ + [2432, 5123], + [2433, 5123], + [2433, 5183], + [2432, 5183], +] + +[[area.polygons]] +vertices = [ + [2438, 5123], + [2446, 5123], + [2446, 5183], + [2438, 5183], +] + +[[area.polygons]] +vertices = [ + [2465, 5133], + [2465, 5183], +] + +[[area.polygons]] +vertices = [ + [2466, 5134], + [2466, 5183], +] + +[[area.polygons]] +vertices = [ + [2467, 5135], + [2467, 5183], +] + +[[area.polygons]] +vertices = [ + [2468, 5136], + [2468, 5183], +] + +[[area.polygons]] +vertices = [ + [2469, 5137], + [2469, 5183], +] + +[[area.polygons]] +vertices = [ + [2470, 5138], + [2470, 5183], +] + +[[area.polygons]] +vertices = [ + [2473, 5138], + [2474, 5138], + [2474, 5183], + [2473, 5183], +] + +[[area.polygons]] +vertices = [ + [2471, 5139], + [2472, 5139], + [2472, 5183], + [2471, 5183], +] + +[[area.polygons]] +vertices = [ + [2475, 5139], + [2477, 5139], + [2477, 5183], + [2475, 5183], +] + +[[area.polygons]] +vertices = [ + [2478, 5140], + [2478, 5183], +] + +[[area.polygons]] +vertices = [ + [2479, 5141], + [2479, 5183], +] + +[[area.polygons]] +vertices = [ + [2480, 5142], + [2480, 5183], +] + +[[area.polygons]] +vertices = [ + [2481, 5143], + [2481, 5183], +] + +[[area.polygons]] +vertices = [ + [2482, 5144], + [2482, 5183], +] + +[[area.polygons]] +vertices = [ + [2483, 5145], + [2483, 5183], +] + +[[area.polygons]] +vertices = [ + [2484, 5146], + [2484, 5183], +] + +[[area.polygons]] +vertices = [ + [2485, 5147], + [2485, 5183], +] + +[[area.polygons]] +vertices = [ + [2486, 5148], + [2488, 5148], + [2488, 5183], + [2486, 5183], +] + +[[area.polygons]] +vertices = [ + [2489, 5149], + [2489, 5183], +] + +[[area.polygons]] +vertices = [ + [2490, 5150], + [2490, 5183], +] + +[[area.polygons]] +vertices = [ + [2491, 5151], + [2491, 5183], +] + +[[area.polygons]] +vertices = [ + [2492, 5152], + [2492, 5162], +] + +[[area.polygons]] +vertices = [ + [2493, 5153], + [2493, 5161], +] + +[[area.polygons]] +vertices = [[2494, 5157]] + +[[area.polygons]] +vertices = [ + [2492, 5172], + [2492, 5178], +] + +[[area.polygons]] +vertices = [[2493, 5174]] + +[[area.polygons]] +vertices = [ + [2880, 9536], + [2943, 9536], + [2943, 9599], + [2880, 9599], +] diff --git a/.data/raw-cache/map/area/inferno.toml b/.data/raw-cache/map/area/inferno.toml new file mode 100644 index 000000000..2b53d28bb --- /dev/null +++ b/.data/raw-cache/map/area/inferno.toml @@ -0,0 +1,16 @@ +# Inferno music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Inferno. +# Bounds x 2240-2303, z 5312-5375; 4096 tiles as 1 rectangles. + +[[area]] +name = "Inferno" +area_id = "area.inferno" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2240, 5312], + [2303, 5312], + [2303, 5375], + [2240, 5375], +] diff --git a/.data/raw-cache/map/area/insect_queen.toml b/.data/raw-cache/map/area/insect_queen.toml new file mode 100644 index 000000000..4590cd8b5 --- /dev/null +++ b/.data/raw-cache/map/area/insect_queen.toml @@ -0,0 +1,32 @@ +# Insect Queen music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Insect Queen. +# Bounds x 3264-3519, z 9472-9599; 16384 tiles as 3 rectangles. + +[[area]] +name = "Insect Queen" +area_id = "area.insect_queen" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 9472], + [3391, 9472], + [3391, 9535], + [3264, 9535], +] + +[[area.polygons]] +vertices = [ + [3456, 9472], + [3519, 9472], + [3519, 9535], + [3456, 9535], +] + +[[area.polygons]] +vertices = [ + [3264, 9536], + [3327, 9536], + [3327, 9599], + [3264, 9599], +] diff --git a/.data/raw-cache/map/area/into_the_abyss.toml b/.data/raw-cache/map/area/into_the_abyss.toml new file mode 100644 index 000000000..10bcea60a --- /dev/null +++ b/.data/raw-cache/map/area/into_the_abyss.toml @@ -0,0 +1,56 @@ +# Into the Abyss music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Into the Abyss. +# Bounds x 2944-3135, z 4736-4927; 18432 tiles as 6 rectangles. + +[[area]] +name = "Into the Abyss" +area_id = "area.into_the_abyss" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2944, 4736], + [3135, 4736], + [3135, 4751], + [2944, 4751], +] + +[[area.polygons]] +vertices = [ + [2984, 4752], + [3095, 4752], + [3095, 4815], + [2984, 4815], +] + +[[area.polygons]] +vertices = [ + [2944, 4800], + [2983, 4800], + [2983, 4807], + [2944, 4807], +] + +[[area.polygons]] +vertices = [ + [3096, 4800], + [3135, 4800], + [3135, 4815], + [3096, 4815], +] + +[[area.polygons]] +vertices = [ + [2976, 4808], + [2983, 4808], + [2983, 4815], + [2976, 4815], +] + +[[area.polygons]] +vertices = [ + [3008, 4816], + [3071, 4816], + [3071, 4927], + [3008, 4927], +] diff --git a/.data/raw-cache/map/area/into_the_tombs.toml b/.data/raw-cache/map/area/into_the_tombs.toml new file mode 100644 index 000000000..b32561123 --- /dev/null +++ b/.data/raw-cache/map/area/into_the_tombs.toml @@ -0,0 +1,16 @@ +# Into the Tombs music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Into the Tombs. +# Bounds x 3328-3391, z 9088-9151; 4096 tiles as 1 rectangles. + +[[area]] +name = "Into the Tombs" +area_id = "area.into_the_tombs" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3328, 9088], + [3391, 9088], + [3391, 9151], + [3328, 9151], +] diff --git a/.data/raw-cache/map/area/intrepid.toml b/.data/raw-cache/map/area/intrepid.toml new file mode 100644 index 000000000..f05f45019 --- /dev/null +++ b/.data/raw-cache/map/area/intrepid.toml @@ -0,0 +1,16 @@ +# Intrepid music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Intrepid. +# Bounds x 2304-2367, z 9792-9919; 8192 tiles as 1 rectangles. + +[[area]] +name = "Intrepid" +area_id = "area.intrepid" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2304, 9792], + [2367, 9792], + [2367, 9919], + [2304, 9919], +] diff --git a/.data/raw-cache/map/area/invader.toml b/.data/raw-cache/map/area/invader.toml new file mode 100644 index 000000000..b1144247d --- /dev/null +++ b/.data/raw-cache/map/area/invader.toml @@ -0,0 +1,48 @@ +# Invader music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Invader. +# Bounds x 2944-3135, z 4752-4863; 10176 tiles as 5 rectangles. + +[[area]] +name = "Invader" +area_id = "area.invader" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2944, 4752], + [2983, 4752], + [2983, 4799], + [2944, 4799], +] + +[[area.polygons]] +vertices = [ + [3096, 4752], + [3135, 4752], + [3135, 4799], + [3096, 4799], +] + +[[area.polygons]] +vertices = [ + [2944, 4808], + [2975, 4808], + [2975, 4863], + [2944, 4863], +] + +[[area.polygons]] +vertices = [ + [2976, 4816], + [3007, 4816], + [3007, 4863], + [2976, 4863], +] + +[[area.polygons]] +vertices = [ + [3072, 4817], + [3135, 4817], + [3135, 4863], + [3072, 4863], +] diff --git a/.data/raw-cache/map/area/isafdar.toml b/.data/raw-cache/map/area/isafdar.toml new file mode 100644 index 000000000..e1a7236da --- /dev/null +++ b/.data/raw-cache/map/area/isafdar.toml @@ -0,0 +1,731 @@ +# Isafdar music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Riverside; Breeze; Elven Mist; Meridian; Forest; Woodland. +# Bounds x 2136-2335, z 3110-3257; 21164 tiles as 117 rectangles. + +[[area]] +name = "Isafdar" +area_id = "area.isafdar" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2157, 3110], + [2290, 3110], + [2290, 3145], + [2157, 3145], +] + +[[area.polygons]] +vertices = [ + [2156, 3111], + [2156, 3145], +] + +[[area.polygons]] +vertices = [ + [2291, 3111], + [2292, 3111], + [2292, 3252], + [2291, 3252], +] + +[[area.polygons]] +vertices = [ + [2155, 3112], + [2155, 3144], +] + +[[area.polygons]] +vertices = [ + [2293, 3112], + [2294, 3112], + [2294, 3252], + [2293, 3252], +] + +[[area.polygons]] +vertices = [ + [2154, 3113], + [2154, 3144], +] + +[[area.polygons]] +vertices = [ + [2295, 3113], + [2296, 3113], + [2296, 3252], + [2295, 3252], +] + +[[area.polygons]] +vertices = [ + [2153, 3114], + [2153, 3144], +] + +[[area.polygons]] +vertices = [ + [2297, 3114], + [2305, 3114], + [2305, 3252], + [2297, 3252], +] + +[[area.polygons]] +vertices = [ + [2152, 3115], + [2152, 3117], +] + +[[area.polygons]] +vertices = [[2151, 3116]] + +[[area.polygons]] +vertices = [ + [2141, 3121], + [2152, 3121], + [2152, 3122], + [2141, 3122], +] + +[[area.polygons]] +vertices = [ + [2142, 3123], + [2142, 3127], +] + +[[area.polygons]] +vertices = [ + [2146, 3123], + [2152, 3123], +] + +[[area.polygons]] +vertices = [ + [2136, 3125], + [2141, 3125], + [2141, 3127], + [2136, 3127], +] + +[[area.polygons]] +vertices = [ + [2143, 3125], + [2144, 3125], + [2144, 3127], + [2143, 3127], +] + +[[area.polygons]] +vertices = [[2145, 3126]] + +[[area.polygons]] +vertices = [ + [2152, 3126], + [2152, 3144], +] + +[[area.polygons]] +vertices = [ + [2151, 3127], + [2151, 3144], +] + +[[area.polygons]] +vertices = [ + [2150, 3129], + [2150, 3143], +] + +[[area.polygons]] +vertices = [ + [2148, 3130], + [2149, 3130], + [2149, 3142], + [2148, 3142], +] + +[[area.polygons]] +vertices = [ + [2147, 3131], + [2147, 3141], +] + +[[area.polygons]] +vertices = [ + [2146, 3132], + [2146, 3141], +] + +[[area.polygons]] +vertices = [ + [2145, 3135], + [2145, 3140], +] + +[[area.polygons]] +vertices = [ + [2158, 3146], + [2290, 3146], +] + +[[area.polygons]] +vertices = [ + [2160, 3147], + [2290, 3147], +] + +[[area.polygons]] +vertices = [ + [2161, 3148], + [2290, 3148], +] + +[[area.polygons]] +vertices = [ + [2163, 3149], + [2290, 3149], +] + +[[area.polygons]] +vertices = [ + [2164, 3150], + [2290, 3150], + [2290, 3151], + [2164, 3151], +] + +[[area.polygons]] +vertices = [ + [2165, 3152], + [2290, 3152], +] + +[[area.polygons]] +vertices = [ + [2166, 3153], + [2290, 3153], + [2290, 3154], + [2166, 3154], +] + +[[area.polygons]] +vertices = [ + [2167, 3155], + [2290, 3155], + [2290, 3229], + [2167, 3229], +] + +[[area.polygons]] +vertices = [ + [2166, 3158], + [2166, 3161], +] + +[[area.polygons]] +vertices = [[2165, 3159]] + +[[area.polygons]] +vertices = [ + [2166, 3167], + [2166, 3248], +] + +[[area.polygons]] +vertices = [ + [2165, 3168], + [2165, 3179], +] + +[[area.polygons]] +vertices = [ + [2164, 3170], + [2164, 3177], +] + +[[area.polygons]] +vertices = [ + [2163, 3171], + [2163, 3176], +] + +[[area.polygons]] +vertices = [ + [2162, 3173], + [2162, 3175], +] + +[[area.polygons]] +vertices = [ + [2165, 3183], + [2165, 3246], +] + +[[area.polygons]] +vertices = [ + [2164, 3185], + [2164, 3198], +] + +[[area.polygons]] +vertices = [ + [2163, 3187], + [2163, 3196], +] + +[[area.polygons]] +vertices = [ + [2162, 3189], + [2162, 3194], +] + +[[area.polygons]] +vertices = [ + [2161, 3191], + [2161, 3192], +] + +[[area.polygons]] +vertices = [ + [2306, 3200], + [2335, 3200], + [2335, 3235], + [2306, 3235], +] + +[[area.polygons]] +vertices = [[2164, 3202]] + +[[area.polygons]] +vertices = [ + [2164, 3209], + [2164, 3234], +] + +[[area.polygons]] +vertices = [ + [2163, 3210], + [2163, 3234], +] + +[[area.polygons]] +vertices = [ + [2162, 3211], + [2162, 3232], +] + +[[area.polygons]] +vertices = [ + [2161, 3212], + [2161, 3223], +] + +[[area.polygons]] +vertices = [ + [2160, 3213], + [2160, 3222], +] + +[[area.polygons]] +vertices = [ + [2159, 3214], + [2159, 3221], +] + +[[area.polygons]] +vertices = [ + [2158, 3215], + [2158, 3220], +] + +[[area.polygons]] +vertices = [ + [2157, 3216], + [2157, 3218], +] + +[[area.polygons]] +vertices = [ + [2161, 3226], + [2161, 3230], +] + +[[area.polygons]] +vertices = [[2160, 3228]] + +[[area.polygons]] +vertices = [ + [2167, 3230], + [2226, 3230], +] + +[[area.polygons]] +vertices = [ + [2232, 3230], + [2290, 3230], +] + +[[area.polygons]] +vertices = [ + [2167, 3231], + [2225, 3231], +] + +[[area.polygons]] +vertices = [ + [2233, 3231], + [2290, 3231], +] + +[[area.polygons]] +vertices = [ + [2167, 3232], + [2188, 3232], +] + +[[area.polygons]] +vertices = [ + [2197, 3232], + [2224, 3232], +] + +[[area.polygons]] +vertices = [ + [2234, 3232], + [2290, 3232], +] + +[[area.polygons]] +vertices = [ + [2167, 3233], + [2187, 3233], +] + +[[area.polygons]] +vertices = [ + [2198, 3233], + [2224, 3233], +] + +[[area.polygons]] +vertices = [ + [2235, 3233], + [2290, 3233], +] + +[[area.polygons]] +vertices = [ + [2167, 3234], + [2186, 3234], +] + +[[area.polygons]] +vertices = [ + [2199, 3234], + [2224, 3234], +] + +[[area.polygons]] +vertices = [ + [2236, 3234], + [2290, 3234], +] + +[[area.polygons]] +vertices = [ + [2167, 3235], + [2185, 3235], +] + +[[area.polygons]] +vertices = [ + [2200, 3235], + [2224, 3235], + [2224, 3236], + [2200, 3236], +] + +[[area.polygons]] +vertices = [ + [2237, 3235], + [2290, 3235], +] + +[[area.polygons]] +vertices = [ + [2167, 3236], + [2184, 3236], +] + +[[area.polygons]] +vertices = [ + [2238, 3236], + [2290, 3236], +] + +[[area.polygons]] +vertices = [ + [2306, 3236], + [2334, 3236], +] + +[[area.polygons]] +vertices = [ + [2167, 3237], + [2183, 3237], +] + +[[area.polygons]] +vertices = [ + [2200, 3237], + [2206, 3237], +] + +[[area.polygons]] +vertices = [ + [2213, 3237], + [2224, 3237], + [2224, 3239], + [2213, 3239], +] + +[[area.polygons]] +vertices = [ + [2239, 3237], + [2290, 3237], +] + +[[area.polygons]] +vertices = [ + [2306, 3237], + [2333, 3237], +] + +[[area.polygons]] +vertices = [ + [2164, 3238], + [2164, 3244], +] + +[[area.polygons]] +vertices = [ + [2167, 3238], + [2182, 3238], +] + +[[area.polygons]] +vertices = [ + [2200, 3238], + [2204, 3238], +] + +[[area.polygons]] +vertices = [ + [2242, 3238], + [2290, 3238], +] + +[[area.polygons]] +vertices = [ + [2306, 3238], + [2332, 3238], +] + +[[area.polygons]] +vertices = [ + [2167, 3239], + [2181, 3239], +] + +[[area.polygons]] +vertices = [ + [2201, 3239], + [2202, 3239], +] + +[[area.polygons]] +vertices = [ + [2243, 3239], + [2290, 3239], +] + +[[area.polygons]] +vertices = [ + [2306, 3239], + [2331, 3239], +] + +[[area.polygons]] +vertices = [ + [2167, 3240], + [2180, 3240], + [2180, 3254], + [2167, 3254], +] + +[[area.polygons]] +vertices = [ + [2213, 3240], + [2223, 3240], +] + +[[area.polygons]] +vertices = [ + [2244, 3240], + [2290, 3240], +] + +[[area.polygons]] +vertices = [ + [2306, 3240], + [2330, 3240], +] + +[[area.polygons]] +vertices = [ + [2163, 3241], + [2163, 3243], +] + +[[area.polygons]] +vertices = [ + [2213, 3241], + [2222, 3241], +] + +[[area.polygons]] +vertices = [ + [2245, 3241], + [2290, 3241], +] + +[[area.polygons]] +vertices = [ + [2306, 3241], + [2329, 3241], +] + +[[area.polygons]] +vertices = [ + [2213, 3242], + [2221, 3242], +] + +[[area.polygons]] +vertices = [ + [2246, 3242], + [2290, 3242], +] + +[[area.polygons]] +vertices = [ + [2306, 3242], + [2328, 3242], +] + +[[area.polygons]] +vertices = [ + [2213, 3243], + [2220, 3243], +] + +[[area.polygons]] +vertices = [ + [2247, 3243], + [2290, 3243], +] + +[[area.polygons]] +vertices = [ + [2306, 3243], + [2327, 3243], +] + +[[area.polygons]] +vertices = [ + [2213, 3244], + [2219, 3244], +] + +[[area.polygons]] +vertices = [ + [2248, 3244], + [2290, 3244], +] + +[[area.polygons]] +vertices = [ + [2306, 3244], + [2326, 3244], +] + +[[area.polygons]] +vertices = [ + [2213, 3245], + [2218, 3245], +] + +[[area.polygons]] +vertices = [ + [2262, 3245], + [2290, 3245], + [2290, 3252], + [2262, 3252], +] + +[[area.polygons]] +vertices = [ + [2306, 3245], + [2325, 3245], +] + +[[area.polygons]] +vertices = [ + [2306, 3246], + [2324, 3246], +] + +[[area.polygons]] +vertices = [ + [2306, 3247], + [2323, 3247], +] + +[[area.polygons]] +vertices = [ + [2306, 3248], + [2322, 3248], +] + +[[area.polygons]] +vertices = [ + [2306, 3249], + [2321, 3249], +] + +[[area.polygons]] +vertices = [ + [2181, 3250], + [2181, 3256], +] + +[[area.polygons]] +vertices = [ + [2182, 3252], + [2182, 3255], +] + +[[area.polygons]] +vertices = [ + [2168, 3255], + [2180, 3255], + [2180, 3256], + [2168, 3256], +] + +[[area.polygons]] +vertices = [ + [2171, 3257], + [2180, 3257], +] diff --git a/.data/raw-cache/map/area/island_life.toml b/.data/raw-cache/map/area/island_life.toml new file mode 100644 index 000000000..ac0e401bb --- /dev/null +++ b/.data/raw-cache/map/area/island_life.toml @@ -0,0 +1,160 @@ +# Island Life music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Island Life. +# Bounds x 1747-1776, z 2647-2679; 560 tiles as 24 rectangles. + +[[area]] +name = "Island Life" +area_id = "area.island_life" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1758, 2647], + [1760, 2647], + [1760, 2679], + [1758, 2679], +] + +[[area.polygons]] +vertices = [ + [1765, 2647], + [1765, 2660], +] + +[[area.polygons]] +vertices = [ + [1757, 2648], + [1757, 2679], +] + +[[area.polygons]] +vertices = [ + [1761, 2648], + [1764, 2648], + [1764, 2676], + [1761, 2676], +] + +[[area.polygons]] +vertices = [ + [1766, 2648], + [1766, 2658], +] + +[[area.polygons]] +vertices = [ + [1755, 2649], + [1756, 2649], + [1756, 2679], + [1755, 2679], +] + +[[area.polygons]] +vertices = [ + [1767, 2649], + [1768, 2649], + [1768, 2657], + [1767, 2657], +] + +[[area.polygons]] +vertices = [ + [1753, 2650], + [1754, 2650], + [1754, 2679], + [1753, 2679], +] + +[[area.polygons]] +vertices = [ + [1769, 2650], + [1775, 2650], + [1775, 2654], + [1769, 2654], +] + +[[area.polygons]] +vertices = [ + [1776, 2651], + [1776, 2653], +] + +[[area.polygons]] +vertices = [ + [1752, 2652], + [1752, 2678], +] + +[[area.polygons]] +vertices = [ + [1751, 2654], + [1751, 2677], +] + +[[area.polygons]] +vertices = [ + [1769, 2655], + [1772, 2655], +] + +[[area.polygons]] +vertices = [[1769, 2656]] + +[[area.polygons]] +vertices = [ + [1750, 2657], + [1750, 2658], +] + +[[area.polygons]] +vertices = [ + [1750, 2662], + [1750, 2676], +] + +[[area.polygons]] +vertices = [ + [1749, 2663], + [1749, 2674], +] + +[[area.polygons]] +vertices = [ + [1748, 2666], + [1748, 2671], +] + +[[area.polygons]] +vertices = [ + [1747, 2668], + [1747, 2670], +] + +[[area.polygons]] +vertices = [ + [1765, 2669], + [1766, 2669], + [1766, 2671], + [1765, 2671], +] + +[[area.polygons]] +vertices = [ + [1765, 2672], + [1765, 2675], +] + +[[area.polygons]] +vertices = [ + [1761, 2677], + [1763, 2677], +] + +[[area.polygons]] +vertices = [ + [1761, 2678], + [1762, 2678], +] + +[[area.polygons]] +vertices = [[1761, 2679]] diff --git a/.data/raw-cache/map/area/island_of_the_trolls.toml b/.data/raw-cache/map/area/island_of_the_trolls.toml new file mode 100644 index 000000000..d644fa754 --- /dev/null +++ b/.data/raw-cache/map/area/island_of_the_trolls.toml @@ -0,0 +1,1470 @@ +# Island of the Trolls music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Island of the Trolls. +# Bounds x 2307-2424, z 3821-3901; 5716 tiles as 236 rectangles. + +[[area]] +name = "Island of the Trolls" +area_id = "area.island_of_the_trolls" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2356, 3821], + [2359, 3821], + [2359, 3837], + [2356, 3837], +] + +[[area.polygons]] +vertices = [ + [2353, 3822], + [2355, 3822], + [2355, 3873], + [2353, 3873], +] + +[[area.polygons]] +vertices = [ + [2360, 3822], + [2360, 3837], +] + +[[area.polygons]] +vertices = [ + [2329, 3823], + [2330, 3823], + [2330, 3866], + [2329, 3866], +] + +[[area.polygons]] +vertices = [ + [2352, 3823], + [2352, 3877], +] + +[[area.polygons]] +vertices = [ + [2326, 3824], + [2328, 3824], + [2328, 3868], + [2326, 3868], +] + +[[area.polygons]] +vertices = [ + [2331, 3824], + [2332, 3824], + [2332, 3866], + [2331, 3866], +] + +[[area.polygons]] +vertices = [ + [2361, 3824], + [2362, 3824], + [2362, 3837], + [2361, 3837], +] + +[[area.polygons]] +vertices = [ + [2325, 3825], + [2325, 3869], +] + +[[area.polygons]] +vertices = [ + [2333, 3825], + [2333, 3866], +] + +[[area.polygons]] +vertices = [ + [2351, 3825], + [2351, 3884], +] + +[[area.polygons]] +vertices = [ + [2363, 3825], + [2363, 3837], +] + +[[area.polygons]] +vertices = [ + [2323, 3826], + [2324, 3826], + [2324, 3869], + [2323, 3869], +] + +[[area.polygons]] +vertices = [ + [2334, 3826], + [2336, 3826], + [2336, 3865], + [2334, 3865], +] + +[[area.polygons]] +vertices = [ + [2347, 3826], + [2350, 3826], + [2350, 3900], + [2347, 3900], +] + +[[area.polygons]] +vertices = [ + [2364, 3826], + [2364, 3836], +] + +[[area.polygons]] +vertices = [ + [2322, 3827], + [2322, 3869], +] + +[[area.polygons]] +vertices = [ + [2337, 3827], + [2337, 3864], +] + +[[area.polygons]] +vertices = [ + [2346, 3827], + [2346, 3901], +] + +[[area.polygons]] +vertices = [ + [2365, 3827], + [2365, 3835], +] + +[[area.polygons]] +vertices = [ + [2370, 3827], + [2370, 3838], +] + +[[area.polygons]] +vertices = [ + [2375, 3827], + [2377, 3827], + [2377, 3838], + [2375, 3838], +] + +[[area.polygons]] +vertices = [ + [2320, 3828], + [2321, 3828], + [2321, 3868], + [2320, 3868], +] + +[[area.polygons]] +vertices = [ + [2338, 3828], + [2338, 3865], +] + +[[area.polygons]] +vertices = [ + [2341, 3828], + [2345, 3828], + [2345, 3869], + [2341, 3869], +] + +[[area.polygons]] +vertices = [ + [2368, 3828], + [2369, 3828], + [2369, 3835], + [2368, 3835], +] + +[[area.polygons]] +vertices = [ + [2371, 3828], + [2371, 3838], +] + +[[area.polygons]] +vertices = [ + [2374, 3828], + [2374, 3837], +] + +[[area.polygons]] +vertices = [ + [2378, 3828], + [2378, 3853], +] + +[[area.polygons]] +vertices = [ + [2319, 3829], + [2319, 3867], +] + +[[area.polygons]] +vertices = [ + [2340, 3829], + [2340, 3868], +] + +[[area.polygons]] +vertices = [ + [2366, 3829], + [2367, 3829], + [2367, 3834], + [2366, 3834], +] + +[[area.polygons]] +vertices = [ + [2372, 3829], + [2373, 3829], + [2373, 3838], + [2372, 3838], +] + +[[area.polygons]] +vertices = [ + [2313, 3830], + [2318, 3830], + [2318, 3840], + [2313, 3840], +] + +[[area.polygons]] +vertices = [ + [2339, 3830], + [2339, 3866], +] + +[[area.polygons]] +vertices = [ + [2379, 3830], + [2379, 3839], +] + +[[area.polygons]] +vertices = [ + [2311, 3831], + [2312, 3831], + [2312, 3839], + [2311, 3839], +] + +[[area.polygons]] +vertices = [ + [2380, 3831], + [2380, 3833], +] + +[[area.polygons]] +vertices = [ + [2310, 3832], + [2310, 3838], +] + +[[area.polygons]] +vertices = [ + [2309, 3833], + [2309, 3837], +] + +[[area.polygons]] +vertices = [ + [2308, 3834], + [2308, 3836], +] + +[[area.polygons]] +vertices = [[2307, 3835]] + +[[area.polygons]] +vertices = [ + [2369, 3836], + [2369, 3837], +] + +[[area.polygons]] +vertices = [ + [2356, 3838], + [2358, 3838], + [2358, 3839], + [2356, 3839], +] + +[[area.polygons]] +vertices = [ + [2376, 3839], + [2377, 3839], + [2377, 3840], + [2376, 3840], +] + +[[area.polygons]] +vertices = [[2312, 3840]] + +[[area.polygons]] +vertices = [ + [2356, 3840], + [2357, 3840], +] + +[[area.polygons]] +vertices = [ + [2314, 3841], + [2318, 3841], + [2318, 3861], + [2314, 3861], +] + +[[area.polygons]] +vertices = [ + [2419, 3842], + [2420, 3842], + [2420, 3856], + [2419, 3856], +] + +[[area.polygons]] +vertices = [ + [2396, 3843], + [2396, 3874], +] + +[[area.polygons]] +vertices = [ + [2405, 3843], + [2407, 3843], + [2407, 3872], + [2405, 3872], +] + +[[area.polygons]] +vertices = [ + [2411, 3843], + [2416, 3843], + [2416, 3855], + [2411, 3855], +] + +[[area.polygons]] +vertices = [ + [2394, 3844], + [2395, 3844], + [2395, 3873], + [2394, 3873], +] + +[[area.polygons]] +vertices = [ + [2397, 3844], + [2397, 3861], +] + +[[area.polygons]] +vertices = [ + [2404, 3844], + [2404, 3871], +] + +[[area.polygons]] +vertices = [ + [2408, 3844], + [2408, 3872], +] + +[[area.polygons]] +vertices = [ + [2410, 3844], + [2410, 3866], +] + +[[area.polygons]] +vertices = [ + [2417, 3844], + [2417, 3857], +] + +[[area.polygons]] +vertices = [ + [2373, 3845], + [2374, 3845], + [2374, 3856], + [2373, 3856], +] + +[[area.polygons]] +vertices = [ + [2398, 3845], + [2398, 3862], +] + +[[area.polygons]] +vertices = [ + [2403, 3845], + [2403, 3866], +] + +[[area.polygons]] +vertices = [ + [2409, 3845], + [2409, 3869], +] + +[[area.polygons]] +vertices = [ + [2418, 3845], + [2418, 3857], +] + +[[area.polygons]] +vertices = [ + [2421, 3845], + [2421, 3855], +] + +[[area.polygons]] +vertices = [ + [2313, 3846], + [2313, 3861], +] + +[[area.polygons]] +vertices = [ + [2356, 3846], + [2357, 3846], + [2357, 3871], + [2356, 3871], +] + +[[area.polygons]] +vertices = [ + [2371, 3846], + [2372, 3846], + [2372, 3854], + [2371, 3854], +] + +[[area.polygons]] +vertices = [ + [2375, 3846], + [2375, 3857], +] + +[[area.polygons]] +vertices = [ + [2393, 3846], + [2393, 3872], +] + +[[area.polygons]] +vertices = [ + [2402, 3846], + [2402, 3865], +] + +[[area.polygons]] +vertices = [ + [2422, 3846], + [2422, 3848], +] + +[[area.polygons]] +vertices = [ + [2311, 3847], + [2312, 3847], + [2312, 3860], + [2311, 3860], +] + +[[area.polygons]] +vertices = [ + [2358, 3847], + [2358, 3857], +] + +[[area.polygons]] +vertices = [ + [2370, 3847], + [2370, 3852], +] + +[[area.polygons]] +vertices = [ + [2376, 3847], + [2377, 3847], + [2377, 3855], + [2376, 3855], +] + +[[area.polygons]] +vertices = [ + [2379, 3847], + [2380, 3847], + [2380, 3849], + [2379, 3849], +] + +[[area.polygons]] +vertices = [ + [2389, 3847], + [2392, 3847], + [2392, 3871], + [2389, 3871], +] + +[[area.polygons]] +vertices = [ + [2399, 3847], + [2401, 3847], + [2401, 3864], + [2399, 3864], +] + +[[area.polygons]] +vertices = [[2423, 3847]] + +[[area.polygons]] +vertices = [ + [2310, 3848], + [2310, 3860], +] + +[[area.polygons]] +vertices = [ + [2359, 3848], + [2359, 3856], +] + +[[area.polygons]] +vertices = [[2369, 3848]] + +[[area.polygons]] +vertices = [[2381, 3848]] + +[[area.polygons]] +vertices = [ + [2309, 3849], + [2309, 3853], +] + +[[area.polygons]] +vertices = [ + [2360, 3850], + [2360, 3855], +] + +[[area.polygons]] +vertices = [ + [2379, 3850], + [2379, 3852], +] + +[[area.polygons]] +vertices = [ + [2388, 3850], + [2388, 3872], +] + +[[area.polygons]] +vertices = [ + [2361, 3851], + [2361, 3854], +] + +[[area.polygons]] +vertices = [ + [2422, 3851], + [2422, 3854], +] + +[[area.polygons]] +vertices = [ + [2387, 3852], + [2387, 3873], +] + +[[area.polygons]] +vertices = [ + [2423, 3852], + [2423, 3853], +] + +[[area.polygons]] +vertices = [ + [2386, 3854], + [2386, 3873], +] + +[[area.polygons]] +vertices = [[2372, 3855]] + +[[area.polygons]] +vertices = [ + [2309, 3856], + [2309, 3860], +] + +[[area.polygons]] +vertices = [[2376, 3856]] + +[[area.polygons]] +vertices = [ + [2384, 3856], + [2385, 3856], + [2385, 3872], + [2384, 3872], +] + +[[area.polygons]] +vertices = [ + [2411, 3856], + [2413, 3856], + [2413, 3867], + [2411, 3867], +] + +[[area.polygons]] +vertices = [[2416, 3856]] + +[[area.polygons]] +vertices = [[2374, 3857]] + +[[area.polygons]] +vertices = [ + [2382, 3857], + [2383, 3857], + [2383, 3870], + [2382, 3870], +] + +[[area.polygons]] +vertices = [ + [2358, 3859], + [2359, 3859], + [2359, 3870], + [2358, 3870], +] + +[[area.polygons]] +vertices = [ + [2381, 3859], + [2381, 3870], +] + +[[area.polygons]] +vertices = [ + [2417, 3859], + [2418, 3859], + [2418, 3873], + [2417, 3873], +] + +[[area.polygons]] +vertices = [ + [2360, 3860], + [2360, 3871], +] + +[[area.polygons]] +vertices = [ + [2378, 3860], + [2380, 3860], + [2380, 3863], + [2378, 3863], +] + +[[area.polygons]] +vertices = [ + [2414, 3860], + [2416, 3860], + [2416, 3870], + [2414, 3870], +] + +[[area.polygons]] +vertices = [ + [2419, 3860], + [2419, 3872], +] + +[[area.polygons]] +vertices = [[2312, 3861]] + +[[area.polygons]] +vertices = [ + [2361, 3861], + [2364, 3861], + [2364, 3875], + [2361, 3875], +] + +[[area.polygons]] +vertices = [ + [2368, 3861], + [2370, 3861], + [2370, 3874], + [2368, 3874], +] + +[[area.polygons]] +vertices = [ + [2316, 3862], + [2318, 3862], +] + +[[area.polygons]] +vertices = [ + [2365, 3862], + [2365, 3874], +] + +[[area.polygons]] +vertices = [ + [2367, 3862], + [2367, 3874], +] + +[[area.polygons]] +vertices = [ + [2371, 3862], + [2371, 3875], +] + +[[area.polygons]] +vertices = [ + [2420, 3862], + [2420, 3872], +] + +[[area.polygons]] +vertices = [ + [2317, 3863], + [2318, 3863], + [2318, 3865], + [2317, 3865], +] + +[[area.polygons]] +vertices = [ + [2366, 3863], + [2366, 3874], +] + +[[area.polygons]] +vertices = [ + [2372, 3863], + [2372, 3875], +] + +[[area.polygons]] +vertices = [ + [2373, 3864], + [2373, 3874], +] + +[[area.polygons]] +vertices = [[2380, 3864]] + +[[area.polygons]] +vertices = [ + [2397, 3864], + [2397, 3875], +] + +[[area.polygons]] +vertices = [ + [2421, 3864], + [2422, 3864], + [2422, 3870], + [2421, 3870], +] + +[[area.polygons]] +vertices = [ + [2374, 3865], + [2374, 3873], +] + +[[area.polygons]] +vertices = [ + [2423, 3865], + [2423, 3869], +] + +[[area.polygons]] +vertices = [[2318, 3866]] + +[[area.polygons]] +vertices = [[2334, 3866]] + +[[area.polygons]] +vertices = [ + [2398, 3866], + [2399, 3866], + [2399, 3874], + [2398, 3874], +] + +[[area.polygons]] +vertices = [ + [2424, 3866], + [2424, 3868], +] + +[[area.polygons]] +vertices = [ + [2375, 3867], + [2380, 3867], + [2380, 3868], + [2375, 3868], +] + +[[area.polygons]] +vertices = [ + [2400, 3867], + [2400, 3873], +] + +[[area.polygons]] +vertices = [ + [2401, 3868], + [2401, 3872], +] + +[[area.polygons]] +vertices = [ + [2412, 3868], + [2413, 3868], +] + +[[area.polygons]] +vertices = [[2321, 3869]] + +[[area.polygons]] +vertices = [ + [2326, 3869], + [2327, 3869], +] + +[[area.polygons]] +vertices = [ + [2402, 3869], + [2402, 3871], +] + +[[area.polygons]] +vertices = [[2413, 3869]] + +[[area.polygons]] +vertices = [ + [2342, 3870], + [2345, 3870], +] + +[[area.polygons]] +vertices = [ + [2375, 3870], + [2375, 3871], +] + +[[area.polygons]] +vertices = [ + [2343, 3871], + [2345, 3871], +] + +[[area.polygons]] +vertices = [[2359, 3871]] + +[[area.polygons]] +vertices = [[2383, 3871]] + +[[area.polygons]] +vertices = [ + [2415, 3871], + [2416, 3871], + [2416, 3872], + [2415, 3872], +] + +[[area.polygons]] +vertices = [[2421, 3871]] + +[[area.polygons]] +vertices = [ + [2344, 3872], + [2345, 3872], +] + +[[area.polygons]] +vertices = [ + [2345, 3873], + [2345, 3874], +] + +[[area.polygons]] +vertices = [[2385, 3873]] + +[[area.polygons]] +vertices = [[2416, 3873]] + +[[area.polygons]] +vertices = [ + [2353, 3874], + [2354, 3874], + [2354, 3875], + [2353, 3875], +] + +[[area.polygons]] +vertices = [ + [2360, 3874], + [2360, 3875], +] + +[[area.polygons]] +vertices = [[2417, 3874]] + +[[area.polygons]] +vertices = [ + [2369, 3875], + [2370, 3875], +] + +[[area.polygons]] +vertices = [[2398, 3875]] + +[[area.polygons]] +vertices = [[2353, 3876]] + +[[area.polygons]] +vertices = [ + [2316, 3878], + [2319, 3878], + [2319, 3899], + [2316, 3899], +] + +[[area.polygons]] +vertices = [ + [2407, 3878], + [2413, 3878], + [2413, 3890], + [2407, 3890], +] + +[[area.polygons]] +vertices = [ + [2312, 3879], + [2315, 3879], + [2315, 3899], + [2312, 3899], +] + +[[area.polygons]] +vertices = [ + [2320, 3879], + [2320, 3899], +] + +[[area.polygons]] +vertices = [ + [2388, 3879], + [2388, 3901], +] + +[[area.polygons]] +vertices = [ + [2405, 3879], + [2406, 3879], + [2406, 3898], + [2405, 3898], +] + +[[area.polygons]] +vertices = [ + [2414, 3879], + [2414, 3895], +] + +[[area.polygons]] +vertices = [ + [2311, 3880], + [2311, 3899], +] + +[[area.polygons]] +vertices = [ + [2321, 3880], + [2321, 3899], +] + +[[area.polygons]] +vertices = [ + [2335, 3880], + [2335, 3898], +] + +[[area.polygons]] +vertices = [ + [2386, 3880], + [2387, 3880], + [2387, 3901], + [2386, 3901], +] + +[[area.polygons]] +vertices = [ + [2389, 3880], + [2389, 3901], +] + +[[area.polygons]] +vertices = [ + [2404, 3880], + [2404, 3899], +] + +[[area.polygons]] +vertices = [ + [2415, 3880], + [2415, 3896], +] + +[[area.polygons]] +vertices = [ + [2310, 3881], + [2310, 3896], +] + +[[area.polygons]] +vertices = [ + [2322, 3881], + [2323, 3881], + [2323, 3900], + [2322, 3900], +] + +[[area.polygons]] +vertices = [ + [2333, 3881], + [2334, 3881], + [2334, 3899], + [2333, 3899], +] + +[[area.polygons]] +vertices = [ + [2336, 3881], + [2336, 3899], +] + +[[area.polygons]] +vertices = [[2345, 3881]] + +[[area.polygons]] +vertices = [ + [2361, 3881], + [2361, 3897], +] + +[[area.polygons]] +vertices = [ + [2384, 3881], + [2385, 3881], + [2385, 3899], + [2384, 3899], +] + +[[area.polygons]] +vertices = [ + [2390, 3881], + [2390, 3901], +] + +[[area.polygons]] +vertices = [ + [2403, 3881], + [2403, 3900], +] + +[[area.polygons]] +vertices = [ + [2416, 3881], + [2416, 3896], +] + +[[area.polygons]] +vertices = [ + [2324, 3882], + [2324, 3900], +] + +[[area.polygons]] +vertices = [ + [2332, 3882], + [2332, 3899], +] + +[[area.polygons]] +vertices = [ + [2337, 3882], + [2337, 3899], +] + +[[area.polygons]] +vertices = [ + [2359, 3882], + [2360, 3882], + [2360, 3898], + [2359, 3898], +] + +[[area.polygons]] +vertices = [ + [2362, 3882], + [2362, 3899], +] + +[[area.polygons]] +vertices = [ + [2380, 3882], + [2383, 3882], + [2383, 3898], + [2380, 3898], +] + +[[area.polygons]] +vertices = [ + [2391, 3882], + [2391, 3892], +] + +[[area.polygons]] +vertices = [ + [2402, 3882], + [2402, 3899], +] + +[[area.polygons]] +vertices = [ + [2417, 3882], + [2417, 3896], +] + +[[area.polygons]] +vertices = [ + [2309, 3883], + [2309, 3895], +] + +[[area.polygons]] +vertices = [ + [2325, 3883], + [2325, 3899], +] + +[[area.polygons]] +vertices = [ + [2331, 3883], + [2331, 3900], +] + +[[area.polygons]] +vertices = [ + [2338, 3883], + [2338, 3899], +] + +[[area.polygons]] +vertices = [ + [2363, 3883], + [2369, 3883], + [2369, 3897], + [2363, 3897], +] + +[[area.polygons]] +vertices = [ + [2376, 3883], + [2379, 3883], + [2379, 3898], + [2376, 3898], +] + +[[area.polygons]] +vertices = [ + [2392, 3883], + [2393, 3883], + [2393, 3892], + [2392, 3892], +] + +[[area.polygons]] +vertices = [ + [2308, 3884], + [2308, 3890], +] + +[[area.polygons]] +vertices = [ + [2326, 3884], + [2326, 3899], +] + +[[area.polygons]] +vertices = [ + [2330, 3884], + [2330, 3900], +] + +[[area.polygons]] +vertices = [ + [2345, 3884], + [2345, 3901], +] + +[[area.polygons]] +vertices = [ + [2358, 3884], + [2358, 3899], +] + +[[area.polygons]] +vertices = [ + [2370, 3884], + [2370, 3898], +] + +[[area.polygons]] +vertices = [ + [2375, 3884], + [2375, 3899], +] + +[[area.polygons]] +vertices = [ + [2394, 3884], + [2394, 3895], +] + +[[area.polygons]] +vertices = [ + [2401, 3884], + [2401, 3899], +] + +[[area.polygons]] +vertices = [ + [2418, 3884], + [2419, 3884], + [2419, 3896], + [2418, 3896], +] + +[[area.polygons]] +vertices = [ + [2327, 3885], + [2329, 3885], + [2329, 3900], + [2327, 3900], +] + +[[area.polygons]] +vertices = [ + [2339, 3885], + [2339, 3899], +] + +[[area.polygons]] +vertices = [ + [2343, 3885], + [2344, 3885], + [2344, 3900], + [2343, 3900], +] + +[[area.polygons]] +vertices = [ + [2355, 3885], + [2357, 3885], + [2357, 3899], + [2355, 3899], +] + +[[area.polygons]] +vertices = [ + [2371, 3885], + [2374, 3885], + [2374, 3900], + [2371, 3900], +] + +[[area.polygons]] +vertices = [ + [2395, 3885], + [2400, 3885], + [2400, 3897], + [2395, 3897], +] + +[[area.polygons]] +vertices = [ + [2420, 3885], + [2420, 3895], +] + +[[area.polygons]] +vertices = [ + [2340, 3886], + [2340, 3900], +] + +[[area.polygons]] +vertices = [ + [2342, 3886], + [2342, 3900], +] + +[[area.polygons]] +vertices = [ + [2351, 3886], + [2351, 3899], +] + +[[area.polygons]] +vertices = [ + [2354, 3886], + [2354, 3901], +] + +[[area.polygons]] +vertices = [ + [2341, 3887], + [2341, 3900], +] + +[[area.polygons]] +vertices = [ + [2353, 3887], + [2353, 3901], +] + +[[area.polygons]] +vertices = [ + [2352, 3888], + [2352, 3900], +] + +[[area.polygons]] +vertices = [ + [2407, 3891], + [2410, 3891], + [2410, 3893], + [2407, 3893], +] + +[[area.polygons]] +vertices = [ + [2412, 3891], + [2413, 3891], +] + +[[area.polygons]] +vertices = [ + [2413, 3892], + [2413, 3895], +] + +[[area.polygons]] +vertices = [ + [2308, 3893], + [2308, 3894], +] + +[[area.polygons]] +vertices = [[2393, 3893]] + +[[area.polygons]] +vertices = [ + [2407, 3894], + [2409, 3894], + [2409, 3897], + [2407, 3897], +] + +[[area.polygons]] +vertices = [ + [2391, 3897], + [2391, 3900], +] + +[[area.polygons]] +vertices = [ + [2363, 3898], + [2367, 3898], +] + +[[area.polygons]] +vertices = [ + [2396, 3898], + [2400, 3898], +] + +[[area.polygons]] +vertices = [ + [2407, 3898], + [2408, 3898], +] + +[[area.polygons]] +vertices = [[2359, 3899]] + +[[area.polygons]] +vertices = [ + [2363, 3899], + [2366, 3899], +] + +[[area.polygons]] +vertices = [[2376, 3899]] + +[[area.polygons]] +vertices = [ + [2397, 3899], + [2400, 3899], +] + +[[area.polygons]] +vertices = [ + [2355, 3900], + [2356, 3900], +] + +[[area.polygons]] +vertices = [[2385, 3900]] + +[[area.polygons]] +vertices = [[2344, 3901]] + +[[area.polygons]] +vertices = [ + [2347, 3901], + [2349, 3901], +] + +[[area.polygons]] +vertices = [[2355, 3901]] diff --git a/.data/raw-cache/map/area/its_not_over_til.toml b/.data/raw-cache/map/area/its_not_over_til.toml new file mode 100644 index 000000000..de2f96071 --- /dev/null +++ b/.data/raw-cache/map/area/its_not_over_til.toml @@ -0,0 +1,16 @@ +# It's not over 'til... music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: It's not over 'til... (plus verzik_combat from wiki polygons sharing this footprint). +# Bounds x 3136-3199, z 4288-4351; 4096 tiles as 1 rectangles. + +[[area]] +name = "It's not over 'til..." +area_id = "area.its_not_over_til" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 4288], + [3199, 4288], + [3199, 4351], + [3136, 4351], +] diff --git a/.data/raw-cache/map/area/itsy_bitsy.toml b/.data/raw-cache/map/area/itsy_bitsy.toml new file mode 100644 index 000000000..f2d60dde4 --- /dev/null +++ b/.data/raw-cache/map/area/itsy_bitsy.toml @@ -0,0 +1,16 @@ +# Itsy Bitsy... music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Itsy Bitsy.... +# Bounds x 3648-3711, z 9792-9855; 4096 tiles as 1 rectangles. + +[[area]] +name = "Itsy Bitsy..." +area_id = "area.itsy_bitsy" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3648, 9792], + [3711, 9792], + [3711, 9855], + [3648, 9855], +] diff --git a/.data/raw-cache/map/area/jaws_of_gluttony.toml b/.data/raw-cache/map/area/jaws_of_gluttony.toml new file mode 100644 index 000000000..342f54908 --- /dev/null +++ b/.data/raw-cache/map/area/jaws_of_gluttony.toml @@ -0,0 +1,16 @@ +# Jaws of Gluttony music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Jaws of Gluttony. +# Bounds x 3904-3967, z 5376-5439; 4096 tiles as 1 rectangles. + +[[area]] +name = "Jaws of Gluttony" +area_id = "area.jaws_of_gluttony" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3904, 5376], + [3967, 5376], + [3967, 5439], + [3904, 5439], +] diff --git a/.data/raw-cache/map/area/judgement_of_the_depths.toml b/.data/raw-cache/map/area/judgement_of_the_depths.toml new file mode 100644 index 000000000..124a8e052 --- /dev/null +++ b/.data/raw-cache/map/area/judgement_of_the_depths.toml @@ -0,0 +1,16 @@ +# Judgement of the Depths music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Judgement of the Depths. +# Bounds x 1600-1727, z 5888-5951; 8192 tiles as 1 rectangles. + +[[area]] +name = "Judgement of the Depths" +area_id = "area.judgement_of_the_depths" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1600, 5888], + [1727, 5888], + [1727, 5951], + [1600, 5951], +] diff --git a/.data/raw-cache/map/area/karamja.toml b/.data/raw-cache/map/area/karamja.toml new file mode 100644 index 000000000..edb204939 --- /dev/null +++ b/.data/raw-cache/map/area/karamja.toml @@ -0,0 +1,2009 @@ +# Karamja music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Reggae; Reggae 2; Tribal 2; Jungly 1; Jungly 2; Jungly 3; Tribal; Jungle Troubles. +# Bounds x 2751-2979, z 2935-3145; 29721 tiles as 322 rectangles. + +[[area]] +name = "Karamja" +area_id = "area.karamja" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2759, 2935], + [2762, 2935], + [2762, 2955], + [2759, 2955], +] + +[[area.polygons]] +vertices = [ + [2822, 2935], + [2823, 2935], + [2823, 2945], + [2822, 2945], +] + +[[area.polygons]] +vertices = [ + [2859, 2935], + [2871, 2935], + [2871, 2943], + [2859, 2943], +] + +[[area.polygons]] +vertices = [ + [2763, 2936], + [2763, 2991], +] + +[[area.polygons]] +vertices = [ + [2816, 2936], + [2818, 2936], + [2818, 2947], + [2816, 2947], +] + +[[area.polygons]] +vertices = [ + [2820, 2936], + [2821, 2936], + [2821, 2945], + [2820, 2945], +] + +[[area.polygons]] +vertices = [ + [2858, 2936], + [2858, 2943], +] + +[[area.polygons]] +vertices = [ + [2872, 2936], + [2872, 2959], +] + +[[area.polygons]] +vertices = [ + [2764, 2937], + [2764, 2991], +] + +[[area.polygons]] +vertices = [ + [2819, 2937], + [2819, 2947], +] + +[[area.polygons]] +vertices = [ + [2857, 2937], + [2857, 2943], +] + +[[area.polygons]] +vertices = [ + [2873, 2937], + [2873, 2959], +] + +[[area.polygons]] +vertices = [ + [2931, 2937], + [2932, 2937], + [2932, 3111], + [2931, 3111], +] + +[[area.polygons]] +vertices = [ + [2792, 2938], + [2793, 2938], + [2793, 3052], + [2792, 3052], +] + +[[area.polygons]] +vertices = [ + [2824, 2938], + [2856, 2938], + [2856, 2943], + [2824, 2943], +] + +[[area.polygons]] +vertices = [ + [2874, 2938], + [2874, 2960], +] + +[[area.polygons]] +vertices = [ + [2933, 2938], + [2940, 2938], + [2940, 3074], + [2933, 3074], +] + +[[area.polygons]] +vertices = [ + [2758, 2939], + [2758, 2955], +] + +[[area.polygons]] +vertices = [ + [2794, 2939], + [2800, 2939], + [2800, 3052], + [2794, 3052], +] + +[[area.polygons]] +vertices = [ + [2875, 2939], + [2875, 2961], +] + +[[area.polygons]] +vertices = [ + [2757, 2940], + [2757, 2954], +] + +[[area.polygons]] +vertices = [ + [2876, 2940], + [2876, 2962], +] + +[[area.polygons]] +vertices = [ + [2756, 2941], + [2756, 2954], +] + +[[area.polygons]] +vertices = [ + [2765, 2941], + [2791, 2941], + [2791, 2979], + [2765, 2979], +] + +[[area.polygons]] +vertices = [ + [2801, 2941], + [2815, 2941], + [2815, 3054], + [2801, 3054], +] + +[[area.polygons]] +vertices = [ + [2877, 2941], + [2930, 2941], + [2930, 2963], + [2877, 2963], +] + +[[area.polygons]] +vertices = [ + [2824, 2944], + [2840, 2944], +] + +[[area.polygons]] +vertices = [ + [2871, 2944], + [2871, 2958], +] + +[[area.polygons]] +vertices = [ + [2824, 2945], + [2839, 2945], +] + +[[area.polygons]] +vertices = [[2820, 2946]] + +[[area.polygons]] +vertices = [ + [2870, 2946], + [2870, 2957], +] + +[[area.polygons]] +vertices = [ + [2869, 2947], + [2869, 2957], +] + +[[area.polygons]] +vertices = [ + [2755, 2948], + [2755, 2953], +] + +[[area.polygons]] +vertices = [ + [2816, 2948], + [2816, 3066], +] + +[[area.polygons]] +vertices = [ + [2868, 2949], + [2868, 2956], +] + +[[area.polygons]] +vertices = [ + [2941, 2950], + [2947, 2950], + [2947, 3027], + [2941, 3027], +] + +[[area.polygons]] +vertices = [ + [2751, 2951], + [2754, 2951], + [2754, 2952], + [2751, 2952], +] + +[[area.polygons]] +vertices = [ + [2948, 2951], + [2948, 3020], +] + +[[area.polygons]] +vertices = [ + [2952, 2951], + [2955, 2951], + [2955, 3005], + [2952, 3005], +] + +[[area.polygons]] +vertices = [ + [2964, 2951], + [2964, 2955], +] + +[[area.polygons]] +vertices = [ + [2949, 2952], + [2951, 2952], + [2951, 3016], + [2949, 3016], +] + +[[area.polygons]] +vertices = [ + [2956, 2952], + [2957, 2952], + [2957, 2963], + [2956, 2963], +] + +[[area.polygons]] +vertices = [ + [2962, 2952], + [2963, 2952], + [2963, 2955], + [2962, 2955], +] + +[[area.polygons]] +vertices = [ + [2965, 2952], + [2966, 2952], + [2966, 2954], + [2965, 2954], +] + +[[area.polygons]] +vertices = [ + [2958, 2953], + [2958, 2963], +] + +[[area.polygons]] +vertices = [ + [2961, 2953], + [2961, 2959], +] + +[[area.polygons]] +vertices = [[2967, 2953]] + +[[area.polygons]] +vertices = [ + [2959, 2954], + [2960, 2954], + [2960, 2960], + [2959, 2960], +] + +[[area.polygons]] +vertices = [ + [2760, 2956], + [2762, 2956], +] + +[[area.polygons]] +vertices = [[2962, 2956]] + +[[area.polygons]] +vertices = [ + [2758, 2959], + [2762, 2959], + [2762, 2991], + [2758, 2991], +] + +[[area.polygons]] +vertices = [[2959, 2961]] + +[[area.polygons]] +vertices = [ + [2970, 2962], + [2972, 2962], + [2972, 2980], + [2970, 2980], +] + +[[area.polygons]] +vertices = [ + [2973, 2963], + [2976, 2963], + [2976, 2970], + [2973, 2970], +] + +[[area.polygons]] +vertices = [ + [2756, 2964], + [2757, 2964], + [2757, 2989], + [2756, 2989], +] + +[[area.polygons]] +vertices = [ + [2878, 2964], + [2930, 2964], +] + +[[area.polygons]] +vertices = [ + [2956, 2964], + [2956, 2965], +] + +[[area.polygons]] +vertices = [ + [2969, 2964], + [2969, 2981], +] + +[[area.polygons]] +vertices = [ + [2977, 2964], + [2977, 2969], +] + +[[area.polygons]] +vertices = [ + [2879, 2965], + [2930, 2965], +] + +[[area.polygons]] +vertices = [ + [2978, 2965], + [2978, 2968], +] + +[[area.polygons]] +vertices = [ + [2755, 2966], + [2755, 2988], +] + +[[area.polygons]] +vertices = [ + [2880, 2966], + [2930, 2966], + [2930, 3109], + [2880, 3109], +] + +[[area.polygons]] +vertices = [ + [2967, 2966], + [2968, 2966], + [2968, 2981], + [2967, 2981], +] + +[[area.polygons]] +vertices = [ + [2979, 2966], + [2979, 2967], +] + +[[area.polygons]] +vertices = [ + [2754, 2967], + [2754, 2977], +] + +[[area.polygons]] +vertices = [ + [2753, 2968], + [2753, 2970], +] + +[[area.polygons]] +vertices = [ + [2956, 2968], + [2956, 2988], +] + +[[area.polygons]] +vertices = [ + [2957, 2969], + [2957, 2986], +] + +[[area.polygons]] +vertices = [ + [2966, 2969], + [2966, 2983], +] + +[[area.polygons]] +vertices = [ + [2958, 2970], + [2965, 2970], + [2965, 2982], + [2958, 2982], +] + +[[area.polygons]] +vertices = [ + [2973, 2971], + [2974, 2971], +] + +[[area.polygons]] +vertices = [ + [2753, 2972], + [2753, 2976], +] + +[[area.polygons]] +vertices = [ + [2973, 2972], + [2973, 2979], +] + +[[area.polygons]] +vertices = [ + [2752, 2973], + [2752, 2975], +] + +[[area.polygons]] +vertices = [ + [2817, 2975], + [2817, 2976], +] + +[[area.polygons]] +vertices = [ + [2974, 2975], + [2974, 2978], +] + +[[area.polygons]] +vertices = [ + [2754, 2980], + [2754, 2981], +] + +[[area.polygons]] +vertices = [ + [2765, 2980], + [2775, 2980], +] + +[[area.polygons]] +vertices = [ + [2780, 2980], + [2791, 2980], + [2791, 2981], + [2780, 2981], +] + +[[area.polygons]] +vertices = [ + [2765, 2981], + [2774, 2981], + [2774, 2982], + [2765, 2982], +] + +[[area.polygons]] +vertices = [ + [2970, 2981], + [2971, 2981], +] + +[[area.polygons]] +vertices = [ + [2781, 2982], + [2791, 2982], + [2791, 2985], + [2781, 2985], +] + +[[area.polygons]] +vertices = [[2967, 2982]] + +[[area.polygons]] +vertices = [ + [2765, 2983], + [2773, 2983], + [2773, 2987], + [2765, 2987], +] + +[[area.polygons]] +vertices = [ + [2958, 2983], + [2964, 2983], +] + +[[area.polygons]] +vertices = [ + [2958, 2984], + [2962, 2984], +] + +[[area.polygons]] +vertices = [ + [2754, 2985], + [2754, 2987], +] + +[[area.polygons]] +vertices = [ + [2958, 2985], + [2960, 2985], +] + +[[area.polygons]] +vertices = [ + [2782, 2986], + [2791, 2986], + [2791, 2988], + [2782, 2988], +] + +[[area.polygons]] +vertices = [[2958, 2986]] + +[[area.polygons]] +vertices = [ + [2765, 2988], + [2772, 2988], +] + +[[area.polygons]] +vertices = [ + [2765, 2989], + [2771, 2989], +] + +[[area.polygons]] +vertices = [ + [2783, 2989], + [2791, 2989], + [2791, 3051], + [2783, 3051], +] + +[[area.polygons]] +vertices = [ + [2757, 2990], + [2757, 2991], +] + +[[area.polygons]] +vertices = [ + [2765, 2990], + [2770, 2990], +] + +[[area.polygons]] +vertices = [ + [2956, 2990], + [2956, 3002], +] + +[[area.polygons]] +vertices = [ + [2765, 2991], + [2769, 2991], +] + +[[area.polygons]] +vertices = [ + [2782, 2991], + [2782, 3055], +] + +[[area.polygons]] +vertices = [ + [2766, 2992], + [2767, 2992], +] + +[[area.polygons]] +vertices = [ + [2781, 2992], + [2781, 3054], +] + +[[area.polygons]] +vertices = [ + [2957, 2992], + [2957, 3002], +] + +[[area.polygons]] +vertices = [ + [2780, 2993], + [2780, 3054], +] + +[[area.polygons]] +vertices = [ + [2771, 2994], + [2779, 2994], + [2779, 3054], + [2771, 3054], +] + +[[area.polygons]] +vertices = [ + [2958, 2994], + [2958, 3001], +] + +[[area.polygons]] +vertices = [ + [2769, 2995], + [2770, 2995], + [2770, 3135], + [2769, 3135], +] + +[[area.polygons]] +vertices = [ + [2959, 2995], + [2959, 3000], +] + +[[area.polygons]] +vertices = [ + [2766, 2996], + [2768, 2996], + [2768, 3134], + [2766, 3134], +] + +[[area.polygons]] +vertices = [[2960, 2996]] + +[[area.polygons]] +vertices = [ + [2764, 2997], + [2765, 2997], + [2765, 3134], + [2764, 3134], +] + +[[area.polygons]] +vertices = [ + [2763, 2998], + [2763, 3134], +] + +[[area.polygons]] +vertices = [ + [2761, 2999], + [2762, 2999], + [2762, 3134], + [2761, 3134], +] + +[[area.polygons]] +vertices = [ + [2758, 3000], + [2760, 3000], + [2760, 3055], + [2758, 3055], +] + +[[area.polygons]] +vertices = [ + [2757, 3001], + [2757, 3011], +] + +[[area.polygons]] +vertices = [ + [2755, 3002], + [2756, 3002], + [2756, 3009], + [2755, 3009], +] + +[[area.polygons]] +vertices = [ + [2754, 3003], + [2754, 3007], +] + +[[area.polygons]] +vertices = [ + [2817, 3003], + [2817, 3067], +] + +[[area.polygons]] +vertices = [ + [2818, 3004], + [2818, 3067], +] + +[[area.polygons]] +vertices = [[2753, 3005]] + +[[area.polygons]] +vertices = [ + [2819, 3005], + [2819, 3068], +] + +[[area.polygons]] +vertices = [ + [2820, 3006], + [2820, 3070], +] + +[[area.polygons]] +vertices = [ + [2952, 3006], + [2954, 3006], + [2954, 3015], + [2952, 3015], +] + +[[area.polygons]] +vertices = [ + [2821, 3007], + [2879, 3007], + [2879, 3071], + [2821, 3071], +] + +[[area.polygons]] +vertices = [[2756, 3010]] + +[[area.polygons]] +vertices = [ + [2955, 3010], + [2956, 3010], + [2956, 3014], + [2955, 3014], +] + +[[area.polygons]] +vertices = [ + [2957, 3011], + [2957, 3014], +] + +[[area.polygons]] +vertices = [ + [2958, 3012], + [2958, 3014], +] + +[[area.polygons]] +vertices = [ + [2967, 3012], + [2970, 3012], + [2970, 3021], + [2967, 3021], +] + +[[area.polygons]] +vertices = [ + [2974, 3012], + [2974, 3018], +] + +[[area.polygons]] +vertices = [ + [2753, 3013], + [2753, 3018], +] + +[[area.polygons]] +vertices = [ + [2959, 3013], + [2966, 3013], + [2966, 3015], + [2959, 3015], +] + +[[area.polygons]] +vertices = [ + [2971, 3013], + [2973, 3013], + [2973, 3019], + [2971, 3019], +] + +[[area.polygons]] +vertices = [ + [2975, 3013], + [2975, 3017], +] + +[[area.polygons]] +vertices = [ + [2754, 3014], + [2754, 3052], +] + +[[area.polygons]] +vertices = [ + [2976, 3014], + [2976, 3017], +] + +[[area.polygons]] +vertices = [ + [2977, 3015], + [2977, 3016], +] + +[[area.polygons]] +vertices = [ + [2755, 3016], + [2755, 3052], +] + +[[area.polygons]] +vertices = [ + [2952, 3016], + [2953, 3016], +] + +[[area.polygons]] +vertices = [ + [2960, 3016], + [2966, 3016], + [2966, 3017], + [2960, 3017], +] + +[[area.polygons]] +vertices = [ + [2949, 3017], + [2950, 3017], +] + +[[area.polygons]] +vertices = [ + [2756, 3018], + [2757, 3018], + [2757, 3053], + [2756, 3053], +] + +[[area.polygons]] +vertices = [ + [2949, 3018], + [2949, 3019], +] + +[[area.polygons]] +vertices = [ + [2961, 3018], + [2966, 3018], +] + +[[area.polygons]] +vertices = [ + [2962, 3019], + [2966, 3019], +] + +[[area.polygons]] +vertices = [ + [2964, 3020], + [2966, 3020], +] + +[[area.polygons]] +vertices = [[2971, 3020]] + +[[area.polygons]] +vertices = [ + [2753, 3023], + [2753, 3024], +] + +[[area.polygons]] +vertices = [ + [2753, 3028], + [2753, 3036], +] + +[[area.polygons]] +vertices = [ + [2941, 3028], + [2946, 3028], +] + +[[area.polygons]] +vertices = [ + [2752, 3029], + [2752, 3032], +] + +[[area.polygons]] +vertices = [ + [2941, 3029], + [2945, 3029], + [2945, 3032], + [2941, 3032], +] + +[[area.polygons]] +vertices = [ + [2941, 3033], + [2944, 3033], + [2944, 3069], + [2941, 3069], +] + +[[area.polygons]] +vertices = [ + [2752, 3035], + [2752, 3036], +] + +[[area.polygons]] +vertices = [ + [2753, 3040], + [2753, 3048], +] + +[[area.polygons]] +vertices = [ + [2752, 3041], + [2752, 3047], +] + +[[area.polygons]] +vertices = [ + [2945, 3048], + [2945, 3069], +] + +[[area.polygons]] +vertices = [ + [2946, 3049], + [2946, 3068], +] + +[[area.polygons]] +vertices = [ + [2783, 3052], + [2787, 3052], +] + +[[area.polygons]] +vertices = [ + [2783, 3053], + [2784, 3053], +] + +[[area.polygons]] +vertices = [ + [2795, 3053], + [2800, 3053], +] + +[[area.polygons]] +vertices = [[2947, 3053]] + +[[area.polygons]] +vertices = [[2757, 3054]] + +[[area.polygons]] +vertices = [[2783, 3054]] + +[[area.polygons]] +vertices = [ + [2796, 3054], + [2800, 3054], +] + +[[area.polygons]] +vertices = [ + [2771, 3055], + [2777, 3055], + [2777, 3057], + [2771, 3057], +] + +[[area.polygons]] +vertices = [[2797, 3055]] + +[[area.polygons]] +vertices = [ + [2802, 3055], + [2815, 3055], +] + +[[area.polygons]] +vertices = [ + [2759, 3056], + [2760, 3056], + [2760, 3133], + [2759, 3133], +] + +[[area.polygons]] +vertices = [ + [2803, 3056], + [2815, 3056], +] + +[[area.polygons]] +vertices = [ + [2758, 3057], + [2758, 3080], +] + +[[area.polygons]] +vertices = [ + [2806, 3057], + [2815, 3057], +] + +[[area.polygons]] +vertices = [ + [2757, 3058], + [2757, 3079], +] + +[[area.polygons]] +vertices = [ + [2771, 3058], + [2776, 3058], +] + +[[area.polygons]] +vertices = [ + [2807, 3058], + [2815, 3058], +] + +[[area.polygons]] +vertices = [ + [2756, 3059], + [2756, 3064], +] + +[[area.polygons]] +vertices = [ + [2771, 3059], + [2775, 3059], +] + +[[area.polygons]] +vertices = [ + [2809, 3059], + [2815, 3059], +] + +[[area.polygons]] +vertices = [ + [2755, 3060], + [2755, 3063], +] + +[[area.polygons]] +vertices = [ + [2771, 3060], + [2774, 3060], +] + +[[area.polygons]] +vertices = [ + [2810, 3060], + [2815, 3060], +] + +[[area.polygons]] +vertices = [ + [2947, 3060], + [2947, 3067], +] + +[[area.polygons]] +vertices = [ + [2771, 3061], + [2772, 3061], + [2772, 3063], + [2771, 3063], +] + +[[area.polygons]] +vertices = [ + [2811, 3061], + [2815, 3061], +] + +[[area.polygons]] +vertices = [ + [2948, 3061], + [2948, 3066], +] + +[[area.polygons]] +vertices = [ + [2812, 3062], + [2815, 3062], +] + +[[area.polygons]] +vertices = [ + [2814, 3063], + [2815, 3063], +] + +[[area.polygons]] +vertices = [ + [2771, 3064], + [2771, 3081], +] + +[[area.polygons]] +vertices = [[2815, 3064]] + +[[area.polygons]] +vertices = [ + [2756, 3068], + [2756, 3078], +] + +[[area.polygons]] +vertices = [ + [2772, 3068], + [2772, 3078], +] + +[[area.polygons]] +vertices = [ + [2755, 3069], + [2755, 3076], +] + +[[area.polygons]] +vertices = [ + [2773, 3069], + [2773, 3076], +] + +[[area.polygons]] +vertices = [ + [2753, 3070], + [2754, 3070], + [2754, 3074], + [2753, 3074], +] + +[[area.polygons]] +vertices = [ + [2941, 3070], + [2943, 3070], +] + +[[area.polygons]] +vertices = [ + [2752, 3071], + [2752, 3072], +] + +[[area.polygons]] +vertices = [ + [2774, 3071], + [2774, 3074], +] + +[[area.polygons]] +vertices = [ + [2941, 3071], + [2942, 3071], +] + +[[area.polygons]] +vertices = [[2775, 3072]] + +[[area.polygons]] +vertices = [ + [2822, 3072], + [2879, 3072], +] + +[[area.polygons]] +vertices = [ + [2941, 3072], + [2941, 3073], +] + +[[area.polygons]] +vertices = [ + [2823, 3073], + [2879, 3073], + [2879, 3074], + [2823, 3074], +] + +[[area.polygons]] +vertices = [[2754, 3075]] + +[[area.polygons]] +vertices = [ + [2824, 3075], + [2879, 3075], +] + +[[area.polygons]] +vertices = [ + [2933, 3075], + [2939, 3075], + [2939, 3083], + [2933, 3083], +] + +[[area.polygons]] +vertices = [ + [2828, 3076], + [2879, 3076], +] + +[[area.polygons]] +vertices = [ + [2940, 3076], + [2940, 3081], +] + +[[area.polygons]] +vertices = [ + [2829, 3077], + [2879, 3077], +] + +[[area.polygons]] +vertices = [ + [2830, 3078], + [2879, 3078], + [2879, 3115], + [2830, 3115], +] + +[[area.polygons]] +vertices = [ + [2941, 3078], + [2941, 3080], +] + +[[area.polygons]] +vertices = [ + [2758, 3084], + [2758, 3132], +] + +[[area.polygons]] +vertices = [ + [2933, 3084], + [2938, 3084], + [2938, 3085], + [2933, 3085], +] + +[[area.polygons]] +vertices = [ + [2757, 3085], + [2757, 3095], +] + +[[area.polygons]] +vertices = [ + [2756, 3086], + [2756, 3093], +] + +[[area.polygons]] +vertices = [ + [2933, 3086], + [2937, 3086], +] + +[[area.polygons]] +vertices = [ + [2755, 3087], + [2755, 3091], +] + +[[area.polygons]] +vertices = [ + [2933, 3087], + [2936, 3087], +] + +[[area.polygons]] +vertices = [ + [2933, 3088], + [2935, 3088], +] + +[[area.polygons]] +vertices = [ + [2829, 3089], + [2829, 3132], +] + +[[area.polygons]] +vertices = [ + [2933, 3089], + [2934, 3089], +] + +[[area.polygons]] +vertices = [ + [2828, 3090], + [2828, 3131], +] + +[[area.polygons]] +vertices = [ + [2933, 3090], + [2933, 3110], +] + +[[area.polygons]] +vertices = [ + [2824, 3091], + [2827, 3091], + [2827, 3131], + [2824, 3131], +] + +[[area.polygons]] +vertices = [ + [2934, 3091], + [2934, 3109], +] + +[[area.polygons]] +vertices = [ + [2935, 3093], + [2935, 3109], +] + +[[area.polygons]] +vertices = [ + [2936, 3094], + [2937, 3094], + [2937, 3107], + [2936, 3107], +] + +[[area.polygons]] +vertices = [ + [2938, 3095], + [2939, 3095], + [2939, 3105], + [2938, 3105], +] + +[[area.polygons]] +vertices = [ + [2940, 3096], + [2940, 3103], +] + +[[area.polygons]] +vertices = [ + [2771, 3098], + [2771, 3145], +] + +[[area.polygons]] +vertices = [ + [2823, 3098], + [2823, 3132], +] + +[[area.polygons]] +vertices = [ + [2941, 3098], + [2941, 3101], +] + +[[area.polygons]] +vertices = [ + [2772, 3100], + [2772, 3144], +] + +[[area.polygons]] +vertices = [ + [2757, 3101], + [2757, 3132], +] + +[[area.polygons]] +vertices = [ + [2773, 3101], + [2773, 3143], +] + +[[area.polygons]] +vertices = [ + [2822, 3101], + [2822, 3132], +] + +[[area.polygons]] +vertices = [ + [2821, 3102], + [2821, 3132], +] + +[[area.polygons]] +vertices = [ + [2756, 3103], + [2756, 3107], +] + +[[area.polygons]] +vertices = [ + [2774, 3104], + [2774, 3143], +] + +[[area.polygons]] +vertices = [ + [2775, 3105], + [2775, 3143], +] + +[[area.polygons]] +vertices = [ + [2820, 3105], + [2820, 3132], +] + +[[area.polygons]] +vertices = [ + [2776, 3106], + [2776, 3143], +] + +[[area.polygons]] +vertices = [ + [2819, 3106], + [2819, 3132], +] + +[[area.polygons]] +vertices = [[2938, 3106]] + +[[area.polygons]] +vertices = [ + [2777, 3107], + [2777, 3143], +] + +[[area.polygons]] +vertices = [ + [2818, 3107], + [2818, 3132], +] + +[[area.polygons]] +vertices = [ + [2778, 3108], + [2778, 3143], +] + +[[area.polygons]] +vertices = [ + [2817, 3108], + [2817, 3131], +] + +[[area.polygons]] +vertices = [[2936, 3108]] + +[[area.polygons]] +vertices = [ + [2779, 3109], + [2780, 3109], + [2780, 3140], + [2779, 3140], +] + +[[area.polygons]] +vertices = [ + [2816, 3109], + [2816, 3130], +] + +[[area.polygons]] +vertices = [ + [2756, 3110], + [2756, 3131], +] + +[[area.polygons]] +vertices = [ + [2781, 3110], + [2781, 3139], +] + +[[area.polygons]] +vertices = [ + [2815, 3110], + [2815, 3130], +] + +[[area.polygons]] +vertices = [ + [2880, 3110], + [2920, 3110], +] + +[[area.polygons]] +vertices = [ + [2924, 3110], + [2930, 3110], +] + +[[area.polygons]] +vertices = [ + [2782, 3111], + [2782, 3139], +] + +[[area.polygons]] +vertices = [ + [2814, 3111], + [2814, 3129], +] + +[[area.polygons]] +vertices = [ + [2880, 3111], + [2917, 3111], + [2917, 3112], + [2880, 3112], +] + +[[area.polygons]] +vertices = [ + [2926, 3111], + [2930, 3111], +] + +[[area.polygons]] +vertices = [ + [2755, 3112], + [2755, 3129], +] + +[[area.polygons]] +vertices = [ + [2783, 3112], + [2784, 3112], + [2784, 3137], + [2783, 3137], +] + +[[area.polygons]] +vertices = [ + [2812, 3112], + [2813, 3112], + [2813, 3128], + [2812, 3128], +] + +[[area.polygons]] +vertices = [ + [2785, 3113], + [2788, 3113], + [2788, 3133], + [2785, 3133], +] + +[[area.polygons]] +vertices = [ + [2810, 3113], + [2811, 3113], + [2811, 3128], + [2810, 3128], +] + +[[area.polygons]] +vertices = [ + [2880, 3113], + [2916, 3113], +] + +[[area.polygons]] +vertices = [ + [2754, 3114], + [2754, 3127], +] + +[[area.polygons]] +vertices = [ + [2789, 3114], + [2809, 3114], + [2809, 3127], + [2789, 3127], +] + +[[area.polygons]] +vertices = [ + [2880, 3114], + [2915, 3114], +] + +[[area.polygons]] +vertices = [ + [2880, 3115], + [2914, 3115], +] + +[[area.polygons]] +vertices = [ + [2830, 3116], + [2864, 3116], +] + +[[area.polygons]] +vertices = [ + [2868, 3116], + [2889, 3116], +] + +[[area.polygons]] +vertices = [ + [2893, 3116], + [2907, 3116], +] + +[[area.polygons]] +vertices = [ + [2909, 3116], + [2914, 3116], +] + +[[area.polygons]] +vertices = [ + [2753, 3117], + [2753, 3125], +] + +[[area.polygons]] +vertices = [ + [2830, 3117], + [2854, 3117], +] + +[[area.polygons]] +vertices = [ + [2859, 3117], + [2864, 3117], +] + +[[area.polygons]] +vertices = [ + [2869, 3117], + [2879, 3117], +] + +[[area.polygons]] +vertices = [ + [2882, 3117], + [2886, 3117], +] + +[[area.polygons]] +vertices = [ + [2893, 3117], + [2905, 3117], +] + +[[area.polygons]] +vertices = [ + [2910, 3117], + [2914, 3117], +] + +[[area.polygons]] +vertices = [ + [2830, 3118], + [2853, 3118], + [2853, 3119], + [2830, 3119], +] + +[[area.polygons]] +vertices = [ + [2860, 3118], + [2864, 3118], +] + +[[area.polygons]] +vertices = [ + [2870, 3118], + [2878, 3118], +] + +[[area.polygons]] +vertices = [ + [2893, 3118], + [2904, 3118], +] + +[[area.polygons]] +vertices = [ + [2910, 3118], + [2913, 3118], +] + +[[area.polygons]] +vertices = [ + [2871, 3119], + [2878, 3119], + [2878, 3120], + [2871, 3120], +] + +[[area.polygons]] +vertices = [ + [2895, 3119], + [2897, 3119], +] + +[[area.polygons]] +vertices = [ + [2900, 3119], + [2903, 3119], +] + +[[area.polygons]] +vertices = [ + [2830, 3120], + [2852, 3120], +] + +[[area.polygons]] +vertices = [ + [2830, 3121], + [2851, 3121], + [2851, 3122], + [2830, 3122], +] + +[[area.polygons]] +vertices = [ + [2872, 3121], + [2878, 3121], + [2878, 3122], + [2872, 3122], +] + +[[area.polygons]] +vertices = [ + [2830, 3123], + [2840, 3123], +] + +[[area.polygons]] +vertices = [ + [2846, 3123], + [2850, 3123], +] + +[[area.polygons]] +vertices = [ + [2872, 3123], + [2877, 3123], + [2877, 3125], + [2872, 3125], +] + +[[area.polygons]] +vertices = [ + [2830, 3124], + [2839, 3124], +] + +[[area.polygons]] +vertices = [ + [2830, 3125], + [2838, 3125], +] + +[[area.polygons]] +vertices = [ + [2871, 3125], + [2871, 3126], +] + +[[area.polygons]] +vertices = [ + [2830, 3126], + [2837, 3126], +] + +[[area.polygons]] +vertices = [ + [2872, 3126], + [2876, 3126], +] + +[[area.polygons]] +vertices = [ + [2830, 3127], + [2836, 3127], +] + +[[area.polygons]] +vertices = [ + [2872, 3127], + [2875, 3127], +] + +[[area.polygons]] +vertices = [ + [2789, 3128], + [2794, 3128], +] + +[[area.polygons]] +vertices = [ + [2798, 3128], + [2809, 3128], +] + +[[area.polygons]] +vertices = [ + [2830, 3128], + [2835, 3128], + [2835, 3131], + [2830, 3131], +] + +[[area.polygons]] +vertices = [ + [2789, 3129], + [2792, 3129], +] + +[[area.polygons]] +vertices = [ + [2800, 3129], + [2810, 3129], +] + +[[area.polygons]] +vertices = [[2813, 3129]] + +[[area.polygons]] +vertices = [ + [2789, 3130], + [2791, 3130], +] + +[[area.polygons]] +vertices = [ + [2802, 3130], + [2808, 3130], +] + +[[area.polygons]] +vertices = [ + [2789, 3131], + [2790, 3131], +] + +[[area.polygons]] +vertices = [[2789, 3132]] + +[[area.polygons]] +vertices = [[2824, 3132]] + +[[area.polygons]] +vertices = [ + [2830, 3132], + [2834, 3132], +] + +[[area.polygons]] +vertices = [ + [2785, 3134], + [2787, 3134], +] + +[[area.polygons]] +vertices = [ + [2767, 3135], + [2768, 3135], +] + +[[area.polygons]] +vertices = [ + [2785, 3135], + [2786, 3135], +] + +[[area.polygons]] +vertices = [[2770, 3136]] + +[[area.polygons]] +vertices = [[2785, 3136]] + +[[area.polygons]] +vertices = [[2783, 3138]] + +[[area.polygons]] +vertices = [ + [2770, 3139], + [2770, 3143], +] + +[[area.polygons]] +vertices = [ + [2769, 3140], + [2769, 3142], +] + +[[area.polygons]] +vertices = [[2768, 3141]] + +[[area.polygons]] +vertices = [[2779, 3141]] diff --git a/.data/raw-cache/map/area/karuulm_slayer_dungeon_music.toml b/.data/raw-cache/map/area/karuulm_slayer_dungeon_music.toml new file mode 100644 index 000000000..655c70df2 --- /dev/null +++ b/.data/raw-cache/map/area/karuulm_slayer_dungeon_music.toml @@ -0,0 +1,24 @@ +# Karuulm Slayer Dungeon music music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Ful to the Brim; Kanon of Kahlith; Way of the Wyrm. +# Bounds x 1216-1367, z 10112-10303; 28596 tiles as 2 rectangles. + +[[area]] +name = "Karuulm Slayer Dungeon music" +area_id = "area.karuulm_slayer_dungeon_music" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1216, 10112], + [1367, 10112], + [1367, 10254], + [1216, 10254], +] + +[[area.polygons]] +vertices = [ + [1216, 10255], + [1355, 10255], + [1355, 10303], + [1216, 10303], +] diff --git a/.data/raw-cache/map/area/keldagrim_music.toml b/.data/raw-cache/map/area/keldagrim_music.toml new file mode 100644 index 000000000..9c7cb9888 --- /dev/null +++ b/.data/raw-cache/map/area/keldagrim_music.toml @@ -0,0 +1,24 @@ +# Keldagrim music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Land of the Dwarves; Tale of Keldagrim. +# Bounds x 2816-2943, z 10150-10239; 9792 tiles as 2 rectangles. + +[[area]] +name = "Keldagrim" +area_id = "area.keldagrim_music" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2880, 10150], + [2943, 10150], + [2943, 10239], + [2880, 10239], +] + +[[area.polygons]] +vertices = [ + [2816, 10177], + [2879, 10177], + [2879, 10239], + [2816, 10239], +] diff --git a/.data/raw-cache/map/area/kharidian_desert.toml b/.data/raw-cache/map/area/kharidian_desert.toml new file mode 100644 index 000000000..38758eeae --- /dev/null +++ b/.data/raw-cache/map/area/kharidian_desert.toml @@ -0,0 +1,3831 @@ +# Kharidian Desert music music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Scarab; Egypt; The Desert; Arabian 3; Desert Voyage; Sunburn; Desert Heat; Sphinx. +# Bounds x 3135-3536, z 2752-3174; 92209 tiles as 620 rectangles. + +[[area]] +name = "Kharidian Desert music" +area_id = "area.kharidian_desert" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3397, 2752], + [3397, 2789], +] + +[[area.polygons]] +vertices = [ + [3395, 2753], + [3396, 2753], + [3396, 2790], + [3395, 2790], +] + +[[area.polygons]] +vertices = [ + [3389, 2755], + [3389, 2797], +] + +[[area.polygons]] +vertices = [ + [3394, 2755], + [3394, 2792], +] + +[[area.polygons]] +vertices = [ + [3323, 2756], + [3347, 2756], + [3347, 2833], + [3323, 2833], +] + +[[area.polygons]] +vertices = [ + [3386, 2756], + [3388, 2756], + [3388, 2798], + [3386, 2798], +] + +[[area.polygons]] +vertices = [ + [3390, 2756], + [3391, 2756], + [3391, 2795], + [3390, 2795], +] + +[[area.polygons]] +vertices = [ + [3392, 2757], + [3393, 2757], + [3393, 2793], + [3392, 2793], +] + +[[area.polygons]] +vertices = [ + [3385, 2758], + [3385, 2801], +] + +[[area.polygons]] +vertices = [ + [3375, 2759], + [3375, 2824], +] + +[[area.polygons]] +vertices = [ + [3382, 2759], + [3382, 2966], +] + +[[area.polygons]] +vertices = [ + [3384, 2759], + [3384, 3136], +] + +[[area.polygons]] +vertices = [ + [3374, 2760], + [3374, 2823], +] + +[[area.polygons]] +vertices = [ + [3376, 2760], + [3376, 2825], +] + +[[area.polygons]] +vertices = [ + [3380, 2760], + [3381, 2760], + [3381, 2828], + [3380, 2828], +] + +[[area.polygons]] +vertices = [ + [3383, 2760], + [3383, 3135], +] + +[[area.polygons]] +vertices = [ + [3348, 2761], + [3366, 2761], + [3366, 2822], + [3348, 2822], +] + +[[area.polygons]] +vertices = [ + [3373, 2761], + [3373, 2822], +] + +[[area.polygons]] +vertices = [ + [3377, 2761], + [3379, 2761], + [3379, 2826], + [3377, 2826], +] + +[[area.polygons]] +vertices = [ + [3367, 2762], + [3372, 2762], + [3372, 2822], + [3367, 2822], +] + +[[area.polygons]] +vertices = [ + [3149, 2765], + [3161, 2765], + [3161, 2766], + [3149, 2766], +] + +[[area.polygons]] +vertices = [ + [3162, 2766], + [3162, 2815], +] + +[[area.polygons]] +vertices = [ + [3150, 2767], + [3161, 2767], + [3161, 2768], + [3150, 2768], +] + +[[area.polygons]] +vertices = [ + [3163, 2767], + [3163, 2815], +] + +[[area.polygons]] +vertices = [ + [3164, 2768], + [3164, 2816], +] + +[[area.polygons]] +vertices = [ + [3151, 2769], + [3161, 2769], + [3161, 2770], + [3151, 2770], +] + +[[area.polygons]] +vertices = [ + [3165, 2769], + [3165, 2816], +] + +[[area.polygons]] +vertices = [ + [3166, 2770], + [3166, 2816], +] + +[[area.polygons]] +vertices = [ + [3152, 2771], + [3161, 2771], +] + +[[area.polygons]] +vertices = [ + [3167, 2771], + [3167, 2816], +] + +[[area.polygons]] +vertices = [ + [3153, 2772], + [3161, 2772], + [3161, 2811], + [3153, 2811], +] + +[[area.polygons]] +vertices = [ + [3168, 2772], + [3168, 2816], +] + +[[area.polygons]] +vertices = [ + [3169, 2773], + [3185, 2773], + [3185, 2793], + [3169, 2793], +] + +[[area.polygons]] +vertices = [ + [3152, 2777], + [3152, 2812], +] + +[[area.polygons]] +vertices = [ + [3151, 2781], + [3151, 2812], +] + +[[area.polygons]] +vertices = [ + [3150, 2782], + [3150, 2812], +] + +[[area.polygons]] +vertices = [ + [3148, 2783], + [3149, 2783], + [3149, 2812], + [3148, 2812], +] + +[[area.polygons]] +vertices = [ + [3147, 2785], + [3147, 2812], +] + +[[area.polygons]] +vertices = [ + [3146, 2787], + [3146, 2812], +] + +[[area.polygons]] +vertices = [ + [3143, 2788], + [3145, 2788], + [3145, 2807], + [3143, 2807], +] + +[[area.polygons]] +vertices = [ + [3142, 2789], + [3142, 2800], +] + +[[area.polygons]] +vertices = [ + [3141, 2790], + [3141, 2798], +] + +[[area.polygons]] +vertices = [[3395, 2791]] + +[[area.polygons]] +vertices = [ + [3140, 2792], + [3140, 2793], +] + +[[area.polygons]] +vertices = [ + [3169, 2794], + [3184, 2794], +] + +[[area.polygons]] +vertices = [[3392, 2794]] + +[[area.polygons]] +vertices = [ + [3169, 2795], + [3183, 2795], +] + +[[area.polygons]] +vertices = [ + [3169, 2796], + [3182, 2796], +] + +[[area.polygons]] +vertices = [[3390, 2796]] + +[[area.polygons]] +vertices = [ + [3169, 2797], + [3181, 2797], +] + +[[area.polygons]] +vertices = [ + [3169, 2798], + [3180, 2798], + [3180, 2816], + [3169, 2816], +] + +[[area.polygons]] +vertices = [ + [3386, 2799], + [3387, 2799], +] + +[[area.polygons]] +vertices = [ + [3181, 2800], + [3181, 2821], +] + +[[area.polygons]] +vertices = [[3386, 2800]] + +[[area.polygons]] +vertices = [ + [3182, 2801], + [3182, 2822], +] + +[[area.polygons]] +vertices = [ + [3183, 2802], + [3183, 2823], +] + +[[area.polygons]] +vertices = [ + [3184, 2803], + [3184, 2825], +] + +[[area.polygons]] +vertices = [ + [3185, 2804], + [3185, 2825], +] + +[[area.polygons]] +vertices = [ + [3186, 2805], + [3186, 2826], +] + +[[area.polygons]] +vertices = [ + [3187, 2806], + [3187, 2827], +] + +[[area.polygons]] +vertices = [ + [3188, 2807], + [3188, 2831], +] + +[[area.polygons]] +vertices = [ + [3144, 2808], + [3145, 2808], + [3145, 2812], + [3144, 2812], +] + +[[area.polygons]] +vertices = [ + [3189, 2808], + [3189, 2834], +] + +[[area.polygons]] +vertices = [ + [3190, 2809], + [3190, 2836], +] + +[[area.polygons]] +vertices = [ + [3191, 2810], + [3191, 2836], +] + +[[area.polygons]] +vertices = [ + [3224, 2810], + [3322, 2810], + [3322, 2940], + [3224, 2940], +] + +[[area.polygons]] +vertices = [ + [3192, 2811], + [3192, 2836], +] + +[[area.polygons]] +vertices = [ + [3223, 2811], + [3223, 3126], +] + +[[area.polygons]] +vertices = [[3143, 2812]] + +[[area.polygons]] +vertices = [[3153, 2812]] + +[[area.polygons]] +vertices = [ + [3158, 2812], + [3161, 2812], +] + +[[area.polygons]] +vertices = [ + [3193, 2812], + [3193, 2839], +] + +[[area.polygons]] +vertices = [ + [3222, 2812], + [3222, 3127], +] + +[[area.polygons]] +vertices = [ + [3159, 2813], + [3161, 2813], + [3161, 2815], + [3159, 2815], +] + +[[area.polygons]] +vertices = [ + [3194, 2813], + [3194, 2840], +] + +[[area.polygons]] +vertices = [ + [3221, 2813], + [3221, 3128], +] + +[[area.polygons]] +vertices = [ + [3195, 2814], + [3195, 2967], +] + +[[area.polygons]] +vertices = [ + [3220, 2814], + [3220, 3128], +] + +[[area.polygons]] +vertices = [ + [3196, 2815], + [3196, 2969], +] + +[[area.polygons]] +vertices = [ + [3219, 2815], + [3219, 3129], +] + +[[area.polygons]] +vertices = [[3160, 2816]] + +[[area.polygons]] +vertices = [ + [3197, 2816], + [3197, 2971], +] + +[[area.polygons]] +vertices = [ + [3218, 2816], + [3218, 3130], +] + +[[area.polygons]] +vertices = [ + [3173, 2817], + [3180, 2817], +] + +[[area.polygons]] +vertices = [ + [3198, 2817], + [3217, 2817], + [3217, 2973], + [3198, 2973], +] + +[[area.polygons]] +vertices = [ + [3174, 2818], + [3180, 2818], +] + +[[area.polygons]] +vertices = [ + [3176, 2819], + [3180, 2819], + [3180, 2820], + [3176, 2820], +] + +[[area.polygons]] +vertices = [ + [3179, 2821], + [3180, 2821], +] + +[[area.polygons]] +vertices = [ + [3385, 2822], + [3386, 2822], + [3386, 3137], + [3385, 3137], +] + +[[area.polygons]] +vertices = [ + [3348, 2823], + [3353, 2823], +] + +[[area.polygons]] +vertices = [ + [3387, 2823], + [3388, 2823], + [3388, 3139], + [3387, 3139], +] + +[[area.polygons]] +vertices = [ + [3348, 2824], + [3352, 2824], +] + +[[area.polygons]] +vertices = [ + [3389, 2824], + [3389, 3141], +] + +[[area.polygons]] +vertices = [ + [3348, 2825], + [3351, 2825], + [3351, 2826], + [3348, 2826], +] + +[[area.polygons]] +vertices = [ + [3348, 2827], + [3350, 2827], + [3350, 2828], + [3348, 2828], +] + +[[area.polygons]] +vertices = [[3379, 2827]] + +[[area.polygons]] +vertices = [ + [3348, 2829], + [3349, 2829], +] + +[[area.polygons]] +vertices = [[3381, 2829]] + +[[area.polygons]] +vertices = [ + [3348, 2830], + [3348, 2831], +] + +[[area.polygons]] +vertices = [ + [3390, 2831], + [3390, 3142], +] + +[[area.polygons]] +vertices = [ + [3391, 2832], + [3391, 3143], +] + +[[area.polygons]] +vertices = [ + [3392, 2833], + [3392, 2908], +] + +[[area.polygons]] +vertices = [ + [3323, 2834], + [3346, 2834], +] + +[[area.polygons]] +vertices = [ + [3393, 2834], + [3393, 2907], +] + +[[area.polygons]] +vertices = [ + [3323, 2835], + [3345, 2835], + [3345, 2927], + [3323, 2927], +] + +[[area.polygons]] +vertices = [ + [3394, 2835], + [3394, 2906], +] + +[[area.polygons]] +vertices = [ + [3395, 2836], + [3395, 2905], +] + +[[area.polygons]] +vertices = [ + [3396, 2837], + [3396, 2904], +] + +[[area.polygons]] +vertices = [ + [3397, 2838], + [3397, 2903], +] + +[[area.polygons]] +vertices = [ + [3398, 2839], + [3398, 2902], +] + +[[area.polygons]] +vertices = [ + [3399, 2840], + [3399, 2901], +] + +[[area.polygons]] +vertices = [ + [3400, 2841], + [3400, 2900], +] + +[[area.polygons]] +vertices = [ + [3401, 2843], + [3401, 2899], +] + +[[area.polygons]] +vertices = [ + [3194, 2845], + [3194, 2965], +] + +[[area.polygons]] +vertices = [ + [3402, 2845], + [3402, 2898], +] + +[[area.polygons]] +vertices = [ + [3193, 2846], + [3193, 2870], +] + +[[area.polygons]] +vertices = [ + [3403, 2847], + [3403, 2897], +] + +[[area.polygons]] +vertices = [ + [3404, 2849], + [3404, 2896], +] + +[[area.polygons]] +vertices = [ + [3405, 2850], + [3405, 2895], +] + +[[area.polygons]] +vertices = [ + [3190, 2851], + [3192, 2851], + [3192, 2868], + [3190, 2868], +] + +[[area.polygons]] +vertices = [ + [3346, 2851], + [3346, 2927], +] + +[[area.polygons]] +vertices = [ + [3189, 2852], + [3189, 2868], +] + +[[area.polygons]] +vertices = [ + [3347, 2852], + [3347, 2927], +] + +[[area.polygons]] +vertices = [ + [3406, 2852], + [3406, 2894], +] + +[[area.polygons]] +vertices = [ + [3187, 2853], + [3188, 2853], + [3188, 2867], + [3187, 2867], +] + +[[area.polygons]] +vertices = [ + [3348, 2853], + [3348, 2927], +] + +[[area.polygons]] +vertices = [ + [3381, 2853], + [3381, 2963], +] + +[[area.polygons]] +vertices = [ + [3407, 2853], + [3407, 2893], +] + +[[area.polygons]] +vertices = [ + [3186, 2854], + [3186, 2866], +] + +[[area.polygons]] +vertices = [ + [3349, 2854], + [3349, 2927], +] + +[[area.polygons]] +vertices = [ + [3380, 2854], + [3380, 2960], +] + +[[area.polygons]] +vertices = [ + [3350, 2855], + [3350, 2928], +] + +[[area.polygons]] +vertices = [ + [3379, 2855], + [3379, 2959], +] + +[[area.polygons]] +vertices = [ + [3408, 2855], + [3408, 2892], +] + +[[area.polygons]] +vertices = [ + [3351, 2856], + [3351, 2929], +] + +[[area.polygons]] +vertices = [ + [3378, 2856], + [3378, 2959], +] + +[[area.polygons]] +vertices = [ + [3409, 2856], + [3409, 2891], +] + +[[area.polygons]] +vertices = [ + [3185, 2857], + [3185, 2866], +] + +[[area.polygons]] +vertices = [ + [3352, 2857], + [3352, 2930], +] + +[[area.polygons]] +vertices = [ + [3377, 2857], + [3377, 2959], +] + +[[area.polygons]] +vertices = [ + [3410, 2857], + [3410, 2890], +] + +[[area.polygons]] +vertices = [ + [3182, 2858], + [3184, 2858], + [3184, 2864], + [3182, 2864], +] + +[[area.polygons]] +vertices = [ + [3353, 2858], + [3353, 2931], +] + +[[area.polygons]] +vertices = [ + [3376, 2858], + [3376, 2959], +] + +[[area.polygons]] +vertices = [ + [3181, 2859], + [3181, 2864], +] + +[[area.polygons]] +vertices = [ + [3354, 2859], + [3354, 2932], +] + +[[area.polygons]] +vertices = [ + [3375, 2859], + [3375, 2959], +] + +[[area.polygons]] +vertices = [ + [3411, 2859], + [3411, 2889], +] + +[[area.polygons]] +vertices = [ + [3179, 2860], + [3180, 2860], + [3180, 2863], + [3179, 2863], +] + +[[area.polygons]] +vertices = [ + [3355, 2860], + [3374, 2860], + [3374, 2933], + [3355, 2933], +] + +[[area.polygons]] +vertices = [ + [3412, 2860], + [3412, 2888], +] + +[[area.polygons]] +vertices = [ + [3176, 2861], + [3178, 2861], +] + +[[area.polygons]] +vertices = [ + [3413, 2861], + [3438, 2861], +] + +[[area.polygons]] +vertices = [ + [3177, 2862], + [3178, 2862], +] + +[[area.polygons]] +vertices = [ + [3413, 2862], + [3437, 2862], +] + +[[area.polygons]] +vertices = [ + [3413, 2863], + [3436, 2863], +] + +[[area.polygons]] +vertices = [ + [3146, 2864], + [3148, 2864], + [3148, 2940], + [3146, 2940], +] + +[[area.polygons]] +vertices = [ + [3413, 2864], + [3435, 2864], +] + +[[area.polygons]] +vertices = [ + [3183, 2865], + [3184, 2865], +] + +[[area.polygons]] +vertices = [ + [3413, 2865], + [3434, 2865], +] + +[[area.polygons]] +vertices = [ + [3145, 2866], + [3145, 2918], +] + +[[area.polygons]] +vertices = [ + [3413, 2866], + [3433, 2866], +] + +[[area.polygons]] +vertices = [ + [3144, 2867], + [3144, 2917], +] + +[[area.polygons]] +vertices = [ + [3413, 2867], + [3432, 2867], +] + +[[area.polygons]] +vertices = [ + [3143, 2868], + [3143, 2917], +] + +[[area.polygons]] +vertices = [ + [3149, 2868], + [3149, 2941], +] + +[[area.polygons]] +vertices = [ + [3413, 2868], + [3431, 2868], +] + +[[area.polygons]] +vertices = [ + [3142, 2869], + [3142, 2904], +] + +[[area.polygons]] +vertices = [ + [3150, 2869], + [3150, 2941], +] + +[[area.polygons]] +vertices = [ + [3191, 2869], + [3192, 2869], +] + +[[area.polygons]] +vertices = [ + [3413, 2869], + [3430, 2869], +] + +[[area.polygons]] +vertices = [ + [3141, 2870], + [3141, 2903], +] + +[[area.polygons]] +vertices = [ + [3151, 2870], + [3151, 2950], +] + +[[area.polygons]] +vertices = [ + [3413, 2870], + [3429, 2870], + [3429, 2871], + [3413, 2871], +] + +[[area.polygons]] +vertices = [ + [3152, 2871], + [3152, 2950], +] + +[[area.polygons]] +vertices = [ + [3140, 2872], + [3140, 2903], +] + +[[area.polygons]] +vertices = [ + [3153, 2872], + [3154, 2872], + [3154, 2949], + [3153, 2949], +] + +[[area.polygons]] +vertices = [ + [3413, 2872], + [3428, 2872], +] + +[[area.polygons]] +vertices = [ + [3139, 2873], + [3139, 2884], +] + +[[area.polygons]] +vertices = [ + [3155, 2873], + [3155, 2948], +] + +[[area.polygons]] +vertices = [ + [3413, 2873], + [3427, 2873], +] + +[[area.polygons]] +vertices = [ + [3138, 2874], + [3138, 2882], +] + +[[area.polygons]] +vertices = [ + [3156, 2874], + [3156, 2948], +] + +[[area.polygons]] +vertices = [ + [3413, 2874], + [3426, 2874], +] + +[[area.polygons]] +vertices = [ + [3137, 2875], + [3137, 2881], +] + +[[area.polygons]] +vertices = [ + [3157, 2875], + [3193, 2875], + [3193, 2948], + [3157, 2948], +] + +[[area.polygons]] +vertices = [ + [3413, 2875], + [3425, 2875], +] + +[[area.polygons]] +vertices = [ + [3136, 2876], + [3136, 2880], +] + +[[area.polygons]] +vertices = [ + [3413, 2876], + [3424, 2876], +] + +[[area.polygons]] +vertices = [ + [3135, 2877], + [3135, 2879], +] + +[[area.polygons]] +vertices = [ + [3413, 2877], + [3423, 2877], +] + +[[area.polygons]] +vertices = [ + [3413, 2878], + [3422, 2878], +] + +[[area.polygons]] +vertices = [ + [3413, 2879], + [3421, 2879], +] + +[[area.polygons]] +vertices = [ + [3413, 2880], + [3420, 2880], +] + +[[area.polygons]] +vertices = [ + [3413, 2881], + [3419, 2881], +] + +[[area.polygons]] +vertices = [ + [3413, 2882], + [3418, 2882], +] + +[[area.polygons]] +vertices = [ + [3413, 2883], + [3417, 2883], +] + +[[area.polygons]] +vertices = [ + [3413, 2884], + [3416, 2884], +] + +[[area.polygons]] +vertices = [ + [3413, 2885], + [3415, 2885], +] + +[[area.polygons]] +vertices = [ + [3413, 2886], + [3414, 2886], +] + +[[area.polygons]] +vertices = [[3413, 2887]] + +[[area.polygons]] +vertices = [ + [3139, 2889], + [3139, 2902], +] + +[[area.polygons]] +vertices = [ + [3138, 2890], + [3138, 2901], +] + +[[area.polygons]] +vertices = [ + [3137, 2892], + [3137, 2897], +] + +[[area.polygons]] +vertices = [[3136, 2895]] + +[[area.polygons]] +vertices = [[3137, 2900]] + +[[area.polygons]] +vertices = [ + [3142, 2906], + [3142, 2918], +] + +[[area.polygons]] +vertices = [ + [3140, 2907], + [3141, 2907], + [3141, 2911], + [3140, 2911], +] + +[[area.polygons]] +vertices = [ + [3139, 2908], + [3139, 2910], +] + +[[area.polygons]] +vertices = [[3141, 2912]] + +[[area.polygons]] +vertices = [ + [3141, 2914], + [3141, 2918], +] + +[[area.polygons]] +vertices = [ + [3139, 2915], + [3140, 2915], + [3140, 2918], + [3139, 2918], +] + +[[area.polygons]] +vertices = [[3138, 2917]] + +[[area.polygons]] +vertices = [ + [3145, 2920], + [3145, 2941], +] + +[[area.polygons]] +vertices = [ + [3144, 2921], + [3144, 2941], +] + +[[area.polygons]] +vertices = [ + [3143, 2922], + [3143, 2941], +] + +[[area.polygons]] +vertices = [ + [3142, 2923], + [3142, 2940], +] + +[[area.polygons]] +vertices = [ + [3141, 2924], + [3141, 2940], +] + +[[area.polygons]] +vertices = [ + [3140, 2926], + [3140, 2938], +] + +[[area.polygons]] +vertices = [ + [3323, 2928], + [3334, 2928], +] + +[[area.polygons]] +vertices = [ + [3323, 2929], + [3333, 2929], +] + +[[area.polygons]] +vertices = [ + [3139, 2930], + [3139, 2936], +] + +[[area.polygons]] +vertices = [ + [3323, 2930], + [3332, 2930], +] + +[[area.polygons]] +vertices = [ + [3138, 2931], + [3138, 2934], +] + +[[area.polygons]] +vertices = [ + [3323, 2931], + [3331, 2931], +] + +[[area.polygons]] +vertices = [ + [3137, 2932], + [3137, 2933], +] + +[[area.polygons]] +vertices = [ + [3323, 2932], + [3330, 2932], +] + +[[area.polygons]] +vertices = [ + [3323, 2933], + [3329, 2933], +] + +[[area.polygons]] +vertices = [ + [3323, 2934], + [3328, 2934], +] + +[[area.polygons]] +vertices = [ + [3356, 2934], + [3374, 2934], +] + +[[area.polygons]] +vertices = [ + [3392, 2934], + [3392, 3144], +] + +[[area.polygons]] +vertices = [ + [3323, 2935], + [3327, 2935], +] + +[[area.polygons]] +vertices = [ + [3357, 2935], + [3374, 2935], +] + +[[area.polygons]] +vertices = [ + [3393, 2935], + [3393, 3145], +] + +[[area.polygons]] +vertices = [ + [3323, 2936], + [3326, 2936], +] + +[[area.polygons]] +vertices = [ + [3358, 2936], + [3374, 2936], +] + +[[area.polygons]] +vertices = [ + [3394, 2936], + [3394, 3164], +] + +[[area.polygons]] +vertices = [ + [3323, 2937], + [3325, 2937], +] + +[[area.polygons]] +vertices = [ + [3359, 2937], + [3374, 2937], +] + +[[area.polygons]] +vertices = [ + [3395, 2937], + [3395, 3164], +] + +[[area.polygons]] +vertices = [ + [3323, 2938], + [3324, 2938], +] + +[[area.polygons]] +vertices = [ + [3360, 2938], + [3374, 2938], + [3374, 2946], + [3360, 2946], +] + +[[area.polygons]] +vertices = [ + [3396, 2938], + [3396, 3164], +] + +[[area.polygons]] +vertices = [[3323, 2939]] + +[[area.polygons]] +vertices = [ + [3397, 2939], + [3397, 3164], +] + +[[area.polygons]] +vertices = [ + [3398, 2940], + [3398, 3164], +] + +[[area.polygons]] +vertices = [ + [3146, 2941], + [3147, 2941], +] + +[[area.polygons]] +vertices = [ + [3224, 2941], + [3321, 2941], +] + +[[area.polygons]] +vertices = [ + [3399, 2941], + [3399, 3164], +] + +[[area.polygons]] +vertices = [ + [3224, 2942], + [3320, 2942], +] + +[[area.polygons]] +vertices = [ + [3400, 2942], + [3400, 3163], +] + +[[area.polygons]] +vertices = [ + [3224, 2943], + [3319, 2943], +] + +[[area.polygons]] +vertices = [ + [3401, 2943], + [3401, 3162], +] + +[[area.polygons]] +vertices = [ + [3224, 2944], + [3318, 2944], + [3318, 3116], + [3224, 3116], +] + +[[area.polygons]] +vertices = [ + [3402, 2944], + [3402, 3162], +] + +[[area.polygons]] +vertices = [ + [3403, 2945], + [3403, 3161], +] + +[[area.polygons]] +vertices = [ + [3150, 2946], + [3150, 2950], +] + +[[area.polygons]] +vertices = [ + [3404, 2946], + [3404, 3161], +] + +[[area.polygons]] +vertices = [ + [3361, 2947], + [3374, 2947], +] + +[[area.polygons]] +vertices = [ + [3405, 2947], + [3405, 3161], +] + +[[area.polygons]] +vertices = [ + [3493, 2947], + [3498, 2947], + [3498, 2948], + [3493, 2948], +] + +[[area.polygons]] +vertices = [ + [3149, 2948], + [3149, 2949], +] + +[[area.polygons]] +vertices = [ + [3362, 2948], + [3374, 2948], +] + +[[area.polygons]] +vertices = [ + [3406, 2948], + [3406, 3161], +] + +[[area.polygons]] +vertices = [ + [3463, 2948], + [3471, 2948], + [3471, 2979], + [3463, 2979], +] + +[[area.polygons]] +vertices = [ + [3499, 2948], + [3499, 2951], +] + +[[area.polygons]] +vertices = [ + [3158, 2949], + [3193, 2949], + [3193, 2950], + [3158, 2950], +] + +[[area.polygons]] +vertices = [ + [3363, 2949], + [3374, 2949], +] + +[[area.polygons]] +vertices = [ + [3407, 2949], + [3407, 3161], +] + +[[area.polygons]] +vertices = [ + [3462, 2949], + [3462, 3082], +] + +[[area.polygons]] +vertices = [ + [3472, 2949], + [3472, 2953], +] + +[[area.polygons]] +vertices = [ + [3495, 2949], + [3498, 2949], +] + +[[area.polygons]] +vertices = [ + [3500, 2949], + [3501, 2949], + [3501, 2952], + [3500, 2952], +] + +[[area.polygons]] +vertices = [[3153, 2950]] + +[[area.polygons]] +vertices = [ + [3364, 2950], + [3374, 2950], +] + +[[area.polygons]] +vertices = [ + [3408, 2950], + [3461, 2950], + [3461, 3082], + [3408, 3082], +] + +[[area.polygons]] +vertices = [ + [3497, 2950], + [3498, 2950], +] + +[[area.polygons]] +vertices = [ + [3502, 2950], + [3503, 2950], + [3503, 3000], + [3502, 3000], +] + +[[area.polygons]] +vertices = [ + [3158, 2951], + [3173, 2951], +] + +[[area.polygons]] +vertices = [ + [3188, 2951], + [3193, 2951], + [3193, 2953], + [3188, 2953], +] + +[[area.polygons]] +vertices = [ + [3366, 2951], + [3374, 2951], +] + +[[area.polygons]] +vertices = [ + [3504, 2951], + [3505, 2951], + [3505, 2994], + [3504, 2994], +] + +[[area.polygons]] +vertices = [ + [3158, 2952], + [3172, 2952], +] + +[[area.polygons]] +vertices = [ + [3367, 2952], + [3374, 2952], +] + +[[area.polygons]] +vertices = [ + [3506, 2952], + [3507, 2952], + [3507, 2994], + [3506, 2994], +] + +[[area.polygons]] +vertices = [ + [3157, 2953], + [3171, 2953], +] + +[[area.polygons]] +vertices = [ + [3368, 2953], + [3374, 2953], +] + +[[area.polygons]] +vertices = [ + [3508, 2953], + [3508, 2995], +] + +[[area.polygons]] +vertices = [ + [3514, 2953], + [3518, 2953], + [3518, 2987], + [3514, 2987], +] + +[[area.polygons]] +vertices = [ + [3151, 2954], + [3170, 2954], +] + +[[area.polygons]] +vertices = [ + [3189, 2954], + [3193, 2954], + [3193, 2955], + [3189, 2955], +] + +[[area.polygons]] +vertices = [ + [3369, 2954], + [3374, 2954], +] + +[[area.polygons]] +vertices = [ + [3501, 2954], + [3501, 2999], +] + +[[area.polygons]] +vertices = [ + [3509, 2954], + [3513, 2954], + [3513, 2994], + [3509, 2994], +] + +[[area.polygons]] +vertices = [ + [3151, 2955], + [3169, 2955], +] + +[[area.polygons]] +vertices = [ + [3370, 2955], + [3374, 2955], +] + +[[area.polygons]] +vertices = [ + [3497, 2955], + [3500, 2955], + [3500, 2985], + [3497, 2985], +] + +[[area.polygons]] +vertices = [ + [3519, 2955], + [3522, 2955], + [3522, 2983], + [3519, 2983], +] + +[[area.polygons]] +vertices = [ + [3151, 2956], + [3161, 2956], +] + +[[area.polygons]] +vertices = [ + [3190, 2956], + [3193, 2956], + [3193, 2957], + [3190, 2957], +] + +[[area.polygons]] +vertices = [ + [3372, 2956], + [3374, 2956], +] + +[[area.polygons]] +vertices = [ + [3472, 2956], + [3472, 2977], +] + +[[area.polygons]] +vertices = [ + [3496, 2956], + [3496, 2983], +] + +[[area.polygons]] +vertices = [ + [3523, 2956], + [3528, 2956], + [3528, 2980], + [3523, 2980], +] + +[[area.polygons]] +vertices = [ + [3150, 2957], + [3160, 2957], +] + +[[area.polygons]] +vertices = [ + [3373, 2957], + [3374, 2957], +] + +[[area.polygons]] +vertices = [ + [3473, 2957], + [3473, 2976], +] + +[[area.polygons]] +vertices = [ + [3495, 2957], + [3495, 2982], +] + +[[area.polygons]] +vertices = [ + [3529, 2957], + [3529, 2979], +] + +[[area.polygons]] +vertices = [ + [3154, 2958], + [3159, 2958], +] + +[[area.polygons]] +vertices = [ + [3191, 2958], + [3193, 2958], + [3193, 2959], + [3191, 2959], +] + +[[area.polygons]] +vertices = [[3374, 2958]] + +[[area.polygons]] +vertices = [ + [3474, 2958], + [3474, 2976], +] + +[[area.polygons]] +vertices = [ + [3494, 2958], + [3494, 2982], +] + +[[area.polygons]] +vertices = [ + [3530, 2958], + [3530, 2970], +] + +[[area.polygons]] +vertices = [ + [3157, 2959], + [3158, 2959], +] + +[[area.polygons]] +vertices = [ + [3531, 2959], + [3531, 2969], +] + +[[area.polygons]] +vertices = [ + [3192, 2960], + [3193, 2960], + [3193, 2961], + [3192, 2961], +] + +[[area.polygons]] +vertices = [ + [3493, 2960], + [3493, 2981], +] + +[[area.polygons]] +vertices = [ + [3532, 2960], + [3532, 2967], +] + +[[area.polygons]] +vertices = [ + [3492, 2961], + [3492, 2980], +] + +[[area.polygons]] +vertices = [ + [3533, 2961], + [3535, 2961], + [3535, 2964], + [3533, 2964], +] + +[[area.polygons]] +vertices = [ + [3193, 2962], + [3193, 2963], +] + +[[area.polygons]] +vertices = [ + [3491, 2962], + [3491, 2980], +] + +[[area.polygons]] +vertices = [ + [3536, 2962], + [3536, 2963], +] + +[[area.polygons]] +vertices = [ + [3533, 2965], + [3534, 2965], +] + +[[area.polygons]] +vertices = [[3533, 2966]] + +[[area.polygons]] +vertices = [ + [3319, 2968], + [3319, 3135], +] + +[[area.polygons]] +vertices = [ + [3475, 2968], + [3475, 2976], +] + +[[area.polygons]] +vertices = [ + [3483, 2968], + [3483, 2972], +] + +[[area.polygons]] +vertices = [ + [3320, 2969], + [3320, 3135], +] + +[[area.polygons]] +vertices = [ + [3476, 2969], + [3476, 2977], +] + +[[area.polygons]] +vertices = [ + [3480, 2969], + [3482, 2969], + [3482, 2972], + [3480, 2972], +] + +[[area.polygons]] +vertices = [ + [3484, 2969], + [3484, 2973], +] + +[[area.polygons]] +vertices = [ + [3321, 2970], + [3321, 3135], +] + +[[area.polygons]] +vertices = [ + [3479, 2970], + [3479, 2980], +] + +[[area.polygons]] +vertices = [ + [3485, 2970], + [3485, 2973], +] + +[[area.polygons]] +vertices = [ + [3322, 2971], + [3322, 3135], +] + +[[area.polygons]] +vertices = [ + [3477, 2971], + [3478, 2971], + [3478, 2981], + [3477, 2981], +] + +[[area.polygons]] +vertices = [ + [3486, 2971], + [3490, 2971], + [3490, 2973], + [3486, 2973], +] + +[[area.polygons]] +vertices = [ + [3323, 2972], + [3323, 3135], +] + +[[area.polygons]] +vertices = [ + [3530, 2972], + [3530, 2976], +] + +[[area.polygons]] +vertices = [ + [3324, 2973], + [3324, 3135], +] + +[[area.polygons]] +vertices = [ + [3480, 2973], + [3480, 2978], +] + +[[area.polygons]] +vertices = [ + [3199, 2974], + [3217, 2974], + [3217, 3067], + [3199, 3067], +] + +[[area.polygons]] +vertices = [ + [3325, 2974], + [3325, 3135], +] + +[[area.polygons]] +vertices = [ + [3490, 2974], + [3490, 2979], +] + +[[area.polygons]] +vertices = [ + [3326, 2975], + [3326, 3135], +] + +[[area.polygons]] +vertices = [ + [3481, 2975], + [3481, 2979], +] + +[[area.polygons]] +vertices = [ + [3327, 2976], + [3327, 3135], +] + +[[area.polygons]] +vertices = [ + [3328, 2977], + [3328, 3137], +] + +[[area.polygons]] +vertices = [ + [3482, 2977], + [3483, 2977], + [3483, 2979], + [3482, 2979], +] + +[[area.polygons]] +vertices = [ + [3198, 2978], + [3198, 3068], +] + +[[area.polygons]] +vertices = [ + [3329, 2978], + [3329, 3137], +] + +[[area.polygons]] +vertices = [ + [3484, 2978], + [3485, 2978], + [3485, 2980], + [3484, 2980], +] + +[[area.polygons]] +vertices = [ + [3197, 2979], + [3197, 3067], +] + +[[area.polygons]] +vertices = [ + [3330, 2979], + [3330, 3138], +] + +[[area.polygons]] +vertices = [ + [3486, 2979], + [3486, 2980], +] + +[[area.polygons]] +vertices = [ + [3331, 2980], + [3331, 3140], +] + +[[area.polygons]] +vertices = [ + [3463, 2980], + [3470, 2980], + [3470, 2982], + [3463, 2982], +] + +[[area.polygons]] +vertices = [ + [3476, 2980], + [3476, 2983], +] + +[[area.polygons]] +vertices = [[3483, 2980]] + +[[area.polygons]] +vertices = [ + [3196, 2981], + [3196, 3067], +] + +[[area.polygons]] +vertices = [ + [3332, 2981], + [3332, 3144], +] + +[[area.polygons]] +vertices = [ + [3474, 2981], + [3475, 2981], + [3475, 2984], + [3474, 2984], +] + +[[area.polygons]] +vertices = [ + [3523, 2981], + [3527, 2981], +] + +[[area.polygons]] +vertices = [ + [3473, 2982], + [3473, 2984], +] + +[[area.polygons]] +vertices = [[3477, 2982]] + +[[area.polygons]] +vertices = [ + [3523, 2982], + [3526, 2982], +] + +[[area.polygons]] +vertices = [ + [3463, 2983], + [3469, 2983], + [3469, 3074], + [3463, 3074], +] + +[[area.polygons]] +vertices = [[3472, 2983]] + +[[area.polygons]] +vertices = [[3523, 2983]] + +[[area.polygons]] +vertices = [ + [3519, 2984], + [3521, 2984], +] + +[[area.polygons]] +vertices = [ + [3382, 2985], + [3382, 3135], +] + +[[area.polygons]] +vertices = [ + [3519, 2985], + [3520, 2985], +] + +[[area.polygons]] +vertices = [ + [3498, 2986], + [3500, 2986], + [3500, 2994], + [3498, 2994], +] + +[[area.polygons]] +vertices = [[3519, 2986]] + +[[area.polygons]] +vertices = [ + [3381, 2988], + [3381, 3135], +] + +[[area.polygons]] +vertices = [ + [3497, 2988], + [3497, 2991], +] + +[[area.polygons]] +vertices = [ + [3514, 2988], + [3517, 2988], + [3517, 2989], + [3514, 2989], +] + +[[area.polygons]] +vertices = [ + [3514, 2990], + [3516, 2990], + [3516, 2993], + [3514, 2993], +] + +[[area.polygons]] +vertices = [ + [3380, 2991], + [3380, 3135], +] + +[[area.polygons]] +vertices = [ + [3470, 2993], + [3470, 3074], +] + +[[area.polygons]] +vertices = [ + [3379, 2994], + [3379, 3135], +] + +[[area.polygons]] +vertices = [ + [3514, 2994], + [3515, 2994], +] + +[[area.polygons]] +vertices = [ + [3378, 2995], + [3378, 3135], +] + +[[area.polygons]] +vertices = [ + [3499, 2995], + [3500, 2995], + [3500, 2996], + [3499, 2996], +] + +[[area.polygons]] +vertices = [ + [3504, 2995], + [3504, 3001], +] + +[[area.polygons]] +vertices = [ + [3509, 2995], + [3510, 2995], +] + +[[area.polygons]] +vertices = [ + [3377, 2996], + [3377, 3135], +] + +[[area.polygons]] +vertices = [ + [3376, 2997], + [3376, 3135], +] + +[[area.polygons]] +vertices = [ + [3500, 2997], + [3500, 2998], +] + +[[area.polygons]] +vertices = [ + [3505, 2997], + [3505, 3001], +] + +[[area.polygons]] +vertices = [ + [3195, 2998], + [3195, 3067], +] + +[[area.polygons]] +vertices = [ + [3375, 2998], + [3375, 3135], +] + +[[area.polygons]] +vertices = [ + [3471, 2998], + [3471, 3074], +] + +[[area.polygons]] +vertices = [ + [3194, 3000], + [3194, 3066], +] + +[[area.polygons]] +vertices = [ + [3374, 3000], + [3374, 3135], +] + +[[area.polygons]] +vertices = [ + [3506, 3000], + [3506, 3001], +] + +[[area.polygons]] +vertices = [ + [3193, 3001], + [3193, 3065], +] + +[[area.polygons]] +vertices = [ + [3333, 3002], + [3333, 3146], +] + +[[area.polygons]] +vertices = [ + [3373, 3002], + [3373, 3135], +] + +[[area.polygons]] +vertices = [ + [3472, 3002], + [3472, 3071], +] + +[[area.polygons]] +vertices = [ + [3192, 3003], + [3192, 3065], +] + +[[area.polygons]] +vertices = [ + [3473, 3003], + [3473, 3070], +] + +[[area.polygons]] +vertices = [ + [3181, 3004], + [3191, 3004], + [3191, 3063], + [3181, 3063], +] + +[[area.polygons]] +vertices = [ + [3334, 3004], + [3334, 3151], +] + +[[area.polygons]] +vertices = [ + [3372, 3004], + [3372, 3135], +] + +[[area.polygons]] +vertices = [ + [3474, 3004], + [3475, 3004], + [3475, 3068], + [3474, 3068], +] + +[[area.polygons]] +vertices = [ + [3178, 3005], + [3180, 3005], + [3180, 3060], + [3178, 3060], +] + +[[area.polygons]] +vertices = [ + [3476, 3005], + [3476, 3068], +] + +[[area.polygons]] +vertices = [ + [3175, 3006], + [3177, 3006], + [3177, 3059], + [3175, 3059], +] + +[[area.polygons]] +vertices = [ + [3335, 3006], + [3335, 3151], +] + +[[area.polygons]] +vertices = [ + [3371, 3006], + [3371, 3135], +] + +[[area.polygons]] +vertices = [ + [3477, 3006], + [3477, 3068], +] + +[[area.polygons]] +vertices = [ + [3172, 3007], + [3174, 3007], + [3174, 3057], + [3172, 3057], +] + +[[area.polygons]] +vertices = [ + [3478, 3007], + [3478, 3068], +] + +[[area.polygons]] +vertices = [ + [3169, 3008], + [3171, 3008], + [3171, 3057], + [3169, 3057], +] + +[[area.polygons]] +vertices = [ + [3336, 3008], + [3336, 3159], +] + +[[area.polygons]] +vertices = [ + [3370, 3008], + [3370, 3135], +] + +[[area.polygons]] +vertices = [ + [3479, 3008], + [3480, 3008], + [3480, 3068], + [3479, 3068], +] + +[[area.polygons]] +vertices = [ + [3166, 3009], + [3168, 3009], + [3168, 3058], + [3166, 3058], +] + +[[area.polygons]] +vertices = [ + [3481, 3009], + [3482, 3009], + [3482, 3068], + [3481, 3068], +] + +[[area.polygons]] +vertices = [ + [3163, 3010], + [3165, 3010], + [3165, 3059], + [3163, 3059], +] + +[[area.polygons]] +vertices = [ + [3337, 3010], + [3337, 3159], +] + +[[area.polygons]] +vertices = [ + [3369, 3010], + [3369, 3135], +] + +[[area.polygons]] +vertices = [ + [3483, 3010], + [3489, 3010], + [3489, 3069], + [3483, 3069], +] + +[[area.polygons]] +vertices = [ + [3504, 3010], + [3507, 3010], + [3507, 3066], + [3504, 3066], +] + +[[area.polygons]] +vertices = [ + [3160, 3011], + [3162, 3011], + [3162, 3019], + [3160, 3019], +] + +[[area.polygons]] +vertices = [ + [3490, 3011], + [3490, 3069], +] + +[[area.polygons]] +vertices = [ + [3503, 3011], + [3503, 3068], +] + +[[area.polygons]] +vertices = [ + [3508, 3011], + [3509, 3011], + [3509, 3066], + [3508, 3066], +] + +[[area.polygons]] +vertices = [ + [3157, 3012], + [3159, 3012], + [3159, 3016], + [3157, 3016], +] + +[[area.polygons]] +vertices = [ + [3338, 3012], + [3338, 3159], +] + +[[area.polygons]] +vertices = [ + [3368, 3012], + [3368, 3135], +] + +[[area.polygons]] +vertices = [ + [3491, 3012], + [3494, 3012], + [3494, 3069], + [3491, 3069], +] + +[[area.polygons]] +vertices = [ + [3502, 3012], + [3502, 3069], +] + +[[area.polygons]] +vertices = [ + [3510, 3012], + [3510, 3032], +] + +[[area.polygons]] +vertices = [ + [3154, 3013], + [3156, 3013], +] + +[[area.polygons]] +vertices = [ + [3339, 3013], + [3339, 3159], +] + +[[area.polygons]] +vertices = [ + [3495, 3013], + [3497, 3013], + [3497, 3069], + [3495, 3069], +] + +[[area.polygons]] +vertices = [ + [3501, 3013], + [3501, 3069], +] + +[[area.polygons]] +vertices = [ + [3155, 3014], + [3156, 3014], +] + +[[area.polygons]] +vertices = [ + [3340, 3014], + [3340, 3159], +] + +[[area.polygons]] +vertices = [ + [3367, 3014], + [3367, 3135], +] + +[[area.polygons]] +vertices = [ + [3498, 3014], + [3500, 3014], + [3500, 3069], + [3498, 3069], +] + +[[area.polygons]] +vertices = [ + [3511, 3014], + [3511, 3029], +] + +[[area.polygons]] +vertices = [ + [3341, 3015], + [3341, 3159], +] + +[[area.polygons]] +vertices = [ + [3512, 3015], + [3512, 3024], +] + +[[area.polygons]] +vertices = [ + [3342, 3016], + [3342, 3159], +] + +[[area.polygons]] +vertices = [ + [3366, 3016], + [3366, 3135], +] + +[[area.polygons]] +vertices = [ + [3158, 3017], + [3159, 3017], +] + +[[area.polygons]] +vertices = [ + [3343, 3017], + [3343, 3159], +] + +[[area.polygons]] +vertices = [[3159, 3018]] + +[[area.polygons]] +vertices = [ + [3344, 3018], + [3344, 3159], +] + +[[area.polygons]] +vertices = [ + [3365, 3018], + [3365, 3135], +] + +[[area.polygons]] +vertices = [ + [3345, 3019], + [3345, 3159], +] + +[[area.polygons]] +vertices = [ + [3161, 3020], + [3162, 3020], + [3162, 3060], + [3161, 3060], +] + +[[area.polygons]] +vertices = [ + [3346, 3020], + [3347, 3020], + [3347, 3159], + [3346, 3159], +] + +[[area.polygons]] +vertices = [ + [3364, 3020], + [3364, 3135], +] + +[[area.polygons]] +vertices = [ + [3348, 3021], + [3363, 3021], + [3363, 3136], + [3348, 3136], +] + +[[area.polygons]] +vertices = [ + [3160, 3023], + [3160, 3060], +] + +[[area.polygons]] +vertices = [ + [3158, 3024], + [3159, 3024], + [3159, 3059], + [3158, 3059], +] + +[[area.polygons]] +vertices = [ + [3156, 3025], + [3157, 3025], + [3157, 3057], + [3156, 3057], +] + +[[area.polygons]] +vertices = [ + [3155, 3027], + [3155, 3056], +] + +[[area.polygons]] +vertices = [ + [3154, 3029], + [3154, 3055], +] + +[[area.polygons]] +vertices = [ + [3153, 3030], + [3153, 3054], +] + +[[area.polygons]] +vertices = [ + [3152, 3031], + [3152, 3052], +] + +[[area.polygons]] +vertices = [ + [3151, 3032], + [3151, 3051], +] + +[[area.polygons]] +vertices = [ + [3150, 3033], + [3150, 3050], +] + +[[area.polygons]] +vertices = [ + [3149, 3034], + [3149, 3049], +] + +[[area.polygons]] +vertices = [ + [3148, 3035], + [3148, 3048], +] + +[[area.polygons]] +vertices = [ + [3510, 3035], + [3510, 3039], +] + +[[area.polygons]] +vertices = [[3147, 3036]] + +[[area.polygons]] +vertices = [ + [3147, 3040], + [3147, 3045], +] + +[[area.polygons]] +vertices = [ + [3510, 3041], + [3510, 3066], +] + +[[area.polygons]] +vertices = [ + [3146, 3042], + [3146, 3043], +] + +[[area.polygons]] +vertices = [ + [3511, 3043], + [3512, 3043], + [3512, 3066], + [3511, 3066], +] + +[[area.polygons]] +vertices = [ + [3513, 3044], + [3513, 3066], +] + +[[area.polygons]] +vertices = [ + [3514, 3045], + [3514, 3066], +] + +[[area.polygons]] +vertices = [ + [3515, 3048], + [3515, 3049], +] + +[[area.polygons]] +vertices = [ + [3515, 3053], + [3518, 3053], + [3518, 3054], + [3515, 3054], +] + +[[area.polygons]] +vertices = [[3157, 3058]] + +[[area.polygons]] +vertices = [[3174, 3058]] + +[[area.polygons]] +vertices = [ + [3515, 3058], + [3515, 3063], +] + +[[area.polygons]] +vertices = [ + [3516, 3059], + [3516, 3060], +] + +[[area.polygons]] +vertices = [[3159, 3060]] + +[[area.polygons]] +vertices = [[3163, 3060]] + +[[area.polygons]] +vertices = [ + [3179, 3061], + [3180, 3061], +] + +[[area.polygons]] +vertices = [[3180, 3062]] + +[[area.polygons]] +vertices = [ + [3182, 3064], + [3191, 3064], +] + +[[area.polygons]] +vertices = [ + [3185, 3065], + [3191, 3065], +] + +[[area.polygons]] +vertices = [ + [3515, 3065], + [3515, 3066], +] + +[[area.polygons]] +vertices = [ + [3189, 3066], + [3191, 3066], +] + +[[area.polygons]] +vertices = [[3504, 3067]] + +[[area.polygons]] +vertices = [[3199, 3068]] + +[[area.polygons]] +vertices = [ + [3203, 3068], + [3217, 3068], +] + +[[area.polygons]] +vertices = [ + [3204, 3069], + [3217, 3069], + [3217, 3082], + [3204, 3082], +] + +[[area.polygons]] +vertices = [[3474, 3069]] + +[[area.polygons]] +vertices = [ + [3203, 3071], + [3203, 3077], +] + +[[area.polygons]] +vertices = [[3202, 3075]] + +[[area.polygons]] +vertices = [ + [3463, 3075], + [3468, 3075], +] + +[[area.polygons]] +vertices = [ + [3463, 3076], + [3467, 3076], +] + +[[area.polygons]] +vertices = [ + [3463, 3077], + [3466, 3077], +] + +[[area.polygons]] +vertices = [ + [3463, 3078], + [3465, 3078], +] + +[[area.polygons]] +vertices = [ + [3463, 3079], + [3464, 3079], +] + +[[area.polygons]] +vertices = [ + [3203, 3080], + [3203, 3081], +] + +[[area.polygons]] +vertices = [ + [3463, 3080], + [3463, 3082], +] + +[[area.polygons]] +vertices = [ + [3206, 3083], + [3217, 3083], + [3217, 3090], + [3206, 3090], +] + +[[area.polygons]] +vertices = [ + [3408, 3083], + [3458, 3083], +] + +[[area.polygons]] +vertices = [ + [3408, 3084], + [3457, 3084], +] + +[[area.polygons]] +vertices = [ + [3408, 3085], + [3456, 3085], +] + +[[area.polygons]] +vertices = [ + [3205, 3086], + [3205, 3089], +] + +[[area.polygons]] +vertices = [ + [3408, 3086], + [3455, 3086], + [3455, 3148], + [3408, 3148], +] + +[[area.polygons]] +vertices = [[3204, 3088]] + +[[area.polygons]] +vertices = [ + [3207, 3091], + [3217, 3091], + [3217, 3128], + [3207, 3128], +] + +[[area.polygons]] +vertices = [ + [3206, 3097], + [3206, 3099], +] + +[[area.polygons]] +vertices = [ + [3206, 3105], + [3206, 3126], +] + +[[area.polygons]] +vertices = [ + [3205, 3107], + [3205, 3126], +] + +[[area.polygons]] +vertices = [ + [3204, 3109], + [3204, 3110], +] + +[[area.polygons]] +vertices = [ + [3204, 3113], + [3204, 3123], +] + +[[area.polygons]] +vertices = [ + [3203, 3115], + [3203, 3118], +] + +[[area.polygons]] +vertices = [ + [3224, 3117], + [3301, 3117], +] + +[[area.polygons]] +vertices = [ + [3305, 3117], + [3318, 3117], +] + +[[area.polygons]] +vertices = [ + [3224, 3118], + [3299, 3118], +] + +[[area.polygons]] +vertices = [ + [3307, 3118], + [3318, 3118], +] + +[[area.polygons]] +vertices = [ + [3224, 3119], + [3297, 3119], +] + +[[area.polygons]] +vertices = [ + [3309, 3119], + [3318, 3119], +] + +[[area.polygons]] +vertices = [ + [3224, 3120], + [3231, 3120], +] + +[[area.polygons]] +vertices = [ + [3238, 3120], + [3297, 3120], +] + +[[area.polygons]] +vertices = [ + [3310, 3120], + [3318, 3120], + [3318, 3121], + [3310, 3121], +] + +[[area.polygons]] +vertices = [ + [3224, 3121], + [3229, 3121], +] + +[[area.polygons]] +vertices = [ + [3239, 3121], + [3296, 3121], +] + +[[area.polygons]] +vertices = [ + [3224, 3122], + [3228, 3122], +] + +[[area.polygons]] +vertices = [ + [3240, 3122], + [3294, 3122], +] + +[[area.polygons]] +vertices = [ + [3311, 3122], + [3318, 3122], + [3318, 3135], + [3311, 3135], +] + +[[area.polygons]] +vertices = [ + [3224, 3123], + [3226, 3123], +] + +[[area.polygons]] +vertices = [ + [3241, 3123], + [3294, 3123], +] + +[[area.polygons]] +vertices = [ + [3310, 3123], + [3310, 3135], +] + +[[area.polygons]] +vertices = [ + [3224, 3124], + [3225, 3124], +] + +[[area.polygons]] +vertices = [ + [3242, 3124], + [3294, 3124], +] + +[[area.polygons]] +vertices = [[3224, 3125]] + +[[area.polygons]] +vertices = [ + [3243, 3125], + [3294, 3125], +] + +[[area.polygons]] +vertices = [ + [3309, 3125], + [3309, 3135], +] + +[[area.polygons]] +vertices = [ + [3248, 3126], + [3294, 3126], +] + +[[area.polygons]] +vertices = [ + [3249, 3127], + [3297, 3127], +] + +[[area.polygons]] +vertices = [ + [3308, 3127], + [3308, 3135], +] + +[[area.polygons]] +vertices = [ + [3253, 3128], + [3298, 3128], +] + +[[area.polygons]] +vertices = [ + [3208, 3129], + [3217, 3129], + [3217, 3130], + [3208, 3130], +] + +[[area.polygons]] +vertices = [ + [3254, 3129], + [3277, 3129], +] + +[[area.polygons]] +vertices = [ + [3281, 3129], + [3299, 3129], +] + +[[area.polygons]] +vertices = [ + [3307, 3129], + [3307, 3135], +] + +[[area.polygons]] +vertices = [ + [3456, 3129], + [3456, 3148], +] + +[[area.polygons]] +vertices = [ + [3261, 3130], + [3277, 3130], +] + +[[area.polygons]] +vertices = [ + [3282, 3130], + [3300, 3130], + [3300, 3131], + [3282, 3131], +] + +[[area.polygons]] +vertices = [ + [3457, 3130], + [3457, 3147], +] + +[[area.polygons]] +vertices = [ + [3212, 3131], + [3216, 3131], +] + +[[area.polygons]] +vertices = [ + [3262, 3131], + [3278, 3131], + [3278, 3132], + [3262, 3132], +] + +[[area.polygons]] +vertices = [ + [3301, 3131], + [3301, 3132], +] + +[[area.polygons]] +vertices = [ + [3306, 3131], + [3306, 3135], +] + +[[area.polygons]] +vertices = [ + [3458, 3131], + [3458, 3147], +] + +[[area.polygons]] +vertices = [ + [3213, 3132], + [3215, 3132], +] + +[[area.polygons]] +vertices = [ + [3283, 3132], + [3300, 3132], + [3300, 3133], + [3283, 3133], +] + +[[area.polygons]] +vertices = [ + [3305, 3132], + [3305, 3134], +] + +[[area.polygons]] +vertices = [ + [3459, 3132], + [3459, 3147], +] + +[[area.polygons]] +vertices = [ + [3267, 3133], + [3270, 3133], +] + +[[area.polygons]] +vertices = [ + [3460, 3133], + [3460, 3147], +] + +[[area.polygons]] +vertices = [ + [3283, 3134], + [3299, 3134], + [3299, 3135], + [3283, 3135], +] + +[[area.polygons]] +vertices = [ + [3461, 3134], + [3461, 3147], +] + +[[area.polygons]] +vertices = [[3282, 3135]] + +[[area.polygons]] +vertices = [ + [3462, 3135], + [3462, 3147], +] + +[[area.polygons]] +vertices = [ + [3463, 3136], + [3463, 3147], +] + +[[area.polygons]] +vertices = [ + [3348, 3137], + [3362, 3137], +] + +[[area.polygons]] +vertices = [ + [3464, 3137], + [3464, 3147], +] + +[[area.polygons]] +vertices = [ + [3348, 3138], + [3361, 3138], +] + +[[area.polygons]] +vertices = [[3386, 3138]] + +[[area.polygons]] +vertices = [ + [3465, 3138], + [3465, 3147], +] + +[[area.polygons]] +vertices = [ + [3348, 3139], + [3360, 3139], +] + +[[area.polygons]] +vertices = [ + [3466, 3139], + [3466, 3146], +] + +[[area.polygons]] +vertices = [ + [3348, 3140], + [3359, 3140], +] + +[[area.polygons]] +vertices = [[3388, 3140]] + +[[area.polygons]] +vertices = [ + [3467, 3140], + [3467, 3146], +] + +[[area.polygons]] +vertices = [ + [3348, 3141], + [3358, 3141], +] + +[[area.polygons]] +vertices = [ + [3468, 3141], + [3468, 3145], +] + +[[area.polygons]] +vertices = [ + [3348, 3142], + [3357, 3142], +] + +[[area.polygons]] +vertices = [ + [3469, 3142], + [3469, 3144], +] + +[[area.polygons]] +vertices = [ + [3348, 3143], + [3356, 3143], +] + +[[area.polygons]] +vertices = [ + [3348, 3144], + [3355, 3144], +] + +[[area.polygons]] +vertices = [ + [3348, 3145], + [3354, 3145], +] + +[[area.polygons]] +vertices = [ + [3348, 3146], + [3353, 3146], +] + +[[area.polygons]] +vertices = [ + [3348, 3147], + [3352, 3147], + [3352, 3156], + [3348, 3156], +] + +[[area.polygons]] +vertices = [ + [3408, 3149], + [3453, 3149], +] + +[[area.polygons]] +vertices = [ + [3333, 3150], + [3333, 3151], +] + +[[area.polygons]] +vertices = [ + [3408, 3150], + [3451, 3150], +] + +[[area.polygons]] +vertices = [ + [3408, 3151], + [3450, 3151], +] + +[[area.polygons]] +vertices = [ + [3408, 3152], + [3449, 3152], +] + +[[area.polygons]] +vertices = [ + [3408, 3153], + [3447, 3153], +] + +[[area.polygons]] +vertices = [ + [3408, 3154], + [3446, 3154], +] + +[[area.polygons]] +vertices = [ + [3408, 3155], + [3444, 3155], +] + +[[area.polygons]] +vertices = [ + [3408, 3156], + [3443, 3156], +] + +[[area.polygons]] +vertices = [ + [3348, 3157], + [3351, 3157], +] + +[[area.polygons]] +vertices = [ + [3408, 3157], + [3442, 3157], +] + +[[area.polygons]] +vertices = [ + [3348, 3158], + [3350, 3158], +] + +[[area.polygons]] +vertices = [ + [3408, 3158], + [3441, 3158], +] + +[[area.polygons]] +vertices = [ + [3348, 3159], + [3349, 3159], +] + +[[area.polygons]] +vertices = [ + [3408, 3159], + [3440, 3159], +] + +[[area.polygons]] +vertices = [ + [3408, 3160], + [3439, 3160], +] + +[[area.polygons]] +vertices = [ + [3408, 3161], + [3438, 3161], +] + +[[area.polygons]] +vertices = [ + [3409, 3162], + [3436, 3162], +] + +[[area.polygons]] +vertices = [ + [3410, 3163], + [3435, 3163], +] + +[[area.polygons]] +vertices = [ + [3411, 3164], + [3433, 3164], +] + +[[area.polygons]] +vertices = [ + [3412, 3165], + [3432, 3165], +] + +[[area.polygons]] +vertices = [ + [3413, 3166], + [3431, 3166], +] + +[[area.polygons]] +vertices = [ + [3413, 3167], + [3429, 3167], +] + +[[area.polygons]] +vertices = [ + [3413, 3168], + [3428, 3168], +] + +[[area.polygons]] +vertices = [ + [3413, 3169], + [3426, 3169], +] + +[[area.polygons]] +vertices = [ + [3421, 3170], + [3425, 3170], +] + +[[area.polygons]] +vertices = [ + [3421, 3171], + [3424, 3171], +] + +[[area.polygons]] +vertices = [ + [3424, 3172], + [3425, 3172], + [3425, 3174], + [3424, 3174], +] + +[[area.polygons]] +vertices = [ + [3426, 3173], + [3426, 3174], +] diff --git a/.data/raw-cache/map/area/knightmare.toml b/.data/raw-cache/map/area/knightmare.toml new file mode 100644 index 000000000..e6d4cc3a5 --- /dev/null +++ b/.data/raw-cache/map/area/knightmare.toml @@ -0,0 +1,113 @@ +# Knightmare music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Knightmare. +# Bounds x 1856-3032, z 3504-4287; 4282 tiles as 15 rectangles. + +[[area]] +name = "Knightmare" +area_id = "area.knightmare" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3027, 3504], + [3030, 3504], + [3030, 3516], + [3027, 3516], +] + +[[area.polygons]] +vertices = [ + [3031, 3505], + [3031, 3508], +] + +[[area.polygons]] +vertices = [ + [3025, 3506], + [3026, 3506], + [3026, 3518], + [3025, 3518], +] + +[[area.polygons]] +vertices = [ + [3032, 3506], + [3032, 3507], +] + +[[area.polygons]] +vertices = [ + [3021, 3510], + [3024, 3510], + [3024, 3518], + [3021, 3518], +] + +[[area.polygons]] +vertices = [ + [3008, 3513], + [3011, 3513], + [3011, 3518], + [3008, 3518], +] + +[[area.polygons]] +vertices = [ + [3019, 3513], + [3020, 3513], + [3020, 3518], + [3019, 3518], +] + +[[area.polygons]] +vertices = [ + [3012, 3514], + [3013, 3514], + [3013, 3516], + [3012, 3516], +] + +[[area.polygons]] +vertices = [ + [3018, 3514], + [3018, 3517], +] + +[[area.polygons]] +vertices = [ + [3014, 3515], + [3017, 3515], + [3017, 3516], + [3014, 3516], +] + +[[area.polygons]] +vertices = [[3012, 3517]] + +[[area.polygons]] +vertices = [ + [3015, 3517], + [3016, 3517], + [3016, 3519], + [3015, 3519], +] + +[[area.polygons]] +vertices = [ + [3027, 3517], + [3029, 3517], +] + +[[area.polygons]] +vertices = [ + [3027, 3518], + [3028, 3518], +] + +[[area.polygons]] +vertices = [ + [1856, 4224], + [1919, 4224], + [1919, 4287], + [1856, 4287], +] diff --git a/.data/raw-cache/map/area/kourend_the_magnificent.toml b/.data/raw-cache/map/area/kourend_the_magnificent.toml new file mode 100644 index 000000000..2f5c037bf --- /dev/null +++ b/.data/raw-cache/map/area/kourend_the_magnificent.toml @@ -0,0 +1,571 @@ +# Kourend the Magnificent music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Kourend the Magnificent. +# Bounds x 1585-1804, z 3636-3723; 12357 tiles as 84 rectangles. + +[[area]] +name = "Kourend the Magnificent" +area_id = "area.kourend_the_magnificent" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1709, 3636], + [1710, 3636], + [1710, 3710], + [1709, 3710], +] + +[[area.polygons]] +vertices = [ + [1711, 3637], + [1715, 3637], + [1715, 3707], + [1711, 3707], +] + +[[area.polygons]] +vertices = [ + [1716, 3638], + [1720, 3638], + [1720, 3707], + [1716, 3707], +] + +[[area.polygons]] +vertices = [ + [1708, 3639], + [1708, 3710], +] + +[[area.polygons]] +vertices = [ + [1721, 3639], + [1724, 3639], + [1724, 3707], + [1721, 3707], +] + +[[area.polygons]] +vertices = [ + [1707, 3640], + [1707, 3710], +] + +[[area.polygons]] +vertices = [ + [1725, 3640], + [1729, 3640], + [1729, 3707], + [1725, 3707], +] + +[[area.polygons]] +vertices = [ + [1700, 3641], + [1706, 3641], + [1706, 3707], + [1700, 3707], +] + +[[area.polygons]] +vertices = [ + [1730, 3641], + [1734, 3641], + [1734, 3707], + [1730, 3707], +] + +[[area.polygons]] +vertices = [ + [1699, 3642], + [1699, 3707], +] + +[[area.polygons]] +vertices = [ + [1735, 3642], + [1739, 3642], + [1739, 3723], + [1735, 3723], +] + +[[area.polygons]] +vertices = [ + [1698, 3643], + [1698, 3707], +] + +[[area.polygons]] +vertices = [ + [1740, 3643], + [1741, 3643], + [1741, 3723], + [1740, 3723], +] + +[[area.polygons]] +vertices = [ + [1697, 3644], + [1697, 3707], +] + +[[area.polygons]] +vertices = [ + [1696, 3645], + [1696, 3707], +] + +[[area.polygons]] +vertices = [ + [1606, 3646], + [1689, 3646], + [1689, 3702], + [1606, 3702], +] + +[[area.polygons]] +vertices = [ + [1695, 3646], + [1695, 3707], +] + +[[area.polygons]] +vertices = [ + [1605, 3647], + [1605, 3701], +] + +[[area.polygons]] +vertices = [ + [1690, 3647], + [1694, 3647], + [1694, 3707], + [1690, 3707], +] + +[[area.polygons]] +vertices = [ + [1604, 3648], + [1604, 3701], +] + +[[area.polygons]] +vertices = [ + [1603, 3649], + [1603, 3700], +] + +[[area.polygons]] +vertices = [ + [1602, 3650], + [1602, 3699], +] + +[[area.polygons]] +vertices = [ + [1599, 3651], + [1601, 3651], + [1601, 3696], + [1599, 3696], +] + +[[area.polygons]] +vertices = [ + [1598, 3652], + [1598, 3696], +] + +[[area.polygons]] +vertices = [ + [1597, 3653], + [1597, 3696], +] + +[[area.polygons]] +vertices = [ + [1596, 3654], + [1596, 3696], +] + +[[area.polygons]] +vertices = [ + [1783, 3654], + [1804, 3654], +] + +[[area.polygons]] +vertices = [ + [1593, 3655], + [1595, 3655], + [1595, 3696], + [1593, 3696], +] + +[[area.polygons]] +vertices = [ + [1782, 3655], + [1803, 3655], + [1803, 3656], + [1782, 3656], +] + +[[area.polygons]] +vertices = [ + [1591, 3656], + [1592, 3656], + [1592, 3696], + [1591, 3696], +] + +[[area.polygons]] +vertices = [ + [1780, 3656], + [1781, 3656], + [1781, 3702], + [1780, 3702], +] + +[[area.polygons]] +vertices = [ + [1588, 3657], + [1590, 3657], + [1590, 3695], + [1588, 3695], +] + +[[area.polygons]] +vertices = [ + [1779, 3657], + [1779, 3702], +] + +[[area.polygons]] +vertices = [ + [1782, 3657], + [1802, 3657], + [1802, 3659], + [1782, 3659], +] + +[[area.polygons]] +vertices = [ + [1587, 3658], + [1587, 3694], +] + +[[area.polygons]] +vertices = [ + [1768, 3658], + [1778, 3658], + [1778, 3702], + [1768, 3702], +] + +[[area.polygons]] +vertices = [ + [1742, 3659], + [1755, 3659], + [1755, 3702], + [1742, 3702], +] + +[[area.polygons]] +vertices = [ + [1767, 3659], + [1767, 3702], +] + +[[area.polygons]] +vertices = [ + [1760, 3660], + [1766, 3660], + [1766, 3702], + [1760, 3702], +] + +[[area.polygons]] +vertices = [ + [1782, 3660], + [1801, 3660], + [1801, 3661], + [1782, 3661], +] + +[[area.polygons]] +vertices = [ + [1756, 3661], + [1759, 3661], + [1759, 3702], + [1756, 3702], +] + +[[area.polygons]] +vertices = [ + [1782, 3662], + [1800, 3662], + [1800, 3663], + [1782, 3663], +] + +[[area.polygons]] +vertices = [ + [1782, 3664], + [1799, 3664], + [1799, 3665], + [1782, 3665], +] + +[[area.polygons]] +vertices = [ + [1782, 3666], + [1798, 3666], + [1798, 3668], + [1782, 3668], +] + +[[area.polygons]] +vertices = [ + [1782, 3669], + [1797, 3669], +] + +[[area.polygons]] +vertices = [ + [1782, 3670], + [1796, 3670], +] + +[[area.polygons]] +vertices = [ + [1782, 3671], + [1795, 3671], +] + +[[area.polygons]] +vertices = [ + [1782, 3672], + [1794, 3672], + [1794, 3673], + [1782, 3673], +] + +[[area.polygons]] +vertices = [ + [1782, 3674], + [1793, 3674], + [1793, 3675], + [1782, 3675], +] + +[[area.polygons]] +vertices = [ + [1782, 3676], + [1792, 3676], + [1792, 3677], + [1782, 3677], +] + +[[area.polygons]] +vertices = [ + [1782, 3678], + [1791, 3678], + [1791, 3679], + [1782, 3679], +] + +[[area.polygons]] +vertices = [ + [1782, 3680], + [1790, 3680], +] + +[[area.polygons]] +vertices = [ + [1782, 3681], + [1789, 3681], + [1789, 3687], + [1782, 3687], +] + +[[area.polygons]] +vertices = [ + [1586, 3685], + [1586, 3693], +] + +[[area.polygons]] +vertices = [ + [1585, 3686], + [1585, 3692], +] + +[[area.polygons]] +vertices = [ + [1782, 3688], + [1787, 3688], + [1787, 3700], + [1782, 3700], +] + +[[area.polygons]] +vertices = [ + [1788, 3692], + [1789, 3692], + [1789, 3698], + [1788, 3698], +] + +[[area.polygons]] +vertices = [ + [1589, 3696], + [1590, 3696], +] + +[[area.polygons]] +vertices = [ + [1600, 3697], + [1601, 3697], +] + +[[area.polygons]] +vertices = [[1601, 3698]] + +[[area.polygons]] +vertices = [[1788, 3699]] + +[[area.polygons]] +vertices = [ + [1782, 3701], + [1786, 3701], +] + +[[area.polygons]] +vertices = [ + [1782, 3702], + [1785, 3702], +] + +[[area.polygons]] +vertices = [ + [1607, 3703], + [1689, 3703], +] + +[[area.polygons]] +vertices = [ + [1769, 3703], + [1774, 3703], +] + +[[area.polygons]] +vertices = [ + [1608, 3704], + [1689, 3704], +] + +[[area.polygons]] +vertices = [ + [1609, 3705], + [1689, 3705], +] + +[[area.polygons]] +vertices = [ + [1610, 3706], + [1689, 3706], +] + +[[area.polygons]] +vertices = [ + [1611, 3707], + [1678, 3707], +] + +[[area.polygons]] +vertices = [ + [1612, 3708], + [1677, 3708], +] + +[[area.polygons]] +vertices = [ + [1702, 3708], + [1706, 3708], +] + +[[area.polygons]] +vertices = [ + [1711, 3708], + [1714, 3708], + [1714, 3710], + [1711, 3710], +] + +[[area.polygons]] +vertices = [[1734, 3708]] + +[[area.polygons]] +vertices = [ + [1613, 3709], + [1676, 3709], +] + +[[area.polygons]] +vertices = [ + [1703, 3709], + [1706, 3709], +] + +[[area.polygons]] +vertices = [ + [1614, 3710], + [1656, 3710], +] + +[[area.polygons]] +vertices = [ + [1704, 3710], + [1706, 3710], +] + +[[area.polygons]] +vertices = [ + [1615, 3711], + [1640, 3711], +] + +[[area.polygons]] +vertices = [ + [1616, 3712], + [1639, 3712], +] + +[[area.polygons]] +vertices = [ + [1617, 3713], + [1638, 3713], +] + +[[area.polygons]] +vertices = [ + [1619, 3714], + [1637, 3714], +] + +[[area.polygons]] +vertices = [ + [1621, 3715], + [1636, 3715], +] + +[[area.polygons]] +vertices = [ + [1624, 3716], + [1635, 3716], +] + +[[area.polygons]] +vertices = [ + [1625, 3717], + [1635, 3717], +] diff --git a/.data/raw-cache/map/area/la_mort.toml b/.data/raw-cache/map/area/la_mort.toml new file mode 100644 index 000000000..d6555cdec --- /dev/null +++ b/.data/raw-cache/map/area/la_mort.toml @@ -0,0 +1,16 @@ +# La Mort music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: La Mort. +# Bounds x 2176-2239, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "La Mort" +area_id = "area.la_mort" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2176, 4800], + [2239, 4800], + [2239, 4863], + [2176, 4863], +] diff --git a/.data/raw-cache/map/area/labyrinth.toml b/.data/raw-cache/map/area/labyrinth.toml new file mode 100644 index 000000000..f76abdad9 --- /dev/null +++ b/.data/raw-cache/map/area/labyrinth.toml @@ -0,0 +1,16 @@ +# Labyrinth music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Labyrinth. +# Bounds x 3264-3327, z 9216-9279; 4096 tiles as 1 rectangles. + +[[area]] +name = "Labyrinth" +area_id = "area.labyrinth" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 9216], + [3327, 9216], + [3327, 9279], + [3264, 9279], +] diff --git a/.data/raw-cache/map/area/lagoon.toml b/.data/raw-cache/map/area/lagoon.toml new file mode 100644 index 000000000..7df3afcbb --- /dev/null +++ b/.data/raw-cache/map/area/lagoon.toml @@ -0,0 +1,16 @@ +# Lagoon music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lagoon. +# Bounds x 3712-3839, z 10240-10303; 8192 tiles as 1 rectangles. + +[[area]] +name = "Lagoon" +area_id = "area.lagoon" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3712, 10240], + [3839, 10240], + [3839, 10303], + [3712, 10303], +] diff --git a/.data/raw-cache/map/area/laid_to_rest.toml b/.data/raw-cache/map/area/laid_to_rest.toml new file mode 100644 index 000000000..c528d97d6 --- /dev/null +++ b/.data/raw-cache/map/area/laid_to_rest.toml @@ -0,0 +1,16 @@ +# Laid to Rest music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Laid to Rest. +# Bounds x 3648-3711, z 5120-5183; 4096 tiles as 1 rectangles. + +[[area]] +name = "Laid to Rest" +area_id = "area.laid_to_rest" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3648, 5120], + [3711, 5120], + [3711, 5183], + [3648, 5183], +] diff --git a/.data/raw-cache/map/area/lair.toml b/.data/raw-cache/map/area/lair.toml new file mode 100644 index 000000000..257cc8c01 --- /dev/null +++ b/.data/raw-cache/map/area/lair.toml @@ -0,0 +1,16 @@ +# Lair music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lair. +# Bounds x 3456-3519, z 9664-9727; 4096 tiles as 1 rectangles. + +[[area]] +name = "Lair" +area_id = "area.lair" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3456, 9664], + [3519, 9664], + [3519, 9727], + [3456, 9727], +] diff --git a/.data/raw-cache/map/area/lair_of_the_basilisk.toml b/.data/raw-cache/map/area/lair_of_the_basilisk.toml new file mode 100644 index 000000000..d0502f6f8 --- /dev/null +++ b/.data/raw-cache/map/area/lair_of_the_basilisk.toml @@ -0,0 +1,16 @@ +# Lair of the Basilisk music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lair of the Basilisk. +# Bounds x 2368-2495, z 10368-10495; 16384 tiles as 1 rectangles. + +[[area]] +name = "Lair of the Basilisk" +area_id = "area.lair_of_the_basilisk" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 10368], + [2495, 10368], + [2495, 10495], + [2368, 10495], +] diff --git a/.data/raw-cache/map/area/lament.toml b/.data/raw-cache/map/area/lament.toml new file mode 100644 index 000000000..782b2e267 --- /dev/null +++ b/.data/raw-cache/map/area/lament.toml @@ -0,0 +1,16 @@ +# Lament music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lament. +# Bounds x 3072-3135, z 9280-9343; 4096 tiles as 1 rectangles. + +[[area]] +name = "Lament" +area_id = "area.lament" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3072, 9280], + [3135, 9280], + [3135, 9343], + [3072, 9343], +] diff --git a/.data/raw-cache/map/area/lament_for_the_hallowed.toml b/.data/raw-cache/map/area/lament_for_the_hallowed.toml new file mode 100644 index 000000000..0e00170c7 --- /dev/null +++ b/.data/raw-cache/map/area/lament_for_the_hallowed.toml @@ -0,0 +1,96 @@ +# Lament for the Hallowed music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lament for the Hallowed. +# Bounds x 3648-3729, z 3170-3263; 5203 tiles as 13 rectangles. + +[[area]] +name = "Lament for the Hallowed" +area_id = "area.lament_for_the_hallowed" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3648, 3170], + [3729, 3170], + [3729, 3199], + [3648, 3199], +] + +[[area.polygons]] +vertices = [ + [3683, 3200], + [3729, 3200], +] + +[[area.polygons]] +vertices = [ + [3684, 3201], + [3729, 3201], +] + +[[area.polygons]] +vertices = [ + [3685, 3202], + [3729, 3202], +] + +[[area.polygons]] +vertices = [ + [3686, 3203], + [3729, 3203], +] + +[[area.polygons]] +vertices = [ + [3687, 3204], + [3729, 3204], + [3729, 3210], + [3687, 3210], +] + +[[area.polygons]] +vertices = [ + [3699, 3211], + [3729, 3211], + [3729, 3263], + [3699, 3263], +] + +[[area.polygons]] +vertices = [ + [3687, 3228], + [3698, 3228], + [3698, 3263], + [3687, 3263], +] + +[[area.polygons]] +vertices = [ + [3686, 3235], + [3686, 3263], +] + +[[area.polygons]] +vertices = [ + [3685, 3236], + [3685, 3263], +] + +[[area.polygons]] +vertices = [ + [3684, 3237], + [3684, 3263], +] + +[[area.polygons]] +vertices = [ + [3683, 3238], + [3683, 3263], +] + +[[area.polygons]] +vertices = [ + [3680, 3239], + [3682, 3239], + [3682, 3263], + [3680, 3263], +] diff --git a/.data/raw-cache/map/area/lament_of_meiyerditch.toml b/.data/raw-cache/map/area/lament_of_meiyerditch.toml new file mode 100644 index 000000000..43f1ddb62 --- /dev/null +++ b/.data/raw-cache/map/area/lament_of_meiyerditch.toml @@ -0,0 +1,32 @@ +# Lament of Meiyerditch music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lament of Meiyerditch. +# Bounds x 2368-3647, z 4608-9727; 9696 tiles as 3 rectangles. + +[[area]] +name = "Lament of Meiyerditch" +area_id = "area.lament_of_meiyerditch" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 4608], + [2431, 4608], + [2431, 4671], + [2368, 4671], +] + +[[area.polygons]] +vertices = [ + [3584, 9600], + [3647, 9600], + [3647, 9663], + [3584, 9663], +] + +[[area.polygons]] +vertices = [ + [3616, 9681], + [3647, 9681], + [3647, 9727], + [3616, 9727], +] diff --git a/.data/raw-cache/map/area/lamistards_labyrinth.toml b/.data/raw-cache/map/area/lamistards_labyrinth.toml new file mode 100644 index 000000000..42e9e4542 --- /dev/null +++ b/.data/raw-cache/map/area/lamistards_labyrinth.toml @@ -0,0 +1,16 @@ +# Lamistard's Labyrinth music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lamistard's Labyrinth. +# Bounds x 2752-2815, z 10240-10303; 4096 tiles as 1 rectangles. + +[[area]] +name = "Lamistard's Labyrinth" +area_id = "area.lamistards_labyrinth" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 10240], + [2815, 10240], + [2815, 10303], + [2752, 10303], +] diff --git a/.data/raw-cache/map/area/land_down_under.toml b/.data/raw-cache/map/area/land_down_under.toml new file mode 100644 index 000000000..d2f023c90 --- /dev/null +++ b/.data/raw-cache/map/area/land_down_under.toml @@ -0,0 +1,16 @@ +# Land Down Under music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Land Down Under. +# Bounds x 2496-2559, z 10240-10303; 4096 tiles as 1 rectangles. + +[[area]] +name = "Land Down Under" +area_id = "area.land_down_under" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2496, 10240], + [2559, 10240], + [2559, 10303], + [2496, 10303], +] diff --git a/.data/raw-cache/map/area/last_man_standing.toml b/.data/raw-cache/map/area/last_man_standing.toml new file mode 100644 index 000000000..2b2ac2bed --- /dev/null +++ b/.data/raw-cache/map/area/last_man_standing.toml @@ -0,0 +1,24 @@ +# Last Man Standing music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Last Man Standing. +# Bounds x 3392-3647, z 5760-6207; 61440 tiles as 2 rectangles. + +[[area]] +name = "Last Man Standing" +area_id = "area.last_man_standing" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3392, 5760], + [3519, 5760], + [3519, 5951], + [3392, 5951], +] + +[[area.polygons]] +vertices = [ + [3456, 6016], + [3647, 6016], + [3647, 6207], + [3456, 6207], +] diff --git a/.data/raw-cache/map/area/lava_is_mine.toml b/.data/raw-cache/map/area/lava_is_mine.toml new file mode 100644 index 000000000..6bab7194d --- /dev/null +++ b/.data/raw-cache/map/area/lava_is_mine.toml @@ -0,0 +1,16 @@ +# Lava is Mine music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lava is Mine. +# Bounds x 3776-3839, z 10112-10239; 8192 tiles as 1 rectangles. + +[[area]] +name = "Lava is Mine" +area_id = "area.lava_is_mine" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3776, 10112], + [3839, 10112], + [3839, 10239], + [3776, 10239], +] diff --git a/.data/raw-cache/map/area/legends_guild.toml b/.data/raw-cache/map/area/legends_guild.toml new file mode 100644 index 000000000..08f1e91ac --- /dev/null +++ b/.data/raw-cache/map/area/legends_guild.toml @@ -0,0 +1,75 @@ +# The fenced guild grounds from the front gate to the north fence. +# +# Regenerated as rectangles from the OSRS wiki music map: 1052 tiles moved to +# neighbouring music areas. + +[[area]] +name = "Legends' Guild" +area_id = "area.legends_guild" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2726, 3350], + [2731, 3350], + [2731, 3386], + [2726, 3386], +] + +[[area.polygons]] +vertices = [ + [2748, 3354], + [2748, 3373], +] + +[[area.polygons]] +vertices = [ + [2747, 3355], + [2747, 3368], +] + +[[area.polygons]] +vertices = [ + [2746, 3357], + [2746, 3366], +] + +[[area.polygons]] +vertices = [ + [2745, 3361], + [2745, 3365], +] + +[[area.polygons]] +vertices = [ + [2720, 3362], + [2725, 3362], + [2725, 3386], + [2720, 3386], +] + +[[area.polygons]] +vertices = [ + [2732, 3362], + [2736, 3362], + [2736, 3386], + [2732, 3386], +] + +[[area.polygons]] +vertices = [ + [2719, 3363], + [2719, 3385], +] + +[[area.polygons]] +vertices = [ + [2737, 3363], + [2737, 3385], +] + +[[area.polygons]] +vertices = [ + [2738, 3364], + [2738, 3384], +] diff --git a/.data/raw-cache/map/area/legion.toml b/.data/raw-cache/map/area/legion.toml new file mode 100644 index 000000000..2cfad95d0 --- /dev/null +++ b/.data/raw-cache/map/area/legion.toml @@ -0,0 +1,24 @@ +# Legion music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Legion. +# Bounds x 2496-2623, z 5248-9983; 8192 tiles as 2 rectangles. + +[[area]] +name = "Legion" +area_id = "area.legion" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 5248], + [2623, 5248], + [2623, 5311], + [2560, 5311], +] + +[[area.polygons]] +vertices = [ + [2496, 9920], + [2559, 9920], + [2559, 9983], + [2496, 9983], +] diff --git a/.data/raw-cache/map/area/life_at_sea.toml b/.data/raw-cache/map/area/life_at_sea.toml new file mode 100644 index 000000000..0b3ef1368 --- /dev/null +++ b/.data/raw-cache/map/area/life_at_sea.toml @@ -0,0 +1,740 @@ +# Life at Sea music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Life at Sea. +# Bounds x 1872-2098, z 2695-2997; 4054 tiles as 117 rectangles. + +[[area]] +name = "Life at Sea" +area_id = "area.life_at_sea" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2055, 2695], + [2056, 2695], + [2056, 2705], + [2055, 2705], +] + +[[area.polygons]] +vertices = [ + [2073, 2695], + [2073, 2733], +] + +[[area.polygons]] +vertices = [ + [2053, 2696], + [2054, 2696], + [2054, 2703], + [2053, 2703], +] + +[[area.polygons]] +vertices = [ + [2057, 2696], + [2057, 2705], +] + +[[area.polygons]] +vertices = [ + [2072, 2696], + [2072, 2733], +] + +[[area.polygons]] +vertices = [ + [2074, 2696], + [2074, 2734], +] + +[[area.polygons]] +vertices = [ + [2052, 2697], + [2052, 2700], +] + +[[area.polygons]] +vertices = [ + [2058, 2697], + [2058, 2706], +] + +[[area.polygons]] +vertices = [ + [2071, 2697], + [2071, 2732], +] + +[[area.polygons]] +vertices = [ + [2059, 2698], + [2059, 2707], +] + +[[area.polygons]] +vertices = [ + [2092, 2698], + [2094, 2698], + [2094, 2734], + [2092, 2734], +] + +[[area.polygons]] +vertices = [ + [2060, 2699], + [2060, 2707], +] + +[[area.polygons]] +vertices = [ + [2064, 2699], + [2067, 2699], + [2067, 2732], + [2064, 2732], +] + +[[area.polygons]] +vertices = [ + [2090, 2699], + [2091, 2699], + [2091, 2739], + [2090, 2739], +] + +[[area.polygons]] +vertices = [ + [2095, 2699], + [2095, 2733], +] + +[[area.polygons]] +vertices = [ + [2061, 2700], + [2063, 2700], + [2063, 2708], + [2061, 2708], +] + +[[area.polygons]] +vertices = [ + [2068, 2700], + [2070, 2700], + [2070, 2731], + [2068, 2731], +] + +[[area.polygons]] +vertices = [ + [2075, 2700], + [2075, 2735], +] + +[[area.polygons]] +vertices = [ + [2087, 2700], + [2089, 2700], + [2089, 2741], + [2087, 2741], +] + +[[area.polygons]] +vertices = [ + [2096, 2700], + [2096, 2733], +] + +[[area.polygons]] +vertices = [ + [2076, 2701], + [2076, 2736], +] + +[[area.polygons]] +vertices = [ + [2082, 2701], + [2086, 2701], + [2086, 2741], + [2082, 2741], +] + +[[area.polygons]] +vertices = [ + [2077, 2702], + [2081, 2702], + [2081, 2739], + [2077, 2739], +] + +[[area.polygons]] +vertices = [ + [2097, 2702], + [2097, 2733], +] + +[[area.polygons]] +vertices = [[2054, 2704]] + +[[area.polygons]] +vertices = [ + [2098, 2704], + [2098, 2732], +] + +[[area.polygons]] +vertices = [ + [2062, 2709], + [2063, 2709], + [2063, 2727], + [2062, 2727], +] + +[[area.polygons]] +vertices = [ + [2061, 2712], + [2061, 2726], +] + +[[area.polygons]] +vertices = [ + [2060, 2713], + [2060, 2726], +] + +[[area.polygons]] +vertices = [ + [2059, 2714], + [2059, 2725], +] + +[[area.polygons]] +vertices = [ + [2058, 2715], + [2058, 2723], +] + +[[area.polygons]] +vertices = [ + [2057, 2720], + [2057, 2722], +] + +[[area.polygons]] +vertices = [[2056, 2721]] + +[[area.polygons]] +vertices = [ + [2063, 2728], + [2063, 2733], +] + +[[area.polygons]] +vertices = [ + [2068, 2732], + [2069, 2732], +] + +[[area.polygons]] +vertices = [ + [2064, 2733], + [2065, 2733], +] + +[[area.polygons]] +vertices = [ + [2092, 2735], + [2093, 2735], + [2093, 2736], + [2092, 2736], +] + +[[area.polygons]] +vertices = [[2092, 2737]] + +[[area.polygons]] +vertices = [ + [2078, 2740], + [2081, 2740], + [2081, 2743], + [2078, 2743], +] + +[[area.polygons]] +vertices = [[2090, 2740]] + +[[area.polygons]] +vertices = [ + [2082, 2742], + [2084, 2742], +] + +[[area.polygons]] +vertices = [[2082, 2743]] + +[[area.polygons]] +vertices = [ + [1941, 2749], + [1949, 2749], + [1949, 2751], + [1941, 2751], +] + +[[area.polygons]] +vertices = [[1950, 2750]] + +[[area.polygons]] +vertices = [ + [1944, 2752], + [1944, 2766], +] + +[[area.polygons]] +vertices = [ + [1935, 2754], + [1943, 2754], + [1943, 2766], + [1935, 2766], +] + +[[area.polygons]] +vertices = [ + [1945, 2754], + [1951, 2754], + [1951, 2766], + [1945, 2766], +] + +[[area.polygons]] +vertices = [ + [1921, 2759], + [1934, 2759], + [1934, 2761], + [1921, 2761], +] + +[[area.polygons]] +vertices = [ + [1952, 2763], + [1958, 2763], + [1958, 2766], + [1952, 2766], +] + +[[area.polygons]] +vertices = [ + [1956, 2767], + [1958, 2767], + [1958, 2806], + [1956, 2806], +] + +[[area.polygons]] +vertices = [ + [1934, 2772], + [1943, 2772], + [1943, 2808], + [1934, 2808], +] + +[[area.polygons]] +vertices = [ + [1932, 2773], + [1933, 2773], + [1933, 2808], + [1932, 2808], +] + +[[area.polygons]] +vertices = [ + [1944, 2773], + [1944, 2812], +] + +[[area.polygons]] +vertices = [ + [1931, 2774], + [1931, 2807], +] + +[[area.polygons]] +vertices = [ + [1945, 2774], + [1945, 2812], +] + +[[area.polygons]] +vertices = [ + [1954, 2774], + [1955, 2774], + [1955, 2809], + [1954, 2809], +] + +[[area.polygons]] +vertices = [ + [1959, 2774], + [1959, 2805], +] + +[[area.polygons]] +vertices = [ + [1930, 2775], + [1930, 2807], +] + +[[area.polygons]] +vertices = [ + [1946, 2775], + [1953, 2775], + [1953, 2810], + [1946, 2810], +] + +[[area.polygons]] +vertices = [ + [1960, 2775], + [1963, 2775], + [1963, 2802], + [1960, 2802], +] + +[[area.polygons]] +vertices = [ + [1929, 2776], + [1929, 2807], +] + +[[area.polygons]] +vertices = [ + [1964, 2776], + [1964, 2802], +] + +[[area.polygons]] +vertices = [ + [1965, 2777], + [1965, 2802], +] + +[[area.polygons]] +vertices = [ + [1966, 2778], + [1967, 2778], + [1967, 2803], + [1966, 2803], +] + +[[area.polygons]] +vertices = [ + [1968, 2779], + [1968, 2803], +] + +[[area.polygons]] +vertices = [ + [1928, 2780], + [1928, 2806], +] + +[[area.polygons]] +vertices = [ + [1969, 2780], + [1969, 2803], +] + +[[area.polygons]] +vertices = [ + [1927, 2782], + [1927, 2806], +] + +[[area.polygons]] +vertices = [ + [1970, 2784], + [1970, 2803], +] + +[[area.polygons]] +vertices = [ + [1971, 2787], + [1971, 2803], +] + +[[area.polygons]] +vertices = [ + [1926, 2791], + [1926, 2805], +] + +[[area.polygons]] +vertices = [ + [1972, 2792], + [1972, 2803], +] + +[[area.polygons]] +vertices = [ + [1925, 2793], + [1925, 2805], +] + +[[area.polygons]] +vertices = [ + [1973, 2793], + [1973, 2802], +] + +[[area.polygons]] +vertices = [ + [1924, 2794], + [1924, 2804], +] + +[[area.polygons]] +vertices = [ + [1974, 2794], + [1974, 2802], +] + +[[area.polygons]] +vertices = [ + [1923, 2795], + [1923, 2801], +] + +[[area.polygons]] +vertices = [ + [1975, 2796], + [1975, 2801], +] + +[[area.polygons]] +vertices = [ + [1922, 2797], + [1922, 2799], +] + +[[area.polygons]] +vertices = [ + [1976, 2798], + [1976, 2800], +] + +[[area.polygons]] +vertices = [ + [1960, 2803], + [1961, 2803], +] + +[[area.polygons]] +vertices = [[1960, 2804]] + +[[area.polygons]] +vertices = [ + [1956, 2807], + [1957, 2807], + [1957, 2808], + [1956, 2808], +] + +[[area.polygons]] +vertices = [ + [1935, 2809], + [1943, 2809], +] + +[[area.polygons]] +vertices = [[1956, 2809]] + +[[area.polygons]] +vertices = [ + [1936, 2810], + [1943, 2810], +] + +[[area.polygons]] +vertices = [[1954, 2810]] + +[[area.polygons]] +vertices = [ + [1939, 2811], + [1943, 2811], +] + +[[area.polygons]] +vertices = [ + [1946, 2811], + [1952, 2811], +] + +[[area.polygons]] +vertices = [[1943, 2812]] + +[[area.polygons]] +vertices = [ + [1946, 2812], + [1950, 2812], +] + +[[area.polygons]] +vertices = [ + [1888, 2970], + [1890, 2970], + [1890, 2992], + [1888, 2992], +] + +[[area.polygons]] +vertices = [ + [1884, 2971], + [1887, 2971], + [1887, 2990], + [1884, 2990], +] + +[[area.polygons]] +vertices = [ + [1891, 2971], + [1895, 2971], + [1895, 2995], + [1891, 2995], +] + +[[area.polygons]] +vertices = [ + [1882, 2972], + [1883, 2972], + [1883, 2990], + [1882, 2990], +] + +[[area.polygons]] +vertices = [ + [1896, 2972], + [1898, 2972], + [1898, 2980], + [1896, 2980], +] + +[[area.polygons]] +vertices = [ + [1879, 2973], + [1881, 2973], + [1881, 2990], + [1879, 2990], +] + +[[area.polygons]] +vertices = [ + [1899, 2973], + [1899, 2978], +] + +[[area.polygons]] +vertices = [ + [1878, 2974], + [1878, 2989], +] + +[[area.polygons]] +vertices = [ + [1876, 2975], + [1877, 2975], + [1877, 2988], + [1876, 2988], +] + +[[area.polygons]] +vertices = [ + [1900, 2975], + [1900, 2977], +] + +[[area.polygons]] +vertices = [ + [1875, 2976], + [1875, 2987], +] + +[[area.polygons]] +vertices = [ + [1874, 2977], + [1874, 2986], +] + +[[area.polygons]] +vertices = [ + [1873, 2978], + [1873, 2985], +] + +[[area.polygons]] +vertices = [ + [1872, 2980], + [1872, 2983], +] + +[[area.polygons]] +vertices = [ + [1896, 2981], + [1897, 2981], + [1897, 2997], + [1896, 2997], +] + +[[area.polygons]] +vertices = [ + [1898, 2983], + [1898, 2997], +] + +[[area.polygons]] +vertices = [[1877, 2989]] + +[[area.polygons]] +vertices = [ + [1899, 2989], + [1902, 2989], + [1902, 2994], + [1899, 2994], +] + +[[area.polygons]] +vertices = [ + [1903, 2990], + [1903, 2993], +] + +[[area.polygons]] +vertices = [[1887, 2991]] + +[[area.polygons]] +vertices = [ + [1889, 2993], + [1890, 2993], +] + +[[area.polygons]] +vertices = [[1890, 2994]] + +[[area.polygons]] +vertices = [ + [1899, 2995], + [1901, 2995], + [1901, 2996], + [1899, 2996], +] + +[[area.polygons]] +vertices = [ + [1892, 2996], + [1895, 2996], +] + +[[area.polygons]] +vertices = [ + [1893, 2997], + [1895, 2997], +] + +[[area.polygons]] +vertices = [[1899, 2997]] diff --git a/.data/raw-cache/map/area/lighthouse.toml b/.data/raw-cache/map/area/lighthouse.toml new file mode 100644 index 000000000..fc5937e93 --- /dev/null +++ b/.data/raw-cache/map/area/lighthouse.toml @@ -0,0 +1,998 @@ +# Lighthouse music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lighthouse. +# Bounds x 2052-2597, z 3586-3709; 3357 tiles as 159 rectangles. + +[[area]] +name = "Lighthouse" +area_id = "area.lighthouse" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2576, 3586], + [2577, 3586], + [2577, 3613], + [2576, 3613], +] + +[[area.polygons]] +vertices = [ + [2573, 3587], + [2575, 3587], + [2575, 3614], + [2573, 3614], +] + +[[area.polygons]] +vertices = [ + [2578, 3587], + [2578, 3612], +] + +[[area.polygons]] +vertices = [ + [2572, 3588], + [2572, 3616], +] + +[[area.polygons]] +vertices = [ + [2571, 3589], + [2571, 3594], +] + +[[area.polygons]] +vertices = [ + [2579, 3590], + [2579, 3611], +] + +[[area.polygons]] +vertices = [ + [2570, 3591], + [2570, 3592], +] + +[[area.polygons]] +vertices = [ + [2580, 3591], + [2580, 3611], +] + +[[area.polygons]] +vertices = [ + [2592, 3591], + [2593, 3591], + [2593, 3597], + [2592, 3597], +] + +[[area.polygons]] +vertices = [ + [2581, 3592], + [2583, 3592], + [2583, 3608], + [2581, 3608], +] + +[[area.polygons]] +vertices = [ + [2590, 3592], + [2591, 3592], + [2591, 3597], + [2590, 3597], +] + +[[area.polygons]] +vertices = [ + [2594, 3592], + [2594, 3596], +] + +[[area.polygons]] +vertices = [ + [2584, 3593], + [2584, 3608], +] + +[[area.polygons]] +vertices = [ + [2595, 3593], + [2595, 3595], +] + +[[area.polygons]] +vertices = [ + [2585, 3594], + [2589, 3594], + [2589, 3609], + [2585, 3609], +] + +[[area.polygons]] +vertices = [ + [2590, 3598], + [2590, 3609], +] + +[[area.polygons]] +vertices = [ + [2571, 3599], + [2571, 3617], +] + +[[area.polygons]] +vertices = [ + [2568, 3600], + [2570, 3600], + [2570, 3618], + [2568, 3618], +] + +[[area.polygons]] +vertices = [ + [2591, 3600], + [2593, 3600], + [2593, 3609], + [2591, 3609], +] + +[[area.polygons]] +vertices = [ + [2594, 3601], + [2595, 3601], + [2595, 3608], + [2594, 3608], +] + +[[area.polygons]] +vertices = [ + [2567, 3602], + [2567, 3605], +] + +[[area.polygons]] +vertices = [[2596, 3602]] + +[[area.polygons]] +vertices = [ + [2596, 3605], + [2597, 3605], + [2597, 3606], + [2596, 3606], +] + +[[area.polygons]] +vertices = [ + [2567, 3607], + [2567, 3619], +] + +[[area.polygons]] +vertices = [ + [2596, 3607], + [2596, 3608], +] + +[[area.polygons]] +vertices = [ + [2566, 3608], + [2566, 3620], +] + +[[area.polygons]] +vertices = [[2597, 3608]] + +[[area.polygons]] +vertices = [ + [2555, 3609], + [2556, 3609], + [2556, 3625], + [2555, 3625], +] + +[[area.polygons]] +vertices = [ + [2565, 3609], + [2565, 3620], +] + +[[area.polygons]] +vertices = [ + [2581, 3609], + [2582, 3609], +] + +[[area.polygons]] +vertices = [[2594, 3609]] + +[[area.polygons]] +vertices = [ + [2552, 3610], + [2554, 3610], + [2554, 3617], + [2552, 3617], +] + +[[area.polygons]] +vertices = [ + [2557, 3610], + [2558, 3610], + [2558, 3623], + [2557, 3623], +] + +[[area.polygons]] +vertices = [ + [2561, 3610], + [2564, 3610], + [2564, 3621], + [2561, 3621], +] + +[[area.polygons]] +vertices = [[2581, 3610]] + +[[area.polygons]] +vertices = [ + [2587, 3610], + [2589, 3610], +] + +[[area.polygons]] +vertices = [ + [2550, 3611], + [2551, 3611], + [2551, 3618], + [2550, 3618], +] + +[[area.polygons]] +vertices = [ + [2559, 3611], + [2560, 3611], + [2560, 3622], + [2559, 3622], +] + +[[area.polygons]] +vertices = [ + [2548, 3612], + [2549, 3612], + [2549, 3620], + [2548, 3620], +] + +[[area.polygons]] +vertices = [ + [2547, 3613], + [2547, 3627], +] + +[[area.polygons]] +vertices = [ + [2546, 3614], + [2546, 3628], +] + +[[area.polygons]] +vertices = [[2576, 3614]] + +[[area.polygons]] +vertices = [ + [2514, 3615], + [2515, 3615], + [2515, 3645], + [2514, 3645], +] + +[[area.polygons]] +vertices = [ + [2544, 3615], + [2545, 3615], + [2545, 3628], + [2544, 3628], +] + +[[area.polygons]] +vertices = [ + [2573, 3615], + [2574, 3615], +] + +[[area.polygons]] +vertices = [ + [2543, 3616], + [2543, 3628], +] + +[[area.polygons]] +vertices = [[2573, 3616]] + +[[area.polygons]] +vertices = [ + [2513, 3618], + [2513, 3646], +] + +[[area.polygons]] +vertices = [[2552, 3618]] + +[[area.polygons]] +vertices = [[2554, 3618]] + +[[area.polygons]] +vertices = [ + [2511, 3619], + [2512, 3619], + [2512, 3647], + [2511, 3647], +] + +[[area.polygons]] +vertices = [ + [2516, 3619], + [2516, 3644], +] + +[[area.polygons]] +vertices = [ + [2526, 3619], + [2526, 3632], +] + +[[area.polygons]] +vertices = [[2550, 3619]] + +[[area.polygons]] +vertices = [[2568, 3619]] + +[[area.polygons]] +vertices = [ + [2506, 3620], + [2510, 3620], + [2510, 3647], + [2506, 3647], +] + +[[area.polygons]] +vertices = [ + [2517, 3620], + [2518, 3620], + [2518, 3636], + [2517, 3636], +] + +[[area.polygons]] +vertices = [ + [2524, 3620], + [2525, 3620], + [2525, 3631], + [2524, 3631], +] + +[[area.polygons]] +vertices = [ + [2527, 3620], + [2528, 3620], + [2528, 3632], + [2527, 3632], +] + +[[area.polygons]] +vertices = [ + [2542, 3620], + [2542, 3628], +] + +[[area.polygons]] +vertices = [ + [2504, 3621], + [2505, 3621], + [2505, 3647], + [2504, 3647], +] + +[[area.polygons]] +vertices = [ + [2519, 3621], + [2520, 3621], + [2520, 3634], + [2519, 3634], +] + +[[area.polygons]] +vertices = [ + [2522, 3621], + [2523, 3621], + [2523, 3630], + [2522, 3630], +] + +[[area.polygons]] +vertices = [ + [2529, 3621], + [2536, 3621], + [2536, 3631], + [2529, 3631], +] + +[[area.polygons]] +vertices = [ + [2541, 3621], + [2541, 3628], +] + +[[area.polygons]] +vertices = [ + [2548, 3621], + [2548, 3627], +] + +[[area.polygons]] +vertices = [ + [2503, 3622], + [2503, 3647], +] + +[[area.polygons]] +vertices = [ + [2521, 3622], + [2521, 3631], +] + +[[area.polygons]] +vertices = [ + [2537, 3622], + [2537, 3630], +] + +[[area.polygons]] +vertices = [ + [2540, 3622], + [2540, 3627], +] + +[[area.polygons]] +vertices = [ + [2549, 3622], + [2549, 3626], +] + +[[area.polygons]] +vertices = [ + [2554, 3622], + [2554, 3624], +] + +[[area.polygons]] +vertices = [ + [2561, 3622], + [2563, 3622], +] + +[[area.polygons]] +vertices = [ + [2502, 3623], + [2502, 3646], +] + +[[area.polygons]] +vertices = [ + [2538, 3623], + [2539, 3623], + [2539, 3627], + [2538, 3627], +] + +[[area.polygons]] +vertices = [ + [2501, 3624], + [2501, 3645], +] + +[[area.polygons]] +vertices = [ + [2550, 3624], + [2550, 3625], +] + +[[area.polygons]] +vertices = [[2557, 3624]] + +[[area.polygons]] +vertices = [ + [2500, 3625], + [2500, 3644], +] + +[[area.polygons]] +vertices = [ + [2499, 3626], + [2499, 3643], +] + +[[area.polygons]] +vertices = [ + [2538, 3628], + [2538, 3629], +] + +[[area.polygons]] +vertices = [ + [2498, 3629], + [2498, 3630], +] + +[[area.polygons]] +vertices = [ + [2529, 3632], + [2534, 3632], +] + +[[area.polygons]] +vertices = [ + [2498, 3633], + [2498, 3642], +] + +[[area.polygons]] +vertices = [ + [2497, 3634], + [2497, 3641], +] + +[[area.polygons]] +vertices = [[2519, 3635]] + +[[area.polygons]] +vertices = [ + [2496, 3636], + [2496, 3640], +] + +[[area.polygons]] +vertices = [[2517, 3637]] + +[[area.polygons]] +vertices = [[2514, 3646]] + +[[area.polygons]] +vertices = [ + [2083, 3657], + [2083, 3689], +] + +[[area.polygons]] +vertices = [ + [2079, 3658], + [2082, 3658], + [2082, 3690], + [2079, 3690], +] + +[[area.polygons]] +vertices = [ + [2084, 3658], + [2084, 3689], +] + +[[area.polygons]] +vertices = [ + [2077, 3659], + [2078, 3659], + [2078, 3669], + [2077, 3669], +] + +[[area.polygons]] +vertices = [ + [2085, 3659], + [2085, 3689], +] + +[[area.polygons]] +vertices = [ + [2094, 3659], + [2095, 3659], + [2095, 3695], + [2094, 3695], +] + +[[area.polygons]] +vertices = [ + [2076, 3660], + [2076, 3669], +] + +[[area.polygons]] +vertices = [ + [2086, 3660], + [2093, 3660], + [2093, 3689], + [2086, 3689], +] + +[[area.polygons]] +vertices = [ + [2096, 3660], + [2096, 3694], +] + +[[area.polygons]] +vertices = [ + [2075, 3661], + [2075, 3669], +] + +[[area.polygons]] +vertices = [ + [2097, 3661], + [2097, 3694], +] + +[[area.polygons]] +vertices = [ + [2074, 3662], + [2074, 3669], +] + +[[area.polygons]] +vertices = [ + [2098, 3662], + [2098, 3694], +] + +[[area.polygons]] +vertices = [ + [2073, 3663], + [2073, 3669], +] + +[[area.polygons]] +vertices = [ + [2099, 3663], + [2099, 3693], +] + +[[area.polygons]] +vertices = [ + [2100, 3664], + [2100, 3693], +] + +[[area.polygons]] +vertices = [ + [2101, 3665], + [2101, 3693], +] + +[[area.polygons]] +vertices = [ + [2072, 3666], + [2072, 3669], +] + +[[area.polygons]] +vertices = [ + [2102, 3666], + [2102, 3692], +] + +[[area.polygons]] +vertices = [ + [2071, 3667], + [2071, 3669], +] + +[[area.polygons]] +vertices = [ + [2103, 3667], + [2103, 3692], +] + +[[area.polygons]] +vertices = [ + [2104, 3669], + [2104, 3692], +] + +[[area.polygons]] +vertices = [[2078, 3670]] + +[[area.polygons]] +vertices = [ + [2105, 3673], + [2105, 3674], +] + +[[area.polygons]] +vertices = [ + [2078, 3674], + [2078, 3693], +] + +[[area.polygons]] +vertices = [ + [2057, 3675], + [2061, 3675], + [2061, 3700], + [2057, 3700], +] + +[[area.polygons]] +vertices = [ + [2076, 3675], + [2077, 3675], + [2077, 3693], + [2076, 3693], +] + +[[area.polygons]] +vertices = [ + [2055, 3676], + [2056, 3676], + [2056, 3695], + [2055, 3695], +] + +[[area.polygons]] +vertices = [ + [2062, 3676], + [2068, 3676], + [2068, 3691], + [2062, 3691], +] + +[[area.polygons]] +vertices = [ + [2074, 3676], + [2075, 3676], + [2075, 3692], + [2074, 3692], +] + +[[area.polygons]] +vertices = [ + [2105, 3676], + [2106, 3676], + [2106, 3684], + [2105, 3684], +] + +[[area.polygons]] +vertices = [ + [2069, 3677], + [2073, 3677], + [2073, 3690], + [2069, 3690], +] + +[[area.polygons]] +vertices = [ + [2107, 3677], + [2107, 3682], +] + +[[area.polygons]] +vertices = [ + [2054, 3678], + [2054, 3693], +] + +[[area.polygons]] +vertices = [ + [2053, 3679], + [2053, 3685], +] + +[[area.polygons]] +vertices = [ + [2052, 3681], + [2052, 3682], +] + +[[area.polygons]] +vertices = [ + [2105, 3685], + [2105, 3692], +] + +[[area.polygons]] +vertices = [ + [2106, 3686], + [2106, 3692], +] + +[[area.polygons]] +vertices = [ + [2053, 3688], + [2053, 3689], +] + +[[area.polygons]] +vertices = [ + [2087, 3690], + [2093, 3690], +] + +[[area.polygons]] +vertices = [ + [2072, 3691], + [2073, 3691], +] + +[[area.polygons]] +vertices = [ + [2079, 3691], + [2080, 3691], +] + +[[area.polygons]] +vertices = [ + [2088, 3691], + [2093, 3691], +] + +[[area.polygons]] +vertices = [ + [2062, 3692], + [2067, 3692], + [2067, 3705], + [2062, 3705], +] + +[[area.polygons]] +vertices = [[2073, 3692]] + +[[area.polygons]] +vertices = [[2079, 3692]] + +[[area.polygons]] +vertices = [ + [2089, 3692], + [2093, 3692], + [2093, 3693], + [2089, 3693], +] + +[[area.polygons]] +vertices = [[2075, 3693]] + +[[area.polygons]] +vertices = [ + [2068, 3694], + [2068, 3707], +] + +[[area.polygons]] +vertices = [ + [2090, 3694], + [2093, 3694], +] + +[[area.polygons]] +vertices = [ + [2091, 3695], + [2093, 3695], +] + +[[area.polygons]] +vertices = [ + [2056, 3696], + [2056, 3699], +] + +[[area.polygons]] +vertices = [ + [2069, 3696], + [2069, 3707], +] + +[[area.polygons]] +vertices = [ + [2092, 3696], + [2094, 3696], +] + +[[area.polygons]] +vertices = [ + [2092, 3697], + [2093, 3697], +] + +[[area.polygons]] +vertices = [ + [2070, 3698], + [2071, 3698], + [2071, 3707], + [2070, 3707], +] + +[[area.polygons]] +vertices = [ + [2072, 3699], + [2072, 3707], +] + +[[area.polygons]] +vertices = [ + [2073, 3700], + [2073, 3708], +] + +[[area.polygons]] +vertices = [ + [2058, 3701], + [2061, 3701], +] + +[[area.polygons]] +vertices = [ + [2074, 3701], + [2074, 3708], +] + +[[area.polygons]] +vertices = [ + [2059, 3702], + [2061, 3702], +] + +[[area.polygons]] +vertices = [ + [2075, 3702], + [2075, 3709], +] + +[[area.polygons]] +vertices = [ + [2060, 3703], + [2061, 3703], +] + +[[area.polygons]] +vertices = [ + [2061, 3704], + [2061, 3705], +] + +[[area.polygons]] +vertices = [ + [2076, 3704], + [2077, 3704], + [2077, 3709], + [2076, 3709], +] + +[[area.polygons]] +vertices = [ + [2078, 3705], + [2081, 3705], + [2081, 3707], + [2078, 3707], +] + +[[area.polygons]] +vertices = [ + [2063, 3706], + [2067, 3706], +] + +[[area.polygons]] +vertices = [ + [2064, 3707], + [2067, 3707], +] + +[[area.polygons]] +vertices = [ + [2078, 3708], + [2080, 3708], +] + +[[area.polygons]] +vertices = [ + [2078, 3709], + [2079, 3709], +] diff --git a/.data/raw-cache/map/area/little_cave_of_horrors.toml b/.data/raw-cache/map/area/little_cave_of_horrors.toml new file mode 100644 index 000000000..f10cee1f1 --- /dev/null +++ b/.data/raw-cache/map/area/little_cave_of_horrors.toml @@ -0,0 +1,24 @@ +# Little Cave of Horrors music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Little Cave of Horrors. +# Bounds x 3712-3839, z 9344-9471; 12288 tiles as 2 rectangles. + +[[area]] +name = "Little Cave of Horrors" +area_id = "area.little_cave_of_horrors" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3712, 9344], + [3775, 9344], + [3775, 9471], + [3712, 9471], +] + +[[area.polygons]] +vertices = [ + [3776, 9408], + [3839, 9408], + [3839, 9471], + [3776, 9471], +] diff --git a/.data/raw-cache/map/area/lonesome.toml b/.data/raw-cache/map/area/lonesome.toml new file mode 100644 index 000000000..077a3407d --- /dev/null +++ b/.data/raw-cache/map/area/lonesome.toml @@ -0,0 +1,16 @@ +# Lonesome music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lonesome. +# Bounds x 3264-3327, z 9408-9471; 4096 tiles as 1 rectangles. + +[[area]] +name = "Lonesome" +area_id = "area.lonesome" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 9408], + [3327, 9408], + [3327, 9471], + [3264, 9471], +] diff --git a/.data/raw-cache/map/area/looking_back.toml b/.data/raw-cache/map/area/looking_back.toml new file mode 100644 index 000000000..89f3a1659 --- /dev/null +++ b/.data/raw-cache/map/area/looking_back.toml @@ -0,0 +1,16 @@ +# Looking Back music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Looking Back. +# Bounds x 1728-1791, z 4928-4991; 4096 tiles as 1 rectangles. + +[[area]] +name = "Looking Back" +area_id = "area.looking_back" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1728, 4928], + [1791, 4928], + [1791, 4991], + [1728, 4991], +] diff --git a/.data/raw-cache/map/area/lord_of_the_larvae.toml b/.data/raw-cache/map/area/lord_of_the_larvae.toml new file mode 100644 index 000000000..9c91ae631 --- /dev/null +++ b/.data/raw-cache/map/area/lord_of_the_larvae.toml @@ -0,0 +1,16 @@ +# Lord of the Larvae music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lord of the Larvae. +# Bounds x 2901-2921, z 8031-8047; 357 tiles as 1 rectangles. + +[[area]] +name = "Lord of the Larvae" +area_id = "area.lord_of_the_larvae" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2901, 8031], + [2921, 8031], + [2921, 8047], + [2901, 8047], +] diff --git a/.data/raw-cache/map/area/lore_and_order.toml b/.data/raw-cache/map/area/lore_and_order.toml new file mode 100644 index 000000000..ec3549a9a --- /dev/null +++ b/.data/raw-cache/map/area/lore_and_order.toml @@ -0,0 +1,16 @@ +# Lore and Order music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lore and Order. +# Bounds x 1792-1855, z 4224-4287; 4096 tiles as 1 rectangles. + +[[area]] +name = "Lore and Order" +area_id = "area.lore_and_order" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1792, 4224], + [1855, 4224], + [1855, 4287], + [1792, 4287], +] diff --git a/.data/raw-cache/map/area/lost_soul.toml b/.data/raw-cache/map/area/lost_soul.toml new file mode 100644 index 000000000..8fa5bb387 --- /dev/null +++ b/.data/raw-cache/map/area/lost_soul.toml @@ -0,0 +1,279 @@ +# Lost Soul music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lost Soul. +# Bounds x 2144-2303, z 3067-3115; 5814 tiles as 45 rectangles. + +[[area]] +name = "Lost Soul" +area_id = "area.lost_soul" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2164, 3067], + [2178, 3067], + [2178, 3109], + [2164, 3109], +] + +[[area.polygons]] +vertices = [ + [2148, 3068], + [2150, 3068], + [2150, 3072], + [2148, 3072], +] + +[[area.polygons]] +vertices = [ + [2163, 3068], + [2163, 3109], +] + +[[area.polygons]] +vertices = [ + [2151, 3069], + [2151, 3073], +] + +[[area.polygons]] +vertices = [ + [2161, 3069], + [2162, 3069], + [2162, 3109], + [2161, 3109], +] + +[[area.polygons]] +vertices = [ + [2179, 3069], + [2179, 3109], +] + +[[area.polygons]] +vertices = [[2147, 3070]] + +[[area.polygons]] +vertices = [ + [2152, 3070], + [2152, 3073], +] + +[[area.polygons]] +vertices = [ + [2180, 3070], + [2180, 3109], +] + +[[area.polygons]] +vertices = [ + [2153, 3071], + [2153, 3073], +] + +[[area.polygons]] +vertices = [ + [2160, 3071], + [2160, 3109], +] + +[[area.polygons]] +vertices = [ + [2181, 3071], + [2181, 3109], +] + +[[area.polygons]] +vertices = [ + [2154, 3072], + [2159, 3072], +] + +[[area.polygons]] +vertices = [ + [2182, 3072], + [2255, 3072], + [2255, 3109], + [2182, 3109], +] + +[[area.polygons]] +vertices = [ + [2280, 3072], + [2303, 3072], + [2303, 3109], + [2280, 3109], +] + +[[area.polygons]] +vertices = [[2150, 3073]] + +[[area.polygons]] +vertices = [[2154, 3073]] + +[[area.polygons]] +vertices = [ + [2159, 3074], + [2159, 3109], +] + +[[area.polygons]] +vertices = [ + [2158, 3075], + [2158, 3109], +] + +[[area.polygons]] +vertices = [ + [2156, 3077], + [2157, 3077], + [2157, 3109], + [2156, 3109], +] + +[[area.polygons]] +vertices = [ + [2155, 3078], + [2155, 3111], +] + +[[area.polygons]] +vertices = [ + [2154, 3079], + [2154, 3091], +] + +[[area.polygons]] +vertices = [ + [2153, 3080], + [2153, 3088], +] + +[[area.polygons]] +vertices = [ + [2256, 3080], + [2279, 3080], + [2279, 3109], + [2256, 3109], +] + +[[area.polygons]] +vertices = [ + [2152, 3081], + [2152, 3087], +] + +[[area.polygons]] +vertices = [ + [2151, 3082], + [2151, 3086], +] + +[[area.polygons]] +vertices = [ + [2150, 3083], + [2150, 3086], +] + +[[area.polygons]] +vertices = [[2149, 3084]] + +[[area.polygons]] +vertices = [ + [2144, 3090], + [2153, 3090], + [2153, 3091], + [2144, 3091], +] + +[[area.polygons]] +vertices = [ + [2154, 3095], + [2154, 3112], +] + +[[area.polygons]] +vertices = [ + [2153, 3096], + [2153, 3113], +] + +[[area.polygons]] +vertices = [ + [2151, 3097], + [2152, 3097], + [2152, 3114], + [2151, 3114], +] + +[[area.polygons]] +vertices = [ + [2149, 3098], + [2150, 3098], + [2150, 3115], + [2149, 3115], +] + +[[area.polygons]] +vertices = [ + [2147, 3099], + [2148, 3099], + [2148, 3102], + [2147, 3102], +] + +[[area.polygons]] +vertices = [ + [2146, 3100], + [2146, 3101], +] + +[[area.polygons]] +vertices = [[2148, 3103]] + +[[area.polygons]] +vertices = [ + [2148, 3109], + [2148, 3115], +] + +[[area.polygons]] +vertices = [ + [2147, 3110], + [2147, 3115], +] + +[[area.polygons]] +vertices = [[2156, 3110]] + +[[area.polygons]] +vertices = [ + [2291, 3110], + [2303, 3110], +] + +[[area.polygons]] +vertices = [ + [2293, 3111], + [2303, 3111], +] + +[[area.polygons]] +vertices = [ + [2146, 3112], + [2146, 3113], +] + +[[area.polygons]] +vertices = [ + [2295, 3112], + [2303, 3112], +] + +[[area.polygons]] +vertices = [ + [2297, 3113], + [2303, 3113], +] + +[[area.polygons]] +vertices = [[2151, 3115]] diff --git a/.data/raw-cache/map/area/lost_to_time.toml b/.data/raw-cache/map/area/lost_to_time.toml new file mode 100644 index 000000000..a41981b97 --- /dev/null +++ b/.data/raw-cache/map/area/lost_to_time.toml @@ -0,0 +1,152 @@ +# Lost to Time music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lost to Time. +# Bounds x 1344-1727, z 9500-9744; 30304 tiles as 22 rectangles. + +[[area]] +name = "Lost to Time" +area_id = "area.lost_to_time" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1344, 9500], + [1391, 9500], + [1391, 9600], + [1344, 9600], +] + +[[area.polygons]] +vertices = [ + [1496, 9560], + [1535, 9560], + [1535, 9600], + [1496, 9600], +] + +[[area.polygons]] +vertices = [ + [1392, 9577], + [1392, 9600], +] + +[[area.polygons]] +vertices = [ + [1393, 9578], + [1393, 9600], +] + +[[area.polygons]] +vertices = [ + [1394, 9579], + [1394, 9600], +] + +[[area.polygons]] +vertices = [ + [1395, 9580], + [1395, 9600], +] + +[[area.polygons]] +vertices = [ + [1396, 9581], + [1396, 9600], +] + +[[area.polygons]] +vertices = [ + [1397, 9582], + [1397, 9600], +] + +[[area.polygons]] +vertices = [ + [1398, 9583], + [1398, 9600], +] + +[[area.polygons]] +vertices = [ + [1399, 9584], + [1399, 9600], +] + +[[area.polygons]] +vertices = [ + [1400, 9585], + [1400, 9600], +] + +[[area.polygons]] +vertices = [ + [1401, 9586], + [1401, 9600], +] + +[[area.polygons]] +vertices = [ + [1402, 9587], + [1402, 9600], +] + +[[area.polygons]] +vertices = [ + [1403, 9588], + [1403, 9600], +] + +[[area.polygons]] +vertices = [ + [1404, 9589], + [1404, 9600], +] + +[[area.polygons]] +vertices = [ + [1405, 9590], + [1405, 9600], +] + +[[area.polygons]] +vertices = [ + [1406, 9591], + [1406, 9600], +] + +[[area.polygons]] +vertices = [ + [1407, 9592], + [1407, 9600], +] + +[[area.polygons]] +vertices = [ + [1568, 9600], + [1727, 9600], + [1727, 9663], + [1568, 9663], +] + +[[area.polygons]] +vertices = [ + [1408, 9601], + [1471, 9601], + [1471, 9663], + [1408, 9663], +] + +[[area.polygons]] +vertices = [ + [1344, 9664], + [1407, 9664], + [1407, 9744], + [1344, 9744], +] + +[[area.polygons]] +vertices = [ + [1472, 9664], + [1535, 9664], + [1535, 9727], + [1472, 9727], +] diff --git a/.data/raw-cache/map/area/lower_depths.toml b/.data/raw-cache/map/area/lower_depths.toml new file mode 100644 index 000000000..d54a7c873 --- /dev/null +++ b/.data/raw-cache/map/area/lower_depths.toml @@ -0,0 +1,24 @@ +# Lower Depths music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lower Depths. +# Bounds x 1152-3359, z 5696-9983; 10240 tiles as 2 rectangles. + +[[area]] +name = "Lower Depths" +area_id = "area.lower_depths" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 5696], + [3359, 5696], + [3359, 5759], + [3264, 5759], +] + +[[area.polygons]] +vertices = [ + [1152, 9920], + [1215, 9920], + [1215, 9983], + [1152, 9983], +] diff --git a/.data/raw-cache/map/area/lucid_dream.toml b/.data/raw-cache/map/area/lucid_dream.toml new file mode 100644 index 000000000..b92de4622 --- /dev/null +++ b/.data/raw-cache/map/area/lucid_dream.toml @@ -0,0 +1,16 @@ +# Lucid Dream music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lucid Dream. +# Bounds x 1792-1855, z 5184-5212; 1856 tiles as 1 rectangles. + +[[area]] +name = "Lucid Dream" +area_id = "area.lucid_dream" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1792, 5184], + [1855, 5184], + [1855, 5212], + [1792, 5212], +] diff --git a/.data/raw-cache/map/area/lucid_nightmare.toml b/.data/raw-cache/map/area/lucid_nightmare.toml new file mode 100644 index 000000000..3229b9d61 --- /dev/null +++ b/.data/raw-cache/map/area/lucid_nightmare.toml @@ -0,0 +1,16 @@ +# Lucid Nightmare music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lucid Nightmare. +# Bounds x 1792-1855, z 5213-5247; 2240 tiles as 1 rectangles. + +[[area]] +name = "Lucid Nightmare" +area_id = "area.lucid_nightmare" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1792, 5213], + [1855, 5213], + [1855, 5247], + [1792, 5247], +] diff --git a/.data/raw-cache/map/area/lumbering.toml b/.data/raw-cache/map/area/lumbering.toml new file mode 100644 index 000000000..a0333719d --- /dev/null +++ b/.data/raw-cache/map/area/lumbering.toml @@ -0,0 +1,134 @@ +# Lumbering music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lumbering. +# Bounds x 2816-2897, z 3917-10367; 6074 tiles as 17 rectangles. + +[[area]] +name = "Lumbering" +area_id = "area.lumbering" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2852, 3917], + [2855, 3917], + [2855, 3960], + [2852, 3960], +] + +[[area.polygons]] +vertices = [ + [2849, 3918], + [2851, 3918], + [2851, 3929], + [2849, 3929], +] + +[[area.polygons]] +vertices = [ + [2856, 3918], + [2856, 3960], +] + +[[area.polygons]] +vertices = [ + [2848, 3919], + [2848, 3929], +] + +[[area.polygons]] +vertices = [ + [2857, 3919], + [2861, 3919], + [2861, 3960], + [2857, 3960], +] + +[[area.polygons]] +vertices = [ + [2873, 3919], + [2874, 3919], + [2874, 3960], + [2873, 3960], +] + +[[area.polygons]] +vertices = [ + [2862, 3920], + [2865, 3920], + [2865, 3960], + [2862, 3960], +] + +[[area.polygons]] +vertices = [ + [2871, 3920], + [2872, 3920], + [2872, 3960], + [2871, 3960], +] + +[[area.polygons]] +vertices = [ + [2875, 3920], + [2876, 3920], + [2876, 3960], + [2875, 3960], +] + +[[area.polygons]] +vertices = [ + [2866, 3921], + [2870, 3921], + [2870, 3960], + [2866, 3960], +] + +[[area.polygons]] +vertices = [ + [2877, 3921], + [2881, 3921], + [2881, 3960], + [2877, 3960], +] + +[[area.polygons]] +vertices = [ + [2886, 3921], + [2897, 3921], + [2897, 3960], + [2886, 3960], +] + +[[area.polygons]] +vertices = [ + [2882, 3922], + [2885, 3922], + [2885, 3960], + [2882, 3960], +] + +[[area.polygons]] +vertices = [ + [2850, 3930], + [2851, 3930], +] + +[[area.polygons]] +vertices = [ + [2851, 3931], + [2851, 3960], +] + +[[area.polygons]] +vertices = [ + [2850, 3939], + [2850, 3960], +] + +[[area.polygons]] +vertices = [ + [2816, 10304], + [2879, 10304], + [2879, 10367], + [2816, 10367], +] diff --git a/.data/raw-cache/map/area/lunar_isle.toml b/.data/raw-cache/map/area/lunar_isle.toml new file mode 100644 index 000000000..fa24d00e9 --- /dev/null +++ b/.data/raw-cache/map/area/lunar_isle.toml @@ -0,0 +1,980 @@ +# Lunar Isle music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Isle of Everywhere; The Lunar Isle. +# Bounds x 2056-2170, z 3843-3958; 8572 tiles as 149 rectangles. + +[[area]] +name = "Lunar Isle" +area_id = "area.lunar_isle" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2117, 3843], + [2118, 3843], + [2118, 3958], + [2117, 3958], +] + +[[area.polygons]] +vertices = [ + [2109, 3844], + [2116, 3844], + [2116, 3852], + [2109, 3852], +] + +[[area.polygons]] +vertices = [ + [2119, 3844], + [2120, 3844], + [2120, 3887], + [2119, 3887], +] + +[[area.polygons]] +vertices = [ + [2106, 3845], + [2108, 3845], + [2108, 3849], + [2106, 3849], +] + +[[area.polygons]] +vertices = [ + [2121, 3845], + [2121, 3885], +] + +[[area.polygons]] +vertices = [ + [2124, 3845], + [2124, 3883], +] + +[[area.polygons]] +vertices = [ + [2127, 3845], + [2129, 3845], + [2129, 3881], + [2127, 3881], +] + +[[area.polygons]] +vertices = [ + [2101, 3846], + [2105, 3846], + [2105, 3850], + [2101, 3850], +] + +[[area.polygons]] +vertices = [ + [2122, 3846], + [2123, 3846], + [2123, 3883], + [2122, 3883], +] + +[[area.polygons]] +vertices = [ + [2125, 3846], + [2126, 3846], + [2126, 3882], + [2125, 3882], +] + +[[area.polygons]] +vertices = [ + [2130, 3846], + [2131, 3846], + [2131, 3878], + [2130, 3878], +] + +[[area.polygons]] +vertices = [ + [2100, 3847], + [2100, 3851], +] + +[[area.polygons]] +vertices = [ + [2132, 3847], + [2139, 3847], + [2139, 3872], + [2132, 3872], +] + +[[area.polygons]] +vertices = [ + [2097, 3848], + [2099, 3848], + [2099, 3850], + [2097, 3850], +] + +[[area.polygons]] +vertices = [ + [2140, 3848], + [2147, 3848], + [2147, 3866], + [2140, 3866], +] + +[[area.polygons]] +vertices = [ + [2148, 3849], + [2148, 3872], +] + +[[area.polygons]] +vertices = [[2106, 3850]] + +[[area.polygons]] +vertices = [[2108, 3850]] + +[[area.polygons]] +vertices = [ + [2097, 3851], + [2098, 3851], +] + +[[area.polygons]] +vertices = [ + [2101, 3851], + [2104, 3851], +] + +[[area.polygons]] +vertices = [ + [2149, 3851], + [2151, 3851], + [2151, 3871], + [2149, 3871], +] + +[[area.polygons]] +vertices = [ + [2101, 3852], + [2103, 3852], +] + +[[area.polygons]] +vertices = [ + [2152, 3852], + [2154, 3852], + [2154, 3873], + [2152, 3873], +] + +[[area.polygons]] +vertices = [ + [2110, 3853], + [2116, 3853], + [2116, 3956], + [2110, 3956], +] + +[[area.polygons]] +vertices = [ + [2155, 3853], + [2155, 3872], +] + +[[area.polygons]] +vertices = [ + [2156, 3854], + [2157, 3854], + [2157, 3871], + [2156, 3871], +] + +[[area.polygons]] +vertices = [ + [2088, 3855], + [2093, 3855], + [2093, 3943], + [2088, 3943], +] + +[[area.polygons]] +vertices = [ + [2158, 3855], + [2158, 3871], +] + +[[area.polygons]] +vertices = [ + [2087, 3856], + [2087, 3942], +] + +[[area.polygons]] +vertices = [ + [2094, 3856], + [2094, 3951], +] + +[[area.polygons]] +vertices = [ + [2109, 3856], + [2109, 3956], +] + +[[area.polygons]] +vertices = [ + [2159, 3856], + [2159, 3872], +] + +[[area.polygons]] +vertices = [ + [2085, 3857], + [2086, 3857], + [2086, 3940], + [2085, 3940], +] + +[[area.polygons]] +vertices = [ + [2102, 3857], + [2103, 3857], + [2103, 3953], + [2102, 3953], +] + +[[area.polygons]] +vertices = [ + [2108, 3857], + [2108, 3955], +] + +[[area.polygons]] +vertices = [ + [2160, 3857], + [2162, 3857], + [2162, 3872], + [2160, 3872], +] + +[[area.polygons]] +vertices = [ + [2083, 3858], + [2084, 3858], + [2084, 3938], + [2083, 3938], +] + +[[area.polygons]] +vertices = [ + [2095, 3858], + [2095, 3952], +] + +[[area.polygons]] +vertices = [ + [2100, 3858], + [2101, 3858], + [2101, 3953], + [2100, 3953], +] + +[[area.polygons]] +vertices = [ + [2104, 3858], + [2107, 3858], + [2107, 3954], + [2104, 3954], +] + +[[area.polygons]] +vertices = [ + [2163, 3858], + [2163, 3871], +] + +[[area.polygons]] +vertices = [ + [2080, 3859], + [2082, 3859], + [2082, 3938], + [2080, 3938], +] + +[[area.polygons]] +vertices = [ + [2096, 3859], + [2099, 3859], + [2099, 3952], + [2096, 3952], +] + +[[area.polygons]] +vertices = [ + [2077, 3860], + [2079, 3860], + [2079, 3938], + [2077, 3938], +] + +[[area.polygons]] +vertices = [ + [2164, 3860], + [2164, 3871], +] + +[[area.polygons]] +vertices = [ + [2076, 3861], + [2076, 3941], +] + +[[area.polygons]] +vertices = [ + [2165, 3861], + [2165, 3870], +] + +[[area.polygons]] +vertices = [ + [2074, 3862], + [2075, 3862], + [2075, 3869], + [2074, 3869], +] + +[[area.polygons]] +vertices = [ + [2073, 3863], + [2073, 3869], +] + +[[area.polygons]] +vertices = [ + [2166, 3863], + [2166, 3869], +] + +[[area.polygons]] +vertices = [ + [2072, 3864], + [2072, 3868], +] + +[[area.polygons]] +vertices = [ + [2167, 3864], + [2167, 3868], +] + +[[area.polygons]] +vertices = [ + [2140, 3867], + [2140, 3871], +] + +[[area.polygons]] +vertices = [ + [2142, 3867], + [2147, 3867], +] + +[[area.polygons]] +vertices = [ + [2143, 3868], + [2147, 3868], + [2147, 3869], + [2143, 3869], +] + +[[area.polygons]] +vertices = [ + [2075, 3870], + [2075, 3942], +] + +[[area.polygons]] +vertices = [ + [2144, 3870], + [2147, 3870], + [2147, 3871], + [2144, 3871], +] + +[[area.polygons]] +vertices = [ + [2064, 3871], + [2064, 3931], +] + +[[area.polygons]] +vertices = [ + [2063, 3872], + [2063, 3930], +] + +[[area.polygons]] +vertices = [ + [2065, 3872], + [2065, 3932], +] + +[[area.polygons]] +vertices = [ + [2071, 3872], + [2074, 3872], + [2074, 3941], + [2071, 3941], +] + +[[area.polygons]] +vertices = [ + [2146, 3872], + [2147, 3872], +] + +[[area.polygons]] +vertices = [ + [2150, 3872], + [2151, 3872], +] + +[[area.polygons]] +vertices = [[2156, 3872]] + +[[area.polygons]] +vertices = [ + [2062, 3873], + [2062, 3929], +] + +[[area.polygons]] +vertices = [ + [2066, 3873], + [2066, 3932], +] + +[[area.polygons]] +vertices = [ + [2070, 3873], + [2070, 3944], +] + +[[area.polygons]] +vertices = [ + [2132, 3873], + [2138, 3873], + [2138, 3875], + [2132, 3875], +] + +[[area.polygons]] +vertices = [ + [2067, 3874], + [2069, 3874], + [2069, 3933], + [2067, 3933], +] + +[[area.polygons]] +vertices = [ + [2061, 3875], + [2061, 3928], +] + +[[area.polygons]] +vertices = [ + [2132, 3876], + [2137, 3876], + [2137, 3877], + [2132, 3877], +] + +[[area.polygons]] +vertices = [ + [2060, 3877], + [2060, 3927], +] + +[[area.polygons]] +vertices = [ + [2132, 3878], + [2136, 3878], +] + +[[area.polygons]] +vertices = [ + [2146, 3878], + [2151, 3878], + [2151, 3884], + [2146, 3884], +] + +[[area.polygons]] +vertices = [ + [2130, 3879], + [2130, 3884], +] + +[[area.polygons]] +vertices = [ + [2132, 3879], + [2135, 3879], +] + +[[area.polygons]] +vertices = [ + [2059, 3880], + [2059, 3920], +] + +[[area.polygons]] +vertices = [ + [2131, 3881], + [2145, 3881], + [2145, 3884], + [2131, 3884], +] + +[[area.polygons]] +vertices = [ + [2128, 3882], + [2129, 3882], + [2129, 3884], + [2128, 3884], +] + +[[area.polygons]] +vertices = [ + [2058, 3884], + [2058, 3919], +] + +[[area.polygons]] +vertices = [ + [2135, 3887], + [2145, 3887], + [2145, 3911], + [2135, 3911], +] + +[[area.polygons]] +vertices = [ + [2119, 3888], + [2119, 3957], +] + +[[area.polygons]] +vertices = [ + [2120, 3889], + [2120, 3893], +] + +[[area.polygons]] +vertices = [ + [2121, 3890], + [2131, 3890], + [2131, 3893], + [2121, 3893], +] + +[[area.polygons]] +vertices = [ + [2057, 3892], + [2057, 3895], +] + +[[area.polygons]] +vertices = [ + [2128, 3894], + [2131, 3894], + [2131, 3902], + [2128, 3902], +] + +[[area.polygons]] +vertices = [ + [2057, 3900], + [2057, 3916], +] + +[[area.polygons]] +vertices = [ + [2132, 3900], + [2134, 3900], +] + +[[area.polygons]] +vertices = [ + [2056, 3902], + [2056, 3904], +] + +[[area.polygons]] +vertices = [ + [2056, 3908], + [2056, 3912], +] + +[[area.polygons]] +vertices = [ + [2136, 3912], + [2144, 3912], + [2144, 3913], + [2136, 3913], +] + +[[area.polygons]] +vertices = [ + [2120, 3914], + [2120, 3957], +] + +[[area.polygons]] +vertices = [ + [2137, 3914], + [2143, 3914], + [2143, 3915], + [2137, 3915], +] + +[[area.polygons]] +vertices = [ + [2121, 3916], + [2121, 3957], +] + +[[area.polygons]] +vertices = [ + [2138, 3916], + [2142, 3916], +] + +[[area.polygons]] +vertices = [ + [2122, 3918], + [2122, 3958], +] + +[[area.polygons]] +vertices = [ + [2123, 3920], + [2123, 3958], +] + +[[area.polygons]] +vertices = [ + [2124, 3921], + [2124, 3957], +] + +[[area.polygons]] +vertices = [ + [2125, 3922], + [2125, 3957], +] + +[[area.polygons]] +vertices = [ + [2128, 3923], + [2129, 3923], + [2129, 3956], + [2128, 3956], +] + +[[area.polygons]] +vertices = [ + [2059, 3924], + [2059, 3926], +] + +[[area.polygons]] +vertices = [ + [2126, 3924], + [2127, 3924], + [2127, 3957], + [2126, 3957], +] + +[[area.polygons]] +vertices = [ + [2130, 3924], + [2130, 3957], +] + +[[area.polygons]] +vertices = [ + [2131, 3925], + [2131, 3957], +] + +[[area.polygons]] +vertices = [ + [2136, 3926], + [2137, 3926], + [2137, 3957], + [2136, 3957], +] + +[[area.polygons]] +vertices = [ + [2132, 3927], + [2135, 3927], + [2135, 3956], + [2132, 3956], +] + +[[area.polygons]] +vertices = [ + [2138, 3927], + [2139, 3927], + [2139, 3957], + [2138, 3957], +] + +[[area.polygons]] +vertices = [ + [2156, 3927], + [2159, 3927], + [2159, 3949], + [2156, 3949], +] + +[[area.polygons]] +vertices = [ + [2140, 3928], + [2142, 3928], + [2142, 3955], + [2140, 3955], +] + +[[area.polygons]] +vertices = [ + [2154, 3928], + [2155, 3928], + [2155, 3951], + [2154, 3951], +] + +[[area.polygons]] +vertices = [ + [2160, 3928], + [2162, 3928], + [2162, 3947], + [2160, 3947], +] + +[[area.polygons]] +vertices = [ + [2143, 3929], + [2143, 3955], +] + +[[area.polygons]] +vertices = [ + [2153, 3929], + [2153, 3952], +] + +[[area.polygons]] +vertices = [ + [2163, 3929], + [2163, 3946], +] + +[[area.polygons]] +vertices = [ + [2166, 3929], + [2169, 3929], + [2169, 3938], + [2166, 3938], +] + +[[area.polygons]] +vertices = [ + [2144, 3930], + [2145, 3930], + [2145, 3955], + [2144, 3955], +] + +[[area.polygons]] +vertices = [ + [2151, 3930], + [2152, 3930], + [2152, 3953], + [2151, 3953], +] + +[[area.polygons]] +vertices = [ + [2164, 3930], + [2165, 3930], + [2165, 3944], + [2164, 3944], +] + +[[area.polygons]] +vertices = [ + [2170, 3930], + [2170, 3934], +] + +[[area.polygons]] +vertices = [ + [2146, 3931], + [2150, 3931], + [2150, 3954], + [2146, 3954], +] + +[[area.polygons]] +vertices = [ + [2068, 3934], + [2069, 3934], + [2069, 3942], + [2068, 3942], +] + +[[area.polygons]] +vertices = [ + [2067, 3937], + [2067, 3941], +] + +[[area.polygons]] +vertices = [ + [2066, 3938], + [2066, 3939], +] + +[[area.polygons]] +vertices = [ + [2077, 3939], + [2078, 3939], +] + +[[area.polygons]] +vertices = [[2084, 3939]] + +[[area.polygons]] +vertices = [ + [2166, 3939], + [2168, 3939], +] + +[[area.polygons]] +vertices = [[2077, 3940]] + +[[area.polygons]] +vertices = [ + [2166, 3940], + [2167, 3940], + [2167, 3942], + [2166, 3942], +] + +[[area.polygons]] +vertices = [[2086, 3941]] + +[[area.polygons]] +vertices = [ + [2071, 3942], + [2072, 3942], + [2072, 3943], + [2071, 3943], +] + +[[area.polygons]] +vertices = [[2069, 3943]] + +[[area.polygons]] +vertices = [[2166, 3943]] + +[[area.polygons]] +vertices = [[2071, 3944]] + +[[area.polygons]] +vertices = [ + [2089, 3944], + [2093, 3944], + [2093, 3945], + [2089, 3945], +] + +[[area.polygons]] +vertices = [ + [2090, 3946], + [2093, 3946], +] + +[[area.polygons]] +vertices = [ + [2091, 3947], + [2093, 3947], + [2093, 3948], + [2091, 3948], +] + +[[area.polygons]] +vertices = [[2160, 3948]] + +[[area.polygons]] +vertices = [ + [2092, 3949], + [2093, 3949], +] + +[[area.polygons]] +vertices = [[2093, 3950]] + +[[area.polygons]] +vertices = [ + [2156, 3950], + [2158, 3950], +] + +[[area.polygons]] +vertices = [ + [2156, 3951], + [2157, 3951], +] + +[[area.polygons]] +vertices = [[2154, 3952]] + +[[area.polygons]] +vertices = [ + [2098, 3953], + [2099, 3953], +] + +[[area.polygons]] +vertices = [[2103, 3954]] + +[[area.polygons]] +vertices = [ + [2106, 3955], + [2107, 3955], +] + +[[area.polygons]] +vertices = [ + [2140, 3956], + [2141, 3956], +] + +[[area.polygons]] +vertices = [ + [2113, 3957], + [2116, 3957], +] + +[[area.polygons]] +vertices = [[2128, 3957]] + +[[area.polygons]] +vertices = [ + [2115, 3958], + [2116, 3958], +] diff --git a/.data/raw-cache/map/area/lurking_threats.toml b/.data/raw-cache/map/area/lurking_threats.toml new file mode 100644 index 000000000..60d6f6134 --- /dev/null +++ b/.data/raw-cache/map/area/lurking_threats.toml @@ -0,0 +1,163 @@ +# Lurking Threats music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lurking Threats. +# Bounds x 2048-2943, z 8576-10495; 25034 tiles as 23 rectangles. + +[[area]] +name = "Lurking Threats" +area_id = "area.lurking_threats" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2573, 8576], + [2622, 8576], + [2622, 8588], + [2573, 8588], +] + +[[area.polygons]] +vertices = [ + [2577, 8589], + [2622, 8589], +] + +[[area.polygons]] +vertices = [ + [2578, 8590], + [2622, 8590], +] + +[[area.polygons]] +vertices = [ + [2579, 8591], + [2622, 8591], +] + +[[area.polygons]] +vertices = [ + [2580, 8592], + [2622, 8592], + [2622, 8599], + [2580, 8599], +] + +[[area.polygons]] +vertices = [ + [2577, 8594], + [2579, 8594], + [2579, 8598], + [2577, 8598], +] + +[[area.polygons]] +vertices = [ + [2578, 8599], + [2579, 8599], +] + +[[area.polygons]] +vertices = [ + [2581, 8600], + [2622, 8600], +] + +[[area.polygons]] +vertices = [ + [2582, 8601], + [2584, 8601], +] + +[[area.polygons]] +vertices = [ + [2587, 8601], + [2622, 8601], +] + +[[area.polygons]] +vertices = [ + [2588, 8602], + [2622, 8602], +] + +[[area.polygons]] +vertices = [ + [2589, 8603], + [2622, 8603], + [2622, 8661], + [2589, 8661], +] + +[[area.polygons]] +vertices = [ + [2582, 8605], + [2588, 8605], + [2588, 8606], + [2582, 8606], +] + +[[area.polygons]] +vertices = [ + [2588, 8607], + [2588, 8661], +] + +[[area.polygons]] +vertices = [ + [2586, 8608], + [2587, 8608], + [2587, 8610], + [2586, 8610], +] + +[[area.polygons]] +vertices = [[2587, 8611]] + +[[area.polygons]] +vertices = [ + [2587, 8616], + [2587, 8661], +] + +[[area.polygons]] +vertices = [ + [2586, 8617], + [2586, 8661], +] + +[[area.polygons]] +vertices = [ + [2585, 8618], + [2585, 8661], +] + +[[area.polygons]] +vertices = [ + [2560, 8619], + [2584, 8619], + [2584, 8661], + [2560, 8661], +] + +[[area.polygons]] +vertices = [ + [2624, 8832], + [2751, 8832], + [2751, 8895], + [2624, 8895], +] + +[[area.polygons]] +vertices = [ + [2048, 10048], + [2111, 10048], + [2111, 10111], + [2048, 10111], +] + +[[area.polygons]] +vertices = [ + [2816, 10432], + [2943, 10432], + [2943, 10495], + [2816, 10495], +] diff --git a/.data/raw-cache/map/area/mad_eadgar.toml b/.data/raw-cache/map/area/mad_eadgar.toml new file mode 100644 index 000000000..98e44586f --- /dev/null +++ b/.data/raw-cache/map/area/mad_eadgar.toml @@ -0,0 +1,16 @@ +# Mad Eadgar music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mad Eadgar. +# Bounds x 2880-2943, z 10048-10111; 4096 tiles as 1 rectangles. + +[[area]] +name = "Mad Eadgar" +area_id = "area.mad_eadgar" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2880, 10048], + [2943, 10048], + [2943, 10111], + [2880, 10111], +] diff --git a/.data/raw-cache/map/area/mage_arena.toml b/.data/raw-cache/map/area/mage_arena.toml new file mode 100644 index 000000000..378a12d80 --- /dev/null +++ b/.data/raw-cache/map/area/mage_arena.toml @@ -0,0 +1,144 @@ +# Mage Arena music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mage Arena. +# Bounds x 2496-3126, z 3914-4735; 5560 tiles as 22 rectangles. + +[[area]] +name = "Mage Arena" +area_id = "area.mage_arena" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3092, 3914], + [3117, 3914], + [3117, 3949], + [3092, 3949], +] + +[[area.polygons]] +vertices = [ + [3091, 3915], + [3091, 3948], +] + +[[area.polygons]] +vertices = [ + [3118, 3915], + [3118, 3949], +] + +[[area.polygons]] +vertices = [ + [3090, 3916], + [3090, 3947], +] + +[[area.polygons]] +vertices = [ + [3119, 3916], + [3119, 3948], +] + +[[area.polygons]] +vertices = [ + [3089, 3917], + [3089, 3946], +] + +[[area.polygons]] +vertices = [ + [3120, 3917], + [3120, 3947], +] + +[[area.polygons]] +vertices = [ + [3088, 3918], + [3088, 3945], +] + +[[area.polygons]] +vertices = [ + [3121, 3918], + [3121, 3946], +] + +[[area.polygons]] +vertices = [ + [3087, 3919], + [3087, 3944], +] + +[[area.polygons]] +vertices = [ + [3122, 3919], + [3122, 3945], +] + +[[area.polygons]] +vertices = [ + [3086, 3920], + [3086, 3943], +] + +[[area.polygons]] +vertices = [ + [3123, 3920], + [3123, 3944], +] + +[[area.polygons]] +vertices = [ + [3085, 3921], + [3085, 3942], +] + +[[area.polygons]] +vertices = [ + [3124, 3921], + [3124, 3943], +] + +[[area.polygons]] +vertices = [ + [3084, 3922], + [3084, 3941], +] + +[[area.polygons]] +vertices = [ + [3125, 3922], + [3125, 3942], +] + +[[area.polygons]] +vertices = [ + [3126, 3923], + [3126, 3941], +] + +[[area.polygons]] +vertices = [ + [3093, 3950], + [3117, 3950], +] + +[[area.polygons]] +vertices = [ + [3094, 3951], + [3116, 3951], +] + +[[area.polygons]] +vertices = [ + [3095, 3952], + [3115, 3952], +] + +[[area.polygons]] +vertices = [ + [2496, 4672], + [2559, 4672], + [2559, 4735], + [2496, 4735], +] diff --git a/.data/raw-cache/map/area/mage_training_arena.toml b/.data/raw-cache/map/area/mage_training_arena.toml new file mode 100644 index 000000000..1215d6880 --- /dev/null +++ b/.data/raw-cache/map/area/mage_training_arena.toml @@ -0,0 +1,25 @@ +# The Mage Training Arena grounds directly north of Emir's Arena; the "Shine" music area. +# +# Regenerated as rectangles from the OSRS wiki music map: 300 tiles moved to +# neighbouring music areas. + +[[area]] +name = "Mage Training Arena" +area_id = "area.mage_training_arena" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3330, 3292], + [3395, 3292], + [3395, 3329], + [3330, 3329], +] + +[[area.polygons]] +vertices = [ + [3380, 3330], + [3395, 3330], + [3395, 3335], + [3380, 3335], +] diff --git a/.data/raw-cache/map/area/magic_magic_magic.toml b/.data/raw-cache/map/area/magic_magic_magic.toml new file mode 100644 index 000000000..df4e0817d --- /dev/null +++ b/.data/raw-cache/map/area/magic_magic_magic.toml @@ -0,0 +1,50 @@ +# Magic Magic Magic music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Magic Magic Magic. +# Bounds x 2644-2654, z 3213-3223; 30 tiles as 10 rectangles. + +[[area]] +name = "Magic Magic Magic" +area_id = "area.magic_magic_magic" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2646, 3213], + [2651, 3213], +] + +[[area.polygons]] +vertices = [[2645, 3214]] + +[[area.polygons]] +vertices = [[2652, 3214]] + +[[area.polygons]] +vertices = [ + [2644, 3215], + [2644, 3221], +] + +[[area.polygons]] +vertices = [[2653, 3215]] + +[[area.polygons]] +vertices = [ + [2654, 3216], + [2654, 3220], +] + +[[area.polygons]] +vertices = [[2653, 3221]] + +[[area.polygons]] +vertices = [[2645, 3222]] + +[[area.polygons]] +vertices = [[2652, 3222]] + +[[area.polygons]] +vertices = [ + [2646, 3223], + [2651, 3223], +] diff --git a/.data/raw-cache/map/area/major_miner.toml b/.data/raw-cache/map/area/major_miner.toml new file mode 100644 index 000000000..0e15a8f9f --- /dev/null +++ b/.data/raw-cache/map/area/major_miner.toml @@ -0,0 +1,16 @@ +# Major Miner music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Major Miner. +# Bounds x 2368-2431, z 10176-10239; 4096 tiles as 1 rectangles. + +[[area]] +name = "Major Miner" +area_id = "area.major_miner" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 10176], + [2431, 10176], + [2431, 10239], + [2368, 10239], +] diff --git a/.data/raw-cache/map/area/making_waves.toml b/.data/raw-cache/map/area/making_waves.toml new file mode 100644 index 000000000..f73b6a710 --- /dev/null +++ b/.data/raw-cache/map/area/making_waves.toml @@ -0,0 +1,142 @@ +# Making Waves music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Making Waves. +# Bounds x 2305-2356, z 3663-3703; 1727 tiles as 21 rectangles. + +[[area]] +name = "Making Waves" +area_id = "area.making_waves" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2312, 3663], + [2347, 3663], + [2347, 3699], + [2312, 3699], +] + +[[area.polygons]] +vertices = [ + [2348, 3664], + [2348, 3701], +] + +[[area.polygons]] +vertices = [ + [2349, 3673], + [2355, 3673], + [2355, 3701], + [2349, 3701], +] + +[[area.polygons]] +vertices = [ + [2356, 3674], + [2356, 3700], +] + +[[area.polygons]] +vertices = [ + [2311, 3686], + [2311, 3694], +] + +[[area.polygons]] +vertices = [ + [2310, 3687], + [2310, 3693], +] + +[[area.polygons]] +vertices = [ + [2309, 3688], + [2309, 3691], +] + +[[area.polygons]] +vertices = [ + [2305, 3689], + [2308, 3689], + [2308, 3690], + [2305, 3690], +] + +[[area.polygons]] +vertices = [ + [2311, 3697], + [2311, 3703], +] + +[[area.polygons]] +vertices = [ + [2309, 3698], + [2310, 3698], + [2310, 3703], + [2309, 3703], +] + +[[area.polygons]] +vertices = [ + [2308, 3699], + [2308, 3702], +] + +[[area.polygons]] +vertices = [ + [2312, 3700], + [2323, 3700], +] + +[[area.polygons]] +vertices = [ + [2329, 3700], + [2347, 3700], +] + +[[area.polygons]] +vertices = [ + [2312, 3701], + [2318, 3701], +] + +[[area.polygons]] +vertices = [ + [2320, 3701], + [2322, 3701], +] + +[[area.polygons]] +vertices = [ + [2330, 3701], + [2347, 3701], +] + +[[area.polygons]] +vertices = [ + [2312, 3702], + [2315, 3702], +] + +[[area.polygons]] +vertices = [ + [2331, 3702], + [2333, 3702], +] + +[[area.polygons]] +vertices = [ + [2335, 3702], + [2338, 3702], +] + +[[area.polygons]] +vertices = [ + [2351, 3702], + [2354, 3702], +] + +[[area.polygons]] +vertices = [ + [2312, 3703], + [2313, 3703], +] diff --git a/.data/raw-cache/map/area/malady.toml b/.data/raw-cache/map/area/malady.toml new file mode 100644 index 000000000..893dab5b2 --- /dev/null +++ b/.data/raw-cache/map/area/malady.toml @@ -0,0 +1,16 @@ +# Malady music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Malady. +# Bounds x 2112-2175, z 5248-5311; 4096 tiles as 1 rectangles. + +[[area]] +name = "Malady" +area_id = "area.malady" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2112, 5248], + [2175, 5248], + [2175, 5311], + [2112, 5311], +] diff --git a/.data/raw-cache/map/area/march_of_the_shayzien.toml b/.data/raw-cache/map/area/march_of_the_shayzien.toml new file mode 100644 index 000000000..1d34cd87d --- /dev/null +++ b/.data/raw-cache/map/area/march_of_the_shayzien.toml @@ -0,0 +1,1114 @@ +# March of the Shayzien music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: March of the Shayzien. +# Bounds x 1280-1541, z 3482-9983; 19621 tiles as 181 rectangles. + +[[area]] +name = "March of the Shayzien" +area_id = "area.march_of_the_shayzien" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1381, 3482], + [1389, 3482], + [1389, 3582], + [1381, 3582], +] + +[[area.polygons]] +vertices = [ + [1380, 3483], + [1380, 3591], +] + +[[area.polygons]] +vertices = [ + [1390, 3483], + [1390, 3581], +] + +[[area.polygons]] +vertices = [ + [1379, 3484], + [1379, 3592], +] + +[[area.polygons]] +vertices = [ + [1391, 3484], + [1391, 3580], +] + +[[area.polygons]] +vertices = [ + [1378, 3485], + [1378, 3593], +] + +[[area.polygons]] +vertices = [ + [1392, 3485], + [1392, 3579], +] + +[[area.polygons]] +vertices = [ + [1377, 3486], + [1377, 3594], +] + +[[area.polygons]] +vertices = [ + [1393, 3486], + [1393, 3578], +] + +[[area.polygons]] +vertices = [ + [1376, 3487], + [1376, 3595], +] + +[[area.polygons]] +vertices = [ + [1394, 3487], + [1394, 3577], +] + +[[area.polygons]] +vertices = [ + [1375, 3488], + [1375, 3596], +] + +[[area.polygons]] +vertices = [ + [1395, 3488], + [1395, 3576], +] + +[[area.polygons]] +vertices = [ + [1374, 3489], + [1374, 3597], +] + +[[area.polygons]] +vertices = [ + [1396, 3489], + [1396, 3575], +] + +[[area.polygons]] +vertices = [ + [1373, 3490], + [1373, 3598], +] + +[[area.polygons]] +vertices = [ + [1397, 3490], + [1397, 3574], +] + +[[area.polygons]] +vertices = [ + [1372, 3491], + [1372, 3599], +] + +[[area.polygons]] +vertices = [ + [1398, 3491], + [1398, 3573], +] + +[[area.polygons]] +vertices = [ + [1371, 3492], + [1371, 3600], +] + +[[area.polygons]] +vertices = [ + [1399, 3492], + [1399, 3572], +] + +[[area.polygons]] +vertices = [ + [1370, 3493], + [1370, 3601], +] + +[[area.polygons]] +vertices = [ + [1400, 3493], + [1400, 3571], +] + +[[area.polygons]] +vertices = [ + [1369, 3494], + [1369, 3601], +] + +[[area.polygons]] +vertices = [ + [1401, 3494], + [1401, 3570], +] + +[[area.polygons]] +vertices = [ + [1368, 3495], + [1368, 3601], +] + +[[area.polygons]] +vertices = [ + [1402, 3495], + [1402, 3569], +] + +[[area.polygons]] +vertices = [ + [1367, 3496], + [1367, 3601], +] + +[[area.polygons]] +vertices = [ + [1403, 3496], + [1403, 3568], +] + +[[area.polygons]] +vertices = [ + [1366, 3497], + [1366, 3601], +] + +[[area.polygons]] +vertices = [ + [1404, 3497], + [1404, 3567], +] + +[[area.polygons]] +vertices = [ + [1365, 3498], + [1365, 3601], +] + +[[area.polygons]] +vertices = [ + [1405, 3498], + [1405, 3566], +] + +[[area.polygons]] +vertices = [ + [1406, 3499], + [1406, 3565], +] + +[[area.polygons]] +vertices = [ + [1407, 3500], + [1407, 3564], +] + +[[area.polygons]] +vertices = [ + [1408, 3501], + [1408, 3563], +] + +[[area.polygons]] +vertices = [ + [1409, 3502], + [1409, 3562], +] + +[[area.polygons]] +vertices = [ + [1410, 3503], + [1410, 3561], +] + +[[area.polygons]] +vertices = [ + [1327, 3504], + [1343, 3504], + [1343, 3592], + [1327, 3592], +] + +[[area.polygons]] +vertices = [ + [1411, 3504], + [1411, 3560], +] + +[[area.polygons]] +vertices = [ + [1326, 3505], + [1326, 3592], +] + +[[area.polygons]] +vertices = [ + [1344, 3505], + [1344, 3601], +] + +[[area.polygons]] +vertices = [ + [1412, 3505], + [1412, 3524], +] + +[[area.polygons]] +vertices = [ + [1325, 3506], + [1325, 3592], +] + +[[area.polygons]] +vertices = [ + [1345, 3506], + [1345, 3601], +] + +[[area.polygons]] +vertices = [ + [1413, 3506], + [1413, 3523], +] + +[[area.polygons]] +vertices = [ + [1324, 3507], + [1324, 3592], +] + +[[area.polygons]] +vertices = [ + [1346, 3507], + [1346, 3601], +] + +[[area.polygons]] +vertices = [ + [1414, 3507], + [1414, 3522], +] + +[[area.polygons]] +vertices = [ + [1323, 3508], + [1323, 3592], +] + +[[area.polygons]] +vertices = [ + [1347, 3508], + [1347, 3601], +] + +[[area.polygons]] +vertices = [ + [1322, 3509], + [1322, 3592], +] + +[[area.polygons]] +vertices = [ + [1348, 3509], + [1348, 3601], +] + +[[area.polygons]] +vertices = [ + [1321, 3510], + [1321, 3592], +] + +[[area.polygons]] +vertices = [ + [1349, 3510], + [1349, 3601], +] + +[[area.polygons]] +vertices = [ + [1320, 3511], + [1320, 3592], +] + +[[area.polygons]] +vertices = [ + [1350, 3511], + [1350, 3601], +] + +[[area.polygons]] +vertices = [ + [1319, 3512], + [1319, 3592], +] + +[[area.polygons]] +vertices = [ + [1351, 3512], + [1351, 3601], +] + +[[area.polygons]] +vertices = [ + [1318, 3513], + [1318, 3592], +] + +[[area.polygons]] +vertices = [ + [1352, 3513], + [1352, 3601], +] + +[[area.polygons]] +vertices = [ + [1317, 3514], + [1317, 3592], +] + +[[area.polygons]] +vertices = [ + [1353, 3514], + [1353, 3601], +] + +[[area.polygons]] +vertices = [ + [1316, 3515], + [1316, 3592], +] + +[[area.polygons]] +vertices = [ + [1354, 3515], + [1354, 3601], +] + +[[area.polygons]] +vertices = [ + [1290, 3516], + [1315, 3516], + [1315, 3568], + [1290, 3568], +] + +[[area.polygons]] +vertices = [ + [1355, 3516], + [1364, 3516], + [1364, 3601], + [1355, 3601], +] + +[[area.polygons]] +vertices = [ + [1412, 3545], + [1412, 3559], +] + +[[area.polygons]] +vertices = [ + [1413, 3546], + [1413, 3558], +] + +[[area.polygons]] +vertices = [ + [1414, 3547], + [1414, 3557], +] + +[[area.polygons]] +vertices = [ + [1291, 3569], + [1315, 3569], +] + +[[area.polygons]] +vertices = [ + [1292, 3570], + [1315, 3570], +] + +[[area.polygons]] +vertices = [ + [1293, 3571], + [1315, 3571], +] + +[[area.polygons]] +vertices = [ + [1294, 3572], + [1315, 3572], +] + +[[area.polygons]] +vertices = [ + [1295, 3573], + [1315, 3573], +] + +[[area.polygons]] +vertices = [ + [1296, 3574], + [1315, 3574], +] + +[[area.polygons]] +vertices = [ + [1297, 3575], + [1315, 3575], +] + +[[area.polygons]] +vertices = [ + [1298, 3576], + [1315, 3576], +] + +[[area.polygons]] +vertices = [ + [1299, 3577], + [1315, 3577], +] + +[[area.polygons]] +vertices = [ + [1300, 3578], + [1315, 3578], +] + +[[area.polygons]] +vertices = [ + [1301, 3579], + [1315, 3579], +] + +[[area.polygons]] +vertices = [ + [1302, 3580], + [1315, 3580], +] + +[[area.polygons]] +vertices = [ + [1303, 3581], + [1315, 3581], +] + +[[area.polygons]] +vertices = [ + [1304, 3582], + [1315, 3582], +] + +[[area.polygons]] +vertices = [ + [1305, 3583], + [1315, 3583], +] + +[[area.polygons]] +vertices = [ + [1381, 3583], + [1388, 3583], +] + +[[area.polygons]] +vertices = [ + [1306, 3584], + [1315, 3584], +] + +[[area.polygons]] +vertices = [ + [1381, 3584], + [1387, 3584], +] + +[[area.polygons]] +vertices = [ + [1307, 3585], + [1315, 3585], +] + +[[area.polygons]] +vertices = [ + [1381, 3585], + [1386, 3585], +] + +[[area.polygons]] +vertices = [ + [1308, 3586], + [1315, 3586], +] + +[[area.polygons]] +vertices = [ + [1381, 3586], + [1385, 3586], +] + +[[area.polygons]] +vertices = [ + [1309, 3587], + [1315, 3587], +] + +[[area.polygons]] +vertices = [ + [1381, 3587], + [1384, 3587], +] + +[[area.polygons]] +vertices = [ + [1310, 3588], + [1315, 3588], +] + +[[area.polygons]] +vertices = [ + [1381, 3588], + [1383, 3588], +] + +[[area.polygons]] +vertices = [ + [1311, 3589], + [1315, 3589], +] + +[[area.polygons]] +vertices = [ + [1381, 3589], + [1382, 3589], +] + +[[area.polygons]] +vertices = [ + [1312, 3590], + [1315, 3590], +] + +[[area.polygons]] +vertices = [[1381, 3590]] + +[[area.polygons]] +vertices = [ + [1313, 3591], + [1315, 3591], +] + +[[area.polygons]] +vertices = [ + [1314, 3592], + [1315, 3592], +] + +[[area.polygons]] +vertices = [ + [1331, 3593], + [1343, 3593], +] + +[[area.polygons]] +vertices = [ + [1332, 3594], + [1343, 3594], +] + +[[area.polygons]] +vertices = [ + [1333, 3595], + [1343, 3595], +] + +[[area.polygons]] +vertices = [ + [1334, 3596], + [1343, 3596], +] + +[[area.polygons]] +vertices = [ + [1335, 3597], + [1343, 3597], +] + +[[area.polygons]] +vertices = [ + [1336, 3598], + [1343, 3598], +] + +[[area.polygons]] +vertices = [ + [1337, 3599], + [1343, 3599], +] + +[[area.polygons]] +vertices = [ + [1338, 3600], + [1343, 3600], +] + +[[area.polygons]] +vertices = [ + [1339, 3601], + [1343, 3601], +] + +[[area.polygons]] +vertices = [ + [1536, 3674], + [1536, 3711], +] + +[[area.polygons]] +vertices = [ + [1535, 3675], + [1535, 3711], +] + +[[area.polygons]] +vertices = [ + [1534, 3676], + [1534, 3711], +] + +[[area.polygons]] +vertices = [ + [1533, 3677], + [1533, 3711], +] + +[[area.polygons]] +vertices = [ + [1532, 3678], + [1532, 3712], +] + +[[area.polygons]] +vertices = [ + [1537, 3678], + [1537, 3679], +] + +[[area.polygons]] +vertices = [ + [1531, 3679], + [1531, 3713], +] + +[[area.polygons]] +vertices = [ + [1530, 3680], + [1530, 3714], +] + +[[area.polygons]] +vertices = [ + [1502, 3681], + [1529, 3681], + [1529, 3715], + [1502, 3715], +] + +[[area.polygons]] +vertices = [ + [1501, 3683], + [1501, 3731], +] + +[[area.polygons]] +vertices = [ + [1500, 3684], + [1500, 3731], +] + +[[area.polygons]] +vertices = [ + [1537, 3684], + [1537, 3704], +] + +[[area.polygons]] +vertices = [ + [1499, 3685], + [1499, 3732], +] + +[[area.polygons]] +vertices = [ + [1538, 3685], + [1538, 3703], +] + +[[area.polygons]] +vertices = [ + [1472, 3686], + [1474, 3686], + [1474, 3731], + [1472, 3731], +] + +[[area.polygons]] +vertices = [ + [1498, 3686], + [1498, 3732], +] + +[[area.polygons]] +vertices = [ + [1475, 3687], + [1477, 3687], + [1477, 3730], + [1475, 3730], +] + +[[area.polygons]] +vertices = [ + [1497, 3687], + [1497, 3732], +] + +[[area.polygons]] +vertices = [ + [1415, 3688], + [1456, 3688], + [1456, 3727], + [1415, 3727], +] + +[[area.polygons]] +vertices = [ + [1468, 3688], + [1471, 3688], + [1471, 3691], + [1468, 3691], +] + +[[area.polygons]] +vertices = [ + [1496, 3688], + [1496, 3732], +] + +[[area.polygons]] +vertices = [ + [1478, 3689], + [1478, 3730], +] + +[[area.polygons]] +vertices = [ + [1483, 3689], + [1495, 3689], + [1495, 3730], + [1483, 3730], +] + +[[area.polygons]] +vertices = [ + [1539, 3689], + [1539, 3699], +] + +[[area.polygons]] +vertices = [ + [1479, 3690], + [1482, 3690], + [1482, 3730], + [1479, 3730], +] + +[[area.polygons]] +vertices = [ + [1540, 3690], + [1540, 3698], +] + +[[area.polygons]] +vertices = [ + [1541, 3691], + [1541, 3697], +] + +[[area.polygons]] +vertices = [ + [1469, 3692], + [1471, 3692], +] + +[[area.polygons]] +vertices = [ + [1470, 3693], + [1471, 3693], +] + +[[area.polygons]] +vertices = [ + [1471, 3694], + [1471, 3734], +] + +[[area.polygons]] +vertices = [ + [1470, 3699], + [1470, 3724], +] + +[[area.polygons]] +vertices = [ + [1457, 3700], + [1457, 3731], +] + +[[area.polygons]] +vertices = [ + [1469, 3700], + [1469, 3723], +] + +[[area.polygons]] +vertices = [ + [1458, 3701], + [1458, 3732], +] + +[[area.polygons]] +vertices = [ + [1468, 3701], + [1468, 3709], +] + +[[area.polygons]] +vertices = [ + [1459, 3702], + [1459, 3733], +] + +[[area.polygons]] +vertices = [ + [1460, 3703], + [1460, 3734], +] + +[[area.polygons]] +vertices = [ + [1537, 3709], + [1537, 3711], +] + +[[area.polygons]] +vertices = [ + [1538, 3710], + [1538, 3711], +] + +[[area.polygons]] +vertices = [ + [1468, 3714], + [1468, 3722], +] + +[[area.polygons]] +vertices = [ + [1502, 3716], + [1528, 3716], +] + +[[area.polygons]] +vertices = [ + [1502, 3717], + [1527, 3717], +] + +[[area.polygons]] +vertices = [ + [1502, 3718], + [1526, 3718], +] + +[[area.polygons]] +vertices = [ + [1502, 3719], + [1525, 3719], +] + +[[area.polygons]] +vertices = [ + [1502, 3720], + [1524, 3720], +] + +[[area.polygons]] +vertices = [ + [1502, 3721], + [1523, 3721], +] + +[[area.polygons]] +vertices = [ + [1502, 3722], + [1522, 3722], +] + +[[area.polygons]] +vertices = [ + [1502, 3723], + [1520, 3723], +] + +[[area.polygons]] +vertices = [ + [1502, 3724], + [1517, 3724], +] + +[[area.polygons]] +vertices = [ + [1461, 3725], + [1461, 3735], +] + +[[area.polygons]] +vertices = [ + [1502, 3725], + [1515, 3725], +] + +[[area.polygons]] +vertices = [ + [1502, 3726], + [1513, 3726], +] + +[[area.polygons]] +vertices = [ + [1502, 3727], + [1510, 3727], +] + +[[area.polygons]] +vertices = [ + [1430, 3728], + [1456, 3728], +] + +[[area.polygons]] +vertices = [ + [1502, 3728], + [1508, 3728], +] + +[[area.polygons]] +vertices = [ + [1431, 3729], + [1456, 3729], +] + +[[area.polygons]] +vertices = [ + [1502, 3729], + [1506, 3729], +] + +[[area.polygons]] +vertices = [ + [1432, 3730], + [1456, 3730], +] + +[[area.polygons]] +vertices = [ + [1470, 3730], + [1470, 3735], +] + +[[area.polygons]] +vertices = [ + [1502, 3730], + [1504, 3730], +] + +[[area.polygons]] +vertices = [ + [1433, 3731], + [1456, 3731], +] + +[[area.polygons]] +vertices = [ + [1462, 3731], + [1462, 3735], +] + +[[area.polygons]] +vertices = [ + [1469, 3731], + [1469, 3735], +] + +[[area.polygons]] +vertices = [ + [1487, 3731], + [1495, 3731], +] + +[[area.polygons]] +vertices = [ + [1463, 3732], + [1464, 3732], + [1464, 3735], + [1463, 3735], +] + +[[area.polygons]] +vertices = [ + [1468, 3732], + [1468, 3735], +] + +[[area.polygons]] +vertices = [ + [1472, 3732], + [1473, 3732], +] + +[[area.polygons]] +vertices = [ + [1489, 3732], + [1495, 3732], +] + +[[area.polygons]] +vertices = [[1472, 3733]] + +[[area.polygons]] +vertices = [ + [1280, 9920], + [1343, 9920], + [1343, 9983], + [1280, 9983], +] diff --git a/.data/raw-cache/map/area/marooned.toml b/.data/raw-cache/map/area/marooned.toml new file mode 100644 index 000000000..596fb8272 --- /dev/null +++ b/.data/raw-cache/map/area/marooned.toml @@ -0,0 +1,262 @@ +# Marooned music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Marooned. +# Bounds x 2887-3071, z 2698-5503; 5749 tiles as 39 rectangles. + +[[area]] +name = "Marooned" +area_id = "area.marooned" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2924, 2698], + [2926, 2698], + [2926, 2737], + [2924, 2737], +] + +[[area.polygons]] +vertices = [ + [2921, 2699], + [2923, 2699], + [2923, 2735], + [2921, 2735], +] + +[[area.polygons]] +vertices = [ + [2927, 2699], + [2928, 2699], + [2928, 2736], + [2927, 2736], +] + +[[area.polygons]] +vertices = [ + [2920, 2700], + [2920, 2735], +] + +[[area.polygons]] +vertices = [ + [2929, 2700], + [2930, 2700], + [2930, 2735], + [2929, 2735], +] + +[[area.polygons]] +vertices = [ + [2919, 2701], + [2919, 2734], +] + +[[area.polygons]] +vertices = [ + [2931, 2701], + [2932, 2701], + [2932, 2735], + [2931, 2735], +] + +[[area.polygons]] +vertices = [ + [2918, 2702], + [2918, 2733], +] + +[[area.polygons]] +vertices = [ + [2933, 2702], + [2933, 2734], +] + +[[area.polygons]] +vertices = [ + [2917, 2703], + [2917, 2733], +] + +[[area.polygons]] +vertices = [ + [2934, 2703], + [2934, 2733], +] + +[[area.polygons]] +vertices = [ + [2915, 2704], + [2916, 2704], + [2916, 2734], + [2915, 2734], +] + +[[area.polygons]] +vertices = [ + [2914, 2705], + [2914, 2737], +] + +[[area.polygons]] +vertices = [ + [2935, 2705], + [2935, 2732], +] + +[[area.polygons]] +vertices = [ + [2911, 2706], + [2913, 2706], + [2913, 2738], + [2911, 2738], +] + +[[area.polygons]] +vertices = [ + [2892, 2707], + [2894, 2707], + [2894, 2739], + [2892, 2739], +] + +[[area.polygons]] +vertices = [ + [2910, 2707], + [2910, 2740], +] + +[[area.polygons]] +vertices = [ + [2890, 2708], + [2891, 2708], + [2891, 2723], + [2890, 2723], +] + +[[area.polygons]] +vertices = [ + [2895, 2708], + [2901, 2708], + [2901, 2740], + [2895, 2740], +] + +[[area.polygons]] +vertices = [ + [2907, 2708], + [2909, 2708], + [2909, 2740], + [2907, 2740], +] + +[[area.polygons]] +vertices = [ + [2936, 2708], + [2936, 2729], +] + +[[area.polygons]] +vertices = [ + [2889, 2709], + [2889, 2722], +] + +[[area.polygons]] +vertices = [ + [2902, 2709], + [2906, 2709], + [2906, 2740], + [2902, 2740], +] + +[[area.polygons]] +vertices = [ + [2888, 2710], + [2888, 2721], +] + +[[area.polygons]] +vertices = [ + [2937, 2711], + [2937, 2728], +] + +[[area.polygons]] +vertices = [ + [2938, 2714], + [2938, 2724], +] + +[[area.polygons]] +vertices = [ + [2887, 2715], + [2887, 2720], +] + +[[area.polygons]] +vertices = [ + [2939, 2717], + [2939, 2721], +] + +[[area.polygons]] +vertices = [ + [2891, 2724], + [2891, 2738], +] + +[[area.polygons]] +vertices = [ + [2890, 2731], + [2890, 2737], +] + +[[area.polygons]] +vertices = [ + [2889, 2733], + [2889, 2736], +] + +[[area.polygons]] +vertices = [ + [2915, 2735], + [2915, 2736], +] + +[[area.polygons]] +vertices = [ + [2922, 2736], + [2923, 2736], +] + +[[area.polygons]] +vertices = [[2929, 2736]] + +[[area.polygons]] +vertices = [[2927, 2737]] + +[[area.polygons]] +vertices = [ + [2911, 2739], + [2912, 2739], +] + +[[area.polygons]] +vertices = [ + [2893, 2740], + [2894, 2740], +] + +[[area.polygons]] +vertices = [ + [2899, 2741], + [2904, 2741], +] + +[[area.polygons]] +vertices = [ + [3008, 5440], + [3071, 5440], + [3071, 5503], + [3008, 5503], +] diff --git a/.data/raw-cache/map/area/marzipan.toml b/.data/raw-cache/map/area/marzipan.toml new file mode 100644 index 000000000..107715709 --- /dev/null +++ b/.data/raw-cache/map/area/marzipan.toml @@ -0,0 +1,24 @@ +# Marzipan music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Marzipan. +# Bounds x 2752-2879, z 10048-10239; 12288 tiles as 2 rectangles. + +[[area]] +name = "Marzipan" +area_id = "area.marzipan" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 10048], + [2879, 10048], + [2879, 10111], + [2816, 10111], +] + +[[area.polygons]] +vertices = [ + [2752, 10112], + [2815, 10112], + [2815, 10239], + [2752, 10239], +] diff --git a/.data/raw-cache/map/area/masquerade.toml b/.data/raw-cache/map/area/masquerade.toml new file mode 100644 index 000000000..ab1c3f820 --- /dev/null +++ b/.data/raw-cache/map/area/masquerade.toml @@ -0,0 +1,16 @@ +# Masquerade music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Masquerade. +# Bounds x 2688-2751, z 9920-10047; 8192 tiles as 1 rectangles. + +[[area]] +name = "Masquerade" +area_id = "area.masquerade" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 9920], + [2751, 9920], + [2751, 10047], + [2688, 10047], +] diff --git a/.data/raw-cache/map/area/mausoleum.toml b/.data/raw-cache/map/area/mausoleum.toml new file mode 100644 index 000000000..7ae2dd805 --- /dev/null +++ b/.data/raw-cache/map/area/mausoleum.toml @@ -0,0 +1,16 @@ +# Mausoleum music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mausoleum. +# Bounds x 3392-3455, z 9856-9919; 4096 tiles as 1 rectangles. + +[[area]] +name = "Mausoleum" +area_id = "area.mausoleum" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3392, 9856], + [3455, 9856], + [3455, 9919], + [3392, 9919], +] diff --git a/.data/raw-cache/map/area/maws_jaws_and_claws.toml b/.data/raw-cache/map/area/maws_jaws_and_claws.toml new file mode 100644 index 000000000..772352647 --- /dev/null +++ b/.data/raw-cache/map/area/maws_jaws_and_claws.toml @@ -0,0 +1,32 @@ +# Maws Jaws & Claws music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Maws Jaws & Claws. +# Bounds x 1216-1407, z 1216-1343; 12288 tiles as 3 rectangles. + +[[area]] +name = "Maws Jaws & Claws" +area_id = "area.maws_jaws_and_claws" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1216, 1216], + [1279, 1216], + [1279, 1279], + [1216, 1279], +] + +[[area.polygons]] +vertices = [ + [1344, 1216], + [1407, 1216], + [1407, 1279], + [1344, 1279], +] + +[[area.polygons]] +vertices = [ + [1280, 1280], + [1343, 1280], + [1343, 1343], + [1280, 1343], +] diff --git a/.data/raw-cache/map/area/method_of_madness.toml b/.data/raw-cache/map/area/method_of_madness.toml new file mode 100644 index 000000000..54f67405d --- /dev/null +++ b/.data/raw-cache/map/area/method_of_madness.toml @@ -0,0 +1,24 @@ +# Method of Madness music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Method of Madness. +# Bounds x 3040-3327, z 5184-9823; 2048 tiles as 2 rectangles. + +[[area]] +name = "Method of Madness" +area_id = "area.method_of_madness" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3040, 5184], + [3071, 5184], + [3071, 5215], + [3040, 5215], +] + +[[area.polygons]] +vertices = [ + [3296, 9792], + [3327, 9792], + [3327, 9823], + [3296, 9823], +] diff --git a/.data/raw-cache/map/area/miles_away.toml b/.data/raw-cache/map/area/miles_away.toml new file mode 100644 index 000000000..7416eab99 --- /dev/null +++ b/.data/raw-cache/map/area/miles_away.toml @@ -0,0 +1,16 @@ +# Miles Away music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Miles Away. +# Bounds x 2624-2687, z 4672-4735; 4096 tiles as 1 rectangles. + +[[area]] +name = "Miles Away" +area_id = "area.miles_away" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2624, 4672], + [2687, 4672], + [2687, 4735], + [2624, 4735], +] diff --git a/.data/raw-cache/map/area/military_life.toml b/.data/raw-cache/map/area/military_life.toml new file mode 100644 index 000000000..a21721690 --- /dev/null +++ b/.data/raw-cache/map/area/military_life.toml @@ -0,0 +1,430 @@ +# Military Life music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Military Life. +# Bounds x 1468-1557, z 3608-3689; 5123 tiles as 65 rectangles. + +[[area]] +name = "Military Life" +area_id = "area.military_life" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1511, 3608], + [1518, 3608], + [1518, 3680], + [1511, 3680], +] + +[[area.polygons]] +vertices = [ + [1509, 3609], + [1510, 3609], + [1510, 3680], + [1509, 3680], +] + +[[area.polygons]] +vertices = [ + [1519, 3609], + [1519, 3680], +] + +[[area.polygons]] +vertices = [ + [1508, 3610], + [1508, 3680], +] + +[[area.polygons]] +vertices = [ + [1520, 3610], + [1523, 3610], + [1523, 3680], + [1520, 3680], +] + +[[area.polygons]] +vertices = [ + [1524, 3611], + [1524, 3680], +] + +[[area.polygons]] +vertices = [ + [1525, 3612], + [1528, 3612], + [1528, 3680], + [1525, 3680], +] + +[[area.polygons]] +vertices = [ + [1529, 3613], + [1529, 3680], +] + +[[area.polygons]] +vertices = [ + [1507, 3614], + [1507, 3621], +] + +[[area.polygons]] +vertices = [ + [1530, 3614], + [1532, 3614], + [1532, 3677], + [1530, 3677], +] + +[[area.polygons]] +vertices = [ + [1506, 3615], + [1506, 3620], +] + +[[area.polygons]] +vertices = [ + [1533, 3615], + [1533, 3676], +] + +[[area.polygons]] +vertices = [ + [1541, 3615], + [1546, 3615], + [1546, 3646], + [1541, 3646], +] + +[[area.polygons]] +vertices = [ + [1534, 3616], + [1534, 3675], +] + +[[area.polygons]] +vertices = [ + [1535, 3617], + [1535, 3674], +] + +[[area.polygons]] +vertices = [ + [1539, 3617], + [1540, 3617], + [1540, 3658], + [1539, 3658], +] + +[[area.polygons]] +vertices = [ + [1547, 3617], + [1548, 3617], + [1548, 3645], + [1547, 3645], +] + +[[area.polygons]] +vertices = [ + [1536, 3618], + [1538, 3618], + [1538, 3671], + [1536, 3671], +] + +[[area.polygons]] +vertices = [ + [1549, 3618], + [1549, 3646], +] + +[[area.polygons]] +vertices = [ + [1550, 3620], + [1552, 3620], + [1552, 3647], + [1550, 3647], +] + +[[area.polygons]] +vertices = [ + [1490, 3621], + [1497, 3621], + [1497, 3686], + [1490, 3686], +] + +[[area.polygons]] +vertices = [ + [1553, 3621], + [1553, 3647], +] + +[[area.polygons]] +vertices = [ + [1498, 3622], + [1499, 3622], + [1499, 3684], + [1498, 3684], +] + +[[area.polygons]] +vertices = [ + [1489, 3623], + [1489, 3688], +] + +[[area.polygons]] +vertices = [ + [1500, 3623], + [1500, 3683], +] + +[[area.polygons]] +vertices = [ + [1473, 3624], + [1474, 3624], + [1474, 3685], + [1473, 3685], +] + +[[area.polygons]] +vertices = [ + [1488, 3624], + [1488, 3688], +] + +[[area.polygons]] +vertices = [ + [1501, 3624], + [1501, 3682], +] + +[[area.polygons]] +vertices = [ + [1504, 3624], + [1505, 3624], + [1505, 3680], + [1504, 3680], +] + +[[area.polygons]] +vertices = [ + [1507, 3624], + [1507, 3680], +] + +[[area.polygons]] +vertices = [ + [1475, 3625], + [1487, 3625], + [1487, 3686], + [1475, 3686], +] + +[[area.polygons]] +vertices = [ + [1502, 3625], + [1503, 3625], + [1503, 3680], + [1502, 3680], +] + +[[area.polygons]] +vertices = [ + [1506, 3625], + [1506, 3680], +] + +[[area.polygons]] +vertices = [ + [1472, 3626], + [1472, 3645], +] + +[[area.polygons]] +vertices = [ + [1471, 3627], + [1471, 3644], +] + +[[area.polygons]] +vertices = [ + [1554, 3627], + [1556, 3627], + [1556, 3635], + [1554, 3635], +] + +[[area.polygons]] +vertices = [ + [1470, 3628], + [1470, 3643], +] + +[[area.polygons]] +vertices = [ + [1468, 3629], + [1469, 3629], + [1469, 3630], + [1468, 3630], +] + +[[area.polygons]] +vertices = [ + [1469, 3631], + [1469, 3642], +] + +[[area.polygons]] +vertices = [ + [1557, 3632], + [1557, 3634], +] + +[[area.polygons]] +vertices = [ + [1554, 3636], + [1555, 3636], + [1555, 3647], + [1554, 3647], +] + +[[area.polygons]] +vertices = [ + [1556, 3640], + [1556, 3647], +] + +[[area.polygons]] +vertices = [ + [1468, 3641], + [1468, 3642], +] + +[[area.polygons]] +vertices = [ + [1557, 3641], + [1557, 3647], +] + +[[area.polygons]] +vertices = [ + [1541, 3647], + [1545, 3647], +] + +[[area.polygons]] +vertices = [ + [1541, 3648], + [1544, 3648], +] + +[[area.polygons]] +vertices = [ + [1541, 3649], + [1543, 3649], +] + +[[area.polygons]] +vertices = [ + [1541, 3650], + [1542, 3650], + [1542, 3657], + [1541, 3657], +] + +[[area.polygons]] +vertices = [ + [1472, 3654], + [1472, 3685], +] + +[[area.polygons]] +vertices = [ + [1471, 3655], + [1471, 3685], +] + +[[area.polygons]] +vertices = [ + [1470, 3656], + [1470, 3685], +] + +[[area.polygons]] +vertices = [ + [1468, 3657], + [1469, 3657], + [1469, 3658], + [1468, 3658], +] + +[[area.polygons]] +vertices = [[1541, 3658]] + +[[area.polygons]] +vertices = [ + [1469, 3659], + [1469, 3681], +] + +[[area.polygons]] +vertices = [ + [1539, 3662], + [1539, 3670], +] + +[[area.polygons]] +vertices = [ + [1468, 3667], + [1468, 3681], +] + +[[area.polygons]] +vertices = [ + [1536, 3672], + [1537, 3672], +] + +[[area.polygons]] +vertices = [[1536, 3673]] + +[[area.polygons]] +vertices = [ + [1530, 3678], + [1531, 3678], +] + +[[area.polygons]] +vertices = [[1530, 3679]] + +[[area.polygons]] +vertices = [[1498, 3685]] + +[[area.polygons]] +vertices = [ + [1478, 3687], + [1487, 3687], + [1487, 3688], + [1478, 3688], +] + +[[area.polygons]] +vertices = [ + [1490, 3687], + [1496, 3687], +] + +[[area.polygons]] +vertices = [ + [1490, 3688], + [1495, 3688], +] + +[[area.polygons]] +vertices = [ + [1479, 3689], + [1482, 3689], +] diff --git a/.data/raw-cache/map/area/mind_over_matter.toml b/.data/raw-cache/map/area/mind_over_matter.toml new file mode 100644 index 000000000..b647d1d7f --- /dev/null +++ b/.data/raw-cache/map/area/mind_over_matter.toml @@ -0,0 +1,16 @@ +# Mind over Matter music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mind over Matter. +# Bounds x 3328-3391, z 9664-9727; 4096 tiles as 1 rectangles. + +[[area]] +name = "Mind over Matter" +area_id = "area.mind_over_matter" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3328, 9664], + [3391, 9664], + [3391, 9727], + [3328, 9727], +] diff --git a/.data/raw-cache/map/area/miracle_dance.toml b/.data/raw-cache/map/area/miracle_dance.toml new file mode 100644 index 000000000..f7bb6eccd --- /dev/null +++ b/.data/raw-cache/map/area/miracle_dance.toml @@ -0,0 +1,16 @@ +# Miracle Dance music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Miracle Dance. +# Bounds x 2752-2815, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Miracle Dance" +area_id = "area.miracle_dance" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 4800], + [2815, 4800], + [2815, 4863], + [2752, 4863], +] diff --git a/.data/raw-cache/map/area/mirage.toml b/.data/raw-cache/map/area/mirage.toml new file mode 100644 index 000000000..ffd97a029 --- /dev/null +++ b/.data/raw-cache/map/area/mirage.toml @@ -0,0 +1,16 @@ +# Mirage music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mirage. +# Bounds x 3264-3327, z 9152-9215; 4096 tiles as 1 rectangles. + +[[area]] +name = "Mirage" +area_id = "area.mirage" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 9152], + [3327, 9152], + [3327, 9215], + [3264, 9215], +] diff --git a/.data/raw-cache/map/area/miscellania.toml b/.data/raw-cache/map/area/miscellania.toml new file mode 100644 index 000000000..977bdf231 --- /dev/null +++ b/.data/raw-cache/map/area/miscellania.toml @@ -0,0 +1,546 @@ +# Miscellania music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Miscellania. +# Bounds x 2493-2587, z 3837-3903; 4318 tiles as 84 rectangles. + +[[area]] +name = "Miscellania" +area_id = "area.miscellania" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2506, 3837], + [2516, 3837], + [2516, 3887], + [2506, 3887], +] + +[[area.polygons]] +vertices = [ + [2504, 3838], + [2505, 3838], + [2505, 3886], + [2504, 3886], +] + +[[area.polygons]] +vertices = [ + [2517, 3838], + [2518, 3838], + [2518, 3894], + [2517, 3894], +] + +[[area.polygons]] +vertices = [ + [2503, 3839], + [2503, 3886], +] + +[[area.polygons]] +vertices = [ + [2519, 3839], + [2519, 3894], +] + +[[area.polygons]] +vertices = [ + [2501, 3840], + [2502, 3840], + [2502, 3882], + [2501, 3882], +] + +[[area.polygons]] +vertices = [ + [2520, 3840], + [2520, 3894], +] + +[[area.polygons]] +vertices = [ + [2540, 3840], + [2546, 3840], + [2546, 3902], + [2540, 3902], +] + +[[area.polygons]] +vertices = [ + [2550, 3840], + [2555, 3840], + [2555, 3899], + [2550, 3899], +] + +[[area.polygons]] +vertices = [ + [2500, 3841], + [2500, 3881], +] + +[[area.polygons]] +vertices = [ + [2521, 3841], + [2521, 3895], +] + +[[area.polygons]] +vertices = [ + [2534, 3841], + [2539, 3841], + [2539, 3902], + [2534, 3902], +] + +[[area.polygons]] +vertices = [ + [2547, 3841], + [2549, 3841], + [2549, 3902], + [2547, 3902], +] + +[[area.polygons]] +vertices = [ + [2556, 3841], + [2557, 3841], + [2557, 3895], + [2556, 3895], +] + +[[area.polygons]] +vertices = [ + [2522, 3842], + [2522, 3895], +] + +[[area.polygons]] +vertices = [ + [2533, 3842], + [2533, 3901], +] + +[[area.polygons]] +vertices = [ + [2558, 3842], + [2562, 3842], + [2562, 3890], + [2558, 3890], +] + +[[area.polygons]] +vertices = [ + [2499, 3843], + [2499, 3878], +] + +[[area.polygons]] +vertices = [ + [2523, 3843], + [2524, 3843], + [2524, 3896], + [2523, 3896], +] + +[[area.polygons]] +vertices = [ + [2532, 3843], + [2532, 3901], +] + +[[area.polygons]] +vertices = [ + [2563, 3843], + [2566, 3843], + [2566, 3860], + [2563, 3860], +] + +[[area.polygons]] +vertices = [ + [2498, 3844], + [2498, 3875], +] + +[[area.polygons]] +vertices = [ + [2525, 3844], + [2525, 3896], +] + +[[area.polygons]] +vertices = [ + [2531, 3844], + [2531, 3900], +] + +[[area.polygons]] +vertices = [ + [2567, 3844], + [2567, 3858], +] + +[[area.polygons]] +vertices = [ + [2572, 3844], + [2582, 3844], + [2582, 3845], + [2572, 3845], +] + +[[area.polygons]] +vertices = [ + [2497, 3845], + [2497, 3874], +] + +[[area.polygons]] +vertices = [ + [2526, 3845], + [2530, 3845], + [2530, 3896], + [2526, 3896], +] + +[[area.polygons]] +vertices = [ + [2568, 3845], + [2568, 3858], +] + +[[area.polygons]] +vertices = [ + [2496, 3846], + [2496, 3873], +] + +[[area.polygons]] +vertices = [ + [2569, 3846], + [2573, 3846], + [2573, 3847], + [2569, 3847], +] + +[[area.polygons]] +vertices = [ + [2581, 3846], + [2581, 3849], +] + +[[area.polygons]] +vertices = [ + [2577, 3847], + [2580, 3847], + [2580, 3849], + [2577, 3849], +] + +[[area.polygons]] +vertices = [ + [2582, 3847], + [2586, 3847], + [2586, 3849], + [2582, 3849], +] + +[[area.polygons]] +vertices = [ + [2495, 3848], + [2495, 3872], +] + +[[area.polygons]] +vertices = [ + [2569, 3848], + [2569, 3857], +] + +[[area.polygons]] +vertices = [ + [2572, 3848], + [2573, 3848], + [2573, 3859], + [2572, 3859], +] + +[[area.polygons]] +vertices = [[2576, 3848]] + +[[area.polygons]] +vertices = [[2587, 3848]] + +[[area.polygons]] +vertices = [ + [2494, 3851], + [2494, 3870], +] + +[[area.polygons]] +vertices = [ + [2570, 3852], + [2570, 3856], +] + +[[area.polygons]] +vertices = [ + [2574, 3852], + [2582, 3852], + [2582, 3853], + [2574, 3853], +] + +[[area.polygons]] +vertices = [ + [2493, 3854], + [2493, 3858], +] + +[[area.polygons]] +vertices = [ + [2563, 3861], + [2565, 3861], + [2565, 3890], + [2563, 3890], +] + +[[area.polygons]] +vertices = [ + [2493, 3862], + [2493, 3867], +] + +[[area.polygons]] +vertices = [ + [2566, 3865], + [2566, 3892], +] + +[[area.polygons]] +vertices = [ + [2567, 3868], + [2569, 3868], + [2569, 3893], + [2567, 3893], +] + +[[area.polygons]] +vertices = [ + [2570, 3869], + [2571, 3869], + [2571, 3885], + [2570, 3885], +] + +[[area.polygons]] +vertices = [ + [2572, 3870], + [2572, 3884], +] + +[[area.polygons]] +vertices = [ + [2573, 3871], + [2573, 3883], +] + +[[area.polygons]] +vertices = [ + [2574, 3873], + [2574, 3883], +] + +[[area.polygons]] +vertices = [ + [2575, 3874], + [2575, 3883], +] + +[[area.polygons]] +vertices = [ + [2576, 3876], + [2576, 3882], +] + +[[area.polygons]] +vertices = [ + [2577, 3879], + [2579, 3879], + [2579, 3880], + [2577, 3880], +] + +[[area.polygons]] +vertices = [ + [2502, 3883], + [2502, 3884], +] + +[[area.polygons]] +vertices = [ + [2570, 3886], + [2570, 3898], +] + +[[area.polygons]] +vertices = [ + [2507, 3888], + [2516, 3888], +] + +[[area.polygons]] +vertices = [ + [2508, 3889], + [2516, 3889], +] + +[[area.polygons]] +vertices = [ + [2510, 3890], + [2516, 3890], +] + +[[area.polygons]] +vertices = [ + [2514, 3891], + [2516, 3891], +] + +[[area.polygons]] +vertices = [ + [2558, 3891], + [2561, 3891], +] + +[[area.polygons]] +vertices = [[2565, 3891]] + +[[area.polygons]] +vertices = [ + [2499, 3892], + [2500, 3892], + [2500, 3898], + [2499, 3898], +] + +[[area.polygons]] +vertices = [ + [2515, 3892], + [2516, 3892], +] + +[[area.polygons]] +vertices = [ + [2498, 3893], + [2498, 3897], +] + +[[area.polygons]] +vertices = [[2516, 3893]] + +[[area.polygons]] +vertices = [ + [2571, 3893], + [2571, 3898], +] + +[[area.polygons]] +vertices = [ + [2501, 3894], + [2502, 3894], + [2502, 3898], + [2501, 3898], +] + +[[area.polygons]] +vertices = [ + [2569, 3894], + [2569, 3896], +] + +[[area.polygons]] +vertices = [ + [2572, 3894], + [2572, 3898], +] + +[[area.polygons]] +vertices = [ + [2503, 3895], + [2503, 3900], +] + +[[area.polygons]] +vertices = [ + [2504, 3896], + [2505, 3896], + [2505, 3900], + [2504, 3900], +] + +[[area.polygons]] +vertices = [ + [2556, 3896], + [2556, 3897], +] + +[[area.polygons]] +vertices = [ + [2573, 3896], + [2573, 3897], +] + +[[area.polygons]] +vertices = [ + [2506, 3897], + [2506, 3900], +] + +[[area.polygons]] +vertices = [ + [2528, 3897], + [2530, 3897], +] + +[[area.polygons]] +vertices = [ + [2507, 3898], + [2507, 3899], +] + +[[area.polygons]] +vertices = [ + [2529, 3898], + [2530, 3898], +] + +[[area.polygons]] +vertices = [[2502, 3899]] + +[[area.polygons]] +vertices = [[2530, 3899]] + +[[area.polygons]] +vertices = [ + [2550, 3900], + [2554, 3900], +] + +[[area.polygons]] +vertices = [ + [2550, 3901], + [2552, 3901], +] + +[[area.polygons]] +vertices = [ + [2550, 3902], + [2551, 3902], +] + +[[area.polygons]] +vertices = [ + [2545, 3903], + [2548, 3903], +] diff --git a/.data/raw-cache/map/area/mistrock.toml b/.data/raw-cache/map/area/mistrock.toml new file mode 100644 index 000000000..e4ee3a39e --- /dev/null +++ b/.data/raw-cache/map/area/mistrock.toml @@ -0,0 +1,729 @@ +# Mistrock music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mistrock. +# Bounds x 1330-1462, z 2842-2896; 4090 tiles as 115 rectangles. + +[[area]] +name = "Mistrock" +area_id = "area.mistrock" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1373, 2842], + [1379, 2842], + [1379, 2889], + [1373, 2889], +] + +[[area.polygons]] +vertices = [ + [1372, 2843], + [1372, 2889], +] + +[[area.polygons]] +vertices = [ + [1380, 2843], + [1380, 2889], +] + +[[area.polygons]] +vertices = [ + [1391, 2843], + [1393, 2843], + [1393, 2886], + [1391, 2886], +] + +[[area.polygons]] +vertices = [ + [1409, 2843], + [1410, 2843], + [1410, 2891], + [1409, 2891], +] + +[[area.polygons]] +vertices = [ + [1369, 2844], + [1371, 2844], + [1371, 2889], + [1369, 2889], +] + +[[area.polygons]] +vertices = [ + [1381, 2844], + [1382, 2844], + [1382, 2889], + [1381, 2889], +] + +[[area.polygons]] +vertices = [ + [1394, 2844], + [1397, 2844], + [1397, 2886], + [1394, 2886], +] + +[[area.polygons]] +vertices = [ + [1404, 2844], + [1405, 2844], + [1405, 2889], + [1404, 2889], +] + +[[area.polygons]] +vertices = [ + [1411, 2844], + [1411, 2891], +] + +[[area.polygons]] +vertices = [ + [1368, 2845], + [1368, 2891], +] + +[[area.polygons]] +vertices = [ + [1383, 2845], + [1383, 2889], +] + +[[area.polygons]] +vertices = [ + [1390, 2845], + [1390, 2889], +] + +[[area.polygons]] +vertices = [ + [1398, 2845], + [1398, 2886], +] + +[[area.polygons]] +vertices = [ + [1403, 2845], + [1403, 2888], +] + +[[area.polygons]] +vertices = [ + [1406, 2845], + [1406, 2893], +] + +[[area.polygons]] +vertices = [ + [1408, 2845], + [1408, 2892], +] + +[[area.polygons]] +vertices = [ + [1412, 2845], + [1412, 2890], +] + +[[area.polygons]] +vertices = [ + [1415, 2845], + [1415, 2889], +] + +[[area.polygons]] +vertices = [ + [1384, 2846], + [1384, 2889], +] + +[[area.polygons]] +vertices = [ + [1389, 2846], + [1389, 2889], +] + +[[area.polygons]] +vertices = [ + [1407, 2846], + [1407, 2893], +] + +[[area.polygons]] +vertices = [ + [1413, 2846], + [1414, 2846], + [1414, 2889], + [1413, 2889], +] + +[[area.polygons]] +vertices = [ + [1367, 2847], + [1367, 2891], +] + +[[area.polygons]] +vertices = [ + [1385, 2847], + [1388, 2847], + [1388, 2889], + [1385, 2889], +] + +[[area.polygons]] +vertices = [ + [1366, 2848], + [1366, 2891], +] + +[[area.polygons]] +vertices = [ + [1399, 2848], + [1400, 2848], + [1400, 2886], + [1399, 2886], +] + +[[area.polygons]] +vertices = [ + [1402, 2848], + [1402, 2887], +] + +[[area.polygons]] +vertices = [ + [1416, 2848], + [1416, 2889], +] + +[[area.polygons]] +vertices = [ + [1419, 2848], + [1423, 2848], + [1423, 2860], + [1419, 2860], +] + +[[area.polygons]] +vertices = [ + [1365, 2849], + [1365, 2891], +] + +[[area.polygons]] +vertices = [ + [1401, 2849], + [1401, 2886], +] + +[[area.polygons]] +vertices = [ + [1417, 2849], + [1418, 2849], + [1418, 2889], + [1417, 2889], +] + +[[area.polygons]] +vertices = [ + [1424, 2849], + [1425, 2849], + [1425, 2856], + [1424, 2856], +] + +[[area.polygons]] +vertices = [ + [1359, 2850], + [1364, 2850], + [1364, 2891], + [1359, 2891], +] + +[[area.polygons]] +vertices = [ + [1426, 2850], + [1427, 2850], + [1427, 2855], + [1426, 2855], +] + +[[area.polygons]] +vertices = [ + [1358, 2851], + [1358, 2891], +] + +[[area.polygons]] +vertices = [ + [1428, 2851], + [1428, 2856], +] + +[[area.polygons]] +vertices = [ + [1357, 2852], + [1357, 2872], +] + +[[area.polygons]] +vertices = [ + [1356, 2853], + [1356, 2870], +] + +[[area.polygons]] +vertices = [ + [1429, 2853], + [1429, 2857], +] + +[[area.polygons]] +vertices = [ + [1430, 2854], + [1430, 2858], +] + +[[area.polygons]] +vertices = [ + [1431, 2855], + [1431, 2860], +] + +[[area.polygons]] +vertices = [ + [1355, 2856], + [1355, 2868], +] + +[[area.polygons]] +vertices = [ + [1432, 2856], + [1432, 2859], +] + +[[area.polygons]] +vertices = [ + [1354, 2857], + [1354, 2859], +] + +[[area.polygons]] +vertices = [[1424, 2857]] + +[[area.polygons]] +vertices = [[1424, 2860]] + +[[area.polygons]] +vertices = [ + [1419, 2861], + [1421, 2861], + [1421, 2865], + [1419, 2865], +] + +[[area.polygons]] +vertices = [ + [1354, 2862], + [1354, 2867], +] + +[[area.polygons]] +vertices = [ + [1353, 2863], + [1353, 2866], +] + +[[area.polygons]] +vertices = [ + [1352, 2864], + [1352, 2866], +] + +[[area.polygons]] +vertices = [ + [1419, 2866], + [1420, 2866], + [1420, 2867], + [1419, 2867], +] + +[[area.polygons]] +vertices = [ + [1458, 2867], + [1459, 2867], + [1459, 2868], + [1458, 2868], +] + +[[area.polygons]] +vertices = [ + [1419, 2868], + [1419, 2891], +] + +[[area.polygons]] +vertices = [ + [1460, 2868], + [1460, 2884], +] + +[[area.polygons]] +vertices = [ + [1420, 2869], + [1420, 2892], +] + +[[area.polygons]] +vertices = [ + [1459, 2869], + [1459, 2884], +] + +[[area.polygons]] +vertices = [ + [1461, 2869], + [1461, 2884], +] + +[[area.polygons]] +vertices = [ + [1421, 2870], + [1421, 2893], +] + +[[area.polygons]] +vertices = [ + [1458, 2870], + [1458, 2885], +] + +[[area.polygons]] +vertices = [ + [1462, 2870], + [1462, 2883], +] + +[[area.polygons]] +vertices = [ + [1422, 2871], + [1425, 2871], + [1425, 2894], + [1422, 2894], +] + +[[area.polygons]] +vertices = [ + [1426, 2872], + [1426, 2893], +] + +[[area.polygons]] +vertices = [ + [1457, 2872], + [1457, 2886], +] + +[[area.polygons]] +vertices = [ + [1455, 2873], + [1456, 2873], + [1456, 2887], + [1455, 2887], +] + +[[area.polygons]] +vertices = [ + [1427, 2874], + [1429, 2874], +] + +[[area.polygons]] +vertices = [ + [1427, 2875], + [1428, 2875], + [1428, 2893], + [1427, 2893], +] + +[[area.polygons]] +vertices = [ + [1451, 2875], + [1451, 2889], +] + +[[area.polygons]] +vertices = [ + [1454, 2875], + [1454, 2889], +] + +[[area.polygons]] +vertices = [ + [1449, 2876], + [1450, 2876], + [1450, 2889], + [1449, 2889], +] + +[[area.polygons]] +vertices = [ + [1350, 2877], + [1351, 2877], + [1351, 2893], + [1350, 2893], +] + +[[area.polygons]] +vertices = [ + [1357, 2877], + [1357, 2891], +] + +[[area.polygons]] +vertices = [ + [1441, 2877], + [1443, 2877], + [1443, 2878], + [1441, 2878], +] + +[[area.polygons]] +vertices = [ + [1448, 2877], + [1448, 2891], +] + +[[area.polygons]] +vertices = [ + [1453, 2877], + [1453, 2888], +] + +[[area.polygons]] +vertices = [ + [1348, 2878], + [1349, 2878], + [1349, 2893], + [1348, 2893], +] + +[[area.polygons]] +vertices = [ + [1352, 2878], + [1356, 2878], + [1356, 2891], + [1352, 2891], +] + +[[area.polygons]] +vertices = [ + [1429, 2878], + [1434, 2878], + [1434, 2893], + [1429, 2893], +] + +[[area.polygons]] +vertices = [ + [1444, 2878], + [1447, 2878], + [1447, 2892], + [1444, 2892], +] + +[[area.polygons]] +vertices = [ + [1452, 2878], + [1452, 2888], +] + +[[area.polygons]] +vertices = [ + [1347, 2879], + [1347, 2894], +] + +[[area.polygons]] +vertices = [ + [1435, 2879], + [1435, 2896], +] + +[[area.polygons]] +vertices = [ + [1442, 2879], + [1443, 2879], + [1443, 2895], + [1442, 2895], +] + +[[area.polygons]] +vertices = [ + [1344, 2880], + [1346, 2880], + [1346, 2895], + [1344, 2895], +] + +[[area.polygons]] +vertices = [ + [1436, 2880], + [1437, 2880], + [1437, 2896], + [1436, 2896], +] + +[[area.polygons]] +vertices = [ + [1343, 2881], + [1343, 2896], +] + +[[area.polygons]] +vertices = [ + [1438, 2881], + [1438, 2896], +] + +[[area.polygons]] +vertices = [ + [1441, 2881], + [1441, 2896], +] + +[[area.polygons]] +vertices = [ + [1439, 2882], + [1440, 2882], + [1440, 2895], + [1439, 2895], +] + +[[area.polygons]] +vertices = [ + [1342, 2883], + [1342, 2896], +] + +[[area.polygons]] +vertices = [ + [1341, 2884], + [1341, 2896], +] + +[[area.polygons]] +vertices = [ + [1340, 2885], + [1340, 2896], +] + +[[area.polygons]] +vertices = [ + [1331, 2886], + [1334, 2886], + [1334, 2896], + [1331, 2896], +] + +[[area.polygons]] +vertices = [ + [1338, 2886], + [1339, 2886], + [1339, 2896], + [1338, 2896], +] + +[[area.polygons]] +vertices = [ + [1330, 2887], + [1330, 2896], +] + +[[area.polygons]] +vertices = [ + [1335, 2887], + [1337, 2887], + [1337, 2896], + [1335, 2896], +] + +[[area.polygons]] +vertices = [ + [1391, 2887], + [1391, 2889], +] + +[[area.polygons]] +vertices = [[1455, 2888]] + +[[area.polygons]] +vertices = [[1369, 2890]] + +[[area.polygons]] +vertices = [[1405, 2890]] + +[[area.polygons]] +vertices = [[1418, 2890]] + +[[area.polygons]] +vertices = [[1449, 2890]] + +[[area.polygons]] +vertices = [ + [1352, 2892], + [1354, 2892], +] + +[[area.polygons]] +vertices = [[1409, 2892]] + +[[area.polygons]] +vertices = [ + [1352, 2893], + [1353, 2893], +] + +[[area.polygons]] +vertices = [ + [1444, 2893], + [1446, 2893], +] + +[[area.polygons]] +vertices = [[1348, 2894]] + +[[area.polygons]] +vertices = [ + [1432, 2894], + [1434, 2894], +] + +[[area.polygons]] +vertices = [ + [1444, 2894], + [1445, 2894], +] + +[[area.polygons]] +vertices = [ + [1433, 2895], + [1434, 2895], +] + +[[area.polygons]] +vertices = [[1444, 2895]] + +[[area.polygons]] +vertices = [[1434, 2896]] + +[[area.polygons]] +vertices = [[1439, 2896]] + +[[area.polygons]] +vertices = [[1442, 2896]] diff --git a/.data/raw-cache/map/area/monarch_waltz.toml b/.data/raw-cache/map/area/monarch_waltz.toml new file mode 100644 index 000000000..abd257cdf --- /dev/null +++ b/.data/raw-cache/map/area/monarch_waltz.toml @@ -0,0 +1,456 @@ +# Monarch Waltz music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Monarch Waltz. +# Bounds x 2641-2761, z 3521-3603; 7471 tiles as 74 rectangles. + +[[area]] +name = "Monarch Waltz" +area_id = "area.monarch_waltz" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2689, 3521], + [2734, 3521], + [2734, 3590], + [2689, 3590], +] + +[[area.polygons]] +vertices = [ + [2688, 3522], + [2688, 3598], +] + +[[area.polygons]] +vertices = [ + [2735, 3522], + [2735, 3590], +] + +[[area.polygons]] +vertices = [ + [2687, 3523], + [2687, 3598], +] + +[[area.polygons]] +vertices = [ + [2736, 3523], + [2736, 3590], +] + +[[area.polygons]] +vertices = [ + [2686, 3524], + [2686, 3598], +] + +[[area.polygons]] +vertices = [ + [2737, 3524], + [2737, 3590], +] + +[[area.polygons]] +vertices = [ + [2685, 3525], + [2685, 3599], +] + +[[area.polygons]] +vertices = [ + [2738, 3525], + [2738, 3590], +] + +[[area.polygons]] +vertices = [ + [2740, 3525], + [2754, 3525], + [2754, 3586], + [2740, 3586], +] + +[[area.polygons]] +vertices = [ + [2684, 3526], + [2684, 3600], +] + +[[area.polygons]] +vertices = [ + [2739, 3526], + [2739, 3590], +] + +[[area.polygons]] +vertices = [ + [2755, 3526], + [2755, 3589], +] + +[[area.polygons]] +vertices = [ + [2683, 3527], + [2683, 3601], +] + +[[area.polygons]] +vertices = [ + [2756, 3528], + [2756, 3588], +] + +[[area.polygons]] +vertices = [ + [2682, 3529], + [2682, 3602], +] + +[[area.polygons]] +vertices = [ + [2681, 3530], + [2681, 3603], +] + +[[area.polygons]] +vertices = [ + [2680, 3531], + [2680, 3602], +] + +[[area.polygons]] +vertices = [ + [2757, 3531], + [2757, 3587], +] + +[[area.polygons]] +vertices = [ + [2679, 3532], + [2679, 3601], +] + +[[area.polygons]] +vertices = [ + [2677, 3533], + [2678, 3533], + [2678, 3600], + [2677, 3600], +] + +[[area.polygons]] +vertices = [ + [2758, 3533], + [2758, 3586], +] + +[[area.polygons]] +vertices = [ + [2676, 3534], + [2676, 3599], +] + +[[area.polygons]] +vertices = [ + [2675, 3535], + [2675, 3599], +] + +[[area.polygons]] +vertices = [ + [2759, 3535], + [2759, 3585], +] + +[[area.polygons]] +vertices = [ + [2674, 3536], + [2674, 3598], +] + +[[area.polygons]] +vertices = [ + [2673, 3538], + [2673, 3598], +] + +[[area.polygons]] +vertices = [ + [2760, 3538], + [2760, 3584], +] + +[[area.polygons]] +vertices = [ + [2672, 3539], + [2672, 3597], +] + +[[area.polygons]] +vertices = [ + [2671, 3540], + [2671, 3597], +] + +[[area.polygons]] +vertices = [ + [2761, 3540], + [2761, 3583], +] + +[[area.polygons]] +vertices = [ + [2670, 3541], + [2670, 3596], +] + +[[area.polygons]] +vertices = [ + [2668, 3542], + [2669, 3542], + [2669, 3544], + [2668, 3544], +] + +[[area.polygons]] +vertices = [ + [2669, 3545], + [2669, 3596], +] + +[[area.polygons]] +vertices = [ + [2668, 3548], + [2668, 3596], +] + +[[area.polygons]] +vertices = [ + [2667, 3549], + [2667, 3597], +] + +[[area.polygons]] +vertices = [ + [2663, 3550], + [2666, 3550], + [2666, 3597], + [2663, 3597], +] + +[[area.polygons]] +vertices = [ + [2662, 3552], + [2662, 3599], +] + +[[area.polygons]] +vertices = [ + [2658, 3553], + [2661, 3553], + [2661, 3597], + [2658, 3597], +] + +[[area.polygons]] +vertices = [ + [2657, 3554], + [2657, 3596], +] + +[[area.polygons]] +vertices = [ + [2656, 3555], + [2656, 3596], +] + +[[area.polygons]] +vertices = [ + [2655, 3556], + [2655, 3595], +] + +[[area.polygons]] +vertices = [ + [2654, 3557], + [2654, 3595], +] + +[[area.polygons]] +vertices = [ + [2653, 3560], + [2653, 3595], +] + +[[area.polygons]] +vertices = [ + [2652, 3561], + [2652, 3593], +] + +[[area.polygons]] +vertices = [ + [2650, 3562], + [2651, 3562], + [2651, 3592], + [2650, 3592], +] + +[[area.polygons]] +vertices = [ + [2649, 3566], + [2649, 3591], +] + +[[area.polygons]] +vertices = [ + [2648, 3567], + [2648, 3590], +] + +[[area.polygons]] +vertices = [ + [2646, 3568], + [2647, 3568], + [2647, 3590], + [2646, 3590], +] + +[[area.polygons]] +vertices = [ + [2645, 3571], + [2645, 3590], +] + +[[area.polygons]] +vertices = [ + [2644, 3572], + [2644, 3589], +] + +[[area.polygons]] +vertices = [ + [2643, 3573], + [2643, 3588], +] + +[[area.polygons]] +vertices = [[2642, 3574]] + +[[area.polygons]] +vertices = [ + [2642, 3580], + [2642, 3582], +] + +[[area.polygons]] +vertices = [ + [2642, 3584], + [2642, 3587], +] + +[[area.polygons]] +vertices = [ + [2641, 3585], + [2641, 3586], +] + +[[area.polygons]] +vertices = [ + [2740, 3587], + [2742, 3587], +] + +[[area.polygons]] +vertices = [ + [2746, 3587], + [2754, 3587], +] + +[[area.polygons]] +vertices = [ + [2740, 3588], + [2741, 3588], +] + +[[area.polygons]] +vertices = [ + [2749, 3588], + [2754, 3588], +] + +[[area.polygons]] +vertices = [[2740, 3589]] + +[[area.polygons]] +vertices = [ + [2751, 3589], + [2754, 3589], +] + +[[area.polygons]] +vertices = [ + [2689, 3591], + [2733, 3591], +] + +[[area.polygons]] +vertices = [ + [2689, 3592], + [2730, 3592], +] + +[[area.polygons]] +vertices = [[2651, 3593]] + +[[area.polygons]] +vertices = [ + [2689, 3593], + [2727, 3593], +] + +[[area.polygons]] +vertices = [ + [2689, 3594], + [2724, 3594], +] + +[[area.polygons]] +vertices = [ + [2689, 3595], + [2716, 3595], +] + +[[area.polygons]] +vertices = [ + [2689, 3596], + [2710, 3596], +] + +[[area.polygons]] +vertices = [ + [2689, 3597], + [2704, 3597], +] + +[[area.polygons]] +vertices = [ + [2660, 3598], + [2661, 3598], +] + +[[area.polygons]] +vertices = [ + [2663, 3598], + [2665, 3598], +] + +[[area.polygons]] +vertices = [ + [2689, 3598], + [2698, 3598], +] + +[[area.polygons]] +vertices = [[2663, 3599]] diff --git a/.data/raw-cache/map/area/monkey_business.toml b/.data/raw-cache/map/area/monkey_business.toml new file mode 100644 index 000000000..16d0a2eb7 --- /dev/null +++ b/.data/raw-cache/map/area/monkey_business.toml @@ -0,0 +1,16 @@ +# Monkey Business music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Monkey Business. +# Bounds x 2048-2175, z 5632-5695; 8192 tiles as 1 rectangles. + +[[area]] +name = "Monkey Business" +area_id = "area.monkey_business" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2048, 5632], + [2175, 5632], + [2175, 5695], + [2048, 5695], +] diff --git a/.data/raw-cache/map/area/monkey_madness.toml b/.data/raw-cache/map/area/monkey_madness.toml new file mode 100644 index 000000000..e0fe4c28c --- /dev/null +++ b/.data/raw-cache/map/area/monkey_madness.toml @@ -0,0 +1,16 @@ +# Monkey Madness music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Monkey Madness. +# Bounds x 2048-2111, z 5376-5439; 4096 tiles as 1 rectangles. + +[[area]] +name = "Monkey Madness" +area_id = "area.monkey_madness" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2048, 5376], + [2111, 5376], + [2111, 5439], + [2048, 5439], +] diff --git a/.data/raw-cache/map/area/monkey_sadness.toml b/.data/raw-cache/map/area/monkey_sadness.toml new file mode 100644 index 000000000..93142abf8 --- /dev/null +++ b/.data/raw-cache/map/area/monkey_sadness.toml @@ -0,0 +1,24 @@ +# Monkey Sadness music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Monkey Sadness. +# Bounds x 2176-2943, z 5440-9151; 8192 tiles as 2 rectangles. + +[[area]] +name = "Monkey Sadness" +area_id = "area.monkey_sadness" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2176, 5440], + [2239, 5440], + [2239, 5503], + [2176, 5503], +] + +[[area.polygons]] +vertices = [ + [2880, 9088], + [2943, 9088], + [2943, 9151], + [2880, 9151], +] diff --git a/.data/raw-cache/map/area/monkey_trouble.toml b/.data/raw-cache/map/area/monkey_trouble.toml new file mode 100644 index 000000000..bcef2f0b7 --- /dev/null +++ b/.data/raw-cache/map/area/monkey_trouble.toml @@ -0,0 +1,16 @@ +# Monkey Trouble music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Monkey Trouble. +# Bounds x 2304-2687, z 9024-9279; 98304 tiles as 1 rectangles. + +[[area]] +name = "Monkey Trouble" +area_id = "area.monkey_trouble" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2304, 9024], + [2687, 9024], + [2687, 9279], + [2304, 9279], +] diff --git a/.data/raw-cache/map/area/monster_melee.toml b/.data/raw-cache/map/area/monster_melee.toml new file mode 100644 index 000000000..b10c1952b --- /dev/null +++ b/.data/raw-cache/map/area/monster_melee.toml @@ -0,0 +1,16 @@ +# Monster Melee music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Monster Melee. +# Bounds x 3136-3199, z 9600-9663; 4096 tiles as 1 rectangles. + +[[area]] +name = "Monster Melee" +area_id = "area.monster_melee" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 9600], + [3199, 9600], + [3199, 9663], + [3136, 9663], +] diff --git a/.data/raw-cache/map/area/mor_ul_rek.toml b/.data/raw-cache/map/area/mor_ul_rek.toml new file mode 100644 index 000000000..63fee23fa --- /dev/null +++ b/.data/raw-cache/map/area/mor_ul_rek.toml @@ -0,0 +1,244 @@ +# Mor Ul Rek music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mor Ul Rek. +# Bounds x 2432-2559, z 5056-5183; 13166 tiles as 38 rectangles. + +[[area]] +name = "Mor Ul Rek" +area_id = "area.mor_ul_rek" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2432, 5056], + [2559, 5056], + [2559, 5119], + [2432, 5119], +] + +[[area.polygons]] +vertices = [ + [2432, 5120], + [2455, 5120], +] + +[[area.polygons]] +vertices = [ + [2458, 5120], + [2559, 5120], +] + +[[area.polygons]] +vertices = [ + [2432, 5121], + [2434, 5121], +] + +[[area.polygons]] +vertices = [ + [2437, 5121], + [2448, 5121], +] + +[[area.polygons]] +vertices = [ + [2452, 5121], + [2454, 5121], +] + +[[area.polygons]] +vertices = [ + [2460, 5121], + [2559, 5121], +] + +[[area.polygons]] +vertices = [ + [2432, 5122], + [2433, 5122], +] + +[[area.polygons]] +vertices = [ + [2438, 5122], + [2446, 5122], +] + +[[area.polygons]] +vertices = [ + [2465, 5122], + [2559, 5122], + [2559, 5132], + [2465, 5132], +] + +[[area.polygons]] +vertices = [ + [2466, 5133], + [2559, 5133], +] + +[[area.polygons]] +vertices = [ + [2467, 5134], + [2559, 5134], +] + +[[area.polygons]] +vertices = [ + [2468, 5135], + [2559, 5135], +] + +[[area.polygons]] +vertices = [ + [2469, 5136], + [2559, 5136], +] + +[[area.polygons]] +vertices = [ + [2470, 5137], + [2559, 5137], +] + +[[area.polygons]] +vertices = [ + [2471, 5138], + [2472, 5138], +] + +[[area.polygons]] +vertices = [ + [2475, 5138], + [2559, 5138], +] + +[[area.polygons]] +vertices = [ + [2478, 5139], + [2559, 5139], +] + +[[area.polygons]] +vertices = [ + [2479, 5140], + [2559, 5140], +] + +[[area.polygons]] +vertices = [ + [2480, 5141], + [2559, 5141], +] + +[[area.polygons]] +vertices = [ + [2481, 5142], + [2559, 5142], +] + +[[area.polygons]] +vertices = [ + [2482, 5143], + [2559, 5143], +] + +[[area.polygons]] +vertices = [ + [2483, 5144], + [2559, 5144], +] + +[[area.polygons]] +vertices = [ + [2484, 5145], + [2559, 5145], +] + +[[area.polygons]] +vertices = [ + [2485, 5146], + [2559, 5146], +] + +[[area.polygons]] +vertices = [ + [2486, 5147], + [2559, 5147], +] + +[[area.polygons]] +vertices = [ + [2489, 5148], + [2559, 5148], +] + +[[area.polygons]] +vertices = [ + [2490, 5149], + [2559, 5149], +] + +[[area.polygons]] +vertices = [ + [2491, 5150], + [2559, 5150], +] + +[[area.polygons]] +vertices = [ + [2492, 5151], + [2559, 5151], +] + +[[area.polygons]] +vertices = [ + [2493, 5152], + [2559, 5152], +] + +[[area.polygons]] +vertices = [ + [2494, 5153], + [2559, 5153], + [2559, 5156], + [2494, 5156], +] + +[[area.polygons]] +vertices = [ + [2495, 5157], + [2559, 5157], + [2559, 5183], + [2495, 5183], +] + +[[area.polygons]] +vertices = [ + [2494, 5158], + [2494, 5183], +] + +[[area.polygons]] +vertices = [ + [2493, 5162], + [2493, 5173], +] + +[[area.polygons]] +vertices = [ + [2492, 5163], + [2492, 5171], +] + +[[area.polygons]] +vertices = [ + [2493, 5175], + [2493, 5183], +] + +[[area.polygons]] +vertices = [ + [2492, 5179], + [2492, 5183], +] diff --git a/.data/raw-cache/map/area/more_than_meets_the_eye.toml b/.data/raw-cache/map/area/more_than_meets_the_eye.toml new file mode 100644 index 000000000..0f8d61180 --- /dev/null +++ b/.data/raw-cache/map/area/more_than_meets_the_eye.toml @@ -0,0 +1,16 @@ +# More Than Meets the Eye music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: More Than Meets the Eye. +# Bounds x 2816-2879, z 4224-4287; 4096 tiles as 1 rectangles. + +[[area]] +name = "More Than Meets the Eye" +area_id = "area.more_than_meets_the_eye" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 4224], + [2879, 4224], + [2879, 4287], + [2816, 4287], +] diff --git a/.data/raw-cache/map/area/morytania.toml b/.data/raw-cache/map/area/morytania.toml new file mode 100644 index 000000000..0ef2e0a10 --- /dev/null +++ b/.data/raw-cache/map/area/morytania.toml @@ -0,0 +1,1045 @@ +# Morytania music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Morytania. +# Bounds x 3408-3647, z 3456-3583; 14154 tiles as 162 rectangles. + +[[area]] +name = "Morytania" +area_id = "area.morytania" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3414, 3456], + [3417, 3456], + [3417, 3467], + [3414, 3467], +] + +[[area.polygons]] +vertices = [ + [3431, 3456], + [3435, 3456], + [3435, 3530], + [3431, 3530], +] + +[[area.polygons]] +vertices = [ + [3446, 3456], + [3447, 3456], + [3447, 3528], + [3446, 3528], +] + +[[area.polygons]] +vertices = [ + [3453, 3456], + [3454, 3456], + [3454, 3528], + [3453, 3528], +] + +[[area.polygons]] +vertices = [ + [3413, 3457], + [3413, 3467], +] + +[[area.polygons]] +vertices = [ + [3418, 3457], + [3420, 3457], + [3420, 3470], + [3418, 3470], +] + +[[area.polygons]] +vertices = [ + [3426, 3457], + [3430, 3457], + [3430, 3535], + [3426, 3535], +] + +[[area.polygons]] +vertices = [ + [3436, 3457], + [3440, 3457], + [3440, 3528], + [3436, 3528], +] + +[[area.polygons]] +vertices = [ + [3445, 3457], + [3445, 3528], +] + +[[area.polygons]] +vertices = [ + [3448, 3457], + [3452, 3457], + [3452, 3528], + [3448, 3528], +] + +[[area.polygons]] +vertices = [ + [3455, 3457], + [3455, 3528], +] + +[[area.polygons]] +vertices = [ + [3412, 3458], + [3412, 3467], +] + +[[area.polygons]] +vertices = [ + [3421, 3458], + [3421, 3474], +] + +[[area.polygons]] +vertices = [ + [3425, 3458], + [3425, 3534], +] + +[[area.polygons]] +vertices = [ + [3441, 3458], + [3444, 3458], + [3444, 3528], + [3441, 3528], +] + +[[area.polygons]] +vertices = [ + [3456, 3458], + [3456, 3583], +] + +[[area.polygons]] +vertices = [ + [3411, 3459], + [3411, 3466], +] + +[[area.polygons]] +vertices = [ + [3422, 3459], + [3424, 3459], + [3424, 3474], + [3422, 3474], +] + +[[area.polygons]] +vertices = [ + [3457, 3459], + [3457, 3583], +] + +[[area.polygons]] +vertices = [ + [3410, 3460], + [3410, 3465], +] + +[[area.polygons]] +vertices = [ + [3458, 3460], + [3458, 3583], +] + +[[area.polygons]] +vertices = [ + [3409, 3461], + [3409, 3464], +] + +[[area.polygons]] +vertices = [ + [3459, 3461], + [3459, 3583], +] + +[[area.polygons]] +vertices = [ + [3408, 3462], + [3408, 3463], +] + +[[area.polygons]] +vertices = [ + [3460, 3462], + [3460, 3583], +] + +[[area.polygons]] +vertices = [ + [3461, 3463], + [3461, 3583], +] + +[[area.polygons]] +vertices = [ + [3462, 3464], + [3462, 3583], +] + +[[area.polygons]] +vertices = [ + [3463, 3465], + [3463, 3583], +] + +[[area.polygons]] +vertices = [ + [3464, 3466], + [3464, 3485], +] + +[[area.polygons]] +vertices = [ + [3465, 3467], + [3465, 3484], +] + +[[area.polygons]] +vertices = [ + [3416, 3468], + [3417, 3468], +] + +[[area.polygons]] +vertices = [ + [3466, 3468], + [3466, 3483], +] + +[[area.polygons]] +vertices = [ + [3417, 3469], + [3417, 3470], +] + +[[area.polygons]] +vertices = [ + [3467, 3469], + [3467, 3482], +] + +[[area.polygons]] +vertices = [ + [3468, 3470], + [3475, 3470], + [3475, 3473], + [3468, 3473], +] + +[[area.polygons]] +vertices = [[3420, 3471]] + +[[area.polygons]] +vertices = [ + [3468, 3474], + [3474, 3474], +] + +[[area.polygons]] +vertices = [ + [3423, 3475], + [3424, 3475], +] + +[[area.polygons]] +vertices = [ + [3468, 3475], + [3473, 3475], +] + +[[area.polygons]] +vertices = [ + [3424, 3476], + [3424, 3533], +] + +[[area.polygons]] +vertices = [ + [3468, 3476], + [3472, 3476], +] + +[[area.polygons]] +vertices = [ + [3468, 3477], + [3471, 3477], + [3471, 3478], + [3468, 3478], +] + +[[area.polygons]] +vertices = [ + [3423, 3479], + [3423, 3532], +] + +[[area.polygons]] +vertices = [ + [3468, 3479], + [3470, 3479], +] + +[[area.polygons]] +vertices = [ + [3422, 3480], + [3422, 3531], +] + +[[area.polygons]] +vertices = [ + [3468, 3480], + [3469, 3480], +] + +[[area.polygons]] +vertices = [ + [3419, 3481], + [3421, 3481], + [3421, 3528], + [3419, 3528], +] + +[[area.polygons]] +vertices = [[3468, 3481]] + +[[area.polygons]] +vertices = [ + [3418, 3494], + [3418, 3528], +] + +[[area.polygons]] +vertices = [ + [3417, 3496], + [3417, 3528], +] + +[[area.polygons]] +vertices = [ + [3464, 3496], + [3464, 3583], +] + +[[area.polygons]] +vertices = [ + [3416, 3497], + [3416, 3528], +] + +[[area.polygons]] +vertices = [ + [3465, 3497], + [3465, 3583], +] + +[[area.polygons]] +vertices = [ + [3414, 3498], + [3415, 3498], + [3415, 3528], + [3414, 3528], +] + +[[area.polygons]] +vertices = [ + [3466, 3498], + [3466, 3583], +] + +[[area.polygons]] +vertices = [ + [3413, 3499], + [3413, 3528], +] + +[[area.polygons]] +vertices = [ + [3467, 3499], + [3467, 3583], +] + +[[area.polygons]] +vertices = [ + [3411, 3500], + [3412, 3500], + [3412, 3528], + [3411, 3528], +] + +[[area.polygons]] +vertices = [ + [3468, 3500], + [3468, 3583], +] + +[[area.polygons]] +vertices = [ + [3410, 3501], + [3410, 3528], +] + +[[area.polygons]] +vertices = [ + [3469, 3501], + [3469, 3583], +] + +[[area.polygons]] +vertices = [ + [3470, 3502], + [3470, 3583], +] + +[[area.polygons]] +vertices = [ + [3471, 3503], + [3471, 3583], +] + +[[area.polygons]] +vertices = [ + [3409, 3504], + [3409, 3528], +] + +[[area.polygons]] +vertices = [ + [3472, 3504], + [3472, 3583], +] + +[[area.polygons]] +vertices = [ + [3408, 3505], + [3408, 3528], +] + +[[area.polygons]] +vertices = [ + [3473, 3505], + [3473, 3583], +] + +[[area.polygons]] +vertices = [ + [3474, 3506], + [3474, 3583], +] + +[[area.polygons]] +vertices = [ + [3475, 3507], + [3475, 3583], +] + +[[area.polygons]] +vertices = [ + [3476, 3508], + [3476, 3583], +] + +[[area.polygons]] +vertices = [ + [3513, 3508], + [3517, 3508], + [3517, 3583], + [3513, 3583], +] + +[[area.polygons]] +vertices = [ + [3477, 3509], + [3491, 3509], + [3491, 3583], + [3477, 3583], +] + +[[area.polygons]] +vertices = [ + [3512, 3509], + [3512, 3583], +] + +[[area.polygons]] +vertices = [ + [3518, 3509], + [3518, 3583], +] + +[[area.polygons]] +vertices = [ + [3492, 3510], + [3492, 3583], +] + +[[area.polygons]] +vertices = [ + [3511, 3510], + [3511, 3583], +] + +[[area.polygons]] +vertices = [ + [3519, 3510], + [3519, 3583], +] + +[[area.polygons]] +vertices = [ + [3493, 3511], + [3493, 3583], +] + +[[area.polygons]] +vertices = [ + [3510, 3511], + [3510, 3583], +] + +[[area.polygons]] +vertices = [ + [3520, 3511], + [3520, 3583], +] + +[[area.polygons]] +vertices = [ + [3494, 3512], + [3494, 3583], +] + +[[area.polygons]] +vertices = [ + [3509, 3512], + [3509, 3583], +] + +[[area.polygons]] +vertices = [ + [3521, 3512], + [3521, 3583], +] + +[[area.polygons]] +vertices = [ + [3601, 3512], + [3605, 3512], + [3605, 3538], + [3601, 3538], +] + +[[area.polygons]] +vertices = [ + [3495, 3513], + [3508, 3513], + [3508, 3583], + [3495, 3583], +] + +[[area.polygons]] +vertices = [ + [3522, 3513], + [3522, 3583], +] + +[[area.polygons]] +vertices = [ + [3600, 3513], + [3600, 3538], +] + +[[area.polygons]] +vertices = [ + [3606, 3513], + [3606, 3538], +] + +[[area.polygons]] +vertices = [ + [3523, 3514], + [3523, 3583], +] + +[[area.polygons]] +vertices = [ + [3599, 3514], + [3599, 3541], +] + +[[area.polygons]] +vertices = [ + [3607, 3514], + [3607, 3538], +] + +[[area.polygons]] +vertices = [ + [3524, 3515], + [3524, 3583], +] + +[[area.polygons]] +vertices = [ + [3598, 3515], + [3598, 3541], +] + +[[area.polygons]] +vertices = [ + [3608, 3515], + [3608, 3538], +] + +[[area.polygons]] +vertices = [ + [3525, 3516], + [3525, 3583], +] + +[[area.polygons]] +vertices = [ + [3597, 3516], + [3597, 3548], +] + +[[area.polygons]] +vertices = [ + [3609, 3516], + [3609, 3538], +] + +[[area.polygons]] +vertices = [ + [3526, 3517], + [3526, 3583], +] + +[[area.polygons]] +vertices = [ + [3596, 3517], + [3596, 3548], +] + +[[area.polygons]] +vertices = [ + [3610, 3517], + [3610, 3538], +] + +[[area.polygons]] +vertices = [ + [3527, 3518], + [3527, 3583], +] + +[[area.polygons]] +vertices = [ + [3595, 3518], + [3595, 3548], +] + +[[area.polygons]] +vertices = [ + [3611, 3518], + [3611, 3538], +] + +[[area.polygons]] +vertices = [ + [3626, 3518], + [3647, 3518], + [3647, 3554], + [3626, 3554], +] + +[[area.polygons]] +vertices = [ + [3528, 3519], + [3528, 3583], +] + +[[area.polygons]] +vertices = [ + [3559, 3519], + [3594, 3519], + [3594, 3541], + [3559, 3541], +] + +[[area.polygons]] +vertices = [ + [3612, 3519], + [3612, 3538], +] + +[[area.polygons]] +vertices = [ + [3624, 3519], + [3625, 3519], + [3625, 3554], + [3624, 3554], +] + +[[area.polygons]] +vertices = [ + [3529, 3520], + [3558, 3520], + [3558, 3535], + [3529, 3535], +] + +[[area.polygons]] +vertices = [ + [3613, 3520], + [3613, 3538], +] + +[[area.polygons]] +vertices = [ + [3622, 3520], + [3623, 3520], + [3623, 3538], + [3622, 3538], +] + +[[area.polygons]] +vertices = [ + [3614, 3521], + [3621, 3521], + [3621, 3538], + [3614, 3538], +] + +[[area.polygons]] +vertices = [ + [3420, 3529], + [3421, 3529], +] + +[[area.polygons]] +vertices = [[3436, 3529]] + +[[area.polygons]] +vertices = [[3421, 3530]] + +[[area.polygons]] +vertices = [ + [3431, 3531], + [3434, 3531], +] + +[[area.polygons]] +vertices = [ + [3431, 3532], + [3433, 3532], +] + +[[area.polygons]] +vertices = [ + [3431, 3533], + [3432, 3533], +] + +[[area.polygons]] +vertices = [[3431, 3534]] + +[[area.polygons]] +vertices = [ + [3529, 3536], + [3546, 3536], +] + +[[area.polygons]] +vertices = [ + [3550, 3536], + [3558, 3536], +] + +[[area.polygons]] +vertices = [ + [3529, 3537], + [3545, 3537], + [3545, 3539], + [3529, 3539], +] + +[[area.polygons]] +vertices = [ + [3551, 3537], + [3558, 3537], +] + +[[area.polygons]] +vertices = [ + [3552, 3538], + [3558, 3538], + [3558, 3539], + [3552, 3539], +] + +[[area.polygons]] +vertices = [ + [3529, 3540], + [3538, 3540], +] + +[[area.polygons]] +vertices = [ + [3542, 3540], + [3545, 3540], +] + +[[area.polygons]] +vertices = [ + [3552, 3540], + [3554, 3540], +] + +[[area.polygons]] +vertices = [[3558, 3540]] + +[[area.polygons]] +vertices = [ + [3529, 3541], + [3537, 3541], + [3537, 3548], + [3529, 3548], +] + +[[area.polygons]] +vertices = [ + [3543, 3541], + [3545, 3541], +] + +[[area.polygons]] +vertices = [ + [3551, 3541], + [3553, 3541], + [3553, 3543], + [3551, 3543], +] + +[[area.polygons]] +vertices = [ + [3544, 3542], + [3546, 3542], + [3546, 3544], + [3544, 3544], +] + +[[area.polygons]] +vertices = [ + [3550, 3542], + [3550, 3544], +] + +[[area.polygons]] +vertices = [ + [3560, 3542], + [3594, 3542], + [3594, 3548], + [3560, 3548], +] + +[[area.polygons]] +vertices = [[3547, 3543]] + +[[area.polygons]] +vertices = [ + [3551, 3544], + [3552, 3544], +] + +[[area.polygons]] +vertices = [ + [3559, 3544], + [3559, 3548], +] + +[[area.polygons]] +vertices = [ + [3538, 3545], + [3538, 3548], +] + +[[area.polygons]] +vertices = [ + [3558, 3545], + [3558, 3548], +] + +[[area.polygons]] +vertices = [ + [3539, 3546], + [3539, 3548], +] + +[[area.polygons]] +vertices = [ + [3529, 3549], + [3535, 3549], + [3535, 3551], + [3529, 3551], +] + +[[area.polygons]] +vertices = [ + [3561, 3549], + [3588, 3549], +] + +[[area.polygons]] +vertices = [ + [3562, 3550], + [3588, 3550], + [3588, 3552], + [3562, 3552], +] + +[[area.polygons]] +vertices = [ + [3529, 3552], + [3534, 3552], + [3534, 3583], + [3529, 3583], +] + +[[area.polygons]] +vertices = [ + [3563, 3553], + [3588, 3553], + [3588, 3583], + [3563, 3583], +] + +[[area.polygons]] +vertices = [ + [3562, 3557], + [3562, 3583], +] + +[[area.polygons]] +vertices = [ + [3535, 3558], + [3538, 3558], +] + +[[area.polygons]] +vertices = [ + [3559, 3558], + [3561, 3558], + [3561, 3559], + [3559, 3559], +] + +[[area.polygons]] +vertices = [ + [3535, 3559], + [3537, 3559], + [3537, 3583], + [3535, 3583], +] + +[[area.polygons]] +vertices = [ + [3560, 3560], + [3561, 3560], + [3561, 3583], + [3560, 3583], +] + +[[area.polygons]] +vertices = [ + [3538, 3562], + [3538, 3583], +] + +[[area.polygons]] +vertices = [ + [3541, 3562], + [3542, 3562], + [3542, 3583], + [3541, 3583], +] + +[[area.polygons]] +vertices = [ + [3555, 3562], + [3555, 3583], +] + +[[area.polygons]] +vertices = [ + [3558, 3562], + [3559, 3562], + [3559, 3583], + [3558, 3583], +] + +[[area.polygons]] +vertices = [ + [3539, 3563], + [3540, 3563], + [3540, 3583], + [3539, 3583], +] + +[[area.polygons]] +vertices = [ + [3556, 3563], + [3557, 3563], + [3557, 3583], + [3556, 3583], +] + +[[area.polygons]] +vertices = [ + [3554, 3564], + [3554, 3583], +] + +[[area.polygons]] +vertices = [ + [3543, 3565], + [3543, 3583], +] + +[[area.polygons]] +vertices = [ + [3553, 3565], + [3553, 3583], +] + +[[area.polygons]] +vertices = [ + [3544, 3566], + [3544, 3583], +] + +[[area.polygons]] +vertices = [ + [3552, 3567], + [3552, 3583], +] + +[[area.polygons]] +vertices = [ + [3545, 3568], + [3551, 3568], + [3551, 3583], + [3545, 3583], +] diff --git a/.data/raw-cache/map/area/mos_leharmless.toml b/.data/raw-cache/map/area/mos_leharmless.toml new file mode 100644 index 000000000..56754a206 --- /dev/null +++ b/.data/raw-cache/map/area/mos_leharmless.toml @@ -0,0 +1,162 @@ +# Mos Le'Harmless music music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: In the Brine; Life's a Beach!. +# Bounds x 3648-3859, z 2924-3071; 23885 tiles as 24 rectangles. + +[[area]] +name = "Mos Le'Harmless music" +area_id = "area.mos_leharmless" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3648, 2924], + [3775, 2924], + [3775, 3071], + [3648, 3071], +] + +[[area.polygons]] +vertices = [ + [3840, 2980], + [3859, 2980], + [3859, 3071], + [3840, 3071], +] + +[[area.polygons]] +vertices = [ + [3776, 3008], + [3789, 3008], + [3789, 3071], + [3776, 3071], +] + +[[area.polygons]] +vertices = [ + [3836, 3008], + [3839, 3008], + [3839, 3071], + [3836, 3071], +] + +[[area.polygons]] +vertices = [ + [3790, 3022], + [3790, 3071], +] + +[[area.polygons]] +vertices = [ + [3835, 3022], + [3835, 3071], +] + +[[area.polygons]] +vertices = [ + [3791, 3023], + [3791, 3071], +] + +[[area.polygons]] +vertices = [ + [3834, 3023], + [3834, 3071], +] + +[[area.polygons]] +vertices = [ + [3792, 3024], + [3792, 3071], +] + +[[area.polygons]] +vertices = [ + [3833, 3024], + [3833, 3071], +] + +[[area.polygons]] +vertices = [ + [3793, 3025], + [3793, 3071], +] + +[[area.polygons]] +vertices = [ + [3832, 3025], + [3832, 3071], +] + +[[area.polygons]] +vertices = [ + [3794, 3026], + [3794, 3071], +] + +[[area.polygons]] +vertices = [ + [3831, 3026], + [3831, 3071], +] + +[[area.polygons]] +vertices = [ + [3795, 3027], + [3795, 3071], +] + +[[area.polygons]] +vertices = [ + [3830, 3027], + [3830, 3071], +] + +[[area.polygons]] +vertices = [ + [3796, 3028], + [3796, 3071], +] + +[[area.polygons]] +vertices = [ + [3829, 3028], + [3829, 3071], +] + +[[area.polygons]] +vertices = [ + [3797, 3029], + [3797, 3071], +] + +[[area.polygons]] +vertices = [ + [3828, 3029], + [3828, 3071], +] + +[[area.polygons]] +vertices = [ + [3798, 3030], + [3798, 3071], +] + +[[area.polygons]] +vertices = [ + [3827, 3030], + [3827, 3071], +] + +[[area.polygons]] +vertices = [ + [3826, 3031], + [3826, 3071], +] + +[[area.polygons]] +vertices = [ + [3799, 3032], + [3825, 3032], + [3825, 3071], + [3799, 3071], +] diff --git a/.data/raw-cache/map/area/mourner_tunnels.toml b/.data/raw-cache/map/area/mourner_tunnels.toml index 5f38039b5..b0681c87b 100644 --- a/.data/raw-cache/map/area/mourner_tunnels.toml +++ b/.data/raw-cache/map/area/mourner_tunnels.toml @@ -1,3 +1,6 @@ +# Regenerated as rectangles from the OSRS wiki music map: 4096 tiles moved to +# neighbouring music areas. + [[area]] name = "Mourner Tunnels" area_id = "area.mourner_tunnels" @@ -5,8 +8,8 @@ levels = [0, 1, 2, 3] [[area.polygons]] vertices = [ - [1856, 4608], + [1920, 4608], [2047, 4608], [2047, 4671], - [1856, 4671], + [1920, 4671], ] diff --git a/.data/raw-cache/map/area/mouse_trap.toml b/.data/raw-cache/map/area/mouse_trap.toml new file mode 100644 index 000000000..2b16f2164 --- /dev/null +++ b/.data/raw-cache/map/area/mouse_trap.toml @@ -0,0 +1,16 @@ +# Mouse Trap music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mouse Trap. +# Bounds x 2240-2303, z 5504-5567; 4096 tiles as 1 rectangles. + +[[area]] +name = "Mouse Trap" +area_id = "area.mouse_trap" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2240, 5504], + [2303, 5504], + [2303, 5567], + [2240, 5567], +] diff --git a/.data/raw-cache/map/area/mudskipper_point.toml b/.data/raw-cache/map/area/mudskipper_point.toml new file mode 100644 index 000000000..b83508311 --- /dev/null +++ b/.data/raw-cache/map/area/mudskipper_point.toml @@ -0,0 +1,68 @@ +# The peninsula south of Port Sarim (fairy ring AIQ, the Rimmington coast road). Music area for +# "Mudskipper Melody". Port Sarim's polygon starts one tile north of this one so the two never +# overlap. +# +# Regenerated as rectangles from the OSRS wiki music map: 134 tiles moved to +# neighbouring music areas. + +[[area]] +name = "Mudskipper Point" +area_id = "area.mudskipper_point" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2944, 3072], + [3007, 3072], + [3007, 3135], + [2944, 3135], +] + +[[area.polygons]] +vertices = [ + [2944, 3136], + [2997, 3136], +] + +[[area.polygons]] +vertices = [[3007, 3136]] + +[[area.polygons]] +vertices = [ + [2944, 3137], + [2995, 3137], +] + +[[area.polygons]] +vertices = [ + [2944, 3138], + [2994, 3138], +] + +[[area.polygons]] +vertices = [ + [2944, 3139], + [2950, 3139], +] + +[[area.polygons]] +vertices = [ + [2970, 3139], + [2994, 3139], +] + +[[area.polygons]] +vertices = [ + [2944, 3140], + [2949, 3140], + [2949, 3141], + [2944, 3141], +] + +[[area.polygons]] +vertices = [ + [2970, 3140], + [2993, 3140], + [2993, 3141], + [2970, 3141], +] diff --git a/.data/raw-cache/map/area/musa_point.toml b/.data/raw-cache/map/area/musa_point.toml new file mode 100644 index 000000000..fa0b6150f --- /dev/null +++ b/.data/raw-cache/map/area/musa_point.toml @@ -0,0 +1,729 @@ +# Musa Point music music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Sea Shanty; Jungle Island. +# Bounds x 2816-2969, z 3136-3206; 6188 tiles as 112 rectangles. + +[[area]] +name = "Musa Point music" +area_id = "area.musa_point" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2923, 3136], + [2923, 3176], +] + +[[area.polygons]] +vertices = [ + [2905, 3137], + [2909, 3137], + [2909, 3181], + [2905, 3181], +] + +[[area.polygons]] +vertices = [ + [2921, 3137], + [2922, 3137], + [2922, 3177], + [2921, 3177], +] + +[[area.polygons]] +vertices = [ + [2924, 3137], + [2927, 3137], + [2927, 3174], + [2924, 3174], +] + +[[area.polygons]] +vertices = [ + [2882, 3138], + [2882, 3177], +] + +[[area.polygons]] +vertices = [ + [2904, 3138], + [2904, 3193], +] + +[[area.polygons]] +vertices = [ + [2910, 3138], + [2913, 3138], + [2913, 3180], + [2910, 3180], +] + +[[area.polygons]] +vertices = [ + [2920, 3138], + [2920, 3177], +] + +[[area.polygons]] +vertices = [ + [2928, 3138], + [2928, 3173], +] + +[[area.polygons]] +vertices = [ + [2932, 3138], + [2937, 3138], + [2937, 3160], + [2932, 3160], +] + +[[area.polygons]] +vertices = [ + [2842, 3139], + [2847, 3139], + [2847, 3203], + [2842, 3203], +] + +[[area.polygons]] +vertices = [ + [2877, 3139], + [2881, 3139], + [2881, 3178], + [2877, 3178], +] + +[[area.polygons]] +vertices = [ + [2883, 3139], + [2885, 3139], + [2885, 3176], + [2883, 3176], +] + +[[area.polygons]] +vertices = [ + [2902, 3139], + [2903, 3139], + [2903, 3182], + [2902, 3182], +] + +[[area.polygons]] +vertices = [ + [2914, 3139], + [2919, 3139], + [2919, 3178], + [2914, 3178], +] + +[[area.polygons]] +vertices = [ + [2929, 3139], + [2931, 3139], + [2931, 3167], + [2929, 3167], +] + +[[area.polygons]] +vertices = [ + [2938, 3139], + [2938, 3160], +] + +[[area.polygons]] +vertices = [ + [2951, 3139], + [2969, 3139], + [2969, 3143], + [2951, 3143], +] + +[[area.polygons]] +vertices = [ + [2841, 3140], + [2841, 3206], +] + +[[area.polygons]] +vertices = [ + [2848, 3140], + [2848, 3202], +] + +[[area.polygons]] +vertices = [ + [2851, 3140], + [2851, 3202], +] + +[[area.polygons]] +vertices = [ + [2876, 3140], + [2876, 3183], +] + +[[area.polygons]] +vertices = [ + [2886, 3140], + [2888, 3140], + [2888, 3176], + [2886, 3176], +] + +[[area.polygons]] +vertices = [ + [2901, 3140], + [2901, 3181], +] + +[[area.polygons]] +vertices = [ + [2939, 3140], + [2939, 3159], +] + +[[area.polygons]] +vertices = [ + [2950, 3140], + [2950, 3142], +] + +[[area.polygons]] +vertices = [ + [2839, 3141], + [2840, 3141], + [2840, 3206], + [2839, 3206], +] + +[[area.polygons]] +vertices = [ + [2849, 3141], + [2850, 3141], + [2850, 3202], + [2849, 3202], +] + +[[area.polygons]] +vertices = [ + [2852, 3141], + [2854, 3141], + [2854, 3200], + [2852, 3200], +] + +[[area.polygons]] +vertices = [ + [2875, 3141], + [2875, 3183], +] + +[[area.polygons]] +vertices = [ + [2889, 3141], + [2889, 3177], +] + +[[area.polygons]] +vertices = [ + [2898, 3141], + [2900, 3141], + [2900, 3180], + [2898, 3180], +] + +[[area.polygons]] +vertices = [ + [2940, 3141], + [2940, 3159], +] + +[[area.polygons]] +vertices = [ + [2835, 3142], + [2835, 3204], +] + +[[area.polygons]] +vertices = [ + [2838, 3142], + [2838, 3206], +] + +[[area.polygons]] +vertices = [ + [2855, 3142], + [2856, 3142], + [2856, 3200], + [2855, 3200], +] + +[[area.polygons]] +vertices = [ + [2873, 3142], + [2874, 3142], + [2874, 3183], + [2873, 3183], +] + +[[area.polygons]] +vertices = [ + [2890, 3142], + [2890, 3178], +] + +[[area.polygons]] +vertices = [ + [2895, 3142], + [2897, 3142], + [2897, 3178], + [2895, 3178], +] + +[[area.polygons]] +vertices = [ + [2941, 3142], + [2944, 3142], + [2944, 3158], + [2941, 3158], +] + +[[area.polygons]] +vertices = [ + [2823, 3143], + [2824, 3143], + [2824, 3198], + [2823, 3198], +] + +[[area.polygons]] +vertices = [ + [2833, 3143], + [2834, 3143], + [2834, 3204], + [2833, 3204], +] + +[[area.polygons]] +vertices = [ + [2836, 3143], + [2837, 3143], + [2837, 3205], + [2836, 3205], +] + +[[area.polygons]] +vertices = [ + [2857, 3143], + [2858, 3143], + [2858, 3199], + [2857, 3199], +] + +[[area.polygons]] +vertices = [ + [2871, 3143], + [2872, 3143], + [2872, 3184], + [2871, 3184], +] + +[[area.polygons]] +vertices = [ + [2891, 3143], + [2894, 3143], + [2894, 3178], + [2891, 3178], +] + +[[area.polygons]] +vertices = [ + [2945, 3143], + [2945, 3158], +] + +[[area.polygons]] +vertices = [ + [2822, 3144], + [2822, 3198], +] + +[[area.polygons]] +vertices = [ + [2825, 3144], + [2825, 3199], +] + +[[area.polygons]] +vertices = [ + [2832, 3144], + [2832, 3204], +] + +[[area.polygons]] +vertices = [ + [2859, 3144], + [2860, 3144], + [2860, 3198], + [2859, 3198], +] + +[[area.polygons]] +vertices = [ + [2869, 3144], + [2870, 3144], + [2870, 3185], + [2869, 3185], +] + +[[area.polygons]] +vertices = [ + [2946, 3144], + [2947, 3144], + [2947, 3156], + [2946, 3156], +] + +[[area.polygons]] +vertices = [ + [2956, 3144], + [2956, 3150], +] + +[[area.polygons]] +vertices = [ + [2821, 3145], + [2821, 3198], +] + +[[area.polygons]] +vertices = [ + [2830, 3145], + [2831, 3145], + [2831, 3204], + [2830, 3204], +] + +[[area.polygons]] +vertices = [ + [2861, 3145], + [2862, 3145], + [2862, 3196], + [2861, 3196], +] + +[[area.polygons]] +vertices = [ + [2867, 3145], + [2868, 3145], + [2868, 3189], + [2867, 3189], +] + +[[area.polygons]] +vertices = [ + [2948, 3145], + [2948, 3152], +] + +[[area.polygons]] +vertices = [ + [2826, 3146], + [2829, 3146], + [2829, 3200], + [2826, 3200], +] + +[[area.polygons]] +vertices = [ + [2863, 3146], + [2863, 3196], +] + +[[area.polygons]] +vertices = [ + [2866, 3146], + [2866, 3193], +] + +[[area.polygons]] +vertices = [ + [2949, 3146], + [2955, 3146], + [2955, 3147], + [2949, 3147], +] + +[[area.polygons]] +vertices = [ + [2957, 3146], + [2960, 3146], + [2960, 3147], + [2957, 3147], +] + +[[area.polygons]] +vertices = [ + [2820, 3147], + [2820, 3197], +] + +[[area.polygons]] +vertices = [ + [2864, 3147], + [2865, 3147], + [2865, 3194], + [2864, 3194], +] + +[[area.polygons]] +vertices = [ + [2819, 3148], + [2819, 3196], +] + +[[area.polygons]] +vertices = [ + [2949, 3148], + [2949, 3150], +] + +[[area.polygons]] +vertices = [ + [2952, 3148], + [2955, 3148], + [2955, 3150], + [2952, 3150], +] + +[[area.polygons]] +vertices = [ + [2818, 3150], + [2818, 3196], +] + +[[area.polygons]] +vertices = [ + [2817, 3151], + [2817, 3198], +] + +[[area.polygons]] +vertices = [ + [2953, 3151], + [2955, 3151], + [2955, 3154], + [2953, 3154], +] + +[[area.polygons]] +vertices = [ + [2816, 3152], + [2816, 3199], +] + +[[area.polygons]] +vertices = [ + [2957, 3154], + [2958, 3154], + [2958, 3163], + [2957, 3163], +] + +[[area.polygons]] +vertices = [ + [2953, 3155], + [2954, 3155], + [2954, 3159], + [2953, 3159], +] + +[[area.polygons]] +vertices = [ + [2959, 3155], + [2959, 3162], +] + +[[area.polygons]] +vertices = [[2946, 3157]] + +[[area.polygons]] +vertices = [ + [2955, 3158], + [2956, 3158], +] + +[[area.polygons]] +vertices = [ + [2941, 3159], + [2943, 3159], +] + +[[area.polygons]] +vertices = [ + [2932, 3161], + [2936, 3161], +] + +[[area.polygons]] +vertices = [ + [2932, 3162], + [2935, 3162], +] + +[[area.polygons]] +vertices = [ + [2932, 3163], + [2934, 3163], +] + +[[area.polygons]] +vertices = [ + [2932, 3164], + [2933, 3164], +] + +[[area.polygons]] +vertices = [ + [2932, 3165], + [2932, 3166], +] + +[[area.polygons]] +vertices = [ + [2929, 3168], + [2930, 3168], + [2930, 3169], + [2929, 3169], +] + +[[area.polygons]] +vertices = [ + [2929, 3170], + [2929, 3171], +] + +[[area.polygons]] +vertices = [ + [2924, 3175], + [2926, 3175], +] + +[[area.polygons]] +vertices = [ + [2924, 3176], + [2925, 3176], + [2925, 3180], + [2924, 3180], +] + +[[area.polygons]] +vertices = [[2888, 3177]] + +[[area.polygons]] +vertices = [ + [2877, 3179], + [2878, 3179], +] + +[[area.polygons]] +vertices = [ + [2896, 3179], + [2897, 3179], +] + +[[area.polygons]] +vertices = [ + [2914, 3179], + [2915, 3179], +] + +[[area.polygons]] +vertices = [ + [2877, 3180], + [2877, 3182], +] + +[[area.polygons]] +vertices = [[2897, 3180]] + +[[area.polygons]] +vertices = [[2900, 3181]] + +[[area.polygons]] +vertices = [[2910, 3181]] + +[[area.polygons]] +vertices = [ + [2905, 3182], + [2908, 3182], +] + +[[area.polygons]] +vertices = [ + [2905, 3183], + [2907, 3183], + [2907, 3193], + [2905, 3193], +] + +[[area.polygons]] +vertices = [ + [2869, 3186], + [2869, 3188], +] + +[[area.polygons]] +vertices = [[2867, 3190]] + +[[area.polygons]] +vertices = [[2864, 3195]] + +[[area.polygons]] +vertices = [[2861, 3197]] + +[[area.polygons]] +vertices = [[2859, 3199]] + +[[area.polygons]] +vertices = [[2857, 3200]] + +[[area.polygons]] +vertices = [ + [2827, 3201], + [2829, 3201], +] + +[[area.polygons]] +vertices = [ + [2852, 3201], + [2853, 3201], +] + +[[area.polygons]] +vertices = [ + [2828, 3202], + [2829, 3202], +] + +[[area.polygons]] +vertices = [[2852, 3202]] + +[[area.polygons]] +vertices = [[2829, 3203]] + +[[area.polygons]] +vertices = [ + [2842, 3204], + [2846, 3204], +] + +[[area.polygons]] +vertices = [ + [2842, 3205], + [2843, 3205], +] + +[[area.polygons]] +vertices = [[2837, 3206]] diff --git a/.data/raw-cache/map/area/museum_medley.toml b/.data/raw-cache/map/area/museum_medley.toml new file mode 100644 index 000000000..c6900c936 --- /dev/null +++ b/.data/raw-cache/map/area/museum_medley.toml @@ -0,0 +1,16 @@ +# Museum Medley music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Museum Medley. +# Bounds x 3008-3071, z 9920-9983; 4096 tiles as 1 rectangles. + +[[area]] +name = "Museum Medley" +area_id = "area.museum_medley" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 9920], + [3071, 9920], + [3071, 9983], + [3008, 9983], +] diff --git a/.data/raw-cache/map/area/mutant_medley.toml b/.data/raw-cache/map/area/mutant_medley.toml new file mode 100644 index 000000000..56b477e8a --- /dev/null +++ b/.data/raw-cache/map/area/mutant_medley.toml @@ -0,0 +1,16 @@ +# Mutant Medley music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mutant Medley. +# Bounds x 3008-3071, z 4352-4415; 4096 tiles as 1 rectangles. + +[[area]] +name = "Mutant Medley" +area_id = "area.mutant_medley" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 4352], + [3071, 4352], + [3071, 4415], + [3008, 4415], +] diff --git a/.data/raw-cache/map/area/my_arms_journey.toml b/.data/raw-cache/map/area/my_arms_journey.toml new file mode 100644 index 000000000..48c967625 --- /dev/null +++ b/.data/raw-cache/map/area/my_arms_journey.toml @@ -0,0 +1,16 @@ +# My Arm's Journey music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: My Arm's Journey. +# Bounds x 1856-1919, z 4864-4927; 4096 tiles as 1 rectangles. + +[[area]] +name = "My Arm's Journey" +area_id = "area.my_arms_journey" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 4864], + [1919, 4864], + [1919, 4927], + [1856, 4927], +] diff --git a/.data/raw-cache/map/area/mysterious_lands.toml b/.data/raw-cache/map/area/mysterious_lands.toml new file mode 100644 index 000000000..6977f0c26 --- /dev/null +++ b/.data/raw-cache/map/area/mysterious_lands.toml @@ -0,0 +1,1188 @@ +# Mysterious Lands music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mysterious Lands. +# Bounds x 2076-3003, z 2247-8767; 9015 tiles as 186 rectangles. + +[[area]] +name = "Mysterious Lands" +area_id = "area.mysterious_lands" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2977, 2247], + [2981, 2247], + [2981, 2296], + [2977, 2296], +] + +[[area.polygons]] +vertices = [ + [2975, 2248], + [2976, 2248], + [2976, 2282], + [2975, 2282], +] + +[[area.polygons]] +vertices = [ + [2982, 2248], + [2984, 2248], + [2984, 2297], + [2982, 2297], +] + +[[area.polygons]] +vertices = [ + [2974, 2249], + [2974, 2281], +] + +[[area.polygons]] +vertices = [ + [2985, 2249], + [2985, 2296], +] + +[[area.polygons]] +vertices = [ + [2951, 2250], + [2955, 2250], + [2955, 2269], + [2951, 2269], +] + +[[area.polygons]] +vertices = [ + [2986, 2250], + [2986, 2295], +] + +[[area.polygons]] +vertices = [ + [2948, 2251], + [2950, 2251], + [2950, 2266], + [2948, 2266], +] + +[[area.polygons]] +vertices = [ + [2956, 2251], + [2962, 2251], + [2962, 2286], + [2956, 2286], +] + +[[area.polygons]] +vertices = [ + [2973, 2251], + [2973, 2280], +] + +[[area.polygons]] +vertices = [ + [2987, 2251], + [2987, 2294], +] + +[[area.polygons]] +vertices = [ + [2963, 2252], + [2964, 2252], + [2964, 2284], + [2963, 2284], +] + +[[area.polygons]] +vertices = [ + [2971, 2252], + [2972, 2252], + [2972, 2279], + [2971, 2279], +] + +[[area.polygons]] +vertices = [ + [2988, 2252], + [2988, 2293], +] + +[[area.polygons]] +vertices = [ + [2965, 2253], + [2965, 2284], +] + +[[area.polygons]] +vertices = [ + [2969, 2253], + [2970, 2253], + [2970, 2279], + [2969, 2279], +] + +[[area.polygons]] +vertices = [ + [2989, 2253], + [2989, 2292], +] + +[[area.polygons]] +vertices = [ + [2947, 2254], + [2947, 2260], +] + +[[area.polygons]] +vertices = [ + [2966, 2254], + [2968, 2254], + [2968, 2281], + [2966, 2281], +] + +[[area.polygons]] +vertices = [ + [2990, 2254], + [2991, 2254], + [2991, 2290], + [2990, 2290], +] + +[[area.polygons]] +vertices = [ + [2992, 2255], + [2993, 2255], + [2993, 2289], + [2992, 2289], +] + +[[area.polygons]] +vertices = [ + [2946, 2256], + [2946, 2257], +] + +[[area.polygons]] +vertices = [ + [2994, 2256], + [2994, 2288], +] + +[[area.polygons]] +vertices = [ + [2995, 2257], + [2995, 2288], +] + +[[area.polygons]] +vertices = [ + [2996, 2258], + [2996, 2288], +] + +[[area.polygons]] +vertices = [ + [2997, 2259], + [2997, 2288], +] + +[[area.polygons]] +vertices = [ + [2998, 2263], + [2998, 2288], +] + +[[area.polygons]] +vertices = [ + [2999, 2266], + [2999, 2287], +] + +[[area.polygons]] +vertices = [ + [2949, 2267], + [2950, 2267], +] + +[[area.polygons]] +vertices = [[2950, 2268]] + +[[area.polygons]] +vertices = [ + [3000, 2269], + [3000, 2287], +] + +[[area.polygons]] +vertices = [ + [2952, 2270], + [2955, 2270], + [2955, 2271], + [2952, 2271], +] + +[[area.polygons]] +vertices = [ + [3001, 2270], + [3001, 2287], +] + +[[area.polygons]] +vertices = [ + [2953, 2272], + [2955, 2272], + [2955, 2282], + [2953, 2282], +] + +[[area.polygons]] +vertices = [ + [3002, 2273], + [3002, 2287], +] + +[[area.polygons]] +vertices = [ + [3003, 2278], + [3003, 2286], +] + +[[area.polygons]] +vertices = [[2969, 2280]] + +[[area.polygons]] +vertices = [ + [2966, 2282], + [2967, 2282], +] + +[[area.polygons]] +vertices = [ + [2954, 2283], + [2955, 2283], + [2955, 2286], + [2954, 2286], +] + +[[area.polygons]] +vertices = [[2966, 2283]] + +[[area.polygons]] +vertices = [ + [2976, 2283], + [2976, 2296], +] + +[[area.polygons]] +vertices = [[2963, 2285]] + +[[area.polygons]] +vertices = [ + [2975, 2286], + [2975, 2296], +] + +[[area.polygons]] +vertices = [ + [2955, 2287], + [2961, 2287], +] + +[[area.polygons]] +vertices = [ + [2955, 2288], + [2960, 2288], + [2960, 2290], + [2955, 2290], +] + +[[area.polygons]] +vertices = [ + [2974, 2288], + [2974, 2295], +] + +[[area.polygons]] +vertices = [ + [2954, 2289], + [2954, 2293], +] + +[[area.polygons]] +vertices = [ + [2973, 2290], + [2973, 2295], +] + +[[area.polygons]] +vertices = [ + [2955, 2291], + [2959, 2291], +] + +[[area.polygons]] +vertices = [[2990, 2291]] + +[[area.polygons]] +vertices = [ + [2955, 2292], + [2957, 2292], +] + +[[area.polygons]] +vertices = [[2955, 2293]] + +[[area.polygons]] +vertices = [ + [2980, 2297], + [2981, 2297], +] + +[[area.polygons]] +vertices = [ + [2204, 2306], + [2209, 2306], + [2209, 2333], + [2204, 2333], +] + +[[area.polygons]] +vertices = [ + [2202, 2307], + [2203, 2307], + [2203, 2333], + [2202, 2333], +] + +[[area.polygons]] +vertices = [ + [2210, 2307], + [2211, 2307], + [2211, 2335], + [2210, 2335], +] + +[[area.polygons]] +vertices = [ + [2181, 2308], + [2184, 2308], + [2184, 2318], + [2181, 2318], +] + +[[area.polygons]] +vertices = [ + [2199, 2308], + [2201, 2308], + [2201, 2334], + [2199, 2334], +] + +[[area.polygons]] +vertices = [ + [2212, 2308], + [2215, 2308], + [2215, 2339], + [2212, 2339], +] + +[[area.polygons]] +vertices = [ + [2180, 2309], + [2180, 2316], +] + +[[area.polygons]] +vertices = [ + [2185, 2309], + [2187, 2309], + [2187, 2324], + [2185, 2324], +] + +[[area.polygons]] +vertices = [ + [2195, 2309], + [2198, 2309], + [2198, 2335], + [2195, 2335], +] + +[[area.polygons]] +vertices = [ + [2216, 2309], + [2218, 2309], + [2218, 2345], + [2216, 2345], +] + +[[area.polygons]] +vertices = [ + [2179, 2310], + [2179, 2313], +] + +[[area.polygons]] +vertices = [ + [2188, 2310], + [2194, 2310], + [2194, 2325], + [2188, 2325], +] + +[[area.polygons]] +vertices = [ + [2219, 2310], + [2219, 2351], +] + +[[area.polygons]] +vertices = [ + [2220, 2311], + [2220, 2353], +] + +[[area.polygons]] +vertices = [ + [2221, 2312], + [2221, 2353], +] + +[[area.polygons]] +vertices = [ + [2222, 2313], + [2222, 2353], +] + +[[area.polygons]] +vertices = [ + [2223, 2314], + [2223, 2353], +] + +[[area.polygons]] +vertices = [ + [2224, 2315], + [2224, 2353], +] + +[[area.polygons]] +vertices = [ + [2225, 2316], + [2225, 2352], +] + +[[area.polygons]] +vertices = [ + [2226, 2318], + [2226, 2352], +] + +[[area.polygons]] +vertices = [ + [2182, 2319], + [2184, 2319], + [2184, 2321], + [2182, 2321], +] + +[[area.polygons]] +vertices = [ + [2227, 2320], + [2227, 2351], +] + +[[area.polygons]] +vertices = [ + [2183, 2322], + [2184, 2322], +] + +[[area.polygons]] +vertices = [ + [2228, 2322], + [2228, 2350], +] + +[[area.polygons]] +vertices = [[2184, 2323]] + +[[area.polygons]] +vertices = [ + [2229, 2324], + [2229, 2349], +] + +[[area.polygons]] +vertices = [ + [2186, 2325], + [2187, 2325], +] + +[[area.polygons]] +vertices = [ + [2190, 2326], + [2194, 2326], +] + +[[area.polygons]] +vertices = [ + [2230, 2326], + [2230, 2348], +] + +[[area.polygons]] +vertices = [ + [2191, 2327], + [2194, 2327], +] + +[[area.polygons]] +vertices = [ + [2231, 2327], + [2232, 2327], + [2232, 2347], + [2231, 2347], +] + +[[area.polygons]] +vertices = [ + [2192, 2328], + [2194, 2328], + [2194, 2330], + [2192, 2330], +] + +[[area.polygons]] +vertices = [ + [2233, 2328], + [2233, 2347], +] + +[[area.polygons]] +vertices = [ + [2234, 2329], + [2234, 2347], +] + +[[area.polygons]] +vertices = [ + [2235, 2330], + [2235, 2346], +] + +[[area.polygons]] +vertices = [ + [2193, 2331], + [2194, 2331], + [2194, 2335], + [2193, 2335], +] + +[[area.polygons]] +vertices = [ + [2236, 2332], + [2236, 2346], +] + +[[area.polygons]] +vertices = [ + [2208, 2334], + [2209, 2334], +] + +[[area.polygons]] +vertices = [[2199, 2335]] + +[[area.polygons]] +vertices = [ + [2194, 2336], + [2197, 2336], +] + +[[area.polygons]] +vertices = [ + [2211, 2336], + [2211, 2337], +] + +[[area.polygons]] +vertices = [ + [2213, 2340], + [2215, 2340], + [2215, 2341], + [2213, 2341], +] + +[[area.polygons]] +vertices = [ + [2237, 2341], + [2237, 2344], +] + +[[area.polygons]] +vertices = [ + [2214, 2342], + [2215, 2342], + [2215, 2343], + [2214, 2343], +] + +[[area.polygons]] +vertices = [[2215, 2344]] + +[[area.polygons]] +vertices = [ + [2217, 2346], + [2218, 2346], +] + +[[area.polygons]] +vertices = [ + [2218, 2347], + [2218, 2348], +] + +[[area.polygons]] +vertices = [[2231, 2348]] + +[[area.polygons]] +vertices = [ + [2961, 2600], + [2964, 2600], + [2964, 2615], + [2961, 2615], +] + +[[area.polygons]] +vertices = [ + [2968, 2600], + [2968, 2607], +] + +[[area.polygons]] +vertices = [ + [2965, 2601], + [2967, 2601], + [2967, 2609], + [2965, 2609], +] + +[[area.polygons]] +vertices = [ + [2969, 2601], + [2970, 2601], + [2970, 2606], + [2969, 2606], +] + +[[area.polygons]] +vertices = [ + [2960, 2602], + [2960, 2616], +] + +[[area.polygons]] +vertices = [ + [2971, 2602], + [2971, 2605], +] + +[[area.polygons]] +vertices = [ + [2957, 2603], + [2959, 2603], + [2959, 2617], + [2957, 2617], +] + +[[area.polygons]] +vertices = [ + [2972, 2603], + [2972, 2604], +] + +[[area.polygons]] +vertices = [ + [2956, 2605], + [2956, 2617], +] + +[[area.polygons]] +vertices = [ + [2955, 2608], + [2955, 2617], +] + +[[area.polygons]] +vertices = [ + [2954, 2609], + [2954, 2616], +] + +[[area.polygons]] +vertices = [ + [2953, 2610], + [2953, 2616], +] + +[[area.polygons]] +vertices = [ + [2965, 2610], + [2966, 2610], + [2966, 2616], + [2965, 2616], +] + +[[area.polygons]] +vertices = [ + [2952, 2611], + [2952, 2616], +] + +[[area.polygons]] +vertices = [ + [2967, 2611], + [2967, 2618], +] + +[[area.polygons]] +vertices = [ + [2950, 2612], + [2951, 2612], + [2951, 2615], + [2950, 2615], +] + +[[area.polygons]] +vertices = [ + [2968, 2613], + [2968, 2618], +] + +[[area.polygons]] +vertices = [ + [2969, 2614], + [2969, 2616], +] + +[[area.polygons]] +vertices = [[2951, 2616]] + +[[area.polygons]] +vertices = [[2966, 2617]] + +[[area.polygons]] +vertices = [ + [2340, 2755], + [2340, 2774], +] + +[[area.polygons]] +vertices = [ + [2337, 2756], + [2339, 2756], + [2339, 2775], + [2337, 2775], +] + +[[area.polygons]] +vertices = [ + [2341, 2756], + [2342, 2756], + [2342, 2771], + [2341, 2771], +] + +[[area.polygons]] +vertices = [ + [2336, 2757], + [2336, 2776], +] + +[[area.polygons]] +vertices = [ + [2343, 2757], + [2345, 2757], + [2345, 2769], + [2343, 2769], +] + +[[area.polygons]] +vertices = [ + [2334, 2758], + [2335, 2758], + [2335, 2776], + [2334, 2776], +] + +[[area.polygons]] +vertices = [ + [2346, 2758], + [2346, 2768], +] + +[[area.polygons]] +vertices = [ + [2331, 2759], + [2333, 2759], + [2333, 2776], + [2331, 2776], +] + +[[area.polygons]] +vertices = [ + [2329, 2760], + [2330, 2760], + [2330, 2777], + [2329, 2777], +] + +[[area.polygons]] +vertices = [ + [2347, 2760], + [2347, 2767], +] + +[[area.polygons]] +vertices = [ + [2328, 2761], + [2328, 2777], +] + +[[area.polygons]] +vertices = [ + [2326, 2762], + [2327, 2762], + [2327, 2777], + [2326, 2777], +] + +[[area.polygons]] +vertices = [ + [2325, 2763], + [2325, 2778], +] + +[[area.polygons]] +vertices = [ + [2348, 2763], + [2348, 2765], +] + +[[area.polygons]] +vertices = [ + [2324, 2764], + [2324, 2778], +] + +[[area.polygons]] +vertices = [ + [2323, 2768], + [2323, 2778], +] + +[[area.polygons]] +vertices = [ + [2322, 2770], + [2322, 2779], +] + +[[area.polygons]] +vertices = [ + [2343, 2770], + [2344, 2770], +] + +[[area.polygons]] +vertices = [[2343, 2771]] + +[[area.polygons]] +vertices = [ + [2321, 2772], + [2321, 2779], +] + +[[area.polygons]] +vertices = [[2341, 2772]] + +[[area.polygons]] +vertices = [ + [2320, 2773], + [2320, 2779], +] + +[[area.polygons]] +vertices = [ + [2319, 2774], + [2319, 2779], +] + +[[area.polygons]] +vertices = [ + [2318, 2776], + [2318, 2779], +] + +[[area.polygons]] +vertices = [ + [2337, 2776], + [2338, 2776], +] + +[[area.polygons]] +vertices = [ + [2317, 2777], + [2317, 2780], +] + +[[area.polygons]] +vertices = [ + [2331, 2777], + [2332, 2777], +] + +[[area.polygons]] +vertices = [ + [2315, 2778], + [2316, 2778], + [2316, 2781], + [2315, 2781], +] + +[[area.polygons]] +vertices = [[2326, 2778]] + +[[area.polygons]] +vertices = [ + [2312, 2780], + [2312, 2782], +] + +[[area.polygons]] +vertices = [ + [2314, 2780], + [2314, 2782], +] + +[[area.polygons]] +vertices = [ + [2311, 2781], + [2311, 2782], +] + +[[area.polygons]] +vertices = [ + [2313, 2781], + [2313, 2782], +] + +[[area.polygons]] +vertices = [ + [2104, 3161], + [2108, 3161], + [2108, 3167], + [2104, 3167], +] + +[[area.polygons]] +vertices = [ + [2103, 3162], + [2103, 3175], +] + +[[area.polygons]] +vertices = [ + [2109, 3162], + [2109, 3166], +] + +[[area.polygons]] +vertices = [ + [2102, 3164], + [2102, 3176], +] + +[[area.polygons]] +vertices = [ + [2093, 3165], + [2101, 3165], + [2101, 3177], + [2093, 3177], +] + +[[area.polygons]] +vertices = [ + [2091, 3166], + [2092, 3166], + [2092, 3194], + [2091, 3194], +] + +[[area.polygons]] +vertices = [ + [2089, 3167], + [2090, 3167], + [2090, 3195], + [2089, 3195], +] + +[[area.polygons]] +vertices = [ + [2088, 3168], + [2088, 3195], +] + +[[area.polygons]] +vertices = [ + [2104, 3168], + [2107, 3168], + [2107, 3170], + [2104, 3170], +] + +[[area.polygons]] +vertices = [ + [2087, 3169], + [2087, 3195], +] + +[[area.polygons]] +vertices = [ + [2086, 3171], + [2086, 3195], +] + +[[area.polygons]] +vertices = [ + [2104, 3171], + [2106, 3171], + [2106, 3173], + [2104, 3173], +] + +[[area.polygons]] +vertices = [ + [2085, 3172], + [2085, 3195], +] + +[[area.polygons]] +vertices = [ + [2083, 3173], + [2084, 3173], + [2084, 3195], + [2083, 3195], +] + +[[area.polygons]] +vertices = [ + [2081, 3174], + [2082, 3174], + [2082, 3194], + [2081, 3194], +] + +[[area.polygons]] +vertices = [ + [2104, 3174], + [2105, 3174], +] + +[[area.polygons]] +vertices = [ + [2080, 3175], + [2080, 3194], +] + +[[area.polygons]] +vertices = [[2104, 3175]] + +[[area.polygons]] +vertices = [ + [2079, 3176], + [2079, 3192], +] + +[[area.polygons]] +vertices = [ + [2093, 3178], + [2100, 3178], + [2100, 3179], + [2093, 3179], +] + +[[area.polygons]] +vertices = [ + [2078, 3179], + [2078, 3191], +] + +[[area.polygons]] +vertices = [ + [2093, 3180], + [2099, 3180], + [2099, 3184], + [2093, 3184], +] + +[[area.polygons]] +vertices = [ + [2077, 3181], + [2077, 3190], +] + +[[area.polygons]] +vertices = [ + [2076, 3183], + [2076, 3189], +] + +[[area.polygons]] +vertices = [ + [2093, 3185], + [2098, 3185], + [2098, 3187], + [2093, 3187], +] + +[[area.polygons]] +vertices = [ + [2093, 3188], + [2097, 3188], +] + +[[area.polygons]] +vertices = [ + [2093, 3189], + [2096, 3189], +] + +[[area.polygons]] +vertices = [ + [2093, 3190], + [2095, 3190], + [2095, 3191], + [2093, 3191], +] + +[[area.polygons]] +vertices = [ + [2093, 3192], + [2094, 3192], +] + +[[area.polygons]] +vertices = [[2093, 3193]] + +[[area.polygons]] +vertices = [[2091, 3195]] + +[[area.polygons]] +vertices = [ + [2176, 8704], + [2239, 8704], + [2239, 8767], + [2176, 8767], +] diff --git a/.data/raw-cache/map/area/mystics_of_nature.toml b/.data/raw-cache/map/area/mystics_of_nature.toml new file mode 100644 index 000000000..3627ad25b --- /dev/null +++ b/.data/raw-cache/map/area/mystics_of_nature.toml @@ -0,0 +1,16 @@ +# Mystics of Nature music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mystics of Nature. +# Bounds x 3008-3071, z 6080-6143; 4096 tiles as 1 rectangles. + +[[area]] +name = "Mystics of Nature" +area_id = "area.mystics_of_nature" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 6080], + [3071, 6080], + [3071, 6143], + [3008, 6143], +] diff --git a/.data/raw-cache/map/area/mythical.toml b/.data/raw-cache/map/area/mythical.toml new file mode 100644 index 000000000..c9efc656d --- /dev/null +++ b/.data/raw-cache/map/area/mythical.toml @@ -0,0 +1,261 @@ +# Mythical music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Mythical. +# Bounds x 2439-2472, z 2817-2865; 928 tiles as 41 rectangles. + +[[area]] +name = "Mythical" +area_id = "area.mythical" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2444, 2817], + [2448, 2817], + [2448, 2829], + [2444, 2829], +] + +[[area.polygons]] +vertices = [ + [2443, 2818], + [2443, 2829], +] + +[[area.polygons]] +vertices = [ + [2449, 2818], + [2449, 2863], +] + +[[area.polygons]] +vertices = [ + [2450, 2819], + [2450, 2863], +] + +[[area.polygons]] +vertices = [ + [2451, 2820], + [2451, 2822], +] + +[[area.polygons]] +vertices = [ + [2442, 2821], + [2442, 2828], +] + +[[area.polygons]] +vertices = [[2452, 2821]] + +[[area.polygons]] +vertices = [ + [2441, 2823], + [2441, 2827], +] + +[[area.polygons]] +vertices = [ + [2451, 2826], + [2451, 2862], +] + +[[area.polygons]] +vertices = [ + [2452, 2829], + [2452, 2861], +] + +[[area.polygons]] +vertices = [ + [2446, 2830], + [2448, 2830], +] + +[[area.polygons]] +vertices = [ + [2453, 2830], + [2453, 2859], +] + +[[area.polygons]] +vertices = [ + [2447, 2831], + [2448, 2831], + [2448, 2834], + [2447, 2834], +] + +[[area.polygons]] +vertices = [ + [2454, 2831], + [2457, 2831], + [2457, 2859], + [2454, 2859], +] + +[[area.polygons]] +vertices = [ + [2458, 2832], + [2461, 2832], + [2461, 2859], + [2458, 2859], +] + +[[area.polygons]] +vertices = [ + [2462, 2833], + [2462, 2859], +] + +[[area.polygons]] +vertices = [ + [2463, 2834], + [2463, 2858], +] + +[[area.polygons]] +vertices = [ + [2448, 2835], + [2448, 2863], +] + +[[area.polygons]] +vertices = [ + [2464, 2836], + [2464, 2857], +] + +[[area.polygons]] +vertices = [ + [2447, 2837], + [2447, 2864], +] + +[[area.polygons]] +vertices = [ + [2445, 2838], + [2446, 2838], + [2446, 2865], + [2445, 2865], +] + +[[area.polygons]] +vertices = [ + [2465, 2838], + [2467, 2838], + [2467, 2853], + [2465, 2853], +] + +[[area.polygons]] +vertices = [ + [2442, 2839], + [2444, 2839], + [2444, 2849], + [2442, 2849], +] + +[[area.polygons]] +vertices = [ + [2468, 2839], + [2468, 2852], +] + +[[area.polygons]] +vertices = [ + [2440, 2840], + [2441, 2840], + [2441, 2846], + [2440, 2846], +] + +[[area.polygons]] +vertices = [ + [2469, 2840], + [2470, 2840], + [2470, 2850], + [2469, 2850], +] + +[[area.polygons]] +vertices = [ + [2471, 2841], + [2471, 2849], +] + +[[area.polygons]] +vertices = [[2439, 2842]] + +[[area.polygons]] +vertices = [ + [2472, 2842], + [2472, 2845], +] + +[[area.polygons]] +vertices = [ + [2441, 2847], + [2441, 2848], +] + +[[area.polygons]] +vertices = [ + [2443, 2850], + [2444, 2850], + [2444, 2864], + [2443, 2864], +] + +[[area.polygons]] +vertices = [ + [2442, 2851], + [2442, 2853], +] + +[[area.polygons]] +vertices = [[2469, 2851]] + +[[area.polygons]] +vertices = [ + [2465, 2854], + [2466, 2854], +] + +[[area.polygons]] +vertices = [ + [2442, 2855], + [2442, 2864], +] + +[[area.polygons]] +vertices = [ + [2465, 2855], + [2465, 2856], +] + +[[area.polygons]] +vertices = [ + [2441, 2856], + [2441, 2862], +] + +[[area.polygons]] +vertices = [ + [2440, 2857], + [2440, 2861], +] + +[[area.polygons]] +vertices = [ + [2455, 2860], + [2458, 2860], + [2458, 2861], + [2455, 2861], +] + +[[area.polygons]] +vertices = [[2459, 2861]] + +[[area.polygons]] +vertices = [[2444, 2865]] diff --git a/.data/raw-cache/map/area/narnodes_theme.toml b/.data/raw-cache/map/area/narnodes_theme.toml new file mode 100644 index 000000000..3acb504cd --- /dev/null +++ b/.data/raw-cache/map/area/narnodes_theme.toml @@ -0,0 +1,16 @@ +# Narnode's Theme music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Narnode's Theme. +# Bounds x 2432-2495, z 9856-9919; 4096 tiles as 1 rectangles. + +[[area]] +name = "Narnode's Theme" +area_id = "area.narnodes_theme" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2432, 9856], + [2495, 9856], + [2495, 9919], + [2432, 9919], +] diff --git a/.data/raw-cache/map/area/natural.toml b/.data/raw-cache/map/area/natural.toml new file mode 100644 index 000000000..824ec9486 --- /dev/null +++ b/.data/raw-cache/map/area/natural.toml @@ -0,0 +1,102 @@ +# Natural music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Natural. +# Bounds x 2016-3450, z 3331-5055; 4326 tiles as 14 rectangles. + +[[area]] +name = "Natural" +area_id = "area.natural" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3435, 3331], + [3446, 3331], + [3446, 3343], + [3435, 3343], +] + +[[area.polygons]] +vertices = [ + [3434, 3332], + [3434, 3342], +] + +[[area.polygons]] +vertices = [ + [3447, 3332], + [3447, 3342], +] + +[[area.polygons]] +vertices = [ + [3433, 3333], + [3433, 3341], +] + +[[area.polygons]] +vertices = [ + [3448, 3333], + [3448, 3341], +] + +[[area.polygons]] +vertices = [ + [3432, 3334], + [3432, 3340], +] + +[[area.polygons]] +vertices = [ + [3449, 3334], + [3449, 3340], +] + +[[area.polygons]] +vertices = [ + [3450, 3335], + [3450, 3339], +] + +[[area.polygons]] +vertices = [ + [3436, 3344], + [3444, 3344], +] + +[[area.polygons]] +vertices = [ + [3437, 3345], + [3442, 3345], +] + +[[area.polygons]] +vertices = [ + [2016, 5024], + [2047, 5024], + [2047, 5055], + [2016, 5055], +] + +[[area.polygons]] +vertices = [ + [2208, 5024], + [2239, 5024], + [2239, 5055], + [2208, 5055], +] + +[[area.polygons]] +vertices = [ + [2272, 5024], + [2303, 5024], + [2303, 5055], + [2272, 5055], +] + +[[area.polygons]] +vertices = [ + [2336, 5024], + [2367, 5024], + [2367, 5055], + [2336, 5055], +] diff --git a/.data/raw-cache/map/area/nether_realm.toml b/.data/raw-cache/map/area/nether_realm.toml new file mode 100644 index 000000000..20c611b96 --- /dev/null +++ b/.data/raw-cache/map/area/nether_realm.toml @@ -0,0 +1,16 @@ +# Nether Realm music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Karamja Jam; 7th Realm; Pathways; Nether Realm. +# Bounds x 2624-2751, z 9408-9599; 24576 tiles as 1 rectangles. + +[[area]] +name = "Nether Realm" +area_id = "area.nether_realm" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2624, 9408], + [2751, 9408], + [2751, 9599], + [2624, 9599], +] diff --git a/.data/raw-cache/map/area/neverland.toml b/.data/raw-cache/map/area/neverland.toml new file mode 100644 index 000000000..a8ef5e7a4 --- /dev/null +++ b/.data/raw-cache/map/area/neverland.toml @@ -0,0 +1,126 @@ +# Neverland music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Neverland. +# Bounds x 1875-1895, z 3421-3445; 297 tiles as 19 rectangles. + +[[area]] +name = "Neverland" +area_id = "area.neverland" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1888, 3421], + [1889, 3421], + [1889, 3435], + [1888, 3435], +] + +[[area.polygons]] +vertices = [ + [1886, 3422], + [1887, 3422], + [1887, 3441], + [1886, 3441], +] + +[[area.polygons]] +vertices = [ + [1890, 3422], + [1890, 3434], +] + +[[area.polygons]] +vertices = [ + [1885, 3423], + [1885, 3444], +] + +[[area.polygons]] +vertices = [ + [1891, 3423], + [1893, 3423], + [1893, 3429], + [1891, 3429], +] + +[[area.polygons]] +vertices = [ + [1881, 3424], + [1884, 3424], + [1884, 3444], + [1881, 3444], +] + +[[area.polygons]] +vertices = [ + [1894, 3424], + [1894, 3428], +] + +[[area.polygons]] +vertices = [ + [1880, 3425], + [1880, 3443], +] + +[[area.polygons]] +vertices = [ + [1895, 3425], + [1895, 3427], +] + +[[area.polygons]] +vertices = [ + [1878, 3426], + [1879, 3426], + [1879, 3439], + [1878, 3439], +] + +[[area.polygons]] +vertices = [ + [1877, 3428], + [1877, 3437], +] + +[[area.polygons]] +vertices = [ + [1876, 3429], + [1876, 3436], +] + +[[area.polygons]] +vertices = [ + [1891, 3430], + [1892, 3430], +] + +[[area.polygons]] +vertices = [ + [1875, 3431], + [1875, 3433], +] + +[[area.polygons]] +vertices = [[1891, 3431]] + +[[area.polygons]] +vertices = [ + [1888, 3436], + [1888, 3438], +] + +[[area.polygons]] +vertices = [ + [1879, 3440], + [1879, 3441], +] + +[[area.polygons]] +vertices = [[1886, 3442]] + +[[area.polygons]] +vertices = [ + [1883, 3445], + [1884, 3445], +] diff --git a/.data/raw-cache/map/area/newbie_farming.toml b/.data/raw-cache/map/area/newbie_farming.toml new file mode 100644 index 000000000..874b91ffb --- /dev/null +++ b/.data/raw-cache/map/area/newbie_farming.toml @@ -0,0 +1,108 @@ +# Newbie Farming music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Newbie Farming. +# Bounds x 1184-1279, z 3665-3711; 4158 tiles as 16 rectangles. + +[[area]] +name = "Newbie Farming" +area_id = "area.newbie_farming" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1184, 3665], + [1242, 3665], + [1242, 3711], + [1184, 3711], +] + +[[area.polygons]] +vertices = [ + [1243, 3666], + [1243, 3711], +] + +[[area.polygons]] +vertices = [ + [1244, 3667], + [1244, 3711], +] + +[[area.polygons]] +vertices = [ + [1245, 3668], + [1245, 3711], +] + +[[area.polygons]] +vertices = [ + [1246, 3669], + [1246, 3711], +] + +[[area.polygons]] +vertices = [ + [1247, 3670], + [1247, 3711], +] + +[[area.polygons]] +vertices = [ + [1248, 3671], + [1248, 3711], +] + +[[area.polygons]] +vertices = [ + [1249, 3672], + [1271, 3672], + [1271, 3711], + [1249, 3711], +] + +[[area.polygons]] +vertices = [ + [1272, 3683], + [1272, 3711], +] + +[[area.polygons]] +vertices = [ + [1273, 3684], + [1273, 3711], +] + +[[area.polygons]] +vertices = [ + [1274, 3685], + [1274, 3711], +] + +[[area.polygons]] +vertices = [ + [1275, 3686], + [1275, 3711], +] + +[[area.polygons]] +vertices = [ + [1276, 3687], + [1276, 3711], +] + +[[area.polygons]] +vertices = [ + [1277, 3688], + [1277, 3711], +] + +[[area.polygons]] +vertices = [ + [1278, 3689], + [1278, 3711], +] + +[[area.polygons]] +vertices = [ + [1279, 3690], + [1279, 3711], +] diff --git a/.data/raw-cache/map/area/newbie_melody.toml b/.data/raw-cache/map/area/newbie_melody.toml new file mode 100644 index 000000000..038e1c5b3 --- /dev/null +++ b/.data/raw-cache/map/area/newbie_melody.toml @@ -0,0 +1,860 @@ +# Newbie Melody music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Newbie Melody. +# Bounds x 3054-3155, z 3008-3134; 6678 tiles as 133 rectangles. + +[[area]] +name = "Newbie Melody" +area_id = "area.newbie_melody" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3096, 3008], + [3108, 3008], + [3108, 3015], + [3096, 3015], +] + +[[area.polygons]] +vertices = [ + [3094, 3009], + [3095, 3009], + [3095, 3016], + [3094, 3016], +] + +[[area.polygons]] +vertices = [ + [3109, 3009], + [3111, 3009], + [3111, 3016], + [3109, 3016], +] + +[[area.polygons]] +vertices = [ + [3093, 3010], + [3093, 3016], +] + +[[area.polygons]] +vertices = [ + [3112, 3010], + [3112, 3016], +] + +[[area.polygons]] +vertices = [ + [3113, 3011], + [3114, 3011], + [3114, 3017], + [3113, 3017], +] + +[[area.polygons]] +vertices = [ + [3092, 3012], + [3092, 3015], +] + +[[area.polygons]] +vertices = [ + [3115, 3012], + [3115, 3020], +] + +[[area.polygons]] +vertices = [ + [3116, 3013], + [3116, 3021], +] + +[[area.polygons]] +vertices = [ + [3117, 3014], + [3117, 3022], +] + +[[area.polygons]] +vertices = [ + [3118, 3015], + [3118, 3023], +] + +[[area.polygons]] +vertices = [ + [3097, 3016], + [3108, 3016], +] + +[[area.polygons]] +vertices = [ + [3119, 3016], + [3119, 3024], +] + +[[area.polygons]] +vertices = [ + [3100, 3017], + [3101, 3017], + [3101, 3033], + [3100, 3033], +] + +[[area.polygons]] +vertices = [ + [3120, 3017], + [3120, 3023], +] + +[[area.polygons]] +vertices = [ + [3114, 3018], + [3114, 3019], +] + +[[area.polygons]] +vertices = [ + [3096, 3020], + [3099, 3020], + [3099, 3032], + [3096, 3032], +] + +[[area.polygons]] +vertices = [ + [3102, 3020], + [3111, 3020], + [3111, 3035], + [3102, 3035], +] + +[[area.polygons]] +vertices = [ + [3094, 3021], + [3095, 3021], + [3095, 3031], + [3094, 3031], +] + +[[area.polygons]] +vertices = [ + [3112, 3021], + [3112, 3035], +] + +[[area.polygons]] +vertices = [ + [3113, 3022], + [3113, 3035], +] + +[[area.polygons]] +vertices = [ + [3093, 3023], + [3093, 3032], +] + +[[area.polygons]] +vertices = [ + [3114, 3023], + [3114, 3035], +] + +[[area.polygons]] +vertices = [ + [3115, 3024], + [3115, 3035], +] + +[[area.polygons]] +vertices = [ + [3092, 3027], + [3092, 3032], +] + +[[area.polygons]] +vertices = [ + [3090, 3028], + [3091, 3028], + [3091, 3032], + [3090, 3032], +] + +[[area.polygons]] +vertices = [ + [3116, 3029], + [3116, 3035], +] + +[[area.polygons]] +vertices = [ + [3117, 3030], + [3117, 3035], +] + +[[area.polygons]] +vertices = [ + [3118, 3031], + [3118, 3034], +] + +[[area.polygons]] +vertices = [[3101, 3034]] + +[[area.polygons]] +vertices = [ + [3090, 3035], + [3095, 3035], + [3095, 3043], + [3090, 3043], +] + +[[area.polygons]] +vertices = [ + [3088, 3036], + [3089, 3036], + [3089, 3042], + [3088, 3042], +] + +[[area.polygons]] +vertices = [ + [3096, 3036], + [3097, 3036], + [3097, 3043], + [3096, 3043], +] + +[[area.polygons]] +vertices = [ + [3103, 3036], + [3110, 3036], +] + +[[area.polygons]] +vertices = [ + [3087, 3037], + [3087, 3038], +] + +[[area.polygons]] +vertices = [ + [3098, 3037], + [3098, 3042], +] + +[[area.polygons]] +vertices = [ + [3106, 3037], + [3107, 3037], + [3107, 3046], + [3106, 3046], +] + +[[area.polygons]] +vertices = [ + [3099, 3039], + [3105, 3039], + [3105, 3040], + [3099, 3040], +] + +[[area.polygons]] +vertices = [ + [3108, 3039], + [3111, 3039], + [3111, 3040], + [3108, 3040], +] + +[[area.polygons]] +vertices = [ + [3102, 3045], + [3105, 3045], + [3105, 3046], + [3102, 3046], +] + +[[area.polygons]] +vertices = [ + [3108, 3045], + [3114, 3045], + [3114, 3046], + [3108, 3046], +] + +[[area.polygons]] +vertices = [ + [3111, 3055], + [3112, 3055], + [3112, 3132], + [3111, 3132], +] + +[[area.polygons]] +vertices = [ + [3086, 3057], + [3087, 3057], + [3087, 3134], + [3086, 3134], +] + +[[area.polygons]] +vertices = [ + [3083, 3058], + [3085, 3058], + [3085, 3133], + [3083, 3133], +] + +[[area.polygons]] +vertices = [ + [3088, 3058], + [3089, 3058], + [3089, 3133], + [3088, 3133], +] + +[[area.polygons]] +vertices = [ + [3081, 3059], + [3082, 3059], + [3082, 3132], + [3081, 3132], +] + +[[area.polygons]] +vertices = [ + [3090, 3059], + [3092, 3059], + [3092, 3133], + [3090, 3133], +] + +[[area.polygons]] +vertices = [ + [3080, 3060], + [3080, 3133], +] + +[[area.polygons]] +vertices = [ + [3093, 3060], + [3093, 3133], +] + +[[area.polygons]] +vertices = [ + [3067, 3061], + [3069, 3061], + [3069, 3122], + [3067, 3122], +] + +[[area.polygons]] +vertices = [ + [3079, 3061], + [3079, 3133], +] + +[[area.polygons]] +vertices = [ + [3094, 3061], + [3094, 3133], +] + +[[area.polygons]] +vertices = [ + [3110, 3061], + [3110, 3131], +] + +[[area.polygons]] +vertices = [ + [3113, 3061], + [3115, 3061], + [3115, 3131], + [3113, 3131], +] + +[[area.polygons]] +vertices = [ + [3066, 3062], + [3066, 3121], +] + +[[area.polygons]] +vertices = [ + [3070, 3062], + [3070, 3134], +] + +[[area.polygons]] +vertices = [ + [3078, 3062], + [3078, 3133], +] + +[[area.polygons]] +vertices = [ + [3095, 3062], + [3096, 3062], + [3096, 3132], + [3095, 3132], +] + +[[area.polygons]] +vertices = [ + [3109, 3062], + [3109, 3131], +] + +[[area.polygons]] +vertices = [ + [3116, 3062], + [3118, 3062], + [3118, 3132], + [3116, 3132], +] + +[[area.polygons]] +vertices = [ + [3064, 3063], + [3065, 3063], + [3065, 3077], + [3064, 3077], +] + +[[area.polygons]] +vertices = [ + [3077, 3063], + [3077, 3134], +] + +[[area.polygons]] +vertices = [ + [3097, 3063], + [3097, 3132], +] + +[[area.polygons]] +vertices = [ + [3108, 3063], + [3108, 3131], +] + +[[area.polygons]] +vertices = [ + [3119, 3063], + [3119, 3133], +] + +[[area.polygons]] +vertices = [ + [3063, 3064], + [3063, 3070], +] + +[[area.polygons]] +vertices = [ + [3071, 3064], + [3072, 3064], + [3072, 3134], + [3071, 3134], +] + +[[area.polygons]] +vertices = [ + [3076, 3064], + [3076, 3134], +] + +[[area.polygons]] +vertices = [ + [3107, 3064], + [3107, 3130], +] + +[[area.polygons]] +vertices = [ + [3120, 3064], + [3120, 3133], +] + +[[area.polygons]] +vertices = [ + [3073, 3065], + [3075, 3065], + [3075, 3134], + [3073, 3134], +] + +[[area.polygons]] +vertices = [ + [3098, 3065], + [3098, 3132], +] + +[[area.polygons]] +vertices = [ + [3105, 3065], + [3106, 3065], + [3106, 3130], + [3105, 3130], +] + +[[area.polygons]] +vertices = [ + [3121, 3065], + [3122, 3065], + [3122, 3133], + [3121, 3133], +] + +[[area.polygons]] +vertices = [ + [3062, 3066], + [3062, 3068], +] + +[[area.polygons]] +vertices = [ + [3099, 3066], + [3104, 3066], + [3104, 3130], + [3099, 3130], +] + +[[area.polygons]] +vertices = [ + [3123, 3066], + [3123, 3133], +] + +[[area.polygons]] +vertices = [ + [3124, 3068], + [3126, 3068], + [3126, 3133], + [3124, 3133], +] + +[[area.polygons]] +vertices = [ + [3127, 3069], + [3130, 3069], + [3130, 3133], + [3127, 3133], +] + +[[area.polygons]] +vertices = [ + [3131, 3070], + [3132, 3070], + [3132, 3134], + [3131, 3134], +] + +[[area.polygons]] +vertices = [ + [3133, 3071], + [3133, 3134], +] + +[[area.polygons]] +vertices = [ + [3134, 3072], + [3134, 3134], +] + +[[area.polygons]] +vertices = [ + [3063, 3073], + [3063, 3074], +] + +[[area.polygons]] +vertices = [ + [3135, 3073], + [3146, 3073], + [3146, 3114], + [3135, 3114], +] + +[[area.polygons]] +vertices = [ + [3147, 3074], + [3147, 3113], +] + +[[area.polygons]] +vertices = [ + [3148, 3075], + [3153, 3075], + [3153, 3084], + [3148, 3084], +] + +[[area.polygons]] +vertices = [ + [3154, 3076], + [3154, 3082], +] + +[[area.polygons]] +vertices = [ + [3155, 3077], + [3155, 3080], +] + +[[area.polygons]] +vertices = [ + [3065, 3078], + [3065, 3079], +] + +[[area.polygons]] +vertices = [ + [3065, 3083], + [3065, 3120], +] + +[[area.polygons]] +vertices = [ + [3064, 3085], + [3064, 3106], +] + +[[area.polygons]] +vertices = [ + [3148, 3085], + [3152, 3085], + [3152, 3087], + [3148, 3087], +] + +[[area.polygons]] +vertices = [ + [3063, 3087], + [3063, 3104], +] + +[[area.polygons]] +vertices = [ + [3062, 3088], + [3062, 3103], +] + +[[area.polygons]] +vertices = [ + [3148, 3088], + [3151, 3088], + [3151, 3092], + [3148, 3092], +] + +[[area.polygons]] +vertices = [ + [3060, 3089], + [3061, 3089], + [3061, 3102], + [3060, 3102], +] + +[[area.polygons]] +vertices = [ + [3059, 3090], + [3059, 3102], +] + +[[area.polygons]] +vertices = [ + [3058, 3091], + [3058, 3101], +] + +[[area.polygons]] +vertices = [ + [3057, 3092], + [3057, 3101], +] + +[[area.polygons]] +vertices = [ + [3056, 3093], + [3056, 3100], +] + +[[area.polygons]] +vertices = [ + [3148, 3093], + [3150, 3093], + [3150, 3094], + [3148, 3094], +] + +[[area.polygons]] +vertices = [ + [3055, 3094], + [3055, 3099], +] + +[[area.polygons]] +vertices = [ + [3148, 3095], + [3149, 3095], +] + +[[area.polygons]] +vertices = [ + [3054, 3096], + [3054, 3097], +] + +[[area.polygons]] +vertices = [ + [3148, 3096], + [3148, 3111], +] + +[[area.polygons]] +vertices = [ + [3149, 3100], + [3149, 3108], +] + +[[area.polygons]] +vertices = [ + [3150, 3102], + [3150, 3107], +] + +[[area.polygons]] +vertices = [[3061, 3103]] + +[[area.polygons]] +vertices = [ + [3064, 3110], + [3064, 3118], +] + +[[area.polygons]] +vertices = [ + [3135, 3115], + [3145, 3115], + [3145, 3116], + [3135, 3116], +] + +[[area.polygons]] +vertices = [ + [3135, 3117], + [3144, 3117], +] + +[[area.polygons]] +vertices = [ + [3135, 3118], + [3143, 3118], + [3143, 3119], + [3135, 3119], +] + +[[area.polygons]] +vertices = [ + [3135, 3120], + [3142, 3120], + [3142, 3129], + [3135, 3129], +] + +[[area.polygons]] +vertices = [ + [3068, 3123], + [3069, 3123], +] + +[[area.polygons]] +vertices = [[3069, 3124]] + +[[area.polygons]] +vertices = [ + [3143, 3124], + [3143, 3128], +] + +[[area.polygons]] +vertices = [ + [3069, 3130], + [3069, 3133], +] + +[[area.polygons]] +vertices = [ + [3135, 3130], + [3140, 3130], +] + +[[area.polygons]] +vertices = [ + [3068, 3131], + [3068, 3133], +] + +[[area.polygons]] +vertices = [ + [3099, 3131], + [3102, 3131], +] + +[[area.polygons]] +vertices = [ + [3135, 3131], + [3139, 3131], +] + +[[area.polygons]] +vertices = [[3099, 3132]] + +[[area.polygons]] +vertices = [[3113, 3132]] + +[[area.polygons]] +vertices = [[3115, 3132]] + +[[area.polygons]] +vertices = [ + [3135, 3132], + [3138, 3132], +] + +[[area.polygons]] +vertices = [[3082, 3133]] + +[[area.polygons]] +vertices = [[3095, 3133]] + +[[area.polygons]] +vertices = [[3118, 3133]] + +[[area.polygons]] +vertices = [ + [3135, 3133], + [3137, 3133], +] + +[[area.polygons]] +vertices = [[3085, 3134]] + +[[area.polygons]] +vertices = [[3088, 3134]] + +[[area.polygons]] +vertices = [ + [3128, 3134], + [3130, 3134], +] + +[[area.polygons]] +vertices = [[3135, 3134]] diff --git a/.data/raw-cache/map/area/night_of_the_vampyre.toml b/.data/raw-cache/map/area/night_of_the_vampyre.toml new file mode 100644 index 000000000..fa5f7a2b7 --- /dev/null +++ b/.data/raw-cache/map/area/night_of_the_vampyre.toml @@ -0,0 +1,300 @@ +# Night of the Vampyre music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Lament of Meiyerditch; The Last Shanty; Night of the Vampyre. +# Bounds x 3520-3671, z 3160-3400; 14738 tiles as 41 rectangles. + +[[area]] +name = "Night of the Vampyre" +area_id = "area.night_of_the_vampyre" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3585, 3160], + [3647, 3160], + [3647, 3199], + [3585, 3199], +] + +[[area.polygons]] +vertices = [ + [3585, 3200], + [3641, 3200], +] + +[[area.polygons]] +vertices = [ + [3585, 3201], + [3640, 3201], +] + +[[area.polygons]] +vertices = [ + [3585, 3202], + [3639, 3202], +] + +[[area.polygons]] +vertices = [ + [3585, 3203], + [3638, 3203], +] + +[[area.polygons]] +vertices = [ + [3585, 3204], + [3637, 3204], + [3637, 3313], + [3585, 3313], +] + +[[area.polygons]] +vertices = [ + [3638, 3235], + [3638, 3331], +] + +[[area.polygons]] +vertices = [ + [3639, 3236], + [3639, 3331], +] + +[[area.polygons]] +vertices = [ + [3640, 3237], + [3640, 3331], +] + +[[area.polygons]] +vertices = [ + [3641, 3238], + [3641, 3331], +] + +[[area.polygons]] +vertices = [ + [3642, 3239], + [3647, 3239], + [3647, 3331], + [3642, 3331], +] + +[[area.polygons]] +vertices = [ + [3585, 3314], + [3593, 3314], + [3593, 3332], + [3585, 3332], +] + +[[area.polygons]] +vertices = [ + [3615, 3314], + [3637, 3314], + [3637, 3331], + [3615, 3331], +] + +[[area.polygons]] +vertices = [ + [3522, 3329], + [3584, 3329], + [3584, 3348], + [3522, 3348], +] + +[[area.polygons]] +vertices = [ + [3648, 3329], + [3663, 3329], + [3663, 3331], + [3648, 3331], +] + +[[area.polygons]] +vertices = [ + [3521, 3330], + [3521, 3391], +] + +[[area.polygons]] +vertices = [ + [3520, 3331], + [3520, 3391], +] + +[[area.polygons]] +vertices = [ + [3661, 3332], + [3663, 3332], + [3663, 3335], + [3661, 3335], +] + +[[area.polygons]] +vertices = [ + [3585, 3333], + [3587, 3333], + [3587, 3400], + [3585, 3400], +] + +[[area.polygons]] +vertices = [ + [3664, 3333], + [3668, 3333], + [3668, 3335], + [3664, 3335], +] + +[[area.polygons]] +vertices = [ + [3666, 3336], + [3668, 3336], + [3668, 3350], + [3666, 3350], +] + +[[area.polygons]] +vertices = [ + [3669, 3348], + [3671, 3348], + [3671, 3385], + [3669, 3385], +] + +[[area.polygons]] +vertices = [ + [3522, 3349], + [3560, 3349], + [3560, 3391], + [3522, 3391], +] + +[[area.polygons]] +vertices = [ + [3571, 3349], + [3584, 3349], +] + +[[area.polygons]] +vertices = [ + [3572, 3350], + [3584, 3350], +] + +[[area.polygons]] +vertices = [ + [3588, 3350], + [3589, 3350], + [3589, 3366], + [3588, 3366], +] + +[[area.polygons]] +vertices = [ + [3573, 3351], + [3584, 3351], +] + +[[area.polygons]] +vertices = [ + [3574, 3352], + [3584, 3352], +] + +[[area.polygons]] +vertices = [ + [3579, 3353], + [3584, 3353], + [3584, 3391], + [3579, 3391], +] + +[[area.polygons]] +vertices = [ + [3574, 3364], + [3578, 3364], + [3578, 3391], + [3574, 3391], +] + +[[area.polygons]] +vertices = [ + [3573, 3365], + [3573, 3391], +] + +[[area.polygons]] +vertices = [ + [3572, 3366], + [3572, 3391], +] + +[[area.polygons]] +vertices = [ + [3571, 3367], + [3571, 3391], +] + +[[area.polygons]] +vertices = [ + [3561, 3368], + [3570, 3368], + [3570, 3391], + [3561, 3391], +] + +[[area.polygons]] +vertices = [ + [3661, 3383], + [3668, 3383], + [3668, 3385], + [3661, 3385], +] + +[[area.polygons]] +vertices = [ + [3588, 3384], + [3592, 3384], + [3592, 3400], + [3588, 3400], +] + +[[area.polygons]] +vertices = [ + [3661, 3386], + [3663, 3386], + [3663, 3393], + [3661, 3393], +] + +[[area.polygons]] +vertices = [ + [3635, 3391], + [3660, 3391], + [3660, 3393], + [3635, 3393], +] + +[[area.polygons]] +vertices = [ + [3584, 3392], + [3584, 3400], +] + +[[area.polygons]] +vertices = [ + [3635, 3394], + [3637, 3394], + [3637, 3400], + [3635, 3400], +] + +[[area.polygons]] +vertices = [ + [3593, 3398], + [3634, 3398], + [3634, 3400], + [3593, 3400], +] diff --git a/.data/raw-cache/map/area/no_pasaran.toml b/.data/raw-cache/map/area/no_pasaran.toml new file mode 100644 index 000000000..6de167733 --- /dev/null +++ b/.data/raw-cache/map/area/no_pasaran.toml @@ -0,0 +1,16 @@ +# No Pasaran music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: No Pasaran. +# Bounds x 2432-2495, z 6080-6207; 8192 tiles as 1 rectangles. + +[[area]] +name = "No Pasaran" +area_id = "area.no_pasaran" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2432, 6080], + [2495, 6080], + [2495, 6207], + [2432, 6207], +] diff --git a/.data/raw-cache/map/area/no_way_out.toml b/.data/raw-cache/map/area/no_way_out.toml new file mode 100644 index 000000000..09b3b3340 --- /dev/null +++ b/.data/raw-cache/map/area/no_way_out.toml @@ -0,0 +1,32 @@ +# No Way Out music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: No Way Out. +# Bounds x 3008-3295, z 5184-9823; 3072 tiles as 3 rectangles. + +[[area]] +name = "No Way Out" +area_id = "area.no_way_out" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 5184], + [3039, 5184], + [3039, 5215], + [3008, 5215], +] + +[[area.polygons]] +vertices = [ + [3072, 5184], + [3103, 5184], + [3103, 5215], + [3072, 5215], +] + +[[area.polygons]] +vertices = [ + [3264, 9792], + [3295, 9792], + [3295, 9823], + [3264, 9823], +] diff --git a/.data/raw-cache/map/area/nomad.toml b/.data/raw-cache/map/area/nomad.toml new file mode 100644 index 000000000..11d7b5c88 --- /dev/null +++ b/.data/raw-cache/map/area/nomad.toml @@ -0,0 +1,467 @@ +# Nomad music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Nomad. +# Bounds x 3129-3194, z 2812-2874; 2380 tiles as 68 rectangles. + +[[area]] +name = "Nomad" +area_id = "area.nomad" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3154, 2812], + [3157, 2812], + [3157, 2851], + [3154, 2851], +] + +[[area.polygons]] +vertices = [ + [3144, 2813], + [3153, 2813], + [3153, 2814], + [3144, 2814], +] + +[[area.polygons]] +vertices = [ + [3158, 2813], + [3158, 2874], +] + +[[area.polygons]] +vertices = [ + [3145, 2815], + [3153, 2815], + [3153, 2816], + [3145, 2816], +] + +[[area.polygons]] +vertices = [ + [3159, 2816], + [3159, 2874], +] + +[[area.polygons]] +vertices = [ + [3161, 2816], + [3163, 2816], + [3163, 2874], + [3161, 2874], +] + +[[area.polygons]] +vertices = [ + [3146, 2817], + [3153, 2817], + [3153, 2818], + [3146, 2818], +] + +[[area.polygons]] +vertices = [ + [3160, 2817], + [3160, 2874], +] + +[[area.polygons]] +vertices = [ + [3164, 2817], + [3172, 2817], + [3172, 2874], + [3164, 2874], +] + +[[area.polygons]] +vertices = [ + [3173, 2818], + [3173, 2874], +] + +[[area.polygons]] +vertices = [ + [3147, 2819], + [3153, 2819], + [3153, 2822], + [3147, 2822], +] + +[[area.polygons]] +vertices = [ + [3174, 2819], + [3175, 2819], + [3175, 2874], + [3174, 2874], +] + +[[area.polygons]] +vertices = [ + [3176, 2821], + [3178, 2821], + [3178, 2860], + [3176, 2860], +] + +[[area.polygons]] +vertices = [ + [3179, 2822], + [3181, 2822], + [3181, 2858], + [3179, 2858], +] + +[[area.polygons]] +vertices = [ + [3148, 2823], + [3153, 2823], +] + +[[area.polygons]] +vertices = [ + [3182, 2823], + [3182, 2857], +] + +[[area.polygons]] +vertices = [ + [3145, 2824], + [3146, 2824], + [3146, 2828], + [3145, 2828], +] + +[[area.polygons]] +vertices = [ + [3149, 2824], + [3153, 2824], +] + +[[area.polygons]] +vertices = [ + [3183, 2824], + [3183, 2857], +] + +[[area.polygons]] +vertices = [ + [3150, 2825], + [3153, 2825], +] + +[[area.polygons]] +vertices = [ + [3131, 2826], + [3132, 2826], + [3132, 2848], + [3131, 2848], +] + +[[area.polygons]] +vertices = [ + [3147, 2826], + [3149, 2826], + [3149, 2828], + [3147, 2828], +] + +[[area.polygons]] +vertices = [ + [3151, 2826], + [3153, 2826], + [3153, 2828], + [3151, 2828], +] + +[[area.polygons]] +vertices = [ + [3184, 2826], + [3185, 2826], + [3185, 2856], + [3184, 2856], +] + +[[area.polygons]] +vertices = [ + [3130, 2827], + [3130, 2848], +] + +[[area.polygons]] +vertices = [ + [3133, 2827], + [3133, 2848], +] + +[[area.polygons]] +vertices = [ + [3143, 2827], + [3144, 2827], + [3144, 2828], + [3143, 2828], +] + +[[area.polygons]] +vertices = [ + [3150, 2827], + [3150, 2828], +] + +[[area.polygons]] +vertices = [ + [3186, 2827], + [3186, 2853], +] + +[[area.polygons]] +vertices = [ + [3129, 2828], + [3129, 2848], +] + +[[area.polygons]] +vertices = [ + [3134, 2828], + [3134, 2848], +] + +[[area.polygons]] +vertices = [ + [3187, 2828], + [3187, 2852], +] + +[[area.polygons]] +vertices = [ + [3135, 2829], + [3135, 2848], +] + +[[area.polygons]] +vertices = [ + [3152, 2829], + [3153, 2829], + [3153, 2830], + [3152, 2830], +] + +[[area.polygons]] +vertices = [ + [3153, 2831], + [3153, 2849], +] + +[[area.polygons]] +vertices = [ + [3188, 2832], + [3188, 2852], +] + +[[area.polygons]] +vertices = [ + [3152, 2835], + [3152, 2842], +] + +[[area.polygons]] +vertices = [ + [3189, 2835], + [3189, 2851], +] + +[[area.polygons]] +vertices = [ + [3190, 2837], + [3192, 2837], + [3192, 2850], + [3190, 2850], +] + +[[area.polygons]] +vertices = [ + [3136, 2839], + [3151, 2839], + [3151, 2840], + [3136, 2840], +] + +[[area.polygons]] +vertices = [ + [3193, 2840], + [3193, 2845], +] + +[[area.polygons]] +vertices = [ + [3136, 2841], + [3140, 2841], + [3140, 2842], + [3136, 2842], +] + +[[area.polygons]] +vertices = [ + [3148, 2841], + [3149, 2841], + [3149, 2844], + [3148, 2844], +] + +[[area.polygons]] +vertices = [ + [3194, 2841], + [3194, 2844], +] + +[[area.polygons]] +vertices = [ + [3155, 2852], + [3157, 2852], + [3157, 2872], + [3155, 2872], +] + +[[area.polygons]] +vertices = [ + [3154, 2855], + [3154, 2871], +] + +[[area.polygons]] +vertices = [ + [3153, 2856], + [3153, 2871], +] + +[[area.polygons]] +vertices = [ + [3152, 2857], + [3152, 2870], +] + +[[area.polygons]] +vertices = [[3184, 2857]] + +[[area.polygons]] +vertices = [ + [3151, 2858], + [3151, 2869], +] + +[[area.polygons]] +vertices = [ + [3150, 2859], + [3150, 2868], +] + +[[area.polygons]] +vertices = [ + [3179, 2859], + [3180, 2859], +] + +[[area.polygons]] +vertices = [ + [3149, 2860], + [3149, 2867], +] + +[[area.polygons]] +vertices = [ + [3148, 2861], + [3148, 2863], +] + +[[area.polygons]] +vertices = [ + [3147, 2862], + [3147, 2863], +] + +[[area.polygons]] +vertices = [ + [3176, 2862], + [3176, 2874], +] + +[[area.polygons]] +vertices = [[3146, 2863]] + +[[area.polygons]] +vertices = [ + [3177, 2863], + [3178, 2863], + [3178, 2874], + [3177, 2874], +] + +[[area.polygons]] +vertices = [ + [3179, 2864], + [3180, 2864], + [3180, 2874], + [3179, 2874], +] + +[[area.polygons]] +vertices = [ + [3181, 2865], + [3182, 2865], + [3182, 2874], + [3181, 2874], +] + +[[area.polygons]] +vertices = [ + [3183, 2866], + [3184, 2866], + [3184, 2874], + [3183, 2874], +] + +[[area.polygons]] +vertices = [ + [3185, 2867], + [3186, 2867], + [3186, 2874], + [3185, 2874], +] + +[[area.polygons]] +vertices = [ + [3187, 2868], + [3188, 2868], + [3188, 2874], + [3187, 2874], +] + +[[area.polygons]] +vertices = [ + [3189, 2869], + [3190, 2869], + [3190, 2874], + [3189, 2874], +] + +[[area.polygons]] +vertices = [ + [3191, 2870], + [3192, 2870], + [3192, 2874], + [3191, 2874], +] + +[[area.polygons]] +vertices = [ + [3193, 2871], + [3193, 2874], +] + +[[area.polygons]] +vertices = [ + [3156, 2873], + [3157, 2873], +] + +[[area.polygons]] +vertices = [[3157, 2874]] diff --git a/.data/raw-cache/map/area/norse_code.toml b/.data/raw-cache/map/area/norse_code.toml new file mode 100644 index 000000000..ce839c45b --- /dev/null +++ b/.data/raw-cache/map/area/norse_code.toml @@ -0,0 +1,408 @@ +# Norse Code music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Norse Code. +# Bounds x 2370-2428, z 3779-3841; 1955 tiles as 64 rectangles. + +[[area]] +name = "Norse Code" +area_id = "area.norse_code" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2402, 3779], + [2404, 3779], + [2404, 3823], + [2402, 3823], +] + +[[area.polygons]] +vertices = [ + [2405, 3780], + [2405, 3827], +] + +[[area.polygons]] +vertices = [ + [2406, 3781], + [2423, 3781], + [2423, 3782], + [2406, 3782], +] + +[[area.polygons]] +vertices = [ + [2401, 3784], + [2401, 3822], +] + +[[area.polygons]] +vertices = [ + [2406, 3784], + [2407, 3784], + [2407, 3829], + [2406, 3829], +] + +[[area.polygons]] +vertices = [ + [2400, 3785], + [2400, 3822], +] + +[[area.polygons]] +vertices = [ + [2408, 3785], + [2408, 3829], +] + +[[area.polygons]] +vertices = [ + [2379, 3786], + [2381, 3786], + [2381, 3804], + [2379, 3804], +] + +[[area.polygons]] +vertices = [ + [2394, 3786], + [2394, 3819], +] + +[[area.polygons]] +vertices = [ + [2397, 3786], + [2399, 3786], + [2399, 3820], + [2397, 3820], +] + +[[area.polygons]] +vertices = [ + [2409, 3786], + [2410, 3786], + [2410, 3829], + [2409, 3829], +] + +[[area.polygons]] +vertices = [ + [2378, 3787], + [2378, 3804], +] + +[[area.polygons]] +vertices = [ + [2382, 3787], + [2384, 3787], + [2384, 3809], + [2382, 3809], +] + +[[area.polygons]] +vertices = [ + [2392, 3787], + [2393, 3787], + [2393, 3817], + [2392, 3817], +] + +[[area.polygons]] +vertices = [ + [2395, 3787], + [2396, 3787], + [2396, 3820], + [2395, 3820], +] + +[[area.polygons]] +vertices = [ + [2411, 3787], + [2412, 3787], + [2412, 3830], + [2411, 3830], +] + +[[area.polygons]] +vertices = [ + [2377, 3788], + [2377, 3804], +] + +[[area.polygons]] +vertices = [ + [2385, 3788], + [2385, 3813], +] + +[[area.polygons]] +vertices = [ + [2389, 3788], + [2391, 3788], + [2391, 3817], + [2389, 3817], +] + +[[area.polygons]] +vertices = [ + [2413, 3788], + [2413, 3831], +] + +[[area.polygons]] +vertices = [ + [2388, 3789], + [2388, 3817], +] + +[[area.polygons]] +vertices = [ + [2376, 3790], + [2376, 3805], +] + +[[area.polygons]] +vertices = [ + [2386, 3790], + [2387, 3790], + [2387, 3814], + [2386, 3814], +] + +[[area.polygons]] +vertices = [ + [2414, 3790], + [2414, 3832], +] + +[[area.polygons]] +vertices = [ + [2418, 3790], + [2422, 3790], + [2422, 3828], + [2418, 3828], +] + +[[area.polygons]] +vertices = [ + [2375, 3791], + [2375, 3806], +] + +[[area.polygons]] +vertices = [ + [2423, 3791], + [2423, 3827], +] + +[[area.polygons]] +vertices = [ + [2374, 3792], + [2374, 3806], +] + +[[area.polygons]] +vertices = [ + [2415, 3792], + [2417, 3792], + [2417, 3833], + [2415, 3833], +] + +[[area.polygons]] +vertices = [ + [2424, 3792], + [2425, 3792], + [2425, 3817], + [2424, 3817], +] + +[[area.polygons]] +vertices = [ + [2373, 3793], + [2373, 3805], +] + +[[area.polygons]] +vertices = [ + [2426, 3793], + [2427, 3793], + [2427, 3794], + [2426, 3794], +] + +[[area.polygons]] +vertices = [ + [2426, 3795], + [2426, 3796], +] + +[[area.polygons]] +vertices = [ + [2372, 3796], + [2372, 3805], +] + +[[area.polygons]] +vertices = [ + [2371, 3797], + [2371, 3804], +] + +[[area.polygons]] +vertices = [ + [2370, 3798], + [2370, 3804], +] + +[[area.polygons]] +vertices = [ + [2426, 3798], + [2426, 3801], +] + +[[area.polygons]] +vertices = [ + [2426, 3803], + [2426, 3810], +] + +[[area.polygons]] +vertices = [ + [2380, 3805], + [2381, 3805], +] + +[[area.polygons]] +vertices = [ + [2427, 3805], + [2427, 3807], +] + +[[area.polygons]] +vertices = [[2381, 3806]] + +[[area.polygons]] +vertices = [[2428, 3806]] + +[[area.polygons]] +vertices = [ + [2383, 3810], + [2384, 3810], + [2384, 3811], + [2383, 3811], +] + +[[area.polygons]] +vertices = [[2384, 3812]] + +[[area.polygons]] +vertices = [ + [2387, 3815], + [2387, 3816], +] + +[[area.polygons]] +vertices = [[2393, 3818]] + +[[area.polygons]] +vertices = [ + [2424, 3818], + [2424, 3828], +] + +[[area.polygons]] +vertices = [ + [2425, 3819], + [2425, 3828], +] + +[[area.polygons]] +vertices = [[2399, 3821]] + +[[area.polygons]] +vertices = [ + [2403, 3824], + [2404, 3824], +] + +[[area.polygons]] +vertices = [ + [2426, 3824], + [2426, 3828], +] + +[[area.polygons]] +vertices = [[2404, 3825]] + +[[area.polygons]] +vertices = [ + [2427, 3825], + [2427, 3827], +] + +[[area.polygons]] +vertices = [ + [2418, 3829], + [2421, 3829], +] + +[[area.polygons]] +vertices = [[2410, 3830]] + +[[area.polygons]] +vertices = [ + [2418, 3830], + [2420, 3830], + [2420, 3837], + [2418, 3837], +] + +[[area.polygons]] +vertices = [ + [2421, 3831], + [2421, 3837], +] + +[[area.polygons]] +vertices = [ + [2422, 3832], + [2422, 3837], +] + +[[area.polygons]] +vertices = [ + [2423, 3833], + [2423, 3838], +] + +[[area.polygons]] +vertices = [ + [2416, 3834], + [2417, 3834], + [2417, 3837], + [2416, 3837], +] + +[[area.polygons]] +vertices = [ + [2424, 3834], + [2424, 3838], +] + +[[area.polygons]] +vertices = [[2415, 3836]] + +[[area.polygons]] +vertices = [ + [2425, 3836], + [2425, 3838], +] + +[[area.polygons]] +vertices = [ + [2419, 3838], + [2420, 3838], + [2420, 3841], + [2419, 3841], +] diff --git a/.data/raw-cache/map/area/not_a_moment_of_relief.toml b/.data/raw-cache/map/area/not_a_moment_of_relief.toml new file mode 100644 index 000000000..6a9f2f9c8 --- /dev/null +++ b/.data/raw-cache/map/area/not_a_moment_of_relief.toml @@ -0,0 +1,540 @@ +# Not a Moment of Relief music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Not a Moment of Relief. +# Bounds x 1600-1791, z 3814-4015; 12724 tiles as 82 rectangles. + +[[area]] +name = "Not a Moment of Relief" +area_id = "area.not_a_moment_of_relief" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1727, 3814], + [1791, 3814], + [1791, 3867], + [1727, 3867], +] + +[[area.polygons]] +vertices = [ + [1724, 3815], + [1726, 3815], + [1726, 3903], + [1724, 3903], +] + +[[area.polygons]] +vertices = [ + [1721, 3816], + [1723, 3816], + [1723, 3903], + [1721, 3903], +] + +[[area.polygons]] +vertices = [ + [1719, 3817], + [1720, 3817], + [1720, 3903], + [1719, 3903], +] + +[[area.polygons]] +vertices = [ + [1716, 3818], + [1718, 3818], + [1718, 3903], + [1716, 3903], +] + +[[area.polygons]] +vertices = [ + [1713, 3819], + [1715, 3819], + [1715, 3903], + [1713, 3903], +] + +[[area.polygons]] +vertices = [ + [1710, 3820], + [1712, 3820], + [1712, 3903], + [1710, 3903], +] + +[[area.polygons]] +vertices = [ + [1708, 3821], + [1709, 3821], + [1709, 3903], + [1708, 3903], +] + +[[area.polygons]] +vertices = [ + [1705, 3822], + [1707, 3822], + [1707, 3903], + [1705, 3903], +] + +[[area.polygons]] +vertices = [ + [1702, 3823], + [1704, 3823], + [1704, 3903], + [1702, 3903], +] + +[[area.polygons]] +vertices = [ + [1701, 3824], + [1701, 3903], +] + +[[area.polygons]] +vertices = [ + [1689, 3842], + [1697, 3842], + [1697, 3903], + [1689, 3903], +] + +[[area.polygons]] +vertices = [ + [1688, 3843], + [1688, 3903], +] + +[[area.polygons]] +vertices = [ + [1698, 3843], + [1699, 3843], + [1699, 3903], + [1698, 3903], +] + +[[area.polygons]] +vertices = [ + [1687, 3844], + [1687, 3903], +] + +[[area.polygons]] +vertices = [ + [1700, 3844], + [1700, 3903], +] + +[[area.polygons]] +vertices = [ + [1686, 3845], + [1686, 3903], +] + +[[area.polygons]] +vertices = [ + [1685, 3846], + [1685, 3903], +] + +[[area.polygons]] +vertices = [ + [1684, 3847], + [1684, 3903], +] + +[[area.polygons]] +vertices = [ + [1683, 3848], + [1683, 3903], +] + +[[area.polygons]] +vertices = [ + [1616, 3849], + [1616, 3890], +] + +[[area.polygons]] +vertices = [ + [1682, 3849], + [1682, 3903], +] + +[[area.polygons]] +vertices = [ + [1615, 3850], + [1615, 3890], +] + +[[area.polygons]] +vertices = [ + [1617, 3850], + [1617, 3890], +] + +[[area.polygons]] +vertices = [ + [1681, 3850], + [1681, 3903], +] + +[[area.polygons]] +vertices = [ + [1614, 3851], + [1614, 3890], +] + +[[area.polygons]] +vertices = [ + [1618, 3851], + [1618, 3890], +] + +[[area.polygons]] +vertices = [ + [1679, 3851], + [1680, 3851], + [1680, 3903], + [1679, 3903], +] + +[[area.polygons]] +vertices = [ + [1605, 3852], + [1613, 3852], + [1613, 3890], + [1605, 3890], +] + +[[area.polygons]] +vertices = [ + [1619, 3852], + [1619, 3890], +] + +[[area.polygons]] +vertices = [ + [1678, 3852], + [1678, 3903], +] + +[[area.polygons]] +vertices = [ + [1604, 3853], + [1604, 3890], +] + +[[area.polygons]] +vertices = [ + [1620, 3853], + [1620, 3891], +] + +[[area.polygons]] +vertices = [ + [1669, 3853], + [1677, 3853], + [1677, 3903], + [1669, 3903], +] + +[[area.polygons]] +vertices = [ + [1603, 3854], + [1603, 3890], +] + +[[area.polygons]] +vertices = [ + [1621, 3854], + [1621, 3892], +] + +[[area.polygons]] +vertices = [ + [1668, 3854], + [1668, 3903], +] + +[[area.polygons]] +vertices = [ + [1622, 3855], + [1622, 3893], +] + +[[area.polygons]] +vertices = [ + [1667, 3855], + [1667, 3903], +] + +[[area.polygons]] +vertices = [ + [1623, 3856], + [1666, 3856], + [1666, 3894], + [1623, 3894], +] + +[[area.polygons]] +vertices = [ + [1727, 3868], + [1790, 3868], +] + +[[area.polygons]] +vertices = [ + [1727, 3869], + [1789, 3869], +] + +[[area.polygons]] +vertices = [ + [1727, 3870], + [1788, 3870], +] + +[[area.polygons]] +vertices = [ + [1727, 3871], + [1787, 3871], +] + +[[area.polygons]] +vertices = [ + [1727, 3872], + [1786, 3872], +] + +[[area.polygons]] +vertices = [ + [1727, 3873], + [1785, 3873], +] + +[[area.polygons]] +vertices = [ + [1727, 3874], + [1784, 3874], +] + +[[area.polygons]] +vertices = [ + [1727, 3875], + [1783, 3875], +] + +[[area.polygons]] +vertices = [ + [1600, 3876], + [1602, 3876], + [1602, 3890], + [1600, 3890], +] + +[[area.polygons]] +vertices = [ + [1727, 3876], + [1782, 3876], +] + +[[area.polygons]] +vertices = [ + [1727, 3877], + [1781, 3877], +] + +[[area.polygons]] +vertices = [ + [1727, 3878], + [1780, 3878], +] + +[[area.polygons]] +vertices = [ + [1727, 3879], + [1779, 3879], +] + +[[area.polygons]] +vertices = [ + [1727, 3880], + [1778, 3880], +] + +[[area.polygons]] +vertices = [ + [1727, 3881], + [1777, 3881], +] + +[[area.polygons]] +vertices = [ + [1727, 3882], + [1776, 3882], +] + +[[area.polygons]] +vertices = [ + [1727, 3883], + [1775, 3883], +] + +[[area.polygons]] +vertices = [ + [1727, 3884], + [1774, 3884], +] + +[[area.polygons]] +vertices = [ + [1727, 3885], + [1773, 3885], +] + +[[area.polygons]] +vertices = [ + [1727, 3886], + [1772, 3886], + [1772, 3903], + [1727, 3903], +] + +[[area.polygons]] +vertices = [ + [1624, 3895], + [1666, 3895], +] + +[[area.polygons]] +vertices = [ + [1625, 3896], + [1666, 3896], +] + +[[area.polygons]] +vertices = [ + [1626, 3897], + [1666, 3897], +] + +[[area.polygons]] +vertices = [ + [1627, 3898], + [1666, 3898], +] + +[[area.polygons]] +vertices = [ + [1628, 3899], + [1666, 3899], +] + +[[area.polygons]] +vertices = [ + [1629, 3900], + [1666, 3900], +] + +[[area.polygons]] +vertices = [ + [1630, 3901], + [1666, 3901], +] + +[[area.polygons]] +vertices = [ + [1631, 3902], + [1666, 3902], +] + +[[area.polygons]] +vertices = [ + [1632, 3903], + [1666, 3903], +] + +[[area.polygons]] +vertices = [ + [1728, 3904], + [1772, 3904], + [1772, 3907], + [1728, 3907], +] + +[[area.polygons]] +vertices = [ + [1743, 4002], + [1746, 4002], + [1746, 4014], + [1743, 4014], +] + +[[area.polygons]] +vertices = [ + [1742, 4003], + [1742, 4013], +] + +[[area.polygons]] +vertices = [ + [1747, 4003], + [1751, 4003], + [1751, 4014], + [1747, 4014], +] + +[[area.polygons]] +vertices = [ + [1740, 4004], + [1741, 4004], + [1741, 4011], + [1740, 4011], +] + +[[area.polygons]] +vertices = [ + [1752, 4004], + [1753, 4004], + [1753, 4013], + [1752, 4013], +] + +[[area.polygons]] +vertices = [ + [1739, 4005], + [1739, 4009], +] + +[[area.polygons]] +vertices = [ + [1754, 4005], + [1754, 4012], +] + +[[area.polygons]] +vertices = [ + [1755, 4006], + [1755, 4011], +] + +[[area.polygons]] +vertices = [ + [1756, 4007], + [1756, 4008], +] + +[[area.polygons]] +vertices = [[1741, 4012]] + +[[area.polygons]] +vertices = [[1752, 4014]] + +[[area.polygons]] +vertices = [ + [1745, 4015], + [1750, 4015], +] diff --git a/.data/raw-cache/map/area/noxious_awakening.toml b/.data/raw-cache/map/area/noxious_awakening.toml new file mode 100644 index 000000000..70b4531a2 --- /dev/null +++ b/.data/raw-cache/map/area/noxious_awakening.toml @@ -0,0 +1,16 @@ +# Noxious Awakening music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Noxious Awakening. +# Bounds x 3584-3647, z 9792-9855; 4096 tiles as 1 rectangles. + +[[area]] +name = "Noxious Awakening" +area_id = "area.noxious_awakening" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3584, 9792], + [3647, 9792], + [3647, 9855], + [3584, 9855], +] diff --git a/.data/raw-cache/map/area/null_and_void.toml b/.data/raw-cache/map/area/null_and_void.toml new file mode 100644 index 000000000..5abfb7da2 --- /dev/null +++ b/.data/raw-cache/map/area/null_and_void.toml @@ -0,0 +1,422 @@ +# Null and Void music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Null and Void. +# Bounds x 2632-2677, z 2636-2679; 1001 tiles as 65 rectangles. + +[[area]] +name = "Null and Void" +area_id = "area.null_and_void" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2657, 2636], + [2657, 2676], +] + +[[area.polygons]] +vertices = [ + [2660, 2638], + [2663, 2638], + [2663, 2643], + [2660, 2643], +] + +[[area.polygons]] +vertices = [ + [2658, 2639], + [2659, 2639], +] + +[[area.polygons]] +vertices = [ + [2644, 2640], + [2644, 2652], +] + +[[area.polygons]] +vertices = [ + [2638, 2642], + [2641, 2642], + [2641, 2647], + [2638, 2647], +] + +[[area.polygons]] +vertices = [ + [2652, 2642], + [2653, 2642], + [2653, 2643], + [2652, 2643], +] + +[[area.polygons]] +vertices = [ + [2670, 2642], + [2672, 2642], + [2672, 2665], + [2670, 2665], +] + +[[area.polygons]] +vertices = [ + [2650, 2643], + [2651, 2643], + [2651, 2669], + [2650, 2669], +] + +[[area.polygons]] +vertices = [ + [2666, 2643], + [2669, 2643], + [2669, 2666], + [2666, 2666], +] + +[[area.polygons]] +vertices = [ + [2673, 2643], + [2674, 2643], + [2674, 2654], + [2673, 2654], +] + +[[area.polygons]] +vertices = [ + [2642, 2644], + [2643, 2644], +] + +[[area.polygons]] +vertices = [ + [2648, 2644], + [2649, 2644], + [2649, 2670], + [2648, 2670], +] + +[[area.polygons]] +vertices = [[2652, 2644]] + +[[area.polygons]] +vertices = [ + [2665, 2644], + [2665, 2664], +] + +[[area.polygons]] +vertices = [ + [2675, 2644], + [2675, 2653], +] + +[[area.polygons]] +vertices = [ + [2664, 2645], + [2664, 2663], +] + +[[area.polygons]] +vertices = [ + [2645, 2646], + [2647, 2646], +] + +[[area.polygons]] +vertices = [ + [2652, 2646], + [2656, 2646], + [2656, 2669], + [2652, 2669], +] + +[[area.polygons]] +vertices = [ + [2659, 2646], + [2663, 2646], + [2663, 2663], + [2659, 2663], +] + +[[area.polygons]] +vertices = [ + [2676, 2646], + [2676, 2651], +] + +[[area.polygons]] +vertices = [ + [2647, 2647], + [2647, 2651], +] + +[[area.polygons]] +vertices = [ + [2658, 2647], + [2658, 2679], +] + +[[area.polygons]] +vertices = [ + [2677, 2647], + [2677, 2649], +] + +[[area.polygons]] +vertices = [ + [2646, 2648], + [2646, 2650], +] + +[[area.polygons]] +vertices = [ + [2632, 2649], + [2635, 2649], + [2635, 2654], + [2632, 2654], +] + +[[area.polygons]] +vertices = [ + [2641, 2649], + [2641, 2652], +] + +[[area.polygons]] +vertices = [[2640, 2650]] + +[[area.polygons]] +vertices = [ + [2642, 2650], + [2642, 2653], +] + +[[area.polygons]] +vertices = [ + [2643, 2651], + [2643, 2652], +] + +[[area.polygons]] +vertices = [ + [2638, 2652], + [2640, 2652], +] + +[[area.polygons]] +vertices = [ + [2636, 2653], + [2638, 2653], +] + +[[area.polygons]] +vertices = [ + [2638, 2654], + [2638, 2657], +] + +[[area.polygons]] +vertices = [ + [2647, 2656], + [2647, 2672], +] + +[[area.polygons]] +vertices = [ + [2646, 2657], + [2646, 2673], +] + +[[area.polygons]] +vertices = [ + [2645, 2658], + [2645, 2674], +] + +[[area.polygons]] +vertices = [ + [2673, 2658], + [2673, 2664], +] + +[[area.polygons]] +vertices = [ + [2644, 2659], + [2644, 2675], +] + +[[area.polygons]] +vertices = [ + [2643, 2660], + [2643, 2669], +] + +[[area.polygons]] +vertices = [ + [2674, 2660], + [2674, 2664], +] + +[[area.polygons]] +vertices = [ + [2642, 2661], + [2642, 2668], +] + +[[area.polygons]] +vertices = [ + [2675, 2661], + [2676, 2661], + [2676, 2662], + [2675, 2662], +] + +[[area.polygons]] +vertices = [ + [2641, 2663], + [2641, 2667], +] + +[[area.polygons]] +vertices = [[2675, 2663]] + +[[area.polygons]] +vertices = [ + [2640, 2664], + [2640, 2666], +] + +[[area.polygons]] +vertices = [ + [2659, 2664], + [2661, 2664], + [2661, 2666], + [2659, 2666], +] + +[[area.polygons]] +vertices = [ + [2670, 2666], + [2671, 2666], + [2671, 2672], + [2670, 2672], +] + +[[area.polygons]] +vertices = [ + [2659, 2667], + [2660, 2667], + [2660, 2668], + [2659, 2668], +] + +[[area.polygons]] +vertices = [ + [2667, 2667], + [2669, 2667], +] + +[[area.polygons]] +vertices = [ + [2668, 2668], + [2669, 2668], + [2669, 2669], + [2668, 2669], +] + +[[area.polygons]] +vertices = [ + [2659, 2669], + [2659, 2679], +] + +[[area.polygons]] +vertices = [ + [2654, 2670], + [2656, 2670], +] + +[[area.polygons]] +vertices = [[2669, 2670]] + +[[area.polygons]] +vertices = [ + [2672, 2670], + [2672, 2674], +] + +[[area.polygons]] +vertices = [[2648, 2671]] + +[[area.polygons]] +vertices = [ + [2655, 2671], + [2656, 2671], + [2656, 2676], + [2655, 2676], +] + +[[area.polygons]] +vertices = [ + [2643, 2672], + [2643, 2676], +] + +[[area.polygons]] +vertices = [ + [2673, 2672], + [2674, 2672], + [2674, 2675], + [2673, 2675], +] + +[[area.polygons]] +vertices = [ + [2651, 2673], + [2654, 2673], + [2654, 2674], + [2651, 2674], +] + +[[area.polygons]] +vertices = [ + [2662, 2673], + [2663, 2673], + [2663, 2679], + [2662, 2679], +] + +[[area.polygons]] +vertices = [ + [2671, 2673], + [2671, 2674], +] + +[[area.polygons]] +vertices = [ + [2675, 2673], + [2675, 2674], +] + +[[area.polygons]] +vertices = [[2642, 2674]] + +[[area.polygons]] +vertices = [ + [2664, 2674], + [2664, 2679], +] + +[[area.polygons]] +vertices = [ + [2651, 2675], + [2652, 2675], + [2652, 2677], + [2651, 2677], +] + +[[area.polygons]] +vertices = [ + [2660, 2676], + [2661, 2676], +] diff --git a/.data/raw-cache/map/area/observatory.toml b/.data/raw-cache/map/area/observatory.toml new file mode 100644 index 000000000..7e6769418 --- /dev/null +++ b/.data/raw-cache/map/area/observatory.toml @@ -0,0 +1,413 @@ +# Observatory music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Soundscape; Serenade; Melodrama. +# Bounds x 2428-2542, z 3064-3239; 13514 tiles as 63 rectangles. + +[[area]] +name = "Observatory" +area_id = "area.observatory" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2456, 3064], + [2495, 3064], + [2495, 3208], + [2456, 3208], +] + +[[area.polygons]] +vertices = [ + [2526, 3064], + [2527, 3064], + [2527, 3140], + [2526, 3140], +] + +[[area.polygons]] +vertices = [ + [2454, 3065], + [2455, 3065], + [2455, 3216], + [2454, 3216], +] + +[[area.polygons]] +vertices = [ + [2524, 3065], + [2525, 3065], + [2525, 3140], + [2524, 3140], +] + +[[area.polygons]] +vertices = [ + [2450, 3066], + [2453, 3066], + [2453, 3216], + [2450, 3216], +] + +[[area.polygons]] +vertices = [ + [2496, 3066], + [2523, 3066], + [2523, 3135], + [2496, 3135], +] + +[[area.polygons]] +vertices = [ + [2446, 3067], + [2449, 3067], + [2449, 3217], + [2446, 3217], +] + +[[area.polygons]] +vertices = [ + [2528, 3067], + [2528, 3140], +] + +[[area.polygons]] +vertices = [ + [2442, 3068], + [2445, 3068], + [2445, 3239], + [2442, 3239], +] + +[[area.polygons]] +vertices = [ + [2529, 3068], + [2529, 3140], +] + +[[area.polygons]] +vertices = [ + [2438, 3069], + [2441, 3069], + [2441, 3236], + [2438, 3236], +] + +[[area.polygons]] +vertices = [ + [2530, 3069], + [2530, 3139], +] + +[[area.polygons]] +vertices = [ + [2433, 3070], + [2437, 3070], + [2437, 3226], + [2433, 3226], +] + +[[area.polygons]] +vertices = [ + [2531, 3070], + [2531, 3139], +] + +[[area.polygons]] +vertices = [ + [2432, 3071], + [2432, 3224], +] + +[[area.polygons]] +vertices = [ + [2532, 3071], + [2532, 3139], +] + +[[area.polygons]] +vertices = [ + [2533, 3072], + [2533, 3082], +] + +[[area.polygons]] +vertices = [ + [2534, 3073], + [2534, 3081], +] + +[[area.polygons]] +vertices = [ + [2535, 3074], + [2535, 3080], +] + +[[area.polygons]] +vertices = [ + [2533, 3101], + [2533, 3139], +] + +[[area.polygons]] +vertices = [ + [2534, 3102], + [2534, 3139], +] + +[[area.polygons]] +vertices = [ + [2535, 3103], + [2535, 3138], +] + +[[area.polygons]] +vertices = [ + [2536, 3109], + [2539, 3109], + [2539, 3136], + [2536, 3136], +] + +[[area.polygons]] +vertices = [ + [2540, 3110], + [2540, 3135], +] + +[[area.polygons]] +vertices = [ + [2541, 3111], + [2542, 3111], + [2542, 3135], + [2541, 3135], +] + +[[area.polygons]] +vertices = [ + [2431, 3120], + [2431, 3222], +] + +[[area.polygons]] +vertices = [ + [2430, 3121], + [2430, 3220], +] + +[[area.polygons]] +vertices = [ + [2429, 3122], + [2429, 3218], +] + +[[area.polygons]] +vertices = [ + [2428, 3123], + [2428, 3216], +] + +[[area.polygons]] +vertices = [ + [2515, 3136], + [2523, 3136], + [2523, 3139], + [2515, 3139], +] + +[[area.polygons]] +vertices = [ + [2536, 3137], + [2537, 3137], +] + +[[area.polygons]] +vertices = [ + [2521, 3140], + [2523, 3140], +] + +[[area.polygons]] +vertices = [ + [2496, 3145], + [2499, 3145], + [2499, 3156], + [2496, 3156], +] + +[[area.polygons]] +vertices = [ + [2496, 3157], + [2497, 3157], + [2497, 3158], + [2496, 3158], +] + +[[area.polygons]] +vertices = [[2496, 3159]] + +[[area.polygons]] +vertices = [ + [2496, 3172], + [2496, 3207], +] + +[[area.polygons]] +vertices = [ + [2497, 3173], + [2497, 3206], +] + +[[area.polygons]] +vertices = [ + [2498, 3174], + [2499, 3174], + [2499, 3204], + [2498, 3204], +] + +[[area.polygons]] +vertices = [ + [2500, 3190], + [2500, 3203], +] + +[[area.polygons]] +vertices = [ + [2501, 3191], + [2501, 3201], +] + +[[area.polygons]] +vertices = [ + [2502, 3192], + [2502, 3200], +] + +[[area.polygons]] +vertices = [ + [2503, 3193], + [2505, 3193], + [2505, 3198], + [2503, 3198], +] + +[[area.polygons]] +vertices = [ + [2506, 3195], + [2506, 3197], +] + +[[area.polygons]] +vertices = [[2507, 3196]] + +[[area.polygons]] +vertices = [[2503, 3199]] + +[[area.polygons]] +vertices = [[2498, 3205]] + +[[area.polygons]] +vertices = [ + [2456, 3209], + [2493, 3209], +] + +[[area.polygons]] +vertices = [ + [2456, 3210], + [2492, 3210], +] + +[[area.polygons]] +vertices = [ + [2456, 3211], + [2485, 3211], +] + +[[area.polygons]] +vertices = [ + [2456, 3212], + [2484, 3212], +] + +[[area.polygons]] +vertices = [ + [2456, 3213], + [2483, 3213], +] + +[[area.polygons]] +vertices = [ + [2456, 3214], + [2482, 3214], +] + +[[area.polygons]] +vertices = [ + [2456, 3215], + [2481, 3215], +] + +[[area.polygons]] +vertices = [ + [2456, 3216], + [2480, 3216], +] + +[[area.polygons]] +vertices = [ + [2446, 3218], + [2448, 3218], +] + +[[area.polygons]] +vertices = [ + [2446, 3219], + [2447, 3219], +] + +[[area.polygons]] +vertices = [[2446, 3220]] + +[[area.polygons]] +vertices = [ + [2434, 3227], + [2437, 3227], + [2437, 3228], + [2434, 3228], +] + +[[area.polygons]] +vertices = [ + [2435, 3229], + [2437, 3229], + [2437, 3230], + [2435, 3230], +] + +[[area.polygons]] +vertices = [ + [2436, 3231], + [2437, 3231], + [2437, 3232], + [2436, 3232], +] + +[[area.polygons]] +vertices = [ + [2437, 3233], + [2437, 3234], +] + +[[area.polygons]] +vertices = [ + [2439, 3237], + [2441, 3237], + [2441, 3238], + [2439, 3238], +] + +[[area.polygons]] +vertices = [ + [2440, 3239], + [2441, 3239], +] diff --git a/.data/raw-cache/map/area/ogre_the_top.toml b/.data/raw-cache/map/area/ogre_the_top.toml new file mode 100644 index 000000000..19fe47ed5 --- /dev/null +++ b/.data/raw-cache/map/area/ogre_the_top.toml @@ -0,0 +1,16 @@ +# Ogre the Top music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Ogre the Top. +# Bounds x 2368-2431, z 10240-10303; 4096 tiles as 1 rectangles. + +[[area]] +name = "Ogre the Top" +area_id = "area.ogre_the_top" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 10240], + [2431, 10240], + [2431, 10303], + [2368, 10303], +] diff --git a/.data/raw-cache/map/area/oh_rats.toml b/.data/raw-cache/map/area/oh_rats.toml new file mode 100644 index 000000000..49160e9da --- /dev/null +++ b/.data/raw-cache/map/area/oh_rats.toml @@ -0,0 +1,16 @@ +# Oh Rats! music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Oh Rats!. +# Bounds x 3289-3310, z 9853-9878; 572 tiles as 1 rectangles. + +[[area]] +name = "Oh Rats!" +area_id = "area.oh_rats" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3289, 9853], + [3310, 9853], + [3310, 9878], + [3289, 9878], +] diff --git a/.data/raw-cache/map/area/on_the_frontline.toml b/.data/raw-cache/map/area/on_the_frontline.toml new file mode 100644 index 000000000..a60727a77 --- /dev/null +++ b/.data/raw-cache/map/area/on_the_frontline.toml @@ -0,0 +1,413 @@ +# On the Frontline music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: On the Frontline. +# Bounds x 1320-1414, z 3664-3770; 6327 tiles as 64 rectangles. + +[[area]] +name = "On the Frontline" +area_id = "area.on_the_frontline" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1344, 3664], + [1386, 3664], + [1386, 3715], + [1344, 3715], +] + +[[area.polygons]] +vertices = [ + [1387, 3665], + [1387, 3770], +] + +[[area.polygons]] +vertices = [ + [1388, 3666], + [1388, 3770], +] + +[[area.polygons]] +vertices = [ + [1389, 3667], + [1389, 3770], +] + +[[area.polygons]] +vertices = [ + [1390, 3668], + [1390, 3770], +] + +[[area.polygons]] +vertices = [ + [1391, 3669], + [1391, 3770], +] + +[[area.polygons]] +vertices = [ + [1392, 3670], + [1392, 3770], +] + +[[area.polygons]] +vertices = [ + [1343, 3671], + [1343, 3715], +] + +[[area.polygons]] +vertices = [ + [1393, 3671], + [1393, 3770], +] + +[[area.polygons]] +vertices = [ + [1342, 3672], + [1342, 3714], +] + +[[area.polygons]] +vertices = [ + [1394, 3672], + [1395, 3672], + [1395, 3756], + [1394, 3756], +] + +[[area.polygons]] +vertices = [ + [1341, 3673], + [1341, 3713], +] + +[[area.polygons]] +vertices = [ + [1396, 3673], + [1396, 3755], +] + +[[area.polygons]] +vertices = [ + [1340, 3674], + [1340, 3709], +] + +[[area.polygons]] +vertices = [ + [1397, 3674], + [1397, 3754], +] + +[[area.polygons]] +vertices = [ + [1339, 3675], + [1339, 3708], +] + +[[area.polygons]] +vertices = [ + [1398, 3675], + [1398, 3753], +] + +[[area.polygons]] +vertices = [ + [1338, 3676], + [1338, 3706], +] + +[[area.polygons]] +vertices = [ + [1399, 3676], + [1399, 3752], +] + +[[area.polygons]] +vertices = [ + [1337, 3677], + [1337, 3706], +] + +[[area.polygons]] +vertices = [ + [1400, 3677], + [1400, 3751], +] + +[[area.polygons]] +vertices = [ + [1336, 3678], + [1336, 3705], +] + +[[area.polygons]] +vertices = [ + [1401, 3678], + [1401, 3750], +] + +[[area.polygons]] +vertices = [ + [1335, 3679], + [1335, 3704], +] + +[[area.polygons]] +vertices = [ + [1334, 3680], + [1334, 3704], +] + +[[area.polygons]] +vertices = [ + [1402, 3680], + [1402, 3749], +] + +[[area.polygons]] +vertices = [ + [1333, 3681], + [1333, 3704], +] + +[[area.polygons]] +vertices = [ + [1403, 3681], + [1403, 3748], +] + +[[area.polygons]] +vertices = [ + [1332, 3682], + [1332, 3704], +] + +[[area.polygons]] +vertices = [ + [1404, 3682], + [1404, 3747], +] + +[[area.polygons]] +vertices = [ + [1405, 3683], + [1405, 3746], +] + +[[area.polygons]] +vertices = [ + [1331, 3684], + [1331, 3704], +] + +[[area.polygons]] +vertices = [ + [1406, 3684], + [1406, 3727], +] + +[[area.polygons]] +vertices = [ + [1330, 3685], + [1330, 3704], +] + +[[area.polygons]] +vertices = [ + [1407, 3685], + [1407, 3727], +] + +[[area.polygons]] +vertices = [ + [1329, 3686], + [1329, 3701], +] + +[[area.polygons]] +vertices = [ + [1408, 3686], + [1408, 3727], +] + +[[area.polygons]] +vertices = [ + [1327, 3687], + [1328, 3687], + [1328, 3700], + [1327, 3700], +] + +[[area.polygons]] +vertices = [ + [1409, 3687], + [1409, 3727], +] + +[[area.polygons]] +vertices = [ + [1326, 3688], + [1326, 3699], +] + +[[area.polygons]] +vertices = [ + [1410, 3688], + [1414, 3688], + [1414, 3727], + [1410, 3727], +] + +[[area.polygons]] +vertices = [ + [1325, 3689], + [1325, 3698], +] + +[[area.polygons]] +vertices = [ + [1324, 3690], + [1324, 3697], +] + +[[area.polygons]] +vertices = [ + [1323, 3691], + [1323, 3697], +] + +[[area.polygons]] +vertices = [ + [1322, 3692], + [1322, 3697], +] + +[[area.polygons]] +vertices = [ + [1321, 3693], + [1321, 3696], +] + +[[area.polygons]] +vertices = [ + [1320, 3694], + [1320, 3695], +] + +[[area.polygons]] +vertices = [[1328, 3701]] + +[[area.polygons]] +vertices = [ + [1346, 3716], + [1386, 3716], + [1386, 3717], + [1346, 3717], +] + +[[area.polygons]] +vertices = [ + [1349, 3718], + [1386, 3718], + [1386, 3723], + [1349, 3723], +] + +[[area.polygons]] +vertices = [ + [1354, 3724], + [1386, 3724], +] + +[[area.polygons]] +vertices = [ + [1358, 3725], + [1386, 3725], + [1386, 3726], + [1358, 3726], +] + +[[area.polygons]] +vertices = [ + [1359, 3727], + [1386, 3727], + [1386, 3729], + [1359, 3729], +] + +[[area.polygons]] +vertices = [ + [1360, 3730], + [1386, 3730], + [1386, 3733], + [1360, 3733], +] + +[[area.polygons]] +vertices = [ + [1361, 3734], + [1386, 3734], +] + +[[area.polygons]] +vertices = [ + [1362, 3735], + [1386, 3735], + [1386, 3756], + [1362, 3756], +] + +[[area.polygons]] +vertices = [ + [1361, 3739], + [1361, 3756], +] + +[[area.polygons]] +vertices = [ + [1360, 3742], + [1360, 3756], +] + +[[area.polygons]] +vertices = [ + [1406, 3743], + [1406, 3745], +] + +[[area.polygons]] +vertices = [ + [1357, 3745], + [1359, 3745], + [1359, 3756], + [1357, 3756], +] + +[[area.polygons]] +vertices = [ + [1356, 3749], + [1356, 3756], +] + +[[area.polygons]] +vertices = [ + [1355, 3750], + [1355, 3751], +] + +[[area.polygons]] +vertices = [ + [1365, 3757], + [1386, 3757], + [1386, 3770], + [1365, 3770], +] + +[[area.polygons]] +vertices = [ + [1394, 3757], + [1394, 3770], +] diff --git a/.data/raw-cache/map/area/on_the_wing.toml b/.data/raw-cache/map/area/on_the_wing.toml new file mode 100644 index 000000000..5c93b6746 --- /dev/null +++ b/.data/raw-cache/map/area/on_the_wing.toml @@ -0,0 +1,16 @@ +# On the Wing music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: On the Wing. +# Bounds x 3008-3071, z 4480-4543; 4096 tiles as 1 rectangles. + +[[area]] +name = "On the Wing" +area_id = "area.on_the_wing" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 4480], + [3071, 4480], + [3071, 4543], + [3008, 4543], +] diff --git a/.data/raw-cache/map/area/on_thin_ice.toml b/.data/raw-cache/map/area/on_thin_ice.toml new file mode 100644 index 000000000..5e5caddd3 --- /dev/null +++ b/.data/raw-cache/map/area/on_thin_ice.toml @@ -0,0 +1,22 @@ +# On Thin Ice music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: On Thin Ice. +# Bounds x 2261-2283, z 4054-4076; 527 tiles as 2 rectangles. + +[[area]] +name = "On Thin Ice" +area_id = "area.on_thin_ice" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2261, 4054], + [2282, 4054], + [2282, 4076], + [2261, 4076], +] + +[[area.polygons]] +vertices = [ + [2283, 4055], + [2283, 4075], +] diff --git a/.data/raw-cache/map/area/oriental.toml b/.data/raw-cache/map/area/oriental.toml new file mode 100644 index 000000000..340e455d9 --- /dev/null +++ b/.data/raw-cache/map/area/oriental.toml @@ -0,0 +1,16 @@ +# Oriental music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Oriental. +# Bounds x 2880-2943, z 9344-9407; 4096 tiles as 1 rectangles. + +[[area]] +name = "Oriental" +area_id = "area.oriental" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2880, 9344], + [2943, 9344], + [2943, 9407], + [2880, 9407], +] diff --git a/.data/raw-cache/map/area/out_at_the_mines.toml b/.data/raw-cache/map/area/out_at_the_mines.toml new file mode 100644 index 000000000..d24f11ec0 --- /dev/null +++ b/.data/raw-cache/map/area/out_at_the_mines.toml @@ -0,0 +1,86 @@ +# Out at the Mines music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Out at the Mines. +# Bounds x 1420-1527, z 3804-3903; 7765 tiles as 10 rectangles. + +[[area]] +name = "Out at the Mines" +area_id = "area.out_at_the_mines" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1420, 3804], + [1430, 3804], + [1430, 3903], + [1420, 3903], +] + +[[area.polygons]] +vertices = [ + [1431, 3807], + [1439, 3807], + [1439, 3903], + [1431, 3903], +] + +[[area.polygons]] +vertices = [ + [1440, 3812], + [1442, 3812], + [1442, 3903], + [1440, 3903], +] + +[[area.polygons]] +vertices = [ + [1443, 3818], + [1444, 3818], + [1444, 3903], + [1443, 3903], +] + +[[area.polygons]] +vertices = [ + [1445, 3823], + [1445, 3903], +] + +[[area.polygons]] +vertices = [ + [1446, 3826], + [1447, 3826], + [1447, 3903], + [1446, 3903], +] + +[[area.polygons]] +vertices = [ + [1448, 3831], + [1452, 3831], + [1452, 3903], + [1448, 3903], +] + +[[area.polygons]] +vertices = [ + [1453, 3836], + [1455, 3836], + [1455, 3903], + [1453, 3903], +] + +[[area.polygons]] +vertices = [ + [1470, 3840], + [1527, 3840], + [1527, 3903], + [1470, 3903], +] + +[[area.polygons]] +vertices = [ + [1456, 3845], + [1469, 3845], + [1469, 3903], + [1456, 3903], +] diff --git a/.data/raw-cache/map/area/out_of_the_deep.toml b/.data/raw-cache/map/area/out_of_the_deep.toml new file mode 100644 index 000000000..294ee5b6f --- /dev/null +++ b/.data/raw-cache/map/area/out_of_the_deep.toml @@ -0,0 +1,24 @@ +# Out of the Deep music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Out of the Deep. +# Bounds x 2496-2559, z 4608-10047; 8192 tiles as 2 rectangles. + +[[area]] +name = "Out of the Deep" +area_id = "area.out_of_the_deep" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2496, 4608], + [2559, 4608], + [2559, 4671], + [2496, 4671], +] + +[[area.polygons]] +vertices = [ + [2496, 9984], + [2559, 9984], + [2559, 10047], + [2496, 10047], +] diff --git a/.data/raw-cache/map/area/outpost.toml b/.data/raw-cache/map/area/outpost.toml new file mode 100644 index 000000000..b8614b468 --- /dev/null +++ b/.data/raw-cache/map/area/outpost.toml @@ -0,0 +1,968 @@ +# Outpost music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Tree Spirits; Neverland; March. +# Bounds x 2346-2597, z 3323-3439; 15457 tiles as 156 rectangles. + +[[area]] +name = "Outpost" +area_id = "area.outpost" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2402, 3323], + [2463, 3323], + [2463, 3383], + [2402, 3383], +] + +[[area.polygons]] +vertices = [ + [2401, 3324], + [2401, 3411], +] + +[[area.polygons]] +vertices = [ + [2400, 3325], + [2400, 3411], +] + +[[area.polygons]] +vertices = [ + [2399, 3326], + [2399, 3411], +] + +[[area.polygons]] +vertices = [ + [2398, 3327], + [2398, 3411], +] + +[[area.polygons]] +vertices = [ + [2397, 3328], + [2397, 3411], +] + +[[area.polygons]] +vertices = [ + [2396, 3329], + [2396, 3411], +] + +[[area.polygons]] +vertices = [ + [2395, 3330], + [2395, 3411], +] + +[[area.polygons]] +vertices = [ + [2394, 3331], + [2394, 3411], +] + +[[area.polygons]] +vertices = [ + [2393, 3332], + [2393, 3411], +] + +[[area.polygons]] +vertices = [ + [2392, 3333], + [2392, 3411], +] + +[[area.polygons]] +vertices = [ + [2391, 3334], + [2391, 3411], +] + +[[area.polygons]] +vertices = [ + [2376, 3335], + [2390, 3335], + [2390, 3406], + [2376, 3406], +] + +[[area.polygons]] +vertices = [ + [2464, 3335], + [2557, 3335], + [2557, 3378], + [2464, 3378], +] + +[[area.polygons]] +vertices = [ + [2375, 3336], + [2375, 3412], +] + +[[area.polygons]] +vertices = [ + [2374, 3337], + [2374, 3416], +] + +[[area.polygons]] +vertices = [ + [2558, 3337], + [2559, 3337], + [2559, 3376], + [2558, 3376], +] + +[[area.polygons]] +vertices = [ + [2373, 3338], + [2373, 3417], +] + +[[area.polygons]] +vertices = [ + [2372, 3339], + [2372, 3418], +] + +[[area.polygons]] +vertices = [ + [2560, 3339], + [2560, 3375], +] + +[[area.polygons]] +vertices = [ + [2371, 3340], + [2371, 3419], +] + +[[area.polygons]] +vertices = [ + [2561, 3340], + [2561, 3375], +] + +[[area.polygons]] +vertices = [ + [2370, 3341], + [2370, 3420], +] + +[[area.polygons]] +vertices = [ + [2562, 3341], + [2562, 3374], +] + +[[area.polygons]] +vertices = [ + [2369, 3342], + [2369, 3421], +] + +[[area.polygons]] +vertices = [ + [2563, 3342], + [2597, 3342], + [2597, 3344], + [2563, 3344], +] + +[[area.polygons]] +vertices = [ + [2368, 3343], + [2368, 3434], +] + +[[area.polygons]] +vertices = [ + [2367, 3345], + [2367, 3434], +] + +[[area.polygons]] +vertices = [ + [2563, 3345], + [2596, 3345], + [2596, 3346], + [2563, 3346], +] + +[[area.polygons]] +vertices = [ + [2366, 3347], + [2366, 3434], +] + +[[area.polygons]] +vertices = [ + [2563, 3347], + [2595, 3347], +] + +[[area.polygons]] +vertices = [ + [2563, 3348], + [2594, 3348], +] + +[[area.polygons]] +vertices = [ + [2365, 3349], + [2365, 3434], +] + +[[area.polygons]] +vertices = [ + [2563, 3349], + [2593, 3349], +] + +[[area.polygons]] +vertices = [ + [2563, 3350], + [2592, 3350], + [2592, 3351], + [2563, 3351], +] + +[[area.polygons]] +vertices = [ + [2364, 3351], + [2364, 3434], +] + +[[area.polygons]] +vertices = [ + [2563, 3352], + [2591, 3352], + [2591, 3353], + [2563, 3353], +] + +[[area.polygons]] +vertices = [ + [2363, 3353], + [2363, 3434], +] + +[[area.polygons]] +vertices = [ + [2563, 3354], + [2590, 3354], + [2590, 3355], + [2563, 3355], +] + +[[area.polygons]] +vertices = [ + [2362, 3355], + [2362, 3434], +] + +[[area.polygons]] +vertices = [ + [2563, 3356], + [2589, 3356], + [2589, 3357], + [2563, 3357], +] + +[[area.polygons]] +vertices = [ + [2361, 3357], + [2361, 3434], +] + +[[area.polygons]] +vertices = [ + [2563, 3358], + [2588, 3358], + [2588, 3359], + [2563, 3359], +] + +[[area.polygons]] +vertices = [ + [2360, 3359], + [2360, 3434], +] + +[[area.polygons]] +vertices = [ + [2563, 3360], + [2587, 3360], +] + +[[area.polygons]] +vertices = [ + [2563, 3361], + [2572, 3361], +] + +[[area.polygons]] +vertices = [ + [2577, 3361], + [2586, 3361], +] + +[[area.polygons]] +vertices = [ + [2563, 3362], + [2570, 3362], +] + +[[area.polygons]] +vertices = [ + [2579, 3362], + [2585, 3362], +] + +[[area.polygons]] +vertices = [ + [2563, 3363], + [2569, 3363], + [2569, 3364], + [2563, 3364], +] + +[[area.polygons]] +vertices = [ + [2563, 3365], + [2568, 3365], + [2568, 3366], + [2563, 3366], +] + +[[area.polygons]] +vertices = [ + [2563, 3367], + [2567, 3367], + [2567, 3368], + [2563, 3368], +] + +[[area.polygons]] +vertices = [ + [2359, 3369], + [2359, 3434], +] + +[[area.polygons]] +vertices = [ + [2563, 3369], + [2566, 3369], + [2566, 3370], + [2563, 3370], +] + +[[area.polygons]] +vertices = [ + [2358, 3371], + [2358, 3434], +] + +[[area.polygons]] +vertices = [ + [2563, 3371], + [2565, 3371], + [2565, 3372], + [2563, 3372], +] + +[[area.polygons]] +vertices = [ + [2357, 3373], + [2357, 3434], +] + +[[area.polygons]] +vertices = [ + [2563, 3373], + [2564, 3373], +] + +[[area.polygons]] +vertices = [[2563, 3374]] + +[[area.polygons]] +vertices = [ + [2356, 3375], + [2356, 3434], +] + +[[area.polygons]] +vertices = [ + [2355, 3377], + [2355, 3434], +] + +[[area.polygons]] +vertices = [[2558, 3377]] + +[[area.polygons]] +vertices = [ + [2354, 3379], + [2354, 3434], +] + +[[area.polygons]] +vertices = [ + [2464, 3379], + [2556, 3379], +] + +[[area.polygons]] +vertices = [ + [2464, 3380], + [2555, 3380], +] + +[[area.polygons]] +vertices = [ + [2464, 3381], + [2554, 3381], +] + +[[area.polygons]] +vertices = [ + [2464, 3382], + [2553, 3382], +] + +[[area.polygons]] +vertices = [ + [2464, 3383], + [2552, 3383], +] + +[[area.polygons]] +vertices = [ + [2402, 3384], + [2457, 3384], +] + +[[area.polygons]] +vertices = [ + [2464, 3384], + [2551, 3384], +] + +[[area.polygons]] +vertices = [ + [2402, 3385], + [2456, 3385], + [2456, 3387], + [2402, 3387], +] + +[[area.polygons]] +vertices = [ + [2465, 3385], + [2550, 3385], +] + +[[area.polygons]] +vertices = [ + [2466, 3386], + [2549, 3386], +] + +[[area.polygons]] +vertices = [ + [2466, 3387], + [2548, 3387], +] + +[[area.polygons]] +vertices = [ + [2402, 3388], + [2433, 3388], +] + +[[area.polygons]] +vertices = [ + [2439, 3388], + [2456, 3388], +] + +[[area.polygons]] +vertices = [ + [2466, 3388], + [2547, 3388], +] + +[[area.polygons]] +vertices = [ + [2402, 3389], + [2432, 3389], +] + +[[area.polygons]] +vertices = [ + [2441, 3389], + [2455, 3389], +] + +[[area.polygons]] +vertices = [ + [2466, 3389], + [2475, 3389], +] + +[[area.polygons]] +vertices = [ + [2481, 3389], + [2484, 3389], +] + +[[area.polygons]] +vertices = [ + [2488, 3389], + [2546, 3389], +] + +[[area.polygons]] +vertices = [ + [2402, 3390], + [2431, 3390], +] + +[[area.polygons]] +vertices = [ + [2442, 3390], + [2445, 3390], +] + +[[area.polygons]] +vertices = [ + [2449, 3390], + [2454, 3390], +] + +[[area.polygons]] +vertices = [ + [2467, 3390], + [2470, 3390], +] + +[[area.polygons]] +vertices = [ + [2488, 3390], + [2492, 3390], +] + +[[area.polygons]] +vertices = [ + [2501, 3390], + [2545, 3390], +] + +[[area.polygons]] +vertices = [ + [2402, 3391], + [2427, 3391], +] + +[[area.polygons]] +vertices = [ + [2505, 3391], + [2544, 3391], +] + +[[area.polygons]] +vertices = [ + [2402, 3392], + [2426, 3392], +] + +[[area.polygons]] +vertices = [ + [2506, 3392], + [2543, 3392], +] + +[[area.polygons]] +vertices = [ + [2402, 3393], + [2419, 3393], +] + +[[area.polygons]] +vertices = [ + [2506, 3393], + [2542, 3393], +] + +[[area.polygons]] +vertices = [ + [2402, 3394], + [2418, 3394], +] + +[[area.polygons]] +vertices = [ + [2506, 3394], + [2541, 3394], +] + +[[area.polygons]] +vertices = [ + [2402, 3395], + [2417, 3395], +] + +[[area.polygons]] +vertices = [ + [2505, 3395], + [2539, 3395], +] + +[[area.polygons]] +vertices = [ + [2402, 3396], + [2416, 3396], +] + +[[area.polygons]] +vertices = [ + [2501, 3396], + [2537, 3396], +] + +[[area.polygons]] +vertices = [ + [2402, 3397], + [2415, 3397], + [2415, 3398], + [2402, 3398], +] + +[[area.polygons]] +vertices = [ + [2500, 3397], + [2535, 3397], +] + +[[area.polygons]] +vertices = [ + [2499, 3398], + [2534, 3398], + [2534, 3407], + [2499, 3407], +] + +[[area.polygons]] +vertices = [ + [2402, 3399], + [2414, 3399], +] + +[[area.polygons]] +vertices = [ + [2498, 3399], + [2498, 3439], +] + +[[area.polygons]] +vertices = [ + [2402, 3400], + [2413, 3400], + [2413, 3407], + [2402, 3407], +] + +[[area.polygons]] +vertices = [ + [2497, 3400], + [2497, 3417], +] + +[[area.polygons]] +vertices = [ + [2353, 3401], + [2353, 3434], +] + +[[area.polygons]] +vertices = [ + [2496, 3401], + [2496, 3416], +] + +[[area.polygons]] +vertices = [ + [2495, 3402], + [2495, 3412], +] + +[[area.polygons]] +vertices = [ + [2352, 3403], + [2352, 3434], +] + +[[area.polygons]] +vertices = [ + [2494, 3403], + [2494, 3408], +] + +[[area.polygons]] +vertices = [ + [2535, 3404], + [2535, 3406], +] + +[[area.polygons]] +vertices = [ + [2351, 3405], + [2351, 3434], +] + +[[area.polygons]] +vertices = [[2536, 3405]] + +[[area.polygons]] +vertices = [ + [2350, 3407], + [2350, 3434], +] + +[[area.polygons]] +vertices = [ + [2376, 3407], + [2382, 3407], +] + +[[area.polygons]] +vertices = [ + [2387, 3407], + [2390, 3407], +] + +[[area.polygons]] +vertices = [ + [2376, 3408], + [2379, 3408], +] + +[[area.polygons]] +vertices = [ + [2388, 3408], + [2390, 3408], +] + +[[area.polygons]] +vertices = [ + [2402, 3408], + [2412, 3408], +] + +[[area.polygons]] +vertices = [ + [2499, 3408], + [2533, 3408], +] + +[[area.polygons]] +vertices = [ + [2349, 3409], + [2349, 3434], +] + +[[area.polygons]] +vertices = [ + [2376, 3409], + [2378, 3409], +] + +[[area.polygons]] +vertices = [ + [2389, 3409], + [2390, 3409], +] + +[[area.polygons]] +vertices = [ + [2402, 3409], + [2411, 3409], +] + +[[area.polygons]] +vertices = [ + [2499, 3409], + [2532, 3409], +] + +[[area.polygons]] +vertices = [[2376, 3410]] + +[[area.polygons]] +vertices = [[2390, 3410]] + +[[area.polygons]] +vertices = [ + [2402, 3410], + [2410, 3410], +] + +[[area.polygons]] +vertices = [ + [2499, 3410], + [2531, 3410], +] + +[[area.polygons]] +vertices = [ + [2348, 3411], + [2348, 3434], +] + +[[area.polygons]] +vertices = [ + [2402, 3411], + [2409, 3411], +] + +[[area.polygons]] +vertices = [ + [2499, 3411], + [2529, 3411], +] + +[[area.polygons]] +vertices = [ + [2499, 3412], + [2520, 3412], +] + +[[area.polygons]] +vertices = [ + [2347, 3413], + [2347, 3434], +] + +[[area.polygons]] +vertices = [ + [2499, 3413], + [2519, 3413], +] + +[[area.polygons]] +vertices = [ + [2499, 3414], + [2518, 3414], +] + +[[area.polygons]] +vertices = [ + [2346, 3415], + [2346, 3434], +] + +[[area.polygons]] +vertices = [ + [2499, 3415], + [2517, 3415], +] + +[[area.polygons]] +vertices = [ + [2499, 3416], + [2516, 3416], +] + +[[area.polygons]] +vertices = [ + [2499, 3417], + [2515, 3417], +] + +[[area.polygons]] +vertices = [ + [2499, 3418], + [2514, 3418], +] + +[[area.polygons]] +vertices = [ + [2499, 3419], + [2513, 3419], +] + +[[area.polygons]] +vertices = [ + [2499, 3420], + [2512, 3420], +] + +[[area.polygons]] +vertices = [ + [2499, 3421], + [2510, 3421], +] + +[[area.polygons]] +vertices = [ + [2499, 3422], + [2504, 3422], +] + +[[area.polygons]] +vertices = [ + [2499, 3423], + [2503, 3423], +] + +[[area.polygons]] +vertices = [ + [2499, 3424], + [2502, 3424], +] + +[[area.polygons]] +vertices = [ + [2499, 3425], + [2501, 3425], + [2501, 3439], + [2499, 3439], +] + +[[area.polygons]] +vertices = [ + [2497, 3429], + [2497, 3439], +] + +[[area.polygons]] +vertices = [ + [2496, 3430], + [2496, 3439], +] + +[[area.polygons]] +vertices = [ + [2495, 3431], + [2495, 3439], +] + +[[area.polygons]] +vertices = [ + [2494, 3432], + [2494, 3438], +] + +[[area.polygons]] +vertices = [ + [2369, 3433], + [2369, 3434], +] + +[[area.polygons]] +vertices = [[2370, 3434]] diff --git a/.data/raw-cache/map/area/over_the_mountains.toml b/.data/raw-cache/map/area/over_the_mountains.toml new file mode 100644 index 000000000..008226fd3 --- /dev/null +++ b/.data/raw-cache/map/area/over_the_mountains.toml @@ -0,0 +1,336 @@ +# Over the Mountains music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Over the Mountains. +# Bounds x 1239-1332, z 3312-3436; 9898 tiles as 49 rectangles. + +[[area]] +name = "Over the Mountains" +area_id = "area.over_the_mountains" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1239, 3312], + [1332, 3312], + [1332, 3321], + [1239, 3321], +] + +[[area.polygons]] +vertices = [ + [1239, 3322], + [1331, 3322], +] + +[[area.polygons]] +vertices = [ + [1239, 3323], + [1330, 3323], +] + +[[area.polygons]] +vertices = [ + [1239, 3324], + [1329, 3324], +] + +[[area.polygons]] +vertices = [ + [1239, 3325], + [1328, 3325], +] + +[[area.polygons]] +vertices = [ + [1239, 3326], + [1327, 3326], +] + +[[area.polygons]] +vertices = [ + [1239, 3327], + [1326, 3327], +] + +[[area.polygons]] +vertices = [ + [1239, 3328], + [1325, 3328], +] + +[[area.polygons]] +vertices = [ + [1239, 3329], + [1324, 3329], + [1324, 3365], + [1239, 3365], +] + +[[area.polygons]] +vertices = [ + [1325, 3334], + [1325, 3363], +] + +[[area.polygons]] +vertices = [ + [1326, 3335], + [1326, 3361], +] + +[[area.polygons]] +vertices = [ + [1327, 3336], + [1327, 3359], +] + +[[area.polygons]] +vertices = [ + [1328, 3337], + [1328, 3357], +] + +[[area.polygons]] +vertices = [ + [1329, 3338], + [1329, 3355], +] + +[[area.polygons]] +vertices = [ + [1239, 3366], + [1323, 3366], + [1323, 3367], + [1239, 3367], +] + +[[area.polygons]] +vertices = [ + [1239, 3368], + [1322, 3368], + [1322, 3369], + [1239, 3369], +] + +[[area.polygons]] +vertices = [ + [1239, 3370], + [1321, 3370], + [1321, 3371], + [1239, 3371], +] + +[[area.polygons]] +vertices = [ + [1239, 3372], + [1320, 3372], + [1320, 3373], + [1239, 3373], +] + +[[area.polygons]] +vertices = [ + [1239, 3374], + [1319, 3374], + [1319, 3400], + [1239, 3400], +] + +[[area.polygons]] +vertices = [ + [1239, 3401], + [1318, 3401], +] + +[[area.polygons]] +vertices = [ + [1240, 3402], + [1318, 3402], +] + +[[area.polygons]] +vertices = [ + [1241, 3403], + [1317, 3403], +] + +[[area.polygons]] +vertices = [ + [1242, 3404], + [1317, 3404], +] + +[[area.polygons]] +vertices = [ + [1243, 3405], + [1316, 3405], +] + +[[area.polygons]] +vertices = [ + [1244, 3406], + [1316, 3406], +] + +[[area.polygons]] +vertices = [ + [1245, 3407], + [1315, 3407], +] + +[[area.polygons]] +vertices = [ + [1246, 3408], + [1315, 3408], +] + +[[area.polygons]] +vertices = [ + [1247, 3409], + [1314, 3409], +] + +[[area.polygons]] +vertices = [ + [1248, 3410], + [1314, 3410], +] + +[[area.polygons]] +vertices = [ + [1249, 3411], + [1313, 3411], + [1313, 3412], + [1249, 3412], +] + +[[area.polygons]] +vertices = [ + [1249, 3413], + [1312, 3413], + [1312, 3414], + [1249, 3414], +] + +[[area.polygons]] +vertices = [ + [1249, 3415], + [1311, 3415], + [1311, 3416], + [1249, 3416], +] + +[[area.polygons]] +vertices = [ + [1249, 3417], + [1310, 3417], + [1310, 3418], + [1249, 3418], +] + +[[area.polygons]] +vertices = [ + [1249, 3419], + [1309, 3419], + [1309, 3420], + [1249, 3420], +] + +[[area.polygons]] +vertices = [ + [1249, 3421], + [1308, 3421], + [1308, 3422], + [1249, 3422], +] + +[[area.polygons]] +vertices = [ + [1249, 3423], + [1307, 3423], + [1307, 3424], + [1249, 3424], +] + +[[area.polygons]] +vertices = [ + [1249, 3425], + [1306, 3425], + [1306, 3426], + [1249, 3426], +] + +[[area.polygons]] +vertices = [ + [1249, 3427], + [1305, 3427], + [1305, 3428], + [1249, 3428], +] + +[[area.polygons]] +vertices = [ + [1249, 3429], + [1304, 3429], + [1304, 3430], + [1249, 3430], +] + +[[area.polygons]] +vertices = [ + [1249, 3431], + [1303, 3431], +] + +[[area.polygons]] +vertices = [ + [1249, 3432], + [1265, 3432], +] + +[[area.polygons]] +vertices = [ + [1276, 3432], + [1303, 3432], +] + +[[area.polygons]] +vertices = [ + [1249, 3433], + [1264, 3433], +] + +[[area.polygons]] +vertices = [ + [1277, 3433], + [1302, 3433], +] + +[[area.polygons]] +vertices = [ + [1250, 3434], + [1263, 3434], +] + +[[area.polygons]] +vertices = [ + [1278, 3434], + [1302, 3434], +] + +[[area.polygons]] +vertices = [ + [1251, 3435], + [1262, 3435], +] + +[[area.polygons]] +vertices = [ + [1279, 3435], + [1301, 3435], +] + +[[area.polygons]] +vertices = [ + [1252, 3436], + [1261, 3436], +] diff --git a/.data/raw-cache/map/area/over_to_nardah.toml b/.data/raw-cache/map/area/over_to_nardah.toml new file mode 100644 index 000000000..482f7f15c --- /dev/null +++ b/.data/raw-cache/map/area/over_to_nardah.toml @@ -0,0 +1,471 @@ +# Over to Nardah music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Over to Nardah. +# Bounds x 3392-3472, z 2861-2949; 5204 tiles as 76 rectangles. + +[[area]] +name = "Over to Nardah" +area_id = "area.over_to_nardah" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3439, 2861], + [3449, 2861], +] + +[[area.polygons]] +vertices = [ + [3438, 2862], + [3448, 2862], + [3448, 2864], + [3438, 2864], +] + +[[area.polygons]] +vertices = [ + [3437, 2863], + [3437, 2949], +] + +[[area.polygons]] +vertices = [ + [3436, 2864], + [3436, 2949], +] + +[[area.polygons]] +vertices = [ + [3435, 2865], + [3435, 2949], +] + +[[area.polygons]] +vertices = [ + [3438, 2865], + [3447, 2865], + [3447, 2949], + [3438, 2949], +] + +[[area.polygons]] +vertices = [ + [3434, 2866], + [3434, 2949], +] + +[[area.polygons]] +vertices = [ + [3433, 2867], + [3433, 2949], +] + +[[area.polygons]] +vertices = [ + [3448, 2867], + [3448, 2949], +] + +[[area.polygons]] +vertices = [ + [3432, 2868], + [3432, 2949], +] + +[[area.polygons]] +vertices = [ + [3431, 2869], + [3431, 2949], +] + +[[area.polygons]] +vertices = [ + [3430, 2870], + [3430, 2949], +] + +[[area.polygons]] +vertices = [ + [3429, 2872], + [3429, 2949], +] + +[[area.polygons]] +vertices = [ + [3449, 2872], + [3449, 2949], +] + +[[area.polygons]] +vertices = [ + [3428, 2873], + [3428, 2949], +] + +[[area.polygons]] +vertices = [ + [3427, 2874], + [3427, 2949], +] + +[[area.polygons]] +vertices = [ + [3450, 2874], + [3450, 2949], +] + +[[area.polygons]] +vertices = [ + [3426, 2875], + [3426, 2949], +] + +[[area.polygons]] +vertices = [ + [3451, 2875], + [3451, 2949], +] + +[[area.polygons]] +vertices = [ + [3425, 2876], + [3425, 2949], +] + +[[area.polygons]] +vertices = [ + [3452, 2876], + [3452, 2949], +] + +[[area.polygons]] +vertices = [ + [3424, 2877], + [3424, 2949], +] + +[[area.polygons]] +vertices = [ + [3453, 2877], + [3453, 2949], +] + +[[area.polygons]] +vertices = [ + [3423, 2878], + [3423, 2949], +] + +[[area.polygons]] +vertices = [ + [3454, 2878], + [3454, 2949], +] + +[[area.polygons]] +vertices = [ + [3422, 2879], + [3422, 2949], +] + +[[area.polygons]] +vertices = [ + [3421, 2880], + [3421, 2949], +] + +[[area.polygons]] +vertices = [ + [3455, 2880], + [3456, 2880], + [3456, 2949], + [3455, 2949], +] + +[[area.polygons]] +vertices = [ + [3420, 2881], + [3420, 2949], +] + +[[area.polygons]] +vertices = [ + [3457, 2881], + [3458, 2881], + [3458, 2949], + [3457, 2949], +] + +[[area.polygons]] +vertices = [ + [3419, 2882], + [3419, 2949], +] + +[[area.polygons]] +vertices = [ + [3459, 2882], + [3459, 2949], +] + +[[area.polygons]] +vertices = [ + [3418, 2883], + [3418, 2949], +] + +[[area.polygons]] +vertices = [ + [3417, 2884], + [3417, 2949], +] + +[[area.polygons]] +vertices = [ + [3460, 2884], + [3460, 2949], +] + +[[area.polygons]] +vertices = [ + [3416, 2885], + [3416, 2949], +] + +[[area.polygons]] +vertices = [ + [3461, 2885], + [3461, 2949], +] + +[[area.polygons]] +vertices = [ + [3415, 2886], + [3415, 2949], +] + +[[area.polygons]] +vertices = [ + [3462, 2886], + [3462, 2948], +] + +[[area.polygons]] +vertices = [ + [3414, 2887], + [3414, 2949], +] + +[[area.polygons]] +vertices = [ + [3463, 2887], + [3463, 2947], +] + +[[area.polygons]] +vertices = [ + [3413, 2888], + [3413, 2949], +] + +[[area.polygons]] +vertices = [ + [3464, 2888], + [3464, 2947], +] + +[[area.polygons]] +vertices = [ + [3412, 2889], + [3412, 2949], +] + +[[area.polygons]] +vertices = [ + [3465, 2889], + [3470, 2889], + [3470, 2890], + [3465, 2890], +] + +[[area.polygons]] +vertices = [ + [3411, 2890], + [3411, 2949], +] + +[[area.polygons]] +vertices = [ + [3410, 2891], + [3410, 2949], +] + +[[area.polygons]] +vertices = [ + [3465, 2891], + [3465, 2947], +] + +[[area.polygons]] +vertices = [ + [3409, 2892], + [3409, 2949], +] + +[[area.polygons]] +vertices = [ + [3408, 2893], + [3408, 2949], +] + +[[area.polygons]] +vertices = [ + [3407, 2894], + [3407, 2948], +] + +[[area.polygons]] +vertices = [ + [3466, 2894], + [3466, 2947], +] + +[[area.polygons]] +vertices = [ + [3406, 2895], + [3406, 2947], +] + +[[area.polygons]] +vertices = [ + [3405, 2896], + [3405, 2946], +] + +[[area.polygons]] +vertices = [ + [3404, 2897], + [3404, 2945], +] + +[[area.polygons]] +vertices = [ + [3467, 2897], + [3467, 2947], +] + +[[area.polygons]] +vertices = [ + [3403, 2898], + [3403, 2944], +] + +[[area.polygons]] +vertices = [ + [3402, 2899], + [3402, 2943], +] + +[[area.polygons]] +vertices = [ + [3401, 2900], + [3401, 2942], +] + +[[area.polygons]] +vertices = [ + [3468, 2900], + [3468, 2947], +] + +[[area.polygons]] +vertices = [ + [3400, 2901], + [3400, 2941], +] + +[[area.polygons]] +vertices = [ + [3399, 2902], + [3399, 2940], +] + +[[area.polygons]] +vertices = [ + [3398, 2903], + [3398, 2939], +] + +[[area.polygons]] +vertices = [ + [3469, 2903], + [3469, 2947], +] + +[[area.polygons]] +vertices = [ + [3397, 2904], + [3397, 2938], +] + +[[area.polygons]] +vertices = [ + [3396, 2905], + [3396, 2937], +] + +[[area.polygons]] +vertices = [ + [3395, 2906], + [3395, 2936], +] + +[[area.polygons]] +vertices = [ + [3394, 2907], + [3394, 2935], +] + +[[area.polygons]] +vertices = [ + [3470, 2907], + [3470, 2911], +] + +[[area.polygons]] +vertices = [ + [3393, 2908], + [3393, 2934], +] + +[[area.polygons]] +vertices = [ + [3392, 2909], + [3392, 2933], +] + +[[area.polygons]] +vertices = [ + [3470, 2914], + [3470, 2940], +] + +[[area.polygons]] +vertices = [ + [3471, 2924], + [3471, 2937], +] + +[[area.polygons]] +vertices = [ + [3472, 2928], + [3472, 2933], +] + +[[area.polygons]] +vertices = [ + [3470, 2943], + [3470, 2947], +] + +[[area.polygons]] +vertices = [[3471, 2947]] diff --git a/.data/raw-cache/map/area/overpass.toml b/.data/raw-cache/map/area/overpass.toml new file mode 100644 index 000000000..2e5bb5713 --- /dev/null +++ b/.data/raw-cache/map/area/overpass.toml @@ -0,0 +1,406 @@ +# Overpass music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Overpass. +# Bounds x 2297-2392, z 3236-3334; 4726 tiles as 65 rectangles. + +[[area]] +name = "Overpass" +area_id = "area.overpass" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2335, 3236], + [2351, 3236], + [2351, 3334], + [2335, 3334], +] + +[[area.polygons]] +vertices = [ + [2334, 3237], + [2334, 3334], +] + +[[area.polygons]] +vertices = [ + [2333, 3238], + [2333, 3334], +] + +[[area.polygons]] +vertices = [ + [2332, 3239], + [2332, 3334], +] + +[[area.polygons]] +vertices = [ + [2331, 3240], + [2331, 3261], +] + +[[area.polygons]] +vertices = [ + [2330, 3241], + [2330, 3261], +] + +[[area.polygons]] +vertices = [ + [2329, 3242], + [2329, 3261], +] + +[[area.polygons]] +vertices = [ + [2328, 3243], + [2328, 3261], +] + +[[area.polygons]] +vertices = [ + [2327, 3244], + [2327, 3261], +] + +[[area.polygons]] +vertices = [ + [2326, 3245], + [2326, 3261], +] + +[[area.polygons]] +vertices = [ + [2325, 3246], + [2325, 3261], +] + +[[area.polygons]] +vertices = [ + [2324, 3247], + [2324, 3261], +] + +[[area.polygons]] +vertices = [ + [2323, 3248], + [2323, 3261], +] + +[[area.polygons]] +vertices = [ + [2322, 3249], + [2322, 3261], +] + +[[area.polygons]] +vertices = [ + [2306, 3250], + [2321, 3250], + [2321, 3261], + [2306, 3261], +] + +[[area.polygons]] +vertices = [ + [2352, 3262], + [2352, 3334], +] + +[[area.polygons]] +vertices = [ + [2353, 3264], + [2353, 3334], +] + +[[area.polygons]] +vertices = [ + [2354, 3265], + [2354, 3334], +] + +[[area.polygons]] +vertices = [ + [2355, 3267], + [2355, 3334], +] + +[[area.polygons]] +vertices = [ + [2356, 3269], + [2356, 3334], +] + +[[area.polygons]] +vertices = [ + [2357, 3271], + [2357, 3334], +] + +[[area.polygons]] +vertices = [ + [2331, 3272], + [2331, 3334], +] + +[[area.polygons]] +vertices = [ + [2358, 3272], + [2358, 3334], +] + +[[area.polygons]] +vertices = [ + [2330, 3273], + [2330, 3334], +] + +[[area.polygons]] +vertices = [ + [2329, 3274], + [2329, 3334], +] + +[[area.polygons]] +vertices = [ + [2359, 3274], + [2359, 3334], +] + +[[area.polygons]] +vertices = [ + [2328, 3275], + [2328, 3334], +] + +[[area.polygons]] +vertices = [ + [2360, 3276], + [2360, 3334], +] + +[[area.polygons]] +vertices = [ + [2361, 3277], + [2361, 3334], +] + +[[area.polygons]] +vertices = [ + [2362, 3279], + [2362, 3334], +] + +[[area.polygons]] +vertices = [ + [2363, 3295], + [2363, 3334], +] + +[[area.polygons]] +vertices = [ + [2364, 3296], + [2364, 3334], +] + +[[area.polygons]] +vertices = [ + [2365, 3297], + [2365, 3334], +] + +[[area.polygons]] +vertices = [ + [2366, 3298], + [2366, 3334], +] + +[[area.polygons]] +vertices = [ + [2367, 3299], + [2367, 3334], +] + +[[area.polygons]] +vertices = [ + [2368, 3300], + [2368, 3334], +] + +[[area.polygons]] +vertices = [ + [2369, 3301], + [2369, 3334], +] + +[[area.polygons]] +vertices = [ + [2370, 3302], + [2370, 3334], +] + +[[area.polygons]] +vertices = [ + [2371, 3303], + [2371, 3334], +] + +[[area.polygons]] +vertices = [ + [2372, 3304], + [2372, 3334], +] + +[[area.polygons]] +vertices = [ + [2316, 3305], + [2327, 3305], + [2327, 3334], + [2316, 3334], +] + +[[area.polygons]] +vertices = [ + [2373, 3305], + [2373, 3334], +] + +[[area.polygons]] +vertices = [ + [2315, 3306], + [2315, 3334], +] + +[[area.polygons]] +vertices = [ + [2374, 3306], + [2374, 3334], +] + +[[area.polygons]] +vertices = [ + [2314, 3307], + [2314, 3334], +] + +[[area.polygons]] +vertices = [ + [2375, 3307], + [2375, 3334], +] + +[[area.polygons]] +vertices = [ + [2313, 3308], + [2313, 3334], +] + +[[area.polygons]] +vertices = [ + [2376, 3308], + [2376, 3334], +] + +[[area.polygons]] +vertices = [ + [2377, 3309], + [2377, 3334], +] + +[[area.polygons]] +vertices = [ + [2378, 3310], + [2378, 3334], +] + +[[area.polygons]] +vertices = [ + [2379, 3311], + [2379, 3334], +] + +[[area.polygons]] +vertices = [ + [2380, 3312], + [2380, 3334], +] + +[[area.polygons]] +vertices = [ + [2381, 3313], + [2381, 3334], +] + +[[area.polygons]] +vertices = [ + [2382, 3314], + [2382, 3334], +] + +[[area.polygons]] +vertices = [ + [2383, 3315], + [2383, 3334], +] + +[[area.polygons]] +vertices = [ + [2384, 3316], + [2384, 3334], +] + +[[area.polygons]] +vertices = [ + [2385, 3317], + [2385, 3334], +] + +[[area.polygons]] +vertices = [ + [2386, 3318], + [2386, 3334], +] + +[[area.polygons]] +vertices = [ + [2387, 3319], + [2387, 3334], +] + +[[area.polygons]] +vertices = [ + [2388, 3320], + [2388, 3334], +] + +[[area.polygons]] +vertices = [ + [2297, 3321], + [2312, 3321], + [2312, 3334], + [2297, 3334], +] + +[[area.polygons]] +vertices = [ + [2389, 3321], + [2389, 3334], +] + +[[area.polygons]] +vertices = [ + [2390, 3322], + [2390, 3334], +] + +[[area.polygons]] +vertices = [ + [2391, 3323], + [2391, 3333], +] + +[[area.polygons]] +vertices = [ + [2392, 3324], + [2392, 3332], +] diff --git a/.data/raw-cache/map/area/parade.toml b/.data/raw-cache/map/area/parade.toml new file mode 100644 index 000000000..704ed560f --- /dev/null +++ b/.data/raw-cache/map/area/parade.toml @@ -0,0 +1,244 @@ +# Parade music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Parade. +# Bounds x 3311-3423, z 3458-3522; 5628 tiles as 36 rectangles. + +[[area]] +name = "Parade" +area_id = "area.parade" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3312, 3458], + [3330, 3458], +] + +[[area.polygons]] +vertices = [ + [3313, 3459], + [3332, 3459], + [3332, 3461], + [3313, 3461], +] + +[[area.polygons]] +vertices = [ + [3333, 3460], + [3334, 3460], + [3334, 3522], + [3333, 3522], +] + +[[area.polygons]] +vertices = [ + [3335, 3461], + [3336, 3461], + [3336, 3522], + [3335, 3522], +] + +[[area.polygons]] +vertices = [ + [3314, 3462], + [3332, 3462], +] + +[[area.polygons]] +vertices = [ + [3337, 3462], + [3338, 3462], + [3338, 3522], + [3337, 3522], +] + +[[area.polygons]] +vertices = [ + [3315, 3463], + [3332, 3463], +] + +[[area.polygons]] +vertices = [ + [3339, 3463], + [3407, 3463], + [3407, 3522], + [3339, 3522], +] + +[[area.polygons]] +vertices = [ + [3318, 3464], + [3332, 3464], +] + +[[area.polygons]] +vertices = [ + [3408, 3464], + [3408, 3504], +] + +[[area.polygons]] +vertices = [ + [3319, 3465], + [3332, 3465], +] + +[[area.polygons]] +vertices = [ + [3409, 3465], + [3409, 3503], +] + +[[area.polygons]] +vertices = [ + [3320, 3466], + [3332, 3466], + [3332, 3487], + [3320, 3487], +] + +[[area.polygons]] +vertices = [ + [3410, 3466], + [3410, 3500], +] + +[[area.polygons]] +vertices = [ + [3411, 3467], + [3411, 3499], +] + +[[area.polygons]] +vertices = [ + [3412, 3468], + [3415, 3468], + [3415, 3497], + [3412, 3497], +] + +[[area.polygons]] +vertices = [ + [3319, 3469], + [3319, 3486], +] + +[[area.polygons]] +vertices = [ + [3416, 3469], + [3416, 3496], +] + +[[area.polygons]] +vertices = [ + [3318, 3471], + [3318, 3486], +] + +[[area.polygons]] +vertices = [ + [3417, 3471], + [3419, 3471], + [3419, 3480], + [3417, 3480], +] + +[[area.polygons]] +vertices = [ + [3314, 3472], + [3317, 3472], + [3317, 3486], + [3314, 3486], +] + +[[area.polygons]] +vertices = [ + [3420, 3472], + [3420, 3480], +] + +[[area.polygons]] +vertices = [ + [3312, 3473], + [3313, 3473], + [3313, 3477], + [3312, 3477], +] + +[[area.polygons]] +vertices = [ + [3421, 3475], + [3422, 3475], + [3422, 3479], + [3421, 3479], +] + +[[area.polygons]] +vertices = [ + [3423, 3476], + [3423, 3478], +] + +[[area.polygons]] +vertices = [ + [3313, 3478], + [3313, 3487], +] + +[[area.polygons]] +vertices = [ + [3312, 3480], + [3312, 3487], +] + +[[area.polygons]] +vertices = [[3421, 3480]] + +[[area.polygons]] +vertices = [ + [3417, 3481], + [3418, 3481], + [3418, 3493], + [3417, 3493], +] + +[[area.polygons]] +vertices = [ + [3311, 3483], + [3311, 3487], +] + +[[area.polygons]] +vertices = [ + [3314, 3487], + [3315, 3487], +] + +[[area.polygons]] +vertices = [ + [3327, 3488], + [3332, 3488], +] + +[[area.polygons]] +vertices = [ + [3328, 3489], + [3332, 3489], + [3332, 3522], + [3328, 3522], +] + +[[area.polygons]] +vertices = [ + [3417, 3494], + [3417, 3495], +] + +[[area.polygons]] +vertices = [ + [3412, 3498], + [3413, 3498], +] + +[[area.polygons]] +vertices = [[3412, 3499]] diff --git a/.data/raw-cache/map/area/path_of_peril.toml b/.data/raw-cache/map/area/path_of_peril.toml new file mode 100644 index 000000000..92bc6f934 --- /dev/null +++ b/.data/raw-cache/map/area/path_of_peril.toml @@ -0,0 +1,16 @@ +# Path of Peril music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Path of Peril. +# Bounds x 2624-2751, z 5056-5119; 8192 tiles as 1 rectangles. + +[[area]] +name = "Path of Peril" +area_id = "area.path_of_peril" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2624, 5056], + [2751, 5056], + [2751, 5119], + [2624, 5119], +] diff --git a/.data/raw-cache/map/area/pest_control.toml b/.data/raw-cache/map/area/pest_control.toml new file mode 100644 index 000000000..848be1f2c --- /dev/null +++ b/.data/raw-cache/map/area/pest_control.toml @@ -0,0 +1,473 @@ +# Pest Control music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Pest Control. +# Bounds x 1600-2686, z 2561-5631; 18994 tiles as 73 rectangles. + +[[area]] +name = "Pest Control" +area_id = "area.pest_control" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2642, 2561], + [2642, 2617], +] + +[[area.polygons]] +vertices = [ + [2641, 2562], + [2641, 2565], +] + +[[area.polygons]] +vertices = [ + [2643, 2562], + [2643, 2617], +] + +[[area.polygons]] +vertices = [ + [2640, 2563], + [2640, 2564], +] + +[[area.polygons]] +vertices = [ + [2644, 2563], + [2646, 2563], + [2646, 2607], + [2644, 2607], +] + +[[area.polygons]] +vertices = [ + [2660, 2563], + [2663, 2563], + [2663, 2607], + [2660, 2607], +] + +[[area.polygons]] +vertices = [ + [2670, 2563], + [2672, 2563], + [2672, 2610], + [2670, 2610], +] + +[[area.polygons]] +vertices = [ + [2647, 2564], + [2647, 2607], +] + +[[area.polygons]] +vertices = [ + [2655, 2564], + [2659, 2564], + [2659, 2608], + [2655, 2608], +] + +[[area.polygons]] +vertices = [ + [2664, 2564], + [2664, 2606], +] + +[[area.polygons]] +vertices = [ + [2668, 2564], + [2669, 2564], + [2669, 2606], + [2668, 2606], +] + +[[area.polygons]] +vertices = [ + [2673, 2564], + [2673, 2613], +] + +[[area.polygons]] +vertices = [ + [2682, 2564], + [2684, 2564], +] + +[[area.polygons]] +vertices = [ + [2648, 2565], + [2654, 2565], + [2654, 2607], + [2648, 2607], +] + +[[area.polygons]] +vertices = [ + [2665, 2565], + [2665, 2606], +] + +[[area.polygons]] +vertices = [ + [2683, 2565], + [2685, 2565], +] + +[[area.polygons]] +vertices = [ + [2633, 2566], + [2636, 2566], + [2636, 2580], + [2633, 2580], +] + +[[area.polygons]] +vertices = [ + [2684, 2566], + [2685, 2566], + [2685, 2571], + [2684, 2571], +] + +[[area.polygons]] +vertices = [ + [2631, 2567], + [2632, 2567], + [2632, 2581], + [2631, 2581], +] + +[[area.polygons]] +vertices = [ + [2637, 2567], + [2638, 2567], + [2638, 2580], + [2637, 2580], +] + +[[area.polygons]] +vertices = [ + [2666, 2567], + [2667, 2567], + [2667, 2606], + [2666, 2606], +] + +[[area.polygons]] +vertices = [ + [2674, 2567], + [2674, 2613], +] + +[[area.polygons]] +vertices = [ + [2630, 2568], + [2630, 2582], +] + +[[area.polygons]] +vertices = [ + [2639, 2568], + [2641, 2568], + [2641, 2580], + [2639, 2580], +] + +[[area.polygons]] +vertices = [ + [2683, 2568], + [2683, 2575], +] + +[[area.polygons]] +vertices = [ + [2681, 2569], + [2682, 2569], + [2682, 2598], + [2681, 2598], +] + +[[area.polygons]] +vertices = [ + [2675, 2570], + [2680, 2570], + [2680, 2614], + [2675, 2614], +] + +[[area.polygons]] +vertices = [ + [2628, 2571], + [2629, 2571], + [2629, 2581], + [2628, 2581], +] + +[[area.polygons]] +vertices = [ + [2626, 2572], + [2627, 2572], + [2627, 2574], + [2626, 2574], +] + +[[area.polygons]] +vertices = [ + [2684, 2572], + [2684, 2573], +] + +[[area.polygons]] +vertices = [ + [2625, 2573], + [2625, 2574], +] + +[[area.polygons]] +vertices = [ + [2627, 2575], + [2627, 2576], +] + +[[area.polygons]] +vertices = [ + [2683, 2578], + [2683, 2581], +] + +[[area.polygons]] +vertices = [ + [2684, 2579], + [2684, 2580], +] + +[[area.polygons]] +vertices = [ + [2633, 2581], + [2634, 2581], +] + +[[area.polygons]] +vertices = [ + [2641, 2581], + [2641, 2615], +] + +[[area.polygons]] +vertices = [[2629, 2582]] + +[[area.polygons]] +vertices = [[2631, 2582]] + +[[area.polygons]] +vertices = [ + [2637, 2582], + [2640, 2582], + [2640, 2615], + [2637, 2615], +] + +[[area.polygons]] +vertices = [ + [2636, 2583], + [2636, 2617], +] + +[[area.polygons]] +vertices = [ + [2635, 2584], + [2635, 2617], +] + +[[area.polygons]] +vertices = [ + [2630, 2585], + [2634, 2585], + [2634, 2602], + [2630, 2602], +] + +[[area.polygons]] +vertices = [ + [2683, 2585], + [2683, 2593], +] + +[[area.polygons]] +vertices = [ + [2629, 2586], + [2629, 2605], +] + +[[area.polygons]] +vertices = [ + [2684, 2587], + [2684, 2593], +] + +[[area.polygons]] +vertices = [ + [2626, 2588], + [2628, 2588], + [2628, 2593], + [2626, 2593], +] + +[[area.polygons]] +vertices = [ + [2685, 2588], + [2685, 2593], +] + +[[area.polygons]] +vertices = [ + [2686, 2591], + [2686, 2592], +] + +[[area.polygons]] +vertices = [ + [2627, 2594], + [2628, 2594], + [2628, 2601], + [2627, 2601], +] + +[[area.polygons]] +vertices = [ + [2626, 2596], + [2626, 2600], +] + +[[area.polygons]] +vertices = [[2625, 2598]] + +[[area.polygons]] +vertices = [ + [2681, 2599], + [2681, 2611], +] + +[[area.polygons]] +vertices = [ + [2682, 2602], + [2682, 2605], +] + +[[area.polygons]] +vertices = [ + [2630, 2603], + [2632, 2603], +] + +[[area.polygons]] +vertices = [ + [2634, 2603], + [2634, 2617], +] + +[[area.polygons]] +vertices = [ + [2683, 2603], + [2684, 2603], + [2684, 2605], + [2683, 2605], +] + +[[area.polygons]] +vertices = [ + [2630, 2604], + [2631, 2604], +] + +[[area.polygons]] +vertices = [[2630, 2605]] + +[[area.polygons]] +vertices = [ + [2633, 2605], + [2633, 2617], +] + +[[area.polygons]] +vertices = [[2669, 2607]] + +[[area.polygons]] +vertices = [ + [2632, 2608], + [2632, 2616], +] + +[[area.polygons]] +vertices = [ + [2644, 2608], + [2645, 2608], +] + +[[area.polygons]] +vertices = [[2654, 2608]] + +[[area.polygons]] +vertices = [ + [2660, 2608], + [2662, 2608], +] + +[[area.polygons]] +vertices = [ + [2631, 2609], + [2631, 2614], +] + +[[area.polygons]] +vertices = [ + [2644, 2609], + [2644, 2617], +] + +[[area.polygons]] +vertices = [ + [2656, 2609], + [2659, 2609], + [2659, 2614], + [2656, 2614], +] + +[[area.polygons]] +vertices = [ + [2671, 2611], + [2672, 2611], + [2672, 2613], + [2671, 2613], +] + +[[area.polygons]] +vertices = [ + [2645, 2615], + [2645, 2617], +] + +[[area.polygons]] +vertices = [ + [2675, 2615], + [2678, 2615], +] + +[[area.polygons]] +vertices = [ + [2637, 2616], + [2638, 2616], +] + +[[area.polygons]] +vertices = [[2637, 2617]] + +[[area.polygons]] +vertices = [ + [1600, 5504], + [1727, 5504], + [1727, 5631], + [1600, 5631], +] diff --git a/.data/raw-cache/map/area/pharaohs_tomb.toml b/.data/raw-cache/map/area/pharaohs_tomb.toml new file mode 100644 index 000000000..e4f7d3a95 --- /dev/null +++ b/.data/raw-cache/map/area/pharaohs_tomb.toml @@ -0,0 +1,117 @@ +# Pharaoh's Tomb music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Pharaoh's Tomb. +# Bounds x 3346-3381, z 2823-2859; 1175 tiles as 18 rectangles. + +[[area]] +name = "Pharaoh's Tomb" +area_id = "area.pharaohs_tomb" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3354, 2823], + [3373, 2823], + [3373, 2858], + [3354, 2858], +] + +[[area.polygons]] +vertices = [ + [3353, 2824], + [3353, 2857], +] + +[[area.polygons]] +vertices = [ + [3374, 2824], + [3374, 2859], +] + +[[area.polygons]] +vertices = [ + [3352, 2825], + [3352, 2856], +] + +[[area.polygons]] +vertices = [ + [3375, 2825], + [3375, 2858], +] + +[[area.polygons]] +vertices = [ + [3376, 2826], + [3376, 2857], +] + +[[area.polygons]] +vertices = [ + [3351, 2827], + [3351, 2855], +] + +[[area.polygons]] +vertices = [ + [3377, 2827], + [3378, 2827], + [3378, 2855], + [3377, 2855], +] + +[[area.polygons]] +vertices = [ + [3379, 2828], + [3379, 2854], +] + +[[area.polygons]] +vertices = [ + [3350, 2829], + [3350, 2854], +] + +[[area.polygons]] +vertices = [ + [3380, 2829], + [3380, 2853], +] + +[[area.polygons]] +vertices = [ + [3349, 2830], + [3349, 2853], +] + +[[area.polygons]] +vertices = [ + [3381, 2830], + [3381, 2852], +] + +[[area.polygons]] +vertices = [ + [3348, 2832], + [3348, 2852], +] + +[[area.polygons]] +vertices = [ + [3347, 2834], + [3347, 2851], +] + +[[area.polygons]] +vertices = [ + [3346, 2835], + [3346, 2850], +] + +[[area.polygons]] +vertices = [[3377, 2856]] + +[[area.polygons]] +vertices = [ + [3355, 2859], + [3373, 2859], +] diff --git a/.data/raw-cache/map/area/phasmatys.toml b/.data/raw-cache/map/area/phasmatys.toml new file mode 100644 index 000000000..9c25fbb12 --- /dev/null +++ b/.data/raw-cache/map/area/phasmatys.toml @@ -0,0 +1,16 @@ +# Phasmatys music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Phasmatys. +# Bounds x 3648-3711, z 9856-9919; 4096 tiles as 1 rectangles. + +[[area]] +name = "Phasmatys" +area_id = "area.phasmatys" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3648, 9856], + [3711, 9856], + [3711, 9919], + [3648, 9919], +] diff --git a/.data/raw-cache/map/area/pheasant_peasant.toml b/.data/raw-cache/map/area/pheasant_peasant.toml new file mode 100644 index 000000000..af99c0c2d --- /dev/null +++ b/.data/raw-cache/map/area/pheasant_peasant.toml @@ -0,0 +1,16 @@ +# Pheasant Peasant music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Pheasant Peasant. +# Bounds x 2560-2623, z 4736-4799; 4096 tiles as 1 rectangles. + +[[area]] +name = "Pheasant Peasant" +area_id = "area.pheasant_peasant" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 4736], + [2623, 4736], + [2623, 4799], + [2560, 4799], +] diff --git a/.data/raw-cache/map/area/pick_and_shovel.toml b/.data/raw-cache/map/area/pick_and_shovel.toml new file mode 100644 index 000000000..da2b49956 --- /dev/null +++ b/.data/raw-cache/map/area/pick_and_shovel.toml @@ -0,0 +1,16 @@ +# Pick & Shovel music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Pick & Shovel. +# Bounds x 3712-3775, z 5632-5695; 4096 tiles as 1 rectangles. + +[[area]] +name = "Pick & Shovel" +area_id = "area.pick_and_shovel" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3712, 5632], + [3775, 5632], + [3775, 5695], + [3712, 5695], +] diff --git a/.data/raw-cache/map/area/pinball_wizard.toml b/.data/raw-cache/map/area/pinball_wizard.toml new file mode 100644 index 000000000..d01dc095c --- /dev/null +++ b/.data/raw-cache/map/area/pinball_wizard.toml @@ -0,0 +1,16 @@ +# Pinball Wizard music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Pinball Wizard. +# Bounds x 1952-1983, z 5024-5055; 1024 tiles as 1 rectangles. + +[[area]] +name = "Pinball Wizard" +area_id = "area.pinball_wizard" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1952, 5024], + [1983, 5024], + [1983, 5055], + [1952, 5055], +] diff --git a/.data/raw-cache/map/area/pirates_of_penance.toml b/.data/raw-cache/map/area/pirates_of_penance.toml new file mode 100644 index 000000000..7e53c2c8c --- /dev/null +++ b/.data/raw-cache/map/area/pirates_of_penance.toml @@ -0,0 +1,16 @@ +# Pirates of Penance music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Pirates of Penance. +# Bounds x 1856-1919, z 5376-5439; 4096 tiles as 1 rectangles. + +[[area]] +name = "Pirates of Penance" +area_id = "area.pirates_of_penance" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 5376], + [1919, 5376], + [1919, 5439], + [1856, 5439], +] diff --git a/.data/raw-cache/map/area/piscatoris_hunter_area.toml b/.data/raw-cache/map/area/piscatoris_hunter_area.toml new file mode 100644 index 000000000..6b256f443 --- /dev/null +++ b/.data/raw-cache/map/area/piscatoris_hunter_area.toml @@ -0,0 +1,802 @@ +# Piscatoris Hunter area music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Joy of the Hunt; On the Wing. +# Bounds x 2259-2404, z 3520-3662; 14831 tiles as 129 rectangles. + +[[area]] +name = "Piscatoris Hunter area" +area_id = "area.piscatoris_hunter_area" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2291, 3520], + [2362, 3520], +] + +[[area.polygons]] +vertices = [ + [2292, 3521], + [2363, 3521], + [2363, 3637], + [2292, 3637], +] + +[[area.polygons]] +vertices = [ + [2364, 3522], + [2366, 3522], + [2366, 3635], + [2364, 3635], +] + +[[area.polygons]] +vertices = [ + [2367, 3523], + [2367, 3634], +] + +[[area.polygons]] +vertices = [ + [2291, 3524], + [2291, 3654], +] + +[[area.polygons]] +vertices = [ + [2368, 3524], + [2368, 3634], +] + +[[area.polygons]] +vertices = [ + [2290, 3525], + [2290, 3654], +] + +[[area.polygons]] +vertices = [ + [2369, 3525], + [2369, 3634], +] + +[[area.polygons]] +vertices = [ + [2288, 3526], + [2289, 3526], + [2289, 3652], + [2288, 3652], +] + +[[area.polygons]] +vertices = [ + [2286, 3527], + [2287, 3527], + [2287, 3649], + [2286, 3649], +] + +[[area.polygons]] +vertices = [ + [2285, 3528], + [2285, 3648], +] + +[[area.polygons]] +vertices = [ + [2284, 3529], + [2284, 3644], +] + +[[area.polygons]] +vertices = [ + [2283, 3531], + [2283, 3643], +] + +[[area.polygons]] +vertices = [ + [2370, 3531], + [2370, 3633], +] + +[[area.polygons]] +vertices = [ + [2371, 3532], + [2371, 3633], +] + +[[area.polygons]] +vertices = [ + [2372, 3533], + [2372, 3633], +] + +[[area.polygons]] +vertices = [ + [2282, 3536], + [2282, 3634], +] + +[[area.polygons]] +vertices = [ + [2281, 3537], + [2281, 3633], +] + +[[area.polygons]] +vertices = [ + [2279, 3538], + [2280, 3538], + [2280, 3604], + [2279, 3604], +] + +[[area.polygons]] +vertices = [ + [2278, 3539], + [2278, 3603], +] + +[[area.polygons]] +vertices = [ + [2373, 3539], + [2373, 3633], +] + +[[area.polygons]] +vertices = [ + [2277, 3540], + [2277, 3602], +] + +[[area.polygons]] +vertices = [ + [2374, 3540], + [2374, 3632], +] + +[[area.polygons]] +vertices = [ + [2276, 3542], + [2276, 3601], +] + +[[area.polygons]] +vertices = [ + [2375, 3542], + [2375, 3632], +] + +[[area.polygons]] +vertices = [ + [2275, 3543], + [2275, 3600], +] + +[[area.polygons]] +vertices = [ + [2376, 3543], + [2376, 3560], +] + +[[area.polygons]] +vertices = [ + [2274, 3544], + [2274, 3599], +] + +[[area.polygons]] +vertices = [ + [2377, 3544], + [2377, 3559], +] + +[[area.polygons]] +vertices = [ + [2273, 3545], + [2273, 3596], +] + +[[area.polygons]] +vertices = [ + [2378, 3545], + [2378, 3558], +] + +[[area.polygons]] +vertices = [ + [2272, 3546], + [2272, 3547], +] + +[[area.polygons]] +vertices = [ + [2379, 3547], + [2379, 3557], +] + +[[area.polygons]] +vertices = [ + [2380, 3548], + [2380, 3549], +] + +[[area.polygons]] +vertices = [ + [2272, 3549], + [2272, 3592], +] + +[[area.polygons]] +vertices = [ + [2271, 3550], + [2271, 3555], +] + +[[area.polygons]] +vertices = [ + [2380, 3551], + [2380, 3556], +] + +[[area.polygons]] +vertices = [ + [2270, 3552], + [2270, 3553], +] + +[[area.polygons]] +vertices = [ + [2381, 3553], + [2381, 3555], +] + +[[area.polygons]] +vertices = [ + [2271, 3557], + [2271, 3558], +] + +[[area.polygons]] +vertices = [ + [2271, 3561], + [2271, 3567], +] + +[[area.polygons]] +vertices = [ + [2270, 3564], + [2270, 3565], +] + +[[area.polygons]] +vertices = [ + [2392, 3565], + [2392, 3609], +] + +[[area.polygons]] +vertices = [ + [2376, 3566], + [2376, 3631], +] + +[[area.polygons]] +vertices = [ + [2390, 3566], + [2391, 3566], + [2391, 3612], + [2390, 3612], +] + +[[area.polygons]] +vertices = [ + [2393, 3566], + [2393, 3608], +] + +[[area.polygons]] +vertices = [ + [2377, 3567], + [2377, 3630], +] + +[[area.polygons]] +vertices = [ + [2394, 3567], + [2394, 3607], +] + +[[area.polygons]] +vertices = [ + [2389, 3568], + [2389, 3613], +] + +[[area.polygons]] +vertices = [ + [2395, 3568], + [2395, 3605], +] + +[[area.polygons]] +vertices = [ + [2271, 3569], + [2271, 3570], +] + +[[area.polygons]] +vertices = [ + [2378, 3569], + [2379, 3569], + [2379, 3627], + [2378, 3627], +] + +[[area.polygons]] +vertices = [ + [2386, 3569], + [2388, 3569], + [2388, 3614], + [2386, 3614], +] + +[[area.polygons]] +vertices = [ + [2396, 3569], + [2397, 3569], + [2397, 3604], + [2396, 3604], +] + +[[area.polygons]] +vertices = [ + [2380, 3570], + [2380, 3627], +] + +[[area.polygons]] +vertices = [ + [2385, 3570], + [2385, 3619], +] + +[[area.polygons]] +vertices = [ + [2398, 3570], + [2398, 3603], +] + +[[area.polygons]] +vertices = [ + [2381, 3571], + [2384, 3571], + [2384, 3619], + [2381, 3619], +] + +[[area.polygons]] +vertices = [ + [2399, 3571], + [2400, 3571], + [2400, 3601], + [2399, 3601], +] + +[[area.polygons]] +vertices = [ + [2401, 3572], + [2401, 3573], +] + +[[area.polygons]] +vertices = [ + [2271, 3573], + [2271, 3591], +] + +[[area.polygons]] +vertices = [ + [2270, 3574], + [2270, 3589], +] + +[[area.polygons]] +vertices = [ + [2401, 3575], + [2401, 3600], +] + +[[area.polygons]] +vertices = [ + [2269, 3576], + [2269, 3577], +] + +[[area.polygons]] +vertices = [ + [2269, 3579], + [2269, 3588], +] + +[[area.polygons]] +vertices = [ + [2402, 3579], + [2402, 3598], +] + +[[area.polygons]] +vertices = [ + [2268, 3580], + [2268, 3587], +] + +[[area.polygons]] +vertices = [ + [2267, 3581], + [2267, 3587], +] + +[[area.polygons]] +vertices = [ + [2266, 3583], + [2266, 3586], +] + +[[area.polygons]] +vertices = [ + [2265, 3584], + [2265, 3585], +] + +[[area.polygons]] +vertices = [ + [2403, 3584], + [2403, 3590], +] + +[[area.polygons]] +vertices = [ + [2404, 3585], + [2404, 3588], +] + +[[area.polygons]] +vertices = [ + [2403, 3592], + [2403, 3597], +] + +[[area.polygons]] +vertices = [ + [2261, 3593], + [2261, 3607], +] + +[[area.polygons]] +vertices = [ + [2260, 3594], + [2260, 3596], +] + +[[area.polygons]] +vertices = [ + [2262, 3594], + [2262, 3612], +] + +[[area.polygons]] +vertices = [ + [2272, 3594], + [2272, 3595], +] + +[[area.polygons]] +vertices = [ + [2404, 3594], + [2404, 3596], +] + +[[area.polygons]] +vertices = [ + [2263, 3595], + [2263, 3613], +] + +[[area.polygons]] +vertices = [ + [2264, 3596], + [2264, 3614], +] + +[[area.polygons]] +vertices = [ + [2265, 3598], + [2265, 3615], +] + +[[area.polygons]] +vertices = [ + [2266, 3599], + [2266, 3600], +] + +[[area.polygons]] +vertices = [ + [2260, 3601], + [2260, 3606], +] + +[[area.polygons]] +vertices = [ + [2259, 3603], + [2259, 3604], +] + +[[area.polygons]] +vertices = [ + [2266, 3603], + [2266, 3616], +] + +[[area.polygons]] +vertices = [ + [2267, 3604], + [2268, 3604], + [2268, 3617], + [2267, 3617], +] + +[[area.polygons]] +vertices = [ + [2269, 3605], + [2269, 3619], +] + +[[area.polygons]] +vertices = [[2280, 3605]] + +[[area.polygons]] +vertices = [ + [2270, 3606], + [2270, 3620], +] + +[[area.polygons]] +vertices = [ + [2271, 3607], + [2271, 3621], +] + +[[area.polygons]] +vertices = [ + [2272, 3608], + [2272, 3622], +] + +[[area.polygons]] +vertices = [ + [2280, 3608], + [2280, 3632], +] + +[[area.polygons]] +vertices = [ + [2273, 3609], + [2274, 3609], + [2274, 3623], + [2273, 3623], +] + +[[area.polygons]] +vertices = [ + [2277, 3609], + [2279, 3609], + [2279, 3627], + [2277, 3627], +] + +[[area.polygons]] +vertices = [ + [2261, 3610], + [2261, 3611], +] + +[[area.polygons]] +vertices = [ + [2275, 3610], + [2276, 3610], + [2276, 3625], + [2275, 3625], +] + +[[area.polygons]] +vertices = [[2390, 3613]] + +[[area.polygons]] +vertices = [ + [2386, 3615], + [2387, 3615], +] + +[[area.polygons]] +vertices = [ + [2386, 3616], + [2386, 3618], +] + +[[area.polygons]] +vertices = [[2268, 3618]] + +[[area.polygons]] +vertices = [ + [2381, 3620], + [2383, 3620], + [2383, 3622], + [2381, 3622], +] + +[[area.polygons]] +vertices = [ + [2381, 3623], + [2382, 3623], +] + +[[area.polygons]] +vertices = [[2274, 3624]] + +[[area.polygons]] +vertices = [ + [2381, 3624], + [2381, 3626], +] + +[[area.polygons]] +vertices = [[2276, 3626]] + +[[area.polygons]] +vertices = [ + [2278, 3628], + [2279, 3628], +] + +[[area.polygons]] +vertices = [ + [2378, 3628], + [2378, 3629], +] + +[[area.polygons]] +vertices = [ + [2279, 3629], + [2279, 3631], +] + +[[area.polygons]] +vertices = [ + [2364, 3636], + [2365, 3636], +] + +[[area.polygons]] +vertices = [ + [2282, 3637], + [2282, 3638], +] + +[[area.polygons]] +vertices = [ + [2292, 3638], + [2362, 3638], +] + +[[area.polygons]] +vertices = [ + [2292, 3639], + [2360, 3639], +] + +[[area.polygons]] +vertices = [ + [2292, 3640], + [2358, 3640], + [2358, 3641], + [2292, 3641], +] + +[[area.polygons]] +vertices = [ + [2282, 3641], + [2282, 3642], +] + +[[area.polygons]] +vertices = [ + [2292, 3642], + [2357, 3642], +] + +[[area.polygons]] +vertices = [ + [2292, 3643], + [2355, 3643], +] + +[[area.polygons]] +vertices = [ + [2292, 3644], + [2354, 3644], +] + +[[area.polygons]] +vertices = [ + [2292, 3645], + [2352, 3645], +] + +[[area.polygons]] +vertices = [ + [2292, 3646], + [2351, 3646], +] + +[[area.polygons]] +vertices = [ + [2292, 3647], + [2349, 3647], +] + +[[area.polygons]] +vertices = [ + [2292, 3648], + [2348, 3648], + [2348, 3655], + [2292, 3655], +] + +[[area.polygons]] +vertices = [ + [2287, 3650], + [2287, 3651], +] + +[[area.polygons]] +vertices = [[2289, 3653]] + +[[area.polygons]] +vertices = [ + [2295, 3656], + [2348, 3656], +] + +[[area.polygons]] +vertices = [ + [2296, 3657], + [2348, 3657], +] + +[[area.polygons]] +vertices = [ + [2298, 3658], + [2348, 3658], +] + +[[area.polygons]] +vertices = [ + [2301, 3659], + [2348, 3659], +] + +[[area.polygons]] +vertices = [ + [2302, 3660], + [2348, 3660], + [2348, 3661], + [2302, 3661], +] + +[[area.polygons]] +vertices = [ + [2303, 3662], + [2349, 3662], +] diff --git a/.data/raw-cache/map/area/poles_apart.toml b/.data/raw-cache/map/area/poles_apart.toml new file mode 100644 index 000000000..7e8f60966 --- /dev/null +++ b/.data/raw-cache/map/area/poles_apart.toml @@ -0,0 +1,494 @@ +# Poles Apart music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Poles Apart. +# Bounds x 2688-2751, z 3743-10239; 7854 tiles as 77 rectangles. + +[[area]] +name = "Poles Apart" +area_id = "area.poles_apart" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2729, 3743], + [2732, 3743], +] + +[[area.polygons]] +vertices = [ + [2728, 3744], + [2731, 3744], + [2731, 3745], + [2728, 3745], +] + +[[area.polygons]] +vertices = [ + [2727, 3746], + [2730, 3746], + [2730, 3747], + [2727, 3747], +] + +[[area.polygons]] +vertices = [ + [2726, 3747], + [2726, 3834], +] + +[[area.polygons]] +vertices = [ + [2724, 3748], + [2725, 3748], + [2725, 3835], + [2724, 3835], +] + +[[area.polygons]] +vertices = [ + [2727, 3748], + [2728, 3748], + [2728, 3752], + [2727, 3752], +] + +[[area.polygons]] +vertices = [ + [2723, 3749], + [2723, 3838], +] + +[[area.polygons]] +vertices = [ + [2729, 3749], + [2729, 3752], +] + +[[area.polygons]] +vertices = [ + [2730, 3750], + [2730, 3751], +] + +[[area.polygons]] +vertices = [ + [2722, 3751], + [2722, 3753], +] + +[[area.polygons]] +vertices = [[2727, 3753]] + +[[area.polygons]] +vertices = [ + [2735, 3755], + [2736, 3755], + [2736, 3758], + [2735, 3758], +] + +[[area.polygons]] +vertices = [ + [2727, 3756], + [2727, 3834], +] + +[[area.polygons]] +vertices = [ + [2737, 3756], + [2737, 3757], +] + +[[area.polygons]] +vertices = [ + [2722, 3757], + [2722, 3838], +] + +[[area.polygons]] +vertices = [ + [2728, 3757], + [2730, 3757], + [2730, 3834], + [2728, 3834], +] + +[[area.polygons]] +vertices = [ + [2734, 3757], + [2734, 3832], +] + +[[area.polygons]] +vertices = [ + [2721, 3758], + [2721, 3838], +] + +[[area.polygons]] +vertices = [ + [2731, 3758], + [2733, 3758], + [2733, 3832], + [2731, 3832], +] + +[[area.polygons]] +vertices = [ + [2719, 3759], + [2720, 3759], + [2720, 3835], + [2719, 3835], +] + +[[area.polygons]] +vertices = [[2735, 3759]] + +[[area.polygons]] +vertices = [ + [2717, 3760], + [2718, 3760], + [2718, 3835], + [2717, 3835], +] + +[[area.polygons]] +vertices = [ + [2715, 3761], + [2716, 3761], + [2716, 3836], + [2715, 3836], +] + +[[area.polygons]] +vertices = [ + [2735, 3761], + [2735, 3831], +] + +[[area.polygons]] +vertices = [ + [2707, 3762], + [2714, 3762], + [2714, 3834], + [2707, 3834], +] + +[[area.polygons]] +vertices = [ + [2736, 3762], + [2736, 3831], +] + +[[area.polygons]] +vertices = [ + [2737, 3763], + [2737, 3831], +] + +[[area.polygons]] +vertices = [ + [2706, 3765], + [2706, 3770], +] + +[[area.polygons]] +vertices = [ + [2738, 3765], + [2738, 3831], +] + +[[area.polygons]] +vertices = [ + [2705, 3766], + [2705, 3769], +] + +[[area.polygons]] +vertices = [ + [2739, 3766], + [2739, 3831], +] + +[[area.polygons]] +vertices = [[2704, 3767]] + +[[area.polygons]] +vertices = [ + [2740, 3767], + [2742, 3767], + [2742, 3772], + [2740, 3772], +] + +[[area.polygons]] +vertices = [ + [2743, 3768], + [2743, 3770], +] + +[[area.polygons]] +vertices = [ + [2706, 3772], + [2706, 3834], +] + +[[area.polygons]] +vertices = [ + [2705, 3773], + [2705, 3833], +] + +[[area.polygons]] +vertices = [ + [2740, 3773], + [2741, 3773], + [2741, 3831], + [2740, 3831], +] + +[[area.polygons]] +vertices = [ + [2704, 3774], + [2704, 3832], +] + +[[area.polygons]] +vertices = [ + [2742, 3774], + [2742, 3831], +] + +[[area.polygons]] +vertices = [ + [2743, 3776], + [2743, 3831], +] + +[[area.polygons]] +vertices = [ + [2744, 3777], + [2745, 3777], + [2745, 3831], + [2744, 3831], +] + +[[area.polygons]] +vertices = [ + [2703, 3778], + [2703, 3832], +] + +[[area.polygons]] +vertices = [ + [2696, 3779], + [2697, 3779], + [2697, 3805], + [2696, 3805], +] + +[[area.polygons]] +vertices = [ + [2702, 3779], + [2702, 3817], +] + +[[area.polygons]] +vertices = [ + [2698, 3780], + [2701, 3780], + [2701, 3808], + [2698, 3808], +] + +[[area.polygons]] +vertices = [ + [2695, 3781], + [2695, 3791], +] + +[[area.polygons]] +vertices = [ + [2694, 3782], + [2694, 3790], +] + +[[area.polygons]] +vertices = [ + [2746, 3782], + [2746, 3831], +] + +[[area.polygons]] +vertices = [ + [2693, 3783], + [2693, 3789], +] + +[[area.polygons]] +vertices = [ + [2747, 3783], + [2747, 3831], +] + +[[area.polygons]] +vertices = [ + [2692, 3784], + [2692, 3788], +] + +[[area.polygons]] +vertices = [ + [2748, 3784], + [2748, 3831], +] + +[[area.polygons]] +vertices = [ + [2691, 3785], + [2691, 3788], +] + +[[area.polygons]] +vertices = [ + [2690, 3786], + [2690, 3788], +] + +[[area.polygons]] +vertices = [ + [2690, 3796], + [2691, 3796], + [2691, 3798], + [2690, 3798], +] + +[[area.polygons]] +vertices = [ + [2692, 3797], + [2695, 3797], + [2695, 3799], + [2692, 3799], +] + +[[area.polygons]] +vertices = [ + [2693, 3800], + [2695, 3800], + [2695, 3804], + [2693, 3804], +] + +[[area.polygons]] +vertices = [ + [2692, 3801], + [2692, 3805], +] + +[[area.polygons]] +vertices = [ + [2691, 3802], + [2691, 3805], +] + +[[area.polygons]] +vertices = [ + [2690, 3803], + [2690, 3805], +] + +[[area.polygons]] +vertices = [ + [2749, 3803], + [2751, 3803], + [2751, 3831], + [2749, 3831], +] + +[[area.polygons]] +vertices = [ + [2694, 3805], + [2695, 3805], +] + +[[area.polygons]] +vertices = [ + [2697, 3806], + [2697, 3807], +] + +[[area.polygons]] +vertices = [ + [2700, 3809], + [2701, 3809], +] + +[[area.polygons]] +vertices = [ + [2702, 3819], + [2702, 3832], +] + +[[area.polygons]] +vertices = [ + [2701, 3820], + [2701, 3823], +] + +[[area.polygons]] +vertices = [ + [2701, 3825], + [2701, 3831], +] + +[[area.polygons]] +vertices = [ + [2700, 3826], + [2700, 3830], +] + +[[area.polygons]] +vertices = [ + [2731, 3833], + [2732, 3833], +] + +[[area.polygons]] +vertices = [[2731, 3834]] + +[[area.polygons]] +vertices = [ + [2707, 3835], + [2711, 3835], +] + +[[area.polygons]] +vertices = [[2714, 3835]] + +[[area.polygons]] +vertices = [ + [2728, 3835], + [2729, 3835], +] + +[[area.polygons]] +vertices = [ + [2707, 3836], + [2708, 3836], +] + +[[area.polygons]] +vertices = [[2720, 3836]] + +[[area.polygons]] +vertices = [ + [2724, 3836], + [2724, 3837], +] + +[[area.polygons]] +vertices = [ + [2688, 10176], + [2751, 10176], + [2751, 10239], + [2688, 10239], +] diff --git a/.data/raw-cache/map/area/port_sarim.toml b/.data/raw-cache/map/area/port_sarim.toml new file mode 100644 index 000000000..3ee85125c --- /dev/null +++ b/.data/raw-cache/map/area/port_sarim.toml @@ -0,0 +1,356 @@ +# Port Sarim from the Falador south wall down past the church and Thurgo's peninsula to the edge +# of Mudskipper Point (mudskipper_point.toml starts at z 3141). Music area for "Sea Shanty 2". +# +# Regenerated as rectangles from the OSRS wiki music map: 2151 tiles moved to +# neighbouring music areas and 240 tiles were added. + +[[area]] +name = "Port Sarim" +area_id = "area.port_sarim" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2991, 3142], + [2994, 3142], +] + +[[area.polygons]] +vertices = [ + [3016, 3142], + [3060, 3142], +] + +[[area.polygons]] +vertices = [ + [2991, 3143], + [2993, 3143], +] + +[[area.polygons]] +vertices = [ + [3018, 3143], + [3060, 3143], +] + +[[area.polygons]] +vertices = [ + [2991, 3144], + [2992, 3144], + [2992, 3163], + [2991, 3163], +] + +[[area.polygons]] +vertices = [ + [3021, 3144], + [3060, 3144], +] + +[[area.polygons]] +vertices = [ + [3022, 3145], + [3060, 3145], + [3060, 3150], + [3022, 3150], +] + +[[area.polygons]] +vertices = [ + [2993, 3148], + [2993, 3162], +] + +[[area.polygons]] +vertices = [[3021, 3148]] + +[[area.polygons]] +vertices = [ + [2994, 3150], + [2994, 3162], +] + +[[area.polygons]] +vertices = [ + [3023, 3151], + [3060, 3151], +] + +[[area.polygons]] +vertices = [ + [2995, 3152], + [2995, 3161], +] + +[[area.polygons]] +vertices = [ + [3024, 3152], + [3060, 3152], + [3060, 3153], + [3024, 3153], +] + +[[area.polygons]] +vertices = [ + [3026, 3154], + [3060, 3154], +] + +[[area.polygons]] +vertices = [ + [3028, 3155], + [3060, 3155], +] + +[[area.polygons]] +vertices = [ + [2996, 3156], + [2996, 3160], +] + +[[area.polygons]] +vertices = [ + [3029, 3156], + [3060, 3156], +] + +[[area.polygons]] +vertices = [ + [3030, 3157], + [3060, 3157], +] + +[[area.polygons]] +vertices = [ + [3032, 3158], + [3060, 3158], +] + +[[area.polygons]] +vertices = [ + [3033, 3159], + [3060, 3159], +] + +[[area.polygons]] +vertices = [ + [3034, 3160], + [3060, 3160], + [3060, 3161], + [3034, 3161], +] + +[[area.polygons]] +vertices = [ + [3035, 3162], + [3060, 3162], + [3060, 3163], + [3035, 3163], +] + +[[area.polygons]] +vertices = [[2991, 3164]] + +[[area.polygons]] +vertices = [ + [3036, 3164], + [3060, 3164], +] + +[[area.polygons]] +vertices = [ + [3037, 3165], + [3060, 3165], + [3060, 3263], + [3037, 3263], +] + +[[area.polygons]] +vertices = [ + [3036, 3167], + [3036, 3263], +] + +[[area.polygons]] +vertices = [ + [3035, 3168], + [3035, 3169], +] + +[[area.polygons]] +vertices = [ + [3035, 3173], + [3035, 3263], +] + +[[area.polygons]] +vertices = [ + [3034, 3175], + [3034, 3263], +] + +[[area.polygons]] +vertices = [ + [3033, 3177], + [3033, 3263], +] + +[[area.polygons]] +vertices = [ + [3032, 3178], + [3032, 3263], +] + +[[area.polygons]] +vertices = [ + [3031, 3179], + [3031, 3263], +] + +[[area.polygons]] +vertices = [ + [3030, 3181], + [3030, 3263], +] + +[[area.polygons]] +vertices = [ + [3029, 3182], + [3029, 3263], +] + +[[area.polygons]] +vertices = [ + [3028, 3183], + [3028, 3184], +] + +[[area.polygons]] +vertices = [ + [3028, 3187], + [3028, 3263], +] + +[[area.polygons]] +vertices = [ + [3027, 3188], + [3027, 3263], +] + +[[area.polygons]] +vertices = [ + [3026, 3190], + [3026, 3263], +] + +[[area.polygons]] +vertices = [ + [3025, 3191], + [3025, 3263], +] + +[[area.polygons]] +vertices = [ + [3024, 3192], + [3024, 3263], +] + +[[area.polygons]] +vertices = [ + [3023, 3195], + [3023, 3263], +] + +[[area.polygons]] +vertices = [ + [3022, 3196], + [3022, 3263], +] + +[[area.polygons]] +vertices = [ + [3021, 3197], + [3021, 3263], +] + +[[area.polygons]] +vertices = [ + [3020, 3199], + [3020, 3263], +] + +[[area.polygons]] +vertices = [ + [3007, 3200], + [3019, 3200], + [3019, 3263], + [3007, 3263], +] + +[[area.polygons]] +vertices = [ + [3006, 3202], + [3006, 3261], +] + +[[area.polygons]] +vertices = [ + [2991, 3203], + [2993, 3203], + [2993, 3260], + [2991, 3260], +] + +[[area.polygons]] +vertices = [ + [2994, 3204], + [2997, 3204], + [2997, 3260], + [2994, 3260], +] + +[[area.polygons]] +vertices = [ + [3005, 3204], + [3005, 3260], +] + +[[area.polygons]] +vertices = [ + [2998, 3206], + [3004, 3206], + [3004, 3260], + [2998, 3260], +] + +[[area.polygons]] +vertices = [ + [3064, 3250], + [3064, 3263], +] + +[[area.polygons]] +vertices = [ + [3061, 3251], + [3063, 3251], + [3063, 3263], + [3061, 3263], +] + +[[area.polygons]] +vertices = [ + [3065, 3255], + [3065, 3263], +] + +[[area.polygons]] +vertices = [ + [3066, 3256], + [3066, 3263], +] + +[[area.polygons]] +vertices = [ + [3067, 3257], + [3067, 3263], +] diff --git a/.data/raw-cache/map/area/power_of_the_shadow_realm.toml b/.data/raw-cache/map/area/power_of_the_shadow_realm.toml new file mode 100644 index 000000000..11890e488 --- /dev/null +++ b/.data/raw-cache/map/area/power_of_the_shadow_realm.toml @@ -0,0 +1,16 @@ +# Power of the Shadow Realm music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Power of the Shadow Realm. +# Bounds x 3264-3327, z 4288-4351; 4096 tiles as 1 rectangles. + +[[area]] +name = "Power of the Shadow Realm" +area_id = "area.power_of_the_shadow_realm" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 4288], + [3327, 4288], + [3327, 4351], + [3264, 4351], +] diff --git a/.data/raw-cache/map/area/predator_xarpus.toml b/.data/raw-cache/map/area/predator_xarpus.toml new file mode 100644 index 000000000..5a8917306 --- /dev/null +++ b/.data/raw-cache/map/area/predator_xarpus.toml @@ -0,0 +1,16 @@ +# Predator Xarpus music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Predator Xarpus (plus xarpus_combat from wiki polygons sharing this footprint). +# Bounds x 3136-3199, z 4352-4415; 4096 tiles as 1 rectangles. + +[[area]] +name = "Predator Xarpus" +area_id = "area.predator_xarpus" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 4352], + [3199, 4352], + [3199, 4415], + [3136, 4415], +] diff --git a/.data/raw-cache/map/area/preservation.toml b/.data/raw-cache/map/area/preservation.toml new file mode 100644 index 000000000..224283c26 --- /dev/null +++ b/.data/raw-cache/map/area/preservation.toml @@ -0,0 +1,204 @@ +# Preservation music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Preservation. +# Bounds x 3648-3839, z 3782-3903; 16599 tiles as 30 rectangles. + +[[area]] +name = "Preservation" +area_id = "area.preservation" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3714, 3782], + [3727, 3782], + [3727, 3903], + [3714, 3903], +] + +[[area.polygons]] +vertices = [ + [3743, 3793], + [3757, 3793], + [3757, 3903], + [3743, 3903], +] + +[[area.polygons]] +vertices = [ + [3728, 3794], + [3732, 3794], + [3732, 3903], + [3728, 3903], +] + +[[area.polygons]] +vertices = [ + [3741, 3794], + [3742, 3794], + [3742, 3903], + [3741, 3903], +] + +[[area.polygons]] +vertices = [ + [3758, 3794], + [3758, 3903], +] + +[[area.polygons]] +vertices = [ + [3733, 3795], + [3740, 3795], + [3740, 3903], + [3733, 3903], +] + +[[area.polygons]] +vertices = [ + [3759, 3795], + [3759, 3903], +] + +[[area.polygons]] +vertices = [ + [3760, 3796], + [3760, 3903], +] + +[[area.polygons]] +vertices = [ + [3761, 3798], + [3761, 3903], +] + +[[area.polygons]] +vertices = [ + [3762, 3799], + [3762, 3903], +] + +[[area.polygons]] +vertices = [ + [3704, 3800], + [3713, 3800], + [3713, 3903], + [3704, 3903], +] + +[[area.polygons]] +vertices = [ + [3763, 3800], + [3763, 3825], +] + +[[area.polygons]] +vertices = [ + [3703, 3801], + [3703, 3903], +] + +[[area.polygons]] +vertices = [ + [3764, 3801], + [3764, 3804], +] + +[[area.polygons]] +vertices = [ + [3702, 3802], + [3702, 3903], +] + +[[area.polygons]] +vertices = [ + [3701, 3803], + [3701, 3903], +] + +[[area.polygons]] +vertices = [ + [3700, 3804], + [3700, 3903], +] + +[[area.polygons]] +vertices = [ + [3699, 3805], + [3699, 3903], +] + +[[area.polygons]] +vertices = [ + [3698, 3806], + [3698, 3903], +] + +[[area.polygons]] +vertices = [ + [3697, 3807], + [3697, 3903], +] + +[[area.polygons]] +vertices = [ + [3696, 3808], + [3696, 3903], +] + +[[area.polygons]] +vertices = [ + [3695, 3809], + [3695, 3903], +] + +[[area.polygons]] +vertices = [ + [3694, 3810], + [3694, 3903], +] + +[[area.polygons]] +vertices = [ + [3693, 3811], + [3693, 3903], +] + +[[area.polygons]] +vertices = [ + [3692, 3812], + [3692, 3903], +] + +[[area.polygons]] +vertices = [ + [3691, 3813], + [3691, 3903], +] + +[[area.polygons]] +vertices = [ + [3690, 3814], + [3690, 3903], +] + +[[area.polygons]] +vertices = [ + [3689, 3815], + [3689, 3903], +] + +[[area.polygons]] +vertices = [ + [3648, 3816], + [3688, 3816], + [3688, 3903], + [3648, 3903], +] + +[[area.polygons]] +vertices = [ + [3763, 3840], + [3839, 3840], + [3839, 3903], + [3763, 3903], +] diff --git a/.data/raw-cache/map/area/preserved.toml b/.data/raw-cache/map/area/preserved.toml new file mode 100644 index 000000000..098bb9d1d --- /dev/null +++ b/.data/raw-cache/map/area/preserved.toml @@ -0,0 +1,212 @@ +# Preserved music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Preserved. +# Bounds x 3520-3839, z 3712-4031; 28457 tiles as 32 rectangles. + +[[area]] +name = "Preserved" +area_id = "area.preserved" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3648, 3712], + [3839, 3712], + [3839, 3781], + [3648, 3781], +] + +[[area.polygons]] +vertices = [ + [3648, 3782], + [3713, 3782], + [3713, 3799], + [3648, 3799], +] + +[[area.polygons]] +vertices = [ + [3728, 3782], + [3839, 3782], + [3839, 3792], + [3728, 3792], +] + +[[area.polygons]] +vertices = [ + [3728, 3793], + [3742, 3793], +] + +[[area.polygons]] +vertices = [ + [3758, 3793], + [3839, 3793], +] + +[[area.polygons]] +vertices = [ + [3733, 3794], + [3740, 3794], +] + +[[area.polygons]] +vertices = [ + [3759, 3794], + [3839, 3794], +] + +[[area.polygons]] +vertices = [ + [3760, 3795], + [3839, 3795], +] + +[[area.polygons]] +vertices = [ + [3761, 3796], + [3839, 3796], + [3839, 3797], + [3761, 3797], +] + +[[area.polygons]] +vertices = [ + [3762, 3798], + [3839, 3798], +] + +[[area.polygons]] +vertices = [ + [3763, 3799], + [3839, 3799], +] + +[[area.polygons]] +vertices = [ + [3648, 3800], + [3703, 3800], +] + +[[area.polygons]] +vertices = [ + [3764, 3800], + [3839, 3800], +] + +[[area.polygons]] +vertices = [ + [3648, 3801], + [3702, 3801], +] + +[[area.polygons]] +vertices = [ + [3765, 3801], + [3839, 3801], + [3839, 3839], + [3765, 3839], +] + +[[area.polygons]] +vertices = [ + [3648, 3802], + [3701, 3802], +] + +[[area.polygons]] +vertices = [ + [3648, 3803], + [3700, 3803], +] + +[[area.polygons]] +vertices = [ + [3648, 3804], + [3699, 3804], +] + +[[area.polygons]] +vertices = [ + [3648, 3805], + [3698, 3805], +] + +[[area.polygons]] +vertices = [ + [3764, 3805], + [3764, 3839], +] + +[[area.polygons]] +vertices = [ + [3648, 3806], + [3697, 3806], +] + +[[area.polygons]] +vertices = [ + [3648, 3807], + [3696, 3807], +] + +[[area.polygons]] +vertices = [ + [3648, 3808], + [3695, 3808], +] + +[[area.polygons]] +vertices = [ + [3648, 3809], + [3694, 3809], +] + +[[area.polygons]] +vertices = [ + [3648, 3810], + [3693, 3810], +] + +[[area.polygons]] +vertices = [ + [3648, 3811], + [3692, 3811], +] + +[[area.polygons]] +vertices = [ + [3648, 3812], + [3691, 3812], +] + +[[area.polygons]] +vertices = [ + [3648, 3813], + [3690, 3813], +] + +[[area.polygons]] +vertices = [ + [3648, 3814], + [3689, 3814], +] + +[[area.polygons]] +vertices = [ + [3648, 3815], + [3688, 3815], +] + +[[area.polygons]] +vertices = [ + [3763, 3826], + [3763, 3839], +] + +[[area.polygons]] +vertices = [ + [3520, 3968], + [3647, 3968], + [3647, 4031], + [3520, 4031], +] diff --git a/.data/raw-cache/map/area/prifddinas.toml b/.data/raw-cache/map/area/prifddinas.toml new file mode 100644 index 000000000..fe4d0f1b1 --- /dev/null +++ b/.data/raw-cache/map/area/prifddinas.toml @@ -0,0 +1,16 @@ +# Prifddinas music music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Architects of Prifddinas; Dance of the Meilyr; Elven Guardians; Faith of the Hefin; Iorwerth's Lament; Mystics of Nature; The Seed of Crwys; The Tower of Voices; Trahaearn Toil. +# Bounds x 3200-3327, z 6016-6143; 16384 tiles as 1 rectangles. + +[[area]] +name = "Prifddinas music" +area_id = "area.prifddinas" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3200, 6016], + [3327, 6016], + [3327, 6143], + [3200, 6143], +] diff --git a/.data/raw-cache/map/area/prime_time.toml b/.data/raw-cache/map/area/prime_time.toml new file mode 100644 index 000000000..fae4755f5 --- /dev/null +++ b/.data/raw-cache/map/area/prime_time.toml @@ -0,0 +1,16 @@ +# Prime Time music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Prime Time. +# Bounds x 1920-1983, z 5120-5183; 4096 tiles as 1 rectangles. + +[[area]] +name = "Prime Time" +area_id = "area.prime_time" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1920, 5120], + [1983, 5120], + [1983, 5183], + [1920, 5183], +] diff --git a/.data/raw-cache/map/area/prospering_fortune.toml b/.data/raw-cache/map/area/prospering_fortune.toml new file mode 100644 index 000000000..18a913df1 --- /dev/null +++ b/.data/raw-cache/map/area/prospering_fortune.toml @@ -0,0 +1,32 @@ +# Prospering Fortune music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Prospering Fortune. +# Bounds x 1600-1855, z 9472-9599; 12288 tiles as 3 rectangles. + +[[area]] +name = "Prospering Fortune" +area_id = "area.prospering_fortune" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1664, 9472], + [1727, 9472], + [1727, 9535], + [1664, 9535], +] + +[[area.polygons]] +vertices = [ + [1792, 9472], + [1855, 9472], + [1855, 9535], + [1792, 9535], +] + +[[area.polygons]] +vertices = [ + [1600, 9536], + [1663, 9536], + [1663, 9599], + [1600, 9599], +] diff --git a/.data/raw-cache/map/area/quest.toml b/.data/raw-cache/map/area/quest.toml new file mode 100644 index 000000000..f3f78a91f --- /dev/null +++ b/.data/raw-cache/map/area/quest.toml @@ -0,0 +1,195 @@ +# Quest music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Quest. +# Bounds x 2129-2623, z 3525-4863; 4953 tiles as 30 rectangles. + +[[area]] +name = "Quest" +area_id = "area.quest" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2159, 3525], + [2163, 3525], + [2163, 3532], + [2159, 3532], +] + +[[area.polygons]] +vertices = [ + [2157, 3526], + [2158, 3526], + [2158, 3542], + [2157, 3542], +] + +[[area.polygons]] +vertices = [ + [2164, 3526], + [2164, 3531], +] + +[[area.polygons]] +vertices = [ + [2156, 3527], + [2156, 3551], +] + +[[area.polygons]] +vertices = [ + [2165, 3527], + [2165, 3530], +] + +[[area.polygons]] +vertices = [ + [2155, 3528], + [2155, 3555], +] + +[[area.polygons]] +vertices = [ + [2153, 3529], + [2154, 3529], + [2154, 3556], + [2153, 3556], +] + +[[area.polygons]] +vertices = [ + [2148, 3530], + [2148, 3563], +] + +[[area.polygons]] +vertices = [ + [2152, 3530], + [2152, 3559], +] + +[[area.polygons]] +vertices = [ + [2147, 3531], + [2147, 3563], +] + +[[area.polygons]] +vertices = [ + [2149, 3531], + [2151, 3531], + [2151, 3560], + [2149, 3560], +] + +[[area.polygons]] +vertices = [ + [2145, 3532], + [2146, 3532], + [2146, 3564], + [2145, 3564], +] + +[[area.polygons]] +vertices = [ + [2144, 3533], + [2144, 3565], +] + +[[area.polygons]] +vertices = [ + [2159, 3533], + [2161, 3533], +] + +[[area.polygons]] +vertices = [ + [2143, 3534], + [2143, 3565], +] + +[[area.polygons]] +vertices = [[2159, 3534]] + +[[area.polygons]] +vertices = [ + [2142, 3536], + [2142, 3565], +] + +[[area.polygons]] +vertices = [ + [2137, 3537], + [2141, 3537], + [2141, 3565], + [2137, 3565], +] + +[[area.polygons]] +vertices = [ + [2134, 3538], + [2136, 3538], + [2136, 3563], + [2134, 3563], +] + +[[area.polygons]] +vertices = [ + [2133, 3539], + [2133, 3562], +] + +[[area.polygons]] +vertices = [ + [2132, 3540], + [2132, 3561], +] + +[[area.polygons]] +vertices = [ + [2157, 3543], + [2157, 3548], +] + +[[area.polygons]] +vertices = [ + [2131, 3544], + [2131, 3559], +] + +[[area.polygons]] +vertices = [ + [2130, 3548], + [2130, 3558], +] + +[[area.polygons]] +vertices = [ + [2129, 3549], + [2129, 3552], +] + +[[area.polygons]] +vertices = [ + [2153, 3557], + [2153, 3558], +] + +[[area.polygons]] +vertices = [ + [2149, 3561], + [2150, 3561], +] + +[[area.polygons]] +vertices = [[2149, 3562]] + +[[area.polygons]] +vertices = [[2136, 3564]] + +[[area.polygons]] +vertices = [ + [2560, 4800], + [2623, 4800], + [2623, 4863], + [2560, 4863], +] diff --git a/.data/raw-cache/map/area/race_against_the_clock.toml b/.data/raw-cache/map/area/race_against_the_clock.toml new file mode 100644 index 000000000..a006b067f --- /dev/null +++ b/.data/raw-cache/map/area/race_against_the_clock.toml @@ -0,0 +1,16 @@ +# Race Against the Clock music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Race Against the Clock. +# Bounds x 2944-3007, z 5824-5887; 4096 tiles as 1 rectangles. + +[[area]] +name = "Race Against the Clock" +area_id = "area.race_against_the_clock" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2944, 5824], + [3007, 5824], + [3007, 5887], + [2944, 5887], +] diff --git a/.data/raw-cache/map/area/rat_a_tat_tat.toml b/.data/raw-cache/map/area/rat_a_tat_tat.toml new file mode 100644 index 000000000..57424c5c0 --- /dev/null +++ b/.data/raw-cache/map/area/rat_a_tat_tat.toml @@ -0,0 +1,16 @@ +# Rat a Tat Tat music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Rat a Tat Tat. +# Bounds x 2880-2943, z 5056-5119; 4096 tiles as 1 rectangles. + +[[area]] +name = "Rat a Tat Tat" +area_id = "area.rat_a_tat_tat" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2880, 5056], + [2943, 5056], + [2943, 5119], + [2880, 5119], +] diff --git a/.data/raw-cache/map/area/rat_hunt.toml b/.data/raw-cache/map/area/rat_hunt.toml new file mode 100644 index 000000000..92f2fa042 --- /dev/null +++ b/.data/raw-cache/map/area/rat_hunt.toml @@ -0,0 +1,16 @@ +# Rat Hunt music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Rat Hunt. +# Bounds x 2816-2879, z 5056-5119; 4096 tiles as 1 rectangles. + +[[area]] +name = "Rat Hunt" +area_id = "area.rat_hunt" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 5056], + [2879, 5056], + [2879, 5119], + [2816, 5119], +] diff --git a/.data/raw-cache/map/area/ready_for_the_hunt.toml b/.data/raw-cache/map/area/ready_for_the_hunt.toml new file mode 100644 index 000000000..f4aaa9cc7 --- /dev/null +++ b/.data/raw-cache/map/area/ready_for_the_hunt.toml @@ -0,0 +1,166 @@ +# Ready for the Hunt music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Ready for the Hunt. +# Bounds x 1536-1599, z 3027-9471; 5622 tiles as 25 rectangles. + +[[area]] +name = "Ready for the Hunt" +area_id = "area.ready_for_the_hunt" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1548, 3027], + [1565, 3027], + [1565, 3064], + [1548, 3064], +] + +[[area.polygons]] +vertices = [ + [1547, 3028], + [1547, 3064], +] + +[[area.polygons]] +vertices = [ + [1566, 3028], + [1566, 3064], +] + +[[area.polygons]] +vertices = [ + [1546, 3029], + [1546, 3064], +] + +[[area.polygons]] +vertices = [ + [1567, 3029], + [1567, 3063], +] + +[[area.polygons]] +vertices = [ + [1545, 3030], + [1545, 3063], +] + +[[area.polygons]] +vertices = [ + [1568, 3030], + [1568, 3062], +] + +[[area.polygons]] +vertices = [ + [1544, 3031], + [1544, 3062], +] + +[[area.polygons]] +vertices = [ + [1569, 3031], + [1569, 3061], +] + +[[area.polygons]] +vertices = [ + [1543, 3032], + [1543, 3061], +] + +[[area.polygons]] +vertices = [ + [1570, 3032], + [1570, 3060], +] + +[[area.polygons]] +vertices = [ + [1542, 3033], + [1542, 3060], +] + +[[area.polygons]] +vertices = [ + [1571, 3033], + [1571, 3059], +] + +[[area.polygons]] +vertices = [ + [1541, 3034], + [1541, 3059], +] + +[[area.polygons]] +vertices = [ + [1572, 3034], + [1572, 3058], +] + +[[area.polygons]] +vertices = [ + [1540, 3035], + [1540, 3058], +] + +[[area.polygons]] +vertices = [ + [1573, 3035], + [1573, 3058], +] + +[[area.polygons]] +vertices = [ + [1539, 3036], + [1539, 3057], +] + +[[area.polygons]] +vertices = [ + [1574, 3036], + [1587, 3036], + [1587, 3055], + [1574, 3055], +] + +[[area.polygons]] +vertices = [ + [1588, 3040], + [1589, 3040], + [1589, 3051], + [1588, 3051], +] + +[[area.polygons]] +vertices = [ + [1588, 3052], + [1588, 3055], +] + +[[area.polygons]] +vertices = [ + [1574, 3056], + [1582, 3056], +] + +[[area.polygons]] +vertices = [ + [1574, 3057], + [1581, 3057], +] + +[[area.polygons]] +vertices = [ + [1574, 3058], + [1580, 3058], +] + +[[area.polygons]] +vertices = [ + [1536, 9408], + [1599, 9408], + [1599, 9471], + [1536, 9471], +] diff --git a/.data/raw-cache/map/area/regal_pomp.toml b/.data/raw-cache/map/area/regal_pomp.toml new file mode 100644 index 000000000..4c96ae73e --- /dev/null +++ b/.data/raw-cache/map/area/regal_pomp.toml @@ -0,0 +1,16 @@ +# Regal Pomp music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Regal Pomp. +# Bounds x 2048-2111, z 6144-6207; 4096 tiles as 1 rectangles. + +[[area]] +name = "Regal Pomp" +area_id = "area.regal_pomp" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2048, 6144], + [2111, 6144], + [2111, 6207], + [2048, 6207], +] diff --git a/.data/raw-cache/map/area/relics.toml b/.data/raw-cache/map/area/relics.toml new file mode 100644 index 000000000..f6bb14689 --- /dev/null +++ b/.data/raw-cache/map/area/relics.toml @@ -0,0 +1,32 @@ +# Relics music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Relics. +# Bounds x 2240-3583, z 9664-10495; 16384 tiles as 3 rectangles. + +[[area]] +name = "Relics" +area_id = "area.relics" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 9664], + [2879, 9664], + [2879, 9727], + [2816, 9727], +] + +[[area.polygons]] +vertices = [ + [3520, 10368], + [3583, 10368], + [3583, 10495], + [3520, 10495], +] + +[[area.polygons]] +vertices = [ + [2240, 10432], + [2303, 10432], + [2303, 10495], + [2240, 10495], +] diff --git a/.data/raw-cache/map/area/rellekka.toml b/.data/raw-cache/map/area/rellekka.toml new file mode 100644 index 000000000..4cb8a969f --- /dev/null +++ b/.data/raw-cache/map/area/rellekka.toml @@ -0,0 +1,355 @@ +# Regenerated as rectangles from the OSRS wiki music map: 5426 tiles moved to +# neighbouring music areas and 314 tiles were added. + +[[area]] +name = "Rellekka" +area_id = "area.rellekka" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2646, 3644], + [2649, 3644], + [2649, 3720], + [2646, 3720], +] + +[[area.polygons]] +vertices = [ + [2653, 3644], + [2656, 3644], + [2656, 3720], + [2653, 3720], +] + +[[area.polygons]] +vertices = [ + [2670, 3644], + [2672, 3644], + [2672, 3720], + [2670, 3720], +] + +[[area.polygons]] +vertices = [ + [2636, 3645], + [2645, 3645], + [2645, 3720], + [2636, 3720], +] + +[[area.polygons]] +vertices = [ + [2650, 3645], + [2652, 3645], + [2652, 3720], + [2650, 3720], +] + +[[area.polygons]] +vertices = [ + [2657, 3645], + [2669, 3645], + [2669, 3720], + [2657, 3720], +] + +[[area.polygons]] +vertices = [ + [2673, 3645], + [2685, 3645], + [2685, 3721], + [2673, 3721], +] + +[[area.polygons]] +vertices = [ + [2627, 3646], + [2635, 3646], + [2635, 3720], + [2627, 3720], +] + +[[area.polygons]] +vertices = [ + [2686, 3646], + [2687, 3646], + [2687, 3732], + [2686, 3732], +] + +[[area.polygons]] +vertices = [ + [2625, 3647], + [2626, 3647], + [2626, 3720], + [2625, 3720], +] + +[[area.polygons]] +vertices = [ + [2688, 3647], + [2689, 3647], + [2689, 3728], + [2688, 3728], +] + +[[area.polygons]] +vertices = [ + [2623, 3648], + [2624, 3648], + [2624, 3720], + [2623, 3720], +] + +[[area.polygons]] +vertices = [ + [2690, 3648], + [2691, 3648], + [2691, 3657], + [2690, 3657], +] + +[[area.polygons]] +vertices = [ + [2621, 3649], + [2622, 3649], + [2622, 3720], + [2621, 3720], +] + +[[area.polygons]] +vertices = [ + [2692, 3649], + [2692, 3655], +] + +[[area.polygons]] +vertices = [ + [2620, 3650], + [2620, 3720], +] + +[[area.polygons]] +vertices = [ + [2618, 3651], + [2619, 3651], + [2619, 3720], + [2618, 3720], +] + +[[area.polygons]] +vertices = [ + [2693, 3651], + [2693, 3653], +] + +[[area.polygons]] +vertices = [ + [2600, 3652], + [2600, 3720], +] + +[[area.polygons]] +vertices = [ + [2616, 3652], + [2617, 3652], + [2617, 3720], + [2616, 3720], +] + +[[area.polygons]] +vertices = [ + [2609, 3653], + [2615, 3653], + [2615, 3720], + [2609, 3720], +] + +[[area.polygons]] +vertices = [ + [2601, 3654], + [2601, 3720], +] + +[[area.polygons]] +vertices = [ + [2607, 3654], + [2608, 3654], + [2608, 3720], + [2607, 3720], +] + +[[area.polygons]] +vertices = [ + [2602, 3655], + [2606, 3655], + [2606, 3720], + [2602, 3720], +] + +[[area.polygons]] +vertices = [ + [2690, 3658], + [2690, 3659], +] + +[[area.polygons]] +vertices = [ + [2690, 3664], + [2690, 3672], +] + +[[area.polygons]] +vertices = [ + [2691, 3666], + [2691, 3670], +] + +[[area.polygons]] +vertices = [ + [2690, 3679], + [2690, 3728], +] + +[[area.polygons]] +vertices = [ + [2691, 3681], + [2691, 3694], +] + +[[area.polygons]] +vertices = [ + [2692, 3683], + [2692, 3692], +] + +[[area.polygons]] +vertices = [ + [2693, 3685], + [2693, 3690], +] + +[[area.polygons]] +vertices = [ + [2691, 3699], + [2691, 3715], +] + +[[area.polygons]] +vertices = [ + [2692, 3701], + [2692, 3708], +] + +[[area.polygons]] +vertices = [ + [2693, 3703], + [2693, 3706], +] + +[[area.polygons]] +vertices = [ + [2691, 3720], + [2691, 3727], +] + +[[area.polygons]] +vertices = [ + [2668, 3722], + [2672, 3722], + [2672, 3732], + [2668, 3732], +] + +[[area.polygons]] +vertices = [ + [2674, 3722], + [2685, 3722], +] + +[[area.polygons]] +vertices = [ + [2667, 3723], + [2667, 3731], +] + +[[area.polygons]] +vertices = [ + [2673, 3723], + [2673, 3734], +] + +[[area.polygons]] +vertices = [ + [2675, 3723], + [2685, 3723], +] + +[[area.polygons]] +vertices = [ + [2666, 3724], + [2666, 3730], +] + +[[area.polygons]] +vertices = [ + [2674, 3724], + [2674, 3734], +] + +[[area.polygons]] +vertices = [ + [2679, 3724], + [2685, 3724], + [2685, 3734], + [2679, 3734], +] + +[[area.polygons]] +vertices = [ + [2675, 3725], + [2675, 3734], +] + +[[area.polygons]] +vertices = [ + [2678, 3725], + [2678, 3735], +] + +[[area.polygons]] +vertices = [[2665, 3726]] + +[[area.polygons]] +vertices = [ + [2677, 3726], + [2677, 3735], +] + +[[area.polygons]] +vertices = [ + [2676, 3727], + [2676, 3734], +] + +[[area.polygons]] +vertices = [[2688, 3729]] + +[[area.polygons]] +vertices = [ + [2670, 3733], + [2672, 3733], +] + +[[area.polygons]] +vertices = [[2686, 3733]] + +[[area.polygons]] +vertices = [[2672, 3734]] + +[[area.polygons]] +vertices = [ + [2679, 3735], + [2684, 3735], +] diff --git a/.data/raw-cache/map/area/remnants.toml b/.data/raw-cache/map/area/remnants.toml new file mode 100644 index 000000000..ff6d391d8 --- /dev/null +++ b/.data/raw-cache/map/area/remnants.toml @@ -0,0 +1,744 @@ +# Remnants music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Remnants. +# Bounds x 2448-2743, z 7712-7879; 37892 tiles as 106 rectangles. + +[[area]] +name = "Remnants" +area_id = "area.remnants" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2496, 7712], + [2560, 7712], + [2560, 7879], + [2496, 7879], +] + +[[area.polygons]] +vertices = [ + [2561, 7713], + [2562, 7713], + [2562, 7879], + [2561, 7879], +] + +[[area.polygons]] +vertices = [ + [2563, 7714], + [2564, 7714], + [2564, 7879], + [2563, 7879], +] + +[[area.polygons]] +vertices = [ + [2565, 7715], + [2566, 7715], + [2566, 7879], + [2565, 7879], +] + +[[area.polygons]] +vertices = [ + [2567, 7716], + [2568, 7716], + [2568, 7879], + [2567, 7879], +] + +[[area.polygons]] +vertices = [ + [2569, 7717], + [2570, 7717], + [2570, 7879], + [2569, 7879], +] + +[[area.polygons]] +vertices = [ + [2571, 7718], + [2572, 7718], + [2572, 7879], + [2571, 7879], +] + +[[area.polygons]] +vertices = [ + [2573, 7719], + [2574, 7719], + [2574, 7879], + [2573, 7879], +] + +[[area.polygons]] +vertices = [ + [2575, 7720], + [2576, 7720], + [2576, 7879], + [2575, 7879], +] + +[[area.polygons]] +vertices = [ + [2577, 7721], + [2578, 7721], + [2578, 7879], + [2577, 7879], +] + +[[area.polygons]] +vertices = [ + [2579, 7722], + [2580, 7722], + [2580, 7879], + [2579, 7879], +] + +[[area.polygons]] +vertices = [ + [2581, 7723], + [2582, 7723], + [2582, 7879], + [2581, 7879], +] + +[[area.polygons]] +vertices = [ + [2583, 7724], + [2584, 7724], + [2584, 7879], + [2583, 7879], +] + +[[area.polygons]] +vertices = [ + [2585, 7725], + [2586, 7725], + [2586, 7879], + [2585, 7879], +] + +[[area.polygons]] +vertices = [ + [2587, 7726], + [2588, 7726], + [2588, 7879], + [2587, 7879], +] + +[[area.polygons]] +vertices = [ + [2589, 7727], + [2590, 7727], + [2590, 7879], + [2589, 7879], +] + +[[area.polygons]] +vertices = [ + [2591, 7728], + [2592, 7728], + [2592, 7879], + [2591, 7879], +] + +[[area.polygons]] +vertices = [ + [2593, 7729], + [2594, 7729], + [2594, 7879], + [2593, 7879], +] + +[[area.polygons]] +vertices = [ + [2595, 7730], + [2596, 7730], + [2596, 7879], + [2595, 7879], +] + +[[area.polygons]] +vertices = [ + [2597, 7731], + [2598, 7731], + [2598, 7879], + [2597, 7879], +] + +[[area.polygons]] +vertices = [ + [2599, 7732], + [2600, 7732], + [2600, 7879], + [2599, 7879], +] + +[[area.polygons]] +vertices = [ + [2601, 7733], + [2602, 7733], + [2602, 7879], + [2601, 7879], +] + +[[area.polygons]] +vertices = [ + [2603, 7734], + [2604, 7734], + [2604, 7879], + [2603, 7879], +] + +[[area.polygons]] +vertices = [ + [2605, 7735], + [2606, 7735], + [2606, 7879], + [2605, 7879], +] + +[[area.polygons]] +vertices = [ + [2607, 7736], + [2608, 7736], + [2608, 7879], + [2607, 7879], +] + +[[area.polygons]] +vertices = [ + [2609, 7737], + [2610, 7737], + [2610, 7879], + [2609, 7879], +] + +[[area.polygons]] +vertices = [ + [2611, 7738], + [2612, 7738], + [2612, 7879], + [2611, 7879], +] + +[[area.polygons]] +vertices = [ + [2613, 7739], + [2614, 7739], + [2614, 7879], + [2613, 7879], +] + +[[area.polygons]] +vertices = [ + [2615, 7740], + [2616, 7740], + [2616, 7879], + [2615, 7879], +] + +[[area.polygons]] +vertices = [ + [2617, 7741], + [2618, 7741], + [2618, 7879], + [2617, 7879], +] + +[[area.polygons]] +vertices = [ + [2619, 7742], + [2620, 7742], + [2620, 7879], + [2619, 7879], +] + +[[area.polygons]] +vertices = [ + [2621, 7743], + [2622, 7743], + [2622, 7879], + [2621, 7879], +] + +[[area.polygons]] +vertices = [ + [2623, 7744], + [2624, 7744], + [2624, 7879], + [2623, 7879], +] + +[[area.polygons]] +vertices = [ + [2625, 7745], + [2626, 7745], + [2626, 7879], + [2625, 7879], +] + +[[area.polygons]] +vertices = [ + [2627, 7746], + [2628, 7746], + [2628, 7879], + [2627, 7879], +] + +[[area.polygons]] +vertices = [ + [2629, 7747], + [2630, 7747], + [2630, 7879], + [2629, 7879], +] + +[[area.polygons]] +vertices = [ + [2631, 7748], + [2632, 7748], + [2632, 7879], + [2631, 7879], +] + +[[area.polygons]] +vertices = [ + [2633, 7749], + [2634, 7749], + [2634, 7879], + [2633, 7879], +] + +[[area.polygons]] +vertices = [ + [2635, 7750], + [2636, 7750], + [2636, 7879], + [2635, 7879], +] + +[[area.polygons]] +vertices = [ + [2637, 7751], + [2638, 7751], + [2638, 7879], + [2637, 7879], +] + +[[area.polygons]] +vertices = [ + [2639, 7752], + [2656, 7752], + [2656, 7879], + [2639, 7879], +] + +[[area.polygons]] +vertices = [ + [2657, 7753], + [2658, 7753], + [2658, 7879], + [2657, 7879], +] + +[[area.polygons]] +vertices = [ + [2659, 7754], + [2660, 7754], + [2660, 7879], + [2659, 7879], +] + +[[area.polygons]] +vertices = [ + [2661, 7755], + [2662, 7755], + [2662, 7879], + [2661, 7879], +] + +[[area.polygons]] +vertices = [ + [2663, 7756], + [2664, 7756], + [2664, 7879], + [2663, 7879], +] + +[[area.polygons]] +vertices = [ + [2665, 7757], + [2666, 7757], + [2666, 7879], + [2665, 7879], +] + +[[area.polygons]] +vertices = [ + [2667, 7758], + [2668, 7758], + [2668, 7879], + [2667, 7879], +] + +[[area.polygons]] +vertices = [ + [2669, 7759], + [2670, 7759], + [2670, 7879], + [2669, 7879], +] + +[[area.polygons]] +vertices = [ + [2671, 7760], + [2687, 7760], + [2687, 7879], + [2671, 7879], +] + +[[area.polygons]] +vertices = [ + [2688, 7761], + [2688, 7879], +] + +[[area.polygons]] +vertices = [ + [2689, 7762], + [2689, 7879], +] + +[[area.polygons]] +vertices = [ + [2690, 7763], + [2690, 7879], +] + +[[area.polygons]] +vertices = [ + [2691, 7764], + [2691, 7879], +] + +[[area.polygons]] +vertices = [ + [2692, 7765], + [2692, 7879], +] + +[[area.polygons]] +vertices = [ + [2693, 7766], + [2693, 7879], +] + +[[area.polygons]] +vertices = [ + [2694, 7767], + [2694, 7879], +] + +[[area.polygons]] +vertices = [ + [2695, 7768], + [2695, 7879], +] + +[[area.polygons]] +vertices = [ + [2696, 7769], + [2696, 7879], +] + +[[area.polygons]] +vertices = [ + [2697, 7770], + [2697, 7879], +] + +[[area.polygons]] +vertices = [ + [2698, 7771], + [2698, 7879], +] + +[[area.polygons]] +vertices = [ + [2699, 7772], + [2699, 7879], +] + +[[area.polygons]] +vertices = [ + [2700, 7773], + [2700, 7879], +] + +[[area.polygons]] +vertices = [ + [2701, 7774], + [2701, 7879], +] + +[[area.polygons]] +vertices = [ + [2702, 7775], + [2702, 7879], +] + +[[area.polygons]] +vertices = [ + [2703, 7776], + [2703, 7879], +] + +[[area.polygons]] +vertices = [ + [2704, 7777], + [2704, 7879], +] + +[[area.polygons]] +vertices = [ + [2705, 7778], + [2705, 7879], +] + +[[area.polygons]] +vertices = [ + [2706, 7779], + [2706, 7879], +] + +[[area.polygons]] +vertices = [ + [2707, 7780], + [2707, 7879], +] + +[[area.polygons]] +vertices = [ + [2708, 7781], + [2708, 7879], +] + +[[area.polygons]] +vertices = [ + [2709, 7782], + [2709, 7879], +] + +[[area.polygons]] +vertices = [ + [2710, 7783], + [2710, 7879], +] + +[[area.polygons]] +vertices = [ + [2711, 7784], + [2711, 7879], +] + +[[area.polygons]] +vertices = [ + [2712, 7785], + [2712, 7879], +] + +[[area.polygons]] +vertices = [ + [2713, 7786], + [2713, 7879], +] + +[[area.polygons]] +vertices = [ + [2714, 7787], + [2714, 7879], +] + +[[area.polygons]] +vertices = [ + [2715, 7788], + [2715, 7879], +] + +[[area.polygons]] +vertices = [ + [2716, 7789], + [2716, 7879], +] + +[[area.polygons]] +vertices = [ + [2717, 7790], + [2717, 7879], +] + +[[area.polygons]] +vertices = [ + [2718, 7791], + [2718, 7879], +] + +[[area.polygons]] +vertices = [ + [2448, 7792], + [2495, 7792], + [2495, 7879], + [2448, 7879], +] + +[[area.polygons]] +vertices = [ + [2719, 7792], + [2719, 7879], +] + +[[area.polygons]] +vertices = [ + [2720, 7793], + [2720, 7879], +] + +[[area.polygons]] +vertices = [ + [2721, 7794], + [2721, 7879], +] + +[[area.polygons]] +vertices = [ + [2722, 7795], + [2722, 7879], +] + +[[area.polygons]] +vertices = [ + [2723, 7796], + [2723, 7879], +] + +[[area.polygons]] +vertices = [ + [2724, 7797], + [2724, 7879], +] + +[[area.polygons]] +vertices = [ + [2725, 7798], + [2725, 7879], +] + +[[area.polygons]] +vertices = [ + [2726, 7799], + [2726, 7879], +] + +[[area.polygons]] +vertices = [ + [2727, 7800], + [2727, 7879], +] + +[[area.polygons]] +vertices = [ + [2728, 7801], + [2728, 7879], +] + +[[area.polygons]] +vertices = [ + [2729, 7802], + [2729, 7879], +] + +[[area.polygons]] +vertices = [ + [2730, 7803], + [2730, 7879], +] + +[[area.polygons]] +vertices = [ + [2731, 7804], + [2731, 7879], +] + +[[area.polygons]] +vertices = [ + [2732, 7805], + [2732, 7879], +] + +[[area.polygons]] +vertices = [ + [2733, 7806], + [2733, 7879], +] + +[[area.polygons]] +vertices = [ + [2734, 7807], + [2734, 7879], +] + +[[area.polygons]] +vertices = [ + [2735, 7808], + [2735, 7879], +] + +[[area.polygons]] +vertices = [ + [2736, 7809], + [2736, 7879], +] + +[[area.polygons]] +vertices = [ + [2737, 7810], + [2737, 7879], +] + +[[area.polygons]] +vertices = [ + [2738, 7811], + [2738, 7879], +] + +[[area.polygons]] +vertices = [ + [2739, 7812], + [2739, 7879], +] + +[[area.polygons]] +vertices = [ + [2740, 7813], + [2740, 7879], +] + +[[area.polygons]] +vertices = [ + [2741, 7814], + [2741, 7879], +] + +[[area.polygons]] +vertices = [ + [2742, 7815], + [2742, 7879], +] + +[[area.polygons]] +vertices = [ + [2743, 7816], + [2743, 7879], +] diff --git a/.data/raw-cache/map/area/rest_in_peace.toml b/.data/raw-cache/map/area/rest_in_peace.toml new file mode 100644 index 000000000..0bdec883c --- /dev/null +++ b/.data/raw-cache/map/area/rest_in_peace.toml @@ -0,0 +1,16 @@ +# Rest in Peace music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Rest in Peace. +# Bounds x 3136-3199, z 5696-5759; 4096 tiles as 1 rectangles. + +[[area]] +name = "Rest in Peace" +area_id = "area.rest_in_peace" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 5696], + [3199, 5696], + [3199, 5759], + [3136, 5759], +] diff --git a/.data/raw-cache/map/area/revenants.toml b/.data/raw-cache/map/area/revenants.toml new file mode 100644 index 000000000..43f0e1401 --- /dev/null +++ b/.data/raw-cache/map/area/revenants.toml @@ -0,0 +1,16 @@ +# Revenants music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Revenants. +# Bounds x 3136-3263, z 10048-10239; 24576 tiles as 1 rectangles. + +[[area]] +name = "Revenants" +area_id = "area.revenants" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 10048], + [3263, 10048], + [3263, 10239], + [3136, 10239], +] diff --git a/.data/raw-cache/map/area/righteousness.toml b/.data/raw-cache/map/area/righteousness.toml new file mode 100644 index 000000000..84235686b --- /dev/null +++ b/.data/raw-cache/map/area/righteousness.toml @@ -0,0 +1,16 @@ +# Righteousness music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Righteousness. +# Bounds x 2432-2495, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Righteousness" +area_id = "area.righteousness" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2432, 4800], + [2495, 4800], + [2495, 4863], + [2432, 4863], +] diff --git a/.data/raw-cache/map/area/rimmington.toml b/.data/raw-cache/map/area/rimmington.toml new file mode 100644 index 000000000..113295df8 --- /dev/null +++ b/.data/raw-cache/map/area/rimmington.toml @@ -0,0 +1,270 @@ +# Regenerated as rectangles from the OSRS wiki music map: 208 tiles moved to +# neighbouring music areas and 1048 tiles were added. + +[[area]] +name = "Rimmington" +area_id = "area.rimmington" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2925, 3185], + [2983, 3185], + [2983, 3190], + [2925, 3190], +] + +[[area.polygons]] +vertices = [ + [2925, 3191], + [2982, 3191], + [2982, 3236], + [2925, 3236], +] + +[[area.polygons]] +vertices = [ + [2983, 3194], + [2983, 3257], +] + +[[area.polygons]] +vertices = [ + [2984, 3195], + [2984, 3257], +] + +[[area.polygons]] +vertices = [ + [2985, 3196], + [2985, 3257], +] + +[[area.polygons]] +vertices = [ + [2986, 3197], + [2986, 3257], +] + +[[area.polygons]] +vertices = [ + [2987, 3198], + [2987, 3257], +] + +[[area.polygons]] +vertices = [ + [2988, 3199], + [2988, 3257], +] + +[[area.polygons]] +vertices = [ + [2989, 3200], + [2989, 3257], +] + +[[area.polygons]] +vertices = [ + [2990, 3201], + [2990, 3257], +] + +[[area.polygons]] +vertices = [ + [2923, 3205], + [2924, 3205], + [2924, 3217], + [2923, 3217], +] + +[[area.polygons]] +vertices = [ + [2922, 3206], + [2922, 3211], +] + +[[area.polygons]] +vertices = [ + [2921, 3208], + [2921, 3210], +] + +[[area.polygons]] +vertices = [[2922, 3213]] + +[[area.polygons]] +vertices = [ + [2924, 3218], + [2924, 3239], +] + +[[area.polygons]] +vertices = [ + [2912, 3220], + [2920, 3220], + [2920, 3222], + [2912, 3222], +] + +[[area.polygons]] +vertices = [ + [2921, 3221], + [2921, 3251], +] + +[[area.polygons]] +vertices = [ + [2923, 3221], + [2923, 3239], +] + +[[area.polygons]] +vertices = [ + [2922, 3222], + [2922, 3250], +] + +[[area.polygons]] +vertices = [ + [2915, 3223], + [2920, 3223], + [2920, 3238], + [2915, 3238], +] + +[[area.polygons]] +vertices = [ + [2907, 3225], + [2914, 3225], + [2914, 3226], + [2907, 3226], +] + +[[area.polygons]] +vertices = [ + [2909, 3227], + [2914, 3227], + [2914, 3231], + [2909, 3231], +] + +[[area.polygons]] +vertices = [ + [2906, 3229], + [2908, 3229], + [2908, 3231], + [2906, 3231], +] + +[[area.polygons]] +vertices = [ + [2914, 3233], + [2914, 3235], +] + +[[area.polygons]] +vertices = [ + [2925, 3237], + [2936, 3237], +] + +[[area.polygons]] +vertices = [ + [2940, 3237], + [2982, 3237], +] + +[[area.polygons]] +vertices = [ + [2925, 3238], + [2926, 3238], + [2926, 3239], + [2925, 3239], +] + +[[area.polygons]] +vertices = [ + [2930, 3238], + [2936, 3238], +] + +[[area.polygons]] +vertices = [ + [2941, 3238], + [2982, 3238], + [2982, 3256], + [2941, 3256], +] + +[[area.polygons]] +vertices = [ + [2916, 3239], + [2920, 3239], + [2920, 3240], + [2916, 3240], +] + +[[area.polygons]] +vertices = [ + [2931, 3239], + [2936, 3239], +] + +[[area.polygons]] +vertices = [ + [2917, 3241], + [2920, 3241], + [2920, 3253], + [2917, 3253], +] + +[[area.polygons]] +vertices = [ + [2916, 3245], + [2916, 3253], +] + +[[area.polygons]] +vertices = [ + [2915, 3246], + [2915, 3253], +] + +[[area.polygons]] +vertices = [ + [2914, 3248], + [2914, 3249], +] + +[[area.polygons]] +vertices = [ + [2945, 3257], + [2982, 3257], +] + +[[area.polygons]] +vertices = [ + [2945, 3258], + [2956, 3258], + [2956, 3259], + [2945, 3259], +] + +[[area.polygons]] +vertices = [ + [2945, 3260], + [2955, 3260], +] + +[[area.polygons]] +vertices = [ + [2945, 3261], + [2954, 3261], +] + +[[area.polygons]] +vertices = [ + [2946, 3262], + [2953, 3262], +] diff --git a/.data/raw-cache/map/area/romancing_the_crone.toml b/.data/raw-cache/map/area/romancing_the_crone.toml new file mode 100644 index 000000000..25bb246c3 --- /dev/null +++ b/.data/raw-cache/map/area/romancing_the_crone.toml @@ -0,0 +1,16 @@ +# Romancing the Crone music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Romancing the Crone. +# Bounds x 2752-2815, z 3835-3875; 2624 tiles as 1 rectangles. + +[[area]] +name = "Romancing the Crone" +area_id = "area.romancing_the_crone" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 3835], + [2815, 3835], + [2815, 3875], + [2752, 3875], +] diff --git a/.data/raw-cache/map/area/romper_chomper.toml b/.data/raw-cache/map/area/romper_chomper.toml new file mode 100644 index 000000000..3650454e2 --- /dev/null +++ b/.data/raw-cache/map/area/romper_chomper.toml @@ -0,0 +1,589 @@ +# Romper Chomper music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Romper Chomper. +# Bounds x 2321-2455, z 3028-3079; 4767 tiles as 90 rectangles. + +[[area]] +name = "Romper Chomper" +area_id = "area.romper_chomper" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2449, 3028], + [2450, 3028], + [2450, 3065], + [2449, 3065], +] + +[[area.polygons]] +vertices = [ + [2389, 3029], + [2389, 3068], +] + +[[area.polygons]] +vertices = [ + [2451, 3029], + [2451, 3065], +] + +[[area.polygons]] +vertices = [ + [2380, 3030], + [2381, 3030], + [2381, 3069], + [2380, 3069], +] + +[[area.polygons]] +vertices = [ + [2387, 3030], + [2388, 3030], + [2388, 3068], + [2387, 3068], +] + +[[area.polygons]] +vertices = [ + [2399, 3030], + [2399, 3068], +] + +[[area.polygons]] +vertices = [ + [2447, 3030], + [2448, 3030], + [2448, 3066], + [2447, 3066], +] + +[[area.polygons]] +vertices = [ + [2452, 3030], + [2452, 3065], +] + +[[area.polygons]] +vertices = [ + [2378, 3031], + [2379, 3031], + [2379, 3071], + [2378, 3071], +] + +[[area.polygons]] +vertices = [ + [2382, 3031], + [2386, 3031], + [2386, 3068], + [2382, 3068], +] + +[[area.polygons]] +vertices = [ + [2390, 3031], + [2390, 3068], +] + +[[area.polygons]] +vertices = [ + [2396, 3031], + [2398, 3031], + [2398, 3068], + [2396, 3068], +] + +[[area.polygons]] +vertices = [ + [2400, 3031], + [2400, 3068], +] + +[[area.polygons]] +vertices = [ + [2446, 3031], + [2446, 3066], +] + +[[area.polygons]] +vertices = [ + [2453, 3031], + [2455, 3031], + [2455, 3064], + [2453, 3064], +] + +[[area.polygons]] +vertices = [ + [2349, 3032], + [2350, 3032], + [2350, 3074], + [2349, 3074], +] + +[[area.polygons]] +vertices = [ + [2376, 3032], + [2377, 3032], + [2377, 3073], + [2376, 3073], +] + +[[area.polygons]] +vertices = [ + [2394, 3032], + [2395, 3032], + [2395, 3068], + [2394, 3068], +] + +[[area.polygons]] +vertices = [ + [2401, 3032], + [2402, 3032], + [2402, 3068], + [2401, 3068], +] + +[[area.polygons]] +vertices = [ + [2348, 3033], + [2348, 3073], +] + +[[area.polygons]] +vertices = [ + [2351, 3033], + [2351, 3074], +] + +[[area.polygons]] +vertices = [ + [2375, 3033], + [2375, 3075], +] + +[[area.polygons]] +vertices = [ + [2391, 3033], + [2393, 3033], + [2393, 3068], + [2391, 3068], +] + +[[area.polygons]] +vertices = [ + [2403, 3033], + [2404, 3033], + [2404, 3068], + [2403, 3068], +] + +[[area.polygons]] +vertices = [ + [2440, 3033], + [2440, 3068], +] + +[[area.polygons]] +vertices = [ + [2347, 3034], + [2347, 3074], +] + +[[area.polygons]] +vertices = [ + [2352, 3034], + [2354, 3034], + [2354, 3073], + [2352, 3073], +] + +[[area.polygons]] +vertices = [ + [2374, 3034], + [2374, 3076], +] + +[[area.polygons]] +vertices = [ + [2405, 3034], + [2405, 3068], +] + +[[area.polygons]] +vertices = [ + [2438, 3034], + [2439, 3034], + [2439, 3068], + [2438, 3068], +] + +[[area.polygons]] +vertices = [ + [2441, 3034], + [2441, 3068], +] + +[[area.polygons]] +vertices = [ + [2445, 3034], + [2445, 3067], +] + +[[area.polygons]] +vertices = [ + [2345, 3035], + [2346, 3035], + [2346, 3074], + [2345, 3074], +] + +[[area.polygons]] +vertices = [ + [2355, 3035], + [2355, 3075], +] + +[[area.polygons]] +vertices = [ + [2373, 3035], + [2373, 3077], +] + +[[area.polygons]] +vertices = [ + [2406, 3035], + [2407, 3035], + [2407, 3068], + [2406, 3068], +] + +[[area.polygons]] +vertices = [ + [2436, 3035], + [2437, 3035], + [2437, 3069], + [2436, 3069], +] + +[[area.polygons]] +vertices = [ + [2442, 3035], + [2444, 3035], + [2444, 3067], + [2442, 3067], +] + +[[area.polygons]] +vertices = [ + [2343, 3036], + [2344, 3036], + [2344, 3074], + [2343, 3074], +] + +[[area.polygons]] +vertices = [ + [2356, 3036], + [2357, 3036], + [2357, 3075], + [2356, 3075], +] + +[[area.polygons]] +vertices = [ + [2372, 3036], + [2372, 3078], +] + +[[area.polygons]] +vertices = [ + [2408, 3036], + [2408, 3068], +] + +[[area.polygons]] +vertices = [ + [2425, 3036], + [2427, 3036], + [2427, 3068], + [2425, 3068], +] + +[[area.polygons]] +vertices = [ + [2435, 3036], + [2435, 3069], +] + +[[area.polygons]] +vertices = [ + [2332, 3037], + [2336, 3037], + [2336, 3077], + [2332, 3077], +] + +[[area.polygons]] +vertices = [ + [2341, 3037], + [2342, 3037], + [2342, 3075], + [2341, 3075], +] + +[[area.polygons]] +vertices = [ + [2358, 3037], + [2358, 3076], +] + +[[area.polygons]] +vertices = [ + [2370, 3037], + [2371, 3037], + [2371, 3079], + [2370, 3079], +] + +[[area.polygons]] +vertices = [ + [2409, 3037], + [2409, 3068], +] + +[[area.polygons]] +vertices = [ + [2418, 3037], + [2418, 3068], +] + +[[area.polygons]] +vertices = [ + [2423, 3037], + [2424, 3037], + [2424, 3068], + [2423, 3068], +] + +[[area.polygons]] +vertices = [ + [2428, 3037], + [2428, 3068], +] + +[[area.polygons]] +vertices = [ + [2433, 3037], + [2434, 3037], + [2434, 3068], + [2433, 3068], +] + +[[area.polygons]] +vertices = [ + [2331, 3038], + [2331, 3077], +] + +[[area.polygons]] +vertices = [ + [2337, 3038], + [2340, 3038], + [2340, 3075], + [2337, 3075], +] + +[[area.polygons]] +vertices = [ + [2359, 3038], + [2362, 3038], + [2362, 3076], + [2359, 3076], +] + +[[area.polygons]] +vertices = [ + [2369, 3038], + [2369, 3079], +] + +[[area.polygons]] +vertices = [ + [2410, 3038], + [2410, 3068], +] + +[[area.polygons]] +vertices = [ + [2416, 3038], + [2417, 3038], + [2417, 3068], + [2416, 3068], +] + +[[area.polygons]] +vertices = [ + [2419, 3038], + [2419, 3068], +] + +[[area.polygons]] +vertices = [ + [2422, 3038], + [2422, 3068], +] + +[[area.polygons]] +vertices = [ + [2429, 3038], + [2432, 3038], + [2432, 3068], + [2429, 3068], +] + +[[area.polygons]] +vertices = [ + [2330, 3039], + [2330, 3041], +] + +[[area.polygons]] +vertices = [ + [2363, 3039], + [2365, 3039], + [2365, 3077], + [2363, 3077], +] + +[[area.polygons]] +vertices = [ + [2368, 3039], + [2368, 3078], +] + +[[area.polygons]] +vertices = [ + [2411, 3039], + [2415, 3039], + [2415, 3068], + [2411, 3068], +] + +[[area.polygons]] +vertices = [ + [2420, 3039], + [2421, 3039], + [2421, 3068], + [2420, 3068], +] + +[[area.polygons]] +vertices = [ + [2366, 3040], + [2367, 3040], + [2367, 3077], + [2366, 3077], +] + +[[area.polygons]] +vertices = [ + [2330, 3046], + [2330, 3076], +] + +[[area.polygons]] +vertices = [ + [2329, 3048], + [2329, 3074], +] + +[[area.polygons]] +vertices = [ + [2328, 3051], + [2328, 3071], +] + +[[area.polygons]] +vertices = [ + [2327, 3053], + [2327, 3068], +] + +[[area.polygons]] +vertices = [ + [2326, 3055], + [2326, 3067], +] + +[[area.polygons]] +vertices = [ + [2325, 3057], + [2325, 3066], +] + +[[area.polygons]] +vertices = [ + [2324, 3058], + [2324, 3065], +] + +[[area.polygons]] +vertices = [ + [2322, 3059], + [2323, 3059], + [2323, 3062], + [2322, 3062], +] + +[[area.polygons]] +vertices = [[2321, 3061]] + +[[area.polygons]] +vertices = [[2323, 3063]] + +[[area.polygons]] +vertices = [[2453, 3065]] + +[[area.polygons]] +vertices = [[2449, 3066]] + +[[area.polygons]] +vertices = [[2434, 3069]] + +[[area.polygons]] +vertices = [[2380, 3070]] + +[[area.polygons]] +vertices = [[2378, 3072]] + +[[area.polygons]] +vertices = [ + [2353, 3074], + [2354, 3074], +] + +[[area.polygons]] +vertices = [[2376, 3074]] + +[[area.polygons]] +vertices = [[2354, 3075]] + +[[area.polygons]] +vertices = [ + [2337, 3076], + [2339, 3076], +] + +[[area.polygons]] +vertices = [[2357, 3076]] + +[[area.polygons]] +vertices = [[2337, 3077]] + +[[area.polygons]] +vertices = [ + [2360, 3077], + [2362, 3077], +] diff --git a/.data/raw-cache/map/area/roots_and_flutes.toml b/.data/raw-cache/map/area/roots_and_flutes.toml new file mode 100644 index 000000000..d0708bbba --- /dev/null +++ b/.data/raw-cache/map/area/roots_and_flutes.toml @@ -0,0 +1,16 @@ +# Roots and Flutes music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Roots and Flutes. +# Bounds x 3200-3263, z 9920-9983; 4096 tiles as 1 rectangles. + +[[area]] +name = "Roots and Flutes" +area_id = "area.roots_and_flutes" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3200, 9920], + [3263, 9920], + [3263, 9983], + [3200, 9983], +] diff --git a/.data/raw-cache/map/area/rose.toml b/.data/raw-cache/map/area/rose.toml new file mode 100644 index 000000000..1a1dee4f4 --- /dev/null +++ b/.data/raw-cache/map/area/rose.toml @@ -0,0 +1,84 @@ +# Rose music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Rose. +# Bounds x 1152-1291, z 3755-10303; 4504 tiles as 12 rectangles. + +[[area]] +name = "Rose" +area_id = "area.rose" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1273, 3755], + [1288, 3755], + [1288, 3771], + [1273, 3771], +] + +[[area.polygons]] +vertices = [ + [1272, 3756], + [1272, 3771], +] + +[[area.polygons]] +vertices = [ + [1289, 3756], + [1289, 3771], +] + +[[area.polygons]] +vertices = [ + [1271, 3757], + [1271, 3771], +] + +[[area.polygons]] +vertices = [ + [1290, 3757], + [1290, 3771], +] + +[[area.polygons]] +vertices = [ + [1270, 3758], + [1270, 3771], +] + +[[area.polygons]] +vertices = [ + [1291, 3758], + [1291, 3771], +] + +[[area.polygons]] +vertices = [ + [1269, 3759], + [1269, 3771], +] + +[[area.polygons]] +vertices = [ + [1268, 3760], + [1268, 3771], +] + +[[area.polygons]] +vertices = [ + [1267, 3761], + [1267, 3771], +] + +[[area.polygons]] +vertices = [ + [1266, 3762], + [1266, 3771], +] + +[[area.polygons]] +vertices = [ + [1152, 10240], + [1215, 10240], + [1215, 10303], + [1152, 10303], +] diff --git a/.data/raw-cache/map/area/rugged_terrain.toml b/.data/raw-cache/map/area/rugged_terrain.toml new file mode 100644 index 000000000..dd1ccb72c --- /dev/null +++ b/.data/raw-cache/map/area/rugged_terrain.toml @@ -0,0 +1,1358 @@ +# Rugged Terrain music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Rugged Terrain. +# Bounds x 1227-1731, z 3432-10101; 17225 tiles as 216 rectangles. + +[[area]] +name = "Rugged Terrain" +area_id = "area.rugged_terrain" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1266, 3432], + [1275, 3432], + [1275, 3488], + [1266, 3488], +] + +[[area.polygons]] +vertices = [ + [1265, 3433], + [1265, 3489], +] + +[[area.polygons]] +vertices = [ + [1276, 3433], + [1276, 3488], +] + +[[area.polygons]] +vertices = [ + [1264, 3434], + [1264, 3489], +] + +[[area.polygons]] +vertices = [ + [1277, 3434], + [1277, 3488], +] + +[[area.polygons]] +vertices = [ + [1263, 3435], + [1263, 3489], +] + +[[area.polygons]] +vertices = [ + [1278, 3435], + [1278, 3488], +] + +[[area.polygons]] +vertices = [ + [1262, 3436], + [1262, 3489], +] + +[[area.polygons]] +vertices = [ + [1279, 3436], + [1279, 3488], +] + +[[area.polygons]] +vertices = [ + [1280, 3452], + [1280, 3488], +] + +[[area.polygons]] +vertices = [ + [1281, 3453], + [1281, 3488], +] + +[[area.polygons]] +vertices = [ + [1282, 3454], + [1282, 3488], +] + +[[area.polygons]] +vertices = [ + [1283, 3455], + [1283, 3488], +] + +[[area.polygons]] +vertices = [ + [1284, 3456], + [1284, 3488], +] + +[[area.polygons]] +vertices = [ + [1285, 3457], + [1285, 3496], +] + +[[area.polygons]] +vertices = [ + [1286, 3458], + [1286, 3497], +] + +[[area.polygons]] +vertices = [ + [1287, 3459], + [1287, 3498], +] + +[[area.polygons]] +vertices = [ + [1288, 3460], + [1288, 3499], +] + +[[area.polygons]] +vertices = [ + [1289, 3461], + [1289, 3499], +] + +[[area.polygons]] +vertices = [ + [1306, 3461], + [1318, 3461], + [1318, 3512], + [1306, 3512], +] + +[[area.polygons]] +vertices = [ + [1227, 3462], + [1233, 3462], + [1233, 3477], + [1227, 3477], +] + +[[area.polygons]] +vertices = [ + [1290, 3462], + [1290, 3499], +] + +[[area.polygons]] +vertices = [ + [1319, 3462], + [1319, 3511], +] + +[[area.polygons]] +vertices = [ + [1234, 3463], + [1234, 3484], +] + +[[area.polygons]] +vertices = [ + [1291, 3463], + [1291, 3499], +] + +[[area.polygons]] +vertices = [ + [1320, 3463], + [1320, 3510], +] + +[[area.polygons]] +vertices = [ + [1235, 3464], + [1235, 3485], +] + +[[area.polygons]] +vertices = [ + [1292, 3464], + [1292, 3499], +] + +[[area.polygons]] +vertices = [ + [1321, 3464], + [1321, 3509], +] + +[[area.polygons]] +vertices = [ + [1236, 3465], + [1236, 3486], +] + +[[area.polygons]] +vertices = [ + [1293, 3465], + [1293, 3499], +] + +[[area.polygons]] +vertices = [ + [1322, 3465], + [1322, 3508], +] + +[[area.polygons]] +vertices = [ + [1237, 3466], + [1261, 3466], + [1261, 3486], + [1237, 3486], +] + +[[area.polygons]] +vertices = [ + [1294, 3466], + [1305, 3466], + [1305, 3500], + [1294, 3500], +] + +[[area.polygons]] +vertices = [ + [1323, 3466], + [1323, 3507], +] + +[[area.polygons]] +vertices = [ + [1324, 3467], + [1324, 3506], +] + +[[area.polygons]] +vertices = [ + [1325, 3468], + [1325, 3505], +] + +[[area.polygons]] +vertices = [ + [1326, 3469], + [1326, 3504], +] + +[[area.polygons]] +vertices = [ + [1327, 3470], + [1334, 3470], + [1334, 3503], + [1327, 3503], +] + +[[area.polygons]] +vertices = [ + [1335, 3471], + [1336, 3471], + [1336, 3503], + [1335, 3503], +] + +[[area.polygons]] +vertices = [ + [1377, 3471], + [1378, 3471], + [1378, 3484], + [1377, 3484], +] + +[[area.polygons]] +vertices = [ + [1337, 3472], + [1338, 3472], + [1338, 3503], + [1337, 3503], +] + +[[area.polygons]] +vertices = [ + [1375, 3472], + [1376, 3472], + [1376, 3486], + [1375, 3486], +] + +[[area.polygons]] +vertices = [ + [1379, 3472], + [1379, 3483], +] + +[[area.polygons]] +vertices = [ + [1339, 3473], + [1340, 3473], + [1340, 3503], + [1339, 3503], +] + +[[area.polygons]] +vertices = [ + [1373, 3473], + [1374, 3473], + [1374, 3488], + [1373, 3488], +] + +[[area.polygons]] +vertices = [ + [1341, 3474], + [1342, 3474], + [1342, 3503], + [1341, 3503], +] + +[[area.polygons]] +vertices = [ + [1371, 3474], + [1372, 3474], + [1372, 3490], + [1371, 3490], +] + +[[area.polygons]] +vertices = [ + [1343, 3475], + [1344, 3475], + [1344, 3503], + [1343, 3503], +] + +[[area.polygons]] +vertices = [ + [1363, 3475], + [1370, 3475], + [1370, 3492], + [1363, 3492], +] + +[[area.polygons]] +vertices = [ + [1345, 3476], + [1346, 3476], + [1346, 3505], + [1345, 3505], +] + +[[area.polygons]] +vertices = [ + [1361, 3476], + [1362, 3476], + [1362, 3515], + [1361, 3515], +] + +[[area.polygons]] +vertices = [ + [1347, 3477], + [1348, 3477], + [1348, 3507], + [1347, 3507], +] + +[[area.polygons]] +vertices = [ + [1360, 3477], + [1360, 3515], +] + +[[area.polygons]] +vertices = [ + [1228, 3478], + [1233, 3478], +] + +[[area.polygons]] +vertices = [ + [1349, 3478], + [1359, 3478], + [1359, 3509], + [1349, 3509], +] + +[[area.polygons]] +vertices = [ + [1229, 3479], + [1233, 3479], +] + +[[area.polygons]] +vertices = [ + [1230, 3480], + [1233, 3480], +] + +[[area.polygons]] +vertices = [ + [1231, 3481], + [1233, 3481], +] + +[[area.polygons]] +vertices = [ + [1232, 3482], + [1233, 3482], +] + +[[area.polygons]] +vertices = [[1233, 3483]] + +[[area.polygons]] +vertices = [[1377, 3485]] + +[[area.polygons]] +vertices = [ + [1237, 3487], + [1243, 3487], +] + +[[area.polygons]] +vertices = [ + [1255, 3487], + [1261, 3487], +] + +[[area.polygons]] +vertices = [[1375, 3487]] + +[[area.polygons]] +vertices = [ + [1238, 3488], + [1242, 3488], +] + +[[area.polygons]] +vertices = [ + [1258, 3488], + [1261, 3488], +] + +[[area.polygons]] +vertices = [ + [1239, 3489], + [1241, 3489], +] + +[[area.polygons]] +vertices = [ + [1260, 3489], + [1261, 3489], +] + +[[area.polygons]] +vertices = [[1266, 3489]] + +[[area.polygons]] +vertices = [[1373, 3489]] + +[[area.polygons]] +vertices = [[1240, 3490]] + +[[area.polygons]] +vertices = [[1371, 3491]] + +[[area.polygons]] +vertices = [ + [1363, 3493], + [1369, 3493], +] + +[[area.polygons]] +vertices = [ + [1363, 3494], + [1368, 3494], +] + +[[area.polygons]] +vertices = [ + [1363, 3495], + [1367, 3495], +] + +[[area.polygons]] +vertices = [ + [1363, 3496], + [1366, 3496], +] + +[[area.polygons]] +vertices = [ + [1363, 3497], + [1365, 3497], +] + +[[area.polygons]] +vertices = [ + [1363, 3498], + [1364, 3498], + [1364, 3515], + [1363, 3515], +] + +[[area.polygons]] +vertices = [ + [1295, 3501], + [1305, 3501], +] + +[[area.polygons]] +vertices = [ + [1296, 3502], + [1305, 3502], +] + +[[area.polygons]] +vertices = [ + [1297, 3503], + [1305, 3503], +] + +[[area.polygons]] +vertices = [ + [1298, 3504], + [1305, 3504], +] + +[[area.polygons]] +vertices = [[1344, 3504]] + +[[area.polygons]] +vertices = [ + [1299, 3505], + [1305, 3505], +] + +[[area.polygons]] +vertices = [ + [1300, 3506], + [1305, 3506], +] + +[[area.polygons]] +vertices = [[1346, 3506]] + +[[area.polygons]] +vertices = [ + [1301, 3507], + [1305, 3507], +] + +[[area.polygons]] +vertices = [ + [1302, 3508], + [1305, 3508], +] + +[[area.polygons]] +vertices = [[1348, 3508]] + +[[area.polygons]] +vertices = [ + [1303, 3509], + [1305, 3509], + [1305, 3515], + [1303, 3515], +] + +[[area.polygons]] +vertices = [ + [1350, 3510], + [1359, 3510], +] + +[[area.polygons]] +vertices = [ + [1351, 3511], + [1359, 3511], +] + +[[area.polygons]] +vertices = [ + [1352, 3512], + [1359, 3512], +] + +[[area.polygons]] +vertices = [ + [1306, 3513], + [1317, 3513], +] + +[[area.polygons]] +vertices = [ + [1353, 3513], + [1359, 3513], +] + +[[area.polygons]] +vertices = [ + [1306, 3514], + [1316, 3514], +] + +[[area.polygons]] +vertices = [ + [1354, 3514], + [1359, 3514], +] + +[[area.polygons]] +vertices = [ + [1306, 3515], + [1315, 3515], +] + +[[area.polygons]] +vertices = [ + [1355, 3515], + [1359, 3515], +] + +[[area.polygons]] +vertices = [ + [1563, 3647], + [1575, 3647], + [1575, 3742], + [1563, 3742], +] + +[[area.polygons]] +vertices = [ + [1562, 3648], + [1562, 3742], +] + +[[area.polygons]] +vertices = [ + [1576, 3648], + [1576, 3746], +] + +[[area.polygons]] +vertices = [ + [1561, 3649], + [1561, 3742], +] + +[[area.polygons]] +vertices = [ + [1577, 3649], + [1577, 3746], +] + +[[area.polygons]] +vertices = [ + [1560, 3650], + [1560, 3742], +] + +[[area.polygons]] +vertices = [ + [1578, 3650], + [1578, 3746], +] + +[[area.polygons]] +vertices = [ + [1559, 3651], + [1559, 3742], +] + +[[area.polygons]] +vertices = [ + [1579, 3651], + [1579, 3747], +] + +[[area.polygons]] +vertices = [ + [1543, 3652], + [1558, 3652], + [1558, 3720], + [1543, 3720], +] + +[[area.polygons]] +vertices = [ + [1580, 3652], + [1580, 3747], +] + +[[area.polygons]] +vertices = [ + [1581, 3653], + [1581, 3748], +] + +[[area.polygons]] +vertices = [ + [1582, 3654], + [1582, 3748], +] + +[[area.polygons]] +vertices = [ + [1583, 3655], + [1583, 3749], +] + +[[area.polygons]] +vertices = [ + [1584, 3656], + [1584, 3769], +] + +[[area.polygons]] +vertices = [ + [1585, 3657], + [1585, 3685], +] + +[[area.polygons]] +vertices = [ + [1542, 3658], + [1542, 3718], +] + +[[area.polygons]] +vertices = [ + [1586, 3658], + [1586, 3684], +] + +[[area.polygons]] +vertices = [ + [1539, 3659], + [1541, 3659], + [1541, 3661], + [1539, 3661], +] + +[[area.polygons]] +vertices = [ + [1540, 3662], + [1541, 3662], + [1541, 3689], + [1540, 3689], +] + +[[area.polygons]] +vertices = [ + [1539, 3671], + [1539, 3688], +] + +[[area.polygons]] +vertices = [ + [1538, 3672], + [1538, 3684], +] + +[[area.polygons]] +vertices = [ + [1537, 3673], + [1537, 3677], +] + +[[area.polygons]] +vertices = [ + [1537, 3680], + [1537, 3683], +] + +[[area.polygons]] +vertices = [[1541, 3690]] + +[[area.polygons]] +vertices = [ + [1585, 3693], + [1585, 3769], +] + +[[area.polygons]] +vertices = [ + [1586, 3694], + [1586, 3770], +] + +[[area.polygons]] +vertices = [ + [1587, 3695], + [1587, 3770], +] + +[[area.polygons]] +vertices = [ + [1588, 3696], + [1588, 3770], +] + +[[area.polygons]] +vertices = [ + [1589, 3697], + [1599, 3697], + [1599, 3769], + [1589, 3769], +] + +[[area.polygons]] +vertices = [ + [1541, 3698], + [1541, 3716], +] + +[[area.polygons]] +vertices = [ + [1600, 3698], + [1600, 3769], +] + +[[area.polygons]] +vertices = [ + [1540, 3699], + [1540, 3713], +] + +[[area.polygons]] +vertices = [ + [1601, 3699], + [1601, 3769], +] + +[[area.polygons]] +vertices = [ + [1539, 3700], + [1539, 3711], +] + +[[area.polygons]] +vertices = [ + [1602, 3700], + [1602, 3769], +] + +[[area.polygons]] +vertices = [ + [1603, 3701], + [1603, 3769], +] + +[[area.polygons]] +vertices = [ + [1604, 3702], + [1605, 3702], + [1605, 3768], + [1604, 3768], +] + +[[area.polygons]] +vertices = [ + [1606, 3703], + [1606, 3767], +] + +[[area.polygons]] +vertices = [ + [1538, 3704], + [1538, 3709], +] + +[[area.polygons]] +vertices = [ + [1607, 3704], + [1607, 3766], +] + +[[area.polygons]] +vertices = [ + [1537, 3705], + [1537, 3708], +] + +[[area.polygons]] +vertices = [ + [1608, 3705], + [1608, 3765], +] + +[[area.polygons]] +vertices = [ + [1609, 3706], + [1609, 3764], +] + +[[area.polygons]] +vertices = [ + [1610, 3707], + [1610, 3764], +] + +[[area.polygons]] +vertices = [ + [1611, 3708], + [1611, 3764], +] + +[[area.polygons]] +vertices = [ + [1612, 3709], + [1612, 3763], +] + +[[area.polygons]] +vertices = [ + [1613, 3710], + [1613, 3762], +] + +[[area.polygons]] +vertices = [ + [1614, 3711], + [1614, 3758], +] + +[[area.polygons]] +vertices = [ + [1615, 3712], + [1615, 3757], +] + +[[area.polygons]] +vertices = [ + [1616, 3713], + [1616, 3756], +] + +[[area.polygons]] +vertices = [ + [1617, 3714], + [1618, 3714], + [1618, 3730], + [1617, 3730], +] + +[[area.polygons]] +vertices = [ + [1619, 3715], + [1620, 3715], + [1620, 3728], + [1619, 3728], +] + +[[area.polygons]] +vertices = [ + [1621, 3716], + [1623, 3716], + [1623, 3722], + [1621, 3722], +] + +[[area.polygons]] +vertices = [ + [1624, 3717], + [1624, 3721], +] + +[[area.polygons]] +vertices = [ + [1544, 3721], + [1558, 3721], + [1558, 3723], + [1544, 3723], +] + +[[area.polygons]] +vertices = [ + [1621, 3723], + [1622, 3723], +] + +[[area.polygons]] +vertices = [ + [1545, 3724], + [1558, 3724], + [1558, 3725], + [1545, 3725], +] + +[[area.polygons]] +vertices = [[1621, 3724]] + +[[area.polygons]] +vertices = [ + [1546, 3726], + [1558, 3726], + [1558, 3727], + [1546, 3727], +] + +[[area.polygons]] +vertices = [ + [1547, 3728], + [1558, 3728], +] + +[[area.polygons]] +vertices = [ + [1548, 3729], + [1558, 3729], +] + +[[area.polygons]] +vertices = [[1619, 3729]] + +[[area.polygons]] +vertices = [ + [1549, 3730], + [1558, 3730], + [1558, 3742], + [1549, 3742], +] + +[[area.polygons]] +vertices = [ + [1617, 3731], + [1617, 3755], +] + +[[area.polygons]] +vertices = [ + [1406, 3740], + [1429, 3740], + [1429, 3742], + [1406, 3742], +] + +[[area.polygons]] +vertices = [ + [1407, 3743], + [1429, 3743], + [1429, 3782], + [1407, 3782], +] + +[[area.polygons]] +vertices = [ + [1570, 3743], + [1575, 3743], +] + +[[area.polygons]] +vertices = [ + [1572, 3744], + [1575, 3744], +] + +[[area.polygons]] +vertices = [ + [1574, 3745], + [1575, 3745], +] + +[[area.polygons]] +vertices = [ + [1406, 3746], + [1406, 3829], +] + +[[area.polygons]] +vertices = [ + [1405, 3747], + [1405, 3829], +] + +[[area.polygons]] +vertices = [ + [1404, 3748], + [1404, 3829], +] + +[[area.polygons]] +vertices = [ + [1403, 3749], + [1403, 3829], +] + +[[area.polygons]] +vertices = [ + [1402, 3750], + [1402, 3829], +] + +[[area.polygons]] +vertices = [ + [1401, 3751], + [1401, 3829], +] + +[[area.polygons]] +vertices = [ + [1400, 3752], + [1400, 3829], +] + +[[area.polygons]] +vertices = [ + [1399, 3753], + [1399, 3829], +] + +[[area.polygons]] +vertices = [ + [1398, 3754], + [1398, 3829], +] + +[[area.polygons]] +vertices = [ + [1430, 3754], + [1437, 3754], + [1437, 3757], + [1430, 3757], +] + +[[area.polygons]] +vertices = [ + [1397, 3755], + [1397, 3829], +] + +[[area.polygons]] +vertices = [ + [1396, 3756], + [1396, 3829], +] + +[[area.polygons]] +vertices = [ + [1344, 3757], + [1364, 3757], + [1364, 3797], + [1344, 3797], +] + +[[area.polygons]] +vertices = [ + [1395, 3757], + [1395, 3829], +] + +[[area.polygons]] +vertices = [ + [1430, 3758], + [1430, 3781], +] + +[[area.polygons]] +vertices = [ + [1431, 3763], + [1431, 3780], +] + +[[area.polygons]] +vertices = [ + [1432, 3764], + [1432, 3779], +] + +[[area.polygons]] +vertices = [ + [1433, 3765], + [1433, 3766], +] + +[[area.polygons]] +vertices = [[1604, 3769]] + +[[area.polygons]] +vertices = [ + [1589, 3770], + [1598, 3770], +] + +[[area.polygons]] +vertices = [ + [1365, 3771], + [1394, 3771], + [1394, 3816], + [1365, 3816], +] + +[[area.polygons]] +vertices = [ + [1589, 3771], + [1597, 3771], +] + +[[area.polygons]] +vertices = [ + [1593, 3772], + [1597, 3772], +] + +[[area.polygons]] +vertices = [ + [1596, 3773], + [1597, 3773], +] + +[[area.polygons]] +vertices = [ + [1407, 3783], + [1428, 3783], +] + +[[area.polygons]] +vertices = [ + [1407, 3784], + [1427, 3784], +] + +[[area.polygons]] +vertices = [ + [1407, 3785], + [1426, 3785], +] + +[[area.polygons]] +vertices = [ + [1407, 3786], + [1425, 3786], +] + +[[area.polygons]] +vertices = [ + [1407, 3787], + [1424, 3787], +] + +[[area.polygons]] +vertices = [ + [1407, 3788], + [1423, 3788], +] + +[[area.polygons]] +vertices = [ + [1407, 3789], + [1422, 3789], +] + +[[area.polygons]] +vertices = [ + [1407, 3790], + [1421, 3790], +] + +[[area.polygons]] +vertices = [ + [1407, 3791], + [1420, 3791], +] + +[[area.polygons]] +vertices = [ + [1407, 3792], + [1419, 3792], + [1419, 3829], + [1407, 3829], +] + +[[area.polygons]] +vertices = [ + [1360, 3798], + [1364, 3798], + [1364, 3829], + [1360, 3829], +] + +[[area.polygons]] +vertices = [ + [1365, 3817], + [1380, 3817], +] + +[[area.polygons]] +vertices = [ + [1383, 3817], + [1394, 3817], +] + +[[area.polygons]] +vertices = [ + [1365, 3818], + [1377, 3818], +] + +[[area.polygons]] +vertices = [ + [1386, 3818], + [1394, 3818], +] + +[[area.polygons]] +vertices = [ + [1365, 3819], + [1376, 3819], + [1376, 3820], + [1365, 3820], +] + +[[area.polygons]] +vertices = [ + [1387, 3819], + [1394, 3819], +] + +[[area.polygons]] +vertices = [ + [1388, 3820], + [1394, 3820], + [1394, 3821], + [1388, 3821], +] + +[[area.polygons]] +vertices = [ + [1365, 3821], + [1375, 3821], + [1375, 3829], + [1365, 3829], +] + +[[area.polygons]] +vertices = [ + [1389, 3822], + [1394, 3822], + [1394, 3829], + [1389, 3829], +] + +[[area.polygons]] +vertices = [ + [1728, 10048], + [1731, 10048], + [1731, 10053], + [1728, 10053], +] + +[[area.polygons]] +vertices = [ + [1472, 10090], + [1474, 10090], + [1474, 10101], + [1472, 10101], +] diff --git a/.data/raw-cache/map/area/ruins_of_isolation.toml b/.data/raw-cache/map/area/ruins_of_isolation.toml new file mode 100644 index 000000000..94a791ccf --- /dev/null +++ b/.data/raw-cache/map/area/ruins_of_isolation.toml @@ -0,0 +1,648 @@ +# Ruins of Isolation music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Ruins of Isolation. +# Bounds x 3385-3450, z 2752-2860; 4771 tiles as 103 rectangles. + +[[area]] +name = "Ruins of Isolation" +area_id = "area.ruins_of_isolation" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3398, 2752], + [3399, 2752], + [3399, 2838], + [3398, 2838], +] + +[[area.polygons]] +vertices = [ + [3400, 2753], + [3400, 2840], +] + +[[area.polygons]] +vertices = [ + [3401, 2755], + [3403, 2755], + [3403, 2842], + [3401, 2842], +] + +[[area.polygons]] +vertices = [ + [3404, 2756], + [3404, 2848], +] + +[[area.polygons]] +vertices = [ + [3420, 2757], + [3426, 2757], + [3426, 2860], + [3420, 2860], +] + +[[area.polygons]] +vertices = [ + [3405, 2758], + [3406, 2758], + [3406, 2849], + [3405, 2849], +] + +[[area.polygons]] +vertices = [ + [3419, 2758], + [3419, 2860], +] + +[[area.polygons]] +vertices = [ + [3427, 2758], + [3427, 2811], +] + +[[area.polygons]] +vertices = [ + [3407, 2759], + [3407, 2852], +] + +[[area.polygons]] +vertices = [ + [3418, 2759], + [3418, 2860], +] + +[[area.polygons]] +vertices = [ + [3428, 2759], + [3428, 2764], +] + +[[area.polygons]] +vertices = [ + [3408, 2760], + [3408, 2854], +] + +[[area.polygons]] +vertices = [ + [3412, 2760], + [3412, 2859], +] + +[[area.polygons]] +vertices = [ + [3417, 2760], + [3417, 2860], +] + +[[area.polygons]] +vertices = [ + [3409, 2761], + [3411, 2761], + [3411, 2855], + [3409, 2855], +] + +[[area.polygons]] +vertices = [ + [3413, 2761], + [3416, 2761], + [3416, 2860], + [3413, 2860], +] + +[[area.polygons]] +vertices = [ + [3437, 2764], + [3437, 2767], +] + +[[area.polygons]] +vertices = [ + [3436, 2765], + [3436, 2766], +] + +[[area.polygons]] +vertices = [ + [3438, 2765], + [3439, 2765], + [3439, 2768], + [3438, 2768], +] + +[[area.polygons]] +vertices = [ + [3428, 2766], + [3428, 2810], +] + +[[area.polygons]] +vertices = [ + [3440, 2766], + [3441, 2766], + [3441, 2773], + [3440, 2773], +] + +[[area.polygons]] +vertices = [ + [3429, 2768], + [3429, 2810], +] + +[[area.polygons]] +vertices = [ + [3430, 2769], + [3430, 2781], +] + +[[area.polygons]] +vertices = [ + [3439, 2769], + [3439, 2771], +] + +[[area.polygons]] +vertices = [ + [3431, 2770], + [3431, 2779], +] + +[[area.polygons]] +vertices = [ + [3442, 2770], + [3442, 2799], +] + +[[area.polygons]] +vertices = [ + [3432, 2771], + [3432, 2778], +] + +[[area.polygons]] +vertices = [ + [3443, 2771], + [3443, 2799], +] + +[[area.polygons]] +vertices = [ + [3433, 2772], + [3433, 2775], +] + +[[area.polygons]] +vertices = [ + [3444, 2772], + [3444, 2793], +] + +[[area.polygons]] +vertices = [[3441, 2774]] + +[[area.polygons]] +vertices = [ + [3445, 2775], + [3445, 2776], +] + +[[area.polygons]] +vertices = [ + [3441, 2778], + [3441, 2800], +] + +[[area.polygons]] +vertices = [ + [3440, 2779], + [3440, 2800], +] + +[[area.polygons]] +vertices = [ + [3445, 2779], + [3445, 2792], +] + +[[area.polygons]] +vertices = [ + [3439, 2781], + [3439, 2800], +] + +[[area.polygons]] +vertices = [ + [3446, 2782], + [3446, 2791], +] + +[[area.polygons]] +vertices = [ + [3430, 2783], + [3430, 2793], +] + +[[area.polygons]] +vertices = [ + [3438, 2784], + [3438, 2800], +] + +[[area.polygons]] +vertices = [ + [3431, 2785], + [3433, 2785], + [3433, 2792], + [3431, 2792], +] + +[[area.polygons]] +vertices = [ + [3437, 2785], + [3437, 2795], +] + +[[area.polygons]] +vertices = [ + [3434, 2786], + [3436, 2786], + [3436, 2792], + [3434, 2792], +] + +[[area.polygons]] +vertices = [ + [3447, 2788], + [3447, 2791], +] + +[[area.polygons]] +vertices = [ + [3397, 2790], + [3397, 2837], +] + +[[area.polygons]] +vertices = [ + [3396, 2791], + [3396, 2836], +] + +[[area.polygons]] +vertices = [ + [3395, 2792], + [3395, 2835], +] + +[[area.polygons]] +vertices = [ + [3394, 2793], + [3394, 2834], +] + +[[area.polygons]] +vertices = [ + [3435, 2793], + [3436, 2793], +] + +[[area.polygons]] +vertices = [ + [3393, 2794], + [3393, 2833], +] + +[[area.polygons]] +vertices = [[3436, 2794]] + +[[area.polygons]] +vertices = [ + [3392, 2795], + [3392, 2832], +] + +[[area.polygons]] +vertices = [ + [3391, 2796], + [3391, 2831], +] + +[[area.polygons]] +vertices = [ + [3444, 2796], + [3444, 2798], +] + +[[area.polygons]] +vertices = [ + [3390, 2797], + [3390, 2830], +] + +[[area.polygons]] +vertices = [ + [3430, 2797], + [3430, 2811], +] + +[[area.polygons]] +vertices = [ + [3389, 2798], + [3389, 2823], +] + +[[area.polygons]] +vertices = [ + [3437, 2798], + [3437, 2800], +] + +[[area.polygons]] +vertices = [ + [3388, 2799], + [3388, 2822], +] + +[[area.polygons]] +vertices = [ + [3431, 2799], + [3431, 2811], +] + +[[area.polygons]] +vertices = [ + [3387, 2800], + [3387, 2822], +] + +[[area.polygons]] +vertices = [ + [3386, 2801], + [3386, 2821], +] + +[[area.polygons]] +vertices = [ + [3432, 2801], + [3432, 2811], +] + +[[area.polygons]] +vertices = [ + [3385, 2802], + [3385, 2821], +] + +[[area.polygons]] +vertices = [ + [3439, 2803], + [3439, 2816], +] + +[[area.polygons]] +vertices = [ + [3433, 2804], + [3433, 2812], +] + +[[area.polygons]] +vertices = [ + [3437, 2804], + [3438, 2804], + [3438, 2815], + [3437, 2815], +] + +[[area.polygons]] +vertices = [ + [3440, 2804], + [3440, 2816], +] + +[[area.polygons]] +vertices = [ + [3434, 2805], + [3436, 2805], + [3436, 2813], + [3434, 2813], +] + +[[area.polygons]] +vertices = [ + [3441, 2805], + [3441, 2815], +] + +[[area.polygons]] +vertices = [ + [3442, 2807], + [3442, 2815], +] + +[[area.polygons]] +vertices = [ + [3443, 2810], + [3443, 2815], +] + +[[area.polygons]] +vertices = [ + [3444, 2811], + [3444, 2816], +] + +[[area.polygons]] +vertices = [ + [3445, 2812], + [3445, 2815], +] + +[[area.polygons]] +vertices = [ + [3446, 2813], + [3446, 2814], +] + +[[area.polygons]] +vertices = [ + [3427, 2814], + [3431, 2814], + [3431, 2860], + [3427, 2860], +] + +[[area.polygons]] +vertices = [ + [3435, 2814], + [3436, 2814], +] + +[[area.polygons]] +vertices = [ + [3432, 2815], + [3432, 2860], +] + +[[area.polygons]] +vertices = [[3436, 2815]] + +[[area.polygons]] +vertices = [ + [3433, 2816], + [3433, 2860], +] + +[[area.polygons]] +vertices = [[3438, 2816]] + +[[area.polygons]] +vertices = [ + [3434, 2817], + [3435, 2817], + [3435, 2860], + [3434, 2860], +] + +[[area.polygons]] +vertices = [ + [3436, 2818], + [3437, 2818], + [3437, 2860], + [3436, 2860], +] + +[[area.polygons]] +vertices = [ + [3441, 2818], + [3442, 2818], + [3442, 2860], + [3441, 2860], +] + +[[area.polygons]] +vertices = [ + [3438, 2819], + [3440, 2819], + [3440, 2860], + [3438, 2860], +] + +[[area.polygons]] +vertices = [ + [3443, 2819], + [3443, 2838], +] + +[[area.polygons]] +vertices = [ + [3444, 2823], + [3444, 2832], +] + +[[area.polygons]] +vertices = [ + [3445, 2825], + [3446, 2825], + [3446, 2830], + [3445, 2830], +] + +[[area.polygons]] +vertices = [ + [3447, 2827], + [3447, 2829], +] + +[[area.polygons]] +vertices = [[3445, 2831]] + +[[area.polygons]] +vertices = [ + [3444, 2834], + [3444, 2837], +] + +[[area.polygons]] +vertices = [[3399, 2839]] + +[[area.polygons]] +vertices = [ + [3443, 2840], + [3443, 2860], +] + +[[area.polygons]] +vertices = [ + [3444, 2842], + [3444, 2860], +] + +[[area.polygons]] +vertices = [ + [3402, 2843], + [3403, 2843], + [3403, 2844], + [3402, 2844], +] + +[[area.polygons]] +vertices = [ + [3403, 2845], + [3403, 2846], +] + +[[area.polygons]] +vertices = [ + [3445, 2848], + [3446, 2848], + [3446, 2860], + [3445, 2860], +] + +[[area.polygons]] +vertices = [ + [3406, 2850], + [3406, 2851], +] + +[[area.polygons]] +vertices = [ + [3447, 2853], + [3447, 2860], +] + +[[area.polygons]] +vertices = [ + [3448, 2854], + [3448, 2860], +] + +[[area.polygons]] +vertices = [ + [3449, 2855], + [3449, 2860], +] + +[[area.polygons]] +vertices = [ + [3410, 2856], + [3411, 2856], +] + +[[area.polygons]] +vertices = [ + [3411, 2857], + [3411, 2858], +] + +[[area.polygons]] +vertices = [ + [3450, 2858], + [3450, 2859], +] diff --git a/.data/raw-cache/map/area/rune_essence.toml b/.data/raw-cache/map/area/rune_essence.toml new file mode 100644 index 000000000..79019f12e --- /dev/null +++ b/.data/raw-cache/map/area/rune_essence.toml @@ -0,0 +1,16 @@ +# Rune Essence music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Rune Essence. +# Bounds x 2880-2943, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Rune Essence" +area_id = "area.rune_essence" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2880, 4800], + [2943, 4800], + [2943, 4863], + [2880, 4863], +] diff --git a/.data/raw-cache/map/area/sad_meadow.toml b/.data/raw-cache/map/area/sad_meadow.toml new file mode 100644 index 000000000..2b778aebd --- /dev/null +++ b/.data/raw-cache/map/area/sad_meadow.toml @@ -0,0 +1,16 @@ +# Sad Meadow music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Sad Meadow. +# Bounds x 2528-2559, z 9728-9759; 1024 tiles as 1 rectangles. + +[[area]] +name = "Sad Meadow" +area_id = "area.sad_meadow" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2528, 9728], + [2559, 9728], + [2559, 9759], + [2528, 9759], +] diff --git a/.data/raw-cache/map/area/safety_in_numbers.toml b/.data/raw-cache/map/area/safety_in_numbers.toml new file mode 100644 index 000000000..06ededb5f --- /dev/null +++ b/.data/raw-cache/map/area/safety_in_numbers.toml @@ -0,0 +1,16 @@ +# Safety in Numbers music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Safety in Numbers. +# Bounds x 3136-3199, z 9664-9727; 4096 tiles as 1 rectangles. + +[[area]] +name = "Safety in Numbers" +area_id = "area.safety_in_numbers" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 9664], + [3199, 9664], + [3199, 9727], + [3136, 9727], +] diff --git a/.data/raw-cache/map/area/saga.toml b/.data/raw-cache/map/area/saga.toml new file mode 100644 index 000000000..909b5a8a4 --- /dev/null +++ b/.data/raw-cache/map/area/saga.toml @@ -0,0 +1,135 @@ +# Saga music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Saga. +# Bounds x 1936-1960, z 4056-4082; 470 tiles as 20 rectangles. + +[[area]] +name = "Saga" +area_id = "area.saga" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1952, 4056], + [1954, 4056], + [1954, 4079], + [1952, 4079], +] + +[[area.polygons]] +vertices = [ + [1951, 4057], + [1951, 4081], +] + +[[area.polygons]] +vertices = [ + [1955, 4057], + [1957, 4057], + [1957, 4072], + [1955, 4072], +] + +[[area.polygons]] +vertices = [ + [1950, 4058], + [1950, 4081], +] + +[[area.polygons]] +vertices = [ + [1958, 4058], + [1958, 4069], +] + +[[area.polygons]] +vertices = [ + [1949, 4059], + [1949, 4082], +] + +[[area.polygons]] +vertices = [ + [1942, 4060], + [1948, 4060], + [1948, 4082], + [1942, 4082], +] + +[[area.polygons]] +vertices = [ + [1940, 4061], + [1941, 4061], + [1941, 4081], + [1940, 4081], +] + +[[area.polygons]] +vertices = [ + [1959, 4061], + [1959, 4067], +] + +[[area.polygons]] +vertices = [ + [1939, 4062], + [1939, 4068], +] + +[[area.polygons]] +vertices = [ + [1960, 4063], + [1960, 4066], +] + +[[area.polygons]] +vertices = [ + [1938, 4064], + [1938, 4067], +] + +[[area.polygons]] +vertices = [ + [1939, 4070], + [1939, 4081], +] + +[[area.polygons]] +vertices = [ + [1938, 4073], + [1938, 4080], +] + +[[area.polygons]] +vertices = [ + [1955, 4073], + [1956, 4073], + [1956, 4076], + [1955, 4076], +] + +[[area.polygons]] +vertices = [ + [1937, 4076], + [1937, 4080], +] + +[[area.polygons]] +vertices = [ + [1955, 4077], + [1955, 4078], +] + +[[area.polygons]] +vertices = [ + [1936, 4078], + [1936, 4079], +] + +[[area.polygons]] +vertices = [ + [1952, 4080], + [1953, 4080], +] + +[[area.polygons]] +vertices = [[1952, 4081]] diff --git a/.data/raw-cache/map/area/sands_of_time.toml b/.data/raw-cache/map/area/sands_of_time.toml new file mode 100644 index 000000000..969dfc365 --- /dev/null +++ b/.data/raw-cache/map/area/sands_of_time.toml @@ -0,0 +1,16 @@ +# Sands of Time music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Sands of Time. +# Bounds x 3648-3711, z 5376-5439; 4096 tiles as 1 rectangles. + +[[area]] +name = "Sands of Time" +area_id = "area.sands_of_time" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3648, 5376], + [3711, 5376], + [3711, 5439], + [3648, 5439], +] diff --git a/.data/raw-cache/map/area/sarachnis.toml b/.data/raw-cache/map/area/sarachnis.toml new file mode 100644 index 000000000..8b9eaab14 --- /dev/null +++ b/.data/raw-cache/map/area/sarachnis.toml @@ -0,0 +1,16 @@ +# Sarachnis music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Sarachnis. +# Bounds x 1792-1855, z 9856-9911; 3584 tiles as 1 rectangles. + +[[area]] +name = "Sarachnis" +area_id = "area.sarachnis" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1792, 9856], + [1855, 9856], + [1855, 9911], + [1792, 9911], +] diff --git a/.data/raw-cache/map/area/sarcophagus.toml b/.data/raw-cache/map/area/sarcophagus.toml new file mode 100644 index 000000000..f0c446524 --- /dev/null +++ b/.data/raw-cache/map/area/sarcophagus.toml @@ -0,0 +1,24 @@ +# Sarcophagus music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Sarcophagus. +# Bounds x 2752-3263, z 4928-9343; 16384 tiles as 2 rectangles. + +[[area]] +name = "Sarcophagus" +area_id = "area.sarcophagus" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 4928], + [2943, 4928], + [2943, 4991], + [2752, 4991], +] + +[[area.polygons]] +vertices = [ + [3200, 9280], + [3263, 9280], + [3263, 9343], + [3200, 9343], +] diff --git a/.data/raw-cache/map/area/sarims_vermin.toml b/.data/raw-cache/map/area/sarims_vermin.toml new file mode 100644 index 000000000..2878b0ff7 --- /dev/null +++ b/.data/raw-cache/map/area/sarims_vermin.toml @@ -0,0 +1,16 @@ +# Sarim's Vermin music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Sarim's Vermin. +# Bounds x 2944-3007, z 9600-9663; 4096 tiles as 1 rectangles. + +[[area]] +name = "Sarim's Vermin" +area_id = "area.sarims_vermin" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2944, 9600], + [3007, 9600], + [3007, 9663], + [2944, 9663], +] diff --git a/.data/raw-cache/map/area/scape_cave.toml b/.data/raw-cache/map/area/scape_cave.toml new file mode 100644 index 000000000..b246493b4 --- /dev/null +++ b/.data/raw-cache/map/area/scape_cave.toml @@ -0,0 +1,40 @@ +# Scape Cave music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Scape Cave. +# Bounds x 1536-3286, z 9344-9919; 21952 tiles as 4 rectangles. + +[[area]] +name = "Scape Cave" +area_id = "area.scape_cave" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 9344], + [3071, 9344], + [3071, 9407], + [3008, 9407], +] + +[[area.polygons]] +vertices = [ + [3072, 9472], + [3135, 9472], + [3135, 9535], + [3072, 9535], +] + +[[area.polygons]] +vertices = [ + [1536, 9856], + [1599, 9856], + [1599, 9919], + [1536, 9919], +] + +[[area.polygons]] +vertices = [ + [3136, 9856], + [3286, 9856], + [3286, 9919], + [3136, 9919], +] diff --git a/.data/raw-cache/map/area/scape_main.toml b/.data/raw-cache/map/area/scape_main.toml new file mode 100644 index 000000000..263e28c33 --- /dev/null +++ b/.data/raw-cache/map/area/scape_main.toml @@ -0,0 +1,16 @@ +# Scape Main music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Scape Main. +# Bounds x 1600-1727, z 5632-5759; 16384 tiles as 1 rectangles. + +[[area]] +name = "Scape Main" +area_id = "area.scape_main" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1600, 5632], + [1727, 5632], + [1727, 5759], + [1600, 5759], +] diff --git a/.data/raw-cache/map/area/scar_tissue.toml b/.data/raw-cache/map/area/scar_tissue.toml new file mode 100644 index 000000000..6f8c5bea5 --- /dev/null +++ b/.data/raw-cache/map/area/scar_tissue.toml @@ -0,0 +1,16 @@ +# Scar Tissue music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Scar Tissue. +# Bounds x 1728-2239, z 6400-6463; 32768 tiles as 1 rectangles. + +[[area]] +name = "Scar Tissue" +area_id = "area.scar_tissue" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1728, 6400], + [2239, 6400], + [2239, 6463], + [1728, 6463], +] diff --git a/.data/raw-cache/map/area/schools_out.toml b/.data/raw-cache/map/area/schools_out.toml new file mode 100644 index 000000000..6e522bfb8 --- /dev/null +++ b/.data/raw-cache/map/area/schools_out.toml @@ -0,0 +1,16 @@ +# School's Out music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: School's Out. +# Bounds x 1856-1919, z 4992-5055; 4096 tiles as 1 rectangles. + +[[area]] +name = "School's Out" +area_id = "area.schools_out" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 4992], + [1919, 4992], + [1919, 5055], + [1856, 5055], +] diff --git a/.data/raw-cache/map/area/scorpia_dances.toml b/.data/raw-cache/map/area/scorpia_dances.toml new file mode 100644 index 000000000..ff083120e --- /dev/null +++ b/.data/raw-cache/map/area/scorpia_dances.toml @@ -0,0 +1,16 @@ +# Scorpia Dances music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Scorpia Dances. +# Bounds x 3200-3263, z 10304-10367; 4096 tiles as 1 rectangles. + +[[area]] +name = "Scorpia Dances" +area_id = "area.scorpia_dances" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3200, 10304], + [3263, 10304], + [3263, 10367], + [3200, 10367], +] diff --git a/.data/raw-cache/map/area/sea_shanty_2.toml b/.data/raw-cache/map/area/sea_shanty_2.toml new file mode 100644 index 000000000..653e92e9b --- /dev/null +++ b/.data/raw-cache/map/area/sea_shanty_2.toml @@ -0,0 +1,16 @@ +# Sea Shanty 2 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Sea Shanty 2. +# Bounds x 2157-2164, z 3325-3326; 16 tiles as 1 rectangles. + +[[area]] +name = "Sea Shanty 2" +area_id = "area.sea_shanty_2" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2157, 3325], + [2164, 3325], + [2164, 3326], + [2157, 3326], +] diff --git a/.data/raw-cache/map/area/secrets_of_the_north.toml b/.data/raw-cache/map/area/secrets_of_the_north.toml new file mode 100644 index 000000000..086a02a9b --- /dev/null +++ b/.data/raw-cache/map/area/secrets_of_the_north.toml @@ -0,0 +1,16 @@ +# Secrets of the North music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Secrets of the North. +# Bounds x 2880-2943, z 10304-10367; 4096 tiles as 1 rectangles. + +[[area]] +name = "Secrets of the North" +area_id = "area.secrets_of_the_north" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2880, 10304], + [2943, 10304], + [2943, 10367], + [2880, 10367], +] diff --git a/.data/raw-cache/map/area/seers_village.toml b/.data/raw-cache/map/area/seers_village.toml new file mode 100644 index 000000000..71d94cff0 --- /dev/null +++ b/.data/raw-cache/map/area/seers_village.toml @@ -0,0 +1,666 @@ +# Regenerated as rectangles from the OSRS wiki music map: 490 tiles moved to +# neighbouring music areas and 5753 tiles were added. + +[[area]] +name = "Seers' Village" +area_id = "area.seers_village" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2741, 3436], + [2745, 3436], + [2745, 3469], + [2741, 3469], +] + +[[area.polygons]] +vertices = [ + [2738, 3437], + [2740, 3437], + [2740, 3524], + [2738, 3524], +] + +[[area.polygons]] +vertices = [ + [2746, 3437], + [2751, 3437], + [2751, 3469], + [2746, 3469], +] + +[[area.polygons]] +vertices = [ + [2737, 3438], + [2737, 3523], +] + +[[area.polygons]] +vertices = [ + [2734, 3439], + [2736, 3439], + [2736, 3520], + [2734, 3520], +] + +[[area.polygons]] +vertices = [ + [2732, 3440], + [2733, 3440], + [2733, 3520], + [2732, 3520], +] + +[[area.polygons]] +vertices = [ + [2752, 3440], + [2790, 3440], + [2790, 3469], + [2752, 3469], +] + +[[area.polygons]] +vertices = [ + [2730, 3441], + [2731, 3441], + [2731, 3520], + [2730, 3520], +] + +[[area.polygons]] +vertices = [ + [2727, 3442], + [2729, 3442], + [2729, 3520], + [2727, 3520], +] + +[[area.polygons]] +vertices = [ + [2726, 3443], + [2726, 3520], +] + +[[area.polygons]] +vertices = [ + [2725, 3444], + [2725, 3520], +] + +[[area.polygons]] +vertices = [ + [2720, 3445], + [2724, 3445], + [2724, 3520], + [2720, 3520], +] + +[[area.polygons]] +vertices = [ + [2719, 3446], + [2719, 3520], +] + +[[area.polygons]] +vertices = [ + [2718, 3447], + [2718, 3520], +] + +[[area.polygons]] +vertices = [ + [2717, 3450], + [2717, 3520], +] + +[[area.polygons]] +vertices = [ + [2716, 3451], + [2716, 3520], +] + +[[area.polygons]] +vertices = [ + [2715, 3452], + [2715, 3520], +] + +[[area.polygons]] +vertices = [ + [2714, 3453], + [2714, 3520], +] + +[[area.polygons]] +vertices = [ + [2713, 3454], + [2713, 3520], +] + +[[area.polygons]] +vertices = [ + [2712, 3455], + [2712, 3520], +] + +[[area.polygons]] +vertices = [ + [2689, 3456], + [2694, 3456], + [2694, 3520], + [2689, 3520], +] + +[[area.polygons]] +vertices = [ + [2701, 3456], + [2703, 3456], + [2703, 3520], + [2701, 3520], +] + +[[area.polygons]] +vertices = [ + [2708, 3456], + [2711, 3456], + [2711, 3520], + [2708, 3520], +] + +[[area.polygons]] +vertices = [ + [2688, 3457], + [2688, 3521], +] + +[[area.polygons]] +vertices = [ + [2695, 3457], + [2700, 3457], + [2700, 3520], + [2695, 3520], +] + +[[area.polygons]] +vertices = [ + [2704, 3457], + [2707, 3457], + [2707, 3520], + [2704, 3520], +] + +[[area.polygons]] +vertices = [ + [2683, 3459], + [2687, 3459], + [2687, 3522], + [2683, 3522], +] + +[[area.polygons]] +vertices = [ + [2682, 3460], + [2682, 3528], +] + +[[area.polygons]] +vertices = [ + [2681, 3462], + [2681, 3529], +] + +[[area.polygons]] +vertices = [ + [2676, 3463], + [2680, 3463], + [2680, 3530], + [2676, 3530], +] + +[[area.polygons]] +vertices = [ + [2610, 3464], + [2619, 3464], + [2619, 3512], + [2610, 3512], +] + +[[area.polygons]] +vertices = [ + [2675, 3464], + [2675, 3534], +] + +[[area.polygons]] +vertices = [ + [2609, 3465], + [2609, 3511], +] + +[[area.polygons]] +vertices = [ + [2620, 3465], + [2620, 3518], +] + +[[area.polygons]] +vertices = [ + [2674, 3465], + [2674, 3535], +] + +[[area.polygons]] +vertices = [ + [2608, 3466], + [2608, 3510], +] + +[[area.polygons]] +vertices = [ + [2607, 3467], + [2607, 3509], +] + +[[area.polygons]] +vertices = [ + [2621, 3467], + [2621, 3518], +] + +[[area.polygons]] +vertices = [ + [2662, 3467], + [2668, 3467], + [2668, 3541], + [2662, 3541], +] + +[[area.polygons]] +vertices = [ + [2673, 3467], + [2673, 3537], +] + +[[area.polygons]] +vertices = [ + [2606, 3468], + [2606, 3508], +] + +[[area.polygons]] +vertices = [ + [2622, 3468], + [2623, 3468], + [2623, 3518], + [2622, 3518], +] + +[[area.polygons]] +vertices = [ + [2630, 3468], + [2640, 3468], + [2640, 3518], + [2630, 3518], +] + +[[area.polygons]] +vertices = [ + [2661, 3468], + [2661, 3541], +] + +[[area.polygons]] +vertices = [ + [2669, 3468], + [2672, 3468], + [2672, 3538], + [2669, 3538], +] + +[[area.polygons]] +vertices = [ + [2605, 3469], + [2605, 3508], +] + +[[area.polygons]] +vertices = [ + [2624, 3469], + [2624, 3518], +] + +[[area.polygons]] +vertices = [ + [2629, 3469], + [2629, 3518], +] + +[[area.polygons]] +vertices = [ + [2641, 3469], + [2645, 3469], + [2645, 3527], + [2641, 3527], +] + +[[area.polygons]] +vertices = [ + [2656, 3469], + [2660, 3469], + [2660, 3539], + [2656, 3539], +] + +[[area.polygons]] +vertices = [ + [2604, 3470], + [2604, 3508], +] + +[[area.polygons]] +vertices = [ + [2625, 3470], + [2628, 3470], + [2628, 3518], + [2625, 3518], +] + +[[area.polygons]] +vertices = [ + [2646, 3470], + [2655, 3470], + [2655, 3531], + [2646, 3531], +] + +[[area.polygons]] +vertices = [ + [2603, 3471], + [2603, 3507], +] + +[[area.polygons]] +vertices = [ + [2602, 3472], + [2602, 3506], +] + +[[area.polygons]] +vertices = [ + [2601, 3473], + [2601, 3505], +] + +[[area.polygons]] +vertices = [ + [2600, 3474], + [2600, 3504], +] + +[[area.polygons]] +vertices = [ + [2791, 3477], + [2794, 3477], + [2794, 3490], + [2791, 3490], +] + +[[area.polygons]] +vertices = [ + [2795, 3482], + [2795, 3490], +] + +[[area.polygons]] +vertices = [ + [2796, 3483], + [2796, 3490], +] + +[[area.polygons]] +vertices = [ + [2797, 3484], + [2798, 3484], + [2798, 3490], + [2797, 3490], +] + +[[area.polygons]] +vertices = [ + [2799, 3485], + [2799, 3490], +] + +[[area.polygons]] +vertices = [ + [2791, 3491], + [2793, 3491], +] + +[[area.polygons]] +vertices = [ + [2791, 3492], + [2792, 3492], +] + +[[area.polygons]] +vertices = [[2791, 3493]] + +[[area.polygons]] +vertices = [ + [2791, 3509], + [2791, 3520], +] + +[[area.polygons]] +vertices = [ + [2611, 3513], + [2619, 3513], +] + +[[area.polygons]] +vertices = [ + [2615, 3514], + [2619, 3514], +] + +[[area.polygons]] +vertices = [ + [2616, 3515], + [2619, 3515], +] + +[[area.polygons]] +vertices = [ + [2617, 3516], + [2619, 3516], +] + +[[area.polygons]] +vertices = [ + [2741, 3516], + [2743, 3516], + [2743, 3524], + [2741, 3524], +] + +[[area.polygons]] +vertices = [ + [2784, 3516], + [2790, 3516], + [2790, 3520], + [2784, 3520], +] + +[[area.polygons]] +vertices = [ + [2618, 3517], + [2619, 3517], +] + +[[area.polygons]] +vertices = [[2619, 3518]] + +[[area.polygons]] +vertices = [ + [2744, 3518], + [2783, 3518], + [2783, 3520], + [2744, 3520], +] + +[[area.polygons]] +vertices = [ + [2640, 3519], + [2640, 3526], +] + +[[area.polygons]] +vertices = [ + [2735, 3521], + [2736, 3521], +] + +[[area.polygons]] +vertices = [ + [2744, 3521], + [2755, 3521], +] + +[[area.polygons]] +vertices = [[2736, 3522]] + +[[area.polygons]] +vertices = [ + [2744, 3522], + [2754, 3522], + [2754, 3523], + [2744, 3523], +] + +[[area.polygons]] +vertices = [ + [2683, 3523], + [2686, 3523], +] + +[[area.polygons]] +vertices = [ + [2683, 3524], + [2685, 3524], +] + +[[area.polygons]] +vertices = [ + [2744, 3524], + [2753, 3524], +] + +[[area.polygons]] +vertices = [ + [2683, 3525], + [2684, 3525], +] + +[[area.polygons]] +vertices = [[2739, 3525]] + +[[area.polygons]] +vertices = [[2683, 3526]] + +[[area.polygons]] +vertices = [ + [2642, 3528], + [2645, 3528], +] + +[[area.polygons]] +vertices = [ + [2643, 3529], + [2645, 3529], +] + +[[area.polygons]] +vertices = [[2645, 3530]] + +[[area.polygons]] +vertices = [ + [2676, 3531], + [2679, 3531], +] + +[[area.polygons]] +vertices = [ + [2647, 3532], + [2655, 3532], +] + +[[area.polygons]] +vertices = [ + [2676, 3532], + [2678, 3532], +] + +[[area.polygons]] +vertices = [ + [2648, 3533], + [2655, 3533], +] + +[[area.polygons]] +vertices = [[2676, 3533]] + +[[area.polygons]] +vertices = [ + [2649, 3534], + [2655, 3534], +] + +[[area.polygons]] +vertices = [ + [2651, 3535], + [2655, 3535], +] + +[[area.polygons]] +vertices = [ + [2652, 3536], + [2655, 3536], +] + +[[area.polygons]] +vertices = [ + [2653, 3537], + [2655, 3537], +] + +[[area.polygons]] +vertices = [ + [2654, 3538], + [2655, 3538], +] + +[[area.polygons]] +vertices = [ + [2669, 3539], + [2671, 3539], +] + +[[area.polygons]] +vertices = [ + [2658, 3540], + [2660, 3540], +] + +[[area.polygons]] +vertices = [ + [2669, 3540], + [2670, 3540], +] + +[[area.polygons]] +vertices = [[2660, 3541]] + +[[area.polygons]] +vertices = [[2669, 3541]] diff --git a/.data/raw-cache/map/area/serene.toml b/.data/raw-cache/map/area/serene.toml new file mode 100644 index 000000000..225b7d9d3 --- /dev/null +++ b/.data/raw-cache/map/area/serene.toml @@ -0,0 +1,16 @@ +# Serene music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Serene. +# Bounds x 2816-2879, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Serene" +area_id = "area.serene" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 4800], + [2879, 4800], + [2879, 4863], + [2816, 4863], +] diff --git a/.data/raw-cache/map/area/servants_of_strife.toml b/.data/raw-cache/map/area/servants_of_strife.toml new file mode 100644 index 000000000..edea6e1ab --- /dev/null +++ b/.data/raw-cache/map/area/servants_of_strife.toml @@ -0,0 +1,258 @@ +# Servants of Strife music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Servants of Strife. +# Bounds x 1272-1343, z 3651-10111; 6674 tiles as 40 rectangles. + +[[area]] +name = "Servants of Strife" +area_id = "area.servants_of_strife" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1272, 3651], + [1299, 3651], + [1299, 3682], + [1272, 3682], +] + +[[area.polygons]] +vertices = [ + [1300, 3652], + [1300, 3689], +] + +[[area.polygons]] +vertices = [ + [1301, 3653], + [1301, 3690], +] + +[[area.polygons]] +vertices = [ + [1315, 3653], + [1343, 3653], + [1343, 3670], + [1315, 3670], +] + +[[area.polygons]] +vertices = [ + [1302, 3654], + [1302, 3691], +] + +[[area.polygons]] +vertices = [ + [1314, 3654], + [1314, 3694], +] + +[[area.polygons]] +vertices = [ + [1303, 3655], + [1313, 3655], + [1313, 3692], + [1303, 3692], +] + +[[area.polygons]] +vertices = [ + [1315, 3671], + [1342, 3671], +] + +[[area.polygons]] +vertices = [ + [1315, 3672], + [1341, 3672], +] + +[[area.polygons]] +vertices = [ + [1315, 3673], + [1340, 3673], +] + +[[area.polygons]] +vertices = [ + [1315, 3674], + [1339, 3674], +] + +[[area.polygons]] +vertices = [ + [1315, 3675], + [1338, 3675], +] + +[[area.polygons]] +vertices = [ + [1315, 3676], + [1337, 3676], +] + +[[area.polygons]] +vertices = [ + [1315, 3677], + [1336, 3677], +] + +[[area.polygons]] +vertices = [ + [1315, 3678], + [1335, 3678], +] + +[[area.polygons]] +vertices = [ + [1315, 3679], + [1334, 3679], +] + +[[area.polygons]] +vertices = [ + [1315, 3680], + [1333, 3680], +] + +[[area.polygons]] +vertices = [ + [1315, 3681], + [1332, 3681], +] + +[[area.polygons]] +vertices = [ + [1315, 3682], + [1331, 3682], + [1331, 3683], + [1315, 3683], +] + +[[area.polygons]] +vertices = [ + [1273, 3683], + [1299, 3683], +] + +[[area.polygons]] +vertices = [ + [1274, 3684], + [1299, 3684], +] + +[[area.polygons]] +vertices = [ + [1315, 3684], + [1330, 3684], +] + +[[area.polygons]] +vertices = [ + [1275, 3685], + [1299, 3685], +] + +[[area.polygons]] +vertices = [ + [1315, 3685], + [1329, 3685], +] + +[[area.polygons]] +vertices = [ + [1276, 3686], + [1299, 3686], +] + +[[area.polygons]] +vertices = [ + [1315, 3686], + [1328, 3686], +] + +[[area.polygons]] +vertices = [ + [1277, 3687], + [1299, 3687], +] + +[[area.polygons]] +vertices = [ + [1315, 3687], + [1326, 3687], +] + +[[area.polygons]] +vertices = [ + [1278, 3688], + [1299, 3688], +] + +[[area.polygons]] +vertices = [ + [1315, 3688], + [1325, 3688], +] + +[[area.polygons]] +vertices = [ + [1279, 3689], + [1299, 3689], +] + +[[area.polygons]] +vertices = [ + [1315, 3689], + [1324, 3689], +] + +[[area.polygons]] +vertices = [ + [1315, 3690], + [1323, 3690], +] + +[[area.polygons]] +vertices = [ + [1315, 3691], + [1322, 3691], +] + +[[area.polygons]] +vertices = [ + [1315, 3692], + [1321, 3692], +] + +[[area.polygons]] +vertices = [ + [1304, 3693], + [1313, 3693], +] + +[[area.polygons]] +vertices = [ + [1315, 3693], + [1320, 3693], +] + +[[area.polygons]] +vertices = [ + [1305, 3694], + [1313, 3694], +] + +[[area.polygons]] +vertices = [ + [1315, 3694], + [1319, 3694], +] + +[[area.polygons]] +vertices = [ + [1280, 10048], + [1343, 10048], + [1343, 10111], + [1280, 10111], +] diff --git a/.data/raw-cache/map/area/settlement.toml b/.data/raw-cache/map/area/settlement.toml new file mode 100644 index 000000000..8250b5e08 --- /dev/null +++ b/.data/raw-cache/map/area/settlement.toml @@ -0,0 +1,255 @@ +# Settlement music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Settlement. +# Bounds x 2752-2823, z 3648-3711; 3884 tiles as 39 rectangles. + +[[area]] +name = "Settlement" +area_id = "area.settlement" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2772, 3648], + [2803, 3648], + [2803, 3711], + [2772, 3711], +] + +[[area.polygons]] +vertices = [ + [2771, 3649], + [2771, 3711], +] + +[[area.polygons]] +vertices = [ + [2804, 3649], + [2805, 3649], + [2805, 3711], + [2804, 3711], +] + +[[area.polygons]] +vertices = [ + [2770, 3650], + [2770, 3711], +] + +[[area.polygons]] +vertices = [ + [2806, 3650], + [2807, 3650], + [2807, 3711], + [2806, 3711], +] + +[[area.polygons]] +vertices = [ + [2769, 3651], + [2769, 3711], +] + +[[area.polygons]] +vertices = [ + [2808, 3651], + [2809, 3651], + [2809, 3711], + [2808, 3711], +] + +[[area.polygons]] +vertices = [ + [2768, 3652], + [2768, 3664], +] + +[[area.polygons]] +vertices = [ + [2810, 3652], + [2811, 3652], + [2811, 3711], + [2810, 3711], +] + +[[area.polygons]] +vertices = [ + [2767, 3653], + [2767, 3663], +] + +[[area.polygons]] +vertices = [ + [2812, 3653], + [2813, 3653], + [2813, 3711], + [2812, 3711], +] + +[[area.polygons]] +vertices = [ + [2766, 3654], + [2766, 3663], +] + +[[area.polygons]] +vertices = [ + [2814, 3654], + [2814, 3711], +] + +[[area.polygons]] +vertices = [ + [2765, 3655], + [2765, 3663], +] + +[[area.polygons]] +vertices = [ + [2815, 3655], + [2815, 3711], +] + +[[area.polygons]] +vertices = [ + [2764, 3656], + [2764, 3663], +] + +[[area.polygons]] +vertices = [ + [2816, 3656], + [2816, 3694], +] + +[[area.polygons]] +vertices = [ + [2763, 3657], + [2763, 3663], +] + +[[area.polygons]] +vertices = [ + [2817, 3657], + [2817, 3692], +] + +[[area.polygons]] +vertices = [ + [2762, 3658], + [2762, 3663], +] + +[[area.polygons]] +vertices = [ + [2818, 3658], + [2818, 3690], +] + +[[area.polygons]] +vertices = [ + [2760, 3659], + [2761, 3659], + [2761, 3661], + [2760, 3661], +] + +[[area.polygons]] +vertices = [ + [2819, 3659], + [2819, 3688], +] + +[[area.polygons]] +vertices = [ + [2820, 3660], + [2820, 3678], +] + +[[area.polygons]] +vertices = [ + [2821, 3661], + [2821, 3676], +] + +[[area.polygons]] +vertices = [[2761, 3662]] + +[[area.polygons]] +vertices = [ + [2822, 3662], + [2822, 3674], +] + +[[area.polygons]] +vertices = [ + [2823, 3663], + [2823, 3672], +] + +[[area.polygons]] +vertices = [ + [2768, 3669], + [2768, 3711], +] + +[[area.polygons]] +vertices = [ + [2767, 3670], + [2767, 3711], +] + +[[area.polygons]] +vertices = [ + [2766, 3671], + [2766, 3711], +] + +[[area.polygons]] +vertices = [ + [2759, 3672], + [2765, 3672], + [2765, 3711], + [2759, 3711], +] + +[[area.polygons]] +vertices = [ + [2758, 3673], + [2758, 3711], +] + +[[area.polygons]] +vertices = [ + [2757, 3674], + [2757, 3711], +] + +[[area.polygons]] +vertices = [ + [2756, 3675], + [2756, 3711], +] + +[[area.polygons]] +vertices = [ + [2755, 3676], + [2755, 3711], +] + +[[area.polygons]] +vertices = [ + [2754, 3677], + [2754, 3711], +] + +[[area.polygons]] +vertices = [ + [2753, 3678], + [2753, 3711], +] + +[[area.polygons]] +vertices = [ + [2752, 3679], + [2752, 3711], +] diff --git a/.data/raw-cache/map/area/seventh_realm.toml b/.data/raw-cache/map/area/seventh_realm.toml new file mode 100644 index 000000000..8933d6878 --- /dev/null +++ b/.data/raw-cache/map/area/seventh_realm.toml @@ -0,0 +1,38 @@ +# 7th Realm music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: 7th Realm. +# Bounds x 1856-1967, z 8960-9023; 7166 tiles as 4 rectangles. + +[[area]] +name = "7th Realm" +area_id = "area.seventh_realm" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 8960], + [1967, 8960], + [1967, 9004], + [1856, 9004], +] + +[[area.polygons]] +vertices = [ + [1856, 9005], + [1900, 9005], + [1900, 9023], + [1856, 9023], +] + +[[area.polygons]] +vertices = [ + [1902, 9005], + [1967, 9005], + [1967, 9023], + [1902, 9023], +] + +[[area.polygons]] +vertices = [ + [1901, 9007], + [1901, 9023], +] diff --git a/.data/raw-cache/map/area/shadowland.toml b/.data/raw-cache/map/area/shadowland.toml new file mode 100644 index 000000000..3e5d7be70 --- /dev/null +++ b/.data/raw-cache/map/area/shadowland.toml @@ -0,0 +1,336 @@ +# Shadowland music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Shadowland. +# Bounds x 3456-3527, z 3242-3327; 5088 tiles as 52 rectangles. + +[[area]] +name = "Shadowland" +area_id = "area.shadowland" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3488, 3242], + [3488, 3324], +] + +[[area.polygons]] +vertices = [ + [3477, 3243], + [3478, 3243], + [3478, 3327], + [3477, 3327], +] + +[[area.polygons]] +vertices = [ + [3487, 3243], + [3487, 3324], +] + +[[area.polygons]] +vertices = [ + [3476, 3244], + [3476, 3327], +] + +[[area.polygons]] +vertices = [ + [3479, 3244], + [3480, 3244], + [3480, 3327], + [3479, 3327], +] + +[[area.polygons]] +vertices = [ + [3482, 3244], + [3486, 3244], + [3486, 3325], + [3482, 3325], +] + +[[area.polygons]] +vertices = [ + [3495, 3244], + [3499, 3244], + [3499, 3323], + [3495, 3323], +] + +[[area.polygons]] +vertices = [ + [3512, 3244], + [3518, 3244], + [3518, 3318], + [3512, 3318], +] + +[[area.polygons]] +vertices = [ + [3475, 3245], + [3475, 3327], +] + +[[area.polygons]] +vertices = [ + [3481, 3245], + [3481, 3327], +] + +[[area.polygons]] +vertices = [ + [3489, 3245], + [3494, 3245], + [3494, 3323], + [3489, 3323], +] + +[[area.polygons]] +vertices = [ + [3500, 3245], + [3504, 3245], + [3504, 3323], + [3500, 3323], +] + +[[area.polygons]] +vertices = [ + [3507, 3245], + [3511, 3245], + [3511, 3323], + [3507, 3323], +] + +[[area.polygons]] +vertices = [ + [3519, 3245], + [3520, 3245], + [3520, 3247], + [3519, 3247], +] + +[[area.polygons]] +vertices = [ + [3474, 3246], + [3474, 3327], +] + +[[area.polygons]] +vertices = [ + [3505, 3246], + [3506, 3246], + [3506, 3323], + [3505, 3323], +] + +[[area.polygons]] +vertices = [ + [3473, 3247], + [3473, 3327], +] + +[[area.polygons]] +vertices = [ + [3472, 3248], + [3472, 3327], +] + +[[area.polygons]] +vertices = [ + [3519, 3248], + [3519, 3317], +] + +[[area.polygons]] +vertices = [ + [3520, 3252], + [3520, 3316], +] + +[[area.polygons]] +vertices = [ + [3471, 3253], + [3471, 3327], +] + +[[area.polygons]] +vertices = [ + [3470, 3254], + [3470, 3327], +] + +[[area.polygons]] +vertices = [ + [3469, 3255], + [3469, 3327], +] + +[[area.polygons]] +vertices = [ + [3521, 3255], + [3521, 3315], +] + +[[area.polygons]] +vertices = [ + [3468, 3256], + [3468, 3327], +] + +[[area.polygons]] +vertices = [ + [3467, 3257], + [3467, 3327], +] + +[[area.polygons]] +vertices = [ + [3522, 3257], + [3523, 3257], + [3523, 3313], + [3522, 3313], +] + +[[area.polygons]] +vertices = [ + [3466, 3258], + [3466, 3327], +] + +[[area.polygons]] +vertices = [ + [3465, 3259], + [3465, 3327], +] + +[[area.polygons]] +vertices = [ + [3464, 3260], + [3464, 3327], +] + +[[area.polygons]] +vertices = [ + [3463, 3261], + [3463, 3327], +] + +[[area.polygons]] +vertices = [ + [3462, 3262], + [3462, 3327], +] + +[[area.polygons]] +vertices = [ + [3461, 3263], + [3461, 3327], +] + +[[area.polygons]] +vertices = [ + [3460, 3264], + [3460, 3298], +] + +[[area.polygons]] +vertices = [ + [3459, 3265], + [3459, 3296], +] + +[[area.polygons]] +vertices = [ + [3524, 3267], + [3524, 3293], +] + +[[area.polygons]] +vertices = [ + [3525, 3269], + [3525, 3291], +] + +[[area.polygons]] +vertices = [ + [3526, 3271], + [3526, 3289], +] + +[[area.polygons]] +vertices = [ + [3527, 3273], + [3527, 3287], +] + +[[area.polygons]] +vertices = [ + [3460, 3311], + [3460, 3327], +] + +[[area.polygons]] +vertices = [ + [3459, 3312], + [3459, 3327], +] + +[[area.polygons]] +vertices = [ + [3458, 3313], + [3458, 3327], +] + +[[area.polygons]] +vertices = [ + [3457, 3314], + [3457, 3327], +] + +[[area.polygons]] +vertices = [[3522, 3314]] + +[[area.polygons]] +vertices = [ + [3456, 3315], + [3456, 3327], +] + +[[area.polygons]] +vertices = [ + [3512, 3319], + [3517, 3319], +] + +[[area.polygons]] +vertices = [ + [3512, 3320], + [3516, 3320], +] + +[[area.polygons]] +vertices = [ + [3512, 3321], + [3515, 3321], +] + +[[area.polygons]] +vertices = [ + [3512, 3322], + [3514, 3322], +] + +[[area.polygons]] +vertices = [ + [3512, 3323], + [3513, 3323], +] + +[[area.polygons]] +vertices = [ + [3482, 3326], + [3484, 3326], +] + +[[area.polygons]] +vertices = [[3482, 3327]] diff --git a/.data/raw-cache/map/area/sharp_end_of_the_crystal.toml b/.data/raw-cache/map/area/sharp_end_of_the_crystal.toml new file mode 100644 index 000000000..d7103440f --- /dev/null +++ b/.data/raw-cache/map/area/sharp_end_of_the_crystal.toml @@ -0,0 +1,16 @@ +# Sharp End of the Crystal music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Sharp End of the Crystal. +# Bounds x 3136-3263, z 12352-12479; 16384 tiles as 1 rectangles. + +[[area]] +name = "Sharp End of the Crystal" +area_id = "area.sharp_end_of_the_crystal" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 12352], + [3263, 12352], + [3263, 12479], + [3136, 12479], +] diff --git a/.data/raw-cache/map/area/shining_spirit.toml b/.data/raw-cache/map/area/shining_spirit.toml new file mode 100644 index 000000000..344178dda --- /dev/null +++ b/.data/raw-cache/map/area/shining_spirit.toml @@ -0,0 +1,16 @@ +# Shining Spirit music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Shining Spirit. +# Bounds x 2944-2971, z 4352-4415; 1792 tiles as 1 rectangles. + +[[area]] +name = "Shining Spirit" +area_id = "area.shining_spirit" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2944, 4352], + [2971, 4352], + [2971, 4415], + [2944, 4415], +] diff --git a/.data/raw-cache/map/area/shipwrecked.toml b/.data/raw-cache/map/area/shipwrecked.toml new file mode 100644 index 000000000..76f2d7d99 --- /dev/null +++ b/.data/raw-cache/map/area/shipwrecked.toml @@ -0,0 +1,32 @@ +# Shipwrecked music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Shipwrecked. +# Bounds x 3589-3623, z 3539-3573; 1129 tiles as 3 rectangles. + +[[area]] +name = "Shipwrecked" +area_id = "area.shipwrecked" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3600, 3539], + [3623, 3539], + [3623, 3573], + [3600, 3573], +] + +[[area.polygons]] +vertices = [ + [3598, 3542], + [3599, 3542], + [3599, 3573], + [3598, 3573], +] + +[[area.polygons]] +vertices = [ + [3589, 3549], + [3597, 3549], + [3597, 3573], + [3589, 3573], +] diff --git a/.data/raw-cache/map/area/showdown.toml b/.data/raw-cache/map/area/showdown.toml new file mode 100644 index 000000000..30827eb10 --- /dev/null +++ b/.data/raw-cache/map/area/showdown.toml @@ -0,0 +1,16 @@ +# Showdown music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Showdown. +# Bounds x 2688-2751, z 9152-9215; 4096 tiles as 1 rectangles. + +[[area]] +name = "Showdown" +area_id = "area.showdown" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 9152], + [2751, 9152], + [2751, 9215], + [2688, 9215], +] diff --git a/.data/raw-cache/map/area/sigmunds_showdown.toml b/.data/raw-cache/map/area/sigmunds_showdown.toml new file mode 100644 index 000000000..426216b7d --- /dev/null +++ b/.data/raw-cache/map/area/sigmunds_showdown.toml @@ -0,0 +1,16 @@ +# Sigmund's Showdown music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Sigmund's Showdown. +# Bounds x 1984-2047, z 5056-5119; 4096 tiles as 1 rectangles. + +[[area]] +name = "Sigmund's Showdown" +area_id = "area.sigmunds_showdown" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1984, 5056], + [2047, 5056], + [2047, 5119], + [1984, 5119], +] diff --git a/.data/raw-cache/map/area/sign_here.toml b/.data/raw-cache/map/area/sign_here.toml new file mode 100644 index 000000000..223d86a01 --- /dev/null +++ b/.data/raw-cache/map/area/sign_here.toml @@ -0,0 +1,16 @@ +# Sign Here music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Sign Here. +# Bounds x 1475-1535, z 10048-10111; 3904 tiles as 1 rectangles. + +[[area]] +name = "Sign Here" +area_id = "area.sign_here" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1475, 10048], + [1535, 10048], + [1535, 10111], + [1475, 10111], +] diff --git a/.data/raw-cache/map/area/slice_of_silent_movie.toml b/.data/raw-cache/map/area/slice_of_silent_movie.toml new file mode 100644 index 000000000..96abe6ba8 --- /dev/null +++ b/.data/raw-cache/map/area/slice_of_silent_movie.toml @@ -0,0 +1,16 @@ +# Slice of Silent Movie music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Slice of Silent Movie. +# Bounds x 2528-2559, z 5504-5567; 2048 tiles as 1 rectangles. + +[[area]] +name = "Slice of Silent Movie" +area_id = "area.slice_of_silent_movie" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2528, 5504], + [2559, 5504], + [2559, 5567], + [2528, 5567], +] diff --git a/.data/raw-cache/map/area/slice_of_station.toml b/.data/raw-cache/map/area/slice_of_station.toml new file mode 100644 index 000000000..dc3d6afe6 --- /dev/null +++ b/.data/raw-cache/map/area/slice_of_station.toml @@ -0,0 +1,16 @@ +# Slice of Station music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Slice of Station. +# Bounds x 2432-2495, z 5504-5567; 4096 tiles as 1 rectangles. + +[[area]] +name = "Slice of Station" +area_id = "area.slice_of_station" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2432, 5504], + [2495, 5504], + [2495, 5567], + [2432, 5567], +] diff --git a/.data/raw-cache/map/area/slither_and_thither.toml b/.data/raw-cache/map/area/slither_and_thither.toml new file mode 100644 index 000000000..b96fa37cb --- /dev/null +++ b/.data/raw-cache/map/area/slither_and_thither.toml @@ -0,0 +1,16 @@ +# Slither and Thither music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Slither and Thither. +# Bounds x 2608-2623, z 10272-10303; 512 tiles as 1 rectangles. + +[[area]] +name = "Slither and Thither" +area_id = "area.slither_and_thither" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2608, 10272], + [2623, 10272], + [2623, 10303], + [2608, 10303], +] diff --git a/.data/raw-cache/map/area/slug_a_bug_ball.toml b/.data/raw-cache/map/area/slug_a_bug_ball.toml new file mode 100644 index 000000000..584cac04d --- /dev/null +++ b/.data/raw-cache/map/area/slug_a_bug_ball.toml @@ -0,0 +1,16 @@ +# Slug a Bug Ball music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Slug a Bug Ball. +# Bounds x 2304-2367, z 5056-5119; 4096 tiles as 1 rectangles. + +[[area]] +name = "Slug a Bug Ball" +area_id = "area.slug_a_bug_ball" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2304, 5056], + [2367, 5056], + [2367, 5119], + [2304, 5119], +] diff --git a/.data/raw-cache/map/area/solitude.toml b/.data/raw-cache/map/area/solitude.toml new file mode 100644 index 000000000..18a9fa041 --- /dev/null +++ b/.data/raw-cache/map/area/solitude.toml @@ -0,0 +1,16 @@ +# Solitude music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Solitude. +# Bounds x 3149-3169, z 7701-7721; 441 tiles as 1 rectangles. + +[[area]] +name = "Solitude" +area_id = "area.solitude" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3149, 7701], + [3169, 7701], + [3169, 7721], + [3149, 7721], +] diff --git a/.data/raw-cache/map/area/song_of_the_elves.toml b/.data/raw-cache/map/area/song_of_the_elves.toml new file mode 100644 index 000000000..1f8b87380 --- /dev/null +++ b/.data/raw-cache/map/area/song_of_the_elves.toml @@ -0,0 +1,16 @@ +# Song of the Elves music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Song of the Elves. +# Bounds x 2560-2687, z 6080-6207; 16384 tiles as 1 rectangles. + +[[area]] +name = "Song of the Elves" +area_id = "area.song_of_the_elves" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 6080], + [2687, 6080], + [2687, 6207], + [2560, 6207], +] diff --git a/.data/raw-cache/map/area/soul_fall.toml b/.data/raw-cache/map/area/soul_fall.toml new file mode 100644 index 000000000..6548f13d1 --- /dev/null +++ b/.data/raw-cache/map/area/soul_fall.toml @@ -0,0 +1,199 @@ +# Soul Fall music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Soul Fall. +# Bounds x 1773-1855, z 3840-3907; 4646 tiles as 32 rectangles. + +[[area]] +name = "Soul Fall" +area_id = "area.soul_fall" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1792, 3840], + [1846, 3840], + [1846, 3903], + [1792, 3903], +] + +[[area.polygons]] +vertices = [ + [1847, 3841], + [1847, 3903], +] + +[[area.polygons]] +vertices = [ + [1848, 3842], + [1848, 3903], +] + +[[area.polygons]] +vertices = [ + [1849, 3843], + [1849, 3903], +] + +[[area.polygons]] +vertices = [ + [1850, 3844], + [1850, 3903], +] + +[[area.polygons]] +vertices = [ + [1851, 3845], + [1851, 3903], +] + +[[area.polygons]] +vertices = [ + [1852, 3846], + [1852, 3903], +] + +[[area.polygons]] +vertices = [ + [1853, 3847], + [1853, 3903], +] + +[[area.polygons]] +vertices = [ + [1854, 3848], + [1854, 3903], +] + +[[area.polygons]] +vertices = [ + [1855, 3849], + [1855, 3903], +] + +[[area.polygons]] +vertices = [ + [1791, 3868], + [1791, 3907], +] + +[[area.polygons]] +vertices = [ + [1790, 3869], + [1790, 3907], +] + +[[area.polygons]] +vertices = [ + [1789, 3870], + [1789, 3907], +] + +[[area.polygons]] +vertices = [ + [1788, 3871], + [1788, 3907], +] + +[[area.polygons]] +vertices = [ + [1787, 3872], + [1787, 3907], +] + +[[area.polygons]] +vertices = [ + [1786, 3873], + [1786, 3907], +] + +[[area.polygons]] +vertices = [ + [1785, 3874], + [1785, 3907], +] + +[[area.polygons]] +vertices = [ + [1784, 3875], + [1784, 3907], +] + +[[area.polygons]] +vertices = [ + [1783, 3876], + [1783, 3907], +] + +[[area.polygons]] +vertices = [ + [1782, 3877], + [1782, 3907], +] + +[[area.polygons]] +vertices = [ + [1781, 3878], + [1781, 3907], +] + +[[area.polygons]] +vertices = [ + [1780, 3879], + [1780, 3907], +] + +[[area.polygons]] +vertices = [ + [1779, 3880], + [1779, 3907], +] + +[[area.polygons]] +vertices = [ + [1778, 3881], + [1778, 3907], +] + +[[area.polygons]] +vertices = [ + [1777, 3882], + [1777, 3907], +] + +[[area.polygons]] +vertices = [ + [1776, 3883], + [1776, 3907], +] + +[[area.polygons]] +vertices = [ + [1775, 3884], + [1775, 3907], +] + +[[area.polygons]] +vertices = [ + [1774, 3885], + [1774, 3907], +] + +[[area.polygons]] +vertices = [ + [1773, 3886], + [1773, 3907], +] + +[[area.polygons]] +vertices = [ + [1792, 3904], + [1794, 3904], +] + +[[area.polygons]] +vertices = [ + [1792, 3905], + [1793, 3905], +] + +[[area.polygons]] +vertices = [[1792, 3906]] diff --git a/.data/raw-cache/map/area/soul_wars.toml b/.data/raw-cache/map/area/soul_wars.toml new file mode 100644 index 000000000..6c8a7f9ea --- /dev/null +++ b/.data/raw-cache/map/area/soul_wars.toml @@ -0,0 +1,357 @@ +# Soul Wars music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Soul Wars. +# Bounds x 2114-2303, z 2880-2943; 10997 tiles as 53 rectangles. + +[[area]] +name = "Soul Wars" +area_id = "area.soul_wars" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2118, 2880], + [2137, 2880], + [2137, 2939], + [2118, 2939], +] + +[[area.polygons]] +vertices = [ + [2187, 2880], + [2259, 2880], + [2259, 2936], + [2187, 2936], +] + +[[area.polygons]] +vertices = [ + [2117, 2881], + [2117, 2939], +] + +[[area.polygons]] +vertices = [ + [2138, 2881], + [2138, 2939], +] + +[[area.polygons]] +vertices = [ + [2185, 2881], + [2186, 2881], + [2186, 2943], + [2185, 2943], +] + +[[area.polygons]] +vertices = [ + [2116, 2882], + [2116, 2939], +] + +[[area.polygons]] +vertices = [ + [2139, 2882], + [2139, 2939], +] + +[[area.polygons]] +vertices = [ + [2183, 2882], + [2184, 2882], + [2184, 2943], + [2183, 2943], +] + +[[area.polygons]] +vertices = [ + [2283, 2882], + [2297, 2882], + [2297, 2943], + [2283, 2943], +] + +[[area.polygons]] +vertices = [ + [2115, 2883], + [2115, 2939], +] + +[[area.polygons]] +vertices = [ + [2140, 2883], + [2140, 2939], +] + +[[area.polygons]] +vertices = [ + [2181, 2883], + [2182, 2883], + [2182, 2943], + [2181, 2943], +] + +[[area.polygons]] +vertices = [ + [2281, 2883], + [2282, 2883], + [2282, 2943], + [2281, 2943], +] + +[[area.polygons]] +vertices = [ + [2114, 2884], + [2114, 2939], +] + +[[area.polygons]] +vertices = [ + [2141, 2884], + [2141, 2939], +] + +[[area.polygons]] +vertices = [ + [2179, 2884], + [2180, 2884], + [2180, 2943], + [2179, 2943], +] + +[[area.polygons]] +vertices = [ + [2278, 2884], + [2280, 2884], + [2280, 2943], + [2278, 2943], +] + +[[area.polygons]] +vertices = [ + [2142, 2885], + [2142, 2939], +] + +[[area.polygons]] +vertices = [ + [2177, 2885], + [2178, 2885], + [2178, 2943], + [2177, 2943], +] + +[[area.polygons]] +vertices = [ + [2276, 2885], + [2277, 2885], + [2277, 2943], + [2276, 2943], +] + +[[area.polygons]] +vertices = [ + [2143, 2886], + [2143, 2939], +] + +[[area.polygons]] +vertices = [ + [2175, 2886], + [2176, 2886], + [2176, 2943], + [2175, 2943], +] + +[[area.polygons]] +vertices = [ + [2274, 2886], + [2275, 2886], + [2275, 2943], + [2274, 2943], +] + +[[area.polygons]] +vertices = [ + [2144, 2887], + [2144, 2939], +] + +[[area.polygons]] +vertices = [ + [2173, 2887], + [2174, 2887], + [2174, 2943], + [2173, 2943], +] + +[[area.polygons]] +vertices = [ + [2272, 2887], + [2273, 2887], + [2273, 2941], + [2272, 2941], +] + +[[area.polygons]] +vertices = [ + [2145, 2888], + [2145, 2939], +] + +[[area.polygons]] +vertices = [ + [2171, 2888], + [2172, 2888], + [2172, 2943], + [2171, 2943], +] + +[[area.polygons]] +vertices = [ + [2269, 2888], + [2271, 2888], + [2271, 2938], + [2269, 2938], +] + +[[area.polygons]] +vertices = [ + [2146, 2889], + [2170, 2889], + [2170, 2939], + [2146, 2939], +] + +[[area.polygons]] +vertices = [ + [2267, 2889], + [2268, 2889], + [2268, 2936], + [2267, 2936], +] + +[[area.polygons]] +vertices = [ + [2260, 2890], + [2266, 2890], + [2266, 2936], + [2260, 2936], +] + +[[area.polygons]] +vertices = [ + [2298, 2911], + [2298, 2943], +] + +[[area.polygons]] +vertices = [ + [2299, 2913], + [2299, 2943], +] + +[[area.polygons]] +vertices = [ + [2300, 2915], + [2300, 2943], +] + +[[area.polygons]] +vertices = [ + [2301, 2917], + [2301, 2943], +] + +[[area.polygons]] +vertices = [ + [2302, 2919], + [2302, 2943], +] + +[[area.polygons]] +vertices = [ + [2303, 2921], + [2303, 2943], +] + +[[area.polygons]] +vertices = [ + [2187, 2937], + [2244, 2937], +] + +[[area.polygons]] +vertices = [[2268, 2937]] + +[[area.polygons]] +vertices = [ + [2187, 2938], + [2242, 2938], +] + +[[area.polygons]] +vertices = [ + [2187, 2939], + [2240, 2939], +] + +[[area.polygons]] +vertices = [ + [2270, 2939], + [2271, 2939], +] + +[[area.polygons]] +vertices = [ + [2157, 2940], + [2170, 2940], +] + +[[area.polygons]] +vertices = [ + [2187, 2940], + [2238, 2940], +] + +[[area.polygons]] +vertices = [[2271, 2940]] + +[[area.polygons]] +vertices = [ + [2159, 2941], + [2170, 2941], +] + +[[area.polygons]] +vertices = [ + [2187, 2941], + [2236, 2941], +] + +[[area.polygons]] +vertices = [ + [2161, 2942], + [2170, 2942], +] + +[[area.polygons]] +vertices = [ + [2187, 2942], + [2234, 2942], +] + +[[area.polygons]] +vertices = [[2273, 2942]] + +[[area.polygons]] +vertices = [ + [2163, 2943], + [2170, 2943], +] + +[[area.polygons]] +vertices = [ + [2187, 2943], + [2232, 2943], +] diff --git a/.data/raw-cache/map/area/south_ardougne.toml b/.data/raw-cache/map/area/south_ardougne.toml new file mode 100644 index 000000000..ecb5a2819 --- /dev/null +++ b/.data/raw-cache/map/area/south_ardougne.toml @@ -0,0 +1,468 @@ +# South of the city wall: the Clock Tower, Ardougne Monastery and the Necromancer Tower. +# Clock Tower dungeon. +# +# Regenerated as rectangles from the OSRS wiki music map: 6542 tiles moved to +# neighbouring music areas. + +[[area]] +name = "South Ardougne" +area_id = "area.south_ardougne" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2639, 3195], + [2680, 3195], +] + +[[area.polygons]] +vertices = [ + [2640, 3196], + [2680, 3196], +] + +[[area.polygons]] +vertices = [ + [2641, 3197], + [2680, 3197], +] + +[[area.polygons]] +vertices = [ + [2642, 3198], + [2680, 3198], +] + +[[area.polygons]] +vertices = [ + [2643, 3199], + [2680, 3199], +] + +[[area.polygons]] +vertices = [ + [2644, 3200], + [2680, 3200], +] + +[[area.polygons]] +vertices = [ + [2645, 3201], + [2680, 3201], + [2680, 3202], + [2645, 3202], +] + +[[area.polygons]] +vertices = [ + [2646, 3203], + [2680, 3203], + [2680, 3204], + [2646, 3204], +] + +[[area.polygons]] +vertices = [ + [2560, 3204], + [2577, 3204], + [2577, 3214], + [2560, 3214], +] + +[[area.polygons]] +vertices = [ + [2594, 3204], + [2598, 3204], + [2598, 3262], + [2594, 3262], +] + +[[area.polygons]] +vertices = [ + [2613, 3204], + [2617, 3204], + [2617, 3262], + [2613, 3262], +] + +[[area.polygons]] +vertices = [ + [2593, 3205], + [2593, 3262], +] + +[[area.polygons]] +vertices = [ + [2599, 3205], + [2599, 3262], +] + +[[area.polygons]] +vertices = [ + [2612, 3205], + [2612, 3262], +] + +[[area.polygons]] +vertices = [ + [2618, 3205], + [2618, 3262], +] + +[[area.polygons]] +vertices = [ + [2648, 3205], + [2680, 3205], +] + +[[area.polygons]] +vertices = [ + [2578, 3206], + [2588, 3206], + [2588, 3262], + [2578, 3262], +] + +[[area.polygons]] +vertices = [ + [2592, 3206], + [2592, 3262], +] + +[[area.polygons]] +vertices = [ + [2600, 3206], + [2600, 3262], +] + +[[area.polygons]] +vertices = [ + [2611, 3206], + [2611, 3262], +] + +[[area.polygons]] +vertices = [ + [2619, 3206], + [2619, 3262], +] + +[[area.polygons]] +vertices = [ + [2649, 3206], + [2680, 3206], +] + +[[area.polygons]] +vertices = [ + [2589, 3207], + [2589, 3262], +] + +[[area.polygons]] +vertices = [ + [2591, 3207], + [2591, 3262], +] + +[[area.polygons]] +vertices = [ + [2601, 3207], + [2610, 3207], + [2610, 3262], + [2601, 3262], +] + +[[area.polygons]] +vertices = [ + [2620, 3207], + [2634, 3207], + [2634, 3262], + [2620, 3262], +] + +[[area.polygons]] +vertices = [ + [2650, 3207], + [2680, 3207], +] + +[[area.polygons]] +vertices = [ + [2590, 3208], + [2590, 3262], +] + +[[area.polygons]] +vertices = [ + [2635, 3208], + [2635, 3214], +] + +[[area.polygons]] +vertices = [ + [2657, 3208], + [2680, 3208], +] + +[[area.polygons]] +vertices = [ + [2636, 3209], + [2636, 3213], +] + +[[area.polygons]] +vertices = [ + [2658, 3209], + [2680, 3209], +] + +[[area.polygons]] +vertices = [ + [2637, 3210], + [2637, 3212], +] + +[[area.polygons]] +vertices = [ + [2659, 3210], + [2680, 3210], +] + +[[area.polygons]] +vertices = [ + [2660, 3211], + [2680, 3211], + [2680, 3223], + [2660, 3223], +] + +[[area.polygons]] +vertices = [ + [2561, 3215], + [2577, 3215], +] + +[[area.polygons]] +vertices = [ + [2562, 3216], + [2577, 3216], +] + +[[area.polygons]] +vertices = [ + [2563, 3217], + [2577, 3217], +] + +[[area.polygons]] +vertices = [ + [2564, 3218], + [2577, 3218], +] + +[[area.polygons]] +vertices = [ + [2565, 3219], + [2577, 3219], + [2577, 3220], + [2565, 3220], +] + +[[area.polygons]] +vertices = [ + [2566, 3221], + [2577, 3221], + [2577, 3262], + [2566, 3262], +] + +[[area.polygons]] +vertices = [ + [2661, 3224], + [2680, 3224], + [2680, 3262], + [2661, 3262], +] + +[[area.polygons]] +vertices = [ + [2660, 3225], + [2660, 3262], +] + +[[area.polygons]] +vertices = [ + [2565, 3226], + [2565, 3262], +] + +[[area.polygons]] +vertices = [ + [2658, 3226], + [2659, 3226], + [2659, 3262], + [2658, 3262], +] + +[[area.polygons]] +vertices = [ + [2657, 3227], + [2657, 3262], +] + +[[area.polygons]] +vertices = [ + [2656, 3228], + [2656, 3262], +] + +[[area.polygons]] +vertices = [ + [2635, 3230], + [2635, 3262], +] + +[[area.polygons]] +vertices = [ + [2655, 3230], + [2655, 3262], +] + +[[area.polygons]] +vertices = [ + [2636, 3231], + [2636, 3262], +] + +[[area.polygons]] +vertices = [ + [2653, 3231], + [2654, 3231], + [2654, 3262], + [2653, 3262], +] + +[[area.polygons]] +vertices = [ + [2564, 3232], + [2564, 3262], +] + +[[area.polygons]] +vertices = [ + [2637, 3232], + [2638, 3232], + [2638, 3262], + [2637, 3262], +] + +[[area.polygons]] +vertices = [ + [2639, 3233], + [2639, 3262], +] + +[[area.polygons]] +vertices = [ + [2648, 3233], + [2652, 3233], + [2652, 3262], + [2648, 3262], +] + +[[area.polygons]] +vertices = [ + [2640, 3234], + [2647, 3234], + [2647, 3262], + [2640, 3262], +] + +[[area.polygons]] +vertices = [ + [2562, 3244], + [2563, 3244], + [2563, 3262], + [2562, 3262], +] + +[[area.polygons]] +vertices = [ + [2560, 3245], + [2561, 3245], + [2561, 3262], + [2560, 3262], +] + +[[area.polygons]] +vertices = [ + [2558, 3246], + [2559, 3246], + [2559, 3262], + [2558, 3262], +] + +[[area.polygons]] +vertices = [ + [2556, 3247], + [2557, 3247], + [2557, 3262], + [2556, 3262], +] + +[[area.polygons]] +vertices = [ + [2553, 3248], + [2555, 3248], + [2555, 3262], + [2553, 3262], +] + +[[area.polygons]] +vertices = [ + [2552, 3249], + [2552, 3262], +] + +[[area.polygons]] +vertices = [ + [2551, 3250], + [2551, 3262], +] + +[[area.polygons]] +vertices = [ + [2550, 3251], + [2550, 3262], +] + +[[area.polygons]] +vertices = [ + [2549, 3252], + [2549, 3262], +] + +[[area.polygons]] +vertices = [ + [2548, 3253], + [2548, 3262], +] + +[[area.polygons]] +vertices = [ + [2547, 3254], + [2547, 3262], +] + +[[area.polygons]] +vertices = [ + [2546, 3255], + [2546, 3262], +] + +[[area.polygons]] +vertices = [ + [2545, 3256], + [2545, 3262], +] diff --git a/.data/raw-cache/map/area/spirits_of_the_elid.toml b/.data/raw-cache/map/area/spirits_of_the_elid.toml new file mode 100644 index 000000000..acf6220b5 --- /dev/null +++ b/.data/raw-cache/map/area/spirits_of_the_elid.toml @@ -0,0 +1,16 @@ +# Spirits of the Elid music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Spirits of the Elid. +# Bounds x 3328-3391, z 9536-9599; 4096 tiles as 1 rectangles. + +[[area]] +name = "Spirits of the Elid" +area_id = "area.spirits_of_the_elid" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3328, 9536], + [3391, 9536], + [3391, 9599], + [3328, 9599], +] diff --git a/.data/raw-cache/map/area/spooky.toml b/.data/raw-cache/map/area/spooky.toml new file mode 100644 index 000000000..35d888b38 --- /dev/null +++ b/.data/raw-cache/map/area/spooky.toml @@ -0,0 +1,16 @@ +# Spooky music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Spooky. +# Bounds x 1920-1951, z 4992-5023; 1024 tiles as 1 rectangles. + +[[area]] +name = "Spooky" +area_id = "area.spooky" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1920, 4992], + [1951, 4992], + [1951, 5023], + [1920, 5023], +] diff --git a/.data/raw-cache/map/area/spooky_2.toml b/.data/raw-cache/map/area/spooky_2.toml new file mode 100644 index 000000000..5577bbac5 --- /dev/null +++ b/.data/raw-cache/map/area/spooky_2.toml @@ -0,0 +1,32 @@ +# Spooky 2 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Spooky 2. +# Bounds x 1920-3519, z 9280-9663; 16384 tiles as 3 rectangles. + +[[area]] +name = "Spooky 2" +area_id = "area.spooky_2" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2112, 9280], + [2175, 9280], + [2175, 9343], + [2112, 9343], +] + +[[area.polygons]] +vertices = [ + [1920, 9408], + [1983, 9408], + [1983, 9471], + [1920, 9471], +] + +[[area.polygons]] +vertices = [ + [3392, 9600], + [3519, 9600], + [3519, 9663], + [3392, 9663], +] diff --git a/.data/raw-cache/map/area/spooky_jungle.toml b/.data/raw-cache/map/area/spooky_jungle.toml new file mode 100644 index 000000000..57370807e --- /dev/null +++ b/.data/raw-cache/map/area/spooky_jungle.toml @@ -0,0 +1,1127 @@ +# Spooky Jungle music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Spooky Jungle. +# Bounds x 2496-2974, z 2881-9535; 22843 tiles as 173 rectangles. + +[[area]] +name = "Spooky Jungle" +area_id = "area.spooky_jungle" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2942, 2881], + [2947, 2881], + [2947, 2945], + [2942, 2945], +] + +[[area.polygons]] +vertices = [ + [2814, 2882], + [2815, 2882], + [2815, 2940], + [2814, 2940], +] + +[[area.polygons]] +vertices = [ + [2899, 2882], + [2901, 2882], + [2901, 2940], + [2899, 2940], +] + +[[area.polygons]] +vertices = [ + [2936, 2882], + [2941, 2882], + [2941, 2937], + [2936, 2937], +] + +[[area.polygons]] +vertices = [ + [2948, 2882], + [2948, 2946], +] + +[[area.polygons]] +vertices = [ + [2816, 2883], + [2822, 2883], + [2822, 2934], + [2816, 2934], +] + +[[area.polygons]] +vertices = [ + [2897, 2883], + [2898, 2883], + [2898, 2940], + [2897, 2940], +] + +[[area.polygons]] +vertices = [ + [2902, 2883], + [2910, 2883], + [2910, 2940], + [2902, 2940], +] + +[[area.polygons]] +vertices = [ + [2918, 2883], + [2922, 2883], + [2922, 2891], + [2918, 2891], +] + +[[area.polygons]] +vertices = [ + [2935, 2883], + [2935, 2937], +] + +[[area.polygons]] +vertices = [ + [2949, 2883], + [2949, 2946], +] + +[[area.polygons]] +vertices = [ + [2813, 2884], + [2813, 2940], +] + +[[area.polygons]] +vertices = [ + [2823, 2884], + [2828, 2884], + [2828, 2934], + [2823, 2934], +] + +[[area.polygons]] +vertices = [ + [2896, 2884], + [2896, 2940], +] + +[[area.polygons]] +vertices = [ + [2917, 2884], + [2917, 2940], +] + +[[area.polygons]] +vertices = [ + [2923, 2884], + [2923, 2890], +] + +[[area.polygons]] +vertices = [ + [2934, 2884], + [2934, 2937], +] + +[[area.polygons]] +vertices = [ + [2950, 2884], + [2951, 2884], + [2951, 2945], + [2950, 2945], +] + +[[area.polygons]] +vertices = [ + [2811, 2885], + [2812, 2885], + [2812, 2940], + [2811, 2940], +] + +[[area.polygons]] +vertices = [ + [2829, 2885], + [2829, 2937], +] + +[[area.polygons]] +vertices = [ + [2894, 2885], + [2895, 2885], + [2895, 2940], + [2894, 2940], +] + +[[area.polygons]] +vertices = [ + [2916, 2885], + [2916, 2940], +] + +[[area.polygons]] +vertices = [ + [2924, 2885], + [2925, 2885], + [2925, 2887], + [2924, 2887], +] + +[[area.polygons]] +vertices = [ + [2933, 2885], + [2933, 2937], +] + +[[area.polygons]] +vertices = [ + [2952, 2885], + [2952, 2945], +] + +[[area.polygons]] +vertices = [ + [2810, 2886], + [2810, 2940], +] + +[[area.polygons]] +vertices = [ + [2892, 2886], + [2893, 2886], + [2893, 2940], + [2892, 2940], +] + +[[area.polygons]] +vertices = [ + [2915, 2886], + [2915, 2940], +] + +[[area.polygons]] +vertices = [ + [2931, 2886], + [2932, 2886], + [2932, 2936], + [2931, 2936], +] + +[[area.polygons]] +vertices = [ + [2769, 2887], + [2769, 2890], +] + +[[area.polygons]] +vertices = [ + [2773, 2887], + [2778, 2887], + [2778, 2895], + [2773, 2895], +] + +[[area.polygons]] +vertices = [ + [2830, 2887], + [2830, 2937], +] + +[[area.polygons]] +vertices = [ + [2841, 2887], + [2841, 2937], +] + +[[area.polygons]] +vertices = [ + [2856, 2887], + [2860, 2887], + [2860, 2934], + [2856, 2934], +] + +[[area.polygons]] +vertices = [ + [2890, 2887], + [2891, 2887], + [2891, 2940], + [2890, 2940], +] + +[[area.polygons]] +vertices = [ + [2914, 2887], + [2914, 2940], +] + +[[area.polygons]] +vertices = [ + [2930, 2887], + [2930, 2940], +] + +[[area.polygons]] +vertices = [ + [2953, 2887], + [2959, 2887], + [2959, 2931], + [2953, 2931], +] + +[[area.polygons]] +vertices = [ + [2768, 2888], + [2768, 2890], +] + +[[area.polygons]] +vertices = [ + [2770, 2888], + [2772, 2888], + [2772, 2890], + [2770, 2890], +] + +[[area.polygons]] +vertices = [ + [2779, 2888], + [2779, 2940], +] + +[[area.polygons]] +vertices = [ + [2809, 2888], + [2809, 2940], +] + +[[area.polygons]] +vertices = [ + [2838, 2888], + [2840, 2888], + [2840, 2937], + [2838, 2937], +] + +[[area.polygons]] +vertices = [ + [2842, 2888], + [2842, 2937], +] + +[[area.polygons]] +vertices = [ + [2861, 2888], + [2861, 2934], +] + +[[area.polygons]] +vertices = [ + [2889, 2888], + [2889, 2940], +] + +[[area.polygons]] +vertices = [ + [2911, 2888], + [2913, 2888], + [2913, 2940], + [2911, 2940], +] + +[[area.polygons]] +vertices = [ + [2924, 2888], + [2924, 2889], +] + +[[area.polygons]] +vertices = [ + [2960, 2888], + [2960, 2930], +] + +[[area.polygons]] +vertices = [ + [2780, 2889], + [2782, 2889], + [2782, 2891], + [2780, 2891], +] + +[[area.polygons]] +vertices = [ + [2808, 2889], + [2808, 2940], +] + +[[area.polygons]] +vertices = [ + [2831, 2889], + [2837, 2889], + [2837, 2937], + [2831, 2937], +] + +[[area.polygons]] +vertices = [ + [2843, 2889], + [2845, 2889], + [2845, 2937], + [2843, 2937], +] + +[[area.polygons]] +vertices = [ + [2855, 2889], + [2855, 2937], +] + +[[area.polygons]] +vertices = [ + [2862, 2889], + [2863, 2889], + [2863, 2891], + [2862, 2891], +] + +[[area.polygons]] +vertices = [ + [2885, 2889], + [2888, 2889], + [2888, 2940], + [2885, 2940], +] + +[[area.polygons]] +vertices = [ + [2929, 2889], + [2929, 2940], +] + +[[area.polygons]] +vertices = [ + [2805, 2890], + [2807, 2890], + [2807, 2940], + [2805, 2940], +] + +[[area.polygons]] +vertices = [ + [2854, 2890], + [2854, 2937], +] + +[[area.polygons]] +vertices = [ + [2882, 2890], + [2884, 2890], + [2884, 2940], + [2882, 2940], +] + +[[area.polygons]] +vertices = [ + [2928, 2890], + [2928, 2940], +] + +[[area.polygons]] +vertices = [ + [2961, 2890], + [2962, 2890], + [2962, 2929], + [2961, 2929], +] + +[[area.polygons]] +vertices = [ + [2771, 2891], + [2772, 2891], + [2772, 2892], + [2771, 2892], +] + +[[area.polygons]] +vertices = [ + [2792, 2891], + [2793, 2891], + [2793, 2937], + [2792, 2937], +] + +[[area.polygons]] +vertices = [ + [2804, 2891], + [2804, 2940], +] + +[[area.polygons]] +vertices = [ + [2846, 2891], + [2846, 2937], +] + +[[area.polygons]] +vertices = [ + [2852, 2891], + [2853, 2891], + [2853, 2937], + [2852, 2937], +] + +[[area.polygons]] +vertices = [ + [2880, 2891], + [2881, 2891], + [2881, 2940], + [2880, 2940], +] + +[[area.polygons]] +vertices = [ + [2927, 2891], + [2927, 2940], +] + +[[area.polygons]] +vertices = [ + [2963, 2891], + [2963, 2926], +] + +[[area.polygons]] +vertices = [ + [2780, 2892], + [2781, 2892], +] + +[[area.polygons]] +vertices = [ + [2794, 2892], + [2794, 2938], +] + +[[area.polygons]] +vertices = [ + [2803, 2892], + [2803, 2940], +] + +[[area.polygons]] +vertices = [ + [2851, 2892], + [2851, 2937], +] + +[[area.polygons]] +vertices = [ + [2862, 2892], + [2862, 2934], +] + +[[area.polygons]] +vertices = [ + [2879, 2892], + [2879, 2940], +] + +[[area.polygons]] +vertices = [ + [2918, 2892], + [2921, 2892], +] + +[[area.polygons]] +vertices = [ + [2926, 2892], + [2926, 2940], +] + +[[area.polygons]] +vertices = [ + [2964, 2892], + [2964, 2926], +] + +[[area.polygons]] +vertices = [ + [2772, 2893], + [2772, 2894], +] + +[[area.polygons]] +vertices = [[2780, 2893]] + +[[area.polygons]] +vertices = [ + [2791, 2893], + [2791, 2940], +] + +[[area.polygons]] +vertices = [ + [2795, 2893], + [2796, 2893], + [2796, 2938], + [2795, 2938], +] + +[[area.polygons]] +vertices = [ + [2800, 2893], + [2802, 2893], + [2802, 2938], + [2800, 2938], +] + +[[area.polygons]] +vertices = [ + [2847, 2893], + [2850, 2893], + [2850, 2937], + [2847, 2937], +] + +[[area.polygons]] +vertices = [ + [2878, 2893], + [2878, 2940], +] + +[[area.polygons]] +vertices = [ + [2918, 2893], + [2920, 2893], + [2920, 2940], + [2918, 2940], +] + +[[area.polygons]] +vertices = [ + [2925, 2893], + [2925, 2940], +] + +[[area.polygons]] +vertices = [ + [2965, 2893], + [2965, 2926], +] + +[[area.polygons]] +vertices = [ + [2790, 2894], + [2790, 2940], +] + +[[area.polygons]] +vertices = [ + [2797, 2894], + [2799, 2894], + [2799, 2938], + [2797, 2938], +] + +[[area.polygons]] +vertices = [ + [2876, 2894], + [2877, 2894], + [2877, 2939], + [2876, 2939], +] + +[[area.polygons]] +vertices = [ + [2924, 2894], + [2924, 2940], +] + +[[area.polygons]] +vertices = [ + [2966, 2894], + [2968, 2894], + [2968, 2902], + [2966, 2902], +] + +[[area.polygons]] +vertices = [ + [2780, 2895], + [2780, 2940], +] + +[[area.polygons]] +vertices = [ + [2784, 2895], + [2785, 2895], + [2785, 2940], + [2784, 2940], +] + +[[area.polygons]] +vertices = [ + [2789, 2895], + [2789, 2940], +] + +[[area.polygons]] +vertices = [ + [2863, 2895], + [2863, 2934], +] + +[[area.polygons]] +vertices = [ + [2874, 2895], + [2875, 2895], + [2875, 2937], + [2874, 2937], +] + +[[area.polygons]] +vertices = [ + [2923, 2895], + [2923, 2940], +] + +[[area.polygons]] +vertices = [ + [2969, 2895], + [2969, 2901], +] + +[[area.polygons]] +vertices = [ + [2774, 2896], + [2778, 2896], + [2778, 2940], + [2774, 2940], +] + +[[area.polygons]] +vertices = [ + [2781, 2896], + [2783, 2896], + [2783, 2940], + [2781, 2940], +] + +[[area.polygons]] +vertices = [ + [2786, 2896], + [2788, 2896], + [2788, 2940], + [2786, 2940], +] + +[[area.polygons]] +vertices = [ + [2872, 2896], + [2873, 2896], + [2873, 2935], + [2872, 2935], +] + +[[area.polygons]] +vertices = [ + [2921, 2896], + [2922, 2896], + [2922, 2940], + [2921, 2940], +] + +[[area.polygons]] +vertices = [ + [2870, 2897], + [2871, 2897], + [2871, 2934], + [2870, 2934], +] + +[[area.polygons]] +vertices = [ + [2970, 2897], + [2970, 2900], +] + +[[area.polygons]] +vertices = [ + [2773, 2898], + [2773, 2940], +] + +[[area.polygons]] +vertices = [ + [2864, 2898], + [2864, 2934], +] + +[[area.polygons]] +vertices = [ + [2869, 2898], + [2869, 2934], +] + +[[area.polygons]] +vertices = [ + [2971, 2898], + [2971, 2899], +] + +[[area.polygons]] +vertices = [ + [2767, 2899], + [2767, 2940], +] + +[[area.polygons]] +vertices = [ + [2865, 2899], + [2868, 2899], + [2868, 2934], + [2865, 2934], +] + +[[area.polygons]] +vertices = [ + [2766, 2900], + [2766, 2940], +] + +[[area.polygons]] +vertices = [ + [2768, 2900], + [2772, 2900], + [2772, 2940], + [2768, 2940], +] + +[[area.polygons]] +vertices = [ + [2765, 2901], + [2765, 2907], +] + +[[area.polygons]] +vertices = [ + [2764, 2903], + [2764, 2905], +] + +[[area.polygons]] +vertices = [ + [2966, 2903], + [2967, 2903], + [2967, 2904], + [2966, 2904], +] + +[[area.polygons]] +vertices = [ + [2966, 2905], + [2966, 2909], +] + +[[area.polygons]] +vertices = [ + [2967, 2906], + [2967, 2907], +] + +[[area.polygons]] +vertices = [ + [2765, 2910], + [2765, 2940], +] + +[[area.polygons]] +vertices = [ + [2761, 2911], + [2764, 2911], + [2764, 2926], + [2761, 2926], +] + +[[area.polygons]] +vertices = [ + [2759, 2912], + [2760, 2912], + [2760, 2920], + [2759, 2920], +] + +[[area.polygons]] +vertices = [ + [2966, 2912], + [2966, 2927], +] + +[[area.polygons]] +vertices = [ + [2967, 2913], + [2967, 2927], +] + +[[area.polygons]] +vertices = [ + [2758, 2914], + [2758, 2919], +] + +[[area.polygons]] +vertices = [ + [2757, 2915], + [2757, 2918], +] + +[[area.polygons]] +vertices = [ + [2968, 2915], + [2968, 2928], +] + +[[area.polygons]] +vertices = [[2756, 2916]] + +[[area.polygons]] +vertices = [ + [2969, 2917], + [2969, 2930], +] + +[[area.polygons]] +vertices = [ + [2970, 2918], + [2971, 2918], + [2971, 2929], + [2970, 2929], +] + +[[area.polygons]] +vertices = [ + [2972, 2919], + [2972, 2928], +] + +[[area.polygons]] +vertices = [ + [2973, 2920], + [2973, 2928], +] + +[[area.polygons]] +vertices = [[2760, 2921]] + +[[area.polygons]] +vertices = [ + [2974, 2921], + [2974, 2922], +] + +[[area.polygons]] +vertices = [[2760, 2924]] + +[[area.polygons]] +vertices = [ + [2974, 2925], + [2974, 2927], +] + +[[area.polygons]] +vertices = [ + [2762, 2927], + [2764, 2927], +] + +[[area.polygons]] +vertices = [ + [2763, 2928], + [2764, 2928], + [2764, 2935], + [2763, 2935], +] + +[[area.polygons]] +vertices = [ + [2762, 2929], + [2762, 2934], +] + +[[area.polygons]] +vertices = [ + [2761, 2930], + [2761, 2934], +] + +[[area.polygons]] +vertices = [[2961, 2930]] + +[[area.polygons]] +vertices = [[2970, 2930]] + +[[area.polygons]] +vertices = [ + [2760, 2931], + [2760, 2934], +] + +[[area.polygons]] +vertices = [ + [2759, 2932], + [2759, 2934], +] + +[[area.polygons]] +vertices = [ + [2953, 2932], + [2957, 2932], +] + +[[area.polygons]] +vertices = [ + [2758, 2933], + [2758, 2934], +] + +[[area.polygons]] +vertices = [ + [2953, 2933], + [2956, 2933], +] + +[[area.polygons]] +vertices = [ + [2953, 2934], + [2955, 2934], + [2955, 2945], + [2953, 2945], +] + +[[area.polygons]] +vertices = [ + [2816, 2935], + [2821, 2935], +] + +[[area.polygons]] +vertices = [ + [2824, 2935], + [2828, 2935], + [2828, 2937], + [2824, 2937], +] + +[[area.polygons]] +vertices = [ + [2856, 2935], + [2858, 2935], +] + +[[area.polygons]] +vertices = [[2764, 2936]] + +[[area.polygons]] +vertices = [[2819, 2936]] + +[[area.polygons]] +vertices = [ + [2856, 2936], + [2857, 2936], +] + +[[area.polygons]] +vertices = [[2873, 2936]] + +[[area.polygons]] +vertices = [[2856, 2937]] + +[[area.polygons]] +vertices = [[2875, 2938]] + +[[area.polygons]] +vertices = [ + [2941, 2938], + [2941, 2948], +] + +[[area.polygons]] +vertices = [ + [2956, 2938], + [2956, 2944], +] + +[[area.polygons]] +vertices = [ + [2801, 2939], + [2802, 2939], + [2802, 2940], + [2801, 2940], +] + +[[area.polygons]] +vertices = [ + [2957, 2939], + [2958, 2939], + [2958, 2944], + [2957, 2944], +] + +[[area.polygons]] +vertices = [[2877, 2940]] + +[[area.polygons]] +vertices = [ + [2959, 2940], + [2959, 2945], +] + +[[area.polygons]] +vertices = [ + [2960, 2943], + [2960, 2944], +] + +[[area.polygons]] +vertices = [[2958, 2945]] + +[[area.polygons]] +vertices = [ + [2942, 2946], + [2945, 2946], +] + +[[area.polygons]] +vertices = [[2947, 2946]] + +[[area.polygons]] +vertices = [[2942, 2947]] + +[[area.polygons]] +vertices = [ + [2496, 4544], + [2559, 4544], + [2559, 4607], + [2496, 4607], +] + +[[area.polygons]] +vertices = [ + [2752, 9344], + [2815, 9344], + [2815, 9407], + [2752, 9407], +] + +[[area.polygons]] +vertices = [ + [2880, 9472], + [2943, 9472], + [2943, 9535], + [2880, 9535], +] diff --git a/.data/raw-cache/map/area/stagnant.toml b/.data/raw-cache/map/area/stagnant.toml new file mode 100644 index 000000000..80901c311 --- /dev/null +++ b/.data/raw-cache/map/area/stagnant.toml @@ -0,0 +1,32 @@ +# Stagnant music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Stagnant. +# Bounds x 1984-2367, z 4992-5023; 9216 tiles as 3 rectangles. + +[[area]] +name = "Stagnant" +area_id = "area.stagnant" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1984, 4992], + [2047, 4992], + [2047, 5023], + [1984, 5023], +] + +[[area.polygons]] +vertices = [ + [2080, 4992], + [2111, 4992], + [2111, 5023], + [2080, 5023], +] + +[[area.polygons]] +vertices = [ + [2176, 4992], + [2367, 4992], + [2367, 5023], + [2176, 5023], +] diff --git a/.data/raw-cache/map/area/stalkers.toml b/.data/raw-cache/map/area/stalkers.toml new file mode 100644 index 000000000..5599eeb35 --- /dev/null +++ b/.data/raw-cache/map/area/stalkers.toml @@ -0,0 +1,24 @@ +# Stalkers music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Stalkers. +# Bounds x 1240-1343, z 9728-9831; 9216 tiles as 2 rectangles. + +[[area]] +name = "Stalkers" +area_id = "area.stalkers" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1240, 9728], + [1343, 9728], + [1343, 9791], + [1240, 9791], +] + +[[area.polygons]] +vertices = [ + [1280, 9792], + [1343, 9792], + [1343, 9831], + [1280, 9831], +] diff --git a/.data/raw-cache/map/area/stillness.toml b/.data/raw-cache/map/area/stillness.toml new file mode 100644 index 000000000..c9123d364 --- /dev/null +++ b/.data/raw-cache/map/area/stillness.toml @@ -0,0 +1,64 @@ +# Stillness music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Stillness. +# Bounds x 3072-3711, z 7424-9919; 21468 tiles as 7 rectangles. + +[[area]] +name = "Stillness" +area_id = "area.stillness" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3072, 7424], + [3135, 7424], + [3135, 7487], + [3072, 7487], +] + +[[area.polygons]] +vertices = [ + [3169, 7445], + [3193, 7445], + [3193, 7469], + [3169, 7469], +] + +[[area.polygons]] +vertices = [ + [3147, 7573], + [3177, 7573], + [3177, 7593], + [3147, 7593], +] + +[[area.polygons]] +vertices = [ + [3648, 9728], + [3711, 9728], + [3711, 9791], + [3648, 9791], +] + +[[area.polygons]] +vertices = [ + [3392, 9792], + [3511, 9792], + [3511, 9855], + [3392, 9855], +] + +[[area.polygons]] +vertices = [ + [3512, 9828], + [3519, 9828], + [3519, 9919], + [3512, 9919], +] + +[[area.polygons]] +vertices = [ + [3456, 9856], + [3511, 9856], + [3511, 9919], + [3456, 9919], +] diff --git a/.data/raw-cache/map/area/stones_of_old.toml b/.data/raw-cache/map/area/stones_of_old.toml new file mode 100644 index 000000000..c7d37373f --- /dev/null +++ b/.data/raw-cache/map/area/stones_of_old.toml @@ -0,0 +1,237 @@ +# Stones of Old music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Stones of Old. +# Bounds x 1480-1531, z 3210-3263; 1633 tiles as 37 rectangles. + +[[area]] +name = "Stones of Old" +area_id = "area.stones_of_old" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1489, 3210], + [1511, 3210], + [1511, 3246], + [1489, 3246], +] + +[[area.polygons]] +vertices = [ + [1512, 3211], + [1530, 3211], + [1530, 3215], + [1512, 3215], +] + +[[area.polygons]] +vertices = [ + [1531, 3212], + [1531, 3215], +] + +[[area.polygons]] +vertices = [ + [1512, 3216], + [1523, 3216], + [1523, 3234], + [1512, 3234], +] + +[[area.polygons]] +vertices = [ + [1488, 3223], + [1488, 3263], +] + +[[area.polygons]] +vertices = [ + [1487, 3224], + [1487, 3241], +] + +[[area.polygons]] +vertices = [ + [1486, 3225], + [1486, 3240], +] + +[[area.polygons]] +vertices = [ + [1485, 3226], + [1485, 3239], +] + +[[area.polygons]] +vertices = [ + [1484, 3227], + [1484, 3238], +] + +[[area.polygons]] +vertices = [ + [1483, 3228], + [1483, 3237], +] + +[[area.polygons]] +vertices = [ + [1482, 3229], + [1482, 3236], +] + +[[area.polygons]] +vertices = [ + [1481, 3230], + [1481, 3235], +] + +[[area.polygons]] +vertices = [ + [1480, 3231], + [1480, 3234], +] + +[[area.polygons]] +vertices = [ + [1512, 3235], + [1520, 3235], + [1520, 3237], + [1512, 3237], +] + +[[area.polygons]] +vertices = [ + [1512, 3238], + [1519, 3238], +] + +[[area.polygons]] +vertices = [ + [1512, 3239], + [1518, 3239], +] + +[[area.polygons]] +vertices = [ + [1512, 3240], + [1517, 3240], +] + +[[area.polygons]] +vertices = [ + [1512, 3241], + [1516, 3241], +] + +[[area.polygons]] +vertices = [ + [1512, 3242], + [1515, 3242], +] + +[[area.polygons]] +vertices = [ + [1512, 3243], + [1514, 3243], +] + +[[area.polygons]] +vertices = [ + [1512, 3244], + [1513, 3244], +] + +[[area.polygons]] +vertices = [[1512, 3245]] + +[[area.polygons]] +vertices = [ + [1489, 3247], + [1502, 3247], + [1502, 3254], + [1489, 3254], +] + +[[area.polygons]] +vertices = [ + [1487, 3248], + [1487, 3263], +] + +[[area.polygons]] +vertices = [ + [1486, 3249], + [1486, 3263], +] + +[[area.polygons]] +vertices = [ + [1485, 3250], + [1485, 3263], +] + +[[area.polygons]] +vertices = [ + [1484, 3251], + [1484, 3263], +] + +[[area.polygons]] +vertices = [ + [1483, 3252], + [1483, 3263], +] + +[[area.polygons]] +vertices = [ + [1489, 3255], + [1501, 3255], +] + +[[area.polygons]] +vertices = [ + [1489, 3256], + [1500, 3256], +] + +[[area.polygons]] +vertices = [ + [1489, 3257], + [1499, 3257], +] + +[[area.polygons]] +vertices = [ + [1489, 3258], + [1498, 3258], +] + +[[area.polygons]] +vertices = [ + [1489, 3259], + [1497, 3259], +] + +[[area.polygons]] +vertices = [ + [1489, 3260], + [1496, 3260], +] + +[[area.polygons]] +vertices = [ + [1489, 3261], + [1495, 3261], +] + +[[area.polygons]] +vertices = [ + [1489, 3262], + [1494, 3262], +] + +[[area.polygons]] +vertices = [ + [1489, 3263], + [1493, 3263], +] diff --git a/.data/raw-cache/map/area/storeroom_shuffle.toml b/.data/raw-cache/map/area/storeroom_shuffle.toml new file mode 100644 index 000000000..d9b62d7df --- /dev/null +++ b/.data/raw-cache/map/area/storeroom_shuffle.toml @@ -0,0 +1,16 @@ +# Storeroom Shuffle music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Storeroom Shuffle. +# Bounds x 2752-2815, z 4224-4287; 4096 tiles as 1 rectangles. + +[[area]] +name = "Storeroom Shuffle" +area_id = "area.storeroom_shuffle" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 4224], + [2815, 4224], + [2815, 4287], + [2752, 4287], +] diff --git a/.data/raw-cache/map/area/storm_brew.toml b/.data/raw-cache/map/area/storm_brew.toml new file mode 100644 index 000000000..5e9bc98cb --- /dev/null +++ b/.data/raw-cache/map/area/storm_brew.toml @@ -0,0 +1,16 @@ +# Storm Brew music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Storm Brew. +# Bounds x 2624-2687, z 5184-5247; 4096 tiles as 1 rectangles. + +[[area]] +name = "Storm Brew" +area_id = "area.storm_brew" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2624, 5184], + [2687, 5184], + [2687, 5247], + [2624, 5247], +] diff --git a/.data/raw-cache/map/area/stranded.toml b/.data/raw-cache/map/area/stranded.toml new file mode 100644 index 000000000..5c710683a --- /dev/null +++ b/.data/raw-cache/map/area/stranded.toml @@ -0,0 +1,342 @@ +# Stranded music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Stranded. +# Bounds x 2816-2943, z 3716-4087; 7799 tiles as 53 rectangles. + +[[area]] +name = "Stranded" +area_id = "area.stranded" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2838, 3716], + [2943, 3716], + [2943, 3775], + [2838, 3775], +] + +[[area.polygons]] +vertices = [ + [2816, 3747], + [2837, 3747], + [2837, 3775], + [2816, 3775], +] + +[[area.polygons]] +vertices = [ + [2924, 4054], + [2927, 4054], + [2927, 4057], + [2924, 4057], +] + +[[area.polygons]] +vertices = [ + [2923, 4055], + [2923, 4056], +] + +[[area.polygons]] +vertices = [ + [2928, 4055], + [2928, 4059], +] + +[[area.polygons]] +vertices = [ + [2911, 4057], + [2913, 4057], + [2913, 4086], + [2911, 4086], +] + +[[area.polygons]] +vertices = [ + [2929, 4057], + [2929, 4058], +] + +[[area.polygons]] +vertices = [ + [2909, 4058], + [2910, 4058], + [2910, 4086], + [2909, 4086], +] + +[[area.polygons]] +vertices = [ + [2914, 4058], + [2914, 4076], +] + +[[area.polygons]] +vertices = [ + [2925, 4058], + [2927, 4058], +] + +[[area.polygons]] +vertices = [ + [2908, 4059], + [2908, 4087], +] + +[[area.polygons]] +vertices = [ + [2926, 4059], + [2927, 4059], + [2927, 4074], + [2926, 4074], +] + +[[area.polygons]] +vertices = [ + [2896, 4060], + [2896, 4074], +] + +[[area.polygons]] +vertices = [ + [2907, 4060], + [2907, 4087], +] + +[[area.polygons]] +vertices = [ + [2915, 4060], + [2915, 4075], +] + +[[area.polygons]] +vertices = [ + [2897, 4061], + [2897, 4075], +] + +[[area.polygons]] +vertices = [ + [2906, 4061], + [2906, 4087], +] + +[[area.polygons]] +vertices = [ + [2916, 4061], + [2917, 4061], + [2917, 4074], + [2916, 4074], +] + +[[area.polygons]] +vertices = [ + [2895, 4062], + [2895, 4073], +] + +[[area.polygons]] +vertices = [ + [2905, 4062], + [2905, 4087], +] + +[[area.polygons]] +vertices = [ + [2918, 4062], + [2918, 4074], +] + +[[area.polygons]] +vertices = [ + [2925, 4062], + [2925, 4073], +] + +[[area.polygons]] +vertices = [ + [2894, 4063], + [2894, 4069], +] + +[[area.polygons]] +vertices = [ + [2898, 4063], + [2899, 4063], + [2899, 4076], + [2898, 4076], +] + +[[area.polygons]] +vertices = [ + [2904, 4063], + [2904, 4087], +] + +[[area.polygons]] +vertices = [ + [2919, 4063], + [2919, 4073], +] + +[[area.polygons]] +vertices = [ + [2900, 4064], + [2903, 4064], + [2903, 4087], + [2900, 4087], +] + +[[area.polygons]] +vertices = [ + [2920, 4064], + [2920, 4072], +] + +[[area.polygons]] +vertices = [ + [2924, 4064], + [2924, 4072], +] + +[[area.polygons]] +vertices = [ + [2928, 4064], + [2928, 4075], +] + +[[area.polygons]] +vertices = [ + [2921, 4065], + [2923, 4065], + [2923, 4071], + [2921, 4071], +] + +[[area.polygons]] +vertices = [ + [2929, 4065], + [2929, 4075], +] + +[[area.polygons]] +vertices = [ + [2893, 4066], + [2893, 4068], +] + +[[area.polygons]] +vertices = [ + [2930, 4066], + [2931, 4066], + [2931, 4075], + [2930, 4075], +] + +[[area.polygons]] +vertices = [ + [2932, 4067], + [2932, 4074], +] + +[[area.polygons]] +vertices = [ + [2933, 4068], + [2933, 4073], +] + +[[area.polygons]] +vertices = [ + [2934, 4071], + [2934, 4072], +] + +[[area.polygons]] +vertices = [[2921, 4072]] + +[[area.polygons]] +vertices = [[2916, 4075]] + +[[area.polygons]] +vertices = [[2927, 4075]] + +[[area.polygons]] +vertices = [ + [2899, 4077], + [2899, 4087], +] + +[[area.polygons]] +vertices = [ + [2898, 4079], + [2898, 4086], +] + +[[area.polygons]] +vertices = [ + [2914, 4079], + [2914, 4087], +] + +[[area.polygons]] +vertices = [ + [2915, 4080], + [2916, 4080], + [2916, 4087], + [2915, 4087], +] + +[[area.polygons]] +vertices = [ + [2917, 4081], + [2918, 4081], + [2918, 4086], + [2917, 4086], +] + +[[area.polygons]] +vertices = [ + [2919, 4082], + [2919, 4086], +] + +[[area.polygons]] +vertices = [ + [2920, 4083], + [2921, 4083], + [2921, 4086], + [2920, 4086], +] + +[[area.polygons]] +vertices = [ + [2897, 4084], + [2897, 4086], +] + +[[area.polygons]] +vertices = [ + [2922, 4084], + [2922, 4086], +] + +[[area.polygons]] +vertices = [ + [2896, 4085], + [2896, 4086], +] + +[[area.polygons]] +vertices = [ + [2923, 4085], + [2923, 4086], +] + +[[area.polygons]] +vertices = [ + [2912, 4087], + [2913, 4087], +] + +[[area.polygons]] +vertices = [[2917, 4087]] diff --git a/.data/raw-cache/map/area/strange_place.toml b/.data/raw-cache/map/area/strange_place.toml new file mode 100644 index 000000000..b41e1bfcc --- /dev/null +++ b/.data/raw-cache/map/area/strange_place.toml @@ -0,0 +1,16 @@ +# Strange Place music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Strange Place. +# Bounds x 1856-1919, z 4480-4543; 4096 tiles as 1 rectangles. + +[[area]] +name = "Strange Place" +area_id = "area.strange_place" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 4480], + [1919, 4480], + [1919, 4543], + [1856, 4543], +] diff --git a/.data/raw-cache/map/area/strangled.toml b/.data/raw-cache/map/area/strangled.toml new file mode 100644 index 000000000..815ba863c --- /dev/null +++ b/.data/raw-cache/map/area/strangled.toml @@ -0,0 +1,565 @@ +# Strangled music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Strangled. +# Bounds x 1088-1238, z 3264-3455; 21738 tiles as 92 rectangles. + +[[area]] +name = "Strangled" +area_id = "area.strangled" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1088, 3264], + [1187, 3264], + [1187, 3311], + [1088, 3311], +] + +[[area.polygons]] +vertices = [ + [1088, 3312], + [1186, 3312], +] + +[[area.polygons]] +vertices = [ + [1088, 3313], + [1185, 3313], +] + +[[area.polygons]] +vertices = [ + [1088, 3314], + [1184, 3314], +] + +[[area.polygons]] +vertices = [ + [1088, 3315], + [1183, 3315], +] + +[[area.polygons]] +vertices = [ + [1088, 3316], + [1182, 3316], +] + +[[area.polygons]] +vertices = [ + [1088, 3317], + [1181, 3317], +] + +[[area.polygons]] +vertices = [ + [1088, 3318], + [1180, 3318], +] + +[[area.polygons]] +vertices = [ + [1088, 3319], + [1179, 3319], +] + +[[area.polygons]] +vertices = [ + [1088, 3320], + [1178, 3320], +] + +[[area.polygons]] +vertices = [ + [1088, 3321], + [1177, 3321], +] + +[[area.polygons]] +vertices = [ + [1088, 3322], + [1176, 3322], +] + +[[area.polygons]] +vertices = [ + [1088, 3323], + [1175, 3323], +] + +[[area.polygons]] +vertices = [ + [1088, 3324], + [1174, 3324], +] + +[[area.polygons]] +vertices = [ + [1088, 3325], + [1173, 3325], +] + +[[area.polygons]] +vertices = [ + [1088, 3326], + [1172, 3326], +] + +[[area.polygons]] +vertices = [ + [1088, 3327], + [1171, 3327], +] + +[[area.polygons]] +vertices = [ + [1088, 3328], + [1170, 3328], +] + +[[area.polygons]] +vertices = [ + [1088, 3329], + [1169, 3329], +] + +[[area.polygons]] +vertices = [ + [1088, 3330], + [1168, 3330], +] + +[[area.polygons]] +vertices = [ + [1088, 3331], + [1167, 3331], +] + +[[area.polygons]] +vertices = [ + [1088, 3332], + [1166, 3332], +] + +[[area.polygons]] +vertices = [ + [1088, 3333], + [1165, 3333], +] + +[[area.polygons]] +vertices = [ + [1088, 3334], + [1164, 3334], +] + +[[area.polygons]] +vertices = [ + [1088, 3335], + [1163, 3335], + [1163, 3455], + [1088, 3455], +] + +[[area.polygons]] +vertices = [ + [1164, 3337], + [1164, 3455], +] + +[[area.polygons]] +vertices = [ + [1165, 3338], + [1165, 3455], +] + +[[area.polygons]] +vertices = [ + [1166, 3339], + [1166, 3455], +] + +[[area.polygons]] +vertices = [ + [1167, 3340], + [1167, 3455], +] + +[[area.polygons]] +vertices = [ + [1168, 3341], + [1168, 3455], +] + +[[area.polygons]] +vertices = [ + [1169, 3342], + [1169, 3455], +] + +[[area.polygons]] +vertices = [ + [1170, 3343], + [1170, 3455], +] + +[[area.polygons]] +vertices = [ + [1171, 3344], + [1171, 3455], +] + +[[area.polygons]] +vertices = [ + [1172, 3345], + [1172, 3455], +] + +[[area.polygons]] +vertices = [ + [1173, 3346], + [1173, 3455], +] + +[[area.polygons]] +vertices = [ + [1174, 3347], + [1174, 3455], +] + +[[area.polygons]] +vertices = [ + [1175, 3348], + [1175, 3455], +] + +[[area.polygons]] +vertices = [ + [1176, 3349], + [1176, 3455], +] + +[[area.polygons]] +vertices = [ + [1177, 3350], + [1177, 3455], +] + +[[area.polygons]] +vertices = [ + [1178, 3351], + [1178, 3455], +] + +[[area.polygons]] +vertices = [ + [1179, 3352], + [1187, 3352], + [1187, 3455], + [1179, 3455], +] + +[[area.polygons]] +vertices = [ + [1188, 3353], + [1188, 3455], +] + +[[area.polygons]] +vertices = [ + [1189, 3354], + [1189, 3455], +] + +[[area.polygons]] +vertices = [ + [1190, 3355], + [1190, 3455], +] + +[[area.polygons]] +vertices = [ + [1191, 3356], + [1191, 3455], +] + +[[area.polygons]] +vertices = [ + [1192, 3357], + [1192, 3455], +] + +[[area.polygons]] +vertices = [ + [1193, 3358], + [1193, 3455], +] + +[[area.polygons]] +vertices = [ + [1194, 3359], + [1194, 3455], +] + +[[area.polygons]] +vertices = [ + [1195, 3360], + [1195, 3455], +] + +[[area.polygons]] +vertices = [ + [1196, 3361], + [1196, 3455], +] + +[[area.polygons]] +vertices = [ + [1197, 3362], + [1197, 3455], +] + +[[area.polygons]] +vertices = [ + [1198, 3363], + [1198, 3455], +] + +[[area.polygons]] +vertices = [ + [1199, 3364], + [1199, 3455], +] + +[[area.polygons]] +vertices = [ + [1200, 3365], + [1200, 3438], +] + +[[area.polygons]] +vertices = [ + [1201, 3366], + [1201, 3437], +] + +[[area.polygons]] +vertices = [ + [1202, 3367], + [1202, 3436], +] + +[[area.polygons]] +vertices = [ + [1203, 3368], + [1203, 3435], +] + +[[area.polygons]] +vertices = [ + [1204, 3369], + [1204, 3434], +] + +[[area.polygons]] +vertices = [ + [1205, 3370], + [1205, 3433], +] + +[[area.polygons]] +vertices = [ + [1206, 3371], + [1238, 3371], + [1238, 3400], + [1206, 3400], +] + +[[area.polygons]] +vertices = [ + [1206, 3401], + [1237, 3401], +] + +[[area.polygons]] +vertices = [ + [1206, 3402], + [1236, 3402], +] + +[[area.polygons]] +vertices = [ + [1206, 3403], + [1235, 3403], +] + +[[area.polygons]] +vertices = [ + [1206, 3404], + [1234, 3404], +] + +[[area.polygons]] +vertices = [ + [1206, 3405], + [1233, 3405], +] + +[[area.polygons]] +vertices = [ + [1206, 3406], + [1232, 3406], +] + +[[area.polygons]] +vertices = [ + [1206, 3407], + [1231, 3407], +] + +[[area.polygons]] +vertices = [ + [1206, 3408], + [1230, 3408], +] + +[[area.polygons]] +vertices = [ + [1206, 3409], + [1229, 3409], +] + +[[area.polygons]] +vertices = [ + [1206, 3410], + [1228, 3410], +] + +[[area.polygons]] +vertices = [ + [1206, 3411], + [1227, 3411], +] + +[[area.polygons]] +vertices = [ + [1206, 3412], + [1226, 3412], +] + +[[area.polygons]] +vertices = [ + [1206, 3413], + [1225, 3413], +] + +[[area.polygons]] +vertices = [ + [1206, 3414], + [1224, 3414], +] + +[[area.polygons]] +vertices = [ + [1206, 3415], + [1223, 3415], +] + +[[area.polygons]] +vertices = [ + [1206, 3416], + [1222, 3416], +] + +[[area.polygons]] +vertices = [ + [1206, 3417], + [1221, 3417], +] + +[[area.polygons]] +vertices = [ + [1206, 3418], + [1220, 3418], +] + +[[area.polygons]] +vertices = [ + [1206, 3419], + [1219, 3419], +] + +[[area.polygons]] +vertices = [ + [1206, 3420], + [1218, 3420], +] + +[[area.polygons]] +vertices = [ + [1206, 3421], + [1217, 3421], +] + +[[area.polygons]] +vertices = [ + [1206, 3422], + [1216, 3422], +] + +[[area.polygons]] +vertices = [ + [1206, 3423], + [1215, 3423], +] + +[[area.polygons]] +vertices = [ + [1206, 3424], + [1214, 3424], +] + +[[area.polygons]] +vertices = [ + [1206, 3425], + [1213, 3425], +] + +[[area.polygons]] +vertices = [ + [1206, 3426], + [1212, 3426], +] + +[[area.polygons]] +vertices = [ + [1206, 3427], + [1211, 3427], +] + +[[area.polygons]] +vertices = [ + [1206, 3428], + [1210, 3428], +] + +[[area.polygons]] +vertices = [ + [1206, 3429], + [1209, 3429], +] + +[[area.polygons]] +vertices = [ + [1206, 3430], + [1208, 3430], +] + +[[area.polygons]] +vertices = [ + [1206, 3431], + [1207, 3431], +] + +[[area.polygons]] +vertices = [[1206, 3432]] diff --git a/.data/raw-cache/map/area/stratosphere.toml b/.data/raw-cache/map/area/stratosphere.toml new file mode 100644 index 000000000..c2cbd65b3 --- /dev/null +++ b/.data/raw-cache/map/area/stratosphere.toml @@ -0,0 +1,16 @@ +# Stratosphere music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Stratosphere. +# Bounds x 2112-2175, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Stratosphere" +area_id = "area.stratosphere" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2112, 4800], + [2175, 4800], + [2175, 4863], + [2112, 4863], +] diff --git a/.data/raw-cache/map/area/strength_of_saradomin.toml b/.data/raw-cache/map/area/strength_of_saradomin.toml new file mode 100644 index 000000000..f30adcac9 --- /dev/null +++ b/.data/raw-cache/map/area/strength_of_saradomin.toml @@ -0,0 +1,32 @@ +# Strength of Saradomin music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Strength of Saradomin. +# Bounds x 2880-2943, z 5248-5311; 3136 tiles as 3 rectangles. + +[[area]] +name = "Strength of Saradomin" +area_id = "area.strength_of_saradomin" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2880, 5248], + [2943, 5248], + [2943, 5279], + [2880, 5279], +] + +[[area.polygons]] +vertices = [ + [2904, 5280], + [2943, 5280], + [2943, 5287], + [2904, 5287], +] + +[[area.polygons]] +vertices = [ + [2912, 5288], + [2943, 5288], + [2943, 5311], + [2912, 5311], +] diff --git a/.data/raw-cache/map/area/stuck_in_the_mire.toml b/.data/raw-cache/map/area/stuck_in_the_mire.toml new file mode 100644 index 000000000..33638b73b --- /dev/null +++ b/.data/raw-cache/map/area/stuck_in_the_mire.toml @@ -0,0 +1,264 @@ +# Stuck in the Mire music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Stuck in the Mire. +# Bounds x 1152-1343, z 3568-3671; 13277 tiles as 41 rectangles. + +[[area]] +name = "Stuck in the Mire" +area_id = "area.stuck_in_the_mire" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1193, 3568], + [1199, 3568], + [1199, 3664], + [1193, 3664], +] + +[[area.polygons]] +vertices = [ + [1192, 3569], + [1192, 3664], +] + +[[area.polygons]] +vertices = [ + [1191, 3570], + [1191, 3664], +] + +[[area.polygons]] +vertices = [ + [1190, 3571], + [1190, 3664], +] + +[[area.polygons]] +vertices = [ + [1189, 3572], + [1189, 3664], +] + +[[area.polygons]] +vertices = [ + [1188, 3573], + [1188, 3664], +] + +[[area.polygons]] +vertices = [ + [1187, 3574], + [1187, 3664], +] + +[[area.polygons]] +vertices = [ + [1186, 3575], + [1186, 3664], +] + +[[area.polygons]] +vertices = [ + [1185, 3576], + [1185, 3664], +] + +[[area.polygons]] +vertices = [ + [1184, 3577], + [1184, 3664], +] + +[[area.polygons]] +vertices = [ + [1183, 3578], + [1183, 3664], +] + +[[area.polygons]] +vertices = [ + [1182, 3579], + [1182, 3664], +] + +[[area.polygons]] +vertices = [ + [1152, 3580], + [1181, 3580], + [1181, 3664], + [1152, 3664], +] + +[[area.polygons]] +vertices = [ + [1278, 3593], + [1330, 3593], + [1330, 3650], + [1278, 3650], +] + +[[area.polygons]] +vertices = [ + [1277, 3594], + [1277, 3650], +] + +[[area.polygons]] +vertices = [ + [1331, 3594], + [1331, 3652], +] + +[[area.polygons]] +vertices = [ + [1276, 3595], + [1276, 3650], +] + +[[area.polygons]] +vertices = [ + [1332, 3595], + [1332, 3652], +] + +[[area.polygons]] +vertices = [ + [1275, 3596], + [1275, 3650], +] + +[[area.polygons]] +vertices = [ + [1333, 3596], + [1333, 3652], +] + +[[area.polygons]] +vertices = [ + [1274, 3597], + [1274, 3650], +] + +[[area.polygons]] +vertices = [ + [1334, 3597], + [1334, 3652], +] + +[[area.polygons]] +vertices = [ + [1273, 3598], + [1273, 3650], +] + +[[area.polygons]] +vertices = [ + [1335, 3598], + [1335, 3652], +] + +[[area.polygons]] +vertices = [ + [1272, 3599], + [1272, 3650], +] + +[[area.polygons]] +vertices = [ + [1336, 3599], + [1336, 3652], +] + +[[area.polygons]] +vertices = [ + [1200, 3600], + [1271, 3600], + [1271, 3664], + [1200, 3664], +] + +[[area.polygons]] +vertices = [ + [1337, 3600], + [1337, 3652], +] + +[[area.polygons]] +vertices = [ + [1338, 3601], + [1338, 3652], +] + +[[area.polygons]] +vertices = [ + [1339, 3602], + [1343, 3602], + [1343, 3652], + [1339, 3652], +] + +[[area.polygons]] +vertices = [ + [1300, 3651], + [1330, 3651], +] + +[[area.polygons]] +vertices = [ + [1301, 3652], + [1330, 3652], +] + +[[area.polygons]] +vertices = [ + [1302, 3653], + [1314, 3653], +] + +[[area.polygons]] +vertices = [ + [1303, 3654], + [1313, 3654], +] + +[[area.polygons]] +vertices = [ + [1243, 3665], + [1271, 3665], +] + +[[area.polygons]] +vertices = [ + [1244, 3666], + [1271, 3666], +] + +[[area.polygons]] +vertices = [ + [1245, 3667], + [1271, 3667], +] + +[[area.polygons]] +vertices = [ + [1246, 3668], + [1271, 3668], +] + +[[area.polygons]] +vertices = [ + [1247, 3669], + [1271, 3669], +] + +[[area.polygons]] +vertices = [ + [1248, 3670], + [1271, 3670], +] + +[[area.polygons]] +vertices = [ + [1249, 3671], + [1271, 3671], +] diff --git a/.data/raw-cache/map/area/subterranea.toml b/.data/raw-cache/map/area/subterranea.toml new file mode 100644 index 000000000..4614d2e0c --- /dev/null +++ b/.data/raw-cache/map/area/subterranea.toml @@ -0,0 +1,32 @@ +# Subterranea music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Subterranea. +# Bounds x 1600-2559, z 9728-10175; 26624 tiles as 3 rectangles. + +[[area]] +name = "Subterranea" +area_id = "area.subterranea" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1600, 9728], + [1727, 9728], + [1727, 9855], + [1600, 9855], +] + +[[area.polygons]] +vertices = [ + [2240, 9824], + [2303, 9824], + [2303, 9919], + [2240, 9919], +] + +[[area.polygons]] +vertices = [ + [2496, 10112], + [2559, 10112], + [2559, 10175], + [2496, 10175], +] diff --git a/.data/raw-cache/map/area/superstition.toml b/.data/raw-cache/map/area/superstition.toml new file mode 100644 index 000000000..8f7198348 --- /dev/null +++ b/.data/raw-cache/map/area/superstition.toml @@ -0,0 +1,32 @@ +# Superstition music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Superstition. +# Bounds x 1536-2815, z 9280-10239; 7168 tiles as 3 rectangles. + +[[area]] +name = "Superstition" +area_id = "area.superstition" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 9280], + [2815, 9280], + [2815, 9343], + [2752, 9343], +] + +[[area.polygons]] +vertices = [ + [1536, 10176], + [1599, 10176], + [1599, 10207], + [1536, 10207], +] + +[[area.polygons]] +vertices = [ + [1568, 10208], + [1599, 10208], + [1599, 10239], + [1568, 10239], +] diff --git a/.data/raw-cache/map/area/suspicious.toml b/.data/raw-cache/map/area/suspicious.toml new file mode 100644 index 000000000..93af23eb6 --- /dev/null +++ b/.data/raw-cache/map/area/suspicious.toml @@ -0,0 +1,16 @@ +# Suspicious music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Suspicious. +# Bounds x 2560-2687, z 4544-4607; 8192 tiles as 1 rectangles. + +[[area]] +name = "Suspicious" +area_id = "area.suspicious" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 4544], + [2687, 4544], + [2687, 4607], + [2560, 4607], +] diff --git a/.data/raw-cache/map/area/taverley.toml b/.data/raw-cache/map/area/taverley.toml new file mode 100644 index 000000000..8205b0f83 --- /dev/null +++ b/.data/raw-cache/map/area/taverley.toml @@ -0,0 +1,367 @@ +# Regenerated as rectangles from the OSRS wiki music map: 808 tiles moved to +# neighbouring music areas and 2139 tiles were added. + +[[area]] +name = "Taverley" +area_id = "area.taverley" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2887, 3384], + [2889, 3384], + [2889, 3504], + [2887, 3504], +] + +[[area.polygons]] +vertices = [ + [2890, 3385], + [2892, 3385], + [2892, 3504], + [2890, 3504], +] + +[[area.polygons]] +vertices = [ + [2893, 3386], + [2895, 3386], + [2895, 3504], + [2893, 3504], +] + +[[area.polygons]] +vertices = [ + [2886, 3387], + [2886, 3504], +] + +[[area.polygons]] +vertices = [ + [2896, 3387], + [2898, 3387], + [2898, 3504], + [2896, 3504], +] + +[[area.polygons]] +vertices = [ + [2885, 3388], + [2885, 3504], +] + +[[area.polygons]] +vertices = [ + [2899, 3388], + [2901, 3388], + [2901, 3504], + [2899, 3504], +] + +[[area.polygons]] +vertices = [ + [2882, 3389], + [2884, 3389], + [2884, 3504], + [2882, 3504], +] + +[[area.polygons]] +vertices = [ + [2902, 3389], + [2904, 3389], + [2904, 3504], + [2902, 3504], +] + +[[area.polygons]] +vertices = [ + [2881, 3390], + [2881, 3504], +] + +[[area.polygons]] +vertices = [ + [2905, 3390], + [2907, 3390], + [2907, 3504], + [2905, 3504], +] + +[[area.polygons]] +vertices = [ + [2880, 3391], + [2880, 3504], +] + +[[area.polygons]] +vertices = [ + [2908, 3391], + [2922, 3391], + [2922, 3504], + [2908, 3504], +] + +[[area.polygons]] +vertices = [ + [2879, 3392], + [2879, 3498], +] + +[[area.polygons]] +vertices = [ + [2923, 3392], + [2923, 3504], +] + +[[area.polygons]] +vertices = [ + [2924, 3393], + [2924, 3504], +] + +[[area.polygons]] +vertices = [ + [2925, 3394], + [2925, 3504], +] + +[[area.polygons]] +vertices = [ + [2878, 3395], + [2878, 3496], +] + +[[area.polygons]] +vertices = [ + [2926, 3395], + [2926, 3504], +] + +[[area.polygons]] +vertices = [ + [2927, 3396], + [2927, 3504], +] + +[[area.polygons]] +vertices = [ + [2928, 3397], + [2928, 3504], +] + +[[area.polygons]] +vertices = [ + [2929, 3398], + [2929, 3504], +] + +[[area.polygons]] +vertices = [ + [2877, 3399], + [2877, 3431], +] + +[[area.polygons]] +vertices = [ + [2930, 3399], + [2930, 3504], +] + +[[area.polygons]] +vertices = [ + [2860, 3400], + [2876, 3400], + [2876, 3427], + [2860, 3427], +] + +[[area.polygons]] +vertices = [ + [2931, 3400], + [2934, 3400], + [2934, 3504], + [2931, 3504], +] + +[[area.polygons]] +vertices = [ + [2860, 3428], + [2868, 3428], +] + +[[area.polygons]] +vertices = [ + [2872, 3428], + [2876, 3428], + [2876, 3429], + [2872, 3429], +] + +[[area.polygons]] +vertices = [ + [2860, 3429], + [2867, 3429], + [2867, 3430], + [2860, 3430], +] + +[[area.polygons]] +vertices = [[2876, 3430]] + +[[area.polygons]] +vertices = [ + [2860, 3431], + [2866, 3431], +] + +[[area.polygons]] +vertices = [ + [2860, 3432], + [2865, 3432], +] + +[[area.polygons]] +vertices = [ + [2860, 3433], + [2864, 3433], +] + +[[area.polygons]] +vertices = [ + [2860, 3434], + [2863, 3434], +] + +[[area.polygons]] +vertices = [ + [2860, 3435], + [2862, 3435], +] + +[[area.polygons]] +vertices = [ + [2860, 3436], + [2861, 3436], +] + +[[area.polygons]] +vertices = [[2860, 3437]] + +[[area.polygons]] +vertices = [ + [2935, 3441], + [2943, 3441], + [2943, 3443], + [2935, 3443], +] + +[[area.polygons]] +vertices = [ + [2935, 3444], + [2942, 3444], +] + +[[area.polygons]] +vertices = [ + [2935, 3445], + [2941, 3445], +] + +[[area.polygons]] +vertices = [ + [2935, 3446], + [2940, 3446], +] + +[[area.polygons]] +vertices = [ + [2935, 3447], + [2939, 3447], +] + +[[area.polygons]] +vertices = [ + [2935, 3448], + [2936, 3448], +] + +[[area.polygons]] +vertices = [ + [2935, 3449], + [2935, 3493], +] + +[[area.polygons]] +vertices = [ + [2936, 3455], + [2938, 3455], + [2938, 3472], + [2936, 3472], +] + +[[area.polygons]] +vertices = [ + [2939, 3456], + [2939, 3471], +] + +[[area.polygons]] +vertices = [ + [2877, 3463], + [2877, 3494], +] + +[[area.polygons]] +vertices = [ + [2876, 3465], + [2876, 3492], +] + +[[area.polygons]] +vertices = [ + [2875, 3467], + [2875, 3490], +] + +[[area.polygons]] +vertices = [ + [2874, 3469], + [2874, 3488], +] + +[[area.polygons]] +vertices = [ + [2873, 3471], + [2873, 3486], +] + +[[area.polygons]] +vertices = [ + [2872, 3473], + [2872, 3484], +] + +[[area.polygons]] +vertices = [ + [2936, 3473], + [2937, 3473], +] + +[[area.polygons]] +vertices = [ + [2936, 3474], + [2936, 3492], +] + +[[area.polygons]] +vertices = [[2935, 3504]] + +[[area.polygons]] +vertices = [ + [2936, 3505], + [2936, 3507], +] + +[[area.polygons]] +vertices = [[2937, 3506]] diff --git a/.data/raw-cache/map/area/taverley_dungeon.toml b/.data/raw-cache/map/area/taverley_dungeon.toml index adbc30e48..23ba285bb 100644 --- a/.data/raw-cache/map/area/taverley_dungeon.toml +++ b/.data/raw-cache/map/area/taverley_dungeon.toml @@ -1,3 +1,6 @@ +# Regenerated as rectangles from the OSRS wiki music map: 8769 tiles moved to +# neighbouring music areas and 4032 tiles were added. + [[area]] name = "Taverley Underground" area_id = "area.taverley_dungeon" @@ -5,31 +8,40 @@ levels = [0, 1, 2, 3] [[area.polygons]] vertices = [ - [2752, 9792], - [2816, 9792], - [2816, 9856], - [2816, 9833], - [2814, 9832], - [2814, 9825], - [2816, 9823], - [2816, 9728], - [2880, 9728], - [2880, 9664], - [2944, 9664], - [2944, 9728], - [2944, 9765], - [2973, 9765], - [2973, 9800], - [2952, 9800], - [2944, 9805], - [2944, 9856], + [2752, 9664], + [2815, 9664], + [2815, 9856], [2752, 9856], ] [[area.polygons]] vertices = [ - [2752, 9664], - [3007, 9664], - [3007, 9855], - [2752, 9855], + [2880, 9664], + [2999, 9664], + [2999, 9791], + [2880, 9791], +] + +[[area.polygons]] +vertices = [ + [2816, 9728], + [2879, 9728], + [2879, 9855], + [2816, 9855], +] + +[[area.polygons]] +vertices = [ + [3000, 9728], + [3007, 9728], + [3007, 9791], + [3000, 9791], +] + +[[area.polygons]] +vertices = [ + [2880, 9792], + [2943, 9792], + [2943, 9919], + [2880, 9919], ] diff --git a/.data/raw-cache/map/area/tears_of_guthix.toml b/.data/raw-cache/map/area/tears_of_guthix.toml new file mode 100644 index 000000000..896db2295 --- /dev/null +++ b/.data/raw-cache/map/area/tears_of_guthix.toml @@ -0,0 +1,16 @@ +# Tears of Guthix music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Tears of Guthix. +# Bounds x 3200-3253, z 9472-9535; 3456 tiles as 1 rectangles. + +[[area]] +name = "Tears of Guthix" +area_id = "area.tears_of_guthix" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3200, 9472], + [3253, 9472], + [3253, 9535], + [3200, 9535], +] diff --git a/.data/raw-cache/map/area/technology.toml b/.data/raw-cache/map/area/technology.toml new file mode 100644 index 000000000..f106a4efc --- /dev/null +++ b/.data/raw-cache/map/area/technology.toml @@ -0,0 +1,16 @@ +# Technology music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Technology. +# Bounds x 2368-2431, z 9856-9919; 4096 tiles as 1 rectangles. + +[[area]] +name = "Technology" +area_id = "area.technology" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 9856], + [2431, 9856], + [2431, 9919], + [2368, 9919], +] diff --git a/.data/raw-cache/map/area/teklan.toml b/.data/raw-cache/map/area/teklan.toml new file mode 100644 index 000000000..4f4c7722e --- /dev/null +++ b/.data/raw-cache/map/area/teklan.toml @@ -0,0 +1,362 @@ +# Teklan music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Teklan. +# Bounds x 1183-1250, z 3081-3139; 2846 tiles as 55 rectangles. + +[[area]] +name = "Teklan" +area_id = "area.teklan" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1208, 3081], + [1208, 3134], +] + +[[area.polygons]] +vertices = [ + [1225, 3081], + [1226, 3081], + [1226, 3137], + [1225, 3137], +] + +[[area.polygons]] +vertices = [ + [1230, 3081], + [1230, 3137], +] + +[[area.polygons]] +vertices = [ + [1205, 3082], + [1207, 3082], + [1207, 3128], + [1205, 3128], +] + +[[area.polygons]] +vertices = [ + [1223, 3082], + [1224, 3082], + [1224, 3137], + [1223, 3137], +] + +[[area.polygons]] +vertices = [ + [1227, 3082], + [1229, 3082], + [1229, 3137], + [1227, 3137], +] + +[[area.polygons]] +vertices = [ + [1231, 3082], + [1231, 3137], +] + +[[area.polygons]] +vertices = [ + [1204, 3083], + [1204, 3127], +] + +[[area.polygons]] +vertices = [ + [1215, 3083], + [1215, 3137], +] + +[[area.polygons]] +vertices = [ + [1221, 3083], + [1222, 3083], + [1222, 3137], + [1221, 3137], +] + +[[area.polygons]] +vertices = [ + [1232, 3083], + [1232, 3137], +] + +[[area.polygons]] +vertices = [ + [1203, 3084], + [1203, 3126], +] + +[[area.polygons]] +vertices = [ + [1209, 3084], + [1214, 3084], + [1214, 3134], + [1209, 3134], +] + +[[area.polygons]] +vertices = [ + [1216, 3084], + [1220, 3084], + [1220, 3137], + [1216, 3137], +] + +[[area.polygons]] +vertices = [ + [1233, 3084], + [1233, 3137], +] + +[[area.polygons]] +vertices = [ + [1202, 3085], + [1202, 3126], +] + +[[area.polygons]] +vertices = [ + [1234, 3085], + [1238, 3085], + [1238, 3137], + [1234, 3137], +] + +[[area.polygons]] +vertices = [ + [1200, 3086], + [1201, 3086], + [1201, 3088], + [1200, 3088], +] + +[[area.polygons]] +vertices = [ + [1239, 3086], + [1239, 3139], +] + +[[area.polygons]] +vertices = [ + [1240, 3087], + [1240, 3139], +] + +[[area.polygons]] +vertices = [ + [1241, 3088], + [1241, 3139], +] + +[[area.polygons]] +vertices = [ + [1201, 3089], + [1201, 3127], +] + +[[area.polygons]] +vertices = [ + [1242, 3089], + [1242, 3139], +] + +[[area.polygons]] +vertices = [ + [1200, 3090], + [1200, 3128], +] + +[[area.polygons]] +vertices = [ + [1243, 3090], + [1243, 3139], +] + +[[area.polygons]] +vertices = [ + [1195, 3091], + [1199, 3091], + [1199, 3097], + [1195, 3097], +] + +[[area.polygons]] +vertices = [ + [1244, 3091], + [1244, 3138], +] + +[[area.polygons]] +vertices = [ + [1245, 3092], + [1245, 3138], +] + +[[area.polygons]] +vertices = [ + [1194, 3093], + [1194, 3095], +] + +[[area.polygons]] +vertices = [ + [1246, 3093], + [1246, 3137], +] + +[[area.polygons]] +vertices = [ + [1196, 3098], + [1199, 3098], + [1199, 3123], + [1196, 3123], +] + +[[area.polygons]] +vertices = [ + [1195, 3099], + [1195, 3123], +] + +[[area.polygons]] +vertices = [ + [1193, 3100], + [1194, 3100], + [1194, 3120], + [1193, 3120], +] + +[[area.polygons]] +vertices = [ + [1192, 3101], + [1192, 3119], +] + +[[area.polygons]] +vertices = [ + [1191, 3104], + [1191, 3118], +] + +[[area.polygons]] +vertices = [ + [1189, 3105], + [1190, 3105], + [1190, 3115], + [1189, 3115], +] + +[[area.polygons]] +vertices = [ + [1188, 3107], + [1188, 3114], +] + +[[area.polygons]] +vertices = [ + [1187, 3108], + [1187, 3113], +] + +[[area.polygons]] +vertices = [ + [1183, 3112], + [1186, 3112], + [1186, 3113], + [1183, 3113], +] + +[[area.polygons]] +vertices = [ + [1247, 3115], + [1249, 3115], + [1249, 3134], + [1247, 3134], +] + +[[area.polygons]] +vertices = [[1190, 3116]] + +[[area.polygons]] +vertices = [ + [1250, 3116], + [1250, 3132], +] + +[[area.polygons]] +vertices = [[1194, 3121]] + +[[area.polygons]] +vertices = [ + [1197, 3124], + [1199, 3124], +] + +[[area.polygons]] +vertices = [ + [1198, 3125], + [1199, 3125], + [1199, 3129], + [1198, 3129], +] + +[[area.polygons]] +vertices = [ + [1197, 3126], + [1197, 3128], +] + +[[area.polygons]] +vertices = [ + [1206, 3129], + [1207, 3129], + [1207, 3133], + [1206, 3133], +] + +[[area.polygons]] +vertices = [ + [1205, 3130], + [1205, 3132], +] + +[[area.polygons]] +vertices = [[1207, 3134]] + +[[area.polygons]] +vertices = [ + [1210, 3135], + [1214, 3135], +] + +[[area.polygons]] +vertices = [ + [1247, 3135], + [1248, 3135], +] + +[[area.polygons]] +vertices = [ + [1213, 3136], + [1214, 3136], + [1214, 3137], + [1213, 3137], +] + +[[area.polygons]] +vertices = [[1247, 3136]] + +[[area.polygons]] +vertices = [ + [1235, 3138], + [1238, 3138], +] + +[[area.polygons]] +vertices = [ + [1236, 3139], + [1238, 3139], +] diff --git a/.data/raw-cache/map/area/tempest.toml b/.data/raw-cache/map/area/tempest.toml new file mode 100644 index 000000000..2e771cb6c --- /dev/null +++ b/.data/raw-cache/map/area/tempest.toml @@ -0,0 +1,16 @@ +# Tempest music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Tempest. +# Bounds x 1664-1727, z 4544-4607; 4096 tiles as 1 rectangles. + +[[area]] +name = "Tempest" +area_id = "area.tempest" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1664, 4544], + [1727, 4544], + [1727, 4607], + [1664, 4607], +] diff --git a/.data/raw-cache/map/area/temple.toml b/.data/raw-cache/map/area/temple.toml new file mode 100644 index 000000000..5c7fb0f2c --- /dev/null +++ b/.data/raw-cache/map/area/temple.toml @@ -0,0 +1,16 @@ +# Temple music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Temple. +# Bounds x 2752-2815, z 9152-9215; 4096 tiles as 1 rectangles. + +[[area]] +name = "Temple" +area_id = "area.temple" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 9152], + [2815, 9152], + [2815, 9215], + [2752, 9215], +] diff --git a/.data/raw-cache/map/area/temple_desecrated.toml b/.data/raw-cache/map/area/temple_desecrated.toml new file mode 100644 index 000000000..c448f60c6 --- /dev/null +++ b/.data/raw-cache/map/area/temple_desecrated.toml @@ -0,0 +1,16 @@ +# Temple Desecrated music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Temple Desecrated. +# Bounds x 4032-4159, z 4352-4479; 16384 tiles as 1 rectangles. + +[[area]] +name = "Temple Desecrated" +area_id = "area.temple_desecrated" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [4032, 4352], + [4159, 4352], + [4159, 4479], + [4032, 4479], +] diff --git a/.data/raw-cache/map/area/temple_of_light.toml b/.data/raw-cache/map/area/temple_of_light.toml new file mode 100644 index 000000000..9ed4cdddd --- /dev/null +++ b/.data/raw-cache/map/area/temple_of_light.toml @@ -0,0 +1,16 @@ +# Temple of Light music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Temple of Light. +# Bounds x 1856-1919, z 4608-4671; 4096 tiles as 1 rectangles. + +[[area]] +name = "Temple of Light" +area_id = "area.temple_of_light" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 4608], + [1919, 4608], + [1919, 4671], + [1856, 4671], +] diff --git a/.data/raw-cache/map/area/temple_of_the_eye.toml b/.data/raw-cache/map/area/temple_of_the_eye.toml new file mode 100644 index 000000000..e1abfe662 --- /dev/null +++ b/.data/raw-cache/map/area/temple_of_the_eye.toml @@ -0,0 +1,16 @@ +# Temple of the Eye music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Temple of the Eye. +# Bounds x 3520-3711, z 9408-9482; 14400 tiles as 1 rectangles. + +[[area]] +name = "Temple of the Eye" +area_id = "area.temple_of_the_eye" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3520, 9408], + [3711, 9408], + [3711, 9482], + [3520, 9482], +] diff --git a/.data/raw-cache/map/area/temple_of_tribes.toml b/.data/raw-cache/map/area/temple_of_tribes.toml new file mode 100644 index 000000000..9e8a2f477 --- /dev/null +++ b/.data/raw-cache/map/area/temple_of_tribes.toml @@ -0,0 +1,16 @@ +# Temple of Tribes music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Temple of Tribes. +# Bounds x 3712-3775, z 4288-4351; 4096 tiles as 1 rectangles. + +[[area]] +name = "Temple of Tribes" +area_id = "area.temple_of_tribes" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3712, 4288], + [3775, 4288], + [3775, 4351], + [3712, 4351], +] diff --git a/.data/raw-cache/map/area/tempor_of_the_storm.toml b/.data/raw-cache/map/area/tempor_of_the_storm.toml new file mode 100644 index 000000000..9ef785a4e --- /dev/null +++ b/.data/raw-cache/map/area/tempor_of_the_storm.toml @@ -0,0 +1,502 @@ +# Tempor of the Storm music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Tempor of the Storm. +# Bounds x 3010-3061, z 2820-2878; 1612 tiles as 78 rectangles. + +[[area]] +name = "Tempor of the Storm" +area_id = "area.tempor_of_the_storm" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3023, 2820], + [3024, 2820], + [3024, 2853], + [3023, 2853], +] + +[[area.polygons]] +vertices = [ + [3022, 2821], + [3022, 2874], +] + +[[area.polygons]] +vertices = [ + [3025, 2821], + [3028, 2821], + [3028, 2838], + [3025, 2838], +] + +[[area.polygons]] +vertices = [ + [3029, 2822], + [3033, 2822], + [3033, 2834], + [3029, 2834], +] + +[[area.polygons]] +vertices = [ + [3040, 2822], + [3041, 2822], + [3041, 2837], + [3040, 2837], +] + +[[area.polygons]] +vertices = [ + [3021, 2823], + [3021, 2873], +] + +[[area.polygons]] +vertices = [ + [3034, 2823], + [3039, 2823], + [3039, 2834], + [3034, 2834], +] + +[[area.polygons]] +vertices = [ + [3042, 2823], + [3042, 2838], +] + +[[area.polygons]] +vertices = [ + [3019, 2824], + [3020, 2824], + [3020, 2871], + [3019, 2871], +] + +[[area.polygons]] +vertices = [ + [3043, 2824], + [3044, 2824], + [3044, 2838], + [3043, 2838], +] + +[[area.polygons]] +vertices = [ + [3017, 2825], + [3018, 2825], + [3018, 2843], + [3017, 2843], +] + +[[area.polygons]] +vertices = [ + [3045, 2825], + [3045, 2838], +] + +[[area.polygons]] +vertices = [ + [3016, 2826], + [3016, 2842], +] + +[[area.polygons]] +vertices = [ + [3015, 2828], + [3015, 2842], +] + +[[area.polygons]] +vertices = [ + [3013, 2829], + [3014, 2829], + [3014, 2839], + [3013, 2839], +] + +[[area.polygons]] +vertices = [ + [3046, 2829], + [3046, 2842], +] + +[[area.polygons]] +vertices = [ + [3012, 2830], + [3012, 2836], +] + +[[area.polygons]] +vertices = [ + [3047, 2830], + [3049, 2830], + [3049, 2838], + [3047, 2838], +] + +[[area.polygons]] +vertices = [ + [3050, 2831], + [3050, 2840], +] + +[[area.polygons]] +vertices = [ + [3011, 2832], + [3011, 2835], +] + +[[area.polygons]] +vertices = [ + [3029, 2835], + [3031, 2835], +] + +[[area.polygons]] +vertices = [[3039, 2835]] + +[[area.polygons]] +vertices = [ + [3029, 2836], + [3029, 2837], +] + +[[area.polygons]] +vertices = [ + [3057, 2837], + [3058, 2837], + [3058, 2860], + [3057, 2860], +] + +[[area.polygons]] +vertices = [ + [3033, 2838], + [3039, 2838], + [3039, 2858], + [3033, 2858], +] + +[[area.polygons]] +vertices = [ + [3051, 2838], + [3051, 2841], +] + +[[area.polygons]] +vertices = [ + [3056, 2838], + [3056, 2860], +] + +[[area.polygons]] +vertices = [ + [3059, 2838], + [3059, 2860], +] + +[[area.polygons]] +vertices = [ + [3025, 2839], + [3027, 2839], + [3027, 2843], + [3025, 2843], +] + +[[area.polygons]] +vertices = [ + [3047, 2839], + [3048, 2839], + [3048, 2842], + [3047, 2842], +] + +[[area.polygons]] +vertices = [ + [3052, 2839], + [3052, 2842], +] + +[[area.polygons]] +vertices = [ + [3055, 2839], + [3055, 2860], +] + +[[area.polygons]] +vertices = [ + [3060, 2839], + [3060, 2860], +] + +[[area.polygons]] +vertices = [ + [3014, 2840], + [3014, 2841], +] + +[[area.polygons]] +vertices = [ + [3053, 2840], + [3053, 2843], +] + +[[area.polygons]] +vertices = [ + [3061, 2840], + [3061, 2860], +] + +[[area.polygons]] +vertices = [ + [3054, 2841], + [3054, 2844], +] + +[[area.polygons]] +vertices = [[3018, 2844]] + +[[area.polygons]] +vertices = [ + [3025, 2844], + [3026, 2844], + [3026, 2848], + [3025, 2848], +] + +[[area.polygons]] +vertices = [ + [3018, 2848], + [3018, 2871], +] + +[[area.polygons]] +vertices = [ + [3017, 2849], + [3017, 2871], +] + +[[area.polygons]] +vertices = [[3025, 2849]] + +[[area.polygons]] +vertices = [ + [3016, 2850], + [3016, 2870], +] + +[[area.polygons]] +vertices = [ + [3015, 2852], + [3015, 2868], +] + +[[area.polygons]] +vertices = [ + [3013, 2853], + [3014, 2853], + [3014, 2867], + [3013, 2867], +] + +[[area.polygons]] +vertices = [ + [3012, 2854], + [3012, 2866], +] + +[[area.polygons]] +vertices = [ + [3023, 2854], + [3023, 2874], +] + +[[area.polygons]] +vertices = [ + [3024, 2855], + [3024, 2875], +] + +[[area.polygons]] +vertices = [ + [3040, 2855], + [3040, 2858], +] + +[[area.polygons]] +vertices = [ + [3025, 2856], + [3025, 2875], +] + +[[area.polygons]] +vertices = [ + [3041, 2856], + [3041, 2859], +] + +[[area.polygons]] +vertices = [ + [3046, 2856], + [3048, 2856], + [3048, 2871], + [3046, 2871], +] + +[[area.polygons]] +vertices = [ + [3026, 2857], + [3026, 2859], +] + +[[area.polygons]] +vertices = [ + [3042, 2857], + [3042, 2860], +] + +[[area.polygons]] +vertices = [ + [3011, 2858], + [3011, 2864], +] + +[[area.polygons]] +vertices = [ + [3043, 2858], + [3043, 2861], +] + +[[area.polygons]] +vertices = [ + [3033, 2859], + [3038, 2859], +] + +[[area.polygons]] +vertices = [ + [3044, 2859], + [3044, 2876], +] + +[[area.polygons]] +vertices = [ + [3034, 2860], + [3037, 2860], +] + +[[area.polygons]] +vertices = [ + [3045, 2860], + [3045, 2875], +] + +[[area.polygons]] +vertices = [ + [3035, 2861], + [3036, 2861], +] + +[[area.polygons]] +vertices = [ + [3049, 2861], + [3049, 2870], +] + +[[area.polygons]] +vertices = [[3010, 2862]] + +[[area.polygons]] +vertices = [ + [3026, 2862], + [3026, 2876], +] + +[[area.polygons]] +vertices = [ + [3043, 2863], + [3043, 2876], +] + +[[area.polygons]] +vertices = [ + [3027, 2864], + [3028, 2864], + [3028, 2876], + [3027, 2876], +] + +[[area.polygons]] +vertices = [ + [3042, 2864], + [3042, 2876], +] + +[[area.polygons]] +vertices = [ + [3029, 2865], + [3029, 2876], +] + +[[area.polygons]] +vertices = [ + [3050, 2865], + [3050, 2869], +] + +[[area.polygons]] +vertices = [ + [3041, 2867], + [3041, 2876], +] + +[[area.polygons]] +vertices = [ + [3039, 2868], + [3040, 2868], + [3040, 2877], + [3039, 2877], +] + +[[area.polygons]] +vertices = [ + [3030, 2869], + [3030, 2876], +] + +[[area.polygons]] +vertices = [ + [3035, 2869], + [3038, 2869], + [3038, 2877], + [3035, 2877], +] + +[[area.polygons]] +vertices = [ + [3031, 2870], + [3034, 2870], + [3034, 2877], + [3031, 2877], +] + +[[area.polygons]] +vertices = [[3020, 2872]] + +[[area.polygons]] +vertices = [ + [3046, 2872], + [3047, 2872], + [3047, 2874], + [3046, 2874], +] + +[[area.polygons]] +vertices = [[3046, 2875]] + +[[area.polygons]] +vertices = [ + [3035, 2878], + [3037, 2878], +] diff --git a/.data/raw-cache/map/area/that_sullen_hall.toml b/.data/raw-cache/map/area/that_sullen_hall.toml new file mode 100644 index 000000000..41f429fba --- /dev/null +++ b/.data/raw-cache/map/area/that_sullen_hall.toml @@ -0,0 +1,16 @@ +# That Sullen Hall music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: That Sullen Hall. +# Bounds x 1280-1343, z 1216-1279; 4096 tiles as 1 rectangles. + +[[area]] +name = "That Sullen Hall" +area_id = "area.that_sullen_hall" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1280, 1216], + [1343, 1216], + [1343, 1279], + [1280, 1279], +] diff --git a/.data/raw-cache/map/area/the_ancient_prison.toml b/.data/raw-cache/map/area/the_ancient_prison.toml new file mode 100644 index 000000000..b9bd5f76e --- /dev/null +++ b/.data/raw-cache/map/area/the_ancient_prison.toml @@ -0,0 +1,16 @@ +# The Ancient Prison music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Ancient Prison. +# Bounds x 2816-2908, z 5184-5247; 5952 tiles as 1 rectangles. + +[[area]] +name = "The Ancient Prison" +area_id = "area.the_ancient_prison" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 5184], + [2908, 5184], + [2908, 5247], + [2816, 5247], +] diff --git a/.data/raw-cache/map/area/the_angels_fury.toml b/.data/raw-cache/map/area/the_angels_fury.toml new file mode 100644 index 000000000..4e9e51f6d --- /dev/null +++ b/.data/raw-cache/map/area/the_angels_fury.toml @@ -0,0 +1,16 @@ +# The Angel's Fury music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Angel's Fury. +# Bounds x 2909-2943, z 5184-5247; 2240 tiles as 1 rectangles. + +[[area]] +name = "The Angel's Fury" +area_id = "area.the_angels_fury" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2909, 5184], + [2943, 5184], + [2943, 5247], + [2909, 5247], +] diff --git a/.data/raw-cache/map/area/the_bane_of_ashihama.toml b/.data/raw-cache/map/area/the_bane_of_ashihama.toml new file mode 100644 index 000000000..0ecdec8ba --- /dev/null +++ b/.data/raw-cache/map/area/the_bane_of_ashihama.toml @@ -0,0 +1,16 @@ +# The Bane of Ashihama music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Bane of Ashihama. +# Bounds x 3840-3903, z 9920-9983; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Bane of Ashihama" +area_id = "area.the_bane_of_ashihama" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3840, 9920], + [3903, 9920], + [3903, 9983], + [3840, 9983], +] diff --git a/.data/raw-cache/map/area/the_chosen.toml b/.data/raw-cache/map/area/the_chosen.toml new file mode 100644 index 000000000..7069c5d2b --- /dev/null +++ b/.data/raw-cache/map/area/the_chosen.toml @@ -0,0 +1,16 @@ +# The Chosen music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Chosen. +# Bounds x 2432-2495, z 4928-4991; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Chosen" +area_id = "area.the_chosen" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2432, 4928], + [2495, 4928], + [2495, 4991], + [2432, 4991], +] diff --git a/.data/raw-cache/map/area/the_consortium.toml b/.data/raw-cache/map/area/the_consortium.toml new file mode 100644 index 000000000..d037b1e11 --- /dev/null +++ b/.data/raw-cache/map/area/the_consortium.toml @@ -0,0 +1,16 @@ +# The Consortium music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Consortium. +# Bounds x 1984-2047, z 4480-4543; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Consortium" +area_id = "area.the_consortium" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1984, 4480], + [2047, 4480], + [2047, 4543], + [1984, 4543], +] diff --git a/.data/raw-cache/map/area/the_curtain_closes.toml b/.data/raw-cache/map/area/the_curtain_closes.toml new file mode 100644 index 000000000..0e1b5291e --- /dev/null +++ b/.data/raw-cache/map/area/the_curtain_closes.toml @@ -0,0 +1,16 @@ +# The Curtain Closes music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Curtain Closes. +# Bounds x 3200-3263, z 4288-4351; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Curtain Closes" +area_id = "area.the_curtain_closes" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3200, 4288], + [3263, 4288], + [3263, 4351], + [3200, 4351], +] diff --git a/.data/raw-cache/map/area/the_depths.toml b/.data/raw-cache/map/area/the_depths.toml new file mode 100644 index 000000000..bc7ab0292 --- /dev/null +++ b/.data/raw-cache/map/area/the_depths.toml @@ -0,0 +1,16 @@ +# The Depths music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Depths. +# Bounds x 3200-3263, z 9216-9279; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Depths" +area_id = "area.the_depths" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3200, 9216], + [3263, 9216], + [3263, 9279], + [3200, 9279], +] diff --git a/.data/raw-cache/map/area/the_desolate_isle.toml b/.data/raw-cache/map/area/the_desolate_isle.toml new file mode 100644 index 000000000..75a390656 --- /dev/null +++ b/.data/raw-cache/map/area/the_desolate_isle.toml @@ -0,0 +1,902 @@ +# The Desolate Isle music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Desolate Isle. +# Bounds x 2240-2557, z 3713-4095; 4299 tiles as 141 rectangles. + +[[area]] +name = "The Desolate Isle" +area_id = "area.the_desolate_isle" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2524, 3713], + [2526, 3713], + [2526, 3730], + [2524, 3730], +] + +[[area.polygons]] +vertices = [ + [2521, 3714], + [2523, 3714], + [2523, 3727], + [2521, 3727], +] + +[[area.polygons]] +vertices = [ + [2527, 3714], + [2533, 3714], + [2533, 3769], + [2527, 3769], +] + +[[area.polygons]] +vertices = [ + [2516, 3715], + [2520, 3715], + [2520, 3727], + [2516, 3727], +] + +[[area.polygons]] +vertices = [ + [2534, 3715], + [2537, 3715], + [2537, 3768], + [2534, 3768], +] + +[[area.polygons]] +vertices = [ + [2514, 3716], + [2515, 3716], + [2515, 3727], + [2514, 3727], +] + +[[area.polygons]] +vertices = [ + [2538, 3716], + [2538, 3768], +] + +[[area.polygons]] +vertices = [ + [2510, 3717], + [2513, 3717], + [2513, 3727], + [2510, 3727], +] + +[[area.polygons]] +vertices = [ + [2539, 3717], + [2541, 3717], + [2541, 3767], + [2539, 3767], +] + +[[area.polygons]] +vertices = [ + [2509, 3718], + [2509, 3729], +] + +[[area.polygons]] +vertices = [ + [2542, 3718], + [2542, 3766], +] + +[[area.polygons]] +vertices = [ + [2508, 3719], + [2508, 3729], +] + +[[area.polygons]] +vertices = [ + [2543, 3719], + [2543, 3766], +] + +[[area.polygons]] +vertices = [ + [2506, 3720], + [2507, 3720], + [2507, 3730], + [2506, 3730], +] + +[[area.polygons]] +vertices = [ + [2544, 3720], + [2544, 3765], +] + +[[area.polygons]] +vertices = [ + [2505, 3721], + [2505, 3732], +] + +[[area.polygons]] +vertices = [ + [2545, 3721], + [2545, 3765], +] + +[[area.polygons]] +vertices = [ + [2504, 3722], + [2504, 3732], +] + +[[area.polygons]] +vertices = [ + [2546, 3722], + [2546, 3764], +] + +[[area.polygons]] +vertices = [ + [2503, 3723], + [2503, 3732], +] + +[[area.polygons]] +vertices = [ + [2547, 3723], + [2547, 3762], +] + +[[area.polygons]] +vertices = [ + [2502, 3724], + [2502, 3733], +] + +[[area.polygons]] +vertices = [ + [2548, 3724], + [2548, 3762], +] + +[[area.polygons]] +vertices = [ + [2501, 3726], + [2501, 3732], +] + +[[area.polygons]] +vertices = [ + [2549, 3726], + [2550, 3726], + [2550, 3761], + [2549, 3761], +] + +[[area.polygons]] +vertices = [ + [2551, 3727], + [2551, 3760], +] + +[[area.polygons]] +vertices = [ + [2510, 3728], + [2512, 3728], +] + +[[area.polygons]] +vertices = [ + [2515, 3728], + [2517, 3728], +] + +[[area.polygons]] +vertices = [ + [2522, 3728], + [2523, 3728], +] + +[[area.polygons]] +vertices = [ + [2500, 3729], + [2500, 3732], +] + +[[area.polygons]] +vertices = [[2523, 3729]] + +[[area.polygons]] +vertices = [ + [2552, 3729], + [2552, 3758], +] + +[[area.polygons]] +vertices = [[2506, 3731]] + +[[area.polygons]] +vertices = [ + [2525, 3731], + [2526, 3731], +] + +[[area.polygons]] +vertices = [ + [2553, 3731], + [2553, 3757], +] + +[[area.polygons]] +vertices = [[2526, 3732]] + +[[area.polygons]] +vertices = [ + [2554, 3732], + [2554, 3753], +] + +[[area.polygons]] +vertices = [ + [2526, 3734], + [2526, 3770], +] + +[[area.polygons]] +vertices = [ + [2555, 3734], + [2555, 3750], +] + +[[area.polygons]] +vertices = [ + [2525, 3735], + [2525, 3770], +] + +[[area.polygons]] +vertices = [ + [2524, 3736], + [2524, 3744], +] + +[[area.polygons]] +vertices = [ + [2556, 3736], + [2556, 3743], +] + +[[area.polygons]] +vertices = [ + [2523, 3737], + [2523, 3742], +] + +[[area.polygons]] +vertices = [ + [2557, 3737], + [2557, 3740], +] + +[[area.polygons]] +vertices = [ + [2522, 3738], + [2522, 3741], +] + +[[area.polygons]] +vertices = [ + [2520, 3739], + [2521, 3739], + [2521, 3740], + [2520, 3740], +] + +[[area.polygons]] +vertices = [ + [2524, 3747], + [2524, 3770], +] + +[[area.polygons]] +vertices = [ + [2523, 3748], + [2523, 3770], +] + +[[area.polygons]] +vertices = [ + [2510, 3749], + [2511, 3749], + [2511, 3765], + [2510, 3765], +] + +[[area.polygons]] +vertices = [ + [2522, 3749], + [2522, 3769], +] + +[[area.polygons]] +vertices = [ + [2501, 3750], + [2502, 3750], + [2502, 3756], + [2501, 3756], +] + +[[area.polygons]] +vertices = [ + [2512, 3750], + [2513, 3750], + [2513, 3766], + [2512, 3766], +] + +[[area.polygons]] +vertices = [ + [2521, 3750], + [2521, 3769], +] + +[[area.polygons]] +vertices = [ + [2500, 3751], + [2500, 3755], +] + +[[area.polygons]] +vertices = [ + [2503, 3751], + [2503, 3760], +] + +[[area.polygons]] +vertices = [ + [2514, 3751], + [2514, 3767], +] + +[[area.polygons]] +vertices = [ + [2520, 3751], + [2520, 3769], +] + +[[area.polygons]] +vertices = [ + [2504, 3752], + [2506, 3752], + [2506, 3761], + [2504, 3761], +] + +[[area.polygons]] +vertices = [ + [2515, 3752], + [2516, 3752], + [2516, 3768], + [2515, 3768], +] + +[[area.polygons]] +vertices = [ + [2519, 3752], + [2519, 3769], +] + +[[area.polygons]] +vertices = [ + [2507, 3753], + [2509, 3753], + [2509, 3763], + [2507, 3763], +] + +[[area.polygons]] +vertices = [ + [2517, 3753], + [2518, 3753], + [2518, 3769], + [2517, 3769], +] + +[[area.polygons]] +vertices = [ + [2502, 3757], + [2502, 3758], +] + +[[area.polygons]] +vertices = [ + [2505, 3762], + [2506, 3762], +] + +[[area.polygons]] +vertices = [[2506, 3763]] + +[[area.polygons]] +vertices = [ + [2508, 3764], + [2509, 3764], +] + +[[area.polygons]] +vertices = [[2511, 3766]] + +[[area.polygons]] +vertices = [[2513, 3767]] + +[[area.polygons]] +vertices = [ + [2539, 3768], + [2540, 3768], +] + +[[area.polygons]] +vertices = [ + [2534, 3769], + [2535, 3769], +] + +[[area.polygons]] +vertices = [ + [2528, 3770], + [2530, 3770], +] + +[[area.polygons]] +vertices = [ + [2276, 4034], + [2278, 4034], + [2278, 4053], + [2276, 4053], +] + +[[area.polygons]] +vertices = [ + [2245, 4035], + [2246, 4035], + [2246, 4042], + [2245, 4042], +] + +[[area.polygons]] +vertices = [ + [2275, 4035], + [2275, 4053], +] + +[[area.polygons]] +vertices = [ + [2279, 4035], + [2279, 4053], +] + +[[area.polygons]] +vertices = [ + [2247, 4036], + [2247, 4046], +] + +[[area.polygons]] +vertices = [ + [2271, 4036], + [2274, 4036], + [2274, 4053], + [2271, 4053], +] + +[[area.polygons]] +vertices = [ + [2280, 4036], + [2280, 4053], +] + +[[area.polygons]] +vertices = [ + [2269, 4037], + [2270, 4037], + [2270, 4053], + [2269, 4053], +] + +[[area.polygons]] +vertices = [ + [2281, 4037], + [2285, 4037], + [2285, 4053], + [2281, 4053], +] + +[[area.polygons]] +vertices = [ + [2248, 4038], + [2248, 4047], +] + +[[area.polygons]] +vertices = [ + [2266, 4038], + [2268, 4038], + [2268, 4053], + [2266, 4053], +] + +[[area.polygons]] +vertices = [ + [2286, 4038], + [2287, 4038], + [2287, 4089], + [2286, 4089], +] + +[[area.polygons]] +vertices = [ + [2249, 4039], + [2252, 4039], + [2252, 4048], + [2249, 4048], +] + +[[area.polygons]] +vertices = [ + [2262, 4039], + [2265, 4039], + [2265, 4053], + [2262, 4053], +] + +[[area.polygons]] +vertices = [ + [2288, 4039], + [2290, 4039], + [2290, 4089], + [2288, 4089], +] + +[[area.polygons]] +vertices = [ + [2253, 4040], + [2256, 4040], + [2256, 4090], + [2253, 4090], +] + +[[area.polygons]] +vertices = [ + [2261, 4040], + [2261, 4053], +] + +[[area.polygons]] +vertices = [ + [2291, 4040], + [2291, 4088], +] + +[[area.polygons]] +vertices = [ + [2257, 4041], + [2260, 4041], + [2260, 4093], + [2257, 4093], +] + +[[area.polygons]] +vertices = [ + [2292, 4041], + [2292, 4087], +] + +[[area.polygons]] +vertices = [ + [2293, 4042], + [2293, 4085], +] + +[[area.polygons]] +vertices = [ + [2246, 4043], + [2246, 4044], +] + +[[area.polygons]] +vertices = [ + [2294, 4043], + [2295, 4043], + [2295, 4055], + [2294, 4055], +] + +[[area.polygons]] +vertices = [ + [2296, 4045], + [2296, 4054], +] + +[[area.polygons]] +vertices = [ + [2297, 4048], + [2297, 4053], +] + +[[area.polygons]] +vertices = [ + [2250, 4049], + [2252, 4049], + [2252, 4050], + [2250, 4050], +] + +[[area.polygons]] +vertices = [ + [2298, 4050], + [2298, 4051], +] + +[[area.polygons]] +vertices = [ + [2251, 4051], + [2252, 4051], + [2252, 4093], + [2251, 4093], +] + +[[area.polygons]] +vertices = [ + [2283, 4054], + [2285, 4054], +] + +[[area.polygons]] +vertices = [ + [2284, 4055], + [2285, 4055], + [2285, 4088], + [2284, 4088], +] + +[[area.polygons]] +vertices = [ + [2250, 4056], + [2250, 4095], +] + +[[area.polygons]] +vertices = [ + [2294, 4056], + [2294, 4060], +] + +[[area.polygons]] +vertices = [ + [2249, 4059], + [2249, 4095], +] + +[[area.polygons]] +vertices = [ + [2248, 4062], + [2248, 4095], +] + +[[area.polygons]] +vertices = [ + [2247, 4066], + [2247, 4095], +] + +[[area.polygons]] +vertices = [ + [2294, 4066], + [2294, 4084], +] + +[[area.polygons]] +vertices = [ + [2246, 4067], + [2246, 4074], +] + +[[area.polygons]] +vertices = [ + [2295, 4067], + [2295, 4083], +] + +[[area.polygons]] +vertices = [ + [2245, 4068], + [2245, 4072], +] + +[[area.polygons]] +vertices = [ + [2244, 4069], + [2244, 4071], +] + +[[area.polygons]] +vertices = [ + [2296, 4070], + [2296, 4083], +] + +[[area.polygons]] +vertices = [ + [2297, 4071], + [2297, 4083], +] + +[[area.polygons]] +vertices = [ + [2283, 4076], + [2283, 4087], +] + +[[area.polygons]] +vertices = [ + [2246, 4077], + [2246, 4095], +] + +[[area.polygons]] +vertices = [ + [2261, 4077], + [2282, 4077], + [2282, 4084], + [2261, 4084], +] + +[[area.polygons]] +vertices = [ + [2298, 4077], + [2298, 4083], +] + +[[area.polygons]] +vertices = [ + [2244, 4078], + [2245, 4078], + [2245, 4095], + [2244, 4095], +] + +[[area.polygons]] +vertices = [ + [2243, 4079], + [2243, 4095], +] + +[[area.polygons]] +vertices = [ + [2299, 4079], + [2299, 4082], +] + +[[area.polygons]] +vertices = [ + [2242, 4080], + [2242, 4083], +] + +[[area.polygons]] +vertices = [[2300, 4080]] + +[[area.polygons]] +vertices = [ + [2242, 4085], + [2242, 4095], +] + +[[area.polygons]] +vertices = [ + [2261, 4085], + [2272, 4085], +] + +[[area.polygons]] +vertices = [ + [2275, 4085], + [2278, 4085], +] + +[[area.polygons]] +vertices = [ + [2281, 4085], + [2282, 4085], +] + +[[area.polygons]] +vertices = [ + [2241, 4086], + [2241, 4095], +] + +[[area.polygons]] +vertices = [ + [2261, 4086], + [2269, 4086], +] + +[[area.polygons]] +vertices = [[2282, 4086]] + +[[area.polygons]] +vertices = [ + [2240, 4087], + [2240, 4094], +] + +[[area.polygons]] +vertices = [ + [2261, 4087], + [2267, 4087], +] + +[[area.polygons]] +vertices = [ + [2261, 4088], + [2266, 4088], +] + +[[area.polygons]] +vertices = [ + [2261, 4089], + [2265, 4089], + [2265, 4090], + [2261, 4090], +] + +[[area.polygons]] +vertices = [ + [2287, 4090], + [2289, 4090], + [2289, 4091], + [2287, 4091], +] + +[[area.polygons]] +vertices = [[2253, 4091]] + +[[area.polygons]] +vertices = [ + [2261, 4091], + [2264, 4091], + [2264, 4092], + [2261, 4092], +] + +[[area.polygons]] +vertices = [ + [2286, 4092], + [2288, 4092], +] + +[[area.polygons]] +vertices = [ + [2261, 4093], + [2263, 4093], +] + +[[area.polygons]] +vertices = [ + [2286, 4093], + [2287, 4093], +] + +[[area.polygons]] +vertices = [[2251, 4094]] + +[[area.polygons]] +vertices = [ + [2258, 4094], + [2262, 4094], +] diff --git a/.data/raw-cache/map/area/the_desolate_mage.toml b/.data/raw-cache/map/area/the_desolate_mage.toml new file mode 100644 index 000000000..18c2a728d --- /dev/null +++ b/.data/raw-cache/map/area/the_desolate_mage.toml @@ -0,0 +1,174 @@ +# The Desolate Mage music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Desolate Mage. +# Bounds x 1219-1270, z 3545-3578; 1424 tiles as 27 rectangles. + +[[area]] +name = "The Desolate Mage" +area_id = "area.the_desolate_mage" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1228, 3545], + [1241, 3545], + [1241, 3578], + [1228, 3578], +] + +[[area.polygons]] +vertices = [ + [1227, 3546], + [1227, 3578], +] + +[[area.polygons]] +vertices = [ + [1242, 3546], + [1242, 3578], +] + +[[area.polygons]] +vertices = [ + [1226, 3547], + [1226, 3578], +] + +[[area.polygons]] +vertices = [ + [1243, 3547], + [1243, 3578], +] + +[[area.polygons]] +vertices = [ + [1225, 3548], + [1225, 3578], +] + +[[area.polygons]] +vertices = [ + [1244, 3548], + [1244, 3578], +] + +[[area.polygons]] +vertices = [ + [1224, 3549], + [1224, 3578], +] + +[[area.polygons]] +vertices = [ + [1245, 3549], + [1245, 3578], +] + +[[area.polygons]] +vertices = [ + [1223, 3550], + [1223, 3578], +] + +[[area.polygons]] +vertices = [ + [1246, 3550], + [1258, 3550], + [1258, 3578], + [1246, 3578], +] + +[[area.polygons]] +vertices = [ + [1222, 3551], + [1222, 3577], +] + +[[area.polygons]] +vertices = [ + [1259, 3551], + [1259, 3578], +] + +[[area.polygons]] +vertices = [ + [1221, 3552], + [1221, 3576], +] + +[[area.polygons]] +vertices = [ + [1260, 3552], + [1260, 3578], +] + +[[area.polygons]] +vertices = [ + [1220, 3553], + [1220, 3575], +] + +[[area.polygons]] +vertices = [ + [1261, 3553], + [1261, 3578], +] + +[[area.polygons]] +vertices = [ + [1219, 3554], + [1219, 3574], +] + +[[area.polygons]] +vertices = [ + [1262, 3554], + [1262, 3578], +] + +[[area.polygons]] +vertices = [ + [1263, 3555], + [1263, 3578], +] + +[[area.polygons]] +vertices = [ + [1264, 3556], + [1264, 3578], +] + +[[area.polygons]] +vertices = [ + [1265, 3557], + [1265, 3570], +] + +[[area.polygons]] +vertices = [ + [1266, 3563], + [1266, 3570], +] + +[[area.polygons]] +vertices = [ + [1267, 3564], + [1267, 3570], +] + +[[area.polygons]] +vertices = [ + [1268, 3565], + [1268, 3570], +] + +[[area.polygons]] +vertices = [ + [1269, 3566], + [1269, 3569], +] + +[[area.polygons]] +vertices = [ + [1270, 3567], + [1270, 3568], +] diff --git a/.data/raw-cache/map/area/the_doom.toml b/.data/raw-cache/map/area/the_doom.toml new file mode 100644 index 000000000..5025c79ba --- /dev/null +++ b/.data/raw-cache/map/area/the_doom.toml @@ -0,0 +1,24 @@ +# The Doom music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Doom. +# Bounds x 1280-3583, z 6400-9599; 14912 tiles as 2 rectangles. + +[[area]] +name = "The Doom" +area_id = "area.the_doom" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3392, 6400], + [3583, 6400], + [3583, 6463], + [3392, 6463], +] + +[[area.polygons]] +vertices = [ + [1280, 9559], + [1343, 9559], + [1343, 9599], + [1280, 9599], +] diff --git a/.data/raw-cache/map/area/the_doors_of_dinh.toml b/.data/raw-cache/map/area/the_doors_of_dinh.toml new file mode 100644 index 000000000..e651bd8d7 --- /dev/null +++ b/.data/raw-cache/map/area/the_doors_of_dinh.toml @@ -0,0 +1,222 @@ +# The Doors of Dinh music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Doors of Dinh. +# Bounds x 1472-1727, z 3860-3967; 19686 tiles as 31 rectangles. + +[[area]] +name = "The Doors of Dinh" +area_id = "area.the_doors_of_dinh" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1528, 3860], + [1594, 3860], + [1594, 3967], + [1528, 3967], +] + +[[area.polygons]] +vertices = [ + [1595, 3876], + [1599, 3876], + [1599, 3967], + [1595, 3967], +] + +[[area.polygons]] +vertices = [ + [1600, 3891], + [1619, 3891], + [1619, 3967], + [1600, 3967], +] + +[[area.polygons]] +vertices = [ + [1620, 3892], + [1620, 3967], +] + +[[area.polygons]] +vertices = [ + [1621, 3893], + [1621, 3967], +] + +[[area.polygons]] +vertices = [ + [1622, 3894], + [1622, 3967], +] + +[[area.polygons]] +vertices = [ + [1623, 3895], + [1623, 3967], +] + +[[area.polygons]] +vertices = [ + [1624, 3896], + [1624, 3967], +] + +[[area.polygons]] +vertices = [ + [1625, 3897], + [1625, 3967], +] + +[[area.polygons]] +vertices = [ + [1626, 3898], + [1626, 3967], +] + +[[area.polygons]] +vertices = [ + [1627, 3899], + [1627, 3967], +] + +[[area.polygons]] +vertices = [ + [1628, 3900], + [1628, 3967], +] + +[[area.polygons]] +vertices = [ + [1629, 3901], + [1629, 3967], +] + +[[area.polygons]] +vertices = [ + [1630, 3902], + [1630, 3967], +] + +[[area.polygons]] +vertices = [ + [1631, 3903], + [1631, 3967], +] + +[[area.polygons]] +vertices = [ + [1472, 3904], + [1527, 3904], + [1527, 3967], + [1472, 3967], +] + +[[area.polygons]] +vertices = [ + [1632, 3904], + [1727, 3904], + [1727, 3956], + [1632, 3956], +] + +[[area.polygons]] +vertices = [ + [1632, 3957], + [1709, 3957], + [1709, 3960], + [1632, 3960], +] + +[[area.polygons]] +vertices = [ + [1711, 3957], + [1712, 3957], + [1712, 3965], + [1711, 3965], +] + +[[area.polygons]] +vertices = [ + [1714, 3957], + [1727, 3957], + [1727, 3958], + [1714, 3958], +] + +[[area.polygons]] +vertices = [ + [1715, 3959], + [1727, 3959], + [1727, 3961], + [1715, 3961], +] + +[[area.polygons]] +vertices = [ + [1632, 3961], + [1699, 3961], +] + +[[area.polygons]] +vertices = [ + [1703, 3961], + [1708, 3961], +] + +[[area.polygons]] +vertices = [ + [1632, 3962], + [1698, 3962], + [1698, 3967], + [1632, 3967], +] + +[[area.polygons]] +vertices = [ + [1716, 3962], + [1727, 3962], + [1727, 3963], + [1716, 3963], +] + +[[area.polygons]] +vertices = [ + [1717, 3964], + [1718, 3964], +] + +[[area.polygons]] +vertices = [ + [1721, 3964], + [1727, 3964], +] + +[[area.polygons]] +vertices = [ + [1699, 3965], + [1700, 3965], + [1700, 3967], + [1699, 3967], +] + +[[area.polygons]] +vertices = [ + [1722, 3965], + [1727, 3965], +] + +[[area.polygons]] +vertices = [ + [1701, 3966], + [1702, 3966], + [1702, 3967], + [1701, 3967], +] + +[[area.polygons]] +vertices = [ + [1723, 3966], + [1727, 3966], + [1727, 3967], + [1723, 3967], +] diff --git a/.data/raw-cache/map/area/the_enchanter.toml b/.data/raw-cache/map/area/the_enchanter.toml new file mode 100644 index 000000000..c76e85f55 --- /dev/null +++ b/.data/raw-cache/map/area/the_enchanter.toml @@ -0,0 +1,16 @@ +# The Enchanter music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Enchanter (plus roll_the_bones, golden_touch from wiki polygons sharing this footprint). +# Bounds x 3328-3391, z 9600-9663; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Enchanter" +area_id = "area.the_enchanter" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3328, 9600], + [3391, 9600], + [3391, 9663], + [3328, 9663], +] diff --git a/.data/raw-cache/map/area/the_enclave.toml b/.data/raw-cache/map/area/the_enclave.toml new file mode 100644 index 000000000..7a18c03c6 --- /dev/null +++ b/.data/raw-cache/map/area/the_enclave.toml @@ -0,0 +1,121 @@ +# The Enclave music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Enclave. +# Bounds x 3123-3199, z 3617-10047; 4880 tiles as 16 rectangles. + +[[area]] +name = "The Enclave" +area_id = "area.the_enclave" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3130, 3617], + [3138, 3617], + [3138, 3639], + [3130, 3639], +] + +[[area.polygons]] +vertices = [ + [3126, 3618], + [3129, 3618], + [3129, 3639], + [3126, 3639], +] + +[[area.polygons]] +vertices = [ + [3139, 3618], + [3143, 3618], + [3143, 3645], + [3139, 3645], +] + +[[area.polygons]] +vertices = [ + [3144, 3620], + [3150, 3620], + [3150, 3644], + [3144, 3644], +] + +[[area.polygons]] +vertices = [ + [3123, 3622], + [3125, 3622], + [3125, 3632], + [3123, 3632], +] + +[[area.polygons]] +vertices = [ + [3151, 3624], + [3151, 3646], +] + +[[area.polygons]] +vertices = [ + [3152, 3626], + [3152, 3646], +] + +[[area.polygons]] +vertices = [ + [3153, 3628], + [3157, 3628], + [3157, 3633], + [3153, 3633], +] + +[[area.polygons]] +vertices = [ + [3125, 3633], + [3125, 3639], +] + +[[area.polygons]] +vertices = [ + [3153, 3634], + [3154, 3634], + [3154, 3646], + [3153, 3646], +] + +[[area.polygons]] +vertices = [ + [3155, 3636], + [3157, 3636], + [3157, 3640], + [3155, 3640], +] + +[[area.polygons]] +vertices = [ + [3138, 3640], + [3138, 3645], +] + +[[area.polygons]] +vertices = [ + [3155, 3641], + [3155, 3646], +] + +[[area.polygons]] +vertices = [[3144, 3645]] + +[[area.polygons]] +vertices = [ + [3148, 3645], + [3150, 3645], + [3150, 3646], + [3148, 3646], +] + +[[area.polygons]] +vertices = [ + [3136, 9984], + [3199, 9984], + [3199, 10047], + [3136, 10047], +] diff --git a/.data/raw-cache/map/area/the_everlasting_slumber.toml b/.data/raw-cache/map/area/the_everlasting_slumber.toml new file mode 100644 index 000000000..53b5492a1 --- /dev/null +++ b/.data/raw-cache/map/area/the_everlasting_slumber.toml @@ -0,0 +1,16 @@ +# The Everlasting Slumber music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Everlasting Slumber. +# Bounds x 3712-3903, z 9664-9855; 36864 tiles as 1 rectangles. + +[[area]] +name = "The Everlasting Slumber" +area_id = "area.the_everlasting_slumber" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3712, 9664], + [3903, 9664], + [3903, 9855], + [3712, 9855], +] diff --git a/.data/raw-cache/map/area/the_fallen_empire.toml b/.data/raw-cache/map/area/the_fallen_empire.toml new file mode 100644 index 000000000..fdc51a0fe --- /dev/null +++ b/.data/raw-cache/map/area/the_fallen_empire.toml @@ -0,0 +1,16 @@ +# The Fallen Empire music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Fallen Empire. +# Bounds x 2560-2751, z 6272-6463; 36864 tiles as 1 rectangles. + +[[area]] +name = "The Fallen Empire" +area_id = "area.the_fallen_empire" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 6272], + [2751, 6272], + [2751, 6463], + [2560, 6463], +] diff --git a/.data/raw-cache/map/area/the_far_side.toml b/.data/raw-cache/map/area/the_far_side.toml new file mode 100644 index 000000000..28730ca59 --- /dev/null +++ b/.data/raw-cache/map/area/the_far_side.toml @@ -0,0 +1,16 @@ +# The Far Side music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Far Side. +# Bounds x 3008-3071, z 5056-5119; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Far Side" +area_id = "area.the_far_side" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 5056], + [3071, 5056], + [3071, 5119], + [3008, 5119], +] diff --git a/.data/raw-cache/map/area/the_feathered_serpent.toml b/.data/raw-cache/map/area/the_feathered_serpent.toml new file mode 100644 index 000000000..c2709bb8b --- /dev/null +++ b/.data/raw-cache/map/area/the_feathered_serpent.toml @@ -0,0 +1,192 @@ +# The Feathered Serpent music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Feathered Serpent. +# Bounds x 1501-1535, z 3266-3298; 785 tiles as 30 rectangles. + +[[area]] +name = "The Feathered Serpent" +area_id = "area.the_feathered_serpent" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1512, 3266], + [1527, 3266], + [1527, 3285], + [1512, 3285], +] + +[[area.polygons]] +vertices = [ + [1511, 3267], + [1511, 3298], +] + +[[area.polygons]] +vertices = [ + [1528, 3267], + [1528, 3285], +] + +[[area.polygons]] +vertices = [ + [1510, 3268], + [1510, 3298], +] + +[[area.polygons]] +vertices = [ + [1529, 3268], + [1529, 3285], +] + +[[area.polygons]] +vertices = [ + [1509, 3269], + [1509, 3297], +] + +[[area.polygons]] +vertices = [ + [1530, 3269], + [1530, 3285], +] + +[[area.polygons]] +vertices = [ + [1508, 3270], + [1508, 3296], +] + +[[area.polygons]] +vertices = [ + [1531, 3270], + [1531, 3285], +] + +[[area.polygons]] +vertices = [ + [1507, 3271], + [1507, 3295], +] + +[[area.polygons]] +vertices = [ + [1532, 3271], + [1532, 3285], +] + +[[area.polygons]] +vertices = [ + [1506, 3272], + [1506, 3294], +] + +[[area.polygons]] +vertices = [ + [1533, 3272], + [1533, 3285], +] + +[[area.polygons]] +vertices = [ + [1505, 3273], + [1505, 3293], +] + +[[area.polygons]] +vertices = [ + [1534, 3273], + [1534, 3285], +] + +[[area.polygons]] +vertices = [ + [1504, 3274], + [1504, 3292], +] + +[[area.polygons]] +vertices = [ + [1535, 3274], + [1535, 3285], +] + +[[area.polygons]] +vertices = [ + [1503, 3275], + [1503, 3291], +] + +[[area.polygons]] +vertices = [ + [1502, 3276], + [1502, 3290], +] + +[[area.polygons]] +vertices = [ + [1501, 3277], + [1501, 3289], +] + +[[area.polygons]] +vertices = [ + [1512, 3286], + [1522, 3286], +] + +[[area.polygons]] +vertices = [ + [1512, 3287], + [1521, 3287], +] + +[[area.polygons]] +vertices = [ + [1512, 3288], + [1520, 3288], +] + +[[area.polygons]] +vertices = [ + [1512, 3289], + [1519, 3289], + [1519, 3292], + [1512, 3292], +] + +[[area.polygons]] +vertices = [ + [1512, 3293], + [1518, 3293], +] + +[[area.polygons]] +vertices = [ + [1512, 3294], + [1517, 3294], +] + +[[area.polygons]] +vertices = [ + [1512, 3295], + [1516, 3295], +] + +[[area.polygons]] +vertices = [ + [1512, 3296], + [1515, 3296], +] + +[[area.polygons]] +vertices = [ + [1512, 3297], + [1514, 3297], +] + +[[area.polygons]] +vertices = [ + [1512, 3298], + [1513, 3298], +] diff --git a/.data/raw-cache/map/area/the_fires_of_lletya.toml b/.data/raw-cache/map/area/the_fires_of_lletya.toml new file mode 100644 index 000000000..7966ac2a4 --- /dev/null +++ b/.data/raw-cache/map/area/the_fires_of_lletya.toml @@ -0,0 +1,16 @@ +# The Fires of Lletya music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Fires of Lletya. +# Bounds x 2880-2943, z 6144-6207; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Fires of Lletya" +area_id = "area.the_fires_of_lletya" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2880, 6144], + [2943, 6144], + [2943, 6207], + [2880, 6207], +] diff --git a/.data/raw-cache/map/area/the_forests_of_shayzien.toml b/.data/raw-cache/map/area/the_forests_of_shayzien.toml new file mode 100644 index 000000000..7dfda7b7b --- /dev/null +++ b/.data/raw-cache/map/area/the_forests_of_shayzien.toml @@ -0,0 +1,1723 @@ +# The Forests of Shayzien music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Forests of Shayzien. +# Bounds x 1387-1606, z 3466-3735; 20994 tiles as 276 rectangles. + +[[area]] +name = "The Forests of Shayzien" +area_id = "area.the_forests_of_shayzien" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1395, 3466], + [1405, 3466], + [1405, 3487], + [1395, 3487], +] + +[[area.polygons]] +vertices = [ + [1393, 3467], + [1394, 3467], + [1394, 3485], + [1393, 3485], +] + +[[area.polygons]] +vertices = [ + [1406, 3467], + [1406, 3498], +] + +[[area.polygons]] +vertices = [ + [1391, 3468], + [1392, 3468], + [1392, 3483], + [1391, 3483], +] + +[[area.polygons]] +vertices = [ + [1407, 3468], + [1407, 3499], +] + +[[area.polygons]] +vertices = [ + [1390, 3469], + [1390, 3482], +] + +[[area.polygons]] +vertices = [ + [1408, 3469], + [1408, 3500], +] + +[[area.polygons]] +vertices = [ + [1389, 3470], + [1389, 3481], +] + +[[area.polygons]] +vertices = [ + [1409, 3470], + [1409, 3501], +] + +[[area.polygons]] +vertices = [ + [1388, 3471], + [1388, 3480], +] + +[[area.polygons]] +vertices = [ + [1410, 3471], + [1410, 3502], +] + +[[area.polygons]] +vertices = [ + [1411, 3472], + [1411, 3503], +] + +[[area.polygons]] +vertices = [ + [1412, 3473], + [1413, 3473], + [1413, 3504], + [1412, 3504], +] + +[[area.polygons]] +vertices = [ + [1414, 3474], + [1415, 3474], + [1415, 3506], + [1414, 3506], +] + +[[area.polygons]] +vertices = [ + [1416, 3475], + [1416, 3687], +] + +[[area.polygons]] +vertices = [ + [1417, 3476], + [1417, 3687], +] + +[[area.polygons]] +vertices = [ + [1418, 3477], + [1418, 3687], +] + +[[area.polygons]] +vertices = [ + [1419, 3478], + [1419, 3687], +] + +[[area.polygons]] +vertices = [ + [1467, 3478], + [1475, 3478], + [1475, 3499], + [1467, 3499], +] + +[[area.polygons]] +vertices = [ + [1420, 3479], + [1420, 3687], +] + +[[area.polygons]] +vertices = [ + [1465, 3479], + [1466, 3479], + [1466, 3545], + [1465, 3545], +] + +[[area.polygons]] +vertices = [ + [1457, 3480], + [1464, 3480], + [1464, 3488], + [1457, 3488], +] + +[[area.polygons]] +vertices = [ + [1421, 3481], + [1421, 3687], +] + +[[area.polygons]] +vertices = [ + [1456, 3481], + [1456, 3487], +] + +[[area.polygons]] +vertices = [ + [1422, 3482], + [1422, 3687], +] + +[[area.polygons]] +vertices = [ + [1455, 3482], + [1455, 3485], +] + +[[area.polygons]] +vertices = [ + [1423, 3483], + [1423, 3687], +] + +[[area.polygons]] +vertices = [ + [1476, 3483], + [1476, 3498], +] + +[[area.polygons]] +vertices = [[1392, 3484]] + +[[area.polygons]] +vertices = [ + [1448, 3484], + [1449, 3484], + [1449, 3687], + [1448, 3687], +] + +[[area.polygons]] +vertices = [ + [1477, 3484], + [1478, 3484], + [1478, 3496], + [1477, 3496], +] + +[[area.polygons]] +vertices = [ + [1424, 3485], + [1424, 3687], +] + +[[area.polygons]] +vertices = [ + [1447, 3485], + [1447, 3687], +] + +[[area.polygons]] +vertices = [ + [1450, 3485], + [1450, 3687], +] + +[[area.polygons]] +vertices = [ + [1479, 3485], + [1480, 3485], + [1480, 3495], + [1479, 3495], +] + +[[area.polygons]] +vertices = [[1394, 3486]] + +[[area.polygons]] +vertices = [ + [1425, 3486], + [1425, 3687], +] + +[[area.polygons]] +vertices = [ + [1445, 3486], + [1446, 3486], + [1446, 3687], + [1445, 3687], +] + +[[area.polygons]] +vertices = [ + [1451, 3486], + [1451, 3687], +] + +[[area.polygons]] +vertices = [ + [1481, 3486], + [1481, 3494], +] + +[[area.polygons]] +vertices = [ + [1444, 3487], + [1444, 3687], +] + +[[area.polygons]] +vertices = [ + [1396, 3488], + [1405, 3488], +] + +[[area.polygons]] +vertices = [ + [1426, 3488], + [1426, 3687], +] + +[[area.polygons]] +vertices = [ + [1397, 3489], + [1405, 3489], +] + +[[area.polygons]] +vertices = [ + [1452, 3489], + [1452, 3687], +] + +[[area.polygons]] +vertices = [ + [1460, 3489], + [1464, 3489], + [1464, 3548], + [1460, 3548], +] + +[[area.polygons]] +vertices = [ + [1398, 3490], + [1405, 3490], +] + +[[area.polygons]] +vertices = [ + [1427, 3490], + [1427, 3687], +] + +[[area.polygons]] +vertices = [ + [1453, 3490], + [1453, 3687], +] + +[[area.polygons]] +vertices = [ + [1459, 3490], + [1459, 3568], +] + +[[area.polygons]] +vertices = [ + [1399, 3491], + [1405, 3491], +] + +[[area.polygons]] +vertices = [ + [1428, 3491], + [1428, 3687], +] + +[[area.polygons]] +vertices = [ + [1454, 3491], + [1458, 3491], + [1458, 3687], + [1454, 3687], +] + +[[area.polygons]] +vertices = [ + [1400, 3492], + [1405, 3492], +] + +[[area.polygons]] +vertices = [ + [1401, 3493], + [1405, 3493], +] + +[[area.polygons]] +vertices = [ + [1402, 3494], + [1405, 3494], +] + +[[area.polygons]] +vertices = [ + [1429, 3494], + [1429, 3687], +] + +[[area.polygons]] +vertices = [ + [1403, 3495], + [1405, 3495], +] + +[[area.polygons]] +vertices = [ + [1437, 3495], + [1438, 3495], + [1438, 3687], + [1437, 3687], +] + +[[area.polygons]] +vertices = [ + [1443, 3495], + [1443, 3687], +] + +[[area.polygons]] +vertices = [ + [1404, 3496], + [1405, 3496], +] + +[[area.polygons]] +vertices = [[1479, 3496]] + +[[area.polygons]] +vertices = [[1405, 3497]] + +[[area.polygons]] +vertices = [ + [1430, 3497], + [1430, 3687], +] + +[[area.polygons]] +vertices = [[1477, 3497]] + +[[area.polygons]] +vertices = [ + [1442, 3498], + [1442, 3687], +] + +[[area.polygons]] +vertices = [ + [1431, 3499], + [1431, 3687], +] + +[[area.polygons]] +vertices = [ + [1432, 3500], + [1432, 3687], +] + +[[area.polygons]] +vertices = [ + [1467, 3500], + [1474, 3500], + [1474, 3539], + [1467, 3539], +] + +[[area.polygons]] +vertices = [ + [1433, 3501], + [1433, 3687], +] + +[[area.polygons]] +vertices = [ + [1441, 3502], + [1441, 3687], +] + +[[area.polygons]] +vertices = [ + [1434, 3503], + [1435, 3503], + [1435, 3687], + [1434, 3687], +] + +[[area.polygons]] +vertices = [ + [1440, 3503], + [1440, 3687], +] + +[[area.polygons]] +vertices = [ + [1436, 3504], + [1436, 3687], +] + +[[area.polygons]] +vertices = [ + [1439, 3504], + [1439, 3687], +] + +[[area.polygons]] +vertices = [[1413, 3505]] + +[[area.polygons]] +vertices = [ + [1415, 3507], + [1415, 3687], +] + +[[area.polygons]] +vertices = [ + [1475, 3512], + [1475, 3539], +] + +[[area.polygons]] +vertices = [ + [1476, 3513], + [1476, 3539], +] + +[[area.polygons]] +vertices = [ + [1477, 3514], + [1477, 3539], +] + +[[area.polygons]] +vertices = [ + [1490, 3514], + [1497, 3514], + [1497, 3535], + [1490, 3535], +] + +[[area.polygons]] +vertices = [ + [1478, 3515], + [1478, 3539], +] + +[[area.polygons]] +vertices = [ + [1488, 3515], + [1489, 3515], + [1489, 3539], + [1488, 3539], +] + +[[area.polygons]] +vertices = [ + [1498, 3515], + [1498, 3535], +] + +[[area.polygons]] +vertices = [ + [1479, 3516], + [1479, 3539], +] + +[[area.polygons]] +vertices = [ + [1486, 3516], + [1487, 3516], + [1487, 3539], + [1486, 3539], +] + +[[area.polygons]] +vertices = [ + [1499, 3516], + [1499, 3535], +] + +[[area.polygons]] +vertices = [ + [1480, 3517], + [1485, 3517], + [1485, 3539], + [1480, 3539], +] + +[[area.polygons]] +vertices = [ + [1500, 3517], + [1500, 3535], +] + +[[area.polygons]] +vertices = [ + [1501, 3518], + [1501, 3535], +] + +[[area.polygons]] +vertices = [ + [1502, 3519], + [1502, 3535], +] + +[[area.polygons]] +vertices = [ + [1503, 3520], + [1503, 3535], +] + +[[area.polygons]] +vertices = [ + [1414, 3523], + [1414, 3546], +] + +[[area.polygons]] +vertices = [ + [1504, 3523], + [1504, 3535], +] + +[[area.polygons]] +vertices = [ + [1413, 3524], + [1413, 3545], +] + +[[area.polygons]] +vertices = [ + [1412, 3525], + [1412, 3544], +] + +[[area.polygons]] +vertices = [ + [1505, 3525], + [1505, 3535], +] + +[[area.polygons]] +vertices = [ + [1506, 3527], + [1506, 3535], +] + +[[area.polygons]] +vertices = [ + [1490, 3536], + [1492, 3536], +] + +[[area.polygons]] +vertices = [ + [1490, 3537], + [1491, 3537], +] + +[[area.polygons]] +vertices = [[1490, 3538]] + +[[area.polygons]] +vertices = [ + [1467, 3540], + [1469, 3540], +] + +[[area.polygons]] +vertices = [ + [1467, 3541], + [1468, 3541], +] + +[[area.polygons]] +vertices = [ + [1467, 3542], + [1467, 3543], +] + +[[area.polygons]] +vertices = [[1465, 3546]] + +[[area.polygons]] +vertices = [ + [1460, 3549], + [1463, 3549], + [1463, 3550], + [1460, 3550], +] + +[[area.polygons]] +vertices = [ + [1460, 3551], + [1462, 3551], +] + +[[area.polygons]] +vertices = [ + [1460, 3552], + [1461, 3552], + [1461, 3563], + [1460, 3563], +] + +[[area.polygons]] +vertices = [ + [1414, 3558], + [1414, 3687], +] + +[[area.polygons]] +vertices = [ + [1413, 3559], + [1413, 3687], +] + +[[area.polygons]] +vertices = [ + [1412, 3560], + [1412, 3687], +] + +[[area.polygons]] +vertices = [ + [1411, 3561], + [1411, 3687], +] + +[[area.polygons]] +vertices = [ + [1410, 3562], + [1410, 3687], +] + +[[area.polygons]] +vertices = [ + [1409, 3563], + [1409, 3686], +] + +[[area.polygons]] +vertices = [ + [1408, 3564], + [1408, 3685], +] + +[[area.polygons]] +vertices = [ + [1460, 3564], + [1460, 3565], +] + +[[area.polygons]] +vertices = [ + [1407, 3565], + [1407, 3684], +] + +[[area.polygons]] +vertices = [ + [1406, 3566], + [1406, 3683], +] + +[[area.polygons]] +vertices = [ + [1405, 3567], + [1405, 3682], +] + +[[area.polygons]] +vertices = [ + [1404, 3568], + [1404, 3681], +] + +[[area.polygons]] +vertices = [ + [1403, 3569], + [1403, 3680], +] + +[[area.polygons]] +vertices = [ + [1402, 3570], + [1402, 3679], +] + +[[area.polygons]] +vertices = [ + [1401, 3571], + [1401, 3677], +] + +[[area.polygons]] +vertices = [ + [1459, 3571], + [1459, 3701], +] + +[[area.polygons]] +vertices = [ + [1400, 3572], + [1400, 3676], +] + +[[area.polygons]] +vertices = [ + [1460, 3572], + [1460, 3702], +] + +[[area.polygons]] +vertices = [ + [1399, 3573], + [1399, 3675], +] + +[[area.polygons]] +vertices = [ + [1461, 3573], + [1461, 3724], +] + +[[area.polygons]] +vertices = [ + [1398, 3574], + [1398, 3674], +] + +[[area.polygons]] +vertices = [ + [1462, 3574], + [1462, 3730], +] + +[[area.polygons]] +vertices = [ + [1397, 3575], + [1397, 3673], +] + +[[area.polygons]] +vertices = [ + [1463, 3575], + [1463, 3731], +] + +[[area.polygons]] +vertices = [ + [1396, 3576], + [1396, 3672], +] + +[[area.polygons]] +vertices = [ + [1395, 3577], + [1395, 3671], +] + +[[area.polygons]] +vertices = [ + [1548, 3577], + [1568, 3577], + [1568, 3616], + [1548, 3616], +] + +[[area.polygons]] +vertices = [ + [1394, 3578], + [1394, 3671], +] + +[[area.polygons]] +vertices = [ + [1464, 3578], + [1464, 3731], +] + +[[area.polygons]] +vertices = [ + [1547, 3578], + [1547, 3616], +] + +[[area.polygons]] +vertices = [ + [1569, 3578], + [1569, 3646], +] + +[[area.polygons]] +vertices = [ + [1393, 3579], + [1393, 3670], +] + +[[area.polygons]] +vertices = [ + [1546, 3579], + [1546, 3614], +] + +[[area.polygons]] +vertices = [ + [1570, 3579], + [1571, 3579], + [1571, 3646], + [1570, 3646], +] + +[[area.polygons]] +vertices = [ + [1392, 3580], + [1392, 3669], +] + +[[area.polygons]] +vertices = [ + [1572, 3580], + [1572, 3646], +] + +[[area.polygons]] +vertices = [ + [1391, 3581], + [1391, 3668], +] + +[[area.polygons]] +vertices = [ + [1545, 3581], + [1545, 3614], +] + +[[area.polygons]] +vertices = [ + [1573, 3581], + [1574, 3581], + [1574, 3646], + [1573, 3646], +] + +[[area.polygons]] +vertices = [ + [1390, 3582], + [1390, 3667], +] + +[[area.polygons]] +vertices = [ + [1544, 3582], + [1544, 3614], +] + +[[area.polygons]] +vertices = [ + [1575, 3582], + [1575, 3646], +] + +[[area.polygons]] +vertices = [ + [1389, 3583], + [1389, 3666], +] + +[[area.polygons]] +vertices = [ + [1465, 3583], + [1465, 3735], +] + +[[area.polygons]] +vertices = [ + [1576, 3583], + [1582, 3583], + [1582, 3647], + [1576, 3647], +] + +[[area.polygons]] +vertices = [ + [1388, 3584], + [1388, 3665], +] + +[[area.polygons]] +vertices = [ + [1543, 3584], + [1543, 3614], +] + +[[area.polygons]] +vertices = [ + [1583, 3584], + [1585, 3584], + [1585, 3654], + [1583, 3654], +] + +[[area.polygons]] +vertices = [ + [1387, 3585], + [1387, 3664], +] + +[[area.polygons]] +vertices = [ + [1542, 3585], + [1542, 3614], +] + +[[area.polygons]] +vertices = [ + [1586, 3585], + [1588, 3585], + [1588, 3656], + [1586, 3656], +] + +[[area.polygons]] +vertices = [ + [1589, 3586], + [1591, 3586], + [1591, 3605], + [1589, 3605], +] + +[[area.polygons]] +vertices = [ + [1541, 3587], + [1541, 3614], +] + +[[area.polygons]] +vertices = [ + [1592, 3587], + [1593, 3587], + [1593, 3604], + [1592, 3604], +] + +[[area.polygons]] +vertices = [ + [1466, 3588], + [1466, 3735], +] + +[[area.polygons]] +vertices = [ + [1540, 3588], + [1540, 3616], +] + +[[area.polygons]] +vertices = [ + [1594, 3588], + [1594, 3604], +] + +[[area.polygons]] +vertices = [ + [1467, 3590], + [1467, 3735], +] + +[[area.polygons]] +vertices = [ + [1539, 3590], + [1539, 3616], +] + +[[area.polygons]] +vertices = [ + [1595, 3590], + [1595, 3603], +] + +[[area.polygons]] +vertices = [ + [1468, 3591], + [1469, 3591], + [1469, 3628], + [1468, 3628], +] + +[[area.polygons]] +vertices = [ + [1538, 3591], + [1538, 3617], +] + +[[area.polygons]] +vertices = [ + [1470, 3592], + [1472, 3592], + [1472, 3625], + [1470, 3625], +] + +[[area.polygons]] +vertices = [ + [1529, 3592], + [1537, 3592], + [1537, 3612], + [1529, 3612], +] + +[[area.polygons]] +vertices = [ + [1596, 3592], + [1596, 3601], +] + +[[area.polygons]] +vertices = [ + [1473, 3593], + [1474, 3593], + [1474, 3623], + [1473, 3623], +] + +[[area.polygons]] +vertices = [ + [1528, 3593], + [1528, 3611], +] + +[[area.polygons]] +vertices = [ + [1475, 3594], + [1475, 3624], +] + +[[area.polygons]] +vertices = [ + [1527, 3594], + [1527, 3611], +] + +[[area.polygons]] +vertices = [ + [1597, 3594], + [1597, 3596], +] + +[[area.polygons]] +vertices = [ + [1476, 3595], + [1476, 3624], +] + +[[area.polygons]] +vertices = [ + [1526, 3595], + [1526, 3611], +] + +[[area.polygons]] +vertices = [ + [1477, 3596], + [1477, 3624], +] + +[[area.polygons]] +vertices = [ + [1525, 3596], + [1525, 3611], +] + +[[area.polygons]] +vertices = [ + [1478, 3597], + [1478, 3624], +] + +[[area.polygons]] +vertices = [ + [1524, 3597], + [1524, 3610], +] + +[[area.polygons]] +vertices = [ + [1479, 3598], + [1479, 3624], +] + +[[area.polygons]] +vertices = [ + [1523, 3598], + [1523, 3609], +] + +[[area.polygons]] +vertices = [ + [1480, 3599], + [1480, 3624], +] + +[[area.polygons]] +vertices = [ + [1522, 3599], + [1522, 3609], +] + +[[area.polygons]] +vertices = [ + [1511, 3600], + [1521, 3600], + [1521, 3607], + [1511, 3607], +] + +[[area.polygons]] +vertices = [ + [1481, 3601], + [1481, 3624], +] + +[[area.polygons]] +vertices = [ + [1510, 3601], + [1510, 3608], +] + +[[area.polygons]] +vertices = [ + [1482, 3602], + [1482, 3624], +] + +[[area.polygons]] +vertices = [ + [1509, 3602], + [1509, 3608], +] + +[[area.polygons]] +vertices = [ + [1483, 3603], + [1483, 3624], +] + +[[area.polygons]] +vertices = [ + [1508, 3603], + [1508, 3609], +] + +[[area.polygons]] +vertices = [ + [1484, 3604], + [1484, 3624], +] + +[[area.polygons]] +vertices = [ + [1507, 3604], + [1507, 3613], +] + +[[area.polygons]] +vertices = [ + [1485, 3605], + [1485, 3624], +] + +[[area.polygons]] +vertices = [ + [1506, 3605], + [1506, 3614], +] + +[[area.polygons]] +vertices = [ + [1486, 3606], + [1505, 3606], + [1505, 3620], + [1486, 3620], +] + +[[area.polygons]] +vertices = [ + [1589, 3606], + [1590, 3606], + [1590, 3656], + [1589, 3656], +] + +[[area.polygons]] +vertices = [ + [1519, 3608], + [1521, 3608], +] + +[[area.polygons]] +vertices = [ + [1600, 3608], + [1600, 3650], +] + +[[area.polygons]] +vertices = [ + [1520, 3609], + [1521, 3609], +] + +[[area.polygons]] +vertices = [ + [1598, 3609], + [1599, 3609], + [1599, 3650], + [1598, 3650], +] + +[[area.polygons]] +vertices = [ + [1591, 3610], + [1592, 3610], + [1592, 3655], + [1591, 3655], +] + +[[area.polygons]] +vertices = [ + [1596, 3610], + [1597, 3610], + [1597, 3652], + [1596, 3652], +] + +[[area.polygons]] +vertices = [ + [1593, 3611], + [1595, 3611], + [1595, 3654], + [1593, 3654], +] + +[[area.polygons]] +vertices = [ + [1530, 3613], + [1537, 3613], +] + +[[area.polygons]] +vertices = [ + [1533, 3614], + [1537, 3614], +] + +[[area.polygons]] +vertices = [ + [1534, 3615], + [1537, 3615], +] + +[[area.polygons]] +vertices = [ + [1535, 3616], + [1537, 3616], +] + +[[area.polygons]] +vertices = [ + [1536, 3617], + [1537, 3617], +] + +[[area.polygons]] +vertices = [ + [1549, 3617], + [1568, 3617], +] + +[[area.polygons]] +vertices = [ + [1550, 3618], + [1568, 3618], + [1568, 3619], + [1550, 3619], +] + +[[area.polygons]] +vertices = [ + [1553, 3620], + [1568, 3620], +] + +[[area.polygons]] +vertices = [ + [1486, 3621], + [1489, 3621], + [1489, 3622], + [1486, 3622], +] + +[[area.polygons]] +vertices = [ + [1498, 3621], + [1506, 3621], +] + +[[area.polygons]] +vertices = [ + [1554, 3621], + [1568, 3621], + [1568, 3626], + [1554, 3626], +] + +[[area.polygons]] +vertices = [ + [1500, 3622], + [1507, 3622], +] + +[[area.polygons]] +vertices = [ + [1486, 3623], + [1488, 3623], +] + +[[area.polygons]] +vertices = [ + [1501, 3623], + [1507, 3623], +] + +[[area.polygons]] +vertices = [ + [1486, 3624], + [1487, 3624], +] + +[[area.polygons]] +vertices = [ + [1502, 3624], + [1503, 3624], +] + +[[area.polygons]] +vertices = [[1506, 3624]] + +[[area.polygons]] +vertices = [ + [1601, 3625], + [1601, 3650], +] + +[[area.polygons]] +vertices = [ + [1470, 3626], + [1471, 3626], +] + +[[area.polygons]] +vertices = [[1470, 3627]] + +[[area.polygons]] +vertices = [ + [1557, 3627], + [1568, 3627], + [1568, 3631], + [1557, 3631], +] + +[[area.polygons]] +vertices = [ + [1602, 3627], + [1602, 3649], +] + +[[area.polygons]] +vertices = [ + [1603, 3629], + [1603, 3648], +] + +[[area.polygons]] +vertices = [ + [1468, 3631], + [1468, 3640], +] + +[[area.polygons]] +vertices = [ + [1604, 3631], + [1604, 3647], +] + +[[area.polygons]] +vertices = [ + [1558, 3632], + [1568, 3632], + [1568, 3646], + [1558, 3646], +] + +[[area.polygons]] +vertices = [ + [1605, 3633], + [1605, 3646], +] + +[[area.polygons]] +vertices = [ + [1557, 3635], + [1557, 3640], +] + +[[area.polygons]] +vertices = [ + [1606, 3635], + [1606, 3645], +] + +[[area.polygons]] +vertices = [ + [1556, 3636], + [1556, 3639], +] + +[[area.polygons]] +vertices = [ + [1468, 3643], + [1469, 3643], + [1469, 3656], + [1468, 3656], +] + +[[area.polygons]] +vertices = [ + [1470, 3644], + [1470, 3655], +] + +[[area.polygons]] +vertices = [ + [1471, 3645], + [1471, 3654], +] + +[[area.polygons]] +vertices = [ + [1472, 3646], + [1472, 3653], +] + +[[area.polygons]] +vertices = [ + [1547, 3646], + [1548, 3646], + [1548, 3651], + [1547, 3651], +] + +[[area.polygons]] +vertices = [ + [1546, 3647], + [1546, 3651], +] + +[[area.polygons]] +vertices = [ + [1549, 3647], + [1549, 3651], +] + +[[area.polygons]] +vertices = [ + [1558, 3647], + [1562, 3647], +] + +[[area.polygons]] +vertices = [ + [1545, 3648], + [1545, 3651], +] + +[[area.polygons]] +vertices = [ + [1550, 3648], + [1561, 3648], +] + +[[area.polygons]] +vertices = [ + [1577, 3648], + [1582, 3648], +] + +[[area.polygons]] +vertices = [ + [1544, 3649], + [1544, 3651], +] + +[[area.polygons]] +vertices = [ + [1550, 3649], + [1560, 3649], +] + +[[area.polygons]] +vertices = [ + [1578, 3649], + [1582, 3649], +] + +[[area.polygons]] +vertices = [ + [1543, 3650], + [1543, 3651], +] + +[[area.polygons]] +vertices = [ + [1550, 3650], + [1559, 3650], +] + +[[area.polygons]] +vertices = [ + [1579, 3650], + [1582, 3650], +] + +[[area.polygons]] +vertices = [ + [1550, 3651], + [1558, 3651], +] + +[[area.polygons]] +vertices = [ + [1580, 3651], + [1582, 3651], +] + +[[area.polygons]] +vertices = [[1598, 3651]] + +[[area.polygons]] +vertices = [ + [1581, 3652], + [1582, 3652], +] + +[[area.polygons]] +vertices = [[1582, 3653]] + +[[area.polygons]] +vertices = [[1596, 3653]] + +[[area.polygons]] +vertices = [ + [1584, 3655], + [1585, 3655], +] + +[[area.polygons]] +vertices = [[1585, 3656]] + +[[area.polygons]] +vertices = [ + [1586, 3657], + [1587, 3657], +] + +[[area.polygons]] +vertices = [ + [1468, 3659], + [1468, 3666], +] + +[[area.polygons]] +vertices = [ + [1468, 3682], + [1469, 3682], + [1469, 3687], + [1468, 3687], +] + +[[area.polygons]] +vertices = [ + [1470, 3686], + [1471, 3686], + [1471, 3687], + [1470, 3687], +] + +[[area.polygons]] +vertices = [ + [1457, 3688], + [1458, 3688], + [1458, 3699], + [1457, 3699], +] + +[[area.polygons]] +vertices = [ + [1468, 3692], + [1468, 3700], +] + +[[area.polygons]] +vertices = [ + [1469, 3693], + [1469, 3699], +] + +[[area.polygons]] +vertices = [ + [1470, 3694], + [1470, 3698], +] + +[[area.polygons]] +vertices = [[1458, 3700]] + +[[area.polygons]] +vertices = [ + [1468, 3710], + [1468, 3713], +] + +[[area.polygons]] +vertices = [ + [1468, 3723], + [1468, 3731], +] + +[[area.polygons]] +vertices = [ + [1469, 3724], + [1469, 3730], +] + +[[area.polygons]] +vertices = [ + [1470, 3725], + [1470, 3729], +] diff --git a/.data/raw-cache/map/area/the_forgotten_tomb.toml b/.data/raw-cache/map/area/the_forgotten_tomb.toml new file mode 100644 index 000000000..d3244fd3b --- /dev/null +++ b/.data/raw-cache/map/area/the_forgotten_tomb.toml @@ -0,0 +1,16 @@ +# The Forgotten Tomb music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Forgotten Tomb. +# Bounds x 3328-3455, z 9216-9279; 8192 tiles as 1 rectangles. + +[[area]] +name = "The Forgotten Tomb" +area_id = "area.the_forgotten_tomb" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3328, 9216], + [3455, 9216], + [3455, 9279], + [3328, 9279], +] diff --git a/.data/raw-cache/map/area/the_forlorn_homestead.toml b/.data/raw-cache/map/area/the_forlorn_homestead.toml new file mode 100644 index 000000000..dff37316c --- /dev/null +++ b/.data/raw-cache/map/area/the_forlorn_homestead.toml @@ -0,0 +1,609 @@ +# The Forlorn Homestead music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Forlorn Homestead. +# Bounds x 1684-1818, z 3542-3660; 10318 tiles as 93 rectangles. + +[[area]] +name = "The Forlorn Homestead" +area_id = "area.the_forlorn_homestead" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1721, 3542], + [1728, 3542], + [1728, 3546], + [1721, 3546], +] + +[[area.polygons]] +vertices = [ + [1747, 3543], + [1753, 3543], + [1753, 3658], + [1747, 3658], +] + +[[area.polygons]] +vertices = [ + [1762, 3543], + [1780, 3543], + [1780, 3557], + [1762, 3557], +] + +[[area.polygons]] +vertices = [ + [1729, 3544], + [1729, 3639], +] + +[[area.polygons]] +vertices = [ + [1746, 3544], + [1746, 3658], +] + +[[area.polygons]] +vertices = [ + [1754, 3544], + [1754, 3658], +] + +[[area.polygons]] +vertices = [ + [1730, 3545], + [1730, 3640], +] + +[[area.polygons]] +vertices = [ + [1734, 3545], + [1745, 3545], + [1745, 3640], + [1734, 3640], +] + +[[area.polygons]] +vertices = [ + [1755, 3545], + [1756, 3545], + [1756, 3658], + [1755, 3658], +] + +[[area.polygons]] +vertices = [ + [1731, 3546], + [1733, 3546], + [1733, 3640], + [1731, 3640], +] + +[[area.polygons]] +vertices = [ + [1757, 3546], + [1761, 3546], + [1761, 3659], + [1757, 3659], +] + +[[area.polygons]] +vertices = [ + [1722, 3547], + [1728, 3547], + [1728, 3638], + [1722, 3638], +] + +[[area.polygons]] +vertices = [ + [1721, 3555], + [1721, 3638], +] + +[[area.polygons]] +vertices = [ + [1720, 3556], + [1720, 3637], +] + +[[area.polygons]] +vertices = [ + [1718, 3557], + [1719, 3557], + [1719, 3637], + [1718, 3637], +] + +[[area.polygons]] +vertices = [ + [1717, 3558], + [1717, 3637], +] + +[[area.polygons]] +vertices = [ + [1762, 3558], + [1776, 3558], + [1776, 3657], + [1762, 3657], +] + +[[area.polygons]] +vertices = [ + [1715, 3559], + [1716, 3559], + [1716, 3604], + [1715, 3604], +] + +[[area.polygons]] +vertices = [ + [1693, 3560], + [1698, 3560], + [1698, 3590], + [1693, 3590], +] + +[[area.polygons]] +vertices = [ + [1714, 3560], + [1714, 3590], +] + +[[area.polygons]] +vertices = [ + [1692, 3561], + [1692, 3592], +] + +[[area.polygons]] +vertices = [ + [1699, 3561], + [1699, 3589], +] + +[[area.polygons]] +vertices = [ + [1713, 3561], + [1713, 3589], +] + +[[area.polygons]] +vertices = [ + [1777, 3561], + [1787, 3561], + [1787, 3653], + [1777, 3653], +] + +[[area.polygons]] +vertices = [ + [1700, 3562], + [1701, 3562], + [1701, 3588], + [1700, 3588], +] + +[[area.polygons]] +vertices = [ + [1711, 3562], + [1712, 3562], + [1712, 3587], + [1711, 3587], +] + +[[area.polygons]] +vertices = [ + [1788, 3562], + [1788, 3653], +] + +[[area.polygons]] +vertices = [ + [1691, 3563], + [1691, 3592], +] + +[[area.polygons]] +vertices = [ + [1702, 3563], + [1702, 3588], +] + +[[area.polygons]] +vertices = [ + [1710, 3563], + [1710, 3587], +] + +[[area.polygons]] +vertices = [ + [1690, 3564], + [1690, 3592], +] + +[[area.polygons]] +vertices = [ + [1703, 3564], + [1709, 3564], + [1709, 3587], + [1703, 3587], +] + +[[area.polygons]] +vertices = [ + [1689, 3566], + [1689, 3592], +] + +[[area.polygons]] +vertices = [ + [1789, 3567], + [1793, 3567], + [1793, 3653], + [1789, 3653], +] + +[[area.polygons]] +vertices = [ + [1688, 3568], + [1688, 3592], +] + +[[area.polygons]] +vertices = [ + [1794, 3568], + [1795, 3568], + [1795, 3653], + [1794, 3653], +] + +[[area.polygons]] +vertices = [ + [1687, 3569], + [1687, 3592], +] + +[[area.polygons]] +vertices = [ + [1796, 3569], + [1797, 3569], + [1797, 3653], + [1796, 3653], +] + +[[area.polygons]] +vertices = [ + [1798, 3570], + [1799, 3570], + [1799, 3653], + [1798, 3653], +] + +[[area.polygons]] +vertices = [ + [1686, 3571], + [1686, 3592], +] + +[[area.polygons]] +vertices = [ + [1800, 3571], + [1803, 3571], + [1803, 3635], + [1800, 3635], +] + +[[area.polygons]] +vertices = [ + [1804, 3573], + [1806, 3573], + [1806, 3575], + [1804, 3575], +] + +[[area.polygons]] +vertices = [ + [1804, 3576], + [1805, 3576], + [1805, 3579], + [1804, 3579], +] + +[[area.polygons]] +vertices = [[1804, 3580]] + +[[area.polygons]] +vertices = [ + [1804, 3585], + [1804, 3633], +] + +[[area.polygons]] +vertices = [ + [1685, 3586], + [1685, 3591], +] + +[[area.polygons]] +vertices = [ + [1805, 3586], + [1805, 3633], +] + +[[area.polygons]] +vertices = [ + [1684, 3587], + [1684, 3590], +] + +[[area.polygons]] +vertices = [ + [1806, 3587], + [1806, 3632], +] + +[[area.polygons]] +vertices = [[1712, 3588]] + +[[area.polygons]] +vertices = [ + [1807, 3588], + [1807, 3631], +] + +[[area.polygons]] +vertices = [[1700, 3589]] + +[[area.polygons]] +vertices = [ + [1808, 3589], + [1808, 3621], +] + +[[area.polygons]] +vertices = [ + [1809, 3590], + [1809, 3620], +] + +[[area.polygons]] +vertices = [ + [1693, 3591], + [1696, 3591], +] + +[[area.polygons]] +vertices = [ + [1810, 3591], + [1810, 3619], +] + +[[area.polygons]] +vertices = [ + [1693, 3592], + [1694, 3592], +] + +[[area.polygons]] +vertices = [ + [1811, 3592], + [1811, 3619], +] + +[[area.polygons]] +vertices = [ + [1812, 3593], + [1812, 3619], +] + +[[area.polygons]] +vertices = [ + [1813, 3594], + [1813, 3618], +] + +[[area.polygons]] +vertices = [ + [1814, 3595], + [1814, 3610], +] + +[[area.polygons]] +vertices = [ + [1815, 3597], + [1815, 3608], +] + +[[area.polygons]] +vertices = [ + [1816, 3598], + [1816, 3607], +] + +[[area.polygons]] +vertices = [ + [1817, 3599], + [1817, 3606], +] + +[[area.polygons]] +vertices = [ + [1818, 3600], + [1818, 3605], +] + +[[area.polygons]] +vertices = [ + [1716, 3605], + [1716, 3606], +] + +[[area.polygons]] +vertices = [ + [1814, 3613], + [1814, 3617], +] + +[[area.polygons]] +vertices = [ + [1716, 3616], + [1716, 3637], +] + +[[area.polygons]] +vertices = [ + [1715, 3617], + [1715, 3636], +] + +[[area.polygons]] +vertices = [ + [1714, 3618], + [1714, 3636], +] + +[[area.polygons]] +vertices = [ + [1713, 3619], + [1713, 3636], +] + +[[area.polygons]] +vertices = [ + [1712, 3620], + [1712, 3636], +] + +[[area.polygons]] +vertices = [ + [1711, 3621], + [1711, 3636], +] + +[[area.polygons]] +vertices = [ + [1710, 3622], + [1710, 3635], +] + +[[area.polygons]] +vertices = [ + [1709, 3623], + [1709, 3635], +] + +[[area.polygons]] +vertices = [ + [1808, 3624], + [1808, 3629], +] + +[[area.polygons]] +vertices = [ + [1800, 3636], + [1802, 3636], + [1802, 3641], + [1800, 3641], +] + +[[area.polygons]] +vertices = [ + [1725, 3639], + [1728, 3639], +] + +[[area.polygons]] +vertices = [ + [1735, 3641], + [1745, 3641], +] + +[[area.polygons]] +vertices = [ + [1740, 3642], + [1745, 3642], +] + +[[area.polygons]] +vertices = [ + [1800, 3642], + [1801, 3642], + [1801, 3653], + [1800, 3653], +] + +[[area.polygons]] +vertices = [ + [1742, 3643], + [1745, 3643], + [1745, 3658], + [1742, 3658], +] + +[[area.polygons]] +vertices = [ + [1802, 3648], + [1802, 3653], +] + +[[area.polygons]] +vertices = [ + [1803, 3650], + [1803, 3653], +] + +[[area.polygons]] +vertices = [ + [1804, 3652], + [1804, 3653], +] + +[[area.polygons]] +vertices = [ + [1777, 3654], + [1782, 3654], +] + +[[area.polygons]] +vertices = [ + [1777, 3655], + [1781, 3655], +] + +[[area.polygons]] +vertices = [ + [1777, 3656], + [1779, 3656], +] + +[[area.polygons]] +vertices = [ + [1777, 3657], + [1778, 3657], +] + +[[area.polygons]] +vertices = [ + [1762, 3658], + [1767, 3658], +] + +[[area.polygons]] +vertices = [ + [1756, 3659], + [1756, 3660], +] + +[[area.polygons]] +vertices = [ + [1762, 3659], + [1766, 3659], +] + +[[area.polygons]] +vertices = [ + [1757, 3660], + [1759, 3660], +] diff --git a/.data/raw-cache/map/area/the_forsaken.toml b/.data/raw-cache/map/area/the_forsaken.toml new file mode 100644 index 000000000..35d10bcba --- /dev/null +++ b/.data/raw-cache/map/area/the_forsaken.toml @@ -0,0 +1,16 @@ +# The Forsaken music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Forsaken. +# Bounds x 1536-1599, z 5056-5119; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Forsaken" +area_id = "area.the_forsaken" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1536, 5056], + [1599, 5056], + [1599, 5119], + [1536, 5119], +] diff --git a/.data/raw-cache/map/area/the_forsaken_tower.toml b/.data/raw-cache/map/area/the_forsaken_tower.toml new file mode 100644 index 000000000..85e696569 --- /dev/null +++ b/.data/raw-cache/map/area/the_forsaken_tower.toml @@ -0,0 +1,70 @@ +# The Forsaken Tower music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Forsaken Tower. +# Bounds x 1368-1399, z 3817-10239; 1171 tiles as 9 rectangles. + +[[area]] +name = "The Forsaken Tower" +area_id = "area.the_forsaken_tower" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1381, 3817], + [1382, 3817], + [1382, 3829], + [1381, 3829], +] + +[[area.polygons]] +vertices = [ + [1378, 3818], + [1380, 3818], + [1380, 3829], + [1378, 3829], +] + +[[area.polygons]] +vertices = [ + [1383, 3818], + [1385, 3818], + [1385, 3829], + [1383, 3829], +] + +[[area.polygons]] +vertices = [ + [1377, 3819], + [1377, 3829], +] + +[[area.polygons]] +vertices = [ + [1386, 3819], + [1386, 3829], +] + +[[area.polygons]] +vertices = [ + [1387, 3820], + [1387, 3829], +] + +[[area.polygons]] +vertices = [ + [1376, 3821], + [1376, 3829], +] + +[[area.polygons]] +vertices = [ + [1388, 3822], + [1388, 3829], +] + +[[area.polygons]] +vertices = [ + [1368, 10208], + [1399, 10208], + [1399, 10239], + [1368, 10239], +] diff --git a/.data/raw-cache/map/area/the_foundry.toml b/.data/raw-cache/map/area/the_foundry.toml new file mode 100644 index 000000000..620a02b4e --- /dev/null +++ b/.data/raw-cache/map/area/the_foundry.toml @@ -0,0 +1,16 @@ +# The Foundry music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Foundry. +# Bounds x 3328-3391, z 11456-11519; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Foundry" +area_id = "area.the_foundry" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3328, 11456], + [3391, 11456], + [3391, 11519], + [3328, 11519], +] diff --git a/.data/raw-cache/map/area/the_fragment.toml b/.data/raw-cache/map/area/the_fragment.toml new file mode 100644 index 000000000..51eda7584 --- /dev/null +++ b/.data/raw-cache/map/area/the_fragment.toml @@ -0,0 +1,16 @@ +# The Fragment music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Fragment. +# Bounds x 3264-3327, z 5888-5951; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Fragment" +area_id = "area.the_fragment" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 5888], + [3327, 5888], + [3327, 5951], + [3264, 5951], +] diff --git a/.data/raw-cache/map/area/the_galleon.toml b/.data/raw-cache/map/area/the_galleon.toml new file mode 100644 index 000000000..cf895ce6a --- /dev/null +++ b/.data/raw-cache/map/area/the_galleon.toml @@ -0,0 +1,289 @@ +# The Galleon music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Galleon. +# Bounds x 2186-2229, z 3784-3834; 1008 tiles as 45 rectangles. + +[[area]] +name = "The Galleon" +area_id = "area.the_galleon" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2219, 3784], + [2229, 3784], + [2229, 3807], + [2219, 3807], +] + +[[area.polygons]] +vertices = [ + [2212, 3794], + [2213, 3794], + [2213, 3795], + [2212, 3795], +] + +[[area.polygons]] +vertices = [ + [2213, 3796], + [2216, 3796], + [2216, 3800], + [2213, 3800], +] + +[[area.polygons]] +vertices = [ + [2217, 3797], + [2218, 3797], +] + +[[area.polygons]] +vertices = [ + [2206, 3798], + [2207, 3798], +] + +[[area.polygons]] +vertices = [ + [2205, 3799], + [2206, 3799], + [2206, 3800], + [2205, 3800], +] + +[[area.polygons]] +vertices = [ + [2204, 3801], + [2205, 3801], + [2205, 3833], + [2204, 3833], +] + +[[area.polygons]] +vertices = [ + [2213, 3801], + [2215, 3801], + [2215, 3810], + [2213, 3810], +] + +[[area.polygons]] +vertices = [ + [2191, 3802], + [2191, 3810], +] + +[[area.polygons]] +vertices = [ + [2203, 3802], + [2203, 3830], +] + +[[area.polygons]] +vertices = [ + [2210, 3802], + [2210, 3803], +] + +[[area.polygons]] +vertices = [ + [2190, 3803], + [2190, 3810], +] + +[[area.polygons]] +vertices = [ + [2192, 3803], + [2192, 3811], +] + +[[area.polygons]] +vertices = [ + [2196, 3803], + [2198, 3803], + [2198, 3824], + [2196, 3824], +] + +[[area.polygons]] +vertices = [ + [2202, 3803], + [2202, 3828], +] + +[[area.polygons]] +vertices = [ + [2206, 3803], + [2206, 3834], +] + +[[area.polygons]] +vertices = [ + [2211, 3803], + [2212, 3803], + [2212, 3830], + [2211, 3830], +] + +[[area.polygons]] +vertices = [ + [2189, 3804], + [2189, 3810], +] + +[[area.polygons]] +vertices = [ + [2193, 3804], + [2193, 3812], +] + +[[area.polygons]] +vertices = [ + [2195, 3804], + [2195, 3814], +] + +[[area.polygons]] +vertices = [ + [2199, 3804], + [2199, 3827], +] + +[[area.polygons]] +vertices = [ + [2201, 3804], + [2201, 3827], +] + +[[area.polygons]] +vertices = [ + [2207, 3804], + [2208, 3804], + [2208, 3834], + [2207, 3834], +] + +[[area.polygons]] +vertices = [ + [2188, 3805], + [2188, 3810], +] + +[[area.polygons]] +vertices = [ + [2194, 3805], + [2194, 3814], +] + +[[area.polygons]] +vertices = [ + [2200, 3805], + [2200, 3827], +] + +[[area.polygons]] +vertices = [ + [2209, 3805], + [2209, 3833], +] + +[[area.polygons]] +vertices = [ + [2186, 3806], + [2187, 3806], + [2187, 3809], + [2186, 3809], +] + +[[area.polygons]] +vertices = [ + [2210, 3806], + [2210, 3831], +] + +[[area.polygons]] +vertices = [ + [2220, 3808], + [2228, 3808], + [2228, 3810], + [2220, 3810], +] + +[[area.polygons]] +vertices = [[2187, 3810]] + +[[area.polygons]] +vertices = [ + [2213, 3811], + [2214, 3811], + [2214, 3828], + [2213, 3828], +] + +[[area.polygons]] +vertices = [ + [2221, 3811], + [2227, 3811], + [2227, 3812], + [2221, 3812], +] + +[[area.polygons]] +vertices = [ + [2215, 3812], + [2215, 3827], +] + +[[area.polygons]] +vertices = [ + [2222, 3813], + [2226, 3813], +] + +[[area.polygons]] +vertices = [ + [2216, 3816], + [2216, 3827], +] + +[[area.polygons]] +vertices = [ + [2217, 3817], + [2217, 3827], +] + +[[area.polygons]] +vertices = [ + [2195, 3818], + [2195, 3823], +] + +[[area.polygons]] +vertices = [ + [2218, 3818], + [2218, 3820], +] + +[[area.polygons]] +vertices = [ + [2218, 3824], + [2218, 3826], +] + +[[area.polygons]] +vertices = [ + [2197, 3825], + [2198, 3825], +] + +[[area.polygons]] +vertices = [[2198, 3826]] + +[[area.polygons]] +vertices = [[2213, 3829]] + +[[area.polygons]] +vertices = [[2211, 3831]] + +[[area.polygons]] +vertices = [[2205, 3834]] diff --git a/.data/raw-cache/map/area/the_gauntlet.toml b/.data/raw-cache/map/area/the_gauntlet.toml new file mode 100644 index 000000000..fdc95f57e --- /dev/null +++ b/.data/raw-cache/map/area/the_gauntlet.toml @@ -0,0 +1,16 @@ +# The Gauntlet music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Gauntlet. +# Bounds x 1856-1983, z 5632-5695; 8192 tiles as 1 rectangles. + +[[area]] +name = "The Gauntlet" +area_id = "area.the_gauntlet" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 5632], + [1983, 5632], + [1983, 5695], + [1856, 5695], +] diff --git a/.data/raw-cache/map/area/the_genie.toml b/.data/raw-cache/map/area/the_genie.toml new file mode 100644 index 000000000..4d4dc6824 --- /dev/null +++ b/.data/raw-cache/map/area/the_genie.toml @@ -0,0 +1,16 @@ +# The Genie music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Genie. +# Bounds x 3328-3391, z 9280-9343; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Genie" +area_id = "area.the_genie" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3328, 9280], + [3391, 9280], + [3391, 9343], + [3328, 9343], +] diff --git a/.data/raw-cache/map/area/the_golem.toml b/.data/raw-cache/map/area/the_golem.toml new file mode 100644 index 000000000..a97b6c2c4 --- /dev/null +++ b/.data/raw-cache/map/area/the_golem.toml @@ -0,0 +1,357 @@ +# The Golem music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Golem. +# Bounds x 3456-3517, z 3067-3144; 3092 tiles as 56 rectangles. + +[[area]] +name = "The Golem" +area_id = "area.the_golem" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3505, 3067], + [3515, 3067], + [3515, 3078], + [3505, 3078], +] + +[[area.polygons]] +vertices = [ + [3504, 3068], + [3504, 3112], +] + +[[area.polygons]] +vertices = [ + [3516, 3068], + [3516, 3077], +] + +[[area.polygons]] +vertices = [ + [3475, 3069], + [3482, 3069], + [3482, 3128], + [3475, 3128], +] + +[[area.polygons]] +vertices = [ + [3503, 3069], + [3503, 3113], +] + +[[area.polygons]] +vertices = [ + [3474, 3070], + [3474, 3142], +] + +[[area.polygons]] +vertices = [ + [3483, 3070], + [3502, 3070], + [3502, 3114], + [3483, 3114], +] + +[[area.polygons]] +vertices = [ + [3473, 3071], + [3473, 3142], +] + +[[area.polygons]] +vertices = [ + [3517, 3071], + [3517, 3074], +] + +[[area.polygons]] +vertices = [ + [3472, 3072], + [3472, 3142], +] + +[[area.polygons]] +vertices = [ + [3469, 3075], + [3471, 3075], + [3471, 3141], + [3469, 3141], +] + +[[area.polygons]] +vertices = [ + [3468, 3076], + [3468, 3140], +] + +[[area.polygons]] +vertices = [ + [3467, 3077], + [3467, 3139], +] + +[[area.polygons]] +vertices = [ + [3466, 3078], + [3466, 3138], +] + +[[area.polygons]] +vertices = [ + [3465, 3079], + [3465, 3137], +] + +[[area.polygons]] +vertices = [ + [3505, 3079], + [3514, 3079], +] + +[[area.polygons]] +vertices = [ + [3464, 3080], + [3464, 3136], +] + +[[area.polygons]] +vertices = [ + [3505, 3080], + [3513, 3080], +] + +[[area.polygons]] +vertices = [ + [3505, 3081], + [3512, 3081], + [3512, 3089], + [3505, 3089], +] + +[[area.polygons]] +vertices = [ + [3459, 3083], + [3463, 3083], + [3463, 3131], + [3459, 3131], +] + +[[area.polygons]] +vertices = [ + [3458, 3084], + [3458, 3130], +] + +[[area.polygons]] +vertices = [ + [3457, 3085], + [3457, 3129], +] + +[[area.polygons]] +vertices = [ + [3456, 3086], + [3456, 3128], +] + +[[area.polygons]] +vertices = [ + [3513, 3086], + [3513, 3088], +] + +[[area.polygons]] +vertices = [ + [3505, 3090], + [3510, 3090], + [3510, 3091], + [3505, 3091], +] + +[[area.polygons]] +vertices = [ + [3505, 3092], + [3509, 3092], + [3509, 3093], + [3505, 3093], +] + +[[area.polygons]] +vertices = [ + [3505, 3094], + [3508, 3094], + [3508, 3110], + [3505, 3110], +] + +[[area.polygons]] +vertices = [ + [3509, 3100], + [3509, 3108], +] + +[[area.polygons]] +vertices = [ + [3510, 3103], + [3510, 3105], +] + +[[area.polygons]] +vertices = [ + [3505, 3111], + [3507, 3111], +] + +[[area.polygons]] +vertices = [[3505, 3112]] + +[[area.polygons]] +vertices = [ + [3483, 3115], + [3500, 3115], +] + +[[area.polygons]] +vertices = [ + [3483, 3116], + [3498, 3116], +] + +[[area.polygons]] +vertices = [ + [3483, 3117], + [3495, 3117], +] + +[[area.polygons]] +vertices = [ + [3483, 3118], + [3492, 3118], +] + +[[area.polygons]] +vertices = [ + [3483, 3119], + [3491, 3119], +] + +[[area.polygons]] +vertices = [ + [3483, 3120], + [3489, 3120], +] + +[[area.polygons]] +vertices = [ + [3483, 3121], + [3488, 3121], +] + +[[area.polygons]] +vertices = [ + [3483, 3122], + [3487, 3122], +] + +[[area.polygons]] +vertices = [ + [3483, 3123], + [3486, 3123], +] + +[[area.polygons]] +vertices = [ + [3483, 3124], + [3485, 3124], +] + +[[area.polygons]] +vertices = [ + [3483, 3125], + [3484, 3125], +] + +[[area.polygons]] +vertices = [[3483, 3126]] + +[[area.polygons]] +vertices = [ + [3475, 3129], + [3481, 3129], + [3481, 3130], + [3475, 3130], +] + +[[area.polygons]] +vertices = [ + [3475, 3131], + [3480, 3131], + [3480, 3133], + [3475, 3133], +] + +[[area.polygons]] +vertices = [ + [3460, 3132], + [3463, 3132], +] + +[[area.polygons]] +vertices = [ + [3461, 3133], + [3463, 3133], +] + +[[area.polygons]] +vertices = [ + [3462, 3134], + [3463, 3134], +] + +[[area.polygons]] +vertices = [ + [3475, 3134], + [3479, 3134], + [3479, 3136], + [3475, 3136], +] + +[[area.polygons]] +vertices = [[3463, 3135]] + +[[area.polygons]] +vertices = [ + [3475, 3137], + [3478, 3137], + [3478, 3139], + [3475, 3139], +] + +[[area.polygons]] +vertices = [ + [3475, 3140], + [3477, 3140], +] + +[[area.polygons]] +vertices = [ + [3475, 3141], + [3476, 3141], +] + +[[area.polygons]] +vertices = [ + [3470, 3142], + [3471, 3142], + [3471, 3143], + [3470, 3143], +] + +[[area.polygons]] +vertices = [[3475, 3142]] + +[[area.polygons]] +vertices = [[3470, 3144]] diff --git a/.data/raw-cache/map/area/the_great_conch.toml b/.data/raw-cache/map/area/the_great_conch.toml new file mode 100644 index 000000000..f31cc9db4 --- /dev/null +++ b/.data/raw-cache/map/area/the_great_conch.toml @@ -0,0 +1,1454 @@ +# The Great Conch music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Elder Wisdom; The Tortugan Way. +# Bounds x 3077-3323, z 2308-2553; 29962 tiles as 230 rectangles. + +[[area]] +name = "The Great Conch" +area_id = "area.the_great_conch" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3268, 2308], + [3270, 2308], + [3270, 2406], + [3268, 2406], +] + +[[area.polygons]] +vertices = [ + [3266, 2309], + [3267, 2309], + [3267, 2416], + [3266, 2416], +] + +[[area.polygons]] +vertices = [ + [3271, 2309], + [3271, 2403], +] + +[[area.polygons]] +vertices = [ + [3265, 2310], + [3265, 2424], +] + +[[area.polygons]] +vertices = [ + [3272, 2310], + [3281, 2310], + [3281, 2377], + [3272, 2377], +] + +[[area.polygons]] +vertices = [ + [3264, 2311], + [3264, 2427], +] + +[[area.polygons]] +vertices = [ + [3282, 2311], + [3288, 2311], + [3288, 2366], + [3282, 2366], +] + +[[area.polygons]] +vertices = [ + [3289, 2312], + [3289, 2365], +] + +[[area.polygons]] +vertices = [ + [3290, 2313], + [3292, 2313], + [3292, 2358], + [3290, 2358], +] + +[[area.polygons]] +vertices = [ + [3293, 2314], + [3294, 2314], + [3294, 2356], + [3293, 2356], +] + +[[area.polygons]] +vertices = [ + [3295, 2315], + [3295, 2355], +] + +[[area.polygons]] +vertices = [ + [3296, 2316], + [3296, 2354], +] + +[[area.polygons]] +vertices = [ + [3297, 2317], + [3298, 2317], + [3298, 2352], + [3297, 2352], +] + +[[area.polygons]] +vertices = [ + [3299, 2318], + [3299, 2349], +] + +[[area.polygons]] +vertices = [ + [3300, 2319], + [3300, 2347], +] + +[[area.polygons]] +vertices = [ + [3301, 2320], + [3302, 2320], + [3302, 2346], + [3301, 2346], +] + +[[area.polygons]] +vertices = [ + [3303, 2321], + [3303, 2344], +] + +[[area.polygons]] +vertices = [ + [3304, 2322], + [3304, 2342], +] + +[[area.polygons]] +vertices = [ + [3263, 2324], + [3263, 2437], +] + +[[area.polygons]] +vertices = [ + [3305, 2324], + [3309, 2324], + [3309, 2338], + [3305, 2338], +] + +[[area.polygons]] +vertices = [ + [3262, 2325], + [3262, 2447], +] + +[[area.polygons]] +vertices = [ + [3310, 2325], + [3313, 2325], + [3313, 2336], + [3310, 2336], +] + +[[area.polygons]] +vertices = [ + [3320, 2325], + [3320, 2330], +] + +[[area.polygons]] +vertices = [ + [3261, 2326], + [3261, 2461], +] + +[[area.polygons]] +vertices = [ + [3314, 2326], + [3319, 2326], + [3319, 2330], + [3314, 2330], +] + +[[area.polygons]] +vertices = [ + [3321, 2326], + [3321, 2329], +] + +[[area.polygons]] +vertices = [ + [3322, 2327], + [3322, 2328], +] + +[[area.polygons]] +vertices = [[3323, 2328]] + +[[area.polygons]] +vertices = [ + [3260, 2329], + [3260, 2473], +] + +[[area.polygons]] +vertices = [ + [3314, 2331], + [3318, 2331], + [3318, 2332], + [3314, 2332], +] + +[[area.polygons]] +vertices = [ + [3259, 2332], + [3259, 2482], +] + +[[area.polygons]] +vertices = [ + [3314, 2333], + [3317, 2333], +] + +[[area.polygons]] +vertices = [ + [3258, 2334], + [3258, 2490], +] + +[[area.polygons]] +vertices = [ + [3314, 2334], + [3315, 2334], +] + +[[area.polygons]] +vertices = [ + [3256, 2335], + [3257, 2335], + [3257, 2498], + [3256, 2498], +] + +[[area.polygons]] +vertices = [[3314, 2335]] + +[[area.polygons]] +vertices = [ + [3310, 2337], + [3312, 2337], +] + +[[area.polygons]] +vertices = [ + [3255, 2338], + [3255, 2504], +] + +[[area.polygons]] +vertices = [ + [3254, 2339], + [3254, 2505], +] + +[[area.polygons]] +vertices = [ + [3305, 2339], + [3308, 2339], +] + +[[area.polygons]] +vertices = [ + [3252, 2340], + [3253, 2340], + [3253, 2506], + [3252, 2506], +] + +[[area.polygons]] +vertices = [ + [3305, 2340], + [3307, 2340], +] + +[[area.polygons]] +vertices = [ + [3250, 2341], + [3251, 2341], + [3251, 2507], + [3250, 2507], +] + +[[area.polygons]] +vertices = [[3305, 2341]] + +[[area.polygons]] +vertices = [ + [3249, 2342], + [3249, 2508], +] + +[[area.polygons]] +vertices = [ + [3235, 2343], + [3235, 2512], +] + +[[area.polygons]] +vertices = [ + [3244, 2343], + [3248, 2343], + [3248, 2508], + [3244, 2508], +] + +[[area.polygons]] +vertices = [ + [3236, 2344], + [3243, 2344], + [3243, 2509], + [3236, 2509], +] + +[[area.polygons]] +vertices = [ + [3234, 2347], + [3234, 2513], +] + +[[area.polygons]] +vertices = [[3301, 2347]] + +[[area.polygons]] +vertices = [ + [3233, 2348], + [3233, 2513], +] + +[[area.polygons]] +vertices = [ + [3232, 2349], + [3232, 2513], +] + +[[area.polygons]] +vertices = [ + [3231, 2350], + [3231, 2514], +] + +[[area.polygons]] +vertices = [ + [3230, 2352], + [3230, 2514], +] + +[[area.polygons]] +vertices = [ + [3228, 2353], + [3229, 2353], + [3229, 2515], + [3228, 2515], +] + +[[area.polygons]] +vertices = [[3297, 2353]] + +[[area.polygons]] +vertices = [ + [3227, 2354], + [3227, 2515], +] + +[[area.polygons]] +vertices = [ + [3226, 2355], + [3226, 2515], +] + +[[area.polygons]] +vertices = [ + [3224, 2356], + [3225, 2356], + [3225, 2516], + [3224, 2516], +] + +[[area.polygons]] +vertices = [ + [3223, 2357], + [3223, 2517], +] + +[[area.polygons]] +vertices = [[3293, 2357]] + +[[area.polygons]] +vertices = [ + [3222, 2358], + [3222, 2518], +] + +[[area.polygons]] +vertices = [ + [3221, 2359], + [3221, 2518], +] + +[[area.polygons]] +vertices = [ + [3290, 2359], + [3291, 2359], + [3291, 2362], + [3290, 2362], +] + +[[area.polygons]] +vertices = [ + [3220, 2360], + [3220, 2519], +] + +[[area.polygons]] +vertices = [ + [3219, 2361], + [3219, 2519], +] + +[[area.polygons]] +vertices = [ + [3218, 2362], + [3218, 2519], +] + +[[area.polygons]] +vertices = [ + [3181, 2363], + [3189, 2363], + [3189, 2365], + [3181, 2365], +] + +[[area.polygons]] +vertices = [ + [3217, 2363], + [3217, 2520], +] + +[[area.polygons]] +vertices = [ + [3290, 2363], + [3290, 2364], +] + +[[area.polygons]] +vertices = [[3190, 2364]] + +[[area.polygons]] +vertices = [ + [3214, 2364], + [3216, 2364], + [3216, 2521], + [3214, 2521], +] + +[[area.polygons]] +vertices = [ + [3211, 2365], + [3213, 2365], + [3213, 2521], + [3211, 2521], +] + +[[area.polygons]] +vertices = [ + [3187, 2366], + [3187, 2370], +] + +[[area.polygons]] +vertices = [ + [3282, 2367], + [3287, 2367], +] + +[[area.polygons]] +vertices = [ + [3171, 2368], + [3186, 2368], + [3186, 2370], + [3171, 2370], +] + +[[area.polygons]] +vertices = [ + [3188, 2368], + [3192, 2368], + [3192, 2370], + [3188, 2370], +] + +[[area.polygons]] +vertices = [ + [3210, 2368], + [3210, 2523], +] + +[[area.polygons]] +vertices = [ + [3282, 2368], + [3285, 2368], + [3285, 2372], + [3282, 2372], +] + +[[area.polygons]] +vertices = [ + [3209, 2369], + [3209, 2524], +] + +[[area.polygons]] +vertices = [ + [3208, 2370], + [3208, 2524], +] + +[[area.polygons]] +vertices = [ + [3183, 2371], + [3185, 2371], + [3185, 2542], + [3183, 2542], +] + +[[area.polygons]] +vertices = [ + [3207, 2372], + [3207, 2524], +] + +[[area.polygons]] +vertices = [ + [3200, 2373], + [3201, 2373], + [3201, 2528], + [3200, 2528], +] + +[[area.polygons]] +vertices = [ + [3206, 2373], + [3206, 2525], +] + +[[area.polygons]] +vertices = [ + [3282, 2373], + [3284, 2373], +] + +[[area.polygons]] +vertices = [ + [3196, 2374], + [3199, 2374], + [3199, 2529], + [3196, 2529], +] + +[[area.polygons]] +vertices = [ + [3202, 2374], + [3202, 2528], +] + +[[area.polygons]] +vertices = [ + [3205, 2374], + [3205, 2526], +] + +[[area.polygons]] +vertices = [ + [3282, 2374], + [3283, 2374], +] + +[[area.polygons]] +vertices = [ + [3194, 2375], + [3195, 2375], + [3195, 2532], + [3194, 2532], +] + +[[area.polygons]] +vertices = [ + [3203, 2375], + [3204, 2375], + [3204, 2526], + [3203, 2526], +] + +[[area.polygons]] +vertices = [[3282, 2375]] + +[[area.polygons]] +vertices = [ + [3193, 2376], + [3193, 2534], +] + +[[area.polygons]] +vertices = [ + [3190, 2377], + [3192, 2377], + [3192, 2535], + [3190, 2535], +] + +[[area.polygons]] +vertices = [ + [3189, 2378], + [3189, 2539], +] + +[[area.polygons]] +vertices = [ + [3272, 2378], + [3280, 2378], +] + +[[area.polygons]] +vertices = [ + [3186, 2379], + [3188, 2379], + [3188, 2540], + [3186, 2540], +] + +[[area.polygons]] +vertices = [ + [3272, 2379], + [3279, 2379], +] + +[[area.polygons]] +vertices = [ + [3272, 2380], + [3278, 2380], + [3278, 2382], + [3272, 2382], +] + +[[area.polygons]] +vertices = [ + [3182, 2381], + [3182, 2543], +] + +[[area.polygons]] +vertices = [ + [3179, 2382], + [3181, 2382], + [3181, 2543], + [3179, 2543], +] + +[[area.polygons]] +vertices = [ + [3140, 2383], + [3143, 2383], + [3143, 2537], + [3140, 2537], +] + +[[area.polygons]] +vertices = [ + [3178, 2383], + [3178, 2543], +] + +[[area.polygons]] +vertices = [ + [3272, 2383], + [3277, 2383], + [3277, 2385], + [3272, 2385], +] + +[[area.polygons]] +vertices = [ + [3138, 2384], + [3139, 2384], + [3139, 2547], + [3138, 2547], +] + +[[area.polygons]] +vertices = [ + [3144, 2384], + [3147, 2384], + [3147, 2537], + [3144, 2537], +] + +[[area.polygons]] +vertices = [ + [3174, 2384], + [3177, 2384], + [3177, 2542], + [3174, 2542], +] + +[[area.polygons]] +vertices = [ + [3137, 2385], + [3137, 2548], +] + +[[area.polygons]] +vertices = [ + [3148, 2385], + [3148, 2538], +] + +[[area.polygons]] +vertices = [ + [3172, 2385], + [3173, 2385], + [3173, 2541], + [3172, 2541], +] + +[[area.polygons]] +vertices = [ + [3149, 2386], + [3151, 2386], + [3151, 2537], + [3149, 2537], +] + +[[area.polygons]] +vertices = [ + [3170, 2386], + [3171, 2386], + [3171, 2540], + [3170, 2540], +] + +[[area.polygons]] +vertices = [ + [3272, 2386], + [3276, 2386], + [3276, 2390], + [3272, 2390], +] + +[[area.polygons]] +vertices = [ + [3136, 2387], + [3136, 2548], +] + +[[area.polygons]] +vertices = [ + [3152, 2387], + [3152, 2537], +] + +[[area.polygons]] +vertices = [ + [3168, 2387], + [3169, 2387], + [3169, 2540], + [3168, 2540], +] + +[[area.polygons]] +vertices = [ + [3135, 2388], + [3135, 2548], +] + +[[area.polygons]] +vertices = [ + [3153, 2388], + [3154, 2388], + [3154, 2536], + [3153, 2536], +] + +[[area.polygons]] +vertices = [ + [3158, 2388], + [3167, 2388], + [3167, 2537], + [3158, 2537], +] + +[[area.polygons]] +vertices = [ + [3132, 2389], + [3134, 2389], + [3134, 2547], + [3132, 2547], +] + +[[area.polygons]] +vertices = [ + [3155, 2389], + [3157, 2389], + [3157, 2536], + [3155, 2536], +] + +[[area.polygons]] +vertices = [ + [3127, 2390], + [3131, 2390], + [3131, 2546], + [3127, 2546], +] + +[[area.polygons]] +vertices = [ + [3121, 2391], + [3126, 2391], + [3126, 2545], + [3121, 2545], +] + +[[area.polygons]] +vertices = [ + [3272, 2391], + [3275, 2391], + [3275, 2392], + [3272, 2392], +] + +[[area.polygons]] +vertices = [ + [3114, 2392], + [3120, 2392], + [3120, 2545], + [3114, 2545], +] + +[[area.polygons]] +vertices = [ + [3113, 2393], + [3113, 2546], +] + +[[area.polygons]] +vertices = [ + [3272, 2393], + [3274, 2393], + [3274, 2397], + [3272, 2397], +] + +[[area.polygons]] +vertices = [ + [3108, 2394], + [3112, 2394], + [3112, 2545], + [3108, 2545], +] + +[[area.polygons]] +vertices = [ + [3107, 2395], + [3107, 2545], +] + +[[area.polygons]] +vertices = [ + [3105, 2396], + [3106, 2396], + [3106, 2545], + [3105, 2545], +] + +[[area.polygons]] +vertices = [ + [3104, 2397], + [3104, 2546], +] + +[[area.polygons]] +vertices = [ + [3103, 2398], + [3103, 2547], +] + +[[area.polygons]] +vertices = [ + [3272, 2398], + [3273, 2398], +] + +[[area.polygons]] +vertices = [ + [3102, 2399], + [3102, 2547], +] + +[[area.polygons]] +vertices = [ + [3272, 2399], + [3272, 2400], +] + +[[area.polygons]] +vertices = [ + [3089, 2400], + [3090, 2400], + [3090, 2416], + [3089, 2416], +] + +[[area.polygons]] +vertices = [ + [3100, 2400], + [3101, 2400], + [3101, 2438], + [3100, 2438], +] + +[[area.polygons]] +vertices = [ + [3088, 2401], + [3088, 2414], +] + +[[area.polygons]] +vertices = [ + [3091, 2401], + [3099, 2401], + [3099, 2418], + [3091, 2418], +] + +[[area.polygons]] +vertices = [ + [3087, 2402], + [3087, 2410], +] + +[[area.polygons]] +vertices = [ + [3086, 2404], + [3086, 2407], +] + +[[area.polygons]] +vertices = [ + [3268, 2407], + [3269, 2407], + [3269, 2410], + [3268, 2410], +] + +[[area.polygons]] +vertices = [ + [3268, 2411], + [3268, 2413], +] + +[[area.polygons]] +vertices = [[3090, 2417]] + +[[area.polygons]] +vertices = [ + [3266, 2417], + [3266, 2424], +] + +[[area.polygons]] +vertices = [ + [3092, 2419], + [3099, 2419], + [3099, 2420], + [3092, 2420], +] + +[[area.polygons]] +vertices = [ + [3093, 2421], + [3099, 2421], +] + +[[area.polygons]] +vertices = [ + [3094, 2422], + [3099, 2422], +] + +[[area.polygons]] +vertices = [ + [3267, 2422], + [3267, 2423], +] + +[[area.polygons]] +vertices = [ + [3095, 2423], + [3099, 2423], +] + +[[area.polygons]] +vertices = [ + [3096, 2424], + [3099, 2424], + [3099, 2427], + [3096, 2427], +] + +[[area.polygons]] +vertices = [ + [3097, 2428], + [3099, 2428], +] + +[[area.polygons]] +vertices = [ + [3098, 2429], + [3099, 2429], +] + +[[area.polygons]] +vertices = [ + [3099, 2430], + [3099, 2436], +] + +[[area.polygons]] +vertices = [ + [3101, 2439], + [3101, 2440], +] + +[[area.polygons]] +vertices = [ + [3101, 2446], + [3101, 2547], +] + +[[area.polygons]] +vertices = [ + [3100, 2447], + [3100, 2448], +] + +[[area.polygons]] +vertices = [ + [3262, 2451], + [3262, 2452], +] + +[[area.polygons]] +vertices = [ + [3100, 2453], + [3100, 2456], +] + +[[area.polygons]] +vertices = [ + [3098, 2454], + [3099, 2454], + [3099, 2455], + [3098, 2455], +] + +[[area.polygons]] +vertices = [ + [3097, 2455], + [3097, 2456], +] + +[[area.polygons]] +vertices = [ + [3262, 2456], + [3262, 2458], +] + +[[area.polygons]] +vertices = [ + [3100, 2460], + [3100, 2465], +] + +[[area.polygons]] +vertices = [ + [3098, 2461], + [3099, 2461], + [3099, 2465], + [3098, 2465], +] + +[[area.polygons]] +vertices = [ + [3097, 2462], + [3097, 2464], +] + +[[area.polygons]] +vertices = [ + [3261, 2463], + [3271, 2463], + [3271, 2464], + [3261, 2464], +] + +[[area.polygons]] +vertices = [ + [3261, 2466], + [3261, 2468], +] + +[[area.polygons]] +vertices = [ + [3100, 2470], + [3100, 2548], +] + +[[area.polygons]] +vertices = [ + [3098, 2471], + [3099, 2471], + [3099, 2550], + [3098, 2550], +] + +[[area.polygons]] +vertices = [ + [3097, 2474], + [3097, 2477], +] + +[[area.polygons]] +vertices = [ + [3097, 2479], + [3097, 2552], +] + +[[area.polygons]] +vertices = [ + [3096, 2481], + [3096, 2553], +] + +[[area.polygons]] +vertices = [ + [3095, 2484], + [3095, 2496], +] + +[[area.polygons]] +vertices = [ + [3093, 2485], + [3094, 2485], + [3094, 2491], + [3093, 2491], +] + +[[area.polygons]] +vertices = [ + [3092, 2487], + [3092, 2490], +] + +[[area.polygons]] +vertices = [ + [3094, 2492], + [3094, 2493], +] + +[[area.polygons]] +vertices = [ + [3256, 2499], + [3256, 2502], +] + +[[area.polygons]] +vertices = [ + [3095, 2500], + [3095, 2552], +] + +[[area.polygons]] +vertices = [ + [3094, 2503], + [3094, 2551], +] + +[[area.polygons]] +vertices = [ + [3093, 2504], + [3093, 2548], +] + +[[area.polygons]] +vertices = [ + [3092, 2505], + [3092, 2547], +] + +[[area.polygons]] +vertices = [ + [3091, 2507], + [3091, 2545], +] + +[[area.polygons]] +vertices = [ + [3090, 2508], + [3090, 2544], +] + +[[area.polygons]] +vertices = [ + [3088, 2509], + [3089, 2509], + [3089, 2543], + [3088, 2543], +] + +[[area.polygons]] +vertices = [[3244, 2509]] + +[[area.polygons]] +vertices = [ + [3087, 2510], + [3087, 2542], +] + +[[area.polygons]] +vertices = [ + [3236, 2510], + [3242, 2510], +] + +[[area.polygons]] +vertices = [ + [3236, 2511], + [3238, 2511], +] + +[[area.polygons]] +vertices = [ + [3086, 2512], + [3086, 2542], +] + +[[area.polygons]] +vertices = [ + [3236, 2512], + [3237, 2512], +] + +[[area.polygons]] +vertices = [[3224, 2517]] + +[[area.polygons]] +vertices = [ + [3085, 2520], + [3085, 2541], +] + +[[area.polygons]] +vertices = [ + [3084, 2521], + [3084, 2522], +] + +[[area.polygons]] +vertices = [ + [3211, 2522], + [3212, 2522], +] + +[[area.polygons]] +vertices = [[3211, 2523]] + +[[area.polygons]] +vertices = [ + [3084, 2524], + [3084, 2541], +] + +[[area.polygons]] +vertices = [ + [3083, 2526], + [3083, 2540], +] + +[[area.polygons]] +vertices = [[3203, 2527]] + +[[area.polygons]] +vertices = [[3200, 2529]] + +[[area.polygons]] +vertices = [ + [3082, 2530], + [3082, 2540], +] + +[[area.polygons]] +vertices = [ + [3196, 2530], + [3198, 2530], +] + +[[area.polygons]] +vertices = [ + [3196, 2531], + [3197, 2531], +] + +[[area.polygons]] +vertices = [ + [3081, 2533], + [3081, 2541], +] + +[[area.polygons]] +vertices = [[3194, 2533]] + +[[area.polygons]] +vertices = [ + [3079, 2534], + [3080, 2534], + [3080, 2540], + [3079, 2540], +] + +[[area.polygons]] +vertices = [ + [3078, 2535], + [3078, 2539], +] + +[[area.polygons]] +vertices = [ + [3077, 2536], + [3077, 2538], +] + +[[area.polygons]] +vertices = [ + [3190, 2536], + [3191, 2536], +] + +[[area.polygons]] +vertices = [[3153, 2537]] + +[[area.polygons]] +vertices = [[3157, 2537]] + +[[area.polygons]] +vertices = [ + [3190, 2537], + [3190, 2538], +] + +[[area.polygons]] +vertices = [ + [3140, 2538], + [3140, 2546], +] + +[[area.polygons]] +vertices = [ + [3149, 2538], + [3150, 2538], +] + +[[area.polygons]] +vertices = [ + [3162, 2538], + [3167, 2538], +] + +[[area.polygons]] +vertices = [ + [3164, 2539], + [3167, 2539], +] + +[[area.polygons]] +vertices = [[3165, 2540]] + +[[area.polygons]] +vertices = [ + [3141, 2541], + [3141, 2545], +] + +[[area.polygons]] +vertices = [[3171, 2541]] + +[[area.polygons]] +vertices = [ + [3186, 2541], + [3187, 2541], +] + +[[area.polygons]] +vertices = [ + [3142, 2542], + [3142, 2543], +] + +[[area.polygons]] +vertices = [[3173, 2542]] + +[[area.polygons]] +vertices = [[3175, 2543]] + +[[area.polygons]] +vertices = [[3183, 2543]] + +[[area.polygons]] +vertices = [ + [3110, 2546], + [3112, 2546], +] + +[[area.polygons]] +vertices = [ + [3114, 2546], + [3116, 2546], +] + +[[area.polygons]] +vertices = [ + [3130, 2547], + [3131, 2547], +] + +[[area.polygons]] +vertices = [ + [3133, 2548], + [3134, 2548], +] + +[[area.polygons]] +vertices = [[3138, 2548]] + +[[area.polygons]] +vertices = [[3098, 2551]] diff --git a/.data/raw-cache/map/area/the_guardian_of_tapoyauik.toml b/.data/raw-cache/map/area/the_guardian_of_tapoyauik.toml new file mode 100644 index 000000000..618c9d9c6 --- /dev/null +++ b/.data/raw-cache/map/area/the_guardian_of_tapoyauik.toml @@ -0,0 +1,16 @@ +# The Guardian of Tapoyauik music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Guardian of Tapoyauik. +# Bounds x 1344-1407, z 4480-4543; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Guardian of Tapoyauik" +area_id = "area.the_guardian_of_tapoyauik" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1344, 4480], + [1407, 4480], + [1407, 4543], + [1344, 4543], +] diff --git a/.data/raw-cache/map/area/the_guidance_of_ralos.toml b/.data/raw-cache/map/area/the_guidance_of_ralos.toml new file mode 100644 index 000000000..e9bc18b64 --- /dev/null +++ b/.data/raw-cache/map/area/the_guidance_of_ralos.toml @@ -0,0 +1,200 @@ +# The Guidance of Ralos music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Guidance of Ralos. +# Bounds x 1414-1467, z 3122-3217; 3400 tiles as 31 rectangles. + +[[area]] +name = "The Guidance of Ralos" +area_id = "area.the_guidance_of_ralos" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1447, 3122], + [1452, 3122], + [1452, 3210], + [1447, 3210], +] + +[[area.polygons]] +vertices = [ + [1418, 3123], + [1424, 3123], + [1424, 3150], + [1418, 3150], +] + +[[area.polygons]] +vertices = [ + [1453, 3123], + [1453, 3210], +] + +[[area.polygons]] +vertices = [ + [1454, 3124], + [1454, 3210], +] + +[[area.polygons]] +vertices = [ + [1455, 3125], + [1455, 3210], +] + +[[area.polygons]] +vertices = [ + [1456, 3126], + [1456, 3210], +] + +[[area.polygons]] +vertices = [ + [1457, 3127], + [1457, 3202], +] + +[[area.polygons]] +vertices = [ + [1458, 3128], + [1458, 3202], +] + +[[area.polygons]] +vertices = [ + [1459, 3129], + [1459, 3202], +] + +[[area.polygons]] +vertices = [ + [1460, 3130], + [1460, 3202], +] + +[[area.polygons]] +vertices = [ + [1425, 3131], + [1446, 3131], + [1446, 3190], + [1425, 3190], +] + +[[area.polygons]] +vertices = [ + [1461, 3131], + [1461, 3202], +] + +[[area.polygons]] +vertices = [ + [1462, 3132], + [1462, 3202], +] + +[[area.polygons]] +vertices = [ + [1463, 3133], + [1463, 3202], +] + +[[area.polygons]] +vertices = [ + [1464, 3134], + [1464, 3202], +] + +[[area.polygons]] +vertices = [ + [1417, 3143], + [1417, 3149], +] + +[[area.polygons]] +vertices = [ + [1416, 3144], + [1416, 3148], +] + +[[area.polygons]] +vertices = [ + [1415, 3145], + [1415, 3147], +] + +[[area.polygons]] +vertices = [[1414, 3146]] + +[[area.polygons]] +vertices = [ + [1419, 3151], + [1424, 3151], +] + +[[area.polygons]] +vertices = [ + [1420, 3152], + [1424, 3152], +] + +[[area.polygons]] +vertices = [ + [1421, 3153], + [1424, 3153], +] + +[[area.polygons]] +vertices = [ + [1422, 3154], + [1424, 3154], +] + +[[area.polygons]] +vertices = [ + [1423, 3155], + [1424, 3155], +] + +[[area.polygons]] +vertices = [[1424, 3156]] + +[[area.polygons]] +vertices = [ + [1465, 3185], + [1465, 3202], +] + +[[area.polygons]] +vertices = [ + [1466, 3186], + [1466, 3202], +] + +[[area.polygons]] +vertices = [ + [1467, 3187], + [1467, 3202], +] + +[[area.polygons]] +vertices = [ + [1434, 3191], + [1446, 3191], + [1446, 3212], + [1434, 3212], +] + +[[area.polygons]] +vertices = [ + [1447, 3211], + [1451, 3211], + [1451, 3217], + [1447, 3217], +] + +[[area.polygons]] +vertices = [ + [1444, 3213], + [1446, 3213], + [1446, 3217], + [1444, 3217], +] diff --git a/.data/raw-cache/map/area/the_houses_of_kourend.toml b/.data/raw-cache/map/area/the_houses_of_kourend.toml new file mode 100644 index 000000000..25b4cb68e --- /dev/null +++ b/.data/raw-cache/map/area/the_houses_of_kourend.toml @@ -0,0 +1,16 @@ +# The Houses of Kourend music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Houses of Kourend. +# Bounds x 1536-1599, z 9920-9983; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Houses of Kourend" +area_id = "area.the_houses_of_kourend" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1536, 9920], + [1599, 9920], + [1599, 9983], + [1536, 9983], +] diff --git a/.data/raw-cache/map/area/the_longramble_scramble.toml b/.data/raw-cache/map/area/the_longramble_scramble.toml new file mode 100644 index 000000000..5114f623b --- /dev/null +++ b/.data/raw-cache/map/area/the_longramble_scramble.toml @@ -0,0 +1,16 @@ +# The Longramble Scramble music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Longramble Scramble. +# Bounds x 2310-2359, z 3080-3135; 2800 tiles as 1 rectangles. + +[[area]] +name = "The Longramble Scramble" +area_id = "area.the_longramble_scramble" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2310, 3080], + [2359, 3080], + [2359, 3135], + [2310, 3135], +] diff --git a/.data/raw-cache/map/area/the_lost_melody.toml b/.data/raw-cache/map/area/the_lost_melody.toml new file mode 100644 index 000000000..7b57f30bb --- /dev/null +++ b/.data/raw-cache/map/area/the_lost_melody.toml @@ -0,0 +1,16 @@ +# The Lost Melody music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Lost Melody. +# Bounds x 3264-3327, z 9600-9663; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Lost Melody" +area_id = "area.the_lost_melody" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 9600], + [3327, 9600], + [3327, 9663], + [3264, 9663], +] diff --git a/.data/raw-cache/map/area/the_lost_tribe.toml b/.data/raw-cache/map/area/the_lost_tribe.toml new file mode 100644 index 000000000..9e9975c1b --- /dev/null +++ b/.data/raw-cache/map/area/the_lost_tribe.toml @@ -0,0 +1,16 @@ +# The Lost Tribe music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Lost Tribe. +# Bounds x 3220-3263, z 9600-9663; 2816 tiles as 1 rectangles. + +[[area]] +name = "The Lost Tribe" +area_id = "area.the_lost_tribe" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3220, 9600], + [3263, 9600], + [3263, 9663], + [3220, 9663], +] diff --git a/.data/raw-cache/map/area/the_mad_mole.toml b/.data/raw-cache/map/area/the_mad_mole.toml new file mode 100644 index 000000000..454739047 --- /dev/null +++ b/.data/raw-cache/map/area/the_mad_mole.toml @@ -0,0 +1,16 @@ +# The Mad Mole music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Mad Mole. +# Bounds x 1728-1791, z 5120-5247; 8192 tiles as 1 rectangles. + +[[area]] +name = "The Mad Mole" +area_id = "area.the_mad_mole" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1728, 5120], + [1791, 5120], + [1791, 5247], + [1728, 5247], +] diff --git a/.data/raw-cache/map/area/the_maidens_anger.toml b/.data/raw-cache/map/area/the_maidens_anger.toml new file mode 100644 index 000000000..141f1ede1 --- /dev/null +++ b/.data/raw-cache/map/area/the_maidens_anger.toml @@ -0,0 +1,16 @@ +# The Maiden's Anger music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Maiden's Anger. +# Bounds x 3136-3263, z 4416-4479; 8192 tiles as 1 rectangles. + +[[area]] +name = "The Maiden's Anger" +area_id = "area.the_maidens_anger" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 4416], + [3263, 4416], + [3263, 4479], + [3136, 4479], +] diff --git a/.data/raw-cache/map/area/the_maze.toml b/.data/raw-cache/map/area/the_maze.toml new file mode 100644 index 000000000..991da4065 --- /dev/null +++ b/.data/raw-cache/map/area/the_maze.toml @@ -0,0 +1,80 @@ +# The Maze music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Maze. +# Bounds x 2816-2943, z 3237-9663; 12640 tiles as 10 rectangles. + +[[area]] +name = "The Maze" +area_id = "area.the_maze" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2937, 3237], + [2939, 3237], + [2939, 3257], + [2937, 3257], +] + +[[area.polygons]] +vertices = [ + [2927, 3238], + [2929, 3238], + [2929, 3257], + [2927, 3257], +] + +[[area.polygons]] +vertices = [ + [2940, 3238], + [2940, 3257], +] + +[[area.polygons]] +vertices = [ + [2930, 3239], + [2930, 3257], +] + +[[area.polygons]] +vertices = [ + [2923, 3240], + [2926, 3240], + [2926, 3257], + [2923, 3257], +] + +[[area.polygons]] +vertices = [ + [2931, 3240], + [2936, 3240], + [2936, 3257], + [2931, 3257], +] + +[[area.polygons]] +vertices = [ + [2922, 3251], + [2922, 3256], +] + +[[area.polygons]] +vertices = [ + [2921, 3252], + [2921, 3255], +] + +[[area.polygons]] +vertices = [ + [2816, 9216], + [2879, 9216], + [2879, 9343], + [2816, 9343], +] + +[[area.polygons]] +vertices = [ + [2880, 9600], + [2943, 9600], + [2943, 9663], + [2880, 9663], +] diff --git a/.data/raw-cache/map/area/the_militia.toml b/.data/raw-cache/map/area/the_militia.toml new file mode 100644 index 000000000..631bc9eec --- /dev/null +++ b/.data/raw-cache/map/area/the_militia.toml @@ -0,0 +1,650 @@ +# The Militia music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Militia. +# Bounds x 1408-1604, z 3516-9983; 12055 tiles as 101 rectangles. + +[[area]] +name = "The Militia" +area_id = "area.the_militia" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1581, 3516], + [1593, 3516], + [1593, 3547], + [1581, 3547], +] + +[[area.polygons]] +vertices = [ + [1578, 3517], + [1580, 3517], + [1580, 3547], + [1578, 3547], +] + +[[area.polygons]] +vertices = [ + [1594, 3517], + [1595, 3517], + [1595, 3547], + [1594, 3547], +] + +[[area.polygons]] +vertices = [ + [1576, 3518], + [1577, 3518], + [1577, 3547], + [1576, 3547], +] + +[[area.polygons]] +vertices = [ + [1596, 3518], + [1597, 3518], + [1597, 3547], + [1596, 3547], +] + +[[area.polygons]] +vertices = [ + [1573, 3519], + [1575, 3519], + [1575, 3580], + [1573, 3580], +] + +[[area.polygons]] +vertices = [ + [1598, 3519], + [1599, 3519], + [1599, 3547], + [1598, 3547], +] + +[[area.polygons]] +vertices = [ + [1572, 3520], + [1572, 3579], +] + +[[area.polygons]] +vertices = [ + [1600, 3520], + [1601, 3520], + [1601, 3547], + [1600, 3547], +] + +[[area.polygons]] +vertices = [ + [1602, 3521], + [1603, 3521], + [1603, 3547], + [1602, 3547], +] + +[[area.polygons]] +vertices = [ + [1604, 3522], + [1604, 3547], +] + +[[area.polygons]] +vertices = [ + [1541, 3526], + [1571, 3526], + [1571, 3576], + [1541, 3576], +] + +[[area.polygons]] +vertices = [ + [1540, 3527], + [1540, 3587], +] + +[[area.polygons]] +vertices = [ + [1539, 3528], + [1539, 3589], +] + +[[area.polygons]] +vertices = [ + [1538, 3529], + [1538, 3590], +] + +[[area.polygons]] +vertices = [ + [1523, 3530], + [1537, 3530], + [1537, 3591], + [1523, 3591], +] + +[[area.polygons]] +vertices = [ + [1521, 3531], + [1522, 3531], + [1522, 3598], + [1521, 3598], +] + +[[area.polygons]] +vertices = [ + [1520, 3532], + [1520, 3599], +] + +[[area.polygons]] +vertices = [ + [1518, 3533], + [1519, 3533], + [1519, 3599], + [1518, 3599], +] + +[[area.polygons]] +vertices = [ + [1516, 3534], + [1517, 3534], + [1517, 3599], + [1516, 3599], +] + +[[area.polygons]] +vertices = [ + [1515, 3535], + [1515, 3599], +] + +[[area.polygons]] +vertices = [ + [1493, 3536], + [1514, 3536], + [1514, 3599], + [1493, 3599], +] + +[[area.polygons]] +vertices = [ + [1492, 3537], + [1492, 3605], +] + +[[area.polygons]] +vertices = [ + [1491, 3538], + [1491, 3605], +] + +[[area.polygons]] +vertices = [ + [1490, 3539], + [1490, 3605], +] + +[[area.polygons]] +vertices = [ + [1470, 3540], + [1489, 3540], + [1489, 3591], + [1470, 3591], +] + +[[area.polygons]] +vertices = [ + [1469, 3541], + [1469, 3590], +] + +[[area.polygons]] +vertices = [ + [1468, 3542], + [1468, 3590], +] + +[[area.polygons]] +vertices = [ + [1467, 3544], + [1467, 3589], +] + +[[area.polygons]] +vertices = [ + [1466, 3546], + [1466, 3587], +] + +[[area.polygons]] +vertices = [ + [1465, 3547], + [1465, 3582], +] + +[[area.polygons]] +vertices = [ + [1464, 3549], + [1464, 3577], +] + +[[area.polygons]] +vertices = [ + [1463, 3551], + [1463, 3574], +] + +[[area.polygons]] +vertices = [ + [1462, 3552], + [1462, 3573], +] + +[[area.polygons]] +vertices = [ + [1461, 3564], + [1461, 3572], +] + +[[area.polygons]] +vertices = [ + [1460, 3566], + [1460, 3571], +] + +[[area.polygons]] +vertices = [ + [1459, 3569], + [1459, 3570], +] + +[[area.polygons]] +vertices = [ + [1576, 3573], + [1576, 3582], +] + +[[area.polygons]] +vertices = [ + [1577, 3574], + [1577, 3582], +] + +[[area.polygons]] +vertices = [ + [1578, 3575], + [1578, 3582], +] + +[[area.polygons]] +vertices = [ + [1579, 3576], + [1579, 3582], +] + +[[area.polygons]] +vertices = [ + [1541, 3577], + [1547, 3577], +] + +[[area.polygons]] +vertices = [ + [1569, 3577], + [1571, 3577], +] + +[[area.polygons]] +vertices = [ + [1541, 3578], + [1546, 3578], +] + +[[area.polygons]] +vertices = [ + [1570, 3578], + [1571, 3578], +] + +[[area.polygons]] +vertices = [ + [1580, 3578], + [1583, 3578], + [1583, 3582], + [1580, 3582], +] + +[[area.polygons]] +vertices = [ + [1541, 3579], + [1545, 3579], + [1545, 3580], + [1541, 3580], +] + +[[area.polygons]] +vertices = [ + [1584, 3579], + [1591, 3579], + [1591, 3583], + [1584, 3583], +] + +[[area.polygons]] +vertices = [ + [1592, 3580], + [1595, 3580], + [1595, 3586], + [1592, 3586], +] + +[[area.polygons]] +vertices = [ + [1541, 3581], + [1544, 3581], +] + +[[area.polygons]] +vertices = [[1575, 3581]] + +[[area.polygons]] +vertices = [ + [1596, 3581], + [1596, 3591], +] + +[[area.polygons]] +vertices = [ + [1541, 3582], + [1543, 3582], + [1543, 3583], + [1541, 3583], +] + +[[area.polygons]] +vertices = [ + [1597, 3582], + [1597, 3593], +] + +[[area.polygons]] +vertices = [[1583, 3583]] + +[[area.polygons]] +vertices = [ + [1598, 3583], + [1598, 3608], +] + +[[area.polygons]] +vertices = [ + [1541, 3584], + [1542, 3584], +] + +[[area.polygons]] +vertices = [ + [1586, 3584], + [1591, 3584], +] + +[[area.polygons]] +vertices = [ + [1599, 3584], + [1599, 3608], +] + +[[area.polygons]] +vertices = [ + [1541, 3585], + [1541, 3586], +] + +[[area.polygons]] +vertices = [ + [1589, 3585], + [1591, 3585], +] + +[[area.polygons]] +vertices = [ + [1600, 3585], + [1600, 3607], +] + +[[area.polygons]] +vertices = [ + [1601, 3586], + [1601, 3606], +] + +[[area.polygons]] +vertices = [ + [1594, 3587], + [1595, 3587], +] + +[[area.polygons]] +vertices = [ + [1595, 3588], + [1595, 3589], +] + +[[area.polygons]] +vertices = [ + [1602, 3588], + [1602, 3603], +] + +[[area.polygons]] +vertices = [ + [1473, 3592], + [1489, 3592], +] + +[[area.polygons]] +vertices = [ + [1523, 3592], + [1528, 3592], +] + +[[area.polygons]] +vertices = [ + [1603, 3592], + [1603, 3601], +] + +[[area.polygons]] +vertices = [ + [1475, 3593], + [1489, 3593], +] + +[[area.polygons]] +vertices = [ + [1523, 3593], + [1527, 3593], +] + +[[area.polygons]] +vertices = [ + [1476, 3594], + [1489, 3594], +] + +[[area.polygons]] +vertices = [ + [1523, 3594], + [1526, 3594], +] + +[[area.polygons]] +vertices = [ + [1477, 3595], + [1489, 3595], +] + +[[area.polygons]] +vertices = [ + [1523, 3595], + [1525, 3595], +] + +[[area.polygons]] +vertices = [ + [1478, 3596], + [1489, 3596], +] + +[[area.polygons]] +vertices = [ + [1523, 3596], + [1524, 3596], +] + +[[area.polygons]] +vertices = [ + [1604, 3596], + [1604, 3598], +] + +[[area.polygons]] +vertices = [ + [1479, 3597], + [1489, 3597], +] + +[[area.polygons]] +vertices = [[1523, 3597]] + +[[area.polygons]] +vertices = [ + [1597, 3597], + [1597, 3609], +] + +[[area.polygons]] +vertices = [ + [1480, 3598], + [1489, 3598], +] + +[[area.polygons]] +vertices = [ + [1481, 3599], + [1489, 3599], + [1489, 3600], + [1481, 3600], +] + +[[area.polygons]] +vertices = [[1521, 3599]] + +[[area.polygons]] +vertices = [ + [1493, 3600], + [1510, 3600], +] + +[[area.polygons]] +vertices = [ + [1482, 3601], + [1489, 3601], +] + +[[area.polygons]] +vertices = [ + [1493, 3601], + [1509, 3601], +] + +[[area.polygons]] +vertices = [ + [1483, 3602], + [1489, 3602], +] + +[[area.polygons]] +vertices = [ + [1493, 3602], + [1508, 3602], +] + +[[area.polygons]] +vertices = [ + [1596, 3602], + [1596, 3609], +] + +[[area.polygons]] +vertices = [ + [1484, 3603], + [1489, 3603], +] + +[[area.polygons]] +vertices = [ + [1493, 3603], + [1507, 3603], +] + +[[area.polygons]] +vertices = [ + [1485, 3604], + [1489, 3604], +] + +[[area.polygons]] +vertices = [ + [1493, 3604], + [1506, 3604], +] + +[[area.polygons]] +vertices = [ + [1595, 3604], + [1595, 3610], +] + +[[area.polygons]] +vertices = [ + [1486, 3605], + [1489, 3605], +] + +[[area.polygons]] +vertices = [ + [1493, 3605], + [1505, 3605], +] + +[[area.polygons]] +vertices = [ + [1592, 3605], + [1594, 3605], + [1594, 3609], + [1592, 3609], +] + +[[area.polygons]] +vertices = [ + [1591, 3606], + [1591, 3609], +] + +[[area.polygons]] +vertices = [ + [1593, 3610], + [1594, 3610], +] + +[[area.polygons]] +vertices = [ + [1408, 9920], + [1471, 9920], + [1471, 9983], + [1408, 9983], +] diff --git a/.data/raw-cache/map/area/the_mollusc_menace.toml b/.data/raw-cache/map/area/the_mollusc_menace.toml new file mode 100644 index 000000000..3c3810e3d --- /dev/null +++ b/.data/raw-cache/map/area/the_mollusc_menace.toml @@ -0,0 +1,335 @@ +# The Mollusc Menace music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Mollusc Menace. +# Bounds x 2686-2746, z 3264-3312; 1795 tiles as 52 rectangles. + +[[area]] +name = "The Mollusc Menace" +area_id = "area.the_mollusc_menace" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2691, 3264], + [2695, 3264], + [2695, 3298], + [2691, 3298], +] + +[[area.polygons]] +vertices = [ + [2689, 3265], + [2690, 3265], + [2690, 3298], + [2689, 3298], +] + +[[area.polygons]] +vertices = [ + [2696, 3265], + [2696, 3298], +] + +[[area.polygons]] +vertices = [ + [2719, 3265], + [2721, 3265], + [2721, 3294], + [2719, 3294], +] + +[[area.polygons]] +vertices = [ + [2697, 3266], + [2697, 3298], +] + +[[area.polygons]] +vertices = [ + [2714, 3266], + [2718, 3266], + [2718, 3296], + [2714, 3296], +] + +[[area.polygons]] +vertices = [ + [2722, 3266], + [2724, 3266], + [2724, 3295], + [2722, 3295], +] + +[[area.polygons]] +vertices = [ + [2711, 3267], + [2713, 3267], + [2713, 3302], + [2711, 3302], +] + +[[area.polygons]] +vertices = [ + [2725, 3267], + [2725, 3296], +] + +[[area.polygons]] +vertices = [ + [2730, 3267], + [2732, 3267], + [2732, 3299], + [2730, 3299], +] + +[[area.polygons]] +vertices = [ + [2698, 3268], + [2698, 3271], +] + +[[area.polygons]] +vertices = [ + [2707, 3268], + [2710, 3268], + [2710, 3299], + [2707, 3299], +] + +[[area.polygons]] +vertices = [ + [2726, 3268], + [2729, 3268], + [2729, 3297], + [2726, 3297], +] + +[[area.polygons]] +vertices = [ + [2733, 3268], + [2733, 3303], +] + +[[area.polygons]] +vertices = [ + [2688, 3269], + [2688, 3271], +] + +[[area.polygons]] +vertices = [ + [2706, 3269], + [2706, 3299], +] + +[[area.polygons]] +vertices = [ + [2734, 3269], + [2736, 3269], + [2736, 3303], + [2734, 3303], +] + +[[area.polygons]] +vertices = [ + [2705, 3270], + [2705, 3299], +] + +[[area.polygons]] +vertices = [ + [2737, 3270], + [2737, 3306], +] + +[[area.polygons]] +vertices = [ + [2738, 3271], + [2738, 3276], +] + +[[area.polygons]] +vertices = [ + [2704, 3272], + [2704, 3299], +] + +[[area.polygons]] +vertices = [ + [2739, 3272], + [2739, 3275], +] + +[[area.polygons]] +vertices = [ + [2698, 3273], + [2698, 3298], +] + +[[area.polygons]] +vertices = [ + [2703, 3273], + [2703, 3298], +] + +[[area.polygons]] +vertices = [ + [2688, 3274], + [2688, 3298], +] + +[[area.polygons]] +vertices = [ + [2699, 3274], + [2702, 3274], + [2702, 3298], + [2699, 3298], +] + +[[area.polygons]] +vertices = [ + [2738, 3283], + [2738, 3306], +] + +[[area.polygons]] +vertices = [ + [2687, 3288], + [2687, 3298], +] + +[[area.polygons]] +vertices = [ + [2686, 3289], + [2686, 3297], +] + +[[area.polygons]] +vertices = [ + [2739, 3293], + [2739, 3297], +] + +[[area.polygons]] +vertices = [[2719, 3295]] + +[[area.polygons]] +vertices = [[2721, 3295]] + +[[area.polygons]] +vertices = [ + [2714, 3297], + [2717, 3297], + [2717, 3305], + [2714, 3305], +] + +[[area.polygons]] +vertices = [ + [2718, 3298], + [2718, 3310], +] + +[[area.polygons]] +vertices = [ + [2728, 3298], + [2729, 3298], +] + +[[area.polygons]] +vertices = [[2729, 3299]] + +[[area.polygons]] +vertices = [ + [2731, 3300], + [2732, 3300], +] + +[[area.polygons]] +vertices = [ + [2719, 3301], + [2720, 3301], + [2720, 3311], + [2719, 3311], +] + +[[area.polygons]] +vertices = [ + [2732, 3301], + [2732, 3302], +] + +[[area.polygons]] +vertices = [ + [2739, 3301], + [2740, 3301], + [2740, 3311], + [2739, 3311], +] + +[[area.polygons]] +vertices = [ + [2721, 3302], + [2721, 3312], +] + +[[area.polygons]] +vertices = [ + [2741, 3302], + [2741, 3305], +] + +[[area.polygons]] +vertices = [ + [2712, 3303], + [2713, 3303], +] + +[[area.polygons]] +vertices = [ + [2722, 3303], + [2722, 3312], +] + +[[area.polygons]] +vertices = [[2713, 3304]] + +[[area.polygons]] +vertices = [ + [2735, 3304], + [2736, 3304], +] + +[[area.polygons]] +vertices = [ + [2742, 3304], + [2746, 3304], + [2746, 3305], + [2742, 3305], +] + +[[area.polygons]] +vertices = [[2736, 3305]] + +[[area.polygons]] +vertices = [ + [2715, 3306], + [2717, 3306], +] + +[[area.polygons]] +vertices = [ + [2723, 3306], + [2723, 3310], +] + +[[area.polygons]] +vertices = [ + [2716, 3307], + [2717, 3307], +] + +[[area.polygons]] +vertices = [ + [2717, 3308], + [2717, 3309], +] diff --git a/.data/raw-cache/map/area/the_monsters_below.toml b/.data/raw-cache/map/area/the_monsters_below.toml new file mode 100644 index 000000000..59b029eec --- /dev/null +++ b/.data/raw-cache/map/area/the_monsters_below.toml @@ -0,0 +1,16 @@ +# The Monsters Below music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Monsters Below. +# Bounds x 2432-2495, z 10112-10175; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Monsters Below" +area_id = "area.the_monsters_below" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2432, 10112], + [2495, 10112], + [2495, 10175], + [2432, 10175], +] diff --git a/.data/raw-cache/map/area/the_moons_of_ruin.toml b/.data/raw-cache/map/area/the_moons_of_ruin.toml new file mode 100644 index 000000000..cfd69f24f --- /dev/null +++ b/.data/raw-cache/map/area/the_moons_of_ruin.toml @@ -0,0 +1,32 @@ +# The Moons of Ruin music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Moons of Ruin. +# Bounds x 1344-1535, z 9601-9727; 12160 tiles as 3 rectangles. + +[[area]] +name = "The Moons of Ruin" +area_id = "area.the_moons_of_ruin" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1344, 9601], + [1407, 9601], + [1407, 9663], + [1344, 9663], +] + +[[area.polygons]] +vertices = [ + [1472, 9601], + [1535, 9601], + [1535, 9663], + [1472, 9663], +] + +[[area.polygons]] +vertices = [ + [1408, 9664], + [1471, 9664], + [1471, 9727], + [1408, 9727], +] diff --git a/.data/raw-cache/map/area/the_navigator.toml b/.data/raw-cache/map/area/the_navigator.toml new file mode 100644 index 000000000..2d1fc6354 --- /dev/null +++ b/.data/raw-cache/map/area/the_navigator.toml @@ -0,0 +1,16 @@ +# The Navigator music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Navigator. +# Bounds x 2624-2687, z 9984-10047; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Navigator" +area_id = "area.the_navigator" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2624, 9984], + [2687, 9984], + [2687, 10047], + [2624, 10047], +] diff --git a/.data/raw-cache/map/area/the_nightmare_continues.toml b/.data/raw-cache/map/area/the_nightmare_continues.toml new file mode 100644 index 000000000..6a6267462 --- /dev/null +++ b/.data/raw-cache/map/area/the_nightmare_continues.toml @@ -0,0 +1,16 @@ +# The Nightmare Continues music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Nightmare Continues. +# Bounds x 3264-3327, z 4416-4479; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Nightmare Continues" +area_id = "area.the_nightmare_continues" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 4416], + [3327, 4416], + [3327, 4479], + [3264, 4479], +] diff --git a/.data/raw-cache/map/area/the_north.toml b/.data/raw-cache/map/area/the_north.toml new file mode 100644 index 000000000..353353a28 --- /dev/null +++ b/.data/raw-cache/map/area/the_north.toml @@ -0,0 +1,16 @@ +# The North music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The North. +# Bounds x 2816-2879, z 3840-3903; 4096 tiles as 1 rectangles. + +[[area]] +name = "The North" +area_id = "area.the_north" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 3840], + [2879, 3840], + [2879, 3903], + [2816, 3903], +] diff --git a/.data/raw-cache/map/area/the_other_side.toml b/.data/raw-cache/map/area/the_other_side.toml new file mode 100644 index 000000000..58659192e --- /dev/null +++ b/.data/raw-cache/map/area/the_other_side.toml @@ -0,0 +1,215 @@ +# The Other Side music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Other Side. +# Bounds x 3648-3719, z 3454-3554; 6175 tiles as 32 rectangles. + +[[area]] +name = "The Other Side" +area_id = "area.the_other_side" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3665, 3454], + [3673, 3454], + [3673, 3554], + [3665, 3554], +] + +[[area.polygons]] +vertices = [ + [3694, 3454], + [3700, 3454], + [3700, 3523], + [3694, 3523], +] + +[[area.polygons]] +vertices = [ + [3661, 3455], + [3664, 3455], + [3664, 3554], + [3661, 3554], +] + +[[area.polygons]] +vertices = [ + [3674, 3455], + [3674, 3554], +] + +[[area.polygons]] +vertices = [ + [3693, 3455], + [3693, 3554], +] + +[[area.polygons]] +vertices = [ + [3701, 3455], + [3719, 3455], + [3719, 3504], + [3701, 3504], +] + +[[area.polygons]] +vertices = [ + [3660, 3456], + [3660, 3554], +] + +[[area.polygons]] +vertices = [ + [3675, 3456], + [3692, 3456], + [3692, 3554], + [3675, 3554], +] + +[[area.polygons]] +vertices = [ + [3651, 3457], + [3659, 3457], + [3659, 3472], + [3651, 3472], +] + +[[area.polygons]] +vertices = [ + [3650, 3458], + [3650, 3471], +] + +[[area.polygons]] +vertices = [ + [3652, 3473], + [3659, 3473], +] + +[[area.polygons]] +vertices = [ + [3653, 3474], + [3659, 3474], + [3659, 3554], + [3653, 3554], +] + +[[area.polygons]] +vertices = [ + [3648, 3504], + [3652, 3504], + [3652, 3554], + [3648, 3554], +] + +[[area.polygons]] +vertices = [ + [3701, 3505], + [3718, 3505], +] + +[[area.polygons]] +vertices = [ + [3701, 3506], + [3717, 3506], +] + +[[area.polygons]] +vertices = [ + [3701, 3507], + [3716, 3507], +] + +[[area.polygons]] +vertices = [ + [3701, 3508], + [3715, 3508], +] + +[[area.polygons]] +vertices = [ + [3701, 3509], + [3714, 3509], +] + +[[area.polygons]] +vertices = [ + [3701, 3510], + [3713, 3510], +] + +[[area.polygons]] +vertices = [ + [3701, 3511], + [3712, 3511], +] + +[[area.polygons]] +vertices = [ + [3701, 3512], + [3711, 3512], +] + +[[area.polygons]] +vertices = [ + [3701, 3513], + [3710, 3513], +] + +[[area.polygons]] +vertices = [ + [3701, 3514], + [3709, 3514], +] + +[[area.polygons]] +vertices = [ + [3701, 3515], + [3708, 3515], +] + +[[area.polygons]] +vertices = [ + [3701, 3516], + [3707, 3516], +] + +[[area.polygons]] +vertices = [ + [3701, 3517], + [3706, 3517], +] + +[[area.polygons]] +vertices = [ + [3701, 3518], + [3705, 3518], +] + +[[area.polygons]] +vertices = [ + [3701, 3519], + [3704, 3519], +] + +[[area.polygons]] +vertices = [ + [3701, 3520], + [3703, 3520], +] + +[[area.polygons]] +vertices = [ + [3701, 3521], + [3702, 3521], +] + +[[area.polygons]] +vertices = [[3701, 3522]] + +[[area.polygons]] +vertices = [ + [3694, 3524], + [3699, 3524], + [3699, 3554], + [3694, 3554], +] diff --git a/.data/raw-cache/map/area/the_pandemonium.toml b/.data/raw-cache/map/area/the_pandemonium.toml new file mode 100644 index 000000000..adaa06cf0 --- /dev/null +++ b/.data/raw-cache/map/area/the_pandemonium.toml @@ -0,0 +1,348 @@ +# The Pandemonium music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Honkytonky Sea Shanty 2; Yo Ho Ho!. +# Bounds x 3025-3072, z 2960-3010; 1292 tiles as 55 rectangles. + +[[area]] +name = "The Pandemonium" +area_id = "area.the_pandemonium" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3037, 2960], + [3038, 2960], + [3038, 2963], + [3037, 2963], +] + +[[area.polygons]] +vertices = [[3036, 2961]] + +[[area.polygons]] +vertices = [ + [3039, 2961], + [3040, 2961], + [3040, 2965], + [3039, 2965], +] + +[[area.polygons]] +vertices = [ + [3041, 2962], + [3041, 2967], +] + +[[area.polygons]] +vertices = [ + [3045, 2963], + [3052, 2963], + [3052, 3003], + [3045, 3003], +] + +[[area.polygons]] +vertices = [ + [3030, 2964], + [3033, 2964], + [3033, 2967], + [3030, 2967], +] + +[[area.polygons]] +vertices = [[3038, 2964]] + +[[area.polygons]] +vertices = [ + [3029, 2965], + [3029, 2966], +] + +[[area.polygons]] +vertices = [ + [3034, 2965], + [3034, 2969], +] + +[[area.polygons]] +vertices = [ + [3042, 2965], + [3044, 2965], + [3044, 2967], + [3042, 2967], +] + +[[area.polygons]] +vertices = [ + [3053, 2965], + [3053, 2967], +] + +[[area.polygons]] +vertices = [ + [3035, 2966], + [3035, 3002], +] + +[[area.polygons]] +vertices = [[3054, 2966]] + +[[area.polygons]] +vertices = [ + [3036, 2967], + [3036, 3003], +] + +[[area.polygons]] +vertices = [ + [3031, 2968], + [3033, 2968], +] + +[[area.polygons]] +vertices = [ + [3037, 2968], + [3038, 2968], + [3038, 3003], + [3037, 3003], +] + +[[area.polygons]] +vertices = [ + [3039, 2969], + [3040, 2969], + [3040, 2976], + [3039, 2976], +] + +[[area.polygons]] +vertices = [ + [3044, 2969], + [3044, 3003], +] + +[[area.polygons]] +vertices = [ + [3041, 2970], + [3041, 2975], +] + +[[area.polygons]] +vertices = [ + [3053, 2973], + [3053, 3002], +] + +[[area.polygons]] +vertices = [ + [3056, 2973], + [3064, 2973], + [3064, 2975], + [3056, 2975], +] + +[[area.polygons]] +vertices = [ + [3034, 2974], + [3034, 3001], +] + +[[area.polygons]] +vertices = [ + [3054, 2974], + [3055, 2974], + [3055, 2995], + [3054, 2995], +] + +[[area.polygons]] +vertices = [[3065, 2974]] + +[[area.polygons]] +vertices = [ + [3026, 2975], + [3027, 2975], + [3027, 2981], + [3026, 2981], +] + +[[area.polygons]] +vertices = [[3025, 2976]] + +[[area.polygons]] +vertices = [ + [3028, 2976], + [3028, 2991], +] + +[[area.polygons]] +vertices = [ + [3033, 2976], + [3033, 2997], +] + +[[area.polygons]] +vertices = [ + [3056, 2976], + [3059, 2976], + [3059, 2995], + [3056, 2995], +] + +[[area.polygons]] +vertices = [ + [3029, 2977], + [3032, 2977], + [3032, 2991], + [3029, 2991], +] + +[[area.polygons]] +vertices = [[3039, 2977]] + +[[area.polygons]] +vertices = [ + [3060, 2978], + [3064, 2978], + [3064, 2980], + [3060, 2980], +] + +[[area.polygons]] +vertices = [[3065, 2979]] + +[[area.polygons]] +vertices = [ + [3039, 2981], + [3043, 2981], + [3043, 3001], + [3039, 3001], +] + +[[area.polygons]] +vertices = [ + [3027, 2982], + [3027, 2984], +] + +[[area.polygons]] +vertices = [ + [3060, 2984], + [3061, 2984], + [3061, 2993], + [3060, 2993], +] + +[[area.polygons]] +vertices = [ + [3062, 2986], + [3069, 2986], + [3069, 2988], + [3062, 2988], +] + +[[area.polygons]] +vertices = [ + [3027, 2987], + [3027, 2991], +] + +[[area.polygons]] +vertices = [ + [3026, 2988], + [3026, 2990], +] + +[[area.polygons]] +vertices = [ + [3062, 2989], + [3065, 2989], + [3065, 2993], + [3062, 2993], +] + +[[area.polygons]] +vertices = [ + [3068, 2991], + [3072, 2991], + [3072, 3008], + [3068, 3008], +] + +[[area.polygons]] +vertices = [[3032, 2992]] + +[[area.polygons]] +vertices = [[3060, 2994]] + +[[area.polygons]] +vertices = [ + [3063, 2994], + [3065, 2994], + [3065, 3004], + [3063, 3004], +] + +[[area.polygons]] +vertices = [ + [3058, 2998], + [3059, 2998], + [3059, 3007], + [3058, 3007], +] + +[[area.polygons]] +vertices = [ + [3054, 2999], + [3054, 3001], +] + +[[area.polygons]] +vertices = [ + [3060, 2999], + [3060, 3006], +] + +[[area.polygons]] +vertices = [ + [3061, 3000], + [3062, 3000], +] + +[[area.polygons]] +vertices = [ + [3039, 3002], + [3041, 3002], +] + +[[area.polygons]] +vertices = [[3043, 3002]] + +[[area.polygons]] +vertices = [ + [3066, 3002], + [3067, 3002], +] + +[[area.polygons]] +vertices = [ + [3039, 3003], + [3040, 3003], +] + +[[area.polygons]] +vertices = [ + [3045, 3004], + [3048, 3004], +] + +[[area.polygons]] +vertices = [ + [3068, 3009], + [3071, 3009], +] + +[[area.polygons]] +vertices = [ + [3069, 3010], + [3070, 3010], +] diff --git a/.data/raw-cache/map/area/the_part_where_you_die.toml b/.data/raw-cache/map/area/the_part_where_you_die.toml new file mode 100644 index 000000000..d40115882 --- /dev/null +++ b/.data/raw-cache/map/area/the_part_where_you_die.toml @@ -0,0 +1,16 @@ +# The Part Where You Die music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Part Where You Die. +# Bounds x 3008-3071, z 5925-5951; 1728 tiles as 1 rectangles. + +[[area]] +name = "The Part Where You Die" +area_id = "area.the_part_where_you_die" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 5925], + [3071, 5925], + [3071, 5951], + [3008, 5951], +] diff --git a/.data/raw-cache/map/area/the_plundered_tomb.toml b/.data/raw-cache/map/area/the_plundered_tomb.toml new file mode 100644 index 000000000..854cf453b --- /dev/null +++ b/.data/raw-cache/map/area/the_plundered_tomb.toml @@ -0,0 +1,24 @@ +# The Plundered Tomb music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Plundered Tomb. +# Bounds x 3840-3903, z 4544-4735; 8192 tiles as 2 rectangles. + +[[area]] +name = "The Plundered Tomb" +area_id = "area.the_plundered_tomb" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3840, 4544], + [3903, 4544], + [3903, 4607], + [3840, 4607], +] + +[[area.polygons]] +vertices = [ + [3840, 4672], + [3903, 4672], + [3903, 4735], + [3840, 4735], +] diff --git a/.data/raw-cache/map/area/the_power_of_tears.toml b/.data/raw-cache/map/area/the_power_of_tears.toml new file mode 100644 index 000000000..946bda6de --- /dev/null +++ b/.data/raw-cache/map/area/the_power_of_tears.toml @@ -0,0 +1,16 @@ +# The Power of Tears music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Power of Tears. +# Bounds x 3254-3263, z 9472-9535; 640 tiles as 1 rectangles. + +[[area]] +name = "The Power of Tears" +area_id = "area.the_power_of_tears" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3254, 9472], + [3263, 9472], + [3263, 9535], + [3254, 9535], +] diff --git a/.data/raw-cache/map/area/the_quizmaster.toml b/.data/raw-cache/map/area/the_quizmaster.toml new file mode 100644 index 000000000..9aca823c3 --- /dev/null +++ b/.data/raw-cache/map/area/the_quizmaster.toml @@ -0,0 +1,16 @@ +# The Quizmaster music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Quizmaster. +# Bounds x 1920-1983, z 4736-4799; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Quizmaster" +area_id = "area.the_quizmaster" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1920, 4736], + [1983, 4736], + [1983, 4799], + [1920, 4799], +] diff --git a/.data/raw-cache/map/area/the_rogues_den.toml b/.data/raw-cache/map/area/the_rogues_den.toml new file mode 100644 index 000000000..35e8fda3c --- /dev/null +++ b/.data/raw-cache/map/area/the_rogues_den.toml @@ -0,0 +1,24 @@ +# The Rogues' Den music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Rogues' Den. +# Bounds x 2944-3071, z 4928-5119; 16384 tiles as 2 rectangles. + +[[area]] +name = "The Rogues' Den" +area_id = "area.the_rogues_den" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 4928], + [3071, 4928], + [3071, 5055], + [3008, 5055], +] + +[[area.polygons]] +vertices = [ + [2944, 4992], + [3007, 4992], + [3007, 5119], + [2944, 5119], +] diff --git a/.data/raw-cache/map/area/the_route_of_all_evil.toml b/.data/raw-cache/map/area/the_route_of_all_evil.toml new file mode 100644 index 000000000..71752542f --- /dev/null +++ b/.data/raw-cache/map/area/the_route_of_all_evil.toml @@ -0,0 +1,16 @@ +# The Route of All Evil music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Route of All Evil. +# Bounds x 4126-4147, z 4842-4857; 352 tiles as 1 rectangles. + +[[area]] +name = "The Route of All Evil" +area_id = "area.the_route_of_all_evil" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [4126, 4842], + [4147, 4842], + [4147, 4857], + [4126, 4857], +] diff --git a/.data/raw-cache/map/area/the_ruins_of_camdozaal.toml b/.data/raw-cache/map/area/the_ruins_of_camdozaal.toml new file mode 100644 index 000000000..cb461d1fe --- /dev/null +++ b/.data/raw-cache/map/area/the_ruins_of_camdozaal.toml @@ -0,0 +1,24 @@ +# The Ruins of Camdozaal music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Ruins of Camdozaal. +# Bounds x 2880-3071, z 5760-5887; 16384 tiles as 2 rectangles. + +[[area]] +name = "The Ruins of Camdozaal" +area_id = "area.the_ruins_of_camdozaal" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2880, 5760], + [3071, 5760], + [3071, 5823], + [2880, 5823], +] + +[[area.polygons]] +vertices = [ + [2880, 5824], + [2943, 5824], + [2943, 5887], + [2880, 5887], +] diff --git a/.data/raw-cache/map/area/the_shadow.toml b/.data/raw-cache/map/area/the_shadow.toml new file mode 100644 index 000000000..5021baa5b --- /dev/null +++ b/.data/raw-cache/map/area/the_shadow.toml @@ -0,0 +1,343 @@ +# The Shadow music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Shadow. +# Bounds x 2813-2864, z 3231-3310; 3078 tiles as 56 rectangles. + +[[area]] +name = "The Shadow" +area_id = "area.the_shadow" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2825, 3231], + [2830, 3231], + [2830, 3301], + [2825, 3301], +] + +[[area.polygons]] +vertices = [ + [2824, 3232], + [2824, 3300], +] + +[[area.polygons]] +vertices = [ + [2831, 3232], + [2831, 3304], +] + +[[area.polygons]] +vertices = [ + [2835, 3232], + [2836, 3232], + [2836, 3306], + [2835, 3306], +] + +[[area.polygons]] +vertices = [ + [2823, 3233], + [2823, 3300], +] + +[[area.polygons]] +vertices = [ + [2832, 3233], + [2834, 3233], + [2834, 3304], + [2832, 3304], +] + +[[area.polygons]] +vertices = [ + [2837, 3233], + [2839, 3233], + [2839, 3306], + [2837, 3306], +] + +[[area.polygons]] +vertices = [ + [2822, 3234], + [2822, 3299], +] + +[[area.polygons]] +vertices = [ + [2840, 3234], + [2840, 3307], +] + +[[area.polygons]] +vertices = [ + [2846, 3234], + [2854, 3234], + [2854, 3253], + [2846, 3253], +] + +[[area.polygons]] +vertices = [ + [2821, 3235], + [2821, 3298], +] + +[[area.polygons]] +vertices = [ + [2841, 3235], + [2845, 3235], + [2845, 3308], + [2841, 3308], +] + +[[area.polygons]] +vertices = [ + [2820, 3237], + [2820, 3298], +] + +[[area.polygons]] +vertices = [ + [2855, 3238], + [2856, 3238], + [2856, 3249], + [2855, 3249], +] + +[[area.polygons]] +vertices = [ + [2857, 3239], + [2857, 3247], +] + +[[area.polygons]] +vertices = [ + [2819, 3240], + [2819, 3297], +] + +[[area.polygons]] +vertices = [ + [2818, 3242], + [2818, 3248], +] + +[[area.polygons]] +vertices = [ + [2858, 3242], + [2858, 3246], +] + +[[area.polygons]] +vertices = [ + [2859, 3243], + [2859, 3245], +] + +[[area.polygons]] +vertices = [[2817, 3246]] + +[[area.polygons]] +vertices = [ + [2818, 3250], + [2818, 3257], +] + +[[area.polygons]] +vertices = [[2855, 3250]] + +[[area.polygons]] +vertices = [ + [2817, 3251], + [2817, 3255], +] + +[[area.polygons]] +vertices = [[2816, 3253]] + +[[area.polygons]] +vertices = [ + [2846, 3254], + [2853, 3254], + [2853, 3255], + [2846, 3255], +] + +[[area.polygons]] +vertices = [ + [2846, 3256], + [2852, 3256], + [2852, 3308], + [2846, 3308], +] + +[[area.polygons]] +vertices = [ + [2818, 3260], + [2818, 3296], +] + +[[area.polygons]] +vertices = [ + [2853, 3260], + [2853, 3309], +] + +[[area.polygons]] +vertices = [ + [2817, 3261], + [2817, 3295], +] + +[[area.polygons]] +vertices = [ + [2854, 3261], + [2854, 3308], +] + +[[area.polygons]] +vertices = [ + [2816, 3263], + [2816, 3278], +] + +[[area.polygons]] +vertices = [ + [2815, 3264], + [2815, 3276], +] + +[[area.polygons]] +vertices = [ + [2855, 3264], + [2856, 3264], + [2856, 3306], + [2855, 3306], +] + +[[area.polygons]] +vertices = [ + [2814, 3265], + [2814, 3273], +] + +[[area.polygons]] +vertices = [ + [2857, 3265], + [2857, 3305], +] + +[[area.polygons]] +vertices = [ + [2858, 3266], + [2858, 3303], +] + +[[area.polygons]] +vertices = [ + [2813, 3268], + [2813, 3269], +] + +[[area.polygons]] +vertices = [ + [2859, 3270], + [2859, 3299], +] + +[[area.polygons]] +vertices = [ + [2860, 3276], + [2860, 3298], +] + +[[area.polygons]] +vertices = [ + [2861, 3280], + [2861, 3297], +] + +[[area.polygons]] +vertices = [ + [2816, 3281], + [2816, 3294], +] + +[[area.polygons]] +vertices = [ + [2862, 3281], + [2862, 3296], +] + +[[area.polygons]] +vertices = [ + [2863, 3282], + [2863, 3286], +] + +[[area.polygons]] +vertices = [ + [2815, 3283], + [2815, 3292], +] + +[[area.polygons]] +vertices = [ + [2864, 3283], + [2864, 3286], +] + +[[area.polygons]] +vertices = [ + [2814, 3286], + [2814, 3289], +] + +[[area.polygons]] +vertices = [ + [2863, 3289], + [2863, 3295], +] + +[[area.polygons]] +vertices = [ + [2864, 3290], + [2864, 3293], +] + +[[area.polygons]] +vertices = [ + [2826, 3302], + [2830, 3302], +] + +[[area.polygons]] +vertices = [ + [2829, 3303], + [2830, 3303], +] + +[[area.polygons]] +vertices = [[2830, 3304]] + +[[area.polygons]] +vertices = [[2834, 3305]] + +[[area.polygons]] +vertices = [[2839, 3307]] + +[[area.polygons]] +vertices = [[2855, 3307]] + +[[area.polygons]] +vertices = [ + [2848, 3309], + [2852, 3309], +] + +[[area.polygons]] +vertices = [ + [2850, 3310], + [2852, 3310], +] diff --git a/.data/raw-cache/map/area/the_slayer.toml b/.data/raw-cache/map/area/the_slayer.toml new file mode 100644 index 000000000..6514f8119 --- /dev/null +++ b/.data/raw-cache/map/area/the_slayer.toml @@ -0,0 +1,16 @@ +# The Slayer music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Slayer. +# Bounds x 2752-2815, z 9984-10047; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Slayer" +area_id = "area.the_slayer" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 9984], + [2815, 9984], + [2815, 10047], + [2752, 10047], +] diff --git a/.data/raw-cache/map/area/the_sound_of_guthix.toml b/.data/raw-cache/map/area/the_sound_of_guthix.toml new file mode 100644 index 000000000..fc19955f8 --- /dev/null +++ b/.data/raw-cache/map/area/the_sound_of_guthix.toml @@ -0,0 +1,16 @@ +# The Sound of Guthix music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Sound of Guthix. +# Bounds x 4032-4095, z 4544-4607; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Sound of Guthix" +area_id = "area.the_sound_of_guthix" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [4032, 4544], + [4095, 4544], + [4095, 4607], + [4032, 4607], +] diff --git a/.data/raw-cache/map/area/the_spurned_demon.toml b/.data/raw-cache/map/area/the_spurned_demon.toml new file mode 100644 index 000000000..8b5f5bec8 --- /dev/null +++ b/.data/raw-cache/map/area/the_spurned_demon.toml @@ -0,0 +1,16 @@ +# The Spurned Demon music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Spurned Demon. +# Bounds x 3008-3071, z 6016-6061; 2944 tiles as 1 rectangles. + +[[area]] +name = "The Spurned Demon" +area_id = "area.the_spurned_demon" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 6016], + [3071, 6016], + [3071, 6061], + [3008, 6061], +] diff --git a/.data/raw-cache/map/area/the_talkasti_people.toml b/.data/raw-cache/map/area/the_talkasti_people.toml new file mode 100644 index 000000000..d845b91d3 --- /dev/null +++ b/.data/raw-cache/map/area/the_talkasti_people.toml @@ -0,0 +1,246 @@ +# The Talkasti People music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Talkasti People. +# Bounds x 1346-1396, z 3010-9457; 2727 tiles as 36 rectangles. + +[[area]] +name = "The Talkasti People" +area_id = "area.the_talkasti_people" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1354, 3010], + [1366, 3010], + [1366, 3016], + [1354, 3016], +] + +[[area.polygons]] +vertices = [ + [1360, 3017], + [1360, 3077], +] + +[[area.polygons]] +vertices = [ + [1349, 3018], + [1350, 3018], + [1350, 3077], + [1349, 3077], +] + +[[area.polygons]] +vertices = [ + [1348, 3019], + [1348, 3077], +] + +[[area.polygons]] +vertices = [ + [1351, 3019], + [1352, 3019], + [1352, 3077], + [1351, 3077], +] + +[[area.polygons]] +vertices = [ + [1369, 3019], + [1373, 3019], + [1373, 3077], + [1369, 3077], +] + +[[area.polygons]] +vertices = [ + [1347, 3020], + [1347, 3077], +] + +[[area.polygons]] +vertices = [ + [1353, 3020], + [1359, 3020], + [1359, 3077], + [1353, 3077], +] + +[[area.polygons]] +vertices = [ + [1361, 3020], + [1368, 3020], + [1368, 3077], + [1361, 3077], +] + +[[area.polygons]] +vertices = [ + [1374, 3020], + [1374, 3077], +] + +[[area.polygons]] +vertices = [ + [1346, 3021], + [1346, 3077], +] + +[[area.polygons]] +vertices = [ + [1375, 3021], + [1375, 3077], +] + +[[area.polygons]] +vertices = [ + [1376, 3026], + [1377, 3026], + [1377, 3077], + [1376, 3077], +] + +[[area.polygons]] +vertices = [ + [1378, 3027], + [1378, 3077], +] + +[[area.polygons]] +vertices = [ + [1379, 3028], + [1379, 3077], +] + +[[area.polygons]] +vertices = [ + [1380, 3030], + [1380, 3077], +] + +[[area.polygons]] +vertices = [ + [1381, 3032], + [1381, 3077], +] + +[[area.polygons]] +vertices = [ + [1382, 3033], + [1382, 3077], +] + +[[area.polygons]] +vertices = [ + [1383, 3038], + [1383, 3077], +] + +[[area.polygons]] +vertices = [ + [1389, 3039], + [1394, 3039], + [1394, 3044], + [1389, 3044], +] + +[[area.polygons]] +vertices = [ + [1384, 3040], + [1384, 3077], +] + +[[area.polygons]] +vertices = [ + [1395, 3040], + [1395, 3048], +] + +[[area.polygons]] +vertices = [ + [1385, 3041], + [1385, 3077], +] + +[[area.polygons]] +vertices = [ + [1396, 3041], + [1396, 3047], +] + +[[area.polygons]] +vertices = [ + [1386, 3042], + [1388, 3042], + [1388, 3043], + [1386, 3043], +] + +[[area.polygons]] +vertices = [ + [1390, 3045], + [1394, 3045], + [1394, 3047], + [1390, 3047], +] + +[[area.polygons]] +vertices = [ + [1386, 3048], + [1386, 3077], +] + +[[area.polygons]] +vertices = [ + [1392, 3048], + [1394, 3048], +] + +[[area.polygons]] +vertices = [ + [1387, 3049], + [1387, 3077], +] + +[[area.polygons]] +vertices = [ + [1388, 3051], + [1388, 3076], +] + +[[area.polygons]] +vertices = [ + [1389, 3052], + [1389, 3075], +] + +[[area.polygons]] +vertices = [ + [1390, 3053], + [1390, 3074], +] + +[[area.polygons]] +vertices = [ + [1391, 3056], + [1391, 3073], +] + +[[area.polygons]] +vertices = [ + [1392, 3057], + [1392, 3071], +] + +[[area.polygons]] +vertices = [ + [1393, 3062], + [1393, 3065], +] + +[[area.polygons]] +vertices = [ + [1374, 9446], + [1388, 9446], + [1388, 9457], + [1374, 9457], +] diff --git a/.data/raw-cache/map/area/the_terrible_tower.toml b/.data/raw-cache/map/area/the_terrible_tower.toml new file mode 100644 index 000000000..7cadd8d01 --- /dev/null +++ b/.data/raw-cache/map/area/the_terrible_tower.toml @@ -0,0 +1,112 @@ +# The Terrible Tower music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Terrible Tower. +# Bounds x 3392-3455, z 3529-9983; 7099 tiles as 16 rectangles. + +[[area]] +name = "The Terrible Tower" +area_id = "area.the_terrible_tower" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3400, 3529], + [3419, 3529], + [3419, 3583], + [3400, 3583], +] + +[[area.polygons]] +vertices = [ + [3437, 3529], + [3455, 3529], + [3455, 3583], + [3437, 3583], +] + +[[area.polygons]] +vertices = [ + [3420, 3530], + [3420, 3583], +] + +[[area.polygons]] +vertices = [ + [3436, 3530], + [3436, 3583], +] + +[[area.polygons]] +vertices = [ + [3421, 3531], + [3421, 3583], +] + +[[area.polygons]] +vertices = [ + [3435, 3531], + [3435, 3583], +] + +[[area.polygons]] +vertices = [ + [3422, 3532], + [3422, 3583], +] + +[[area.polygons]] +vertices = [ + [3434, 3532], + [3434, 3583], +] + +[[area.polygons]] +vertices = [ + [3423, 3533], + [3423, 3583], +] + +[[area.polygons]] +vertices = [ + [3433, 3533], + [3433, 3583], +] + +[[area.polygons]] +vertices = [ + [3424, 3534], + [3424, 3583], +] + +[[area.polygons]] +vertices = [ + [3432, 3534], + [3432, 3583], +] + +[[area.polygons]] +vertices = [ + [3425, 3535], + [3425, 3583], +] + +[[area.polygons]] +vertices = [ + [3431, 3535], + [3431, 3583], +] + +[[area.polygons]] +vertices = [ + [3426, 3536], + [3430, 3536], + [3430, 3583], + [3426, 3583], +] + +[[area.polygons]] +vertices = [ + [3392, 9920], + [3455, 9920], + [3455, 9983], + [3392, 9983], +] diff --git a/.data/raw-cache/map/area/the_terrible_tunnels.toml b/.data/raw-cache/map/area/the_terrible_tunnels.toml new file mode 100644 index 000000000..3f95f60c5 --- /dev/null +++ b/.data/raw-cache/map/area/the_terrible_tunnels.toml @@ -0,0 +1,48 @@ +# The Terrible Tunnels music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Terrible Tunnels. +# Bounds x 3512-3647, z 9664-9827; 12864 tiles as 5 rectangles. + +[[area]] +name = "The Terrible Tunnels" +area_id = "area.the_terrible_tunnels" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3600, 9664], + [3647, 9664], + [3647, 9680], + [3600, 9680], +] + +[[area.polygons]] +vertices = [ + [3600, 9681], + [3615, 9681], + [3615, 9791], + [3600, 9791], +] + +[[area.polygons]] +vertices = [ + [3512, 9728], + [3599, 9728], + [3599, 9791], + [3512, 9791], +] + +[[area.polygons]] +vertices = [ + [3616, 9728], + [3647, 9728], + [3647, 9791], + [3616, 9791], +] + +[[area.polygons]] +vertices = [ + [3512, 9792], + [3583, 9792], + [3583, 9827], + [3512, 9827], +] diff --git a/.data/raw-cache/map/area/the_tortugan_way.toml b/.data/raw-cache/map/area/the_tortugan_way.toml new file mode 100644 index 000000000..6ab6506fa --- /dev/null +++ b/.data/raw-cache/map/area/the_tortugan_way.toml @@ -0,0 +1,16 @@ +# The Tortugan Way music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Elder Wisdom; The Tortugan Way. +# Bounds x 3264-3327, z 8832-8895; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Tortugan Way" +area_id = "area.the_tortugan_way" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 8832], + [3327, 8832], + [3327, 8895], + [3264, 8895], +] diff --git a/.data/raw-cache/map/area/the_vault.toml b/.data/raw-cache/map/area/the_vault.toml new file mode 100644 index 000000000..4d81a056a --- /dev/null +++ b/.data/raw-cache/map/area/the_vault.toml @@ -0,0 +1,16 @@ +# The Vault music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Vault. +# Bounds x 3136-3327, z 6400-6463; 12288 tiles as 1 rectangles. + +[[area]] +name = "The Vault" +area_id = "area.the_vault" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 6400], + [3327, 6400], + [3327, 6463], + [3136, 6463], +] diff --git a/.data/raw-cache/map/area/the_waiting_game.toml b/.data/raw-cache/map/area/the_waiting_game.toml new file mode 100644 index 000000000..07452be01 --- /dev/null +++ b/.data/raw-cache/map/area/the_waiting_game.toml @@ -0,0 +1,170 @@ +# The Waiting Game music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Waiting Game. +# Bounds x 2181-2234, z 2827-2879; 2481 tiles as 25 rectangles. + +[[area]] +name = "The Waiting Game" +area_id = "area.the_waiting_game" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2185, 2827], + [2202, 2827], + [2202, 2859], + [2185, 2859], +] + +[[area.polygons]] +vertices = [ + [2218, 2827], + [2229, 2827], + [2229, 2861], + [2218, 2861], +] + +[[area.polygons]] +vertices = [ + [2184, 2828], + [2184, 2858], +] + +[[area.polygons]] +vertices = [ + [2203, 2828], + [2203, 2879], +] + +[[area.polygons]] +vertices = [ + [2217, 2828], + [2217, 2879], +] + +[[area.polygons]] +vertices = [ + [2230, 2828], + [2230, 2859], +] + +[[area.polygons]] +vertices = [ + [2183, 2829], + [2183, 2856], +] + +[[area.polygons]] +vertices = [ + [2204, 2829], + [2204, 2879], +] + +[[area.polygons]] +vertices = [ + [2216, 2829], + [2216, 2879], +] + +[[area.polygons]] +vertices = [ + [2231, 2829], + [2231, 2857], +] + +[[area.polygons]] +vertices = [ + [2182, 2830], + [2182, 2854], +] + +[[area.polygons]] +vertices = [ + [2205, 2830], + [2205, 2879], +] + +[[area.polygons]] +vertices = [ + [2215, 2830], + [2215, 2879], +] + +[[area.polygons]] +vertices = [ + [2232, 2830], + [2232, 2856], +] + +[[area.polygons]] +vertices = [ + [2181, 2831], + [2181, 2853], +] + +[[area.polygons]] +vertices = [ + [2206, 2831], + [2206, 2879], +] + +[[area.polygons]] +vertices = [ + [2214, 2831], + [2214, 2879], +] + +[[area.polygons]] +vertices = [ + [2233, 2831], + [2233, 2854], +] + +[[area.polygons]] +vertices = [ + [2207, 2832], + [2213, 2832], + [2213, 2879], + [2207, 2879], +] + +[[area.polygons]] +vertices = [ + [2234, 2832], + [2234, 2853], +] + +[[area.polygons]] +vertices = [ + [2186, 2860], + [2202, 2860], + [2202, 2861], + [2186, 2861], +] + +[[area.polygons]] +vertices = [ + [2187, 2862], + [2202, 2862], +] + +[[area.polygons]] +vertices = [ + [2218, 2862], + [2228, 2862], +] + +[[area.polygons]] +vertices = [ + [2188, 2863], + [2202, 2863], + [2202, 2879], + [2188, 2879], +] + +[[area.polygons]] +vertices = [ + [2218, 2863], + [2227, 2863], + [2227, 2879], + [2218, 2879], +] diff --git a/.data/raw-cache/map/area/the_walking_dead.toml b/.data/raw-cache/map/area/the_walking_dead.toml new file mode 100644 index 000000000..7a72c7096 --- /dev/null +++ b/.data/raw-cache/map/area/the_walking_dead.toml @@ -0,0 +1,16 @@ +# The Walking Dead music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: The Walking Dead. +# Bounds x 1472-1535, z 9920-9983; 4096 tiles as 1 rectangles. + +[[area]] +name = "The Walking Dead" +area_id = "area.the_walking_dead" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1472, 9920], + [1535, 9920], + [1535, 9983], + [1472, 9983], +] diff --git a/.data/raw-cache/map/area/theme.toml b/.data/raw-cache/map/area/theme.toml new file mode 100644 index 000000000..da2d9d709 --- /dev/null +++ b/.data/raw-cache/map/area/theme.toml @@ -0,0 +1,252 @@ +# Theme music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Theme. +# Bounds x 2202-2623, z 3460-9919; 4576 tiles as 39 rectangles. + +[[area]] +name = "Theme" +area_id = "area.theme" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2208, 3460], + [2209, 3460], + [2209, 3463], + [2208, 3463], +] + +[[area.polygons]] +vertices = [ + [2210, 3461], + [2211, 3461], + [2211, 3467], + [2210, 3467], +] + +[[area.polygons]] +vertices = [ + [2212, 3462], + [2213, 3462], + [2213, 3478], + [2212, 3478], +] + +[[area.polygons]] +vertices = [ + [2214, 3463], + [2214, 3487], +] + +[[area.polygons]] +vertices = [ + [2209, 3464], + [2209, 3465], +] + +[[area.polygons]] +vertices = [ + [2215, 3464], + [2216, 3464], + [2216, 3487], + [2215, 3487], +] + +[[area.polygons]] +vertices = [ + [2217, 3465], + [2218, 3465], + [2218, 3486], + [2217, 3486], +] + +[[area.polygons]] +vertices = [ + [2219, 3466], + [2224, 3466], + [2224, 3481], + [2219, 3481], +] + +[[area.polygons]] +vertices = [ + [2225, 3467], + [2225, 3480], +] + +[[area.polygons]] +vertices = [ + [2211, 3468], + [2211, 3478], +] + +[[area.polygons]] +vertices = [ + [2226, 3468], + [2226, 3480], +] + +[[area.polygons]] +vertices = [ + [2227, 3469], + [2227, 3480], +] + +[[area.polygons]] +vertices = [ + [2210, 3470], + [2210, 3479], +] + +[[area.polygons]] +vertices = [ + [2209, 3471], + [2209, 3479], +] + +[[area.polygons]] +vertices = [ + [2228, 3471], + [2228, 3480], +] + +[[area.polygons]] +vertices = [ + [2207, 3472], + [2208, 3472], + [2208, 3478], + [2207, 3478], +] + +[[area.polygons]] +vertices = [ + [2229, 3472], + [2229, 3480], +] + +[[area.polygons]] +vertices = [ + [2206, 3473], + [2206, 3478], +] + +[[area.polygons]] +vertices = [ + [2230, 3473], + [2230, 3480], +] + +[[area.polygons]] +vertices = [ + [2203, 3474], + [2205, 3474], + [2205, 3477], + [2203, 3477], +] + +[[area.polygons]] +vertices = [ + [2202, 3475], + [2202, 3477], +] + +[[area.polygons]] +vertices = [ + [2231, 3475], + [2231, 3481], +] + +[[area.polygons]] +vertices = [ + [2232, 3476], + [2232, 3482], +] + +[[area.polygons]] +vertices = [ + [2233, 3477], + [2234, 3477], + [2234, 3482], + [2233, 3482], +] + +[[area.polygons]] +vertices = [ + [2204, 3478], + [2205, 3478], +] + +[[area.polygons]] +vertices = [ + [2235, 3478], + [2236, 3478], + [2236, 3483], + [2235, 3483], +] + +[[area.polygons]] +vertices = [[2213, 3479]] + +[[area.polygons]] +vertices = [ + [2237, 3480], + [2237, 3483], +] + +[[area.polygons]] +vertices = [ + [2213, 3481], + [2213, 3487], +] + +[[area.polygons]] +vertices = [ + [2238, 3481], + [2238, 3482], +] + +[[area.polygons]] +vertices = [ + [2212, 3482], + [2212, 3487], +] + +[[area.polygons]] +vertices = [ + [2219, 3482], + [2223, 3482], +] + +[[area.polygons]] +vertices = [ + [2211, 3483], + [2211, 3486], +] + +[[area.polygons]] +vertices = [ + [2219, 3483], + [2222, 3483], +] + +[[area.polygons]] +vertices = [[2234, 3483]] + +[[area.polygons]] +vertices = [ + [2219, 3484], + [2220, 3484], +] + +[[area.polygons]] +vertices = [[2210, 3485]] + +[[area.polygons]] +vertices = [[2219, 3485]] + +[[area.polygons]] +vertices = [ + [2560, 9856], + [2623, 9856], + [2623, 9919], + [2560, 9919], +] diff --git a/.data/raw-cache/map/area/thrall_of_the_serpent.toml b/.data/raw-cache/map/area/thrall_of_the_serpent.toml new file mode 100644 index 000000000..95c6ba46e --- /dev/null +++ b/.data/raw-cache/map/area/thrall_of_the_serpent.toml @@ -0,0 +1,142 @@ +# Thrall of the Serpent music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Thrall of the Serpent. +# Bounds x 2176-2215, z 3040-3071; 1053 tiles as 20 rectangles. + +[[area]] +name = "Thrall of the Serpent" +area_id = "area.thrall_of_the_serpent" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2205, 3040], + [2215, 3040], + [2215, 3071], + [2205, 3071], +] + +[[area.polygons]] +vertices = [ + [2204, 3041], + [2204, 3071], +] + +[[area.polygons]] +vertices = [ + [2194, 3042], + [2197, 3042], + [2197, 3071], + [2194, 3071], +] + +[[area.polygons]] +vertices = [ + [2193, 3043], + [2193, 3071], +] + +[[area.polygons]] +vertices = [ + [2198, 3043], + [2200, 3043], + [2200, 3071], + [2198, 3071], +] + +[[area.polygons]] +vertices = [ + [2203, 3043], + [2203, 3071], +] + +[[area.polygons]] +vertices = [ + [2192, 3044], + [2192, 3071], +] + +[[area.polygons]] +vertices = [ + [2201, 3044], + [2202, 3044], + [2202, 3071], + [2201, 3071], +] + +[[area.polygons]] +vertices = [ + [2191, 3045], + [2191, 3071], +] + +[[area.polygons]] +vertices = [ + [2190, 3046], + [2190, 3071], +] + +[[area.polygons]] +vertices = [ + [2189, 3047], + [2189, 3071], +] + +[[area.polygons]] +vertices = [ + [2188, 3049], + [2188, 3071], +] + +[[area.polygons]] +vertices = [ + [2187, 3050], + [2187, 3071], +] + +[[area.polygons]] +vertices = [ + [2184, 3051], + [2186, 3051], + [2186, 3071], + [2184, 3071], +] + +[[area.polygons]] +vertices = [ + [2179, 3052], + [2183, 3052], + [2183, 3068], + [2179, 3068], +] + +[[area.polygons]] +vertices = [ + [2177, 3053], + [2178, 3053], + [2178, 3066], + [2177, 3066], +] + +[[area.polygons]] +vertices = [ + [2176, 3054], + [2176, 3066], +] + +[[area.polygons]] +vertices = [ + [2180, 3069], + [2183, 3069], +] + +[[area.polygons]] +vertices = [ + [2181, 3070], + [2183, 3070], +] + +[[area.polygons]] +vertices = [ + [2182, 3071], + [2183, 3071], +] diff --git a/.data/raw-cache/map/area/throne_of_the_demon.toml b/.data/raw-cache/map/area/throne_of_the_demon.toml new file mode 100644 index 000000000..3761037e7 --- /dev/null +++ b/.data/raw-cache/map/area/throne_of_the_demon.toml @@ -0,0 +1,16 @@ +# Throne of the Demon music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Throne of the Demon (plus forgotten from wiki polygons sharing this footprint). +# Bounds x 2688-2751, z 4864-4927; 4096 tiles as 1 rectangles. + +[[area]] +name = "Throne of the Demon" +area_id = "area.throne_of_the_demon" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 4864], + [2751, 4864], + [2751, 4927], + [2688, 4927], +] diff --git a/.data/raw-cache/map/area/time_out.toml b/.data/raw-cache/map/area/time_out.toml new file mode 100644 index 000000000..57072fb79 --- /dev/null +++ b/.data/raw-cache/map/area/time_out.toml @@ -0,0 +1,16 @@ +# Time Out music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Time Out. +# Bounds x 2880-2943, z 4544-4607; 4096 tiles as 1 rectangles. + +[[area]] +name = "Time Out" +area_id = "area.time_out" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2880, 4544], + [2943, 4544], + [2943, 4607], + [2880, 4607], +] diff --git a/.data/raw-cache/map/area/time_to_mine.toml b/.data/raw-cache/map/area/time_to_mine.toml new file mode 100644 index 000000000..32d65e1f6 --- /dev/null +++ b/.data/raw-cache/map/area/time_to_mine.toml @@ -0,0 +1,16 @@ +# Time to Mine music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Time to Mine. +# Bounds x 2816-2879, z 10112-10176; 4160 tiles as 1 rectangles. + +[[area]] +name = "Time to Mine" +area_id = "area.time_to_mine" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2816, 10112], + [2879, 10112], + [2879, 10176], + [2816, 10176], +] diff --git a/.data/raw-cache/map/area/tiptoe.toml b/.data/raw-cache/map/area/tiptoe.toml new file mode 100644 index 000000000..4a45e076f --- /dev/null +++ b/.data/raw-cache/map/area/tiptoe.toml @@ -0,0 +1,16 @@ +# Tiptoe music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Tiptoe. +# Bounds x 3072-3135, z 9728-9791; 4096 tiles as 1 rectangles. + +[[area]] +name = "Tiptoe" +area_id = "area.tiptoe" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3072, 9728], + [3135, 9728], + [3135, 9791], + [3072, 9791], +] diff --git a/.data/raw-cache/map/area/title_fight.toml b/.data/raw-cache/map/area/title_fight.toml new file mode 100644 index 000000000..511e82e41 --- /dev/null +++ b/.data/raw-cache/map/area/title_fight.toml @@ -0,0 +1,172 @@ +# Title Fight music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Title Fight. +# Bounds x 3136-3199, z 9728-9791; 3862 tiles as 24 rectangles. + +[[area]] +name = "Title Fight" +area_id = "area.title_fight" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 9728], + [3199, 9728], + [3199, 9751], + [3136, 9751], +] + +[[area.polygons]] +vertices = [ + [3136, 9752], + [3165, 9752], +] + +[[area.polygons]] +vertices = [ + [3171, 9752], + [3199, 9752], +] + +[[area.polygons]] +vertices = [ + [3136, 9753], + [3161, 9753], +] + +[[area.polygons]] +vertices = [ + [3174, 9753], + [3177, 9753], +] + +[[area.polygons]] +vertices = [ + [3180, 9753], + [3199, 9753], +] + +[[area.polygons]] +vertices = [ + [3136, 9754], + [3160, 9754], +] + +[[area.polygons]] +vertices = [ + [3175, 9754], + [3176, 9754], +] + +[[area.polygons]] +vertices = [ + [3181, 9754], + [3199, 9754], + [3199, 9757], + [3181, 9757], +] + +[[area.polygons]] +vertices = [ + [3136, 9755], + [3158, 9755], +] + +[[area.polygons]] +vertices = [ + [3136, 9756], + [3157, 9756], + [3157, 9791], + [3136, 9791], +] + +[[area.polygons]] +vertices = [ + [3182, 9758], + [3199, 9758], + [3199, 9791], + [3182, 9791], +] + +[[area.polygons]] +vertices = [ + [3181, 9759], + [3181, 9791], +] + +[[area.polygons]] +vertices = [ + [3158, 9761], + [3158, 9791], +] + +[[area.polygons]] +vertices = [ + [3159, 9762], + [3160, 9762], + [3160, 9791], + [3159, 9791], +] + +[[area.polygons]] +vertices = [ + [3175, 9762], + [3176, 9762], + [3176, 9791], + [3175, 9791], +] + +[[area.polygons]] +vertices = [ + [3161, 9763], + [3161, 9791], +] + +[[area.polygons]] +vertices = [ + [3174, 9763], + [3174, 9791], +] + +[[area.polygons]] +vertices = [ + [3177, 9763], + [3177, 9791], +] + +[[area.polygons]] +vertices = [ + [3180, 9763], + [3180, 9791], +] + +[[area.polygons]] +vertices = [ + [3162, 9764], + [3165, 9764], + [3165, 9791], + [3162, 9791], +] + +[[area.polygons]] +vertices = [ + [3171, 9764], + [3173, 9764], + [3173, 9791], + [3171, 9791], +] + +[[area.polygons]] +vertices = [ + [3178, 9764], + [3179, 9764], + [3179, 9791], + [3178, 9791], +] + +[[area.polygons]] +vertices = [ + [3166, 9765], + [3170, 9765], + [3170, 9791], + [3166, 9791], +] diff --git a/.data/raw-cache/map/area/tomb_raider.toml b/.data/raw-cache/map/area/tomb_raider.toml new file mode 100644 index 000000000..7347b1e99 --- /dev/null +++ b/.data/raw-cache/map/area/tomb_raider.toml @@ -0,0 +1,16 @@ +# Tomb Raider music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Tomb Raider. +# Bounds x 1920-1983, z 4416-4479; 4096 tiles as 1 rectangles. + +[[area]] +name = "Tomb Raider" +area_id = "area.tomb_raider" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1920, 4416], + [1983, 4416], + [1983, 4479], + [1920, 4479], +] diff --git a/.data/raw-cache/map/area/tomorrow.toml b/.data/raw-cache/map/area/tomorrow.toml new file mode 100644 index 000000000..6122b5f2c --- /dev/null +++ b/.data/raw-cache/map/area/tomorrow.toml @@ -0,0 +1,336 @@ +# Tomorrow music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Tomorrow. +# Bounds x 2983-3036, z 3136-3205; 2403 tiles as 53 rectangles. + +[[area]] +name = "Tomorrow" +area_id = "area.tomorrow" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2998, 3136], + [3006, 3136], + [3006, 3201], + [2998, 3201], +] + +[[area.polygons]] +vertices = [ + [2996, 3137], + [2997, 3137], + [2997, 3155], + [2996, 3155], +] + +[[area.polygons]] +vertices = [ + [3007, 3137], + [3007, 3199], +] + +[[area.polygons]] +vertices = [ + [2995, 3138], + [2995, 3151], +] + +[[area.polygons]] +vertices = [ + [3008, 3138], + [3008, 3199], +] + +[[area.polygons]] +vertices = [ + [3009, 3139], + [3009, 3199], +] + +[[area.polygons]] +vertices = [ + [2994, 3140], + [2994, 3141], +] + +[[area.polygons]] +vertices = [ + [3010, 3140], + [3013, 3140], + [3013, 3199], + [3010, 3199], +] + +[[area.polygons]] +vertices = [ + [3014, 3141], + [3014, 3199], +] + +[[area.polygons]] +vertices = [ + [3015, 3142], + [3015, 3199], +] + +[[area.polygons]] +vertices = [ + [2994, 3143], + [2994, 3149], +] + +[[area.polygons]] +vertices = [ + [3016, 3143], + [3017, 3143], + [3017, 3199], + [3016, 3199], +] + +[[area.polygons]] +vertices = [ + [2993, 3144], + [2993, 3147], +] + +[[area.polygons]] +vertices = [ + [3018, 3144], + [3020, 3144], + [3020, 3198], + [3018, 3198], +] + +[[area.polygons]] +vertices = [ + [3021, 3145], + [3021, 3147], +] + +[[area.polygons]] +vertices = [ + [3021, 3149], + [3021, 3196], +] + +[[area.polygons]] +vertices = [ + [3022, 3151], + [3022, 3195], +] + +[[area.polygons]] +vertices = [ + [3023, 3152], + [3023, 3194], +] + +[[area.polygons]] +vertices = [ + [3024, 3154], + [3025, 3154], + [3025, 3190], + [3024, 3190], +] + +[[area.polygons]] +vertices = [ + [3026, 3155], + [3027, 3155], + [3027, 3187], + [3026, 3187], +] + +[[area.polygons]] +vertices = [ + [2997, 3156], + [2997, 3203], +] + +[[area.polygons]] +vertices = [ + [3028, 3156], + [3028, 3182], +] + +[[area.polygons]] +vertices = [ + [3029, 3157], + [3029, 3181], +] + +[[area.polygons]] +vertices = [ + [3030, 3158], + [3031, 3158], + [3031, 3178], + [3030, 3178], +] + +[[area.polygons]] +vertices = [ + [3032, 3159], + [3032, 3177], +] + +[[area.polygons]] +vertices = [ + [3033, 3160], + [3033, 3176], +] + +[[area.polygons]] +vertices = [ + [2996, 3161], + [2996, 3203], +] + +[[area.polygons]] +vertices = [ + [2995, 3162], + [2995, 3203], +] + +[[area.polygons]] +vertices = [ + [3034, 3162], + [3034, 3174], +] + +[[area.polygons]] +vertices = [ + [2993, 3163], + [2994, 3163], + [2994, 3202], + [2993, 3202], +] + +[[area.polygons]] +vertices = [ + [2992, 3164], + [2992, 3202], +] + +[[area.polygons]] +vertices = [ + [3035, 3164], + [3035, 3167], +] + +[[area.polygons]] +vertices = [ + [2991, 3165], + [2991, 3202], +] + +[[area.polygons]] +vertices = [ + [3036, 3165], + [3036, 3166], +] + +[[area.polygons]] +vertices = [[2990, 3167]] + +[[area.polygons]] +vertices = [ + [2990, 3170], + [2990, 3200], +] + +[[area.polygons]] +vertices = [ + [3035, 3170], + [3035, 3172], +] + +[[area.polygons]] +vertices = [ + [2989, 3171], + [2989, 3199], +] + +[[area.polygons]] +vertices = [ + [2988, 3173], + [2988, 3198], +] + +[[area.polygons]] +vertices = [ + [2987, 3175], + [2987, 3197], +] + +[[area.polygons]] +vertices = [ + [2986, 3178], + [2986, 3196], +] + +[[area.polygons]] +vertices = [ + [3030, 3179], + [3030, 3180], +] + +[[area.polygons]] +vertices = [ + [2985, 3181], + [2985, 3195], +] + +[[area.polygons]] +vertices = [ + [2984, 3182], + [2984, 3194], +] + +[[area.polygons]] +vertices = [[2983, 3184]] + +[[area.polygons]] +vertices = [ + [3028, 3185], + [3028, 3186], +] + +[[area.polygons]] +vertices = [ + [3026, 3188], + [3026, 3189], +] + +[[area.polygons]] +vertices = [ + [2983, 3191], + [2983, 3193], +] + +[[area.polygons]] +vertices = [[3024, 3191]] + +[[area.polygons]] +vertices = [ + [3018, 3199], + [3019, 3199], +] + +[[area.polygons]] +vertices = [ + [2998, 3202], + [3005, 3202], + [3005, 3203], + [2998, 3203], +] + +[[area.polygons]] +vertices = [[2994, 3203]] + +[[area.polygons]] +vertices = [ + [2998, 3204], + [3004, 3204], + [3004, 3205], + [2998, 3205], +] diff --git a/.data/raw-cache/map/area/too_many_cooks.toml b/.data/raw-cache/map/area/too_many_cooks.toml new file mode 100644 index 000000000..58fd4e47c --- /dev/null +++ b/.data/raw-cache/map/area/too_many_cooks.toml @@ -0,0 +1,16 @@ +# Too Many Cooks... music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Too Many Cooks.... +# Bounds x 2944-3007, z 9856-9919; 4096 tiles as 1 rectangles. + +[[area]] +name = "Too Many Cooks..." +area_id = "area.too_many_cooks" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2944, 9856], + [3007, 9856], + [3007, 9919], + [2944, 9919], +] diff --git a/.data/raw-cache/map/area/trahaearn_toil.toml b/.data/raw-cache/map/area/trahaearn_toil.toml new file mode 100644 index 000000000..f414f9aff --- /dev/null +++ b/.data/raw-cache/map/area/trahaearn_toil.toml @@ -0,0 +1,32 @@ +# Trahaearn Toil music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Trahaearn Toil. +# Bounds x 3008-3327, z 6062-12543; 9344 tiles as 3 rectangles. + +[[area]] +name = "Trahaearn Toil" +area_id = "area.trahaearn_toil" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3008, 6062], + [3071, 6062], + [3071, 6079], + [3008, 6079], +] + +[[area.polygons]] +vertices = [ + [3264, 12416], + [3327, 12416], + [3327, 12479], + [3264, 12479], +] + +[[area.polygons]] +vertices = [ + [3200, 12480], + [3263, 12480], + [3263, 12543], + [3200, 12543], +] diff --git a/.data/raw-cache/map/area/tranquillity.toml b/.data/raw-cache/map/area/tranquillity.toml new file mode 100644 index 000000000..b331e4a10 --- /dev/null +++ b/.data/raw-cache/map/area/tranquillity.toml @@ -0,0 +1,1017 @@ +# Tranquillity music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Tranquillity. +# Bounds x 1538-3064, z 2596-9535; 6765 tiles as 163 rectangles. + +[[area]] +name = "Tranquillity" +area_id = "area.tranquillity" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2065, 2596], + [2069, 2596], + [2069, 2616], + [2065, 2616], +] + +[[area.polygons]] +vertices = [ + [2064, 2597], + [2064, 2616], +] + +[[area.polygons]] +vertices = [ + [2070, 2597], + [2072, 2597], + [2072, 2615], + [2070, 2615], +] + +[[area.polygons]] +vertices = [ + [2063, 2598], + [2063, 2615], +] + +[[area.polygons]] +vertices = [ + [2073, 2598], + [2073, 2614], +] + +[[area.polygons]] +vertices = [ + [2062, 2600], + [2062, 2614], +] + +[[area.polygons]] +vertices = [ + [2061, 2601], + [2061, 2611], +] + +[[area.polygons]] +vertices = [ + [2060, 2602], + [2060, 2610], +] + +[[area.polygons]] +vertices = [ + [2059, 2604], + [2059, 2606], +] + +[[area.polygons]] +vertices = [ + [2074, 2606], + [2074, 2613], +] + +[[area.polygons]] +vertices = [ + [2075, 2607], + [2075, 2612], +] + +[[area.polygons]] +vertices = [ + [3042, 2630], + [3042, 2658], +] + +[[area.polygons]] +vertices = [ + [3041, 2631], + [3041, 2656], +] + +[[area.polygons]] +vertices = [ + [3043, 2631], + [3044, 2631], + [3044, 2659], + [3043, 2659], +] + +[[area.polygons]] +vertices = [ + [3038, 2632], + [3040, 2632], + [3040, 2653], + [3038, 2653], +] + +[[area.polygons]] +vertices = [ + [3045, 2632], + [3046, 2632], + [3046, 2662], + [3045, 2662], +] + +[[area.polygons]] +vertices = [ + [3037, 2633], + [3037, 2652], +] + +[[area.polygons]] +vertices = [ + [3047, 2633], + [3055, 2633], + [3055, 2660], + [3047, 2660], +] + +[[area.polygons]] +vertices = [ + [3036, 2634], + [3036, 2652], +] + +[[area.polygons]] +vertices = [ + [3056, 2634], + [3057, 2634], + [3057, 2659], + [3056, 2659], +] + +[[area.polygons]] +vertices = [ + [3035, 2635], + [3035, 2650], +] + +[[area.polygons]] +vertices = [ + [3058, 2635], + [3058, 2654], +] + +[[area.polygons]] +vertices = [ + [3059, 2636], + [3059, 2650], +] + +[[area.polygons]] +vertices = [ + [3034, 2637], + [3034, 2639], +] + +[[area.polygons]] +vertices = [ + [3060, 2637], + [3060, 2648], +] + +[[area.polygons]] +vertices = [[3033, 2638]] + +[[area.polygons]] +vertices = [ + [3061, 2639], + [3061, 2647], +] + +[[area.polygons]] +vertices = [ + [3062, 2640], + [3062, 2646], +] + +[[area.polygons]] +vertices = [ + [3063, 2641], + [3063, 2645], +] + +[[area.polygons]] +vertices = [ + [3034, 2642], + [3034, 2649], +] + +[[area.polygons]] +vertices = [ + [3064, 2643], + [3064, 2644], +] + +[[area.polygons]] +vertices = [ + [3033, 2645], + [3033, 2647], +] + +[[area.polygons]] +vertices = [ + [3039, 2654], + [3040, 2654], +] + +[[area.polygons]] +vertices = [[3040, 2655]] + +[[area.polygons]] +vertices = [ + [2020, 2659], + [2020, 2674], +] + +[[area.polygons]] +vertices = [ + [2019, 2660], + [2019, 2674], +] + +[[area.polygons]] +vertices = [ + [2021, 2660], + [2021, 2673], +] + +[[area.polygons]] +vertices = [ + [3044, 2660], + [3044, 2661], +] + +[[area.polygons]] +vertices = [[3056, 2660]] + +[[area.polygons]] +vertices = [ + [2022, 2661], + [2028, 2661], + [2028, 2669], + [2022, 2669], +] + +[[area.polygons]] +vertices = [ + [3047, 2661], + [3054, 2661], +] + +[[area.polygons]] +vertices = [ + [2018, 2662], + [2018, 2674], +] + +[[area.polygons]] +vertices = [ + [2029, 2662], + [2030, 2662], + [2030, 2669], + [2029, 2669], +] + +[[area.polygons]] +vertices = [ + [2033, 2662], + [2036, 2662], + [2036, 2665], + [2033, 2665], +] + +[[area.polygons]] +vertices = [ + [3047, 2662], + [3049, 2662], +] + +[[area.polygons]] +vertices = [ + [2016, 2663], + [2017, 2663], + [2017, 2672], + [2016, 2672], +] + +[[area.polygons]] +vertices = [ + [2031, 2663], + [2032, 2663], + [2032, 2668], + [2031, 2668], +] + +[[area.polygons]] +vertices = [ + [2037, 2663], + [2037, 2664], +] + +[[area.polygons]] +vertices = [ + [2015, 2664], + [2015, 2671], +] + +[[area.polygons]] +vertices = [ + [2014, 2666], + [2014, 2670], +] + +[[area.polygons]] +vertices = [ + [2033, 2666], + [2035, 2666], +] + +[[area.polygons]] +vertices = [[2033, 2667]] + +[[area.polygons]] +vertices = [ + [2022, 2670], + [2027, 2670], + [2027, 2671], + [2022, 2671], +] + +[[area.polygons]] +vertices = [ + [2022, 2672], + [2026, 2672], +] + +[[area.polygons]] +vertices = [[2017, 2673]] + +[[area.polygons]] +vertices = [ + [2022, 2673], + [2025, 2673], +] + +[[area.polygons]] +vertices = [ + [2023, 2674], + [2024, 2674], +] + +[[area.polygons]] +vertices = [ + [1593, 2753], + [1594, 2753], + [1594, 2757], + [1593, 2757], +] + +[[area.polygons]] +vertices = [ + [1590, 2754], + [1592, 2754], + [1592, 2758], + [1590, 2758], +] + +[[area.polygons]] +vertices = [ + [1595, 2754], + [1595, 2772], +] + +[[area.polygons]] +vertices = [ + [1588, 2755], + [1589, 2755], + [1589, 2760], + [1588, 2760], +] + +[[area.polygons]] +vertices = [ + [1587, 2756], + [1587, 2762], +] + +[[area.polygons]] +vertices = [ + [1596, 2756], + [1596, 2770], +] + +[[area.polygons]] +vertices = [ + [1585, 2757], + [1586, 2757], + [1586, 2763], + [1585, 2763], +] + +[[area.polygons]] +vertices = [ + [1597, 2757], + [1597, 2766], +] + +[[area.polygons]] +vertices = [ + [1584, 2758], + [1584, 2765], +] + +[[area.polygons]] +vertices = [ + [1594, 2758], + [1594, 2775], +] + +[[area.polygons]] +vertices = [ + [1583, 2759], + [1583, 2766], +] + +[[area.polygons]] +vertices = [[1590, 2759]] + +[[area.polygons]] +vertices = [ + [1582, 2760], + [1582, 2767], +] + +[[area.polygons]] +vertices = [ + [1581, 2761], + [1581, 2768], +] + +[[area.polygons]] +vertices = [[1588, 2761]] + +[[area.polygons]] +vertices = [ + [1580, 2762], + [1580, 2768], +] + +[[area.polygons]] +vertices = [ + [1579, 2763], + [1579, 2769], +] + +[[area.polygons]] +vertices = [ + [1593, 2763], + [1593, 2789], +] + +[[area.polygons]] +vertices = [ + [1578, 2764], + [1578, 2770], +] + +[[area.polygons]] +vertices = [[1585, 2764]] + +[[area.polygons]] +vertices = [ + [1592, 2764], + [1592, 2791], +] + +[[area.polygons]] +vertices = [ + [1577, 2765], + [1577, 2771], +] + +[[area.polygons]] +vertices = [ + [1576, 2766], + [1576, 2771], +] + +[[area.polygons]] +vertices = [ + [1591, 2766], + [1591, 2792], +] + +[[area.polygons]] +vertices = [ + [1574, 2767], + [1575, 2767], + [1575, 2772], + [1574, 2772], +] + +[[area.polygons]] +vertices = [ + [1573, 2768], + [1573, 2773], +] + +[[area.polygons]] +vertices = [ + [1590, 2768], + [1590, 2793], +] + +[[area.polygons]] +vertices = [ + [1570, 2769], + [1572, 2769], + [1572, 2773], + [1570, 2773], +] + +[[area.polygons]] +vertices = [ + [1567, 2770], + [1569, 2770], + [1569, 2774], + [1567, 2774], +] + +[[area.polygons]] +vertices = [ + [1589, 2770], + [1589, 2794], +] + +[[area.polygons]] +vertices = [ + [1549, 2771], + [1550, 2771], + [1550, 2776], + [1549, 2776], +] + +[[area.polygons]] +vertices = [ + [1558, 2771], + [1566, 2771], + [1566, 2775], + [1558, 2775], +] + +[[area.polygons]] +vertices = [ + [1546, 2772], + [1548, 2772], + [1548, 2778], + [1546, 2778], +] + +[[area.polygons]] +vertices = [ + [1551, 2772], + [1557, 2772], + [1557, 2775], + [1551, 2775], +] + +[[area.polygons]] +vertices = [ + [1588, 2772], + [1588, 2795], +] + +[[area.polygons]] +vertices = [ + [1545, 2773], + [1545, 2781], +] + +[[area.polygons]] +vertices = [[1570, 2774]] + +[[area.polygons]] +vertices = [[1567, 2775]] + +[[area.polygons]] +vertices = [ + [1544, 2776], + [1544, 2781], +] + +[[area.polygons]] +vertices = [[1551, 2776]] + +[[area.polygons]] +vertices = [ + [1553, 2776], + [1563, 2776], +] + +[[area.polygons]] +vertices = [ + [1543, 2777], + [1543, 2781], +] + +[[area.polygons]] +vertices = [[1549, 2777]] + +[[area.polygons]] +vertices = [ + [1542, 2778], + [1542, 2781], +] + +[[area.polygons]] +vertices = [ + [1541, 2779], + [1541, 2780], +] + +[[area.polygons]] +vertices = [ + [1546, 2779], + [1547, 2779], +] + +[[area.polygons]] +vertices = [ + [1587, 2779], + [1587, 2795], +] + +[[area.polygons]] +vertices = [[1546, 2780]] + +[[area.polygons]] +vertices = [ + [1586, 2781], + [1586, 2795], +] + +[[area.polygons]] +vertices = [ + [1585, 2783], + [1585, 2796], +] + +[[area.polygons]] +vertices = [ + [1594, 2784], + [1594, 2788], +] + +[[area.polygons]] +vertices = [ + [1584, 2786], + [1584, 2796], +] + +[[area.polygons]] +vertices = [ + [1583, 2788], + [1583, 2796], +] + +[[area.polygons]] +vertices = [ + [1541, 2789], + [1543, 2789], + [1543, 2813], + [1541, 2813], +] + +[[area.polygons]] +vertices = [ + [1582, 2789], + [1582, 2797], +] + +[[area.polygons]] +vertices = [ + [1540, 2790], + [1540, 2812], +] + +[[area.polygons]] +vertices = [ + [1544, 2790], + [1545, 2790], + [1545, 2798], + [1544, 2798], +] + +[[area.polygons]] +vertices = [ + [1580, 2790], + [1581, 2790], + [1581, 2797], + [1580, 2797], +] + +[[area.polygons]] +vertices = [ + [1579, 2791], + [1579, 2797], +] + +[[area.polygons]] +vertices = [ + [1539, 2792], + [1539, 2812], +] + +[[area.polygons]] +vertices = [ + [1575, 2792], + [1578, 2792], + [1578, 2797], + [1575, 2797], +] + +[[area.polygons]] +vertices = [ + [1573, 2793], + [1574, 2793], + [1574, 2800], + [1573, 2800], +] + +[[area.polygons]] +vertices = [ + [1538, 2794], + [1538, 2799], +] + +[[area.polygons]] +vertices = [ + [1571, 2794], + [1572, 2794], + [1572, 2801], + [1571, 2801], +] + +[[area.polygons]] +vertices = [ + [1546, 2795], + [1547, 2795], + [1547, 2796], + [1546, 2796], +] + +[[area.polygons]] +vertices = [ + [1570, 2795], + [1570, 2802], +] + +[[area.polygons]] +vertices = [ + [1569, 2796], + [1569, 2803], +] + +[[area.polygons]] +vertices = [[1546, 2797]] + +[[area.polygons]] +vertices = [ + [1568, 2797], + [1568, 2804], +] + +[[area.polygons]] +vertices = [ + [1567, 2798], + [1567, 2805], +] + +[[area.polygons]] +vertices = [ + [1575, 2798], + [1577, 2798], +] + +[[area.polygons]] +vertices = [ + [1544, 2799], + [1544, 2812], +] + +[[area.polygons]] +vertices = [ + [1566, 2799], + [1566, 2805], +] + +[[area.polygons]] +vertices = [ + [1575, 2799], + [1576, 2799], +] + +[[area.polygons]] +vertices = [ + [1565, 2800], + [1565, 2805], +] + +[[area.polygons]] +vertices = [ + [1563, 2801], + [1564, 2801], + [1564, 2806], + [1563, 2806], +] + +[[area.polygons]] +vertices = [ + [1545, 2802], + [1545, 2812], +] + +[[area.polygons]] +vertices = [ + [1562, 2802], + [1562, 2807], +] + +[[area.polygons]] +vertices = [ + [1561, 2803], + [1561, 2808], +] + +[[area.polygons]] +vertices = [ + [1538, 2804], + [1538, 2810], +] + +[[area.polygons]] +vertices = [ + [1546, 2804], + [1546, 2812], +] + +[[area.polygons]] +vertices = [ + [1560, 2804], + [1560, 2809], +] + +[[area.polygons]] +vertices = [ + [1559, 2805], + [1559, 2810], +] + +[[area.polygons]] +vertices = [ + [1556, 2806], + [1558, 2806], + [1558, 2811], + [1556, 2811], +] + +[[area.polygons]] +vertices = [ + [1547, 2807], + [1547, 2812], +] + +[[area.polygons]] +vertices = [ + [1553, 2807], + [1555, 2807], + [1555, 2812], + [1553, 2812], +] + +[[area.polygons]] +vertices = [ + [1548, 2808], + [1552, 2808], + [1552, 2812], + [1548, 2812], +] + +[[area.polygons]] +vertices = [[1556, 2812]] + +[[area.polygons]] +vertices = [ + [1965, 3088], + [1970, 3088], + [1970, 3113], + [1965, 3113], +] + +[[area.polygons]] +vertices = [ + [1963, 3089], + [1964, 3089], + [1964, 3116], + [1963, 3116], +] + +[[area.polygons]] +vertices = [ + [1962, 3090], + [1962, 3117], +] + +[[area.polygons]] +vertices = [ + [1961, 3091], + [1961, 3117], +] + +[[area.polygons]] +vertices = [ + [1971, 3092], + [1971, 3112], +] + +[[area.polygons]] +vertices = [ + [1972, 3094], + [1972, 3111], +] + +[[area.polygons]] +vertices = [ + [1960, 3095], + [1960, 3116], +] + +[[area.polygons]] +vertices = [ + [1973, 3095], + [1973, 3109], +] + +[[area.polygons]] +vertices = [ + [1959, 3096], + [1959, 3116], +] + +[[area.polygons]] +vertices = [ + [1958, 3097], + [1958, 3115], +] + +[[area.polygons]] +vertices = [ + [1974, 3097], + [1974, 3107], +] + +[[area.polygons]] +vertices = [ + [1957, 3099], + [1957, 3102], +] + +[[area.polygons]] +vertices = [ + [1957, 3105], + [1957, 3113], +] + +[[area.polygons]] +vertices = [ + [1956, 3106], + [1956, 3111], +] + +[[area.polygons]] +vertices = [ + [1955, 3107], + [1955, 3110], +] + +[[area.polygons]] +vertices = [ + [1965, 3114], + [1969, 3114], +] + +[[area.polygons]] +vertices = [ + [1965, 3115], + [1966, 3115], +] + +[[area.polygons]] +vertices = [ + [1920, 9472], + [1983, 9472], + [1983, 9535], + [1920, 9535], +] diff --git a/.data/raw-cache/map/area/trawler.toml b/.data/raw-cache/map/area/trawler.toml new file mode 100644 index 000000000..75980e893 --- /dev/null +++ b/.data/raw-cache/map/area/trawler.toml @@ -0,0 +1,16 @@ +# Trawler music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Trawler. +# Bounds x 1856-1919, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Trawler" +area_id = "area.trawler" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 4800], + [1919, 4800], + [1919, 4863], + [1856, 4863], +] diff --git a/.data/raw-cache/map/area/trawler_minor.toml b/.data/raw-cache/map/area/trawler_minor.toml new file mode 100644 index 000000000..c95ba1d1e --- /dev/null +++ b/.data/raw-cache/map/area/trawler_minor.toml @@ -0,0 +1,16 @@ +# Trawler Minor music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Trawler Minor. +# Bounds x 1920-2047, z 4800-4863; 8192 tiles as 1 rectangles. + +[[area]] +name = "Trawler Minor" +area_id = "area.trawler_minor" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1920, 4800], + [2047, 4800], + [2047, 4863], + [1920, 4863], +] diff --git a/.data/raw-cache/map/area/tree_gnome_stronghold.toml b/.data/raw-cache/map/area/tree_gnome_stronghold.toml new file mode 100644 index 000000000..f94adc15b --- /dev/null +++ b/.data/raw-cache/map/area/tree_gnome_stronghold.toml @@ -0,0 +1,565 @@ +# Everything inside the Tree Gnome Stronghold walls (south gate at 2459,3383 up to the north +# wall, the terrorbird pens in the west and the agility course in the east) except the Gnome Ball +# field, which is its own music area (see gnome_ball_field.toml, x 2384-2405, z 3479-3497). Music +# area for "Gnome King", "Gnome Village" and "Gnome Village 2". +# South of the ball field: the whole stronghold below z 3479. +# Strip between the west wall and the ball field gate. +# Everything east of the ball field, including the Grand Tree. +# North of the ball field up to the north wall. +# +# Regenerated as rectangles from the OSRS wiki music map: 1895 tiles moved to +# neighbouring music areas and 497 tiles were added. + +[[area]] +name = "Tree Gnome Stronghold" +area_id = "area.tree_gnome_stronghold" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2458, 3384], + [2463, 3384], + [2463, 3519], + [2458, 3519], +] + +[[area.polygons]] +vertices = [ + [2457, 3385], + [2457, 3519], +] + +[[area.polygons]] +vertices = [ + [2464, 3385], + [2464, 3519], +] + +[[area.polygons]] +vertices = [ + [2465, 3386], + [2465, 3519], +] + +[[area.polygons]] +vertices = [ + [2434, 3388], + [2438, 3388], + [2438, 3519], + [2434, 3519], +] + +[[area.polygons]] +vertices = [ + [2433, 3389], + [2433, 3519], +] + +[[area.polygons]] +vertices = [ + [2439, 3389], + [2440, 3389], + [2440, 3519], + [2439, 3519], +] + +[[area.polygons]] +vertices = [ + [2456, 3389], + [2456, 3519], +] + +[[area.polygons]] +vertices = [ + [2476, 3389], + [2480, 3389], + [2480, 3519], + [2476, 3519], +] + +[[area.polygons]] +vertices = [ + [2485, 3389], + [2487, 3389], + [2487, 3519], + [2485, 3519], +] + +[[area.polygons]] +vertices = [ + [2432, 3390], + [2432, 3519], +] + +[[area.polygons]] +vertices = [ + [2441, 3390], + [2441, 3519], +] + +[[area.polygons]] +vertices = [ + [2446, 3390], + [2448, 3390], + [2448, 3519], + [2446, 3519], +] + +[[area.polygons]] +vertices = [ + [2455, 3390], + [2455, 3519], +] + +[[area.polygons]] +vertices = [ + [2466, 3390], + [2466, 3519], +] + +[[area.polygons]] +vertices = [ + [2471, 3390], + [2475, 3390], + [2475, 3519], + [2471, 3519], +] + +[[area.polygons]] +vertices = [ + [2481, 3390], + [2484, 3390], + [2484, 3519], + [2481, 3519], +] + +[[area.polygons]] +vertices = [ + [2493, 3390], + [2500, 3390], + [2500, 3396], + [2493, 3396], +] + +[[area.polygons]] +vertices = [ + [2428, 3391], + [2431, 3391], + [2431, 3519], + [2428, 3519], +] + +[[area.polygons]] +vertices = [ + [2442, 3391], + [2445, 3391], + [2445, 3519], + [2442, 3519], +] + +[[area.polygons]] +vertices = [ + [2449, 3391], + [2454, 3391], + [2454, 3519], + [2449, 3519], +] + +[[area.polygons]] +vertices = [ + [2467, 3391], + [2470, 3391], + [2470, 3519], + [2467, 3519], +] + +[[area.polygons]] +vertices = [ + [2488, 3391], + [2492, 3391], + [2492, 3519], + [2488, 3519], +] + +[[area.polygons]] +vertices = [ + [2501, 3391], + [2504, 3391], + [2504, 3395], + [2501, 3395], +] + +[[area.polygons]] +vertices = [ + [2427, 3392], + [2427, 3531], +] + +[[area.polygons]] +vertices = [ + [2505, 3392], + [2505, 3394], +] + +[[area.polygons]] +vertices = [ + [2420, 3393], + [2426, 3393], + [2426, 3531], + [2420, 3531], +] + +[[area.polygons]] +vertices = [ + [2419, 3394], + [2419, 3531], +] + +[[area.polygons]] +vertices = [ + [2418, 3395], + [2418, 3531], +] + +[[area.polygons]] +vertices = [ + [2417, 3396], + [2417, 3531], +] + +[[area.polygons]] +vertices = [ + [2416, 3397], + [2416, 3531], +] + +[[area.polygons]] +vertices = [ + [2493, 3397], + [2499, 3397], +] + +[[area.polygons]] +vertices = [ + [2493, 3398], + [2498, 3398], +] + +[[area.polygons]] +vertices = [ + [2415, 3399], + [2415, 3531], +] + +[[area.polygons]] +vertices = [ + [2493, 3399], + [2497, 3399], +] + +[[area.polygons]] +vertices = [ + [2414, 3400], + [2414, 3531], +] + +[[area.polygons]] +vertices = [ + [2493, 3400], + [2496, 3400], +] + +[[area.polygons]] +vertices = [ + [2493, 3401], + [2495, 3401], +] + +[[area.polygons]] +vertices = [ + [2493, 3402], + [2494, 3402], +] + +[[area.polygons]] +vertices = [ + [2493, 3403], + [2493, 3519], +] + +[[area.polygons]] +vertices = [ + [2383, 3407], + [2386, 3407], + [2386, 3478], + [2383, 3478], +] + +[[area.polygons]] +vertices = [ + [2380, 3408], + [2382, 3408], + [2382, 3463], + [2380, 3463], +] + +[[area.polygons]] +vertices = [ + [2387, 3408], + [2387, 3478], +] + +[[area.polygons]] +vertices = [ + [2413, 3408], + [2413, 3531], +] + +[[area.polygons]] +vertices = [ + [2379, 3409], + [2379, 3462], +] + +[[area.polygons]] +vertices = [ + [2388, 3409], + [2388, 3478], +] + +[[area.polygons]] +vertices = [ + [2412, 3409], + [2412, 3531], +] + +[[area.polygons]] +vertices = [ + [2494, 3409], + [2494, 3431], +] + +[[area.polygons]] +vertices = [ + [2377, 3410], + [2378, 3410], + [2378, 3460], + [2377, 3460], +] + +[[area.polygons]] +vertices = [ + [2389, 3410], + [2389, 3478], +] + +[[area.polygons]] +vertices = [ + [2411, 3410], + [2411, 3531], +] + +[[area.polygons]] +vertices = [ + [2376, 3411], + [2376, 3459], +] + +[[area.polygons]] +vertices = [ + [2390, 3411], + [2390, 3478], +] + +[[area.polygons]] +vertices = [ + [2410, 3411], + [2410, 3531], +] + +[[area.polygons]] +vertices = [ + [2391, 3412], + [2409, 3412], + [2409, 3478], + [2391, 3478], +] + +[[area.polygons]] +vertices = [ + [2375, 3413], + [2375, 3458], +] + +[[area.polygons]] +vertices = [ + [2495, 3413], + [2495, 3430], +] + +[[area.polygons]] +vertices = [ + [2374, 3417], + [2374, 3446], +] + +[[area.polygons]] +vertices = [ + [2496, 3417], + [2496, 3429], +] + +[[area.polygons]] +vertices = [ + [2373, 3418], + [2373, 3445], +] + +[[area.polygons]] +vertices = [ + [2497, 3418], + [2497, 3428], +] + +[[area.polygons]] +vertices = [ + [2372, 3419], + [2372, 3444], +] + +[[area.polygons]] +vertices = [ + [2371, 3420], + [2371, 3443], +] + +[[area.polygons]] +vertices = [ + [2370, 3421], + [2370, 3433], +] + +[[area.polygons]] +vertices = [ + [2369, 3422], + [2369, 3432], +] + +[[area.polygons]] +vertices = [ + [2494, 3439], + [2494, 3519], +] + +[[area.polygons]] +vertices = [ + [2495, 3440], + [2498, 3440], + [2498, 3519], + [2495, 3519], +] + +[[area.polygons]] +vertices = [[2378, 3461]] + +[[area.polygons]] +vertices = [ + [2381, 3464], + [2382, 3464], + [2382, 3520], + [2381, 3520], +] + +[[area.polygons]] +vertices = [ + [2380, 3473], + [2380, 3520], +] + +[[area.polygons]] +vertices = [ + [2379, 3474], + [2379, 3519], +] + +[[area.polygons]] +vertices = [ + [2378, 3475], + [2378, 3519], +] + +[[area.polygons]] +vertices = [ + [2377, 3476], + [2377, 3519], +] + +[[area.polygons]] +vertices = [ + [2376, 3477], + [2376, 3519], +] + +[[area.polygons]] +vertices = [ + [2375, 3478], + [2375, 3519], +] + +[[area.polygons]] +vertices = [ + [2383, 3479], + [2383, 3521], +] + +[[area.polygons]] +vertices = [ + [2406, 3479], + [2409, 3479], + [2409, 3521], + [2406, 3521], +] + +[[area.polygons]] +vertices = [ + [2384, 3498], + [2405, 3498], + [2405, 3522], + [2384, 3522], +] + +[[area.polygons]] +vertices = [ + [2428, 3520], + [2430, 3520], +] + +[[area.polygons]] +vertices = [[2382, 3521]] + +[[area.polygons]] +vertices = [ + [2428, 3521], + [2429, 3521], +] + +[[area.polygons]] +vertices = [ + [2409, 3522], + [2409, 3531], +] + +[[area.polygons]] +vertices = [[2428, 3522]] + +[[area.polygons]] +vertices = [ + [2393, 3523], + [2403, 3523], +] + +[[area.polygons]] +vertices = [ + [2399, 3524], + [2402, 3524], +] diff --git a/.data/raw-cache/map/area/tribal_background.toml b/.data/raw-cache/map/area/tribal_background.toml new file mode 100644 index 000000000..a58fa551b --- /dev/null +++ b/.data/raw-cache/map/area/tribal_background.toml @@ -0,0 +1,300 @@ +# Tribal Background music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Tribal Background. +# Bounds x 2771-2829, z 3052-3113; 2760 tiles as 46 rectangles. + +[[area]] +name = "Tribal Background" +area_id = "area.tribal_background" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2788, 3052], + [2791, 3052], + [2791, 3112], + [2788, 3112], +] + +[[area.polygons]] +vertices = [ + [2785, 3053], + [2787, 3053], + [2787, 3112], + [2785, 3112], +] + +[[area.polygons]] +vertices = [ + [2792, 3053], + [2794, 3053], + [2794, 3113], + [2792, 3113], +] + +[[area.polygons]] +vertices = [ + [2784, 3054], + [2784, 3111], +] + +[[area.polygons]] +vertices = [ + [2795, 3054], + [2795, 3113], +] + +[[area.polygons]] +vertices = [ + [2778, 3055], + [2781, 3055], + [2781, 3107], + [2778, 3107], +] + +[[area.polygons]] +vertices = [ + [2783, 3055], + [2783, 3111], +] + +[[area.polygons]] +vertices = [ + [2796, 3055], + [2796, 3113], +] + +[[area.polygons]] +vertices = [ + [2798, 3055], + [2801, 3055], + [2801, 3113], + [2798, 3113], +] + +[[area.polygons]] +vertices = [ + [2782, 3056], + [2782, 3110], +] + +[[area.polygons]] +vertices = [ + [2797, 3056], + [2797, 3113], +] + +[[area.polygons]] +vertices = [ + [2802, 3056], + [2802, 3113], +] + +[[area.polygons]] +vertices = [ + [2803, 3057], + [2805, 3057], + [2805, 3113], + [2803, 3113], +] + +[[area.polygons]] +vertices = [ + [2777, 3058], + [2777, 3106], +] + +[[area.polygons]] +vertices = [ + [2806, 3058], + [2806, 3113], +] + +[[area.polygons]] +vertices = [ + [2776, 3059], + [2776, 3105], +] + +[[area.polygons]] +vertices = [ + [2807, 3059], + [2808, 3059], + [2808, 3113], + [2807, 3113], +] + +[[area.polygons]] +vertices = [ + [2775, 3060], + [2775, 3071], +] + +[[area.polygons]] +vertices = [ + [2809, 3060], + [2809, 3113], +] + +[[area.polygons]] +vertices = [ + [2773, 3061], + [2774, 3061], + [2774, 3068], + [2773, 3068], +] + +[[area.polygons]] +vertices = [ + [2810, 3061], + [2810, 3112], +] + +[[area.polygons]] +vertices = [ + [2811, 3062], + [2811, 3112], +] + +[[area.polygons]] +vertices = [ + [2812, 3063], + [2813, 3063], + [2813, 3111], + [2812, 3111], +] + +[[area.polygons]] +vertices = [ + [2772, 3064], + [2772, 3067], +] + +[[area.polygons]] +vertices = [ + [2814, 3064], + [2814, 3110], +] + +[[area.polygons]] +vertices = [ + [2815, 3065], + [2815, 3109], +] + +[[area.polygons]] +vertices = [ + [2816, 3067], + [2816, 3108], +] + +[[area.polygons]] +vertices = [ + [2817, 3068], + [2818, 3068], + [2818, 3106], + [2817, 3106], +] + +[[area.polygons]] +vertices = [ + [2774, 3069], + [2774, 3070], +] + +[[area.polygons]] +vertices = [ + [2819, 3069], + [2819, 3105], +] + +[[area.polygons]] +vertices = [ + [2820, 3071], + [2820, 3104], +] + +[[area.polygons]] +vertices = [ + [2821, 3072], + [2821, 3101], +] + +[[area.polygons]] +vertices = [ + [2775, 3073], + [2775, 3104], +] + +[[area.polygons]] +vertices = [ + [2822, 3073], + [2822, 3100], +] + +[[area.polygons]] +vertices = [ + [2774, 3075], + [2774, 3103], +] + +[[area.polygons]] +vertices = [ + [2823, 3075], + [2823, 3097], +] + +[[area.polygons]] +vertices = [ + [2824, 3076], + [2827, 3076], + [2827, 3090], + [2824, 3090], +] + +[[area.polygons]] +vertices = [ + [2773, 3077], + [2773, 3100], +] + +[[area.polygons]] +vertices = [ + [2828, 3077], + [2828, 3089], +] + +[[area.polygons]] +vertices = [ + [2829, 3078], + [2829, 3088], +] + +[[area.polygons]] +vertices = [ + [2772, 3079], + [2772, 3099], +] + +[[area.polygons]] +vertices = [ + [2771, 3082], + [2771, 3097], +] + +[[area.polygons]] +vertices = [[2817, 3107]] + +[[area.polygons]] +vertices = [ + [2779, 3108], + [2781, 3108], +] + +[[area.polygons]] +vertices = [[2781, 3109]] + +[[area.polygons]] +vertices = [ + [2789, 3113], + [2791, 3113], +] diff --git a/.data/raw-cache/map/area/trinity.toml b/.data/raw-cache/map/area/trinity.toml new file mode 100644 index 000000000..3cc8c8111 --- /dev/null +++ b/.data/raw-cache/map/area/trinity.toml @@ -0,0 +1,39 @@ +# Trinity music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Trinity. +# Bounds x 2688-2756, z 3387-9791; 4113 tiles as 5 rectangles. + +[[area]] +name = "Trinity" +area_id = "area.trinity" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 3387], + [2753, 3387], + [2753, 3391], + [2752, 3391], +] + +[[area.polygons]] +vertices = [ + [2754, 3388], + [2754, 3391], +] + +[[area.polygons]] +vertices = [ + [2755, 3390], + [2755, 3391], +] + +[[area.polygons]] +vertices = [[2756, 3391]] + +[[area.polygons]] +vertices = [ + [2688, 9728], + [2751, 9728], + [2751, 9791], + [2688, 9791], +] diff --git a/.data/raw-cache/map/area/troll_country.toml b/.data/raw-cache/map/area/troll_country.toml new file mode 100644 index 000000000..d9364386c --- /dev/null +++ b/.data/raw-cache/map/area/troll_country.toml @@ -0,0 +1,372 @@ +# Troll Country music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Tremble; Sojourn; Contest. +# Bounds x 2803-2947, z 3546-3746; 20007 tiles as 57 rectangles. + +[[area]] +name = "Troll Country" +area_id = "area.troll_country" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2803, 3546], + [2827, 3546], + [2827, 3647], + [2803, 3647], +] + +[[area.polygons]] +vertices = [ + [2828, 3547], + [2828, 3746], +] + +[[area.polygons]] +vertices = [ + [2829, 3548], + [2829, 3746], +] + +[[area.polygons]] +vertices = [ + [2830, 3549], + [2830, 3746], +] + +[[area.polygons]] +vertices = [ + [2831, 3550], + [2831, 3746], +] + +[[area.polygons]] +vertices = [ + [2832, 3551], + [2832, 3746], +] + +[[area.polygons]] +vertices = [ + [2833, 3552], + [2833, 3746], +] + +[[area.polygons]] +vertices = [ + [2834, 3553], + [2834, 3746], +] + +[[area.polygons]] +vertices = [ + [2835, 3554], + [2835, 3746], +] + +[[area.polygons]] +vertices = [ + [2836, 3555], + [2836, 3746], +] + +[[area.polygons]] +vertices = [ + [2837, 3556], + [2846, 3556], + [2846, 3715], + [2837, 3715], +] + +[[area.polygons]] +vertices = [ + [2872, 3576], + [2879, 3576], + [2879, 3715], + [2872, 3715], +] + +[[area.polygons]] +vertices = [ + [2870, 3577], + [2871, 3577], + [2871, 3715], + [2870, 3715], +] + +[[area.polygons]] +vertices = [ + [2880, 3577], + [2880, 3715], +] + +[[area.polygons]] +vertices = [ + [2847, 3578], + [2848, 3578], + [2848, 3715], + [2847, 3715], +] + +[[area.polygons]] +vertices = [ + [2868, 3578], + [2869, 3578], + [2869, 3715], + [2868, 3715], +] + +[[area.polygons]] +vertices = [ + [2881, 3578], + [2881, 3715], +] + +[[area.polygons]] +vertices = [ + [2866, 3579], + [2867, 3579], + [2867, 3715], + [2866, 3715], +] + +[[area.polygons]] +vertices = [ + [2882, 3579], + [2882, 3715], +] + +[[area.polygons]] +vertices = [ + [2849, 3580], + [2849, 3715], +] + +[[area.polygons]] +vertices = [ + [2857, 3580], + [2865, 3580], + [2865, 3715], + [2857, 3715], +] + +[[area.polygons]] +vertices = [ + [2883, 3580], + [2883, 3715], +] + +[[area.polygons]] +vertices = [ + [2856, 3581], + [2856, 3715], +] + +[[area.polygons]] +vertices = [ + [2884, 3581], + [2884, 3715], +] + +[[area.polygons]] +vertices = [ + [2850, 3582], + [2855, 3582], + [2855, 3715], + [2850, 3715], +] + +[[area.polygons]] +vertices = [ + [2885, 3582], + [2885, 3715], +] + +[[area.polygons]] +vertices = [ + [2886, 3583], + [2886, 3715], +] + +[[area.polygons]] +vertices = [ + [2887, 3584], + [2943, 3584], + [2943, 3715], + [2887, 3715], +] + +[[area.polygons]] +vertices = [ + [2804, 3648], + [2827, 3648], +] + +[[area.polygons]] +vertices = [ + [2806, 3649], + [2827, 3649], +] + +[[area.polygons]] +vertices = [ + [2808, 3650], + [2827, 3650], +] + +[[area.polygons]] +vertices = [ + [2810, 3651], + [2827, 3651], +] + +[[area.polygons]] +vertices = [ + [2812, 3652], + [2827, 3652], +] + +[[area.polygons]] +vertices = [ + [2814, 3653], + [2827, 3653], +] + +[[area.polygons]] +vertices = [ + [2815, 3654], + [2827, 3654], +] + +[[area.polygons]] +vertices = [ + [2816, 3655], + [2827, 3655], +] + +[[area.polygons]] +vertices = [ + [2817, 3656], + [2827, 3656], +] + +[[area.polygons]] +vertices = [ + [2818, 3657], + [2827, 3657], +] + +[[area.polygons]] +vertices = [ + [2819, 3658], + [2827, 3658], +] + +[[area.polygons]] +vertices = [ + [2820, 3659], + [2827, 3659], +] + +[[area.polygons]] +vertices = [ + [2821, 3660], + [2827, 3660], +] + +[[area.polygons]] +vertices = [ + [2822, 3661], + [2827, 3661], +] + +[[area.polygons]] +vertices = [ + [2823, 3662], + [2827, 3662], +] + +[[area.polygons]] +vertices = [ + [2824, 3663], + [2827, 3663], + [2827, 3746], + [2824, 3746], +] + +[[area.polygons]] +vertices = [ + [2823, 3673], + [2823, 3746], +] + +[[area.polygons]] +vertices = [ + [2944, 3673], + [2944, 3682], +] + +[[area.polygons]] +vertices = [ + [2945, 3674], + [2945, 3681], +] + +[[area.polygons]] +vertices = [ + [2822, 3675], + [2822, 3746], +] + +[[area.polygons]] +vertices = [ + [2946, 3675], + [2946, 3680], +] + +[[area.polygons]] +vertices = [ + [2947, 3676], + [2947, 3679], +] + +[[area.polygons]] +vertices = [ + [2821, 3677], + [2821, 3746], +] + +[[area.polygons]] +vertices = [ + [2820, 3679], + [2820, 3746], +] + +[[area.polygons]] +vertices = [ + [2819, 3689], + [2819, 3746], +] + +[[area.polygons]] +vertices = [ + [2818, 3691], + [2818, 3746], +] + +[[area.polygons]] +vertices = [ + [2817, 3693], + [2817, 3746], +] + +[[area.polygons]] +vertices = [ + [2816, 3695], + [2816, 3746], +] + +[[area.polygons]] +vertices = [ + [2837, 3716], + [2837, 3746], +] diff --git a/.data/raw-cache/map/area/troll_shuffle.toml b/.data/raw-cache/map/area/troll_shuffle.toml new file mode 100644 index 000000000..b210e19d3 --- /dev/null +++ b/.data/raw-cache/map/area/troll_shuffle.toml @@ -0,0 +1,16 @@ +# Troll Shuffle music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Troll Shuffle. +# Bounds x 2688-2751, z 5760-5823; 4096 tiles as 1 rectangles. + +[[area]] +name = "Troll Shuffle" +area_id = "area.troll_shuffle" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 5760], + [2751, 5760], + [2751, 5823], + [2688, 5823], +] diff --git a/.data/raw-cache/map/area/trouble_brewing.toml b/.data/raw-cache/map/area/trouble_brewing.toml new file mode 100644 index 000000000..426f7db58 --- /dev/null +++ b/.data/raw-cache/map/area/trouble_brewing.toml @@ -0,0 +1,16 @@ +# Trouble Brewing music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Trouble Brewing. +# Bounds x 3776-3839, z 2944-3007; 4096 tiles as 1 rectangles. + +[[area]] +name = "Trouble Brewing" +area_id = "area.trouble_brewing" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3776, 2944], + [3839, 2944], + [3839, 3007], + [3776, 3007], +] diff --git a/.data/raw-cache/map/area/troubled_waters.toml b/.data/raw-cache/map/area/troubled_waters.toml new file mode 100644 index 000000000..d454b59c4 --- /dev/null +++ b/.data/raw-cache/map/area/troubled_waters.toml @@ -0,0 +1,16 @@ +# Troubled Waters music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Troubled Waters. +# Bounds x 2240-2303, z 9984-10047; 4096 tiles as 1 rectangles. + +[[area]] +name = "Troubled Waters" +area_id = "area.troubled_waters" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2240, 9984], + [2303, 9984], + [2303, 10047], + [2240, 10047], +] diff --git a/.data/raw-cache/map/area/twilight.toml b/.data/raw-cache/map/area/twilight.toml new file mode 100644 index 000000000..5200b46d9 --- /dev/null +++ b/.data/raw-cache/map/area/twilight.toml @@ -0,0 +1,16 @@ +# Twilight music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Twilight. +# Bounds x 2688-2751, z 9856-9919; 4096 tiles as 1 rectangles. + +[[area]] +name = "Twilight" +area_id = "area.twilight" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2688, 9856], + [2751, 9856], + [2751, 9919], + [2688, 9919], +] diff --git a/.data/raw-cache/map/area/tzhaar.toml b/.data/raw-cache/map/area/tzhaar.toml new file mode 100644 index 000000000..7099fab0e --- /dev/null +++ b/.data/raw-cache/map/area/tzhaar.toml @@ -0,0 +1,16 @@ +# TzHaar! music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: TzHaar!. +# Bounds x 2368-2431, z 5056-5119; 4096 tiles as 1 rectangles. + +[[area]] +name = "TzHaar!" +area_id = "area.tzhaar" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 5056], + [2431, 5056], + [2431, 5119], + [2368, 5119], +] diff --git a/.data/raw-cache/map/area/undead_dungeon.toml b/.data/raw-cache/map/area/undead_dungeon.toml new file mode 100644 index 000000000..868efeec3 --- /dev/null +++ b/.data/raw-cache/map/area/undead_dungeon.toml @@ -0,0 +1,16 @@ +# Undead Dungeon music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Undead Dungeon. +# Bounds x 3136-3199, z 4544-4671; 8192 tiles as 1 rectangles. + +[[area]] +name = "Undead Dungeon" +area_id = "area.undead_dungeon" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 4544], + [3199, 4544], + [3199, 4671], + [3136, 4671], +] diff --git a/.data/raw-cache/map/area/under_the_mountain.toml b/.data/raw-cache/map/area/under_the_mountain.toml new file mode 100644 index 000000000..c8349f458 --- /dev/null +++ b/.data/raw-cache/map/area/under_the_mountain.toml @@ -0,0 +1,122 @@ +# Under the Mountain music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Under the Mountain. +# Bounds x 1392-1535, z 9496-9600; 13216 tiles as 18 rectangles. + +[[area]] +name = "Under the Mountain" +area_id = "area.under_the_mountain" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1392, 9496], + [1535, 9496], + [1535, 9559], + [1392, 9559], +] + +[[area.polygons]] +vertices = [ + [1392, 9560], + [1495, 9560], + [1495, 9576], + [1392, 9576], +] + +[[area.polygons]] +vertices = [ + [1393, 9577], + [1495, 9577], +] + +[[area.polygons]] +vertices = [ + [1394, 9578], + [1495, 9578], +] + +[[area.polygons]] +vertices = [ + [1395, 9579], + [1495, 9579], +] + +[[area.polygons]] +vertices = [ + [1396, 9580], + [1495, 9580], +] + +[[area.polygons]] +vertices = [ + [1397, 9581], + [1495, 9581], +] + +[[area.polygons]] +vertices = [ + [1398, 9582], + [1495, 9582], +] + +[[area.polygons]] +vertices = [ + [1399, 9583], + [1495, 9583], +] + +[[area.polygons]] +vertices = [ + [1400, 9584], + [1495, 9584], +] + +[[area.polygons]] +vertices = [ + [1401, 9585], + [1495, 9585], +] + +[[area.polygons]] +vertices = [ + [1402, 9586], + [1495, 9586], +] + +[[area.polygons]] +vertices = [ + [1403, 9587], + [1495, 9587], +] + +[[area.polygons]] +vertices = [ + [1404, 9588], + [1495, 9588], +] + +[[area.polygons]] +vertices = [ + [1405, 9589], + [1495, 9589], +] + +[[area.polygons]] +vertices = [ + [1406, 9590], + [1495, 9590], +] + +[[area.polygons]] +vertices = [ + [1407, 9591], + [1495, 9591], +] + +[[area.polygons]] +vertices = [ + [1408, 9592], + [1495, 9592], + [1495, 9600], + [1408, 9600], +] diff --git a/.data/raw-cache/map/area/underground.toml b/.data/raw-cache/map/area/underground.toml new file mode 100644 index 000000000..605305798 --- /dev/null +++ b/.data/raw-cache/map/area/underground.toml @@ -0,0 +1,16 @@ +# Underground music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Underground. +# Bounds x 1920-2111, z 9152-9215; 12288 tiles as 1 rectangles. + +[[area]] +name = "Underground" +area_id = "area.underground" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1920, 9152], + [2111, 9152], + [2111, 9215], + [1920, 9215], +] diff --git a/.data/raw-cache/map/area/underground_pass.toml b/.data/raw-cache/map/area/underground_pass.toml new file mode 100644 index 000000000..94a1541d2 --- /dev/null +++ b/.data/raw-cache/map/area/underground_pass.toml @@ -0,0 +1,16 @@ +# Underground Pass music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Underground Pass. +# Bounds x 2368-2431, z 9536-9663; 8192 tiles as 1 rectangles. + +[[area]] +name = "Underground Pass" +area_id = "area.underground_pass" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 9536], + [2431, 9536], + [2431, 9663], + [2368, 9663], +] diff --git a/.data/raw-cache/map/area/understanding.toml b/.data/raw-cache/map/area/understanding.toml new file mode 100644 index 000000000..4c10dc959 --- /dev/null +++ b/.data/raw-cache/map/area/understanding.toml @@ -0,0 +1,16 @@ +# Understanding music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Understanding. +# Bounds x 2368-2431, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "Understanding" +area_id = "area.understanding" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 4800], + [2431, 4800], + [2431, 4863], + [2368, 4863], +] diff --git a/.data/raw-cache/map/area/untouchable.toml b/.data/raw-cache/map/area/untouchable.toml new file mode 100644 index 000000000..90f928061 --- /dev/null +++ b/.data/raw-cache/map/area/untouchable.toml @@ -0,0 +1,16 @@ +# Untouchable music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Untouchable. +# Bounds x 1792-1855, z 5120-5183; 4096 tiles as 1 rectangles. + +[[area]] +name = "Untouchable" +area_id = "area.untouchable" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1792, 5120], + [1855, 5120], + [1855, 5183], + [1792, 5183], +] diff --git a/.data/raw-cache/map/area/unturned_stones.toml b/.data/raw-cache/map/area/unturned_stones.toml new file mode 100644 index 000000000..d1971e9f2 --- /dev/null +++ b/.data/raw-cache/map/area/unturned_stones.toml @@ -0,0 +1,16 @@ +# Unturned Stones music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Unturned Stones. +# Bounds x 2752-2815, z 6400-6463; 4096 tiles as 1 rectangles. + +[[area]] +name = "Unturned Stones" +area_id = "area.unturned_stones" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2752, 6400], + [2815, 6400], + [2815, 6463], + [2752, 6463], +] diff --git a/.data/raw-cache/map/area/upcoming.toml b/.data/raw-cache/map/area/upcoming.toml new file mode 100644 index 000000000..8bdb0a0da --- /dev/null +++ b/.data/raw-cache/map/area/upcoming.toml @@ -0,0 +1,16 @@ +# Upcoming music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Upcoming. +# Bounds x 1824-1855, z 4896-4927; 1024 tiles as 1 rectangles. + +[[area]] +name = "Upcoming" +area_id = "area.upcoming" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1824, 4896], + [1855, 4896], + [1855, 4927], + [1824, 4927], +] diff --git a/.data/raw-cache/map/area/upir_likhyi.toml b/.data/raw-cache/map/area/upir_likhyi.toml new file mode 100644 index 000000000..173983896 --- /dev/null +++ b/.data/raw-cache/map/area/upir_likhyi.toml @@ -0,0 +1,42 @@ +# Upir Likhyi music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Upir Likhyi. +# Bounds x 3561-3578, z 3349-3367; 290 tiles as 5 rectangles. + +[[area]] +name = "Upir Likhyi" +area_id = "area.upir_likhyi" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3561, 3349], + [3570, 3349], + [3570, 3367], + [3561, 3367], +] + +[[area.polygons]] +vertices = [ + [3571, 3350], + [3571, 3366], +] + +[[area.polygons]] +vertices = [ + [3572, 3351], + [3572, 3365], +] + +[[area.polygons]] +vertices = [ + [3573, 3352], + [3573, 3364], +] + +[[area.polygons]] +vertices = [ + [3574, 3353], + [3578, 3353], + [3578, 3363], + [3574, 3363], +] diff --git a/.data/raw-cache/map/area/upper_depths.toml b/.data/raw-cache/map/area/upper_depths.toml new file mode 100644 index 000000000..6b6ca3790 --- /dev/null +++ b/.data/raw-cache/map/area/upper_depths.toml @@ -0,0 +1,16 @@ +# Upper Depths music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Upper Depths. +# Bounds x 3264-3359, z 5120-5471; 33792 tiles as 1 rectangles. + +[[area]] +name = "Upper Depths" +area_id = "area.upper_depths" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3264, 5120], + [3359, 5120], + [3359, 5471], + [3264, 5471], +] diff --git a/.data/raw-cache/map/area/varlamores_sunset.toml b/.data/raw-cache/map/area/varlamores_sunset.toml new file mode 100644 index 000000000..f74d1709e --- /dev/null +++ b/.data/raw-cache/map/area/varlamores_sunset.toml @@ -0,0 +1,268 @@ +# Varlamore's Sunset music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Varlamore's Sunset. +# Bounds x 1489-1544, z 2966-3005; 1288 tiles as 42 rectangles. + +[[area]] +name = "Varlamore's Sunset" +area_id = "area.varlamores_sunset" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1511, 2966], + [1519, 2966], + [1519, 2968], + [1511, 2968], +] + +[[area.polygons]] +vertices = [[1520, 2967]] + +[[area.polygons]] +vertices = [ + [1514, 2969], + [1514, 3005], +] + +[[area.polygons]] +vertices = [ + [1513, 2971], + [1513, 3005], +] + +[[area.polygons]] +vertices = [ + [1515, 2971], + [1515, 3005], +] + +[[area.polygons]] +vertices = [ + [1535, 2972], + [1535, 3000], +] + +[[area.polygons]] +vertices = [ + [1537, 2972], + [1537, 2998], +] + +[[area.polygons]] +vertices = [ + [1534, 2973], + [1534, 3001], +] + +[[area.polygons]] +vertices = [ + [1536, 2973], + [1536, 2999], +] + +[[area.polygons]] +vertices = [ + [1538, 2973], + [1538, 2997], +] + +[[area.polygons]] +vertices = [ + [1518, 2974], + [1523, 2974], + [1523, 3005], + [1518, 3005], +] + +[[area.polygons]] +vertices = [ + [1533, 2974], + [1533, 3002], +] + +[[area.polygons]] +vertices = [ + [1539, 2974], + [1539, 2996], +] + +[[area.polygons]] +vertices = [ + [1524, 2975], + [1524, 3005], +] + +[[area.polygons]] +vertices = [ + [1528, 2975], + [1532, 2975], + [1532, 3003], + [1528, 3003], +] + +[[area.polygons]] +vertices = [ + [1540, 2975], + [1540, 2995], +] + +[[area.polygons]] +vertices = [ + [1516, 2976], + [1517, 2976], + [1517, 3005], + [1516, 3005], +] + +[[area.polygons]] +vertices = [ + [1525, 2976], + [1527, 2976], + [1527, 3005], + [1525, 3005], +] + +[[area.polygons]] +vertices = [ + [1541, 2976], + [1541, 2994], +] + +[[area.polygons]] +vertices = [ + [1542, 2977], + [1542, 2993], +] + +[[area.polygons]] +vertices = [ + [1512, 2978], + [1512, 3005], +] + +[[area.polygons]] +vertices = [ + [1543, 2978], + [1543, 2993], +] + +[[area.polygons]] +vertices = [ + [1510, 2979], + [1511, 2979], + [1511, 3003], + [1510, 3003], +] + +[[area.polygons]] +vertices = [ + [1544, 2979], + [1544, 2992], +] + +[[area.polygons]] +vertices = [ + [1502, 2980], + [1509, 2980], + [1509, 2998], + [1502, 2998], +] + +[[area.polygons]] +vertices = [ + [1501, 2981], + [1501, 2997], +] + +[[area.polygons]] +vertices = [ + [1489, 2982], + [1490, 2982], + [1490, 2991], + [1489, 2991], +] + +[[area.polygons]] +vertices = [ + [1491, 2983], + [1491, 2991], +] + +[[area.polygons]] +vertices = [ + [1494, 2984], + [1500, 2984], + [1500, 2986], + [1494, 2986], +] + +[[area.polygons]] +vertices = [ + [1492, 2985], + [1493, 2985], +] + +[[area.polygons]] +vertices = [ + [1499, 2987], + [1500, 2987], + [1500, 2996], + [1499, 2996], +] + +[[area.polygons]] +vertices = [ + [1498, 2988], + [1498, 2995], +] + +[[area.polygons]] +vertices = [ + [1497, 2989], + [1497, 2992], +] + +[[area.polygons]] +vertices = [[1500, 2997]] + +[[area.polygons]] +vertices = [ + [1503, 2999], + [1509, 2999], +] + +[[area.polygons]] +vertices = [ + [1505, 3000], + [1509, 3000], +] + +[[area.polygons]] +vertices = [ + [1506, 3001], + [1509, 3001], +] + +[[area.polygons]] +vertices = [ + [1508, 3002], + [1509, 3002], +] + +[[area.polygons]] +vertices = [[1509, 3003]] + +[[area.polygons]] +vertices = [[1511, 3004]] + +[[area.polygons]] +vertices = [ + [1528, 3004], + [1531, 3004], +] + +[[area.polygons]] +vertices = [ + [1528, 3005], + [1530, 3005], +] diff --git a/.data/raw-cache/map/area/varrock.toml b/.data/raw-cache/map/area/varrock.toml new file mode 100644 index 000000000..566eaaf1d --- /dev/null +++ b/.data/raw-cache/map/area/varrock.toml @@ -0,0 +1,618 @@ +# The city minus the Grand Exchange rectangle (x 3143-3187, z 3462-3510), which is its own music +# area (see grand_exchange.toml). Four pieces cover everything around it. +# South of the exchange: the whole city below z 3462. +# West strip beside the exchange. +# Everything east of the exchange, up to the north edge. +# The sliver north of the exchange walls. +# +# Regenerated as rectangles from the OSRS wiki music map: 741 tiles moved to +# neighbouring music areas and 10571 tiles were added. + +[[area]] +name = "Varrock" +area_id = "area.varrock" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3267, 3329], + [3270, 3329], + [3270, 3522], + [3267, 3522], +] + +[[area.polygons]] +vertices = [ + [3307, 3329], + [3311, 3329], + [3311, 3358], + [3307, 3358], +] + +[[area.polygons]] +vertices = [ + [3253, 3330], + [3266, 3330], + [3266, 3522], + [3253, 3522], +] + +[[area.polygons]] +vertices = [ + [3271, 3330], + [3299, 3330], + [3299, 3383], + [3271, 3383], +] + +[[area.polygons]] +vertices = [ + [3304, 3330], + [3306, 3330], + [3306, 3359], + [3304, 3359], +] + +[[area.polygons]] +vertices = [ + [3227, 3331], + [3233, 3331], + [3233, 3522], + [3227, 3522], +] + +[[area.polygons]] +vertices = [ + [3251, 3331], + [3252, 3331], + [3252, 3522], + [3251, 3522], +] + +[[area.polygons]] +vertices = [ + [3300, 3331], + [3303, 3331], + [3303, 3382], + [3300, 3382], +] + +[[area.polygons]] +vertices = [ + [3226, 3332], + [3226, 3522], +] + +[[area.polygons]] +vertices = [ + [3234, 3332], + [3234, 3522], +] + +[[area.polygons]] +vertices = [ + [3250, 3332], + [3250, 3522], +] + +[[area.polygons]] +vertices = [ + [3209, 3333], + [3209, 3522], +] + +[[area.polygons]] +vertices = [ + [3225, 3333], + [3225, 3522], +] + +[[area.polygons]] +vertices = [ + [3235, 3333], + [3235, 3522], +] + +[[area.polygons]] +vertices = [ + [3208, 3334], + [3208, 3522], +] + +[[area.polygons]] +vertices = [ + [3210, 3334], + [3210, 3522], +] + +[[area.polygons]] +vertices = [ + [3216, 3334], + [3224, 3334], + [3224, 3522], + [3216, 3522], +] + +[[area.polygons]] +vertices = [ + [3236, 3334], + [3236, 3522], +] + +[[area.polygons]] +vertices = [ + [3249, 3334], + [3249, 3522], +] + +[[area.polygons]] +vertices = [ + [3206, 3335], + [3207, 3335], + [3207, 3522], + [3206, 3522], +] + +[[area.polygons]] +vertices = [ + [3211, 3335], + [3211, 3522], +] + +[[area.polygons]] +vertices = [ + [3215, 3335], + [3215, 3522], +] + +[[area.polygons]] +vertices = [ + [3237, 3335], + [3248, 3335], + [3248, 3522], + [3237, 3522], +] + +[[area.polygons]] +vertices = [ + [3204, 3336], + [3205, 3336], + [3205, 3522], + [3204, 3522], +] + +[[area.polygons]] +vertices = [ + [3212, 3336], + [3214, 3336], + [3214, 3522], + [3212, 3522], +] + +[[area.polygons]] +vertices = [ + [3202, 3337], + [3203, 3337], + [3203, 3522], + [3202, 3522], +] + +[[area.polygons]] +vertices = [ + [3200, 3338], + [3201, 3338], + [3201, 3522], + [3200, 3522], +] + +[[area.polygons]] +vertices = [ + [3198, 3339], + [3199, 3339], + [3199, 3522], + [3198, 3522], +] + +[[area.polygons]] +vertices = [ + [3196, 3340], + [3197, 3340], + [3197, 3522], + [3196, 3522], +] + +[[area.polygons]] +vertices = [ + [3195, 3341], + [3195, 3522], +] + +[[area.polygons]] +vertices = [ + [3193, 3342], + [3194, 3342], + [3194, 3522], + [3193, 3522], +] + +[[area.polygons]] +vertices = [ + [3191, 3343], + [3192, 3343], + [3192, 3522], + [3191, 3522], +] + +[[area.polygons]] +vertices = [ + [3189, 3344], + [3190, 3344], + [3190, 3522], + [3189, 3522], +] + +[[area.polygons]] +vertices = [ + [3187, 3345], + [3188, 3345], + [3188, 3461], + [3187, 3461], +] + +[[area.polygons]] +vertices = [ + [3185, 3346], + [3186, 3346], + [3186, 3461], + [3185, 3461], +] + +[[area.polygons]] +vertices = [ + [3183, 3347], + [3184, 3347], + [3184, 3461], + [3183, 3461], +] + +[[area.polygons]] +vertices = [ + [3161, 3348], + [3182, 3348], + [3182, 3461], + [3161, 3461], +] + +[[area.polygons]] +vertices = [ + [3160, 3349], + [3160, 3461], +] + +[[area.polygons]] +vertices = [ + [3159, 3350], + [3159, 3461], +] + +[[area.polygons]] +vertices = [ + [3157, 3351], + [3158, 3351], + [3158, 3461], + [3157, 3461], +] + +[[area.polygons]] +vertices = [ + [3156, 3352], + [3156, 3461], +] + +[[area.polygons]] +vertices = [ + [3155, 3353], + [3155, 3461], +] + +[[area.polygons]] +vertices = [ + [3312, 3353], + [3312, 3354], +] + +[[area.polygons]] +vertices = [ + [3154, 3354], + [3154, 3461], +] + +[[area.polygons]] +vertices = [ + [3153, 3355], + [3153, 3461], +] + +[[area.polygons]] +vertices = [ + [3152, 3356], + [3152, 3461], +] + +[[area.polygons]] +vertices = [ + [3151, 3357], + [3151, 3461], +] + +[[area.polygons]] +vertices = [ + [3307, 3359], + [3310, 3359], +] + +[[area.polygons]] +vertices = [ + [3304, 3363], + [3304, 3364], +] + +[[area.polygons]] +vertices = [ + [3135, 3372], + [3150, 3372], + [3150, 3461], + [3135, 3461], +] + +[[area.polygons]] +vertices = [ + [3300, 3383], + [3302, 3383], +] + +[[area.polygons]] +vertices = [ + [3271, 3384], + [3295, 3384], + [3295, 3522], + [3271, 3522], +] + +[[area.polygons]] +vertices = [ + [3121, 3388], + [3134, 3388], + [3134, 3463], + [3121, 3463], +] + +[[area.polygons]] +vertices = [ + [3120, 3389], + [3120, 3462], +] + +[[area.polygons]] +vertices = [ + [3119, 3390], + [3119, 3461], +] + +[[area.polygons]] +vertices = [ + [3118, 3391], + [3118, 3394], +] + +[[area.polygons]] +vertices = [ + [3117, 3392], + [3117, 3394], +] + +[[area.polygons]] +vertices = [ + [3116, 3393], + [3116, 3394], +] + +[[area.polygons]] +vertices = [[3115, 3394]] + +[[area.polygons]] +vertices = [ + [3296, 3438], + [3296, 3439], +] + +[[area.polygons]] +vertices = [ + [3296, 3449], + [3296, 3450], +] + +[[area.polygons]] +vertices = [ + [3103, 3451], + [3118, 3451], + [3118, 3461], + [3103, 3461], +] + +[[area.polygons]] +vertices = [ + [3296, 3456], + [3311, 3456], + [3311, 3482], + [3296, 3482], +] + +[[area.polygons]] +vertices = [ + [3312, 3459], + [3312, 3472], +] + +[[area.polygons]] +vertices = [ + [3103, 3462], + [3107, 3462], +] + +[[area.polygons]] +vertices = [ + [3135, 3462], + [3142, 3462], + [3142, 3515], + [3135, 3515], +] + +[[area.polygons]] +vertices = [ + [3188, 3462], + [3188, 3515], +] + +[[area.polygons]] +vertices = [ + [3313, 3462], + [3313, 3472], +] + +[[area.polygons]] +vertices = [ + [3103, 3463], + [3106, 3463], +] + +[[area.polygons]] +vertices = [ + [3314, 3463], + [3314, 3471], +] + +[[area.polygons]] +vertices = [ + [3122, 3464], + [3134, 3464], +] + +[[area.polygons]] +vertices = [ + [3315, 3464], + [3317, 3464], + [3317, 3471], + [3315, 3471], +] + +[[area.polygons]] +vertices = [ + [3132, 3465], + [3134, 3465], +] + +[[area.polygons]] +vertices = [ + [3318, 3465], + [3318, 3470], +] + +[[area.polygons]] +vertices = [ + [3133, 3466], + [3134, 3466], +] + +[[area.polygons]] +vertices = [ + [3319, 3466], + [3319, 3468], +] + +[[area.polygons]] +vertices = [[3134, 3467]] + +[[area.polygons]] +vertices = [ + [3312, 3478], + [3312, 3479], +] + +[[area.polygons]] +vertices = [ + [3296, 3483], + [3310, 3483], + [3310, 3522], + [3296, 3522], +] + +[[area.polygons]] +vertices = [ + [3316, 3487], + [3319, 3487], + [3319, 3522], + [3316, 3522], +] + +[[area.polygons]] +vertices = [ + [3311, 3488], + [3315, 3488], + [3315, 3522], + [3311, 3522], +] + +[[area.polygons]] +vertices = [ + [3320, 3488], + [3326, 3488], + [3326, 3522], + [3320, 3522], +] + +[[area.polygons]] +vertices = [ + [3327, 3489], + [3327, 3522], +] + +[[area.polygons]] +vertices = [ + [3143, 3511], + [3187, 3511], + [3187, 3515], + [3143, 3515], +] + +[[area.polygons]] +vertices = [ + [3158, 3516], + [3169, 3516], + [3169, 3522], + [3158, 3522], +] + +[[area.polygons]] +vertices = [ + [3143, 3517], + [3157, 3517], + [3157, 3522], + [3143, 3522], +] + +[[area.polygons]] +vertices = [ + [3170, 3517], + [3188, 3517], + [3188, 3522], + [3170, 3522], +] + +[[area.polygons]] +vertices = [ + [3228, 9760], + [3255, 9760], + [3255, 9809], + [3228, 9809], +] diff --git a/.data/raw-cache/map/area/venture_2.toml b/.data/raw-cache/map/area/venture_2.toml new file mode 100644 index 000000000..14b95ced2 --- /dev/null +++ b/.data/raw-cache/map/area/venture_2.toml @@ -0,0 +1,16 @@ +# Venture 2 music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Venture 2. +# Bounds x 3328-3391, z 9728-9855; 8192 tiles as 1 rectangles. + +[[area]] +name = "Venture 2" +area_id = "area.venture_2" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3328, 9728], + [3391, 9728], + [3391, 9855], + [3328, 9855], +] diff --git a/.data/raw-cache/map/area/victory_is_mine.toml b/.data/raw-cache/map/area/victory_is_mine.toml new file mode 100644 index 000000000..333ee9fd7 --- /dev/null +++ b/.data/raw-cache/map/area/victory_is_mine.toml @@ -0,0 +1,89 @@ +# Victory is Mine music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Victory is Mine. +# Bounds x 3158-3181, z 9752-9764; 234 tiles as 12 rectangles. + +[[area]] +name = "Victory is Mine" +area_id = "area.victory_is_mine" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3166, 9752], + [3170, 9752], + [3170, 9764], + [3166, 9764], +] + +[[area.polygons]] +vertices = [ + [3162, 9753], + [3165, 9753], + [3165, 9763], + [3162, 9763], +] + +[[area.polygons]] +vertices = [ + [3171, 9753], + [3173, 9753], + [3173, 9763], + [3171, 9763], +] + +[[area.polygons]] +vertices = [ + [3178, 9753], + [3179, 9753], + [3179, 9763], + [3178, 9763], +] + +[[area.polygons]] +vertices = [ + [3161, 9754], + [3161, 9762], +] + +[[area.polygons]] +vertices = [ + [3174, 9754], + [3174, 9762], +] + +[[area.polygons]] +vertices = [ + [3177, 9754], + [3177, 9762], +] + +[[area.polygons]] +vertices = [ + [3180, 9754], + [3180, 9762], +] + +[[area.polygons]] +vertices = [ + [3159, 9755], + [3160, 9755], + [3160, 9761], + [3159, 9761], +] + +[[area.polygons]] +vertices = [ + [3175, 9755], + [3176, 9755], + [3176, 9761], + [3175, 9761], +] + +[[area.polygons]] +vertices = [ + [3158, 9756], + [3158, 9760], +] + +[[area.polygons]] +vertices = [[3181, 9758]] diff --git a/.data/raw-cache/map/area/village.toml b/.data/raw-cache/map/area/village.toml new file mode 100644 index 000000000..5ea12b8be --- /dev/null +++ b/.data/raw-cache/map/area/village.toml @@ -0,0 +1,251 @@ +# Village music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Village. +# Bounds x 3464-3518, z 3462-3512; 2091 tiles as 40 rectangles. + +[[area]] +name = "Village" +area_id = "area.village" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3485, 3462], + [3502, 3462], + [3502, 3508], + [3485, 3508], +] + +[[area.polygons]] +vertices = [ + [3484, 3463], + [3484, 3508], +] + +[[area.polygons]] +vertices = [ + [3503, 3463], + [3503, 3512], +] + +[[area.polygons]] +vertices = [ + [3483, 3464], + [3483, 3508], +] + +[[area.polygons]] +vertices = [ + [3504, 3464], + [3504, 3512], +] + +[[area.polygons]] +vertices = [ + [3482, 3465], + [3482, 3508], +] + +[[area.polygons]] +vertices = [ + [3505, 3465], + [3507, 3465], + [3507, 3512], + [3505, 3512], +] + +[[area.polygons]] +vertices = [ + [3481, 3466], + [3481, 3508], +] + +[[area.polygons]] +vertices = [ + [3508, 3466], + [3510, 3466], + [3510, 3510], + [3508, 3510], +] + +[[area.polygons]] +vertices = [ + [3480, 3467], + [3480, 3508], +] + +[[area.polygons]] +vertices = [ + [3511, 3467], + [3511, 3509], +] + +[[area.polygons]] +vertices = [ + [3479, 3468], + [3479, 3508], +] + +[[area.polygons]] +vertices = [ + [3512, 3468], + [3512, 3508], +] + +[[area.polygons]] +vertices = [ + [3478, 3469], + [3478, 3508], +] + +[[area.polygons]] +vertices = [ + [3513, 3469], + [3513, 3507], +] + +[[area.polygons]] +vertices = [ + [3477, 3470], + [3477, 3508], +] + +[[area.polygons]] +vertices = [ + [3514, 3470], + [3514, 3487], +] + +[[area.polygons]] +vertices = [ + [3476, 3471], + [3476, 3507], +] + +[[area.polygons]] +vertices = [ + [3515, 3471], + [3515, 3486], +] + +[[area.polygons]] +vertices = [ + [3516, 3472], + [3516, 3485], +] + +[[area.polygons]] +vertices = [ + [3517, 3473], + [3517, 3484], +] + +[[area.polygons]] +vertices = [ + [3475, 3474], + [3475, 3506], +] + +[[area.polygons]] +vertices = [ + [3518, 3474], + [3518, 3483], +] + +[[area.polygons]] +vertices = [ + [3474, 3475], + [3474, 3505], +] + +[[area.polygons]] +vertices = [ + [3473, 3476], + [3473, 3504], +] + +[[area.polygons]] +vertices = [ + [3472, 3477], + [3472, 3503], +] + +[[area.polygons]] +vertices = [ + [3471, 3479], + [3471, 3502], +] + +[[area.polygons]] +vertices = [ + [3470, 3480], + [3470, 3501], +] + +[[area.polygons]] +vertices = [ + [3469, 3481], + [3469, 3500], +] + +[[area.polygons]] +vertices = [ + [3468, 3482], + [3468, 3499], +] + +[[area.polygons]] +vertices = [ + [3467, 3483], + [3467, 3498], +] + +[[area.polygons]] +vertices = [ + [3466, 3484], + [3466, 3497], +] + +[[area.polygons]] +vertices = [ + [3465, 3485], + [3465, 3496], +] + +[[area.polygons]] +vertices = [ + [3464, 3486], + [3464, 3495], +] + +[[area.polygons]] +vertices = [ + [3492, 3509], + [3502, 3509], +] + +[[area.polygons]] +vertices = [ + [3493, 3510], + [3502, 3510], +] + +[[area.polygons]] +vertices = [ + [3494, 3511], + [3502, 3511], +] + +[[area.polygons]] +vertices = [ + [3508, 3511], + [3509, 3511], +] + +[[area.polygons]] +vertices = [ + [3495, 3512], + [3502, 3512], +] + +[[area.polygons]] +vertices = [[3508, 3512]] diff --git a/.data/raw-cache/map/area/volcanic_vikings.toml b/.data/raw-cache/map/area/volcanic_vikings.toml new file mode 100644 index 000000000..ef56fc098 --- /dev/null +++ b/.data/raw-cache/map/area/volcanic_vikings.toml @@ -0,0 +1,347 @@ +# Volcanic Vikings music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Volcanic Vikings. +# Bounds x 2305-2366, z 3772-3825; 1743 tiles as 56 rectangles. + +[[area]] +name = "Volcanic Vikings" +area_id = "area.volcanic_vikings" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2305, 3772], + [2311, 3772], + [2311, 3775], + [2305, 3775], +] + +[[area.polygons]] +vertices = [ + [2310, 3776], + [2311, 3776], + [2311, 3808], + [2310, 3808], +] + +[[area.polygons]] +vertices = [ + [2324, 3780], + [2324, 3817], +] + +[[area.polygons]] +vertices = [ + [2322, 3781], + [2323, 3781], + [2323, 3818], + [2322, 3818], +] + +[[area.polygons]] +vertices = [ + [2325, 3781], + [2325, 3817], +] + +[[area.polygons]] +vertices = [ + [2321, 3783], + [2321, 3820], +] + +[[area.polygons]] +vertices = [ + [2312, 3784], + [2320, 3784], + [2320, 3818], + [2312, 3818], +] + +[[area.polygons]] +vertices = [ + [2308, 3785], + [2309, 3785], + [2309, 3792], + [2308, 3792], +] + +[[area.polygons]] +vertices = [ + [2326, 3785], + [2326, 3817], +] + +[[area.polygons]] +vertices = [ + [2327, 3786], + [2330, 3786], + [2330, 3817], + [2327, 3817], +] + +[[area.polygons]] +vertices = [ + [2353, 3786], + [2355, 3786], + [2355, 3808], + [2353, 3808], +] + +[[area.polygons]] +vertices = [ + [2307, 3787], + [2307, 3790], +] + +[[area.polygons]] +vertices = [ + [2352, 3787], + [2352, 3810], +] + +[[area.polygons]] +vertices = [ + [2356, 3787], + [2356, 3805], +] + +[[area.polygons]] +vertices = [ + [2331, 3788], + [2332, 3788], + [2332, 3818], + [2331, 3818], +] + +[[area.polygons]] +vertices = [ + [2337, 3788], + [2337, 3821], +] + +[[area.polygons]] +vertices = [ + [2349, 3788], + [2351, 3788], + [2351, 3812], + [2349, 3812], +] + +[[area.polygons]] +vertices = [ + [2357, 3788], + [2357, 3805], +] + +[[area.polygons]] +vertices = [ + [2333, 3789], + [2333, 3819], +] + +[[area.polygons]] +vertices = [ + [2336, 3789], + [2336, 3821], +] + +[[area.polygons]] +vertices = [ + [2338, 3789], + [2338, 3821], +] + +[[area.polygons]] +vertices = [ + [2358, 3789], + [2358, 3806], +] + +[[area.polygons]] +vertices = [ + [2334, 3790], + [2335, 3790], + [2335, 3820], + [2334, 3820], +] + +[[area.polygons]] +vertices = [ + [2339, 3790], + [2340, 3790], + [2340, 3821], + [2339, 3821], +] + +[[area.polygons]] +vertices = [ + [2359, 3790], + [2359, 3807], +] + +[[area.polygons]] +vertices = [ + [2341, 3791], + [2341, 3821], +] + +[[area.polygons]] +vertices = [ + [2348, 3791], + [2348, 3819], +] + +[[area.polygons]] +vertices = [ + [2360, 3791], + [2362, 3791], + [2362, 3807], + [2360, 3807], +] + +[[area.polygons]] +vertices = [ + [2342, 3792], + [2343, 3792], + [2343, 3821], + [2342, 3821], +] + +[[area.polygons]] +vertices = [ + [2347, 3792], + [2347, 3820], +] + +[[area.polygons]] +vertices = [ + [2363, 3792], + [2363, 3806], +] + +[[area.polygons]] +vertices = [[2309, 3793]] + +[[area.polygons]] +vertices = [ + [2344, 3793], + [2344, 3821], +] + +[[area.polygons]] +vertices = [ + [2346, 3793], + [2346, 3820], +] + +[[area.polygons]] +vertices = [ + [2345, 3794], + [2345, 3821], +] + +[[area.polygons]] +vertices = [ + [2364, 3795], + [2364, 3804], +] + +[[area.polygons]] +vertices = [ + [2365, 3796], + [2365, 3803], +] + +[[area.polygons]] +vertices = [ + [2309, 3797], + [2309, 3808], +] + +[[area.polygons]] +vertices = [ + [2308, 3798], + [2308, 3806], +] + +[[area.polygons]] +vertices = [ + [2366, 3798], + [2366, 3802], +] + +[[area.polygons]] +vertices = [[2307, 3799]] + +[[area.polygons]] +vertices = [[2307, 3804]] + +[[area.polygons]] +vertices = [[2311, 3809]] + +[[area.polygons]] +vertices = [ + [2353, 3809], + [2354, 3809], +] + +[[area.polygons]] +vertices = [[2353, 3810]] + +[[area.polygons]] +vertices = [ + [2311, 3812], + [2311, 3816], +] + +[[area.polygons]] +vertices = [ + [2349, 3813], + [2350, 3813], +] + +[[area.polygons]] +vertices = [[2330, 3818]] + +[[area.polygons]] +vertices = [ + [2313, 3819], + [2320, 3819], +] + +[[area.polygons]] +vertices = [[2322, 3819]] + +[[area.polygons]] +vertices = [[2332, 3819]] + +[[area.polygons]] +vertices = [ + [2314, 3820], + [2320, 3820], + [2320, 3821], + [2314, 3821], +] + +[[area.polygons]] +vertices = [[2335, 3821]] + +[[area.polygons]] +vertices = [ + [2315, 3822], + [2320, 3822], +] + +[[area.polygons]] +vertices = [ + [2315, 3823], + [2319, 3823], + [2319, 3824], + [2315, 3824], +] + +[[area.polygons]] +vertices = [ + [2315, 3825], + [2318, 3825], +] diff --git a/.data/raw-cache/map/area/voodoo_cult.toml b/.data/raw-cache/map/area/voodoo_cult.toml new file mode 100644 index 000000000..0606596db --- /dev/null +++ b/.data/raw-cache/map/area/voodoo_cult.toml @@ -0,0 +1,24 @@ +# Voodoo Cult music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Voodoo Cult. +# Bounds x 2368-2943, z 4672-9343; 8192 tiles as 2 rectangles. + +[[area]] +name = "Voodoo Cult" +area_id = "area.voodoo_cult" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 4672], + [2431, 4672], + [2431, 4735], + [2368, 4735], +] + +[[area.polygons]] +vertices = [ + [2880, 9280], + [2943, 9280], + [2943, 9343], + [2880, 9343], +] diff --git a/.data/raw-cache/map/area/waking_dream.toml b/.data/raw-cache/map/area/waking_dream.toml new file mode 100644 index 000000000..041343193 --- /dev/null +++ b/.data/raw-cache/map/area/waking_dream.toml @@ -0,0 +1,24 @@ +# Waking Dream music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Waking Dream. +# Bounds x 1744-1855, z 5056-5119; 5120 tiles as 2 rectangles. + +[[area]] +name = "Waking Dream" +area_id = "area.waking_dream" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1792, 5056], + [1855, 5056], + [1855, 5119], + [1792, 5119], +] + +[[area.polygons]] +vertices = [ + [1744, 5072], + [1775, 5072], + [1775, 5103], + [1744, 5103], +] diff --git a/.data/raw-cache/map/area/warrior.toml b/.data/raw-cache/map/area/warrior.toml new file mode 100644 index 000000000..b680dc687 --- /dev/null +++ b/.data/raw-cache/map/area/warrior.toml @@ -0,0 +1,16 @@ +# Warrior music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Warrior. +# Bounds x 2624-2687, z 10048-10111; 4096 tiles as 1 rectangles. + +[[area]] +name = "Warrior" +area_id = "area.warrior" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2624, 10048], + [2687, 10048], + [2687, 10111], + [2624, 10111], +] diff --git a/.data/raw-cache/map/area/warriors_guild.toml b/.data/raw-cache/map/area/warriors_guild.toml new file mode 100644 index 000000000..d49a0b591 --- /dev/null +++ b/.data/raw-cache/map/area/warriors_guild.toml @@ -0,0 +1,158 @@ +# Warriors' Guild music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Warriors' Guild. +# Bounds x 2828-2876, z 3534-3556; 892 tiles as 23 rectangles. + +[[area]] +name = "Warriors' Guild" +area_id = "area.warriors_guild" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2849, 3534], + [2859, 3534], + [2859, 3555], + [2849, 3555], +] + +[[area.polygons]] +vertices = [ + [2839, 3536], + [2840, 3536], + [2840, 3555], + [2839, 3555], +] + +[[area.polygons]] +vertices = [ + [2845, 3536], + [2846, 3536], + [2846, 3555], + [2845, 3555], +] + +[[area.polygons]] +vertices = [ + [2865, 3536], + [2866, 3536], + [2866, 3556], + [2865, 3556], +] + +[[area.polygons]] +vertices = [ + [2871, 3536], + [2872, 3536], + [2872, 3556], + [2871, 3556], +] + +[[area.polygons]] +vertices = [ + [2837, 3537], + [2838, 3537], + [2838, 3555], + [2837, 3555], +] + +[[area.polygons]] +vertices = [ + [2841, 3537], + [2844, 3537], + [2844, 3555], + [2841, 3555], +] + +[[area.polygons]] +vertices = [ + [2847, 3537], + [2848, 3537], + [2848, 3555], + [2847, 3555], +] + +[[area.polygons]] +vertices = [ + [2860, 3537], + [2864, 3537], + [2864, 3555], + [2860, 3555], +] + +[[area.polygons]] +vertices = [ + [2867, 3537], + [2870, 3537], + [2870, 3555], + [2867, 3555], +] + +[[area.polygons]] +vertices = [ + [2873, 3537], + [2875, 3537], + [2875, 3555], + [2873, 3555], +] + +[[area.polygons]] +vertices = [ + [2835, 3538], + [2836, 3538], + [2836, 3553], + [2835, 3553], +] + +[[area.polygons]] +vertices = [ + [2833, 3539], + [2834, 3539], + [2834, 3551], + [2833, 3551], +] + +[[area.polygons]] +vertices = [ + [2831, 3540], + [2832, 3540], + [2832, 3549], + [2831, 3549], +] + +[[area.polygons]] +vertices = [[2876, 3540]] + +[[area.polygons]] +vertices = [ + [2829, 3541], + [2830, 3541], + [2830, 3547], + [2829, 3547], +] + +[[area.polygons]] +vertices = [ + [2828, 3542], + [2828, 3546], +] + +[[area.polygons]] +vertices = [ + [2876, 3545], + [2876, 3547], +] + +[[area.polygons]] +vertices = [[2830, 3548]] + +[[area.polygons]] +vertices = [[2832, 3550]] + +[[area.polygons]] +vertices = [[2834, 3552]] + +[[area.polygons]] +vertices = [[2876, 3552]] + +[[area.polygons]] +vertices = [[2836, 3554]] diff --git a/.data/raw-cache/map/area/waste_defaced.toml b/.data/raw-cache/map/area/waste_defaced.toml new file mode 100644 index 000000000..684a9f362 --- /dev/null +++ b/.data/raw-cache/map/area/waste_defaced.toml @@ -0,0 +1,16 @@ +# Waste Defaced music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Waste Defaced. +# Bounds x 1472-1535, z 4224-4351; 8192 tiles as 1 rectangles. + +[[area]] +name = "Waste Defaced" +area_id = "area.waste_defaced" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1472, 4224], + [1535, 4224], + [1535, 4351], + [1472, 4351], +] diff --git a/.data/raw-cache/map/area/watch_your_step.toml b/.data/raw-cache/map/area/watch_your_step.toml new file mode 100644 index 000000000..5cb546ea6 --- /dev/null +++ b/.data/raw-cache/map/area/watch_your_step.toml @@ -0,0 +1,32 @@ +# Watch Your Step music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Watch Your Step. +# Bounds x 2176-2623, z 5760-6079; 135168 tiles as 3 rectangles. + +[[area]] +name = "Watch Your Step" +area_id = "area.watch_your_step" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2176, 5760], + [2623, 5760], + [2623, 5951], + [2176, 5951], +] + +[[area.polygons]] +vertices = [ + [2176, 5952], + [2367, 5952], + [2367, 6079], + [2176, 6079], +] + +[[area.polygons]] +vertices = [ + [2432, 5952], + [2623, 5952], + [2623, 6079], + [2432, 6079], +] diff --git a/.data/raw-cache/map/area/waterfall.toml b/.data/raw-cache/map/area/waterfall.toml new file mode 100644 index 000000000..8bcb33422 --- /dev/null +++ b/.data/raw-cache/map/area/waterfall.toml @@ -0,0 +1,16 @@ +# Waterfall music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Waterfall. +# Bounds x 2496-2559, z 9792-9855; 4096 tiles as 1 rectangles. + +[[area]] +name = "Waterfall" +area_id = "area.waterfall" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2496, 9792], + [2559, 9792], + [2559, 9855], + [2496, 9855], +] diff --git a/.data/raw-cache/map/area/waterlogged.toml b/.data/raw-cache/map/area/waterlogged.toml new file mode 100644 index 000000000..75eaebac1 --- /dev/null +++ b/.data/raw-cache/map/area/waterlogged.toml @@ -0,0 +1,1026 @@ +# Waterlogged music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Waterlogged. +# Bounds x 1136-2111, z 2717-9215; 10977 tiles as 165 rectangles. + +[[area]] +name = "Waterlogged" +area_id = "area.waterlogged" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1181, 2717], + [1181, 2809], +] + +[[area.polygons]] +vertices = [ + [1175, 2718], + [1180, 2718], + [1180, 2747], + [1175, 2747], +] + +[[area.polygons]] +vertices = [ + [1182, 2718], + [1182, 2726], +] + +[[area.polygons]] +vertices = [ + [1173, 2719], + [1174, 2719], + [1174, 2722], + [1173, 2722], +] + +[[area.polygons]] +vertices = [ + [1172, 2720], + [1172, 2721], +] + +[[area.polygons]] +vertices = [ + [1183, 2722], + [1183, 2726], +] + +[[area.polygons]] +vertices = [ + [1174, 2723], + [1174, 2733], +] + +[[area.polygons]] +vertices = [ + [1173, 2726], + [1173, 2733], +] + +[[area.polygons]] +vertices = [ + [1172, 2727], + [1172, 2733], +] + +[[area.polygons]] +vertices = [ + [1171, 2728], + [1171, 2733], +] + +[[area.polygons]] +vertices = [ + [1182, 2728], + [1182, 2809], +] + +[[area.polygons]] +vertices = [ + [1169, 2729], + [1170, 2729], + [1170, 2731], + [1169, 2731], +] + +[[area.polygons]] +vertices = [ + [1183, 2729], + [1184, 2729], + [1184, 2808], + [1183, 2808], +] + +[[area.polygons]] +vertices = [ + [1185, 2730], + [1185, 2808], +] + +[[area.polygons]] +vertices = [ + [1186, 2731], + [1186, 2808], +] + +[[area.polygons]] +vertices = [ + [1198, 2731], + [1200, 2731], + [1200, 2737], + [1198, 2737], +] + +[[area.polygons]] +vertices = [[1170, 2732]] + +[[area.polygons]] +vertices = [ + [1194, 2732], + [1197, 2732], + [1197, 2739], + [1194, 2739], +] + +[[area.polygons]] +vertices = [ + [1201, 2732], + [1201, 2737], +] + +[[area.polygons]] +vertices = [ + [1187, 2733], + [1187, 2808], +] + +[[area.polygons]] +vertices = [ + [1190, 2733], + [1193, 2733], + [1193, 2809], + [1190, 2809], +] + +[[area.polygons]] +vertices = [ + [1202, 2733], + [1202, 2737], +] + +[[area.polygons]] +vertices = [ + [1188, 2734], + [1189, 2734], + [1189, 2808], + [1188, 2808], +] + +[[area.polygons]] +vertices = [ + [1203, 2734], + [1204, 2734], + [1204, 2737], + [1203, 2737], +] + +[[area.polygons]] +vertices = [ + [1174, 2735], + [1174, 2747], +] + +[[area.polygons]] +vertices = [ + [1205, 2735], + [1205, 2737], +] + +[[area.polygons]] +vertices = [ + [1170, 2736], + [1173, 2736], + [1173, 2737], + [1170, 2737], +] + +[[area.polygons]] +vertices = [ + [1172, 2738], + [1173, 2738], +] + +[[area.polygons]] +vertices = [[1198, 2738]] + +[[area.polygons]] +vertices = [ + [1173, 2739], + [1173, 2746], +] + +[[area.polygons]] +vertices = [ + [1194, 2740], + [1196, 2740], + [1196, 2810], + [1194, 2810], +] + +[[area.polygons]] +vertices = [ + [1172, 2741], + [1172, 2746], +] + +[[area.polygons]] +vertices = [ + [1199, 2741], + [1204, 2741], + [1204, 2746], + [1199, 2746], +] + +[[area.polygons]] +vertices = [ + [1171, 2742], + [1171, 2745], +] + +[[area.polygons]] +vertices = [ + [1197, 2742], + [1198, 2742], + [1198, 2772], + [1197, 2772], +] + +[[area.polygons]] +vertices = [ + [1205, 2742], + [1205, 2745], +] + +[[area.polygons]] +vertices = [ + [1169, 2743], + [1170, 2743], + [1170, 2744], + [1169, 2744], +] + +[[area.polygons]] +vertices = [[1168, 2744]] + +[[area.polygons]] +vertices = [[1170, 2745]] + +[[area.polygons]] +vertices = [ + [1199, 2747], + [1203, 2747], +] + +[[area.polygons]] +vertices = [ + [1176, 2748], + [1180, 2748], +] + +[[area.polygons]] +vertices = [ + [1199, 2748], + [1202, 2748], +] + +[[area.polygons]] +vertices = [ + [1177, 2749], + [1180, 2749], +] + +[[area.polygons]] +vertices = [ + [1199, 2749], + [1201, 2749], +] + +[[area.polygons]] +vertices = [ + [1178, 2750], + [1180, 2750], + [1180, 2812], + [1178, 2812], +] + +[[area.polygons]] +vertices = [ + [1199, 2750], + [1200, 2750], + [1200, 2770], + [1199, 2770], +] + +[[area.polygons]] +vertices = [ + [1177, 2753], + [1177, 2812], +] + +[[area.polygons]] +vertices = [ + [1175, 2754], + [1176, 2754], + [1176, 2812], + [1175, 2812], +] + +[[area.polygons]] +vertices = [ + [1201, 2754], + [1201, 2770], +] + +[[area.polygons]] +vertices = [ + [1173, 2755], + [1174, 2755], + [1174, 2811], + [1173, 2811], +] + +[[area.polygons]] +vertices = [ + [1202, 2755], + [1203, 2755], + [1203, 2768], + [1202, 2768], +] + +[[area.polygons]] +vertices = [ + [1172, 2756], + [1172, 2811], +] + +[[area.polygons]] +vertices = [ + [1204, 2756], + [1205, 2756], + [1205, 2766], + [1204, 2766], +] + +[[area.polygons]] +vertices = [ + [1171, 2757], + [1171, 2811], +] + +[[area.polygons]] +vertices = [ + [1206, 2757], + [1206, 2764], +] + +[[area.polygons]] +vertices = [ + [1169, 2758], + [1170, 2758], + [1170, 2811], + [1169, 2811], +] + +[[area.polygons]] +vertices = [ + [1207, 2758], + [1207, 2763], +] + +[[area.polygons]] +vertices = [ + [1167, 2759], + [1168, 2759], + [1168, 2811], + [1167, 2811], +] + +[[area.polygons]] +vertices = [ + [1208, 2759], + [1208, 2762], +] + +[[area.polygons]] +vertices = [ + [1220, 2759], + [1222, 2759], + [1222, 2811], + [1220, 2811], +] + +[[area.polygons]] +vertices = [ + [1161, 2760], + [1166, 2760], + [1166, 2792], + [1161, 2792], +] + +[[area.polygons]] +vertices = [ + [1218, 2760], + [1219, 2760], + [1219, 2809], + [1218, 2809], +] + +[[area.polygons]] +vertices = [ + [1223, 2760], + [1223, 2762], +] + +[[area.polygons]] +vertices = [ + [1158, 2761], + [1160, 2761], + [1160, 2791], + [1158, 2791], +] + +[[area.polygons]] +vertices = [ + [1157, 2762], + [1157, 2790], +] + +[[area.polygons]] +vertices = [ + [1217, 2762], + [1217, 2807], +] + +[[area.polygons]] +vertices = [ + [1156, 2763], + [1156, 2788], +] + +[[area.polygons]] +vertices = [ + [1155, 2766], + [1155, 2787], +] + +[[area.polygons]] +vertices = [ + [1154, 2767], + [1154, 2786], +] + +[[area.polygons]] +vertices = [ + [1204, 2767], + [1204, 2768], +] + +[[area.polygons]] +vertices = [ + [1153, 2768], + [1153, 2785], +] + +[[area.polygons]] +vertices = [ + [1152, 2769], + [1152, 2784], +] + +[[area.polygons]] +vertices = [[1202, 2769]] + +[[area.polygons]] +vertices = [ + [1151, 2770], + [1151, 2784], +] + +[[area.polygons]] +vertices = [ + [1216, 2770], + [1216, 2806], +] + +[[area.polygons]] +vertices = [ + [1223, 2770], + [1223, 2811], +] + +[[area.polygons]] +vertices = [ + [1149, 2771], + [1150, 2771], + [1150, 2783], + [1149, 2783], +] + +[[area.polygons]] +vertices = [[1199, 2771]] + +[[area.polygons]] +vertices = [ + [1215, 2771], + [1215, 2806], +] + +[[area.polygons]] +vertices = [ + [1224, 2771], + [1224, 2811], +] + +[[area.polygons]] +vertices = [ + [1147, 2772], + [1148, 2772], + [1148, 2782], + [1147, 2782], +] + +[[area.polygons]] +vertices = [ + [1212, 2772], + [1214, 2772], + [1214, 2806], + [1212, 2806], +] + +[[area.polygons]] +vertices = [ + [1225, 2772], + [1226, 2772], + [1226, 2805], + [1225, 2805], +] + +[[area.polygons]] +vertices = [[1197, 2773]] + +[[area.polygons]] +vertices = [ + [1210, 2773], + [1211, 2773], + [1211, 2807], + [1210, 2807], +] + +[[area.polygons]] +vertices = [ + [1227, 2773], + [1229, 2773], + [1229, 2804], + [1227, 2804], +] + +[[area.polygons]] +vertices = [[1146, 2774]] + +[[area.polygons]] +vertices = [ + [1208, 2774], + [1209, 2774], + [1209, 2808], + [1208, 2808], +] + +[[area.polygons]] +vertices = [ + [1230, 2774], + [1231, 2774], + [1231, 2805], + [1230, 2805], +] + +[[area.polygons]] +vertices = [ + [1207, 2775], + [1207, 2809], +] + +[[area.polygons]] +vertices = [ + [1232, 2775], + [1232, 2777], +] + +[[area.polygons]] +vertices = [ + [1206, 2776], + [1206, 2809], +] + +[[area.polygons]] +vertices = [ + [1205, 2777], + [1205, 2809], +] + +[[area.polygons]] +vertices = [ + [1146, 2778], + [1146, 2781], +] + +[[area.polygons]] +vertices = [ + [1197, 2778], + [1197, 2806], +] + +[[area.polygons]] +vertices = [ + [1204, 2778], + [1204, 2809], +] + +[[area.polygons]] +vertices = [ + [1145, 2779], + [1145, 2781], +] + +[[area.polygons]] +vertices = [ + [1203, 2779], + [1203, 2808], +] + +[[area.polygons]] +vertices = [ + [1198, 2780], + [1202, 2780], + [1202, 2805], + [1198, 2805], +] + +[[area.polygons]] +vertices = [[1144, 2781]] + +[[area.polygons]] +vertices = [ + [1232, 2783], + [1232, 2807], +] + +[[area.polygons]] +vertices = [ + [1233, 2784], + [1234, 2784], + [1234, 2807], + [1233, 2807], +] + +[[area.polygons]] +vertices = [ + [1235, 2785], + [1235, 2808], +] + +[[area.polygons]] +vertices = [ + [1250, 2787], + [1252, 2787], + [1252, 2802], + [1250, 2802], +] + +[[area.polygons]] +vertices = [ + [1248, 2788], + [1249, 2788], + [1249, 2801], + [1248, 2801], +] + +[[area.polygons]] +vertices = [ + [1253, 2788], + [1253, 2792], +] + +[[area.polygons]] +vertices = [ + [1236, 2789], + [1236, 2809], +] + +[[area.polygons]] +vertices = [ + [1247, 2789], + [1247, 2801], +] + +[[area.polygons]] +vertices = [ + [1246, 2790], + [1246, 2802], +] + +[[area.polygons]] +vertices = [ + [1237, 2791], + [1241, 2791], + [1241, 2806], + [1237, 2806], +] + +[[area.polygons]] +vertices = [ + [1142, 2792], + [1144, 2792], + [1144, 2799], + [1142, 2799], +] + +[[area.polygons]] +vertices = [[1160, 2792]] + +[[area.polygons]] +vertices = [ + [1242, 2792], + [1242, 2805], +] + +[[area.polygons]] +vertices = [ + [1245, 2792], + [1245, 2802], +] + +[[area.polygons]] +vertices = [ + [1141, 2793], + [1141, 2801], +] + +[[area.polygons]] +vertices = [ + [1145, 2793], + [1145, 2799], +] + +[[area.polygons]] +vertices = [ + [1164, 2793], + [1166, 2793], + [1166, 2809], + [1164, 2809], +] + +[[area.polygons]] +vertices = [ + [1243, 2793], + [1244, 2793], + [1244, 2803], + [1243, 2803], +] + +[[area.polygons]] +vertices = [ + [1138, 2794], + [1140, 2794], + [1140, 2801], + [1138, 2801], +] + +[[area.polygons]] +vertices = [ + [1146, 2794], + [1146, 2798], +] + +[[area.polygons]] +vertices = [ + [1147, 2795], + [1147, 2796], +] + +[[area.polygons]] +vertices = [ + [1162, 2795], + [1163, 2795], + [1163, 2807], + [1162, 2807], +] + +[[area.polygons]] +vertices = [ + [1159, 2796], + [1161, 2796], + [1161, 2807], + [1159, 2807], +] + +[[area.polygons]] +vertices = [ + [1158, 2797], + [1158, 2807], +] + +[[area.polygons]] +vertices = [ + [1157, 2798], + [1157, 2806], +] + +[[area.polygons]] +vertices = [ + [1253, 2798], + [1253, 2801], +] + +[[area.polygons]] +vertices = [ + [1137, 2800], + [1137, 2802], +] + +[[area.polygons]] +vertices = [ + [1142, 2800], + [1143, 2800], +] + +[[area.polygons]] +vertices = [ + [1136, 2801], + [1136, 2802], +] + +[[area.polygons]] +vertices = [ + [1142, 2801], + [1142, 2806], +] + +[[area.polygons]] +vertices = [ + [1138, 2802], + [1139, 2802], +] + +[[area.polygons]] +vertices = [ + [1146, 2803], + [1148, 2803], + [1148, 2805], + [1146, 2805], +] + +[[area.polygons]] +vertices = [ + [1145, 2804], + [1145, 2807], +] + +[[area.polygons]] +vertices = [ + [1149, 2804], + [1156, 2804], +] + +[[area.polygons]] +vertices = [[1243, 2804]] + +[[area.polygons]] +vertices = [ + [1141, 2805], + [1141, 2807], +] + +[[area.polygons]] +vertices = [ + [1143, 2805], + [1144, 2805], + [1144, 2806], + [1143, 2806], +] + +[[area.polygons]] +vertices = [[1229, 2805]] + +[[area.polygons]] +vertices = [ + [1146, 2806], + [1147, 2806], +] + +[[area.polygons]] +vertices = [ + [1199, 2806], + [1202, 2806], +] + +[[area.polygons]] +vertices = [ + [1225, 2806], + [1225, 2811], +] + +[[area.polygons]] +vertices = [[1231, 2806]] + +[[area.polygons]] +vertices = [[1140, 2807]] + +[[area.polygons]] +vertices = [[1144, 2807]] + +[[area.polygons]] +vertices = [[1146, 2807]] + +[[area.polygons]] +vertices = [ + [1201, 2807], + [1202, 2807], +] + +[[area.polygons]] +vertices = [[1212, 2807]] + +[[area.polygons]] +vertices = [ + [1226, 2807], + [1226, 2811], +] + +[[area.polygons]] +vertices = [ + [1237, 2807], + [1240, 2807], + [1240, 2809], + [1237, 2809], +] + +[[area.polygons]] +vertices = [[1163, 2808]] + +[[area.polygons]] +vertices = [[1202, 2808]] + +[[area.polygons]] +vertices = [ + [1227, 2808], + [1227, 2810], +] + +[[area.polygons]] +vertices = [[1234, 2808]] + +[[area.polygons]] +vertices = [[1189, 2809]] + +[[area.polygons]] +vertices = [ + [1197, 2809], + [1197, 2810], +] + +[[area.polygons]] +vertices = [[1208, 2809]] + +[[area.polygons]] +vertices = [[1166, 2810]] + +[[area.polygons]] +vertices = [ + [1191, 2810], + [1193, 2810], +] + +[[area.polygons]] +vertices = [ + [1198, 2810], + [1199, 2810], +] + +[[area.polygons]] +vertices = [[1219, 2810]] + +[[area.polygons]] +vertices = [ + [1238, 2810], + [1239, 2810], +] + +[[area.polygons]] +vertices = [ + [1193, 2811], + [1195, 2811], +] + +[[area.polygons]] +vertices = [ + [1199, 2811], + [1200, 2811], +] + +[[area.polygons]] +vertices = [ + [2056, 5024], + [2111, 5024], + [2111, 5055], + [2056, 5055], +] + +[[area.polygons]] +vertices = [ + [1152, 9152], + [1215, 9152], + [1215, 9215], + [1152, 9215], +] diff --git a/.data/raw-cache/map/area/way_of_the_enchanter.toml b/.data/raw-cache/map/area/way_of_the_enchanter.toml new file mode 100644 index 000000000..7592d8879 --- /dev/null +++ b/.data/raw-cache/map/area/way_of_the_enchanter.toml @@ -0,0 +1,24 @@ +# Way of the Enchanter music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Way of the Enchanter. +# Bounds x 2304-2367, z 10304-10367; 3584 tiles as 2 rectangles. + +[[area]] +name = "Way of the Enchanter" +area_id = "area.way_of_the_enchanter" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2304, 10304], + [2367, 10304], + [2367, 10351], + [2304, 10351], +] + +[[area.polygons]] +vertices = [ + [2304, 10352], + [2335, 10352], + [2335, 10367], + [2304, 10367], +] diff --git a/.data/raw-cache/map/area/wayward.toml b/.data/raw-cache/map/area/wayward.toml new file mode 100644 index 000000000..b0edcbbe5 --- /dev/null +++ b/.data/raw-cache/map/area/wayward.toml @@ -0,0 +1,16 @@ +# Wayward music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Wayward. +# Bounds x 2432-2495, z 9344-9471; 8192 tiles as 1 rectangles. + +[[area]] +name = "Wayward" +area_id = "area.wayward" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2432, 9344], + [2495, 9344], + [2495, 9471], + [2432, 9471], +] diff --git a/.data/raw-cache/map/area/we_are_the_fairies.toml b/.data/raw-cache/map/area/we_are_the_fairies.toml new file mode 100644 index 000000000..9cb235aeb --- /dev/null +++ b/.data/raw-cache/map/area/we_are_the_fairies.toml @@ -0,0 +1,16 @@ +# We are the Fairies music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: We are the Fairies. +# Bounds x 2048-2111, z 4800-4863; 4096 tiles as 1 rectangles. + +[[area]] +name = "We are the Fairies" +area_id = "area.we_are_the_fairies" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2048, 4800], + [2111, 4800], + [2111, 4863], + [2048, 4863], +] diff --git a/.data/raw-cache/map/area/welcome_to_the_theatre.toml b/.data/raw-cache/map/area/welcome_to_the_theatre.toml new file mode 100644 index 000000000..b9ccd1807 --- /dev/null +++ b/.data/raw-cache/map/area/welcome_to_the_theatre.toml @@ -0,0 +1,72 @@ +# Welcome to the Theatre music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Welcome to the Theatre. +# Bounds x 3638-3698, z 3200-3238; 2075 tiles as 10 rectangles. + +[[area]] +name = "Welcome to the Theatre" +area_id = "area.welcome_to_the_theatre" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3642, 3200], + [3682, 3200], + [3682, 3238], + [3642, 3238], +] + +[[area.polygons]] +vertices = [ + [3641, 3201], + [3641, 3237], +] + +[[area.polygons]] +vertices = [ + [3683, 3201], + [3683, 3237], +] + +[[area.polygons]] +vertices = [ + [3640, 3202], + [3640, 3236], +] + +[[area.polygons]] +vertices = [ + [3684, 3202], + [3684, 3236], +] + +[[area.polygons]] +vertices = [ + [3639, 3203], + [3639, 3235], +] + +[[area.polygons]] +vertices = [ + [3685, 3203], + [3685, 3235], +] + +[[area.polygons]] +vertices = [ + [3638, 3204], + [3638, 3234], +] + +[[area.polygons]] +vertices = [ + [3686, 3204], + [3686, 3234], +] + +[[area.polygons]] +vertices = [ + [3687, 3211], + [3698, 3211], + [3698, 3227], + [3687, 3227], +] diff --git a/.data/raw-cache/map/area/well_hallowed_air.toml b/.data/raw-cache/map/area/well_hallowed_air.toml new file mode 100644 index 000000000..6a0cb36a3 --- /dev/null +++ b/.data/raw-cache/map/area/well_hallowed_air.toml @@ -0,0 +1,16 @@ +# Well Hallowed Air music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Well Hallowed Air. +# Bounds x 2368-2431, z 5952-6015; 4096 tiles as 1 rectangles. + +[[area]] +name = "Well Hallowed Air" +area_id = "area.well_hallowed_air" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2368, 5952], + [2431, 5952], + [2431, 6015], + [2368, 6015], +] diff --git a/.data/raw-cache/map/area/well_of_voyage.toml b/.data/raw-cache/map/area/well_of_voyage.toml new file mode 100644 index 000000000..781a89069 --- /dev/null +++ b/.data/raw-cache/map/area/well_of_voyage.toml @@ -0,0 +1,24 @@ +# Well of Voyage music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Well of Voyage. +# Bounds x 1984-2367, z 4672-9663; 12288 tiles as 2 rectangles. + +[[area]] +name = "Well of Voyage" +area_id = "area.well_of_voyage" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1984, 4672], + [2047, 4672], + [2047, 4735], + [1984, 4735], +] + +[[area.polygons]] +vertices = [ + [2304, 9536], + [2367, 9536], + [2367, 9663], + [2304, 9663], +] diff --git a/.data/raw-cache/map/area/west_ardougne.toml b/.data/raw-cache/map/area/west_ardougne.toml new file mode 100644 index 000000000..f52731ea0 --- /dev/null +++ b/.data/raw-cache/map/area/west_ardougne.toml @@ -0,0 +1,52 @@ +# Inside the plague walls, up to the main gate shared with East Ardougne. +# +# Regenerated as rectangles from the OSRS wiki music map: 265 tiles moved to +# neighbouring music areas and 476 tiles were added. + +[[area]] +name = "West Ardougne" +area_id = "area.west_ardougne" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2538, 3264], + [2558, 3264], + [2558, 3334], + [2538, 3334], +] + +[[area.polygons]] +vertices = [ + [2511, 3265], + [2537, 3265], + [2537, 3334], + [2511, 3334], +] + +[[area.polygons]] +vertices = [ + [2461, 3280], + [2510, 3280], + [2510, 3322], + [2461, 3322], +] + +[[area.polygons]] +vertices = [ + [2432, 3306], + [2460, 3306], + [2460, 3322], + [2432, 3322], +] + +[[area.polygons]] +vertices = [ + [2464, 3323], + [2510, 3323], + [2510, 3334], + [2464, 3334], +] + +[[area.polygons]] +vertices = [[2558, 3335]] diff --git a/.data/raw-cache/map/area/what_happens_below.toml b/.data/raw-cache/map/area/what_happens_below.toml new file mode 100644 index 000000000..507bd8dab --- /dev/null +++ b/.data/raw-cache/map/area/what_happens_below.toml @@ -0,0 +1,16 @@ +# What Happens Below... music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: What Happens Below.... +# Bounds x 1728-1855, z 10112-10175; 8192 tiles as 1 rectangles. + +[[area]] +name = "What Happens Below..." +area_id = "area.what_happens_below" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1728, 10112], + [1855, 10112], + [1855, 10175], + [1728, 10175], +] diff --git a/.data/raw-cache/map/area/what_the_shell.toml b/.data/raw-cache/map/area/what_the_shell.toml new file mode 100644 index 000000000..ef0280bbe --- /dev/null +++ b/.data/raw-cache/map/area/what_the_shell.toml @@ -0,0 +1,16 @@ +# What the Shell! music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: What the Shell!. +# Bounds x 3136-3199, z 8832-8895; 4096 tiles as 1 rectangles. + +[[area]] +name = "What the Shell!" +area_id = "area.what_the_shell" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3136, 8832], + [3199, 8832], + [3199, 8895], + [3136, 8895], +] diff --git a/.data/raw-cache/map/area/where_eagles_lair.toml b/.data/raw-cache/map/area/where_eagles_lair.toml new file mode 100644 index 000000000..57a5ee69f --- /dev/null +++ b/.data/raw-cache/map/area/where_eagles_lair.toml @@ -0,0 +1,24 @@ +# Where Eagles Lair music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Where Eagles Lair. +# Bounds x 1920-2047, z 4864-4991; 8192 tiles as 2 rectangles. + +[[area]] +name = "Where Eagles Lair" +area_id = "area.where_eagles_lair" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1920, 4864], + [1983, 4864], + [1983, 4927], + [1920, 4927], +] + +[[area.polygons]] +vertices = [ + [1984, 4928], + [2047, 4928], + [2047, 4991], + [1984, 4991], +] diff --git a/.data/raw-cache/map/area/whispering_wind.toml b/.data/raw-cache/map/area/whispering_wind.toml new file mode 100644 index 000000000..c2edb1f03 --- /dev/null +++ b/.data/raw-cache/map/area/whispering_wind.toml @@ -0,0 +1,2285 @@ +# Whispering Wind music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Whispering Wind. +# Bounds x 1399-1726, z 3164-3335; 25973 tiles as 370 rectangles. + +[[area]] +name = "Whispering Wind" +area_id = "area.whispering_wind" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1465, 3164], + [1472, 3164], + [1472, 3184], + [1465, 3184], +] + +[[area.polygons]] +vertices = [ + [1568, 3180], + [1584, 3180], + [1584, 3298], + [1568, 3298], +] + +[[area.polygons]] +vertices = [ + [1567, 3181], + [1567, 3325], +] + +[[area.polygons]] +vertices = [ + [1585, 3181], + [1585, 3298], +] + +[[area.polygons]] +vertices = [ + [1473, 3182], + [1473, 3306], +] + +[[area.polygons]] +vertices = [ + [1566, 3182], + [1566, 3325], +] + +[[area.polygons]] +vertices = [ + [1586, 3182], + [1586, 3298], +] + +[[area.polygons]] +vertices = [ + [1474, 3183], + [1488, 3183], + [1488, 3222], + [1474, 3222], +] + +[[area.polygons]] +vertices = [ + [1565, 3183], + [1565, 3326], +] + +[[area.polygons]] +vertices = [ + [1587, 3183], + [1587, 3299], +] + +[[area.polygons]] +vertices = [ + [1489, 3184], + [1489, 3209], +] + +[[area.polygons]] +vertices = [ + [1564, 3184], + [1564, 3326], +] + +[[area.polygons]] +vertices = [ + [1588, 3184], + [1588, 3299], +] + +[[area.polygons]] +vertices = [ + [1466, 3185], + [1472, 3185], +] + +[[area.polygons]] +vertices = [ + [1490, 3185], + [1490, 3209], +] + +[[area.polygons]] +vertices = [ + [1563, 3185], + [1563, 3327], +] + +[[area.polygons]] +vertices = [ + [1589, 3185], + [1589, 3299], +] + +[[area.polygons]] +vertices = [ + [1467, 3186], + [1472, 3186], +] + +[[area.polygons]] +vertices = [ + [1491, 3186], + [1491, 3209], +] + +[[area.polygons]] +vertices = [ + [1562, 3186], + [1562, 3328], +] + +[[area.polygons]] +vertices = [ + [1590, 3186], + [1590, 3298], +] + +[[area.polygons]] +vertices = [ + [1468, 3187], + [1472, 3187], + [1472, 3303], + [1468, 3303], +] + +[[area.polygons]] +vertices = [ + [1492, 3187], + [1492, 3209], +] + +[[area.polygons]] +vertices = [ + [1561, 3187], + [1561, 3328], +] + +[[area.polygons]] +vertices = [ + [1591, 3187], + [1591, 3297], +] + +[[area.polygons]] +vertices = [ + [1560, 3188], + [1560, 3328], +] + +[[area.polygons]] +vertices = [ + [1592, 3188], + [1592, 3296], +] + +[[area.polygons]] +vertices = [ + [1533, 3189], + [1559, 3189], + [1559, 3271], + [1533, 3271], +] + +[[area.polygons]] +vertices = [ + [1593, 3189], + [1593, 3295], +] + +[[area.polygons]] +vertices = [ + [1594, 3190], + [1594, 3295], +] + +[[area.polygons]] +vertices = [ + [1595, 3191], + [1595, 3295], +] + +[[area.polygons]] +vertices = [ + [1596, 3192], + [1596, 3294], +] + +[[area.polygons]] +vertices = [ + [1597, 3193], + [1597, 3294], +] + +[[area.polygons]] +vertices = [ + [1598, 3194], + [1598, 3293], +] + +[[area.polygons]] +vertices = [ + [1599, 3195], + [1599, 3292], +] + +[[area.polygons]] +vertices = [ + [1600, 3196], + [1600, 3292], +] + +[[area.polygons]] +vertices = [ + [1601, 3197], + [1601, 3292], +] + +[[area.polygons]] +vertices = [ + [1602, 3198], + [1602, 3291], +] + +[[area.polygons]] +vertices = [ + [1493, 3199], + [1493, 3209], +] + +[[area.polygons]] +vertices = [ + [1603, 3199], + [1603, 3291], +] + +[[area.polygons]] +vertices = [ + [1494, 3200], + [1494, 3209], +] + +[[area.polygons]] +vertices = [ + [1532, 3200], + [1532, 3270], +] + +[[area.polygons]] +vertices = [ + [1604, 3200], + [1674, 3200], + [1674, 3207], + [1604, 3207], +] + +[[area.polygons]] +vertices = [ + [1495, 3201], + [1495, 3209], +] + +[[area.polygons]] +vertices = [ + [1531, 3201], + [1531, 3211], +] + +[[area.polygons]] +vertices = [ + [1675, 3201], + [1676, 3201], + [1676, 3225], + [1675, 3225], +] + +[[area.polygons]] +vertices = [ + [1496, 3202], + [1496, 3209], +] + +[[area.polygons]] +vertices = [ + [1530, 3202], + [1530, 3210], +] + +[[area.polygons]] +vertices = [ + [1677, 3202], + [1678, 3202], + [1678, 3225], + [1677, 3225], +] + +[[area.polygons]] +vertices = [ + [1457, 3203], + [1467, 3203], + [1467, 3290], + [1457, 3290], +] + +[[area.polygons]] +vertices = [ + [1497, 3203], + [1497, 3209], +] + +[[area.polygons]] +vertices = [ + [1529, 3203], + [1529, 3210], +] + +[[area.polygons]] +vertices = [ + [1679, 3203], + [1680, 3203], + [1680, 3225], + [1679, 3225], +] + +[[area.polygons]] +vertices = [ + [1498, 3204], + [1498, 3209], +] + +[[area.polygons]] +vertices = [ + [1528, 3204], + [1528, 3210], +] + +[[area.polygons]] +vertices = [ + [1681, 3204], + [1682, 3204], + [1682, 3225], + [1681, 3225], +] + +[[area.polygons]] +vertices = [ + [1499, 3205], + [1499, 3209], +] + +[[area.polygons]] +vertices = [ + [1527, 3205], + [1527, 3210], +] + +[[area.polygons]] +vertices = [ + [1683, 3205], + [1684, 3205], + [1684, 3225], + [1683, 3225], +] + +[[area.polygons]] +vertices = [ + [1500, 3206], + [1500, 3209], +] + +[[area.polygons]] +vertices = [ + [1526, 3206], + [1526, 3210], +] + +[[area.polygons]] +vertices = [ + [1685, 3206], + [1686, 3206], + [1686, 3225], + [1685, 3225], +] + +[[area.polygons]] +vertices = [ + [1501, 3207], + [1501, 3209], +] + +[[area.polygons]] +vertices = [ + [1525, 3207], + [1525, 3210], +] + +[[area.polygons]] +vertices = [ + [1687, 3207], + [1689, 3207], + [1689, 3225], + [1687, 3225], +] + +[[area.polygons]] +vertices = [ + [1502, 3208], + [1502, 3209], +] + +[[area.polygons]] +vertices = [ + [1524, 3208], + [1524, 3210], +] + +[[area.polygons]] +vertices = [ + [1604, 3208], + [1634, 3208], +] + +[[area.polygons]] +vertices = [ + [1649, 3208], + [1674, 3208], +] + +[[area.polygons]] +vertices = [ + [1690, 3208], + [1691, 3208], + [1691, 3225], + [1690, 3225], +] + +[[area.polygons]] +vertices = [[1503, 3209]] + +[[area.polygons]] +vertices = [ + [1523, 3209], + [1523, 3210], +] + +[[area.polygons]] +vertices = [ + [1604, 3209], + [1633, 3209], +] + +[[area.polygons]] +vertices = [ + [1650, 3209], + [1674, 3209], +] + +[[area.polygons]] +vertices = [ + [1692, 3209], + [1693, 3209], + [1693, 3225], + [1692, 3225], +] + +[[area.polygons]] +vertices = [[1522, 3210]] + +[[area.polygons]] +vertices = [ + [1604, 3210], + [1626, 3210], + [1626, 3291], + [1604, 3291], +] + +[[area.polygons]] +vertices = [ + [1651, 3210], + [1674, 3210], +] + +[[area.polygons]] +vertices = [ + [1694, 3210], + [1695, 3210], + [1695, 3225], + [1694, 3225], +] + +[[area.polygons]] +vertices = [ + [1452, 3211], + [1456, 3211], + [1456, 3287], + [1452, 3287], +] + +[[area.polygons]] +vertices = [ + [1653, 3211], + [1674, 3211], +] + +[[area.polygons]] +vertices = [ + [1696, 3211], + [1697, 3211], + [1697, 3225], + [1696, 3225], +] + +[[area.polygons]] +vertices = [ + [1654, 3212], + [1674, 3212], +] + +[[area.polygons]] +vertices = [ + [1698, 3212], + [1699, 3212], + [1699, 3225], + [1698, 3225], +] + +[[area.polygons]] +vertices = [ + [1433, 3213], + [1443, 3213], + [1443, 3285], + [1433, 3285], +] + +[[area.polygons]] +vertices = [ + [1655, 3213], + [1674, 3213], +] + +[[area.polygons]] +vertices = [ + [1700, 3213], + [1700, 3235], +] + +[[area.polygons]] +vertices = [ + [1432, 3214], + [1432, 3285], +] + +[[area.polygons]] +vertices = [ + [1656, 3214], + [1674, 3214], +] + +[[area.polygons]] +vertices = [ + [1701, 3214], + [1701, 3236], +] + +[[area.polygons]] +vertices = [ + [1431, 3215], + [1431, 3285], +] + +[[area.polygons]] +vertices = [ + [1658, 3215], + [1674, 3215], +] + +[[area.polygons]] +vertices = [ + [1702, 3215], + [1702, 3237], +] + +[[area.polygons]] +vertices = [ + [1430, 3216], + [1430, 3285], +] + +[[area.polygons]] +vertices = [ + [1524, 3216], + [1531, 3216], + [1531, 3265], + [1524, 3265], +] + +[[area.polygons]] +vertices = [ + [1659, 3216], + [1674, 3216], +] + +[[area.polygons]] +vertices = [ + [1703, 3216], + [1703, 3238], +] + +[[area.polygons]] +vertices = [ + [1429, 3217], + [1429, 3285], +] + +[[area.polygons]] +vertices = [ + [1660, 3217], + [1674, 3217], +] + +[[area.polygons]] +vertices = [ + [1704, 3217], + [1704, 3243], +] + +[[area.polygons]] +vertices = [ + [1428, 3218], + [1428, 3285], +] + +[[area.polygons]] +vertices = [ + [1444, 3218], + [1451, 3218], + [1451, 3285], + [1444, 3285], +] + +[[area.polygons]] +vertices = [ + [1672, 3218], + [1674, 3218], + [1674, 3225], + [1672, 3225], +] + +[[area.polygons]] +vertices = [ + [1705, 3218], + [1705, 3244], +] + +[[area.polygons]] +vertices = [ + [1427, 3219], + [1427, 3285], +] + +[[area.polygons]] +vertices = [ + [1426, 3220], + [1426, 3285], +] + +[[area.polygons]] +vertices = [ + [1706, 3220], + [1706, 3245], +] + +[[area.polygons]] +vertices = [ + [1425, 3221], + [1425, 3285], +] + +[[area.polygons]] +vertices = [ + [1424, 3222], + [1424, 3285], +] + +[[area.polygons]] +vertices = [ + [1627, 3222], + [1627, 3307], +] + +[[area.polygons]] +vertices = [ + [1707, 3222], + [1707, 3246], +] + +[[area.polygons]] +vertices = [ + [1423, 3223], + [1423, 3285], +] + +[[area.polygons]] +vertices = [ + [1474, 3223], + [1487, 3223], +] + +[[area.polygons]] +vertices = [ + [1422, 3224], + [1422, 3285], +] + +[[area.polygons]] +vertices = [ + [1474, 3224], + [1486, 3224], +] + +[[area.polygons]] +vertices = [ + [1708, 3224], + [1708, 3247], +] + +[[area.polygons]] +vertices = [ + [1421, 3225], + [1421, 3285], +] + +[[area.polygons]] +vertices = [ + [1474, 3225], + [1485, 3225], +] + +[[area.polygons]] +vertices = [ + [1709, 3225], + [1709, 3252], +] + +[[area.polygons]] +vertices = [ + [1420, 3226], + [1420, 3274], +] + +[[area.polygons]] +vertices = [ + [1474, 3226], + [1484, 3226], +] + +[[area.polygons]] +vertices = [ + [1628, 3226], + [1628, 3307], +] + +[[area.polygons]] +vertices = [ + [1699, 3226], + [1699, 3234], +] + +[[area.polygons]] +vertices = [ + [1419, 3227], + [1419, 3273], +] + +[[area.polygons]] +vertices = [ + [1474, 3227], + [1483, 3227], +] + +[[area.polygons]] +vertices = [ + [1418, 3228], + [1418, 3273], +] + +[[area.polygons]] +vertices = [ + [1474, 3228], + [1482, 3228], +] + +[[area.polygons]] +vertices = [ + [1710, 3228], + [1710, 3253], +] + +[[area.polygons]] +vertices = [ + [1417, 3229], + [1417, 3272], +] + +[[area.polygons]] +vertices = [ + [1474, 3229], + [1481, 3229], +] + +[[area.polygons]] +vertices = [ + [1629, 3229], + [1629, 3307], +] + +[[area.polygons]] +vertices = [ + [1416, 3230], + [1416, 3272], +] + +[[area.polygons]] +vertices = [ + [1474, 3230], + [1480, 3230], +] + +[[area.polygons]] +vertices = [ + [1711, 3230], + [1711, 3254], +] + +[[area.polygons]] +vertices = [ + [1415, 3231], + [1415, 3271], +] + +[[area.polygons]] +vertices = [ + [1474, 3231], + [1479, 3231], + [1479, 3306], + [1474, 3306], +] + +[[area.polygons]] +vertices = [ + [1414, 3232], + [1414, 3271], +] + +[[area.polygons]] +vertices = [ + [1413, 3233], + [1413, 3270], +] + +[[area.polygons]] +vertices = [ + [1630, 3233], + [1630, 3307], +] + +[[area.polygons]] +vertices = [ + [1412, 3234], + [1412, 3270], +] + +[[area.polygons]] +vertices = [ + [1712, 3234], + [1712, 3255], +] + +[[area.polygons]] +vertices = [ + [1411, 3235], + [1411, 3269], +] + +[[area.polygons]] +vertices = [ + [1480, 3235], + [1480, 3309], +] + +[[area.polygons]] +vertices = [ + [1521, 3235], + [1523, 3235], + [1523, 3265], + [1521, 3265], +] + +[[area.polygons]] +vertices = [ + [1481, 3236], + [1481, 3310], +] + +[[area.polygons]] +vertices = [ + [1713, 3236], + [1713, 3263], +] + +[[area.polygons]] +vertices = [ + [1482, 3237], + [1482, 3310], +] + +[[area.polygons]] +vertices = [ + [1631, 3237], + [1631, 3307], +] + +[[area.polygons]] +vertices = [ + [1483, 3238], + [1483, 3251], +] + +[[area.polygons]] +vertices = [ + [1520, 3238], + [1520, 3265], +] + +[[area.polygons]] +vertices = [ + [1714, 3238], + [1714, 3264], +] + +[[area.polygons]] +vertices = [ + [1484, 3239], + [1484, 3250], +] + +[[area.polygons]] +vertices = [ + [1519, 3239], + [1519, 3265], +] + +[[area.polygons]] +vertices = [ + [1485, 3240], + [1485, 3249], +] + +[[area.polygons]] +vertices = [ + [1518, 3240], + [1518, 3265], +] + +[[area.polygons]] +vertices = [ + [1632, 3240], + [1632, 3307], +] + +[[area.polygons]] +vertices = [ + [1715, 3240], + [1715, 3265], +] + +[[area.polygons]] +vertices = [ + [1486, 3241], + [1486, 3248], +] + +[[area.polygons]] +vertices = [ + [1517, 3241], + [1517, 3265], +] + +[[area.polygons]] +vertices = [ + [1716, 3241], + [1716, 3266], +] + +[[area.polygons]] +vertices = [ + [1487, 3242], + [1487, 3247], +] + +[[area.polygons]] +vertices = [ + [1516, 3242], + [1516, 3265], +] + +[[area.polygons]] +vertices = [ + [1633, 3242], + [1637, 3242], + [1637, 3298], + [1633, 3298], +] + +[[area.polygons]] +vertices = [ + [1717, 3242], + [1718, 3242], + [1718, 3287], + [1717, 3287], +] + +[[area.polygons]] +vertices = [ + [1515, 3243], + [1515, 3265], +] + +[[area.polygons]] +vertices = [ + [1638, 3243], + [1638, 3293], +] + +[[area.polygons]] +vertices = [ + [1719, 3243], + [1721, 3243], + [1721, 3250], + [1719, 3250], +] + +[[area.polygons]] +vertices = [ + [1514, 3244], + [1514, 3265], +] + +[[area.polygons]] +vertices = [ + [1639, 3244], + [1641, 3244], + [1641, 3292], + [1639, 3292], +] + +[[area.polygons]] +vertices = [ + [1722, 3244], + [1723, 3244], + [1723, 3249], + [1722, 3249], +] + +[[area.polygons]] +vertices = [ + [1513, 3245], + [1513, 3265], +] + +[[area.polygons]] +vertices = [ + [1642, 3245], + [1642, 3295], +] + +[[area.polygons]] +vertices = [ + [1724, 3245], + [1725, 3245], + [1725, 3247], + [1724, 3247], +] + +[[area.polygons]] +vertices = [ + [1512, 3246], + [1512, 3265], +] + +[[area.polygons]] +vertices = [ + [1643, 3246], + [1643, 3294], +] + +[[area.polygons]] +vertices = [[1726, 3246]] + +[[area.polygons]] +vertices = [ + [1503, 3247], + [1511, 3247], + [1511, 3266], + [1503, 3266], +] + +[[area.polygons]] +vertices = [ + [1644, 3247], + [1644, 3295], +] + +[[area.polygons]] +vertices = [ + [1645, 3248], + [1645, 3296], +] + +[[area.polygons]] +vertices = [ + [1646, 3249], + [1646, 3297], +] + +[[area.polygons]] +vertices = [ + [1647, 3250], + [1647, 3298], +] + +[[area.polygons]] +vertices = [[1722, 3250]] + +[[area.polygons]] +vertices = [ + [1719, 3251], + [1720, 3251], + [1720, 3288], + [1719, 3288], +] + +[[area.polygons]] +vertices = [ + [1410, 3253], + [1410, 3269], +] + +[[area.polygons]] +vertices = [ + [1408, 3254], + [1409, 3254], + [1409, 3269], + [1408, 3269], +] + +[[area.polygons]] +vertices = [ + [1406, 3255], + [1407, 3255], + [1407, 3269], + [1406, 3269], +] + +[[area.polygons]] +vertices = [ + [1502, 3255], + [1502, 3275], +] + +[[area.polygons]] +vertices = [ + [1404, 3256], + [1405, 3256], + [1405, 3269], + [1404, 3269], +] + +[[area.polygons]] +vertices = [ + [1501, 3256], + [1501, 3276], +] + +[[area.polygons]] +vertices = [ + [1402, 3257], + [1403, 3257], + [1403, 3269], + [1402, 3269], +] + +[[area.polygons]] +vertices = [ + [1500, 3257], + [1500, 3311], +] + +[[area.polygons]] +vertices = [ + [1400, 3258], + [1401, 3258], + [1401, 3265], + [1400, 3265], +] + +[[area.polygons]] +vertices = [ + [1499, 3258], + [1499, 3311], +] + +[[area.polygons]] +vertices = [ + [1721, 3258], + [1721, 3260], +] + +[[area.polygons]] +vertices = [ + [1399, 3259], + [1399, 3262], +] + +[[area.polygons]] +vertices = [ + [1498, 3259], + [1498, 3311], +] + +[[area.polygons]] +vertices = [[1722, 3259]] + +[[area.polygons]] +vertices = [ + [1497, 3260], + [1497, 3311], +] + +[[area.polygons]] +vertices = [ + [1496, 3261], + [1496, 3311], +] + +[[area.polygons]] +vertices = [ + [1495, 3262], + [1495, 3311], +] + +[[area.polygons]] +vertices = [ + [1494, 3263], + [1494, 3311], +] + +[[area.polygons]] +vertices = [ + [1483, 3264], + [1493, 3264], + [1493, 3311], + [1483, 3311], +] + +[[area.polygons]] +vertices = [ + [1721, 3264], + [1721, 3292], +] + +[[area.polygons]] +vertices = [ + [1722, 3265], + [1722, 3292], +] + +[[area.polygons]] +vertices = [ + [1401, 3266], + [1401, 3267], +] + +[[area.polygons]] +vertices = [ + [1528, 3266], + [1531, 3266], +] + +[[area.polygons]] +vertices = [ + [1723, 3266], + [1723, 3276], +] + +[[area.polygons]] +vertices = [ + [1503, 3267], + [1510, 3267], +] + +[[area.polygons]] +vertices = [ + [1529, 3267], + [1531, 3267], +] + +[[area.polygons]] +vertices = [ + [1724, 3267], + [1724, 3274], +] + +[[area.polygons]] +vertices = [ + [1503, 3268], + [1509, 3268], +] + +[[area.polygons]] +vertices = [ + [1530, 3268], + [1531, 3268], +] + +[[area.polygons]] +vertices = [ + [1725, 3268], + [1725, 3271], +] + +[[area.polygons]] +vertices = [ + [1503, 3269], + [1508, 3269], +] + +[[area.polygons]] +vertices = [[1531, 3269]] + +[[area.polygons]] +vertices = [ + [1648, 3269], + [1648, 3298], +] + +[[area.polygons]] +vertices = [ + [1503, 3270], + [1507, 3270], +] + +[[area.polygons]] +vertices = [ + [1649, 3270], + [1649, 3295], +] + +[[area.polygons]] +vertices = [ + [1503, 3271], + [1506, 3271], +] + +[[area.polygons]] +vertices = [ + [1503, 3272], + [1505, 3272], +] + +[[area.polygons]] +vertices = [ + [1534, 3272], + [1559, 3272], +] + +[[area.polygons]] +vertices = [ + [1650, 3272], + [1650, 3294], +] + +[[area.polygons]] +vertices = [ + [1672, 3272], + [1682, 3272], + [1682, 3297], + [1672, 3297], +] + +[[area.polygons]] +vertices = [ + [1709, 3272], + [1716, 3272], + [1716, 3286], + [1709, 3286], +] + +[[area.polygons]] +vertices = [ + [1503, 3273], + [1504, 3273], +] + +[[area.polygons]] +vertices = [ + [1535, 3273], + [1559, 3273], +] + +[[area.polygons]] +vertices = [ + [1651, 3273], + [1651, 3293], +] + +[[area.polygons]] +vertices = [ + [1670, 3273], + [1671, 3273], + [1671, 3300], + [1670, 3300], +] + +[[area.polygons]] +vertices = [ + [1683, 3273], + [1683, 3293], +] + +[[area.polygons]] +vertices = [ + [1708, 3273], + [1708, 3292], +] + +[[area.polygons]] +vertices = [[1503, 3274]] + +[[area.polygons]] +vertices = [ + [1536, 3274], + [1559, 3274], + [1559, 3327], + [1536, 3327], +] + +[[area.polygons]] +vertices = [ + [1668, 3274], + [1669, 3274], + [1669, 3300], + [1668, 3300], +] + +[[area.polygons]] +vertices = [ + [1684, 3274], + [1684, 3293], +] + +[[area.polygons]] +vertices = [ + [1707, 3274], + [1707, 3292], +] + +[[area.polygons]] +vertices = [ + [1652, 3275], + [1652, 3293], +] + +[[area.polygons]] +vertices = [ + [1666, 3275], + [1667, 3275], + [1667, 3298], + [1666, 3298], +] + +[[area.polygons]] +vertices = [ + [1685, 3275], + [1685, 3294], +] + +[[area.polygons]] +vertices = [ + [1706, 3275], + [1706, 3297], +] + +[[area.polygons]] +vertices = [ + [1653, 3276], + [1653, 3296], +] + +[[area.polygons]] +vertices = [ + [1664, 3276], + [1665, 3276], + [1665, 3297], + [1664, 3297], +] + +[[area.polygons]] +vertices = [ + [1686, 3276], + [1686, 3294], +] + +[[area.polygons]] +vertices = [ + [1705, 3276], + [1705, 3297], +] + +[[area.polygons]] +vertices = [ + [1663, 3277], + [1663, 3299], +] + +[[area.polygons]] +vertices = [ + [1687, 3277], + [1687, 3295], +] + +[[area.polygons]] +vertices = [ + [1704, 3277], + [1704, 3297], +] + +[[area.polygons]] +vertices = [ + [1654, 3278], + [1654, 3297], +] + +[[area.polygons]] +vertices = [ + [1688, 3278], + [1688, 3293], +] + +[[area.polygons]] +vertices = [ + [1703, 3278], + [1703, 3298], +] + +[[area.polygons]] +vertices = [ + [1723, 3278], + [1723, 3292], +] + +[[area.polygons]] +vertices = [ + [1655, 3279], + [1655, 3296], +] + +[[area.polygons]] +vertices = [ + [1689, 3279], + [1689, 3292], +] + +[[area.polygons]] +vertices = [ + [1702, 3279], + [1702, 3299], +] + +[[area.polygons]] +vertices = [ + [1690, 3280], + [1701, 3280], + [1701, 3293], + [1690, 3293], +] + +[[area.polygons]] +vertices = [ + [1724, 3280], + [1724, 3291], +] + +[[area.polygons]] +vertices = [ + [1656, 3281], + [1656, 3296], +] + +[[area.polygons]] +vertices = [ + [1657, 3282], + [1657, 3296], +] + +[[area.polygons]] +vertices = [ + [1725, 3282], + [1725, 3289], +] + +[[area.polygons]] +vertices = [ + [1658, 3283], + [1662, 3283], + [1662, 3298], + [1658, 3298], +] + +[[area.polygons]] +vertices = [ + [1726, 3284], + [1726, 3287], +] + +[[area.polygons]] +vertices = [ + [1449, 3286], + [1451, 3286], +] + +[[area.polygons]] +vertices = [ + [1523, 3286], + [1535, 3286], + [1535, 3324], + [1523, 3324], +] + +[[area.polygons]] +vertices = [[1451, 3287]] + +[[area.polygons]] +vertices = [ + [1522, 3287], + [1522, 3324], +] + +[[area.polygons]] +vertices = [ + [1709, 3287], + [1714, 3287], +] + +[[area.polygons]] +vertices = [ + [1453, 3288], + [1456, 3288], +] + +[[area.polygons]] +vertices = [ + [1521, 3288], + [1521, 3323], +] + +[[area.polygons]] +vertices = [ + [1709, 3288], + [1710, 3288], + [1710, 3291], + [1709, 3291], +] + +[[area.polygons]] +vertices = [ + [1712, 3288], + [1715, 3288], +] + +[[area.polygons]] +vertices = [ + [1455, 3289], + [1456, 3289], +] + +[[area.polygons]] +vertices = [ + [1520, 3289], + [1520, 3323], +] + +[[area.polygons]] +vertices = [ + [1713, 3289], + [1715, 3289], +] + +[[area.polygons]] +vertices = [ + [1720, 3289], + [1720, 3290], +] + +[[area.polygons]] +vertices = [ + [1501, 3290], + [1501, 3311], +] + +[[area.polygons]] +vertices = [[1711, 3290]] + +[[area.polygons]] +vertices = [ + [1459, 3291], + [1467, 3291], +] + +[[area.polygons]] +vertices = [ + [1502, 3291], + [1502, 3311], +] + +[[area.polygons]] +vertices = [ + [1460, 3292], + [1467, 3292], + [1467, 3296], + [1460, 3296], +] + +[[area.polygons]] +vertices = [ + [1503, 3292], + [1503, 3311], +] + +[[area.polygons]] +vertices = [ + [1607, 3292], + [1626, 3292], +] + +[[area.polygons]] +vertices = [[1709, 3292]] + +[[area.polygons]] +vertices = [ + [1504, 3293], + [1504, 3312], +] + +[[area.polygons]] +vertices = [ + [1519, 3293], + [1519, 3322], +] + +[[area.polygons]] +vertices = [ + [1610, 3293], + [1626, 3293], +] + +[[area.polygons]] +vertices = [ + [1641, 3293], + [1641, 3294], +] + +[[area.polygons]] +vertices = [ + [1505, 3294], + [1505, 3313], +] + +[[area.polygons]] +vertices = [ + [1518, 3294], + [1518, 3322], +] + +[[area.polygons]] +vertices = [ + [1611, 3294], + [1626, 3294], +] + +[[area.polygons]] +vertices = [ + [1691, 3294], + [1701, 3294], + [1701, 3295], + [1691, 3295], +] + +[[area.polygons]] +vertices = [ + [1506, 3295], + [1506, 3314], +] + +[[area.polygons]] +vertices = [ + [1517, 3295], + [1517, 3321], +] + +[[area.polygons]] +vertices = [ + [1612, 3295], + [1626, 3295], + [1626, 3306], + [1612, 3306], +] + +[[area.polygons]] +vertices = [ + [1507, 3296], + [1507, 3315], +] + +[[area.polygons]] +vertices = [ + [1516, 3296], + [1516, 3321], +] + +[[area.polygons]] +vertices = [ + [1638, 3296], + [1638, 3297], +] + +[[area.polygons]] +vertices = [ + [1683, 3296], + [1683, 3297], +] + +[[area.polygons]] +vertices = [ + [1694, 3296], + [1701, 3296], +] + +[[area.polygons]] +vertices = [[1707, 3296]] + +[[area.polygons]] +vertices = [ + [1461, 3297], + [1467, 3297], +] + +[[area.polygons]] +vertices = [ + [1508, 3297], + [1508, 3316], +] + +[[area.polygons]] +vertices = [ + [1515, 3297], + [1515, 3320], +] + +[[area.polygons]] +vertices = [[1684, 3297]] + +[[area.polygons]] +vertices = [ + [1695, 3297], + [1701, 3297], +] + +[[area.polygons]] +vertices = [ + [1462, 3298], + [1467, 3298], +] + +[[area.polygons]] +vertices = [ + [1509, 3298], + [1509, 3317], +] + +[[area.polygons]] +vertices = [ + [1514, 3298], + [1514, 3320], +] + +[[area.polygons]] +vertices = [ + [1611, 3298], + [1611, 3307], +] + +[[area.polygons]] +vertices = [ + [1649, 3298], + [1649, 3299], +] + +[[area.polygons]] +vertices = [ + [1657, 3298], + [1657, 3300], +] + +[[area.polygons]] +vertices = [ + [1672, 3298], + [1679, 3298], + [1679, 3300], + [1672, 3300], +] + +[[area.polygons]] +vertices = [ + [1696, 3298], + [1701, 3298], +] + +[[area.polygons]] +vertices = [ + [1463, 3299], + [1467, 3299], +] + +[[area.polygons]] +vertices = [ + [1510, 3299], + [1513, 3299], + [1513, 3318], + [1510, 3318], +] + +[[area.polygons]] +vertices = [ + [1568, 3299], + [1582, 3299], + [1582, 3301], + [1568, 3301], +] + +[[area.polygons]] +vertices = [ + [1610, 3299], + [1610, 3306], +] + +[[area.polygons]] +vertices = [ + [1633, 3299], + [1636, 3299], + [1636, 3303], + [1633, 3303], +] + +[[area.polygons]] +vertices = [[1650, 3299]] + +[[area.polygons]] +vertices = [ + [1658, 3299], + [1659, 3299], +] + +[[area.polygons]] +vertices = [[1667, 3299]] + +[[area.polygons]] +vertices = [ + [1700, 3299], + [1701, 3299], +] + +[[area.polygons]] +vertices = [ + [1464, 3300], + [1467, 3300], +] + +[[area.polygons]] +vertices = [ + [1465, 3301], + [1467, 3301], +] + +[[area.polygons]] +vertices = [ + [1609, 3301], + [1609, 3306], +] + +[[area.polygons]] +vertices = [ + [1674, 3301], + [1678, 3301], +] + +[[area.polygons]] +vertices = [ + [1466, 3302], + [1467, 3302], +] + +[[area.polygons]] +vertices = [ + [1568, 3302], + [1581, 3302], + [1581, 3307], + [1568, 3307], +] + +[[area.polygons]] +vertices = [ + [1607, 3302], + [1608, 3302], + [1608, 3303], + [1607, 3303], +] + +[[area.polygons]] +vertices = [[1637, 3302]] + +[[area.polygons]] +vertices = [ + [1676, 3302], + [1677, 3302], +] + +[[area.polygons]] +vertices = [[1467, 3303]] + +[[area.polygons]] +vertices = [ + [1469, 3304], + [1472, 3304], +] + +[[area.polygons]] +vertices = [[1608, 3304]] + +[[area.polygons]] +vertices = [ + [1633, 3304], + [1634, 3304], +] + +[[area.polygons]] +vertices = [ + [1471, 3305], + [1472, 3305], +] + +[[area.polygons]] +vertices = [ + [1633, 3305], + [1633, 3306], +] + +[[area.polygons]] +vertices = [ + [1475, 3307], + [1479, 3307], +] + +[[area.polygons]] +vertices = [ + [1612, 3307], + [1619, 3307], +] + +[[area.polygons]] +vertices = [ + [1621, 3307], + [1626, 3307], +] + +[[area.polygons]] +vertices = [ + [1477, 3308], + [1479, 3308], +] + +[[area.polygons]] +vertices = [ + [1568, 3308], + [1580, 3308], +] + +[[area.polygons]] +vertices = [ + [1623, 3308], + [1626, 3308], +] + +[[area.polygons]] +vertices = [[1479, 3309]] + +[[area.polygons]] +vertices = [ + [1568, 3309], + [1579, 3309], +] + +[[area.polygons]] +vertices = [ + [1568, 3310], + [1578, 3310], + [1578, 3318], + [1568, 3318], +] + +[[area.polygons]] +vertices = [ + [1580, 3310], + [1582, 3310], + [1582, 3311], + [1580, 3311], +] + +[[area.polygons]] +vertices = [ + [1579, 3311], + [1579, 3318], +] + +[[area.polygons]] +vertices = [[1583, 3311]] + +[[area.polygons]] +vertices = [ + [1580, 3312], + [1581, 3312], + [1581, 3317], + [1580, 3317], +] + +[[area.polygons]] +vertices = [ + [1582, 3315], + [1582, 3317], +] + +[[area.polygons]] +vertices = [ + [1583, 3316], + [1583, 3317], +] + +[[area.polygons]] +vertices = [[1580, 3318]] + +[[area.polygons]] +vertices = [ + [1512, 3319], + [1513, 3319], +] + +[[area.polygons]] +vertices = [ + [1568, 3319], + [1574, 3319], +] + +[[area.polygons]] +vertices = [ + [1568, 3320], + [1573, 3320], +] + +[[area.polygons]] +vertices = [ + [1568, 3321], + [1572, 3321], + [1572, 3323], + [1568, 3323], +] + +[[area.polygons]] +vertices = [ + [1568, 3324], + [1571, 3324], +] + +[[area.polygons]] +vertices = [ + [1524, 3325], + [1535, 3325], +] + +[[area.polygons]] +vertices = [[1568, 3325]] + +[[area.polygons]] +vertices = [ + [1529, 3326], + [1535, 3326], + [1535, 3334], + [1529, 3334], +] + +[[area.polygons]] +vertices = [ + [1536, 3328], + [1556, 3328], +] + +[[area.polygons]] +vertices = [ + [1536, 3329], + [1554, 3329], +] + +[[area.polygons]] +vertices = [ + [1536, 3330], + [1552, 3330], +] + +[[area.polygons]] +vertices = [ + [1536, 3331], + [1551, 3331], +] + +[[area.polygons]] +vertices = [ + [1536, 3332], + [1546, 3332], +] + +[[area.polygons]] +vertices = [[1550, 3332]] + +[[area.polygons]] +vertices = [ + [1536, 3333], + [1540, 3333], +] + +[[area.polygons]] +vertices = [[1545, 3333]] + +[[area.polygons]] +vertices = [[1536, 3334]] + +[[area.polygons]] +vertices = [ + [1529, 3335], + [1530, 3335], +] + +[[area.polygons]] +vertices = [[1533, 3335]] diff --git a/.data/raw-cache/map/area/wilderness_2.toml b/.data/raw-cache/map/area/wilderness_2.toml new file mode 100644 index 000000000..44c5321b8 --- /dev/null +++ b/.data/raw-cache/map/area/wilderness_2.toml @@ -0,0 +1,16 @@ +# Wilderness music music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Venomous; Underground; Wild Isle; Wildwood; Moody; Witching; Wonder; Deep Wildy; Wilderness 2; Wilderness 3; Close Quarters; Dark; Inspiration; Shining; Forbidden; Army of Darkness; Crystal Sword; Dangerous; Troubled; Regal; Scape Sad; Scape Wild; Faithless; Dead Can Dance; Everlasting Fire; Undercurrent; Wild Side; Wilderness. +# Bounds x 2944-3007, z 10304-10367; 4096 tiles as 1 rectangles. + +[[area]] +name = "Wilderness music" +area_id = "area.wilderness_2" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2944, 10304], + [3007, 10304], + [3007, 10367], + [2944, 10367], +] diff --git a/.data/raw-cache/map/area/wilderness_music.toml b/.data/raw-cache/map/area/wilderness_music.toml new file mode 100644 index 000000000..523a4aebe --- /dev/null +++ b/.data/raw-cache/map/area/wilderness_music.toml @@ -0,0 +1,671 @@ +# Regenerated as rectangles from the OSRS wiki music map: 2261 tiles moved to +# neighbouring music areas and 36761 tiles were added. + +[[area]] +name = "Wilderness (music)" +area_id = "area.wilderness_music" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2944, 3523], + [2984, 3523], + [2984, 3672], + [2944, 3672], +] + +[[area.polygons]] +vertices = [ + [3041, 3523], + [3069, 3523], + [3069, 3985], + [3041, 3985], +] + +[[area.polygons]] +vertices = [ + [3131, 3523], + [3391, 3523], + [3391, 3616], + [3131, 3616], +] + +[[area.polygons]] +vertices = [ + [2985, 3524], + [2993, 3524], + [2993, 3986], + [2985, 3986], +] + +[[area.polygons]] +vertices = [ + [3036, 3524], + [3040, 3524], + [3040, 3986], + [3036, 3986], +] + +[[area.polygons]] +vertices = [ + [3070, 3524], + [3130, 3524], + [3130, 3616], + [3070, 3616], +] + +[[area.polygons]] +vertices = [ + [2994, 3525], + [2994, 3986], +] + +[[area.polygons]] +vertices = [ + [3035, 3525], + [3035, 3986], +] + +[[area.polygons]] +vertices = [ + [2995, 3526], + [2995, 3986], +] + +[[area.polygons]] +vertices = [ + [3034, 3526], + [3034, 3986], +] + +[[area.polygons]] +vertices = [ + [2996, 3527], + [2996, 3986], +] + +[[area.polygons]] +vertices = [ + [3033, 3527], + [3033, 3986], +] + +[[area.polygons]] +vertices = [ + [2997, 3528], + [2997, 3986], +] + +[[area.polygons]] +vertices = [ + [3032, 3528], + [3032, 3986], +] + +[[area.polygons]] +vertices = [ + [3392, 3528], + [3395, 3528], + [3395, 4159], + [3392, 4159], +] + +[[area.polygons]] +vertices = [ + [3031, 3529], + [3031, 3986], +] + +[[area.polygons]] +vertices = [ + [3030, 3530], + [3030, 3986], +] + +[[area.polygons]] +vertices = [ + [3029, 3531], + [3029, 3986], +] + +[[area.polygons]] +vertices = [ + [2998, 3532], + [2998, 3986], +] + +[[area.polygons]] +vertices = [ + [2999, 3536], + [3000, 3536], + [3000, 3986], + [2999, 3986], +] + +[[area.polygons]] +vertices = [ + [3001, 3537], + [3001, 3986], +] + +[[area.polygons]] +vertices = [ + [3028, 3537], + [3028, 3986], +] + +[[area.polygons]] +vertices = [ + [3002, 3538], + [3002, 3986], +] + +[[area.polygons]] +vertices = [ + [3027, 3538], + [3027, 3986], +] + +[[area.polygons]] +vertices = [ + [3026, 3539], + [3026, 3986], +] + +[[area.polygons]] +vertices = [ + [3003, 3540], + [3003, 3986], +] + +[[area.polygons]] +vertices = [ + [3025, 3540], + [3025, 3986], +] + +[[area.polygons]] +vertices = [ + [3004, 3541], + [3004, 3986], +] + +[[area.polygons]] +vertices = [ + [3024, 3541], + [3024, 3986], +] + +[[area.polygons]] +vertices = [ + [3005, 3542], + [3005, 3986], +] + +[[area.polygons]] +vertices = [ + [3023, 3542], + [3023, 3986], +] + +[[area.polygons]] +vertices = [ + [3006, 3543], + [3006, 3986], +] + +[[area.polygons]] +vertices = [ + [3022, 3543], + [3022, 3986], +] + +[[area.polygons]] +vertices = [ + [3007, 3544], + [3021, 3544], + [3021, 3986], + [3007, 3986], +] + +[[area.polygons]] +vertices = [ + [3070, 3617], + [3129, 3617], +] + +[[area.polygons]] +vertices = [ + [3139, 3617], + [3391, 3617], +] + +[[area.polygons]] +vertices = [ + [3070, 3618], + [3125, 3618], + [3125, 3621], + [3070, 3621], +] + +[[area.polygons]] +vertices = [ + [3144, 3618], + [3391, 3618], + [3391, 3619], + [3144, 3619], +] + +[[area.polygons]] +vertices = [ + [3151, 3620], + [3391, 3620], + [3391, 3623], + [3151, 3623], +] + +[[area.polygons]] +vertices = [ + [3070, 3622], + [3122, 3622], + [3122, 3913], + [3070, 3913], +] + +[[area.polygons]] +vertices = [ + [3152, 3624], + [3391, 3624], + [3391, 3625], + [3152, 3625], +] + +[[area.polygons]] +vertices = [ + [3153, 3626], + [3391, 3626], + [3391, 3627], + [3153, 3627], +] + +[[area.polygons]] +vertices = [ + [3158, 3628], + [3391, 3628], + [3391, 3984], + [3158, 3984], +] + +[[area.polygons]] +vertices = [ + [3123, 3633], + [3124, 3633], + [3124, 3919], + [3123, 3919], +] + +[[area.polygons]] +vertices = [ + [3155, 3634], + [3157, 3634], + [3157, 3635], + [3155, 3635], +] + +[[area.polygons]] +vertices = [ + [3125, 3640], + [3137, 3640], + [3137, 3921], + [3125, 3921], +] + +[[area.polygons]] +vertices = [ + [3156, 3641], + [3157, 3641], + [3157, 3985], + [3156, 3985], +] + +[[area.polygons]] +vertices = [ + [3145, 3645], + [3147, 3645], + [3147, 3985], + [3145, 3985], +] + +[[area.polygons]] +vertices = [ + [3138, 3646], + [3144, 3646], + [3144, 3985], + [3138, 3985], +] + +[[area.polygons]] +vertices = [ + [3148, 3647], + [3155, 3647], + [3155, 3985], + [3148, 3985], +] + +[[area.polygons]] +vertices = [ + [2945, 3673], + [2984, 3673], +] + +[[area.polygons]] +vertices = [ + [2946, 3674], + [2984, 3674], +] + +[[area.polygons]] +vertices = [ + [2947, 3675], + [2984, 3675], +] + +[[area.polygons]] +vertices = [ + [2948, 3676], + [2984, 3676], + [2984, 3986], + [2948, 3986], +] + +[[area.polygons]] +vertices = [ + [2947, 3680], + [2947, 3986], +] + +[[area.polygons]] +vertices = [ + [2946, 3681], + [2946, 3986], +] + +[[area.polygons]] +vertices = [ + [2945, 3682], + [2945, 3986], +] + +[[area.polygons]] +vertices = [ + [2944, 3683], + [2944, 3986], +] + +[[area.polygons]] +vertices = [ + [3070, 3914], + [3091, 3914], +] + +[[area.polygons]] +vertices = [ + [3118, 3914], + [3122, 3914], +] + +[[area.polygons]] +vertices = [ + [3070, 3915], + [3090, 3915], +] + +[[area.polygons]] +vertices = [ + [3119, 3915], + [3122, 3915], +] + +[[area.polygons]] +vertices = [ + [3070, 3916], + [3089, 3916], +] + +[[area.polygons]] +vertices = [ + [3120, 3916], + [3122, 3916], +] + +[[area.polygons]] +vertices = [ + [3070, 3917], + [3088, 3917], +] + +[[area.polygons]] +vertices = [ + [3121, 3917], + [3122, 3917], +] + +[[area.polygons]] +vertices = [ + [3070, 3918], + [3087, 3918], +] + +[[area.polygons]] +vertices = [[3122, 3918]] + +[[area.polygons]] +vertices = [ + [3070, 3919], + [3086, 3919], +] + +[[area.polygons]] +vertices = [ + [3070, 3920], + [3085, 3920], +] + +[[area.polygons]] +vertices = [[3124, 3920]] + +[[area.polygons]] +vertices = [ + [3070, 3921], + [3084, 3921], +] + +[[area.polygons]] +vertices = [ + [3070, 3922], + [3083, 3922], + [3083, 3985], + [3070, 3985], +] + +[[area.polygons]] +vertices = [ + [3126, 3922], + [3137, 3922], +] + +[[area.polygons]] +vertices = [ + [3127, 3923], + [3137, 3923], + [3137, 3985], + [3127, 3985], +] + +[[area.polygons]] +vertices = [ + [3084, 3942], + [3084, 3985], +] + +[[area.polygons]] +vertices = [ + [3126, 3942], + [3126, 3985], +] + +[[area.polygons]] +vertices = [ + [3085, 3943], + [3085, 3985], +] + +[[area.polygons]] +vertices = [ + [3125, 3943], + [3125, 3985], +] + +[[area.polygons]] +vertices = [ + [3086, 3944], + [3086, 3985], +] + +[[area.polygons]] +vertices = [ + [3124, 3944], + [3124, 3985], +] + +[[area.polygons]] +vertices = [ + [3087, 3945], + [3087, 3985], +] + +[[area.polygons]] +vertices = [ + [3123, 3945], + [3123, 3985], +] + +[[area.polygons]] +vertices = [ + [3088, 3946], + [3088, 3985], +] + +[[area.polygons]] +vertices = [ + [3122, 3946], + [3122, 3985], +] + +[[area.polygons]] +vertices = [ + [3089, 3947], + [3089, 3985], +] + +[[area.polygons]] +vertices = [ + [3121, 3947], + [3121, 3985], +] + +[[area.polygons]] +vertices = [ + [3090, 3948], + [3090, 3985], +] + +[[area.polygons]] +vertices = [ + [3120, 3948], + [3120, 3985], +] + +[[area.polygons]] +vertices = [ + [3091, 3949], + [3091, 3985], +] + +[[area.polygons]] +vertices = [ + [3119, 3949], + [3119, 3985], +] + +[[area.polygons]] +vertices = [ + [3092, 3950], + [3092, 3985], +] + +[[area.polygons]] +vertices = [ + [3118, 3950], + [3118, 3985], +] + +[[area.polygons]] +vertices = [ + [3093, 3951], + [3093, 3985], +] + +[[area.polygons]] +vertices = [ + [3117, 3951], + [3117, 3985], +] + +[[area.polygons]] +vertices = [ + [3094, 3952], + [3094, 3985], +] + +[[area.polygons]] +vertices = [ + [3116, 3952], + [3116, 3985], +] + +[[area.polygons]] +vertices = [ + [3095, 3953], + [3115, 3953], + [3115, 3985], + [3095, 3985], +] + +[[area.polygons]] +vertices = [ + [3396, 3968], + [3519, 3968], + [3519, 4159], + [3396, 4159], +] + +[[area.polygons]] +vertices = [ + [3158, 3985], + [3282, 3985], +] + +[[area.polygons]] +vertices = [ + [3328, 3985], + [3391, 3985], + [3391, 4159], + [3328, 4159], +] + +[[area.polygons]] +vertices = [ + [3041, 3986], + [3056, 3986], +] diff --git a/.data/raw-cache/map/area/wings_of_madness.toml b/.data/raw-cache/map/area/wings_of_madness.toml new file mode 100644 index 000000000..15f74e426 --- /dev/null +++ b/.data/raw-cache/map/area/wings_of_madness.toml @@ -0,0 +1,48 @@ +# Wings of Madness music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Wings of Madness. +# Bounds x 2523-2538, z 2210-2221; 170 tiles as 6 rectangles. + +[[area]] +name = "Wings of Madness" +area_id = "area.wings_of_madness" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2528, 2210], + [2536, 2210], + [2536, 2221], + [2528, 2221], +] + +[[area.polygons]] +vertices = [ + [2525, 2211], + [2527, 2211], + [2527, 2220], + [2525, 2220], +] + +[[area.polygons]] +vertices = [ + [2537, 2211], + [2537, 2220], +] + +[[area.polygons]] +vertices = [ + [2524, 2212], + [2524, 2219], +] + +[[area.polygons]] +vertices = [ + [2538, 2212], + [2538, 2219], +] + +[[area.polygons]] +vertices = [ + [2523, 2213], + [2523, 2218], +] diff --git a/.data/raw-cache/map/area/wizards_tower.toml b/.data/raw-cache/map/area/wizards_tower.toml new file mode 100644 index 000000000..5877f0d86 --- /dev/null +++ b/.data/raw-cache/map/area/wizards_tower.toml @@ -0,0 +1,70 @@ +# The Wizards' Tower and its island south of Draynor Village, plus the tower basement. Music +# area for "Vision". +[[area]] +name = "Wizards' Tower" +area_id = "area.wizards_tower" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3090, 3140], + [3130, 3140], + [3130, 3184], + [3090, 3184], +] + +[[area.polygons]] +vertices = [ + [3092, 9552], + [3128, 9552], + [3128, 9590], + [3092, 9590], +] + +# Extension traced from the OSRS wiki music map (1692 tiles). + +[[area.polygons]] +vertices = [ + [3112, 3185], + [3115, 3185], + [3115, 3199], + [3112, 3199], +] + +[[area.polygons]] +vertices = [ + [3116, 3195], + [3116, 3197], +] + +[[area.polygons]] +vertices = [ + [3088, 9536], + [3135, 9536], + [3135, 9551], + [3088, 9551], +] + +[[area.polygons]] +vertices = [ + [3088, 9552], + [3091, 9552], + [3091, 9599], + [3088, 9599], +] + +[[area.polygons]] +vertices = [ + [3129, 9552], + [3135, 9552], + [3135, 9599], + [3129, 9599], +] + +[[area.polygons]] +vertices = [ + [3092, 9591], + [3128, 9591], + [3128, 9599], + [3092, 9599], +] diff --git a/.data/raw-cache/map/area/wonder.toml b/.data/raw-cache/map/area/wonder.toml new file mode 100644 index 000000000..f6e59d61a --- /dev/null +++ b/.data/raw-cache/map/area/wonder.toml @@ -0,0 +1,259 @@ +# Wonder music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Wonder. +# Bounds x 2628-2665, z 2384-2426; 1109 tiles as 41 rectangles. + +[[area]] +name = "Wonder" +area_id = "area.wonder" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2638, 2384], + [2646, 2384], + [2646, 2421], + [2638, 2421], +] + +[[area.polygons]] +vertices = [ + [2651, 2384], + [2657, 2384], + [2657, 2397], + [2651, 2397], +] + +[[area.polygons]] +vertices = [ + [2636, 2385], + [2637, 2385], + [2637, 2421], + [2636, 2421], +] + +[[area.polygons]] +vertices = [ + [2647, 2385], + [2650, 2385], + [2650, 2426], + [2647, 2426], +] + +[[area.polygons]] +vertices = [ + [2658, 2385], + [2659, 2385], + [2659, 2395], + [2658, 2395], +] + +[[area.polygons]] +vertices = [ + [2635, 2386], + [2635, 2421], +] + +[[area.polygons]] +vertices = [ + [2660, 2386], + [2660, 2395], +] + +[[area.polygons]] +vertices = [ + [2634, 2387], + [2634, 2421], +] + +[[area.polygons]] +vertices = [ + [2661, 2387], + [2662, 2387], + [2662, 2394], + [2661, 2394], +] + +[[area.polygons]] +vertices = [ + [2633, 2388], + [2633, 2420], +] + +[[area.polygons]] +vertices = [ + [2663, 2388], + [2663, 2395], +] + +[[area.polygons]] +vertices = [ + [2632, 2389], + [2632, 2419], +] + +[[area.polygons]] +vertices = [ + [2631, 2390], + [2631, 2418], +] + +[[area.polygons]] +vertices = [ + [2664, 2390], + [2664, 2395], +] + +[[area.polygons]] +vertices = [ + [2630, 2391], + [2630, 2403], +] + +[[area.polygons]] +vertices = [ + [2629, 2392], + [2629, 2400], +] + +[[area.polygons]] +vertices = [ + [2665, 2392], + [2665, 2395], +] + +[[area.polygons]] +vertices = [[2662, 2395]] + +[[area.polygons]] +vertices = [ + [2628, 2396], + [2628, 2399], +] + +[[area.polygons]] +vertices = [[2658, 2396]] + +[[area.polygons]] +vertices = [ + [2651, 2398], + [2656, 2398], +] + +[[area.polygons]] +vertices = [ + [2651, 2399], + [2655, 2399], +] + +[[area.polygons]] +vertices = [ + [2651, 2400], + [2653, 2400], +] + +[[area.polygons]] +vertices = [ + [2651, 2401], + [2652, 2401], + [2652, 2403], + [2651, 2403], +] + +[[area.polygons]] +vertices = [ + [2651, 2404], + [2651, 2426], +] + +[[area.polygons]] +vertices = [ + [2630, 2407], + [2630, 2408], +] + +[[area.polygons]] +vertices = [ + [2652, 2410], + [2652, 2426], +] + +[[area.polygons]] +vertices = [ + [2630, 2411], + [2630, 2416], +] + +[[area.polygons]] +vertices = [ + [2629, 2412], + [2629, 2414], +] + +[[area.polygons]] +vertices = [ + [2653, 2414], + [2653, 2426], +] + +[[area.polygons]] +vertices = [ + [2654, 2416], + [2655, 2416], + [2655, 2426], + [2654, 2426], +] + +[[area.polygons]] +vertices = [ + [2656, 2417], + [2656, 2426], +] + +[[area.polygons]] +vertices = [ + [2657, 2418], + [2660, 2418], + [2660, 2424], + [2657, 2424], +] + +[[area.polygons]] +vertices = [ + [2661, 2419], + [2661, 2423], +] + +[[area.polygons]] +vertices = [[2662, 2420]] + +[[area.polygons]] +vertices = [ + [2637, 2422], + [2639, 2422], +] + +[[area.polygons]] +vertices = [ + [2643, 2422], + [2646, 2422], + [2646, 2423], + [2643, 2423], +] + +[[area.polygons]] +vertices = [ + [2644, 2424], + [2646, 2424], +] + +[[area.polygons]] +vertices = [[2646, 2425]] + +[[area.polygons]] +vertices = [ + [2657, 2425], + [2659, 2425], +] + +[[area.polygons]] +vertices = [[2657, 2426]] diff --git a/.data/raw-cache/map/area/work_work_work.toml b/.data/raw-cache/map/area/work_work_work.toml new file mode 100644 index 000000000..528c02a39 --- /dev/null +++ b/.data/raw-cache/map/area/work_work_work.toml @@ -0,0 +1,181 @@ +# Work Work Work music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Work Work Work. +# Bounds x 2635-2660, z 3207-3233; 455 tiles as 27 rectangles. + +[[area]] +name = "Work Work Work" +area_id = "area.work_work_work" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2648, 3207], + [2649, 3207], + [2649, 3212], + [2648, 3212], +] + +[[area.polygons]] +vertices = [ + [2646, 3208], + [2647, 3208], + [2647, 3212], + [2646, 3212], +] + +[[area.polygons]] +vertices = [ + [2650, 3208], + [2656, 3208], + [2656, 3212], + [2650, 3212], +] + +[[area.polygons]] +vertices = [ + [2645, 3209], + [2645, 3213], +] + +[[area.polygons]] +vertices = [ + [2657, 3209], + [2657, 3226], +] + +[[area.polygons]] +vertices = [ + [2638, 3210], + [2644, 3210], + [2644, 3214], + [2638, 3214], +] + +[[area.polygons]] +vertices = [ + [2658, 3210], + [2658, 3225], +] + +[[area.polygons]] +vertices = [ + [2659, 3211], + [2659, 3225], +] + +[[area.polygons]] +vertices = [ + [2637, 3213], + [2637, 3231], +] + +[[area.polygons]] +vertices = [ + [2652, 3213], + [2656, 3213], +] + +[[area.polygons]] +vertices = [ + [2636, 3214], + [2636, 3230], +] + +[[area.polygons]] +vertices = [ + [2653, 3214], + [2656, 3214], +] + +[[area.polygons]] +vertices = [ + [2635, 3215], + [2635, 3229], +] + +[[area.polygons]] +vertices = [ + [2638, 3215], + [2643, 3215], + [2643, 3231], + [2638, 3231], +] + +[[area.polygons]] +vertices = [ + [2654, 3215], + [2656, 3215], +] + +[[area.polygons]] +vertices = [ + [2655, 3216], + [2656, 3216], + [2656, 3227], + [2655, 3227], +] + +[[area.polygons]] +vertices = [ + [2654, 3221], + [2654, 3230], +] + +[[area.polygons]] +vertices = [ + [2644, 3222], + [2644, 3233], +] + +[[area.polygons]] +vertices = [ + [2653, 3222], + [2653, 3230], +] + +[[area.polygons]] +vertices = [ + [2645, 3223], + [2645, 3233], +] + +[[area.polygons]] +vertices = [ + [2652, 3223], + [2652, 3232], +] + +[[area.polygons]] +vertices = [ + [2646, 3224], + [2651, 3224], + [2651, 3232], + [2646, 3232], +] + +[[area.polygons]] +vertices = [[2660, 3224]] + +[[area.polygons]] +vertices = [ + [2655, 3228], + [2655, 3229], +] + +[[area.polygons]] +vertices = [ + [2639, 3232], + [2643, 3232], +] + +[[area.polygons]] +vertices = [ + [2640, 3233], + [2643, 3233], +] + +[[area.polygons]] +vertices = [ + [2646, 3233], + [2647, 3233], +] diff --git a/.data/raw-cache/map/area/wrath_and_ruin.toml b/.data/raw-cache/map/area/wrath_and_ruin.toml new file mode 100644 index 000000000..b0c422021 --- /dev/null +++ b/.data/raw-cache/map/area/wrath_and_ruin.toml @@ -0,0 +1,32 @@ +# Wrath and Ruin music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Wrath and Ruin. +# Bounds x 2944-3135, z 5184-5311; 9216 tiles as 3 rectangles. + +[[area]] +name = "Wrath and Ruin" +area_id = "area.wrath_and_ruin" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2944, 5184], + [3007, 5184], + [3007, 5247], + [2944, 5247], +] + +[[area.polygons]] +vertices = [ + [3008, 5216], + [3039, 5216], + [3039, 5247], + [3008, 5247], +] + +[[area.polygons]] +vertices = [ + [3072, 5248], + [3135, 5248], + [3135, 5311], + [3072, 5311], +] diff --git a/.data/raw-cache/map/area/xenophobe.toml b/.data/raw-cache/map/area/xenophobe.toml new file mode 100644 index 000000000..2ba2d9d1d --- /dev/null +++ b/.data/raw-cache/map/area/xenophobe.toml @@ -0,0 +1,24 @@ +# Xenophobe music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Xenophobe. +# Bounds x 1856-2943, z 4352-4479; 12288 tiles as 2 rectangles. + +[[area]] +name = "Xenophobe" +area_id = "area.xenophobe" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [1856, 4352], + [1919, 4352], + [1919, 4415], + [1856, 4415], +] + +[[area.polygons]] +vertices = [ + [2880, 4352], + [2943, 4352], + [2943, 4479], + [2880, 4479], +] diff --git a/.data/raw-cache/map/area/yanille.toml b/.data/raw-cache/map/area/yanille.toml new file mode 100644 index 000000000..ba4b72476 --- /dev/null +++ b/.data/raw-cache/map/area/yanille.toml @@ -0,0 +1,273 @@ +# Yanille and its surroundings: the walled city, the Watchtower, the Nightmare Zone and the +# agility dungeon entrance house north of the walls. Stops one tile short of the Yanille Chain +# islands (yanille_chain.toml) and of Port Khazard to the north. Music area for "Big Chords" and +# "Magic Dance". +# +# Regenerated as rectangles from the OSRS wiki music map: 2436 tiles moved to +# neighbouring music areas and 793 tiles were added. + +[[area]] +name = "Yanille" +area_id = "area.yanille" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2615, 3062], + [2632, 3062], +] + +[[area.polygons]] +vertices = [ + [2617, 3063], + [2632, 3063], +] + +[[area.polygons]] +vertices = [ + [2618, 3064], + [2632, 3064], +] + +[[area.polygons]] +vertices = [ + [2619, 3065], + [2632, 3065], +] + +[[area.polygons]] +vertices = [ + [2620, 3066], + [2632, 3066], + [2632, 3067], + [2620, 3067], +] + +[[area.polygons]] +vertices = [ + [2621, 3068], + [2632, 3068], +] + +[[area.polygons]] +vertices = [ + [2622, 3069], + [2632, 3069], + [2632, 3155], + [2622, 3155], +] + +[[area.polygons]] +vertices = [ + [2582, 3071], + [2589, 3071], + [2589, 3135], + [2582, 3135], +] + +[[area.polygons]] +vertices = [ + [2581, 3072], + [2581, 3135], +] + +[[area.polygons]] +vertices = [ + [2590, 3072], + [2590, 3135], +] + +[[area.polygons]] +vertices = [ + [2541, 3073], + [2580, 3073], + [2580, 3110], + [2541, 3110], +] + +[[area.polygons]] +vertices = [ + [2591, 3073], + [2621, 3073], + [2621, 3136], + [2591, 3136], +] + +[[area.polygons]] +vertices = [ + [2540, 3074], + [2540, 3109], +] + +[[area.polygons]] +vertices = [ + [2536, 3075], + [2539, 3075], + [2539, 3108], + [2536, 3108], +] + +[[area.polygons]] +vertices = [ + [2535, 3081], + [2535, 3102], +] + +[[area.polygons]] +vertices = [ + [2534, 3082], + [2534, 3101], +] + +[[area.polygons]] +vertices = [ + [2533, 3083], + [2533, 3100], +] + +[[area.polygons]] +vertices = [ + [2543, 3111], + [2580, 3111], + [2580, 3135], + [2543, 3135], +] + +[[area.polygons]] +vertices = [ + [2551, 3136], + [2557, 3136], +] + +[[area.polygons]] +vertices = [ + [2633, 3136], + [2646, 3136], + [2646, 3137], + [2633, 3137], +] + +[[area.polygons]] +vertices = [ + [2553, 3137], + [2556, 3137], +] + +[[area.polygons]] +vertices = [ + [2592, 3137], + [2621, 3137], +] + +[[area.polygons]] +vertices = [[2555, 3138]] + +[[area.polygons]] +vertices = [ + [2593, 3138], + [2621, 3138], +] + +[[area.polygons]] +vertices = [ + [2633, 3138], + [2645, 3138], +] + +[[area.polygons]] +vertices = [ + [2620, 3139], + [2621, 3139], + [2621, 3160], + [2620, 3160], +] + +[[area.polygons]] +vertices = [ + [2633, 3139], + [2644, 3139], +] + +[[area.polygons]] +vertices = [ + [2633, 3140], + [2641, 3140], +] + +[[area.polygons]] +vertices = [ + [2633, 3141], + [2640, 3141], +] + +[[area.polygons]] +vertices = [ + [2633, 3142], + [2639, 3142], +] + +[[area.polygons]] +vertices = [ + [2633, 3143], + [2637, 3143], + [2637, 3144], + [2633, 3144], +] + +[[area.polygons]] +vertices = [ + [2633, 3145], + [2636, 3145], +] + +[[area.polygons]] +vertices = [ + [2633, 3146], + [2635, 3146], + [2635, 3151], + [2633, 3151], +] + +[[area.polygons]] +vertices = [ + [2619, 3151], + [2619, 3155], +] + +[[area.polygons]] +vertices = [ + [2618, 3152], + [2618, 3154], +] + +[[area.polygons]] +vertices = [ + [2633, 3152], + [2633, 3153], +] + +[[area.polygons]] +vertices = [ + [2622, 3156], + [2631, 3156], +] + +[[area.polygons]] +vertices = [ + [2622, 3157], + [2629, 3157], +] + +[[area.polygons]] +vertices = [ + [2622, 3158], + [2628, 3158], + [2628, 3159], + [2622, 3159], +] + +[[area.polygons]] +vertices = [ + [2622, 3160], + [2627, 3160], +] diff --git a/.data/raw-cache/map/area/yanille_agility_dungeon.toml b/.data/raw-cache/map/area/yanille_agility_dungeon.toml new file mode 100644 index 000000000..a92f847aa --- /dev/null +++ b/.data/raw-cache/map/area/yanille_agility_dungeon.toml @@ -0,0 +1,24 @@ +# The Yanille Agility Dungeon map square (chaos druids, Salarin the Twisted, the poison spider +# pit and the lower chamber). Music area for "Cavern". +[[area]] +name = "Yanille Agility Dungeon" +area_id = "area.yanille_agility_dungeon" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2560, 9472], + [2623, 9472], + [2623, 9535], + [2560, 9535], +] + +# Extension traced from the OSRS wiki music map (4096 tiles). + +[[area.polygons]] +vertices = [ + [2560, 9536], + [2623, 9536], + [2623, 9599], + [2560, 9599], +] diff --git a/.data/raw-cache/map/area/yanille_chain.toml b/.data/raw-cache/map/area/yanille_chain.toml new file mode 100644 index 000000000..f390c4cba --- /dev/null +++ b/.data/raw-cache/map/area/yanille_chain.toml @@ -0,0 +1,14 @@ +# The chain of islands east of Yanille where Hazelmere lives (his house is at 2677,3087, fairy +# ring CLS behind it). Music area for "Long Ago". +[[area]] +name = "Yanille Chain" +area_id = "area.yanille_chain" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2633, 3062], + [2700, 3062], + [2700, 3135], + [2633, 3135], +] diff --git a/.data/raw-cache/map/area/zamorak_zoo.toml b/.data/raw-cache/map/area/zamorak_zoo.toml new file mode 100644 index 000000000..2af7e0b5d --- /dev/null +++ b/.data/raw-cache/map/area/zamorak_zoo.toml @@ -0,0 +1,32 @@ +# Zamorak Zoo music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Zamorak Zoo. +# Bounds x 2880-2943, z 5312-5375; 3136 tiles as 3 rectangles. + +[[area]] +name = "Zamorak Zoo" +area_id = "area.zamorak_zoo" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2912, 5312], + [2943, 5312], + [2943, 5375], + [2912, 5375], +] + +[[area.polygons]] +vertices = [ + [2904, 5336], + [2911, 5336], + [2911, 5375], + [2904, 5375], +] + +[[area.polygons]] +vertices = [ + [2880, 5344], + [2903, 5344], + [2903, 5375], + [2880, 5375], +] diff --git a/.data/raw-cache/map/area/zanaris.toml b/.data/raw-cache/map/area/zanaris.toml index 1d85e426f..f949044af 100644 --- a/.data/raw-cache/map/area/zanaris.toml +++ b/.data/raw-cache/map/area/zanaris.toml @@ -1,3 +1,6 @@ +# Regenerated as rectangles from the OSRS wiki music map: 4096 tiles moved to +# neighbouring music areas. + [[area]] name = "Zanaris" area_id = "area.zanaris" @@ -5,12 +8,16 @@ levels = [0, 1, 2, 3] [[area.polygons]] vertices = [ - [2304, 4416], - [2368, 4416], [2368, 4352], [2431, 4352], - [2431, 4416], + [2431, 4479], + [2368, 4479], +] + +[[area.polygons]] +vertices = [ + [2432, 4416], [2495, 4416], [2495, 4479], - [2304, 4479], + [2432, 4479], ] diff --git a/.data/raw-cache/map/area/zaniks_theme.toml b/.data/raw-cache/map/area/zaniks_theme.toml new file mode 100644 index 000000000..45cdb935f --- /dev/null +++ b/.data/raw-cache/map/area/zaniks_theme.toml @@ -0,0 +1,16 @@ +# Zanik's Theme music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Zanik's Theme. +# Bounds x 3840-3903, z 4352-4415; 4096 tiles as 1 rectangles. + +[[area]] +name = "Zanik's Theme" +area_id = "area.zaniks_theme" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3840, 4352], + [3903, 4352], + [3903, 4415], + [3840, 4415], +] diff --git a/.data/raw-cache/map/area/zealot.toml b/.data/raw-cache/map/area/zealot.toml new file mode 100644 index 000000000..5fab1e424 --- /dev/null +++ b/.data/raw-cache/map/area/zealot.toml @@ -0,0 +1,183 @@ +# Zealot music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Zealot. +# Bounds x 2465-2751, z 2694-4863; 4791 tiles as 28 rectangles. + +[[area]] +name = "Zealot" +area_id = "area.zealot" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2482, 2694], + [2484, 2694], + [2484, 2721], + [2482, 2721], +] + +[[area.polygons]] +vertices = [ + [2471, 2695], + [2472, 2695], + [2472, 2724], + [2471, 2724], +] + +[[area.polygons]] +vertices = [ + [2479, 2695], + [2481, 2695], + [2481, 2724], + [2479, 2724], +] + +[[area.polygons]] +vertices = [ + [2485, 2695], + [2486, 2695], + [2486, 2715], + [2485, 2715], +] + +[[area.polygons]] +vertices = [ + [2470, 2696], + [2470, 2723], +] + +[[area.polygons]] +vertices = [ + [2473, 2696], + [2478, 2696], + [2478, 2726], + [2473, 2726], +] + +[[area.polygons]] +vertices = [ + [2487, 2696], + [2487, 2712], +] + +[[area.polygons]] +vertices = [ + [2468, 2697], + [2469, 2697], + [2469, 2721], + [2468, 2721], +] + +[[area.polygons]] +vertices = [ + [2488, 2697], + [2488, 2710], +] + +[[area.polygons]] +vertices = [ + [2467, 2698], + [2467, 2720], +] + +[[area.polygons]] +vertices = [ + [2489, 2698], + [2489, 2707], +] + +[[area.polygons]] +vertices = [ + [2466, 2700], + [2466, 2704], +] + +[[area.polygons]] +vertices = [ + [2466, 2708], + [2466, 2718], +] + +[[area.polygons]] +vertices = [ + [2465, 2709], + [2465, 2716], +] + +[[area.polygons]] +vertices = [[2485, 2716]] + +[[area.polygons]] +vertices = [[2469, 2722]] + +[[area.polygons]] +vertices = [ + [2482, 2722], + [2483, 2722], +] + +[[area.polygons]] +vertices = [ + [2482, 2723], + [2482, 2724], +] + +[[area.polygons]] +vertices = [[2472, 2725]] + +[[area.polygons]] +vertices = [ + [2479, 2725], + [2480, 2725], +] + +[[area.polygons]] +vertices = [ + [2475, 2727], + [2475, 2737], +] + +[[area.polygons]] +vertices = [ + [2478, 2730], + [2478, 2738], +] + +[[area.polygons]] +vertices = [ + [2476, 2731], + [2477, 2731], + [2477, 2738], + [2476, 2738], +] + +[[area.polygons]] +vertices = [ + [2479, 2731], + [2479, 2738], +] + +[[area.polygons]] +vertices = [ + [2474, 2732], + [2474, 2735], +] + +[[area.polygons]] +vertices = [ + [2480, 2732], + [2480, 2738], +] + +[[area.polygons]] +vertices = [ + [2481, 2735], + [2481, 2737], +] + +[[area.polygons]] +vertices = [ + [2688, 4800], + [2751, 4800], + [2751, 4863], + [2688, 4863], +] diff --git a/.data/raw-cache/map/area/zogre_dance.toml b/.data/raw-cache/map/area/zogre_dance.toml new file mode 100644 index 000000000..125e8d787 --- /dev/null +++ b/.data/raw-cache/map/area/zogre_dance.toml @@ -0,0 +1,16 @@ +# Zogre Dance music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Zogre Dance. +# Bounds x 2456-2495, z 3031-3063; 1320 tiles as 1 rectangles. + +[[area]] +name = "Zogre Dance" +area_id = "area.zogre_dance" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [2456, 3031], + [2495, 3031], + [2495, 3063], + [2456, 3063], +] diff --git a/.data/raw-cache/map/area/zombiism.toml b/.data/raw-cache/map/area/zombiism.toml new file mode 100644 index 000000000..8ecb83dff --- /dev/null +++ b/.data/raw-cache/map/area/zombiism.toml @@ -0,0 +1,24 @@ +# Zombiism music area, traced from the OSRS wiki music map +# (Map:Music tracks). Plays: Zombiism. +# Bounds x 3776-3839, z 2816-9279; 8192 tiles as 2 rectangles. + +[[area]] +name = "Zombiism" +area_id = "area.zombiism" +levels = [0, 1, 2, 3] + +[[area.polygons]] +vertices = [ + [3776, 2816], + [3839, 2816], + [3839, 2879], + [3776, 2879], +] + +[[area.polygons]] +vertices = [ + [3776, 9216], + [3839, 9216], + [3839, 9279], + [3776, 9279], +] diff --git a/.data/raw-cache/map/npcs/monkey_on_unicorn.toml b/.data/raw-cache/map/npcs/monkey_on_unicorn.toml index 4a542802b..cbf137e29 100644 --- a/.data/raw-cache/map/npcs/monkey_on_unicorn.toml +++ b/.data/raw-cache/map/npcs/monkey_on_unicorn.toml @@ -6,7 +6,3 @@ coords = "0_49_50_7_10" npc = "npc.unicorn" coords = "0_49_50_4_9" -[[spawn]] -npc = "npc.zanarisleprechaun" -coords = "0_49_50_2_11" - diff --git a/.data/raw-cache/server/inv.toml b/.data/raw-cache/server/inv.toml index ab013e3c9..bf3fc8d44 100644 --- a/.data/raw-cache/server/inv.toml +++ b/.data/raw-cache/server/inv.toml @@ -57,3 +57,14 @@ scope = "Perm" stack = "Always" protect = false + +# Emir's Arena duel stakes. Persisted so a crash or restart mid-duel cannot lose a stake; the +# plugin shows it through the trade screen's inv id and returns leftovers at login. +[[inventory]] +isServerOnly = true +id = "inv.emirs_arena_stake" +size = 28 +scope = "Perm" +stack = "Normal" +protect = false +runWeight = false diff --git a/.data/raw-cache/server/items.toml b/.data/raw-cache/server/items.toml index 4a3bb87a9..23c49032f 100644 --- a/.data/raw-cache/server/items.toml +++ b/.data/raw-cache/server/items.toml @@ -52832,3 +52832,89 @@ contentGroup = "content.max_cape" id = "obj.skillcape_max_mythical" inherit = "obj.skillcape_max_mythical" contentGroup = "content.max_cape" + + +[[item]] +id = "obj.deal_wrench" +inherit = "obj.deal_wrench" +weaponCategory="Blunt" +[item.params] +"param.attack_anim_stance1"="seq.human_blunt_pound" +"param.attack_sound_stance1"=2567 +"param.attack_anim_stance2"="seq.human_blunt_pound" +"param.attack_sound_stance2"=2567 +"param.attack_anim_stance4"="seq.human_blunt_pound" +"param.attack_sound_stance4"=2567 +"param.defend_anim"="seq.human_blunt_block" + + +[[item]] +id = "obj.deal_wrench_blessed" +inherit = "obj.deal_wrench_blessed" +weaponCategory="Blunt" +[item.params] +"param.attack_anim_stance1"="seq.human_blunt_pound" +"param.attack_sound_stance1"=2567 +"param.attack_anim_stance2"="seq.human_blunt_pound" +"param.attack_sound_stance2"=2567 +"param.attack_anim_stance4"="seq.human_blunt_pound" +"param.attack_sound_stance4"=2567 +"param.defend_anim"="seq.human_blunt_block" + + +[[item]] +id = "obj.roguesden_helm" +inherit = "obj.roguesden_helm" +[item.params] +"param.statreq1_skill"=17 +"param.statreq1_level"=50 +"param.statreq2_skill"=16 +"param.statreq2_level"=50 + + +[[item]] +id = "obj.roguesden_body" +inherit = "obj.roguesden_body" +[item.params] +"param.statreq1_skill"=17 +"param.statreq1_level"=50 +"param.statreq2_skill"=16 +"param.statreq2_level"=50 + + +[[item]] +id = "obj.roguesden_legs" +inherit = "obj.roguesden_legs" +[item.params] +"param.statreq1_skill"=17 +"param.statreq1_level"=50 +"param.statreq2_skill"=16 +"param.statreq2_level"=50 + + +[[item]] +id = "obj.roguesden_gloves" +inherit = "obj.roguesden_gloves" +[item.params] +"param.statreq1_skill"=17 +"param.statreq1_level"=50 +"param.statreq2_skill"=16 +"param.statreq2_level"=50 + + +[[item]] +id = "obj.roguesden_boots" +inherit = "obj.roguesden_boots" +[item.params] +"param.statreq1_skill"=17 +"param.statreq1_level"=50 +"param.statreq2_skill"=16 +"param.statreq2_level"=50 + + +[[item]] +id = "obj.hunting_silent_gloves" +inherit = "obj.hunting_silent_gloves" +[item.params] +"param.statreq1_skill"=21 +"param.statreq1_level"=54 diff --git a/.data/raw-cache/server/npcs.toml b/.data/raw-cache/server/npcs.toml index ca4835076..d28b5c3c2 100644 --- a/.data/raw-cache/server/npcs.toml +++ b/.data/raw-cache/server/npcs.toml @@ -4544,6 +4544,114 @@ contentGroup = "content.dark_crab_fishing_spot" moveRestrict = "NoMove" wanderRange = 0 +# Grand Exchange staff stand at their desks: the clerks, pricing experts, bankers, the bond +# clerk and James do not wander in OSRS (the engine default is a five-tile wander range). +[[npc]] +id = "npc.ge_clerk_1" +inherit = "npc.ge_clerk_1" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.ge_clerk_2" +inherit = "npc.ge_clerk_2" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.ge_clerk_3" +inherit = "npc.ge_clerk_3" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.ge_clerk_4" +inherit = "npc.ge_clerk_4" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.ge_boss" +inherit = "npc.ge_boss" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.ge_expert_ores" +inherit = "npc.ge_expert_ores" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.ge_expert_herbs" +inherit = "npc.ge_expert_herbs" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.ge_expert_runes" +inherit = "npc.ge_expert_runes" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.ge_expert_logs" +inherit = "npc.ge_expert_logs" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.ge_expert_combat" +inherit = "npc.ge_expert_combat" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.banker1_west" +inherit = "npc.banker1_west" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.banker1_east" +inherit = "npc.banker1_east" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.banker2_east" +inherit = "npc.banker2_east" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.deadman_banker_grey_west" +inherit = "npc.deadman_banker_grey_west" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.bond_james_visible" +inherit = "npc.bond_james_visible" +moveRestrict = "NoMove" +wanderRange = 0 + +[[npc]] +id = "npc.clan_clerk" +inherit = "npc.clan_clerk" +moveRestrict = "NoMove" +wanderRange = 0 + + +# Gertrude's Cat: the invisible mewing "crates" around the lumber yard. The timer drives their +# ambient "Mew!"; they stay on their crates so the Search option is always where the sound is. +[[npc]] +id = "npc.kittens_mew" +inherit = "npc.kittens_mew" +timer = 1 +moveRestrict = "NoMove" +wanderRange = 0 + [[npc]] id = "npc.tormented_demon_1" inherit = "npc.tormented_demon_1" diff --git a/.data/raw-cache/server/shops/pvpa_rewards.toml b/.data/raw-cache/server/shops/pvpa_rewards.toml new file mode 100644 index 000000000..05d14b437 --- /dev/null +++ b/.data/raw-cache/server/shops/pvpa_rewards.toml @@ -0,0 +1,31 @@ +[[inventory]] +isServerOnly = true +id = "inv.pvpa_shop_inv" +name = "PvP Arena Rewards" + +scope = "Shared" +stack = "Always" + +sellMultiplier = 1000 +buyMultiplier = 0 +delta = 0 + +size = 3 + +protect = false +runWeight = false +restock = true +allStock = false +placeholders = false + +# Prices are in PvP Arena reward points, set by the Emir's Arena plugin's cost override rather +# than the objs' coin values. +[[inventory.stock]] +obj = "obj.blighted_sack_surge" +count = 5000 +restockCycles = 10 + +[[inventory.stock]] +obj = "obj.pvpa_imbuing_scroll" +count = 100 +restockCycles = 10 diff --git a/.data/raw-cache/server/varn.toml b/.data/raw-cache/server/varn.toml index 36487abf0..fd7578b57 100644 --- a/.data/raw-cache/server/varn.toml +++ b/.data/raw-cache/server/varn.toml @@ -34,6 +34,9 @@ id = "varn.gorilla_protect_hits" [[varn]] id = "varn.gorilla_miss_streak" +[[varn]] +id = "varn.npc_poison_severity" + [[varn]] id = "varn.gorilla_protect_switching" diff --git a/.gitignore b/.gitignore index b70d4f00a..499390418 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ bin/ .DS_Store /api/tables/src/main/kotlin/org/rsmod/api/table game.yml +xp-rates.yml /.idea /api/generated CLAUDE.md diff --git a/api/account/src/main/kotlin/org/rsmod/api/account/character/appearance/CharacterAppearancePersistence.kt b/api/account/src/main/kotlin/org/rsmod/api/account/character/appearance/CharacterAppearancePersistence.kt new file mode 100644 index 000000000..571e64b69 --- /dev/null +++ b/api/account/src/main/kotlin/org/rsmod/api/account/character/appearance/CharacterAppearancePersistence.kt @@ -0,0 +1,84 @@ +package org.rsmod.api.account.character.appearance + +import org.rsmod.game.entity.player.Appearance + +/** + * Saves and restores a character's [Appearance]. + * + * The `account_characters` table has no appearance columns and its schema lives in an external + * module, so appearance is stored in `character_attrs` - the generic key/json store the character + * save already writes - rather than in dedicated columns. + * + * Values are read straight off [Appearance] when saving and written back before the player is + * registered on login, so nothing that edits appearance has to remember to persist it. + */ +public object CharacterAppearancePersistence { + private const val BODY_TYPE_KEY: String = "appearance_body_type" + private const val PRONOUN_KEY: String = "appearance_pronoun" + private const val IDENT_KIT_KEY: String = "appearance_ident_kit" + private const val COLOURS_KEY: String = "appearance_colours" + + /** + * The attribute keys this object owns. + * + * They are written by [encode] instead of by an [org.rsmod.api.attr.AttributeKey], so the + * generic attribute restore skips them and [Appearance] stays the single source of truth. + */ + public val persistenceKeys: Set = + setOf(BODY_TYPE_KEY, PRONOUN_KEY, IDENT_KIT_KEY, COLOURS_KEY) + + public fun encode(appearance: Appearance): Map = + mapOf( + BODY_TYPE_KEY to appearance.bodyType, + PRONOUN_KEY to appearance.pronoun, + IDENT_KIT_KEY to appearance.identKitSnapshot().map(Short::toInt), + // Colours are kept in a `ByteArray`, so anything past 127 comes back signed. + COLOURS_KEY to appearance.coloursSnapshot().map { it.toInt() and 0xFF }, + ) + + /** + * Applies the appearance stored in [attrs] to [appearance], leaving the engine defaults in + * place for anything missing (a character created before appearance was persisted) or out of + * range (a hand-edited or corrupt row). Out-of-range values would otherwise throw out of + * [Appearance.setIdentKit] / [Appearance.setColour] and fail the login. + */ + public fun restore(appearance: Appearance, attrs: Map) { + val bodyType = (attrs[BODY_TYPE_KEY] as? Number)?.toInt() + if (bodyType == Appearance.BODY_TYPE_A || bodyType == Appearance.BODY_TYPE_B) { + appearance.bodyType = bodyType + } + + val pronoun = (attrs[PRONOUN_KEY] as? Number)?.toInt() + if (pronoun != null && pronoun in Appearance.PRONOUN_HE..Appearance.PRONOUN_THEY) { + appearance.pronoun = pronoun + } + + val identKit = attrs[IDENT_KIT_KEY] as? List<*> + if (identKit != null) { + val slotCount = appearance.identKitSnapshot().size + for ((slot, value) in identKit.withIndex()) { + if (slot >= slotCount) { + break + } + val style = (value as? Number)?.toInt() ?: continue + if (style == Appearance.NO_IDENT_KIT || style in 0..65535) { + appearance.setIdentKit(slot, style) + } + } + } + + val colours = attrs[COLOURS_KEY] as? List<*> + if (colours != null) { + val indexCount = appearance.coloursSnapshot().size + for ((index, value) in colours.withIndex()) { + if (index >= indexCount) { + break + } + val colour = (value as? Number)?.toInt() ?: continue + if (colour in 0..255) { + appearance.setColour(index, colour) + } + } + } + } +} diff --git a/api/account/src/main/kotlin/org/rsmod/api/account/character/main/CharacterAccountApplier.kt b/api/account/src/main/kotlin/org/rsmod/api/account/character/main/CharacterAccountApplier.kt index 8fa952c42..50b47a18c 100644 --- a/api/account/src/main/kotlin/org/rsmod/api/account/character/main/CharacterAccountApplier.kt +++ b/api/account/src/main/kotlin/org/rsmod/api/account/character/main/CharacterAccountApplier.kt @@ -4,6 +4,7 @@ import jakarta.inject.Inject import java.time.LocalDateTime import org.rsmod.api.account.character.CharacterAccountLoginSegment import org.rsmod.api.account.character.CharacterDataStage +import org.rsmod.api.account.character.appearance.CharacterAppearancePersistence import org.rsmod.game.entity.Player import org.rsmod.map.CoordGrid @@ -28,7 +29,11 @@ public class CharacterAccountApplier @Inject constructor() : player.trustedDevices = d.trustedDevices.toMutableList() player.twoFactorAuth = d.twoFactorAuth player.lastKnownDevice = d.trustedDevices.maxByOrNull { it.verifiedAt }?.deviceId - player.members = c.members + // Every account is a member: all of the server's content is members content, and the + // client only counts rune pouch and combination runes (and draws member spells as + // castable) when the login response flags the player as a member. The stored flag is + // ignored and written back as `true` on the next save. + player.members = true player.username = d.accountName player.displayName = c.displayName ?: "" player.previousDisplayName = c.previousDisplayName ?: "" @@ -40,9 +45,14 @@ public class CharacterAccountApplier @Inject constructor() : player.xpRate = c.xpRate player.lastLogin = LocalDateTime.now() player.vars.backing.putAll(c.varps) - if (c.attrs.isNotEmpty()) { - player.attr.putAllFromPersistence(c.attrs) + val attrs = c.attrs + // Appearance is stored under attribute keys but owned by `player.appearance`; keeping it + // out of the attribute map leaves a single source of truth for the next save. + val nonAppearanceAttrs = attrs - CharacterAppearancePersistence.persistenceKeys + if (nonAppearanceAttrs.isNotEmpty()) { + player.attr.putAllFromPersistence(nonAppearanceAttrs) } + CharacterAppearancePersistence.restore(player.appearance, attrs) player.modLevel = d.rights } } diff --git a/api/account/src/main/kotlin/org/rsmod/api/account/character/main/CharacterAccountRepository.kt b/api/account/src/main/kotlin/org/rsmod/api/account/character/main/CharacterAccountRepository.kt index e975d82a8..404b8e4d1 100644 --- a/api/account/src/main/kotlin/org/rsmod/api/account/character/main/CharacterAccountRepository.kt +++ b/api/account/src/main/kotlin/org/rsmod/api/account/character/main/CharacterAccountRepository.kt @@ -2,14 +2,14 @@ package org.rsmod.api.account.character.main import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper +import dev.openrune.ServerCacheManager +import dev.openrune.types.varp.VarpLifetime import dev.or2.central.account.AccountData import dev.or2.central.account.CharacterData import dev.or2.central.account.Rights import dev.or2.central.account.TrustedDeviceData import dev.or2.central.account.TwoFactorAuthData import dev.or2.sql.OpenRuneSql -import dev.openrune.ServerCacheManager -import dev.openrune.types.varp.VarpLifetime import jakarta.inject.Inject import java.sql.Statement import java.sql.Timestamp @@ -18,12 +18,12 @@ import java.time.temporal.ChronoUnit import kotlin.math.roundToInt import org.rsmod.api.account.character.CharacterAccountLoginSegment import org.rsmod.api.account.character.CharacterMetadataList +import org.rsmod.api.account.character.appearance.CharacterAppearancePersistence import org.rsmod.api.account.persistence.GamePersistenceRscmKeys import org.rsmod.api.db.DatabaseConnection import org.rsmod.api.db.util.getIntOrNull import org.rsmod.api.db.util.getLocalDateTime import org.rsmod.api.db.util.getStringOrNull -import org.rsmod.api.db.util.setNullableInt import org.rsmod.api.db.util.setNullableString import org.rsmod.api.parsers.json.Json import org.rsmod.game.entity.Player @@ -221,7 +221,10 @@ constructor( ServerCacheManager.getVarp(id)?.scope == VarpLifetime.Perm } - val persistentAttrs = player.attr.toPersistentMap() + // Appearance has no columns of its own; it rides along in `character_attrs`. + val persistentAttrs = + player.attr.toPersistentMap() + + CharacterAppearancePersistence.encode(player.appearance) connection.prepareStatement(sql).use { it.setInt(1, player.x) diff --git a/api/account/src/test/kotlin/org/rsmod/api/account/character/appearance/CharacterAppearancePersistenceTest.kt b/api/account/src/test/kotlin/org/rsmod/api/account/character/appearance/CharacterAppearancePersistenceTest.kt new file mode 100644 index 000000000..9a0d2d536 --- /dev/null +++ b/api/account/src/test/kotlin/org/rsmod/api/account/character/appearance/CharacterAppearancePersistenceTest.kt @@ -0,0 +1,73 @@ +package org.rsmod.api.account.character.appearance + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.rsmod.game.entity.player.Appearance + +class CharacterAppearancePersistenceTest { + @Test + fun `restores an appearance saved through the attribute json`() { + val saved = + Appearance().apply { + bodyType = Appearance.BODY_TYPE_B + pronoun = Appearance.PRONOUN_SHE + setIdentKit(0, 45) + setIdentKit(1, Appearance.NO_IDENT_KIT) + setIdentKit(2, 56) + // Past `Byte.MAX_VALUE`: colours are stored signed and must come back unsigned. + setColour(0, 200) + setColour(4, 5) + } + + val restored = Appearance() + CharacterAppearancePersistence.restore(restored, roundTripThroughJson(saved)) + + assertEquals(Appearance.BODY_TYPE_B, restored.bodyType) + assertEquals(Appearance.PRONOUN_SHE, restored.pronoun) + assertEquals(saved.identKitSnapshot(), restored.identKitSnapshot()) + assertEquals(saved.coloursSnapshot(), restored.coloursSnapshot()) + } + + @Test + fun `keeps engine defaults when nothing was saved`() { + val defaults = Appearance() + + val restored = Appearance() + CharacterAppearancePersistence.restore(restored, emptyMap()) + + assertEquals(defaults.bodyType, restored.bodyType) + assertEquals(defaults.pronoun, restored.pronoun) + assertEquals(defaults.identKitSnapshot(), restored.identKitSnapshot()) + assertEquals(defaults.coloursSnapshot(), restored.coloursSnapshot()) + } + + @Test + fun `ignores out of range values instead of failing the login`() { + val defaults = Appearance() + val corrupt = + mapOf( + "appearance_body_type" to 7, + "appearance_pronoun" to -1, + "appearance_ident_kit" to listOf(-9, 999_999, "hair", 26, 33, 36, 42, 1), + "appearance_colours" to listOf(300, 3, 2, 0, 0, 1), + ) + + val restored = Appearance() + CharacterAppearancePersistence.restore(restored, corrupt) + + assertEquals(defaults.bodyType, restored.bodyType) + assertEquals(defaults.pronoun, restored.pronoun) + assertEquals(defaults.identKitSnapshot(), restored.identKitSnapshot()) + assertEquals(defaults.coloursSnapshot(), restored.coloursSnapshot()) + } + + /** Mirrors what `CharacterAccountRepository` writes to, and reads back from, `character_attrs`. */ + private fun roundTripThroughJson(appearance: Appearance): Map { + val mapper = ObjectMapper() + return CharacterAppearancePersistence.encode(appearance).mapValues { (_, value) -> + mapper.readValue(mapper.writeValueAsString(value), object : TypeReference() {}) + } + } +} diff --git a/api/combat/combat-commons/src/main/kotlin/org/rsmod/api/combat/commons/CombatEffects.kt b/api/combat/combat-commons/src/main/kotlin/org/rsmod/api/combat/commons/CombatEffects.kt index d8c3b1438..9488f8b1f 100644 --- a/api/combat/combat-commons/src/main/kotlin/org/rsmod/api/combat/commons/CombatEffects.kt +++ b/api/combat/combat-commons/src/main/kotlin/org/rsmod/api/combat/commons/CombatEffects.kt @@ -13,23 +13,49 @@ public object CombatEffects { } private const val FREEZE_IMMUNITY_TICKS = 5 + private const val FREEZE_TIMER = "timer.combat_freeze" + private const val STUN_TIMER = "timer.combat_stun" + /** + * Freezes [target] in place for [ticks], after which they are immune to further freezes for + * a short while. A running freeze is not replaced; a stun ([stun]) that is running does not + * block a freeze, the two simply overlap. + */ public fun freeze(target: Player, ticks: Int) { - if (target.isFrozen) return + if (FREEZE_TIMER in target.timerMap) return if (target.freezeImmune) return target.frozen = true target.routeDestination.clear() - target.timer("timer.combat_freeze", ticks) + target.timer(FREEZE_TIMER, ticks) target.mes("You have been frozen!", ChatType.Spam) } public fun unfreeze(target: Player) { - target.frozen = false + // A stun that is still running keeps the player held. + target.frozen = STUN_TIMER in target.timerMap target.freezeImmune = true - target.clearTimer("timer.combat_freeze") + target.clearTimer(FREEZE_TIMER) target.timer("timer.combat_freeze_immunity", FREEZE_IMMUNITY_TICKS) } + /** + * Stuns [target] for [ticks]: they cannot move or attack, as with the dragon spear's Shove. + * Unlike a freeze, a stun ignores freeze immunity, grants none when it ends, and a new stun + * restarts the timer. + */ + public fun stun(target: Player, ticks: Int) { + target.frozen = true + target.routeDestination.clear() + target.actionDelay = maxOf(target.actionDelay, target.currentMapClock + ticks) + target.timer(STUN_TIMER, ticks) + } + + public fun unstun(target: Player) { + target.clearTimer(STUN_TIMER) + // A freeze that is still running keeps the player held. + target.frozen = FREEZE_TIMER in target.timerMap + } + public fun clearFreezeImmunity(target: Player) { target.freezeImmune = false } diff --git a/api/combat/combat-commons/src/main/kotlin/org/rsmod/api/combat/commons/hook/PvPAttackRestrictionHook.kt b/api/combat/combat-commons/src/main/kotlin/org/rsmod/api/combat/commons/hook/PvPAttackRestrictionHook.kt new file mode 100644 index 000000000..0a954738e --- /dev/null +++ b/api/combat/combat-commons/src/main/kotlin/org/rsmod/api/combat/commons/hook/PvPAttackRestrictionHook.kt @@ -0,0 +1,22 @@ +package org.rsmod.api.combat.commons.hook + +import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.game.entity.Player + +/** + * Asked once the engine has resolved *how* [attacker] is about to hit [target] (melee, ranged, a + * spell or a staff attack, and whether a special attack is queued). A non-null message cancels the + * attack and is shown to the attacker. + * + * Unlike `PvPAttackValidateHook`, which decides whether the two players may fight at all, this hook + * lets content restrict the combat style within a fight that is otherwise allowed - duel rules such + * as "No Magic" or "No Special Attacks". + */ +public fun interface PvPAttackRestrictionHook { + public fun restriction( + attacker: Player, + target: Player, + attack: CombatAttack.PlayerAttack, + special: Boolean, + ): String? +} diff --git a/api/combat/combat-commons/src/main/kotlin/org/rsmod/api/combat/commons/player/PlayerExtensions.kt b/api/combat/combat-commons/src/main/kotlin/org/rsmod/api/combat/commons/player/PlayerExtensions.kt index 8373c997e..c7d99b083 100644 --- a/api/combat/combat-commons/src/main/kotlin/org/rsmod/api/combat/commons/player/PlayerExtensions.kt +++ b/api/combat/combat-commons/src/main/kotlin/org/rsmod/api/combat/commons/player/PlayerExtensions.kt @@ -57,16 +57,28 @@ public fun ProtectedAccess.combatRetaliate(uid: PlayerUid, flinchDelay: Int) { opPlayer2(source) } +/** + * Queues an npc's hit on this player. Melee and ranged hits play the defend animation (ranged + * after [defendClientDelay] client cycles, when the projectile lands); magic hits never do, as + * in the real game, so a spell cannot interrupt the target's own attack animation. + * + * @param defendClientDelay Client cycles (20ms) to wait before the defend animation plays. + */ public fun Player.finishNpcHit( source: Npc, delay: Int, type: HitType, damage: Int, modifier: PlayerHitModifier, + defendClientDelay: Int = 0, ): Hit { - queueCombatRetaliate(source) + // Queued before the hit and with the same delay: the hit's strong queue interrupts whatever + // the player is doing when it lands, and the retaliation queued alongside it fights back. + queueCombatRetaliate(source, delay.coerceAtLeast(1)) val hit = queueHit(source, delay, type, damage, modifier) - combatPlayDefendAnim() + if (type != HitType.Magic) { + combatPlayDefendAnim(defendClientDelay) + } return hit } diff --git a/api/combat/combat-manager/src/main/kotlin/org/rsmod/api/combat/manager/MagicRuneManager.kt b/api/combat/combat-manager/src/main/kotlin/org/rsmod/api/combat/manager/MagicRuneManager.kt index 77e8f81c4..c5f19dad5 100644 --- a/api/combat/combat-manager/src/main/kotlin/org/rsmod/api/combat/manager/MagicRuneManager.kt +++ b/api/combat/combat-manager/src/main/kotlin/org/rsmod/api/combat/manager/MagicRuneManager.kt @@ -1,6 +1,5 @@ package org.rsmod.api.combat.manager -import dev.openrune.definition.type.VarBitType import dev.openrune.types.ItemServerType import jakarta.inject.Inject import kotlin.collections.any @@ -9,6 +8,7 @@ import org.rsmod.api.combat.commons.magic.MagicSpell import org.rsmod.api.combat.commons.magic.Spellbook import org.rsmod.api.config.refs.BaseParams import org.rsmod.api.invtx.invDelAll +import org.rsmod.api.player.cheat.adminInfiniteRunes import org.rsmod.api.player.output.mes import org.rsmod.api.player.stat.magicLvl import org.rsmod.api.player.vars.VarPlayerIntMapSetter @@ -161,9 +161,13 @@ constructor( consume += invObj.copy(count = required) } - val transaction = player.invDelAll(player.inv, consume, strict = true) - if (!transaction.success) { - return CastResult.Failure.NotEnoughInvObj + // A cast paid for entirely from a rune pouch has nothing to delete from the inventory. + // An empty transaction reports failure, so only run one when there are objs to consume. + if (consume.isNotEmpty()) { + val transaction = player.invDelAll(player.inv, consume, strict = true) + if (!transaction.success) { + return CastResult.Failure.NotEnoughInvObj + } } for (source in varbitSources) { @@ -180,6 +184,12 @@ constructor( } public fun validateSpell(player: Player, spell: MagicSpell): List { + // Admin "infinite runes" cheat: treat every obj requirement as an unlimited source so + // nothing is consumed. Level and spellbook requirements are handled by `validateAccess` + // and remain in effect. + if (player.adminInfiniteRunes) { + return listOf(MagicRunes.Validation.Valid.Unlimited) + } val runePack = validateRunePack(player, spell.obj) return if (runePack != null) { listOf(runePack) diff --git a/api/combat/combat-manager/src/main/kotlin/org/rsmod/api/combat/manager/PlayerAttackManager.kt b/api/combat/combat-manager/src/main/kotlin/org/rsmod/api/combat/manager/PlayerAttackManager.kt index 04c761eaf..163ccf329 100644 --- a/api/combat/combat-manager/src/main/kotlin/org/rsmod/api/combat/manager/PlayerAttackManager.kt +++ b/api/combat/combat-manager/src/main/kotlin/org/rsmod/api/combat/manager/PlayerAttackManager.kt @@ -41,7 +41,6 @@ import org.rsmod.api.player.interact.NpcTInteractions import org.rsmod.api.player.interact.PlayerInteractions import org.rsmod.api.player.interact.PlayerTInteractions import org.rsmod.api.player.ironman.shouldBlockNpcCombatXp -import org.rsmod.api.player.output.soundSynth import org.rsmod.api.player.protect.clearPendingAction import org.rsmod.api.player.stat.hitpoints import org.rsmod.api.player.stat.statAdvance @@ -59,6 +58,9 @@ import org.rsmod.game.type.getInvObj import org.rsmod.game.type.getOrNull import org.rsmod.map.CoordGrid +/** Radius, in tiles, that weapon and spell attack sounds are heard within. */ +private const val ATTACK_SOUND_RADIUS: Int = 10 + public class PlayerAttackManager @Inject constructor( @@ -206,7 +208,7 @@ constructor( weapon?.paramOrNull(soundParam) ?: SynthType(defaultSound.asRSCM(RSCMType.SYNTH)) player.anim(RSCM.getReverseMapping(RSCMType.SEQ, attackAnim.id), priority = 6) - player.soundSynth(attackSound) + playAttackSound(player, attackSound) } /** @@ -224,10 +226,18 @@ constructor( val attackAnim = weapon.paramOrNull(params.attack_anim_stance1) ?: return false player.anim(RSCM.getReverseMapping(RSCMType.SEQ, attackAnim.id), priority = 6) val attackSound = weapon.paramOrNull(params.attack_sound_stance1) - attackSound?.let(player::soundSynth) + attackSound?.let { playAttackSound(player, it) } return true } + /** + * Plays a weapon attack sound as an area sound around [player], so that nearby players hear + * the attack as well as the attacker - the official behaviour for combat sounds. + */ + private fun playAttackSound(player: Player, synth: SynthType) { + worldRepo.soundArea(player, synth.id, radius = ATTACK_SOUND_RADIUS) + } + /** * Calculates and grants combat experience based on the given [attack], [damage], and the * [target]'s predefined combat xp multiplier. @@ -1553,7 +1563,7 @@ constructor( modifier = npcHitModifier, sourceSecondary = spell, ) - target.combatPlayDefendAnim(clientDelay) + // Targets never play their defend animation for a spell, only for melee and ranged. return hit } @@ -1581,7 +1591,7 @@ constructor( sourceSecondary = spell, ) notifyPlayerHit(source, target) - target.combatPlayDefendAnim(clientDelay) + // Targets never play their defend animation for a spell, only for melee and ranged. return hit } @@ -1671,7 +1681,14 @@ constructor( hitSound: String?, ) { if (castSound != null) { - source.soundSynth(castSound) + soundArea( + source = source.coords, + synth = castSound, + delay = 0, + loops = 1, + radius = ATTACK_SOUND_RADIUS, + size = 0, + ) } if (hitSpot != null) { @@ -1732,8 +1749,8 @@ constructor( * * @param clientDelay The delay in client cycles (`20ms` per cycle) before the splash spotanim * and sound are played on [target]. Typically derived from the projectile's metadata. - * @param castSound The sound to play immediately. Plays as a `soundarea` if [target] is a - * [Player], or a `soundsynth` if [target] is an [Npc]. If `null`, no cast sound is played. + * @param castSound The sound to play immediately as a `soundarea` around [source]. If `null`, + * no cast sound is played. * @param soundRadius The radius to use for [castSound] when played as a `soundarea`. */ public fun playMagicSplashFx( @@ -1768,7 +1785,14 @@ constructor( castSound: String?, ) { if (castSound != null) { - source.soundSynth(castSound) + soundArea( + source = source.coords, + synth = castSound, + delay = 0, + loops = 1, + radius = ATTACK_SOUND_RADIUS, + size = 0, + ) } target.spotanim("spotanim.failedspell_impact", delay = clientDelay, height = 124) soundArea( diff --git a/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/CombatModule.kt b/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/CombatModule.kt index b22182c15..75dc97302 100644 --- a/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/CombatModule.kt +++ b/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/CombatModule.kt @@ -1,5 +1,6 @@ package org.rsmod.api.combat +import org.rsmod.api.combat.commons.hook.PvPAttackRestrictionHook import org.rsmod.plugin.module.PluginModule internal class CombatModule : PluginModule() { @@ -7,5 +8,6 @@ internal class CombatModule : PluginModule() { bindInstance() bindInstance() bindInstance() + newSetBinding() } } diff --git a/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/FreezeTimerScript.kt b/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/FreezeTimerScript.kt index 76eebfc61..478bf165e 100644 --- a/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/FreezeTimerScript.kt +++ b/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/FreezeTimerScript.kt @@ -13,5 +13,8 @@ public class FreezeTimerScript : PluginScript() { onPlayerTimer("timer.combat_freeze_immunity") { CombatEffects.clearFreezeImmunity(player) } + onPlayerTimer("timer.combat_stun") { + CombatEffects.unstun(player) + } } } diff --git a/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/NvPCombat.kt b/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/NvPCombat.kt index 41184ce7a..d24856d4c 100644 --- a/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/NvPCombat.kt +++ b/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/NvPCombat.kt @@ -1,6 +1,7 @@ package org.rsmod.api.combat import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCM.asRSCM import dev.openrune.rscm.RSCMType import dev.openrune.types.ProjAnimType import jakarta.inject.Inject @@ -18,8 +19,8 @@ import org.rsmod.api.npc.access.StandardNpcAccess import org.rsmod.api.npc.isInCombat import org.rsmod.api.player.hit.modifier.PlayerHitModifier import org.rsmod.api.player.isValidTarget -import org.rsmod.api.player.output.soundSynth import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.game.entity.Npc import org.rsmod.game.entity.Player import org.rsmod.game.hit.HitType import org.rsmod.game.proj.ProjAnim @@ -58,20 +59,30 @@ constructor( return } playAttackFx(target) - val hitDelay = spawnProjectile(target) + val flight = spawnProjectile(target) val successfulHit = accuracy.rollRangedAccuracy(npc, target, random) val damage = if (successfulHit) random.of(0..maxHits.getRangedMaxHit(npc, target)) else 0 - finishAttack(target, delay = hitDelay, type = HitType.Ranged, damage = damage) + finishAttack( + target, + delay = flight.serverCycles, + type = HitType.Ranged, + damage = damage, + defendClientDelay = flight.clientCycles, + ) } private fun StandardNpcAccess.attackMagic(target: Player, attack: CombatAttack.NpcMagic) { if (!beginAttack(target)) { return } - playAttackFx(target) - val hitDelay = spawnProjectile(target) + val spell = npc.spellFx() + val playedAttackSound = playAttackFx(target) + if (!playedAttackSound && spell != null) { + worldRepo.soundArea(npc, spell.castSound, radius = ATTACK_SOUND_RADIUS) + } + val flight = spawnProjectile(target) // A fixed `maxHit` (e.g. from a scripted attack) takes precedence; otherwise derive it from // the npc's magic level and magic strength. @@ -79,7 +90,54 @@ constructor( val successfulHit = accuracy.rollMagicAccuracy(npc, target, random) val damage = if (successfulHit) random.of(0..maxHit) else 0 - finishAttack(target, delay = hitDelay, type = HitType.Magic, damage = damage) + if (successfulHit) { + spell?.playImpact(target, flight) + } else if (spell != null) { + playSplash(target, flight) + } + + finishAttack( + target, + delay = flight.serverCycles, + type = HitType.Magic, + damage = damage, + defendClientDelay = flight.clientCycles, + ) + } + + /** + * The graphics and sounds of the spell an npc casts, worked out from its `proj_travel` param: + * a projectile named `spotanim._travel` belongs to the spell whose casting sound is + * `synth._cast_and_fire`, whose landing graphic is `spotanim._impact` and whose + * landing sound is `synth._hit`. Returns `null` when the npc has no projectile or the + * names do not exist, so only real spells gain the extra effects. + */ + private fun Npc.spellFx(): SpellFx? { + val travel = visType.paramOrNull(params.proj_travel) ?: return null + val travelName = RSCM.getReverseMapping(RSCMType.SPOTANIM, travel.id) + if (!travelName.endsWith(TRAVEL_SUFFIX)) { + return null + } + val spell = travelName.removePrefix("spotanim.").removeSuffix(TRAVEL_SUFFIX) + return spellFxCache.getOrPut(spell) { + val castSound = "synth.${spell}_cast_and_fire" + val hitSound = "synth.${spell}_hit" + val impact = "spotanim.${spell}_impact" + val known = listOf(castSound, hitSound, impact).all(::gamevalExists) + if (known) SpellFx(castSound, hitSound, impact) else null + } + } + + private fun gamevalExists(name: String): Boolean = runCatching { name.asRSCM() }.isSuccess + + private fun SpellFx.playImpact(target: Player, flight: ProjectileFlight) { + target.spotanim(impact, delay = flight.clientCycles, height = IMPACT_HEIGHT) + worldRepo.soundArea(target, hitSound, delay = flight.clientCycles, radius = ATTACK_SOUND_RADIUS) + } + + private fun playSplash(target: Player, flight: ProjectileFlight) { + target.spotanim(SPLASH_SPOTANIM, delay = flight.clientCycles, height = IMPACT_HEIGHT) + worldRepo.soundArea(target, SPLASH_SOUND, delay = flight.clientCycles, radius = ATTACK_SOUND_RADIUS) } /** @@ -108,11 +166,16 @@ constructor( return true } - private fun StandardNpcAccess.playAttackFx(target: Player) { + /** Plays the npc's attack animation and sound. Returns `true` if it had a sound to play. */ + private fun StandardNpcAccess.playAttackFx(target: Player): Boolean { val attackAnim = RSCM.getReverseMapping(RSCMType.SEQ, npc.visType.param(params.attack_anim).id) anim(attackAnim) - npc.visType.paramOrNull(params.attack_sound)?.let(target::soundSynth) + // Attack sounds are area sounds so that everyone nearby hears the npc strike, not only its + // target. + val attackSound = npc.visType.paramOrNull(params.attack_sound) ?: return false + worldRepo.soundArea(npc, attackSound.id, radius = ATTACK_SOUND_RADIUS) + return true } /** @@ -121,25 +184,31 @@ constructor( * The projectile graphic comes from the npc's `proj_travel` (spotanim) param; its flight is * described by the `proj_type` (projanim) param when present, otherwise * [DEFAULT_PROJECTILE_TYPE]. When the npc defines no `proj_travel`, no projectile is spawned - * and the hit lands after [DEFAULT_PROJECTILE_HIT_DELAY] cycles. + * and the hit lands after [DEFAULT_PROJECTILE_FLIGHT]. */ - private fun StandardNpcAccess.spawnProjectile(target: Player): Int { + private fun StandardNpcAccess.spawnProjectile(target: Player): ProjectileFlight { val travelSpot = - npc.visType.paramOrNull(params.proj_travel) ?: return DEFAULT_PROJECTILE_HIT_DELAY + npc.visType.paramOrNull(params.proj_travel) ?: return DEFAULT_PROJECTILE_FLIGHT val projType = npc.visType.paramOrNull(params.proj_type) ?: DEFAULT_PROJECTILE_TYPE val proj = ProjAnim.fromNpcToPlayer(npc, target, travelSpot.id, projType) worldRepo.projAnim(proj) - return proj.serverCycles + return ProjectileFlight(proj.serverCycles, proj.clientCycles) } + /** How long a projectile takes to land, in server cycles and in client cycles (20ms). */ + private data class ProjectileFlight(val serverCycles: Int, val clientCycles: Int) + + private data class SpellFx(val castSound: String, val hitSound: String, val impact: String) + private fun StandardNpcAccess.finishAttack( target: Player, delay: Int, type: HitType, damage: Int, + defendClientDelay: Int = 0, ) { setAttackVars(target) - target.finishNpcHit(npc, delay, type, damage, hitModifier) + target.finishNpcHit(npc, delay, type, damage, hitModifier, defendClientDelay) } private fun canAttack(target: Player): Boolean { @@ -153,9 +222,19 @@ constructor( target.aggressiveNpc = npc.uid } + private val spellFxCache = HashMap() + private companion object { private const val MELEE_HIT_DELAY = 1 - private const val DEFAULT_PROJECTILE_HIT_DELAY = 2 + private const val ATTACK_SOUND_RADIUS = 10 + private const val CLIENT_CYCLES_PER_SERVER_CYCLE = 30 + private val DEFAULT_PROJECTILE_FLIGHT = + ProjectileFlight(serverCycles = 2, clientCycles = 2 * CLIENT_CYCLES_PER_SERVER_CYCLE) + + private const val TRAVEL_SUFFIX = "_travel" + private const val IMPACT_HEIGHT = 124 + private const val SPLASH_SPOTANIM = "spotanim.failedspell_impact" + private const val SPLASH_SOUND = "synth.spellfail" /** * Standard projectile arc used when an npc provides a `proj_travel` graphic but no diff --git a/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/PvPCombat.kt b/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/PvPCombat.kt index 9f9131368..78bae92e8 100644 --- a/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/PvPCombat.kt +++ b/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/PvPCombat.kt @@ -4,6 +4,7 @@ import dev.openrune.rscm.RSCM import dev.openrune.rscm.RSCMType import jakarta.inject.Inject import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.api.combat.commons.hook.PvPAttackRestrictionHook import org.rsmod.api.combat.manager.PlayerAttackManager import org.rsmod.api.combat.manager.RangedAmmoManager import org.rsmod.api.combat.player.activateMagicSpecial @@ -45,6 +46,7 @@ constructor( private val spellsReg: SpellAttackRegistry, private val skullHooks: Set, private val specialAttackHooks: Set, + private val attackRestrictionHooks: Set, ) { suspend fun attack(access: ProtectedAccess, target: Player, attack: CombatAttack.PlayerAttack) { when (attack) { @@ -55,6 +57,27 @@ constructor( } } + /** + * Returns `false` (and stops combat) when a registered [PvPAttackRestrictionHook] forbids this + * particular attack against [target]. + */ + private fun ProtectedAccess.isAttackAllowed( + target: Player, + attack: CombatAttack.PlayerAttack, + ): Boolean { + val special = specialAttackType != SpecialAttackType.None + for (hook in attackRestrictionHooks) { + val message = hook.restriction(player, target, attack, special) ?: continue + if (special) { + specialAttackType = SpecialAttackType.None + } + manager.stopCombat(player) + mes(message) + return false + } + return true + } + private fun ProtectedAccess.applyPkVars(target: Player) { for (hook in skullHooks) { hook.onPlayerAttack(player, target) @@ -72,6 +95,9 @@ constructor( if (!canAttack(target)) { return } + if (!isAttackAllowed(target, attack)) { + return + } if (manager.isAttackDelayed(player)) { manager.continueCombat(player, target) @@ -133,6 +159,9 @@ constructor( if (!canAttack(target)) { return } + if (!isAttackAllowed(target, attack)) { + return + } if (manager.isAttackDelayed(player)) { manager.continueCombat(player, target) @@ -274,6 +303,9 @@ constructor( if (!canAttack(target)) { return } + if (!isAttackAllowed(target, attack)) { + return + } if (manager.isAttackDelayed(player)) { manager.continueCombat(player, target, attack.spell) @@ -302,6 +334,9 @@ constructor( if (!canAttack(target)) { return } + if (!isAttackAllowed(target, attack)) { + return + } if (manager.isAttackDelayed(player)) { manager.continueCombat(player, target) diff --git a/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/scripts/PvPCombatScript.kt b/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/scripts/PvPCombatScript.kt index 41803a772..b14234e48 100644 --- a/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/scripts/PvPCombatScript.kt +++ b/api/combat/combat-scripts/src/main/kotlin/org/rsmod/api/combat/scripts/PvPCombatScript.kt @@ -14,6 +14,7 @@ import org.rsmod.api.combat.weapon.styles.AttackStyles import org.rsmod.api.combat.weapon.types.AttackTypes import org.rsmod.api.death.PvPAttackValidateHook import org.rsmod.api.death.PvPAttackValidateResult +import org.rsmod.api.player.clearInteractionRoute import org.rsmod.api.player.isInPvpCombat import org.rsmod.api.player.protect.ProtectedAccess import org.rsmod.api.player.righthand @@ -136,6 +137,14 @@ constructor( clearPendingAction() return false } + is PvPAttackValidateResult.Silent -> { + // The hook acted on the click itself (it may have opened an interface), so + // only drop the attack interaction: no `clearPendingAction`, which would close + // whatever the hook just opened. + player.clearInteractionRoute() + player.resetFaceEntity() + return false + } } } diff --git a/api/core/src/main/kotlin/org/rsmod/api/core/module/PlayerModule.kt b/api/core/src/main/kotlin/org/rsmod/api/core/module/PlayerModule.kt index 4947576e2..f2d599abe 100644 --- a/api/core/src/main/kotlin/org/rsmod/api/core/module/PlayerModule.kt +++ b/api/core/src/main/kotlin/org/rsmod/api/core/module/PlayerModule.kt @@ -2,13 +2,17 @@ package org.rsmod.api.core.module import com.google.inject.Provider import jakarta.inject.Inject -import org.rsmod.api.player.music.MusicPlayer import org.rsmod.api.player.hook.GroundItemDropResolver import org.rsmod.api.player.hook.PlayerGroundItemDropHook +import org.rsmod.api.player.hook.PlayerObjTakeRedirectHook +import org.rsmod.api.player.hook.PlayerObjTakeRedirector import org.rsmod.api.player.hook.PlayerObjTakeValidateHook import org.rsmod.api.player.hook.PlayerObjTakeValidator +import org.rsmod.api.player.hook.PlayerRestrictionHook +import org.rsmod.api.player.hook.PlayerRestrictions import org.rsmod.api.player.hook.PlayerTeleportValidateHook import org.rsmod.api.player.hook.PlayerTeleportValidator +import org.rsmod.api.player.music.MusicPlayer import org.rsmod.api.player.protect.ProtectedAccessContextFactory import org.rsmod.api.player.protect.ProtectedAccessLauncher import org.rsmod.game.entity.PlayerList @@ -20,12 +24,16 @@ public object PlayerModule : ExtendedModule() { newSetBinding() newSetBinding() newSetBinding() + newSetBinding() + newSetBinding() + bindInstance() bindInstance() bindInstance() bindInstance() bindInstance() bindInstance() bindInstance() + bindInstance() bindProvider(ShuffledPlayerListProvider::class.java) } diff --git a/api/death-plugin/src/main/kotlin/org/rsmod/api/death/plugin/DeathDropHooksModule.kt b/api/death-plugin/src/main/kotlin/org/rsmod/api/death/plugin/DeathDropHooksModule.kt index 027227394..b95acd29b 100644 --- a/api/death-plugin/src/main/kotlin/org/rsmod/api/death/plugin/DeathDropHooksModule.kt +++ b/api/death-plugin/src/main/kotlin/org/rsmod/api/death/plugin/DeathDropHooksModule.kt @@ -4,6 +4,8 @@ import org.rsmod.api.death.NpcDeathDropHook import org.rsmod.api.death.NpcDeathKillHook import org.rsmod.api.death.PlayerDeathCleanupHook import org.rsmod.api.death.PlayerDeathHook +import org.rsmod.api.death.PlayerDeathItemHook +import org.rsmod.api.death.PlayerRespawnHook import org.rsmod.api.death.PvPAttackValidateHook import org.rsmod.api.death.PvPPlayerHitHook import org.rsmod.api.death.PvPSkullHook @@ -16,6 +18,8 @@ public class DeathDropHooksModule : PluginModule() { newSetBinding() newSetBinding() newSetBinding() + newSetBinding() + newSetBinding() newSetBinding() newSetBinding() newSetBinding() diff --git a/api/death/src/main/kotlin/org/rsmod/api/death/PlayerDeath.kt b/api/death/src/main/kotlin/org/rsmod/api/death/PlayerDeath.kt index b63826f5c..8e0d94137 100644 --- a/api/death/src/main/kotlin/org/rsmod/api/death/PlayerDeath.kt +++ b/api/death/src/main/kotlin/org/rsmod/api/death/PlayerDeath.kt @@ -1,23 +1,25 @@ package org.rsmod.api.death -import dev.or2.central.account.Rights import dev.openrune.ServerCacheManager import dev.openrune.rscm.RSCM import dev.openrune.rscm.RSCMType +import dev.or2.central.account.Rights import jakarta.inject.Inject import jakarta.inject.Singleton import org.rsmod.api.area.checker.AreaChecker import org.rsmod.api.area.checker.isInWildernessBasic +import org.rsmod.api.mechanics.toxins.Toxin.cureAllToxins import org.rsmod.api.player.death.DEATH_CAUSE_ATTR import org.rsmod.api.player.death.DeathCause -import org.rsmod.api.player.hasProtectItemPrayer -import org.rsmod.api.player.hook.TeleportType -import org.rsmod.api.mechanics.toxins.Toxin.cureAllToxins import org.rsmod.api.player.deathResetTimers import org.rsmod.api.player.disablePrayers +import org.rsmod.api.player.hasProtectItemPrayer +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.midiJingle import org.rsmod.api.player.protect.ProtectedAccess import org.rsmod.api.player.vars.boolVarBit import org.rsmod.api.player.vars.intVarp +import org.rsmod.api.repo.world.WorldRepository import org.rsmod.game.MapClock import org.rsmod.game.entity.Player import org.rsmod.map.CoordGrid @@ -30,7 +32,10 @@ constructor( private val drops: PlayerDeathDrops, private val handlingResolver: PlayerDeathHandlingResolver, private val cleanupHooks: Set, + private val itemHooks: Set, + private val respawnHooks: Set, private val areaChecker: AreaChecker, + private val worldRepo: WorldRepository, ) { private var Player.specialAttackType by intVarp("varp.sa_attack") private var Player.inInstance by boolVarBit("varbit.player_in_instance") @@ -41,12 +46,13 @@ constructor( } private suspend fun ProtectedAccess.deathSequence() { - val respawn = CoordGrid(0, 50, 50, 21, 18) + val respawn = respawnHooks.firstNotNullOfOrNull { it.respawn(player) } ?: DEFAULT_RESPAWN val randomRespawn = mapFindSquareLineOfWalk(respawn, minRadius = 0, maxRadius = 2) stopAction() delay(2) - soundSynth("synth.human_death") + // An area sound, so players nearby hear the death as well as the one dying. + worldRepo.soundArea(player, "synth.human_death", radius = DEATH_SOUND_RADIUS) anim("seq.human_death") delay(4) combatClearQueue() @@ -56,7 +62,7 @@ constructor( handleDeathDrops(player, deathCoords) midiSong("midi.stop_music") - midiJingle("jingle.air_guitar_jingle") + player.midiJingle(DEATH_JINGLE_GROUP) mes("Oh dear, you are dead!") telejump(randomRespawn ?: respawn, TeleportType.Exempt) resetAnim() @@ -91,6 +97,10 @@ constructor( val context = buildContext(player, deathCoords, killer) val handling = handlingResolver.resolve(context) + for (hook in itemHooks) { + hook.beforeDrops(context, handling) + } + val result = drops.selectDrops(player, context, handling) drops.applyDrops(player, result, handling, deathCoords) drops.spawnRemains(deathCoords, handling) @@ -125,6 +135,8 @@ constructor( player.specialAttackType = 0 player.skullIcon = null + // Attributes flagged `resetOnDeath` (vengeance, toxin immunity, absorption) end here. + player.attr.removeIf { it.resetOnDeath } for (hook in cleanupHooks) { hook.cleanup(player) @@ -142,6 +154,17 @@ constructor( } private companion object { + private const val DEATH_SOUND_RADIUS = 10 + + private val DEFAULT_RESPAWN = CoordGrid(0, 50, 50, 21, 18) + + /** + * Js5 archive 11 group of the death jingle. The `jingle.death` gameval id is not the group + * id the client plays (see [org.rsmod.api.player.midiJingle]); this number was confirmed + * in-game by ear with the `::jingle` command. + */ + private const val DEATH_JINGLE_GROUP = 90 + private fun CoordGrid.wildernessLevel(): Int { if (!isInWildernessBasic()) return -1 val y = z diff --git a/api/death/src/main/kotlin/org/rsmod/api/death/PlayerDeathItemHook.kt b/api/death/src/main/kotlin/org/rsmod/api/death/PlayerDeathItemHook.kt new file mode 100644 index 000000000..776418cbc --- /dev/null +++ b/api/death/src/main/kotlin/org/rsmod/api/death/PlayerDeathItemHook.kt @@ -0,0 +1,13 @@ +package org.rsmod.api.death + +/** + * Runs once a player's death handling has been resolved and before their carried objs are selected + * for keeping or dropping. + * + * Content that stores objs outside the regular inventories (a looting bag, the runes in a rune + * pouch) uses this to release or destroy that storage as part of the death, so those objs never + * take part in the "items kept on death" selection. + */ +public fun interface PlayerDeathItemHook { + public fun beforeDrops(context: PlayerDeathContext, handling: PlayerDeathHandling) +} diff --git a/api/death/src/main/kotlin/org/rsmod/api/death/PlayerRespawnHook.kt b/api/death/src/main/kotlin/org/rsmod/api/death/PlayerRespawnHook.kt new file mode 100644 index 000000000..8557d12ad --- /dev/null +++ b/api/death/src/main/kotlin/org/rsmod/api/death/PlayerRespawnHook.kt @@ -0,0 +1,13 @@ +package org.rsmod.api.death + +import org.rsmod.game.entity.Player +import org.rsmod.map.CoordGrid + +/** + * Lets content choose where a player wakes up after dying. Hooks are asked in registration order + * and the first non-null coordinate wins; when none answers, the player respawns at the default + * respawn point. + */ +public fun interface PlayerRespawnHook { + public fun respawn(player: Player): CoordGrid? +} diff --git a/api/death/src/main/kotlin/org/rsmod/api/death/PvPAttackValidateHook.kt b/api/death/src/main/kotlin/org/rsmod/api/death/PvPAttackValidateHook.kt index bb6987311..1c43f2796 100644 --- a/api/death/src/main/kotlin/org/rsmod/api/death/PvPAttackValidateHook.kt +++ b/api/death/src/main/kotlin/org/rsmod/api/death/PvPAttackValidateHook.kt @@ -10,4 +10,7 @@ public sealed class PvPAttackValidateResult { public data object Pass : PvPAttackValidateResult() public data class Deny(val message: String) : PvPAttackValidateResult() + + /** Drop the attack without a message; the hook has already acted on the click itself. */ + public data object Silent : PvPAttackValidateResult() } diff --git a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/npc/hunt/AggressionTolerance.kt b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/npc/hunt/AggressionTolerance.kt new file mode 100644 index 000000000..eaf949c41 --- /dev/null +++ b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/npc/hunt/AggressionTolerance.kt @@ -0,0 +1,60 @@ +package org.rsmod.api.game.process.npc.hunt + +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.game.entity.Player +import org.rsmod.game.entity.player.PlayerUid +import org.rsmod.map.CoordGrid +import org.rsmod.map.square.MapSquareKey + +/** + * Tracks how long each player has stayed in one map square, so that aggressive monsters can + * become tolerant of them. + * + * In the real game an aggressive monster stops attacking a player who has spent ten minutes in + * its area, and starts again once the player leaves and returns. The area is taken to be the + * 64x64 map square the player stands in: the timer restarts whenever that square changes. Only + * hunts that apply the combat-level rule honour tolerance; monsters that hunt everyone + * regardless of level (bosses, the always-aggressive kinds) never tire. + */ +@Singleton +public class AggressionTolerance @Inject constructor() { + private class Stay(var square: MapSquareKey, var since: Int, var lastSeen: Int) + + private val stays = HashMap() + private var lastPrune = 0 + + /** Records where [player] is on [cycle]; call once per cycle for every online player. */ + public fun tick(player: Player, cycle: Int) { + record(player.uid, player.coords, cycle) + if (cycle - lastPrune >= PRUNE_INTERVAL) { + lastPrune = cycle + stays.values.removeIf { cycle - it.lastSeen > PRUNE_INTERVAL } + } + } + + /** Whether monsters that respect tolerance should ignore [player] on [cycle]. */ + public fun isTolerant(player: Player, cycle: Int): Boolean = isTolerant(player.uid, cycle) + + internal fun record(uid: PlayerUid, coords: CoordGrid, cycle: Int) { + val square = MapSquareKey.from(coords) + val stay = stays[uid] + if (stay == null || stay.square != square) { + stays[uid] = Stay(square, since = cycle, lastSeen = cycle) + return + } + stay.lastSeen = cycle + } + + internal fun isTolerant(uid: PlayerUid, cycle: Int): Boolean { + val stay = stays[uid] ?: return false + return cycle - stay.since >= TOLERANCE_CYCLES + } + + public companion object { + /** Ten minutes of game cycles. */ + public const val TOLERANCE_CYCLES: Int = 1000 + + private const val PRUNE_INTERVAL = 500 + } +} diff --git a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/npc/hunt/NpcPlayerHuntProcessor.kt b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/npc/hunt/NpcPlayerHuntProcessor.kt index 7e10965da..bcfa43832 100644 --- a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/npc/hunt/NpcPlayerHuntProcessor.kt +++ b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/npc/hunt/NpcPlayerHuntProcessor.kt @@ -22,6 +22,7 @@ constructor( @CoreRandom private val random: GameRandom, private val mapClock: MapClock, private val hunt: Hunt, + private val tolerance: AggressionTolerance, ) { public fun process(npc: Npc) { if (!npc.isValidTarget() || npc.isDelayed) { @@ -76,24 +77,27 @@ constructor( if (player.combatLevel > type.combatLevel * 2 && !player.isInWilderness()) { continue } + // Monsters that respect the level rule also tire of a player who has stayed in + // their area for ten minutes; the always-aggressive modes never do. + if (tolerance.isTolerant(player, mapClock.cycle)) { + continue + } } if (!player.isInMulti()) { + // Vars are stored by id, and the server-only combat varp the hunt modes point + // at has no entry in the cache varp table, so read both by id. if (mode.checkNotCombat != -1) { - val varp = - ServerCacheManager.getVarp(mode.checkNotCombat) - ?: error("Error finding varp") - val delay = player.vars[varp] + constants.combat_activecombat_delay + val lastCombat = player.vars.backing.getOrDefault(mode.checkNotCombat, 0) + val delay = lastCombat + constants.combat_activecombat_delay if (delay > mapClock.cycle) { continue } } if (mode.checkNotCombatSelf != -1) { - val varn = - ServerCacheManager.getVarn(mode.checkNotCombatSelf) - ?: error("Unable to find varn: ${mode.checkNotCombatSelf}") - val delay = vars[varn] + constants.combat_activecombat_delay + val lastCombat = vars.backing.getOrDefault(mode.checkNotCombatSelf, 0) + val delay = lastCombat + constants.combat_activecombat_delay if (delay > mapClock.cycle) { continue } @@ -165,8 +169,10 @@ constructor( } } + // Reservoir sampling: the n-th eligible player replaces the pick with probability + // 1/n, so every eligible player is equally likely and one is always chosen. count++ - if (random.of(minInclusive = 0, maxInclusive = count) == 0) { + if (random.of(minInclusive = 0, maxInclusive = count - 1) == 0) { target = player.uid } } @@ -181,9 +187,9 @@ constructor( return false } - // TODO(combat): Wilderness indicator. + // The wilderness plugin keeps this varbit in step with the player's area. private fun Player.isInWilderness(): Boolean { - return false + return vars["varbit.inside_wilderness"] == 1 } // Hunt can be quite expensive if not careful. We are assuming that using a possibly delayed diff --git a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerInteractionProcessor.kt b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerInteractionProcessor.kt index d448a6e3e..0feda26fd 100644 --- a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerInteractionProcessor.kt +++ b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerInteractionProcessor.kt @@ -32,6 +32,7 @@ import org.rsmod.game.interact.InteractionNpcOp import org.rsmod.game.interact.InteractionNpcT import org.rsmod.game.interact.InteractionObj import org.rsmod.game.interact.InteractionPlayer +import org.rsmod.game.interact.InteractionOp import org.rsmod.game.interact.InteractionPlayerOp import org.rsmod.game.interact.InteractionPlayerT import org.rsmod.game.movement.RouteRequestPathingEntity @@ -64,7 +65,13 @@ constructor( // new one (e.g., combat calling `opnpc2`), the original interaction completes before the // new one is processed. val interaction = player.interaction - val followOp = interaction is InteractionPlayerOp && interaction.isFollowOp() + // Player option 3 is "Follow" by default, but content may relabel the slot (Emir's Arena + // puts "Challenge" there); only follow when the slot still says so, otherwise let the + // op reach its script like any other option. + val followOp = + interaction is InteractionPlayerOp && + interaction.isFollowOp() && + player.options.getOrNull(InteractionOp.Op3.slot) == FOLLOW_OPTION var interacted = false if (interaction != null && !player.isAccessProtected) { @@ -581,3 +588,5 @@ constructor( } } } + +private const val FOLLOW_OPTION = "Follow" diff --git a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerMovementProcessor.kt b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerMovementProcessor.kt index 9e1b058b1..ede236cc0 100644 --- a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerMovementProcessor.kt +++ b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerMovementProcessor.kt @@ -1,6 +1,7 @@ package org.rsmod.api.game.process.player import jakarta.inject.Inject +import org.rsmod.api.player.cheat.adminNoClip import org.rsmod.api.player.events.PlayerMovementEvent import org.rsmod.api.player.output.MapFlag.setMapFlag import org.rsmod.api.player.output.clearMapFlag @@ -12,6 +13,9 @@ import org.rsmod.game.entity.Player import org.rsmod.game.movement.MoveSpeed import org.rsmod.game.movement.RouteDestination import org.rsmod.game.movement.RouteRequest +import org.rsmod.game.movement.RouteRequestCoord +import org.rsmod.game.movement.RouteRequestLoc +import org.rsmod.game.movement.RouteRequestPathingEntity import org.rsmod.map.CoordGrid import org.rsmod.routefinder.Route import org.rsmod.routefinder.collision.CollisionFlagMap @@ -37,12 +41,41 @@ constructor( } private fun Player.routeTo(request: RouteRequest) { - val route = routeFactory.create(avatar, request) cachedMoveSpeed = tempMoveSpeed ?: varMoveSpeed moveSpeed = cachedMoveSpeed + val route = routeFactory.create(avatar, request) + // Admin "no clip" cheat: only take over when normal pathfinding cannot actually reach the + // requested destination. Routes that do reach it are kept as-is so that loc and npc + // interactions still resolve at their proper approach tile. + if (adminNoClip && (route.failed || route.alternative)) { + consumeNoClipRoute(request) + return + } consumeRoute(route) } + /** + * Queues the raw destination as the only waypoint; [validatedStep] then walks straight toward + * it, ignoring collision flags. + */ + private fun Player.consumeNoClipRoute(request: RouteRequest) { + routeDestination.clear() + val dest = request.noClipDestination() + if (dest == coords) { + clearMapFlag() + return + } + routeDestination.add(dest) + setMapFlag(this, dest.x, dest.z) + } + + private fun RouteRequest.noClipDestination(): CoordGrid = + when (this) { + is RouteRequestCoord -> destination + is RouteRequestLoc -> destination + is RouteRequestPathingEntity -> destination.coords + } + private fun Player.consumeRoute(route: Route) { routeDestination.clear() routeDestination.addAll(route) @@ -109,13 +142,20 @@ constructor( coords = current } - private fun Player.validatedStep(current: CoordGrid, target: CoordGrid): CoordGrid = - stepFactory.validated( + private fun Player.validatedStep(current: CoordGrid, target: CoordGrid): CoordGrid { + if (adminNoClip) { + if (current == target) { + return CoordGrid.NULL + } + return stepFactory.unvalidated(current, target) + } + return stepFactory.validated( source = current, dest = target, size = size, extraFlag = CollisionFlag.BLOCK_PLAYERS, ) + } private fun Player.addBlockWalkCollision(coords: CoordGrid) { if (!hidden) { diff --git a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerPostTickProcess.kt b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerPostTickProcess.kt index ca7345280..ac9cf8cbd 100644 --- a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerPostTickProcess.kt +++ b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerPostTickProcess.kt @@ -3,6 +3,7 @@ package org.rsmod.api.game.process.player import jakarta.inject.Inject import org.rsmod.api.account.autosave.PlayerAutosaveOrchestrator import org.rsmod.api.game.process.GameLifecycle +import org.rsmod.api.game.process.npc.hunt.AggressionTolerance import org.rsmod.api.player.forceDisconnect import org.rsmod.api.player.hook.PlayerPostTickHook import org.rsmod.api.player.output.MiscOutput @@ -30,6 +31,7 @@ constructor( private val exceptionHandler: GameExceptionHandler, private val playerAutosave: PlayerAutosaveOrchestrator, private val postTickHooks: Set, + private val tolerance: AggressionTolerance, ) { public fun process() { computeSharedBuffers() @@ -104,6 +106,7 @@ constructor( } private fun Player.processPostTickHooks() { + tolerance.tick(this, currentMapClock) for (hook in postTickHooks) { hook.onPostTick(this) } diff --git a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerQueueProcessor.kt b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerQueueProcessor.kt index 48c6af8fb..dad06836f 100644 --- a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerQueueProcessor.kt +++ b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerQueueProcessor.kt @@ -13,13 +13,37 @@ public class PlayerQueueProcessor @Inject constructor(private val eventBus: EventBus, private val protectedAccess: ProtectedAccessLauncher) { public fun process(player: Player) { - if (player.queueList.strongQueues > 0) { - player.ifClose(eventBus) + // Only a strong queue that fires this cycle interrupts. A pending one (a spell that lands + // in three cycles, say) must leave the player free to keep fighting until it does. + if (player.queueList.strongQueues > 0 && player.hasStrongQueueDue()) { + player.interruptForStrongQueue() } player.publishExpiredQueues() player.publishExpiredWeakQueues() } + /** + * Strong queues interrupt the player: modals close and the current interaction and route are + * dropped so the queue can launch. + * + * A script that is already mid-way - suspended in a delay or a dialogue - is left to finish: + * closing the modals is enough to unwind a dialogue, and a delayed script (the death sequence + * itself, for one) must not be cancelled by the very queue it is servicing. The strong queue + * launches once the script has ended, which [canLaunchQueue] enforces. + */ + private fun Player.hasStrongQueueDue(): Boolean = + queueList.anyDue(QueueCategory.Strong, currentMapClock) + + private fun Player.interruptForStrongQueue() { + ifClose(eventBus) + // Delayed, or a coroutine is suspended: the same test [canLaunchQueue] applies. + if (isModalButtonProtected) { + return + } + clearInteraction() + abortRoute() + } + private fun Player.publishExpiredQueues() { while (queueList.isNotEmpty) { var processedNone = true @@ -64,7 +88,13 @@ constructor(private val eventBus: EventBus, private val protectedAccess: Protect category == QueueCategory.Strong.id || category == QueueCategory.Soft.id private fun Player.canLaunchQueue(queue: PlayerQueueList.Queue): Boolean = - queue.category == QueueCategory.Soft.id || !isAccessProtected + when (queue.category) { + QueueCategory.Soft.id -> true + // The interaction was cleared by [interruptForStrongQueue]; only a coroutine that is + // still suspended (mid-delay) holds a strong queue back. + QueueCategory.Strong.id -> !isModalButtonProtected + else -> !isAccessProtected + } private fun Player.publish(queue: PlayerQueueList.Queue) { if (queue.category == QueueCategory.Soft.id) { @@ -105,7 +135,7 @@ constructor(private val eventBus: EventBus, private val protectedAccess: Protect } iterator.cleanUp() - if (processedNone || queueList.size == 1) { + if (processedNone || weakQueueList.size == 1) { break } } diff --git a/api/game-process/src/test/kotlin/org/rsmod/api/game/process/npc/hunt/AggressionToleranceTest.kt b/api/game-process/src/test/kotlin/org/rsmod/api/game/process/npc/hunt/AggressionToleranceTest.kt new file mode 100644 index 000000000..48c7f5001 --- /dev/null +++ b/api/game-process/src/test/kotlin/org/rsmod/api/game/process/npc/hunt/AggressionToleranceTest.kt @@ -0,0 +1,43 @@ +package org.rsmod.api.game.process.npc.hunt + +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.rsmod.game.entity.player.PlayerUid +import org.rsmod.map.CoordGrid + +class AggressionToleranceTest { + private val uid = PlayerUid(1) + + @Test + fun `a player becomes tolerated after ten minutes in one map square`() { + val tolerance = AggressionTolerance() + val tile = CoordGrid(3222, 3218) + tolerance.record(uid, tile, cycle = 0) + assertFalse(tolerance.isTolerant(uid, cycle = 0)) + tolerance.record(uid, tile.translateX(5), cycle = 999) + assertFalse(tolerance.isTolerant(uid, cycle = 999)) + tolerance.record(uid, tile.translateX(5), cycle = 1000) + assertTrue(tolerance.isTolerant(uid, cycle = 1000)) + } + + @Test + fun `leaving the map square restarts the timer`() { + val tolerance = AggressionTolerance() + tolerance.record(uid, CoordGrid(3222, 3218), cycle = 0) + tolerance.record(uid, CoordGrid(3222, 3218), cycle = 1500) + assertTrue(tolerance.isTolerant(uid, cycle = 1500)) + // 3264 is the first tile of the next map square east. + tolerance.record(uid, CoordGrid(3264, 3218), cycle = 1501) + assertFalse(tolerance.isTolerant(uid, cycle = 1501)) + assertFalse(tolerance.isTolerant(uid, cycle = 2400)) + tolerance.record(uid, CoordGrid(3270, 3218), cycle = 2501) + assertTrue(tolerance.isTolerant(uid, cycle = 2501)) + } + + @Test + fun `unknown players are never tolerated`() { + val tolerance = AggressionTolerance() + assertFalse(tolerance.isTolerant(PlayerUid(7), cycle = 5000)) + } +} diff --git a/api/game-process/src/test/kotlin/org/rsmod/api/game/process/player/PlayerNoClipTest.kt b/api/game-process/src/test/kotlin/org/rsmod/api/game/process/player/PlayerNoClipTest.kt new file mode 100644 index 000000000..fb4495065 --- /dev/null +++ b/api/game-process/src/test/kotlin/org/rsmod/api/game/process/player/PlayerNoClipTest.kt @@ -0,0 +1,139 @@ +package org.rsmod.api.game.process.player + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.rsmod.api.player.cheat.adminNoClip +import org.rsmod.api.route.RouteFactory +import org.rsmod.api.route.StepFactory +import org.rsmod.events.EventBus +import org.rsmod.game.entity.Player +import org.rsmod.game.movement.MoveSpeed +import org.rsmod.game.movement.RouteRequestCoord +import org.rsmod.map.CoordGrid +import org.rsmod.routefinder.collision.CollisionFlagMap +import org.rsmod.routefinder.flag.CollisionFlag + +/** + * Covers the admin "no clip" cheat in [PlayerMovementProcessor]. + * + * The map is a block of allocated zones with a solid column of blocked tiles down the middle. + * Everything outside the allocated zones reads as [CollisionFlagMap.DEFAULT_COLLISION_FLAG] - every + * bit set - so there is no way around the wall and the destination is genuinely unreachable by + * pathfinding. + */ +class PlayerNoClipTest { + @Test + fun `player is stopped by a wall without no clip`() { + val collision = walledMap() + val movement = processor(collision) + val player = playerAt(START) + + player.walkTo(movement, DESTINATION) + + assertNotEquals(DESTINATION, player.coords, "Player should not have crossed the wall.") + assertTrue( + player.coords.x < WALL_X, + "Player should have stopped west of the wall, but is at ${player.coords}.", + ) + } + + @Test + fun `player walks through a wall with no clip`() { + val collision = walledMap() + val movement = processor(collision) + val player = playerAt(START) + player.adminNoClip = true + + player.walkTo(movement, DESTINATION) + + assertEquals(DESTINATION, player.coords, "No clip should have walked through the wall.") + } + + /** + * No clip only takes over when pathfinding cannot reach the destination. An ordinary walk on an + * open map must still work, since that is the path loc and npc interactions rely on. + */ + @Test + fun `no clip does not disturb a reachable destination`() { + val collision = openMap() + val movement = processor(collision) + val player = playerAt(START) + player.adminNoClip = true + + player.walkTo(movement, DESTINATION) + + assertEquals(DESTINATION, player.coords) + } + + @Test + fun `no clip can be switched back off`() { + val collision = walledMap() + val movement = processor(collision) + val player = playerAt(START) + + player.adminNoClip = true + player.walkTo(movement, DESTINATION) + assertEquals(DESTINATION, player.coords) + + player.adminNoClip = false + player.walkTo(movement, START) + assertNotEquals(START, player.coords, "The wall should block the way back again.") + } + + private fun processor(collision: CollisionFlagMap) = + PlayerMovementProcessor( + collision, + RouteFactory(collision), + StepFactory(collision), + EventBus(), + ) + + private fun playerAt(coords: CoordGrid): Player = + Player().apply { + this.coords = coords + // Pinned so the processor never reads `varMoveSpeed`, which would need a loaded cache. + tempMoveSpeed = MoveSpeed.Walk + } + + /** Issues a route request and runs enough cycles for the walk to finish. */ + private fun Player.walkTo(movement: PlayerMovementProcessor, destination: CoordGrid) { + tempMoveSpeed = MoveSpeed.Walk + routeRequest = RouteRequestCoord(destination) + repeat(CYCLES) { + previousCoords = coords + currentMapClock++ + processedMapClock++ + movement.process(this) + } + } + + private fun openMap(): CollisionFlagMap { + val collision = CollisionFlagMap() + for (x in 0 until SPAN step ZONE_SIZE) { + for (z in 0 until SPAN step ZONE_SIZE) { + collision.allocateIfAbsent(x, z, 0) + } + } + return collision + } + + private fun walledMap(): CollisionFlagMap { + val collision = openMap() + for (z in 0 until SPAN) { + collision.add(WALL_X, z, 0, CollisionFlag.BLOCK_WALK) + } + return collision + } + + private companion object { + const val ZONE_SIZE = 8 + const val SPAN = 32 + const val WALL_X = 12 + const val CYCLES = 40 + + val START = CoordGrid(8, 16, 0) + val DESTINATION = CoordGrid(16, 16, 0) + } +} diff --git a/api/music-plugin/src/main/kotlin/org/rsmod/api/music/plugin/scripts/MusicTimerScript.kt b/api/music-plugin/src/main/kotlin/org/rsmod/api/music/plugin/scripts/MusicTimerScript.kt index 69074630a..3d699e193 100644 --- a/api/music-plugin/src/main/kotlin/org/rsmod/api/music/plugin/scripts/MusicTimerScript.kt +++ b/api/music-plugin/src/main/kotlin/org/rsmod/api/music/plugin/scripts/MusicTimerScript.kt @@ -3,7 +3,6 @@ package org.rsmod.api.music.plugin.scripts import jakarta.inject.Inject import org.rsmod.api.player.music.MusicPlayer import org.rsmod.api.player.vars.intVarBit -import org.rsmod.api.player.vars.intVarp import org.rsmod.api.script.onPlayerLogin import org.rsmod.api.script.onPlayerSoftTimer import org.rsmod.game.entity.Player @@ -59,7 +58,7 @@ public class MusicTimerScript @Inject constructor(private val musicPlayer: Music val endMusic = musicClock > musicDuration if (endMusic) { - musicPlayer.stop(this) + musicPlayer.trackEnded(this) return } } diff --git a/api/music/src/main/kotlin/org/rsmod/api/music/Music.kt b/api/music/src/main/kotlin/org/rsmod/api/music/Music.kt index 3abeec322..ae2750ff5 100644 --- a/api/music/src/main/kotlin/org/rsmod/api/music/Music.kt +++ b/api/music/src/main/kotlin/org/rsmod/api/music/Music.kt @@ -1,16 +1,19 @@ package org.rsmod.api.music import dev.openrune.types.aconverted.MidiType -import dev.openrune.types.varp.VarpServerType import org.rsmod.api.table.MusicRow public data class Music( val id: Int, + /** The `dbrow.music_*` id of the track's row in `dbtable.music`. */ + val rowId: Int, val displayName: String, val unlockHint: String, val duration: Int, val midi: MidiType, val unlockVarp: String?, + /** 1-based index into the `varp.musicmulti_*` unlock varps, or `0` when [canUnlock] is false. */ + val unlockVarpIndex: Int, val unlockBitpos: Int, val hidden: Boolean, val secondary: MusicRow?, @@ -20,6 +23,13 @@ public data class Music( val canUnlock: Boolean get() = unlockVarp != null + + /** + * The value the music tab stores in a `varbit.music_playlist_*_track_*` slot for this track: + * the client resolves it back to the row through `variable = (value / 100, value % 100)`. + */ + val playlistSlotValue: Int + get() = unlockVarpIndex * 100 + unlockBitpos } public data class MusicVariable(val varpIndex: Int, val bitpos: Int) diff --git a/api/music/src/main/kotlin/org/rsmod/api/music/MusicRepository.kt b/api/music/src/main/kotlin/org/rsmod/api/music/MusicRepository.kt index 64e4c8fa7..9a4744193 100644 --- a/api/music/src/main/kotlin/org/rsmod/api/music/MusicRepository.kt +++ b/api/music/src/main/kotlin/org/rsmod/api/music/MusicRepository.kt @@ -12,14 +12,26 @@ import org.rsmod.api.table.MusicRow public class MusicRepository @Inject constructor(private val random: GameRandom) { private lateinit var musicRows: Int2ObjectMap private lateinit var musicIds: Int2ObjectMap + private lateinit var jukebox: List private lateinit var modernAreas: Int2ObjectMap> private lateinit var classicAreas: Int2ObjectMap public fun forRow(row: MusicRow): Music? = musicRows[row.rowId] + public fun forRowId(rowId: Int): Music? = musicRows[rowId] + public fun forId(id: Int): Music? = musicIds[id] + /** + * The tracks in the order the music tab lists them. The client builds the jukebox from + * `db_find(dbcol.music:hidden = 0)` and the comsub it sends when a track is clicked is the + * index into that result, so this is every non-hidden row by ascending row id. + */ + public fun jukeboxTracks(): List = jukebox + + public fun jukeboxTrack(index: Int): Music? = jukebox.getOrNull(index) + public fun getModernArea(area: String): List? { return modernAreas[area.asRSCM(RSCMType.AREA)] } @@ -41,6 +53,8 @@ public class MusicRepository @Inject constructor(private val random: GameRandom) val musicSlots = mapMusicById(musicRows) this.musicIds = Int2ObjectOpenHashMap(musicSlots) + this.jukebox = musicRows.values.filterNot(Music::hidden).sortedBy(Music::rowId) + val modernAreas = loadModernAreas(musicRows) this.modernAreas = Int2ObjectOpenHashMap(modernAreas) @@ -55,19 +69,23 @@ public class MusicRepository @Inject constructor(private val random: GameRandom) for (row in rows) { val variable = row.variable var unlockVarp: String? = null + var unlockVarpIndex = 0 var unlockBitpos = -1 if (variable.isNotEmpty()) { unlockVarp = unlockVarps.getOrNull(variable[0] - 1) + unlockVarpIndex = variable[0] unlockBitpos = variable[1] } mapped[row.rowId] = Music( id = currId++, + rowId = row.rowId, displayName = row.displayname, unlockHint = row.unlockhint, duration = row.duration, midi = row.midi, unlockVarp = unlockVarp, + unlockVarpIndex = unlockVarpIndex, unlockBitpos = unlockBitpos, hidden = row.hidden ?: false, secondary = null, @@ -88,7 +106,7 @@ public class MusicRepository @Inject constructor(private val random: GameRandom) val trackRows = it.tracks val musicList = ArrayList(trackRows.size) for (trackRow in trackRows) { - val music = musicRows[trackRow.rowId]?: continue + val music = musicRows[trackRow.rowId] ?: continue musicList += music } val mappedList = grouped.computeIfAbsent(area) { mutableListOf() } diff --git a/api/net/src/main/kotlin/org/rsmod/api/net/rsprot/handlers/MoveGameClickHandler.kt b/api/net/src/main/kotlin/org/rsmod/api/net/rsprot/handlers/MoveGameClickHandler.kt index 8e0aef197..2610447dc 100644 --- a/api/net/src/main/kotlin/org/rsmod/api/net/rsprot/handlers/MoveGameClickHandler.kt +++ b/api/net/src/main/kotlin/org/rsmod/api/net/rsprot/handlers/MoveGameClickHandler.kt @@ -4,7 +4,10 @@ import jakarta.inject.Inject import net.rsprot.protocol.game.incoming.misc.user.MoveGameClick import org.rsmod.api.net.rsprot.player.modLevelTeleMoveSpeed import org.rsmod.api.net.rsprot.player.protectedTelejump +import org.rsmod.api.player.hook.PlayerRestrictions +import org.rsmod.api.player.hook.RestrictedAction import org.rsmod.api.player.output.clearMapFlag +import org.rsmod.api.player.output.mes import org.rsmod.api.player.protect.clearPendingAction import org.rsmod.api.player.vars.ctrlMoveSpeed import org.rsmod.api.realm.Realm @@ -21,12 +24,19 @@ constructor( private val realm: Realm, private val eventBus: EventBus, private val collision: CollisionFlagMap, + private val restrictions: PlayerRestrictions, ) : MessageHandler { override fun handle(player: Player, message: MoveGameClick) { if (player.isDelayed) { player.clearMapFlag() return } + val restriction = restrictions.check(player, RestrictedAction.Walk) + if (restriction != null) { + player.clearMapFlag() + player.mes(restriction) + return + } val dest = CoordGrid(message.x, message.z, player.level) val speed = when (message.keyCombination) { diff --git a/api/net/src/main/kotlin/org/rsmod/api/net/rsprot/handlers/MoveMinimapClickHandler.kt b/api/net/src/main/kotlin/org/rsmod/api/net/rsprot/handlers/MoveMinimapClickHandler.kt index a291bd374..d57a5ab1a 100644 --- a/api/net/src/main/kotlin/org/rsmod/api/net/rsprot/handlers/MoveMinimapClickHandler.kt +++ b/api/net/src/main/kotlin/org/rsmod/api/net/rsprot/handlers/MoveMinimapClickHandler.kt @@ -4,7 +4,10 @@ import jakarta.inject.Inject import net.rsprot.protocol.game.incoming.misc.user.MoveMinimapClick import org.rsmod.api.net.rsprot.player.modLevelTeleMoveSpeed import org.rsmod.api.net.rsprot.player.protectedTelejump +import org.rsmod.api.player.hook.PlayerRestrictions +import org.rsmod.api.player.hook.RestrictedAction import org.rsmod.api.player.output.clearMapFlag +import org.rsmod.api.player.output.mes import org.rsmod.api.player.protect.clearPendingAction import org.rsmod.api.player.vars.ctrlMoveSpeed import org.rsmod.api.realm.Realm @@ -21,12 +24,19 @@ constructor( private val realm: Realm, private val eventBus: EventBus, private val collision: CollisionFlagMap, + private val restrictions: PlayerRestrictions, ) : MessageHandler { override fun handle(player: Player, message: MoveMinimapClick) { if (player.isDelayed) { player.clearMapFlag() return } + val restriction = restrictions.check(player, RestrictedAction.Walk) + if (restriction != null) { + player.clearMapFlag() + player.mes(restriction) + return + } val dest = CoordGrid(message.x, message.z, player.level) val speed = when (message.keyCombination) { diff --git a/api/npc/src/main/kotlin/org/rsmod/api/npc/events/NpcCombatEvents.kt b/api/npc/src/main/kotlin/org/rsmod/api/npc/events/NpcCombatEvents.kt index 62dda7eaa..eeff70440 100644 --- a/api/npc/src/main/kotlin/org/rsmod/api/npc/events/NpcCombatEvents.kt +++ b/api/npc/src/main/kotlin/org/rsmod/api/npc/events/NpcCombatEvents.kt @@ -1,6 +1,7 @@ package org.rsmod.api.npc.events import org.rsmod.events.KeyedEvent +import org.rsmod.events.UnboundEvent import org.rsmod.game.entity.Npc import org.rsmod.game.hit.Hit import org.rsmod.game.hit.HitBuilder @@ -17,4 +18,11 @@ public class NpcHitEvents { public val hit: Hit, override val id: Long = npc.visType.id.toLong(), ) : KeyedEvent + + /** + * Published for every npc hit impact, whatever the npc type. [Impact] is keyed by npc type + * and suits scripts about one npc; this one suits mechanics that apply to all of them, such as + * weapon poison. + */ + public data class AnyImpact(public val npc: Npc, public val hit: Hit) : UnboundEvent } diff --git a/api/npc/src/main/kotlin/org/rsmod/api/npc/hit/modifier/StandardNpcHitModifier.kt b/api/npc/src/main/kotlin/org/rsmod/api/npc/hit/modifier/StandardNpcHitModifier.kt index 3be66801e..fab7f6884 100644 --- a/api/npc/src/main/kotlin/org/rsmod/api/npc/hit/modifier/StandardNpcHitModifier.kt +++ b/api/npc/src/main/kotlin/org/rsmod/api/npc/hit/modifier/StandardNpcHitModifier.kt @@ -2,19 +2,26 @@ package org.rsmod.api.npc.hit.modifier import jakarta.inject.Inject import kotlin.math.absoluteValue +import kotlin.math.max import kotlin.math.min import org.rsmod.api.npc.events.NpcHitEvents import org.rsmod.api.npc.hit.isStyleImmuneTo +import org.rsmod.api.player.cheat.adminOneHitKill import org.rsmod.events.EventBus import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PlayerList +import org.rsmod.game.entity.player.PlayerUid import org.rsmod.game.hit.HitBuilder -public class StandardNpcHitModifier @Inject constructor(private val eventBus: EventBus) : - NpcHitModifier { +public class StandardNpcHitModifier +@Inject +constructor(private val eventBus: EventBus, private val playerList: PlayerList) : NpcHitModifier { override fun HitBuilder.modify(target: Npc) { target.publishEvent(this) target.applyStyleImmunity(this) target.applyFlatArmour(this) + // Applied last so style immunity and flat armour cannot undo the cheat. + target.applyOneHitKill(this) } private fun Npc.publishEvent(hit: HitBuilder) { @@ -42,4 +49,16 @@ public class StandardNpcHitModifier @Inject constructor(private val eventBus: Ev hit.damage += armour.absoluteValue } } + + private fun Npc.applyOneHitKill(hit: HitBuilder) { + if (!hit.isFromPlayer) { + return + } + val sourceUid = hit.sourceUid ?: return + val source = PlayerUid(sourceUid).resolve(playerList) ?: return + if (!source.adminOneHitKill) { + return + } + hit.damage = max(hit.damage, hitpoints) + } } diff --git a/api/npc/src/main/kotlin/org/rsmod/api/npc/hit/processor/StandardNpcHitProcessor.kt b/api/npc/src/main/kotlin/org/rsmod/api/npc/hit/processor/StandardNpcHitProcessor.kt index fffc3cbf5..2018915e3 100644 --- a/api/npc/src/main/kotlin/org/rsmod/api/npc/hit/processor/StandardNpcHitProcessor.kt +++ b/api/npc/src/main/kotlin/org/rsmod/api/npc/hit/processor/StandardNpcHitProcessor.kt @@ -118,5 +118,6 @@ constructor( private fun Npc.publishHitEvent(hit: Hit) { val event = NpcHitEvents.Impact(this, hit) eventBus.publish(event) + eventBus.publish(NpcHitEvents.AnyImpact(this, hit)) } } diff --git a/api/obj-plugin/src/main/kotlin/org/rsmod/api/obj/plugin/ObjTakePlugin.kt b/api/obj-plugin/src/main/kotlin/org/rsmod/api/obj/plugin/ObjTakePlugin.kt index 2ccdd0ee9..8ef14c005 100644 --- a/api/obj-plugin/src/main/kotlin/org/rsmod/api/obj/plugin/ObjTakePlugin.kt +++ b/api/obj-plugin/src/main/kotlin/org/rsmod/api/obj/plugin/ObjTakePlugin.kt @@ -1,9 +1,11 @@ package org.rsmod.api.obj.plugin import dev.openrune.ServerCacheManager +import dev.openrune.types.ItemServerType import jakarta.inject.Inject import org.rsmod.api.config.Constants import org.rsmod.api.invtx.invAdd +import org.rsmod.api.player.hook.PlayerObjTakeRedirector import org.rsmod.api.player.hook.PlayerObjTakeValidator import org.rsmod.api.player.output.mes import org.rsmod.api.player.protect.ProtectedAccess @@ -21,6 +23,7 @@ public class ObjTakePlugin constructor( private val repo: ObjRepository, private val takeValidator: PlayerObjTakeValidator, + private val takeRedirector: PlayerObjTakeRedirector, ) : PluginScript() { override fun ScriptContext.startup() { onDefaultOpObj3 { triggerTake(it.obj) } @@ -33,33 +36,44 @@ constructor( mes(denial) return } - if (!player.hasInvSpace(obj)) { + val redirected = takeRedirector.find(player, obj, type) != null + if (!redirected && !player.hasInvSpace(obj)) { player.mes(Constants.dm_take_invspace) return } player.resetAnim() if (player.coords != obj.coords) { - takeFar(obj) + takeFar(obj, type) } else { soundSynth("synth.pick2") - player.takeClose(obj) + player.takeClose(obj, type) } } - private suspend fun ProtectedAccess.takeFar(obj: Obj) { + private suspend fun ProtectedAccess.takeFar(obj: Obj, type: ItemServerType) { delay(1) anim("seq.human_pickuptable") soundSynth("synth.pick2") - player.takeClose(obj) + player.takeClose(obj, type) } - private fun Player.takeClose(obj: Obj) { + private fun Player.takeClose(obj: Obj, type: ItemServerType) { + val redirect = takeRedirector.find(this, obj, type) + val take = transaction(obj) + if (redirect == null && take.failure) { + mes(Constants.dm_take_invspace) + return + } val removed = repo.del(obj) if (!removed) { mes(Constants.dm_take_taken) return } - val take = transaction(obj) + if (redirect != null && redirect.take(this, obj, type)) { + return + } + // The redirect container refused after all; fall back to the inventory transaction that + // was prepared before the obj left the ground. if (take.failure) { mes(Constants.dm_take_invspace) } else { diff --git a/api/player-output/src/main/kotlin/org/rsmod/api/player/output/MiscOutput.kt b/api/player-output/src/main/kotlin/org/rsmod/api/player/output/MiscOutput.kt index 2b1fe95fc..bf205eb93 100644 --- a/api/player-output/src/main/kotlin/org/rsmod/api/player/output/MiscOutput.kt +++ b/api/player-output/src/main/kotlin/org/rsmod/api/player/output/MiscOutput.kt @@ -10,7 +10,10 @@ import org.rsmod.game.entity.Player public object MiscOutput { /** @see [SetPlayerOp] */ public fun setPlayerOp(player: Player, slot: Int, op: String?, priority: Boolean = false) { - player.options.add(slot ,op) + while (player.options.size <= slot) { + player.options.add(null) + } + player.options[slot] = op player.client.write(SetPlayerOp(slot, priority, op)) } diff --git a/api/player-output/src/main/kotlin/org/rsmod/api/player/output/UpdateInventory.kt b/api/player-output/src/main/kotlin/org/rsmod/api/player/output/UpdateInventory.kt index a4413e8f2..77b4e6758 100644 --- a/api/player-output/src/main/kotlin/org/rsmod/api/player/output/UpdateInventory.kt +++ b/api/player-output/src/main/kotlin/org/rsmod/api/player/output/UpdateInventory.kt @@ -18,6 +18,40 @@ public object UpdateInventory { player.client.write(message) } + /** + * Sends [inv] as a "mirrored" inventory: the client files it under the partner's copy of the + * same inv id (what clientscripts read with `invother_*`), which is how the trade screens show + * the other player's offer next to your own. + */ + public fun updateInvFullMirror(player: Player, inv: Inventory) { + updateInvFullMirrorAs(player, inv, inv.type.id) + } + + /** + * Sends [inv]'s contents to the client under a different inventory id, [asInvId]. Useful when + * a cache interface is hard-wired to one inv id but the server keeps the objs elsewhere (a + * persisted holding inventory shown through the trade screen, for example). Nothing keeps the + * client copy in sync afterwards; call again after every change. + */ + public fun updateInvFullAs(player: Player, inv: Inventory, asInvId: Int) { + val message = UpdateInvFull(-(1234 + asInvId), asInvId, inv.lastOccupiedSlot(), RspObjProvider(inv.objs)) + player.client.write(message) + } + + /** Mirrored variant of [updateInvFullAs]; see [updateInvFullMirror]. */ + public fun updateInvFullMirrorAs(player: Player, inv: Inventory, asInvId: Int) { + val message = UpdateInvFull(MIRROR_ID_OFFSET - asInvId, asInvId, inv.lastOccupiedSlot(), RspObjProvider(inv.objs)) + player.client.write(message) + } + + /** Tells the client to forget an inventory it was shown under [invId]. */ + public fun updateInvStopTransmit(player: Player, invId: Int) { + player.client.write(UpdateInvStopTransmit(invId)) + } + + /** rsprot documents any combined id below -70000 as marking a mirrored inventory. */ + private const val MIRROR_ID_OFFSET: Int = -(70_000 + 1234) + /** @see [UpdateInvPartial] */ public fun updateInvPartial(player: Player, inv: Inventory) { val changedSlots = inv.modifiedSlots.asSequence().iterator() diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/PlayerExtensions.kt b/api/player/src/main/kotlin/org/rsmod/api/player/PlayerExtensions.kt index 6fc9a36d7..4d25bfb93 100644 --- a/api/player/src/main/kotlin/org/rsmod/api/player/PlayerExtensions.kt +++ b/api/player/src/main/kotlin/org/rsmod/api/player/PlayerExtensions.kt @@ -24,8 +24,13 @@ public fun Player.clearInteractionRoute() { clearMapFlag() } +/** + * Queues the player's death. A strong queue is used so the death sequence interrupts whatever the + * player is doing - a normal queue can never launch while the player is busy, and a player who has + * just been killed is almost always busy fighting back. + */ public fun Player.queueDeath() { - queue("queue.death", 1) + strongQueue("queue.death", 1) } public fun Player.combatClearQueue() { @@ -52,6 +57,10 @@ public fun Player.disablePrayers() { clearQueue("queue.preserve_activation") clearSoftTimer("timer.prayer_drain") clearSoftTimer("timer.rapidrestore_regen") + // Rapid Heal and Preserve speed up these timers while they are on; put them back to their + // normal rates so running out of prayer points does not leave the boosts running. + softTimer("timer.health_regen", constants.health_regen_interval) + softTimer("timer.stat_boost_restore", constants.stat_boost_restore_interval) } public fun Player.deathResetTimers() { @@ -159,4 +168,3 @@ public fun Player.hasAtLeast99s(requiredCount: Int): Boolean { return statMap.getBaseLevel("stat.${enum.value?.lowercase()}") >= 99 } >= requiredCount } - diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/PlayerMidiExtensions.kt b/api/player/src/main/kotlin/org/rsmod/api/player/PlayerMidiExtensions.kt index c2eab628c..f59ddb605 100644 --- a/api/player/src/main/kotlin/org/rsmod/api/player/PlayerMidiExtensions.kt +++ b/api/player/src/main/kotlin/org/rsmod/api/player/PlayerMidiExtensions.kt @@ -2,7 +2,6 @@ package org.rsmod.api.player import dev.openrune.rscm.RSCM.asRSCM import dev.openrune.rscm.RSCMType -import dev.openrune.types.aconverted.MidiType import net.rsprot.protocol.game.outgoing.sound.MidiJingle import net.rsprot.protocol.game.outgoing.sound.MidiSongV2 import org.rsmod.api.player.vars.intVarBit @@ -16,6 +15,18 @@ public fun Player.midiJingle(jingle: String) { client.write(MidiJingle(jingle.asRSCM(RSCMType.JINGLE))) } +/** + * Plays the jingle stored as js5 archive 11 group [group]. + * + * The client looks a jingle up by its archive group, which is not the id the `jingle.*` gameval + * resolves to (the sound gameval table numbers music and jingles in its own space), so a jingle + * whose group has been identified by ear or by duration is played through this overload. + */ +public fun Player.midiJingle(group: Int) { + musicClocks = 0 // Client restarts music when a jingle is played. + client.write(MidiJingle(group)) +} + /** @see [MidiSongV2] */ public fun Player.midiSong( midi: String, diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/cheat/AdminCheatAttributes.kt b/api/player/src/main/kotlin/org/rsmod/api/player/cheat/AdminCheatAttributes.kt index 2cbc29c4a..6541ac6cd 100644 --- a/api/player/src/main/kotlin/org/rsmod/api/player/cheat/AdminCheatAttributes.kt +++ b/api/player/src/main/kotlin/org/rsmod/api/player/cheat/AdminCheatAttributes.kt @@ -7,6 +7,12 @@ public val ADMIN_GOD_MODE_ATTR: AttributeKey = AttributeKey() public val ADMIN_MAX_HIT_ATTR: AttributeKey = AttributeKey() +public val ADMIN_ONE_HIT_KILL_ATTR: AttributeKey = AttributeKey() + +public val ADMIN_INFINITE_RUNES_ATTR: AttributeKey = AttributeKey() + +public val ADMIN_NO_CLIP_ATTR: AttributeKey = AttributeKey() + public var Player.adminGodMode: Boolean get() = attr[ADMIN_GOD_MODE_ATTR] == true set(value) { @@ -26,3 +32,45 @@ public var Player.adminMaxHit: Boolean attr.remove(ADMIN_MAX_HIT_ATTR) } } + +/** + * When enabled, any hit this player deals to an npc is raised to the npc's remaining hitpoints, + * killing it in a single hitsplat. Player-vs-player hits are deliberately left untouched. + */ +public var Player.adminOneHitKill: Boolean + get() = attr[ADMIN_ONE_HIT_KILL_ATTR] == true + set(value) { + if (value) { + attr[ADMIN_ONE_HIT_KILL_ATTR] = true + } else { + attr.remove(ADMIN_ONE_HIT_KILL_ATTR) + } + } + +/** + * When enabled, spell requirements are treated as satisfied without consuming any runes. The spell's + * magic level and spellbook requirements still apply. + */ +public var Player.adminInfiniteRunes: Boolean + get() = attr[ADMIN_INFINITE_RUNES_ATTR] == true + set(value) { + if (value) { + attr[ADMIN_INFINITE_RUNES_ATTR] = true + } else { + attr.remove(ADMIN_INFINITE_RUNES_ATTR) + } + } + +/** + * When enabled, route requests bypass pathfinding and the player walks in a straight line to the + * requested destination, ignoring every collision flag on the way. + */ +public var Player.adminNoClip: Boolean + get() = attr[ADMIN_NO_CLIP_ATTR] == true + set(value) { + if (value) { + attr[ADMIN_NO_CLIP_ATTR] = true + } else { + attr.remove(ADMIN_NO_CLIP_ATTR) + } + } diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/events/interact/HeldEvents.kt b/api/player/src/main/kotlin/org/rsmod/api/player/events/interact/HeldEvents.kt index cbc3e1533..96acd71a1 100644 --- a/api/player/src/main/kotlin/org/rsmod/api/player/events/interact/HeldEvents.kt +++ b/api/player/src/main/kotlin/org/rsmod/api/player/events/interact/HeldEvents.kt @@ -3,8 +3,6 @@ package org.rsmod.api.player.events.interact import dev.openrune.rscm.RSCM import dev.openrune.rscm.RSCM.asRSCM import dev.openrune.rscm.RSCMType -import dev.openrune.definition.type.widget.ComponentType -import dev.openrune.types.InventoryServerType import dev.openrune.types.ItemServerType import dev.openrune.types.aconverted.interf.IfButtonOp import dev.openrune.util.Wearpos @@ -23,6 +21,7 @@ public class HeldBanksideEvents { public val player: Player, public val slot: Int, public val type: ItemServerType, + public val op: IfButtonOp = IfButtonOp.Op9, override val id: Long = type.id.toLong(), ) : KeyedEvent } diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/hook/GroundItemHooks.kt b/api/player/src/main/kotlin/org/rsmod/api/player/hook/GroundItemHooks.kt index 5f98d35e1..04abe0528 100644 --- a/api/player/src/main/kotlin/org/rsmod/api/player/hook/GroundItemHooks.kt +++ b/api/player/src/main/kotlin/org/rsmod/api/player/hook/GroundItemHooks.kt @@ -75,3 +75,25 @@ constructor(private val hooks: Set<@JvmSuppressWildcards PlayerObjTakeValidateHo return null } } + +/** + * Lets content receive a picked-up ground obj into a container other than the player inventory, + * such as an open looting bag in the Wilderness. + * + * [redirects] is asked before the pickup starts (so the inventory-space check is skipped) and + * again once the obj has been removed from the ground. [take] should only fail if the container + * changed in between, in which case the obj falls back to the regular inventory. + */ +public interface PlayerObjTakeRedirectHook { + public fun redirects(player: Player, obj: Obj, objType: ItemServerType): Boolean + + /** @return `true` if [obj] was added to the container. */ + public fun take(player: Player, obj: Obj, objType: ItemServerType): Boolean +} + +public class PlayerObjTakeRedirector +@Inject +constructor(private val hooks: Set<@JvmSuppressWildcards PlayerObjTakeRedirectHook>) { + public fun find(player: Player, obj: Obj, objType: ItemServerType): PlayerObjTakeRedirectHook? = + hooks.firstOrNull { it.redirects(player, obj, objType) } +} diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/hook/PlayerRestrictionHook.kt b/api/player/src/main/kotlin/org/rsmod/api/player/hook/PlayerRestrictionHook.kt new file mode 100644 index 000000000..e26826645 --- /dev/null +++ b/api/player/src/main/kotlin/org/rsmod/api/player/hook/PlayerRestrictionHook.kt @@ -0,0 +1,49 @@ +package org.rsmod.api.player.hook + +import dev.openrune.types.ItemServerType +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.game.entity.Player + +/** + * An everyday action a piece of content may want to forbid while the player is in some special + * state, such as a duel with "No Food" ticked or a minigame that disallows prayer. + */ +public sealed class RestrictedAction { + /** A walk request from the client (map click or minimap click). */ + public data object Walk : RestrictedAction() + + /** Turning a prayer on, including quick-prayers. */ + public data object Prayer : RestrictedAction() + + /** Eating food. */ + public data object Food : RestrictedAction() + + /** Drinking a potion. */ + public data object Drink : RestrictedAction() + + /** Wielding or wearing [obj]. */ + public data class Equip(val obj: ItemServerType) : RestrictedAction() +} + +/** + * Registered as a set binding; every hook is asked before the action runs and the first non-null + * message blocks the action and is shown to the player. + */ +public fun interface PlayerRestrictionHook { + public fun restriction(player: Player, action: RestrictedAction): String? +} + +@Singleton +public class PlayerRestrictions @Inject constructor(private val hooks: Set) { + /** Returns the denial message for [action], or `null` when every hook allows it. */ + public fun check(player: Player, action: RestrictedAction): String? { + for (hook in hooks) { + val message = hook.restriction(player, action) + if (message != null) { + return message + } + } + return null + } +} diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/music/MusicPlayMode.kt b/api/player/src/main/kotlin/org/rsmod/api/player/music/MusicPlayMode.kt index ae9d03bae..e1cb988d7 100644 --- a/api/player/src/main/kotlin/org/rsmod/api/player/music/MusicPlayMode.kt +++ b/api/player/src/main/kotlin/org/rsmod/api/player/music/MusicPlayMode.kt @@ -2,8 +2,18 @@ package org.rsmod.api.player.music import org.rsmod.api.utils.vars.VarEnumDelegate +/** + * The `varp.musicplay` values the music tab's client scripts write when one of its mode buttons + * is pressed (`music_optionbuttons_setmode`): the "Area Mode" button stores `0`, "Shuffle Mode" + * stores `1` and "Single Mode" stores `2`. + */ public enum class MusicPlayMode(override val varValue: Int) : VarEnumDelegate { - Manual(0), - Area(1), - Random(2), + /** Plays the tracks of the music area the player is standing in. */ + Area(0), + + /** Plays random unlocked tracks. */ + Random(1), + + /** Loops the track the player picked in the music tab. */ + Manual(2), } diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/music/MusicPlayer.kt b/api/player/src/main/kotlin/org/rsmod/api/player/music/MusicPlayer.kt index b7269da3e..019ac657c 100644 --- a/api/player/src/main/kotlin/org/rsmod/api/player/music/MusicPlayer.kt +++ b/api/player/src/main/kotlin/org/rsmod/api/player/music/MusicPlayer.kt @@ -3,7 +3,6 @@ package org.rsmod.api.player.music import dev.openrune.rscm.RSCM import dev.openrune.rscm.RSCM.asRSCM import dev.openrune.rscm.RSCMType -import dev.openrune.types.aconverted.AreaType import jakarta.inject.Inject import org.rsmod.api.music.Music import org.rsmod.api.music.MusicRepository @@ -24,7 +23,7 @@ import org.rsmod.game.entity.Player public class MusicPlayer @Inject internal constructor(private val random: GameRandom, private val repo: MusicRepository) { - private val Player.playMode by enumVarp("varp.musicplay") + private var Player.playMode by enumVarp("varp.musicplay") private val Player.areaMode by enumVarBit("varbit.music_area_mode") private var Player.lastMusicId by intVarBit("varbit.music_last_id") @@ -37,6 +36,15 @@ internal constructor(private val random: GameRandom, private val repo: MusicRepo private var Player.musicPlaylist by intVarp("varp.music_playlist") private var Player.unlockMessageDisabled by boolVarBit("varbit.music_unlock_text_toggle") + /** The `dbtable.music` row of the playing track. The music tab colours it as "playing". */ + private var Player.currentTrackRow by intVarp("varp.music_current_track") + + /** The playlist picked in the music tab dropdown: `0` for every track, otherwise `1..3`. */ + private val Player.currentPlaylist by intVarBit("varbit.music_current_playlist") + + private val Player.shuffleOnManualSelect by + boolVarBit("varbit.use_shuffle_mode_on_manual_music_selection") + public fun unlockAndPlay(player: Player, musicRow: MusicRow) { val music = getUnlockableOrThrow(musicRow) unlockAndPlay(player, music) @@ -78,12 +86,71 @@ internal constructor(private val random: GameRandom, private val repo: MusicRepo player.musicDuration = music.duration } player.currMusicId = music.id + player.currentTrackRow = music.rowId val fadeSpeed = if (currMusicId == 0) 0 else MUSIC_PLAY_FADE player.midiSong(music.midi.id, fadeOutSpeed = fadeSpeed, fadeInDelay = fadeSpeed) player.ifSetText("component.music:now_playing_text", music.displayName) } + /** + * Plays [music] because the player picked it in the music tab. Returns `false` without playing + * anything when the track has not been unlocked. + * + * A manual pick switches the player to [MusicPlayMode.Manual] so the track loops, or to + * [MusicPlayMode.Random] when the "use shuffle mode on manual music selection" setting is on. + */ + public fun playSelected(player: Player, music: Music): Boolean { + if (!hasUnlocked(player, music)) { + return false + } + player.playMode = + if (player.shuffleOnManualSelect) MusicPlayMode.Random else MusicPlayMode.Manual + play(player, music) + return true + } + + /** + * Switches the player to [mode] the way the music tab's mode buttons do: area mode picks up + * the music of the area the player stands in, shuffle mode starts a random unlocked track and + * single mode keeps whatever is playing so it can loop. + */ + public fun selectMode(player: Player, mode: MusicPlayMode) { + if (player.playMode == mode) { + return + } + player.playMode = mode + when (mode) { + MusicPlayMode.Area -> playNextArea(player) + MusicPlayMode.Random -> playNextRandom(player) + MusicPlayMode.Manual -> {} + } + } + + /** Moves on to the next track in area or shuffle mode; the "Skip Track" tab button. */ + public fun skipTrack(player: Player) { + when (player.playMode) { + MusicPlayMode.Area -> playNextArea(player) + MusicPlayMode.Random -> playNextRandom(player) + MusicPlayMode.Manual -> {} + } + } + + /** Called when the playing track has run for its full duration. */ + public fun trackEnded(player: Player) { + if (player.playMode != MusicPlayMode.Manual) { + stop(player) + return + } + val music = repo.forId(player.currMusicId) + if (music == null) { + stop(player) + return + } + player.currMusicId = 0 // Sends the midi song again so the client restarts it. + play(player, music) + } + public fun resume(player: Player) { val music = repo.forId(player.currMusicId) if (music == null) { @@ -100,12 +167,23 @@ internal constructor(private val random: GameRandom, private val repo: MusicRepo player.currMusicId = 0 player.musicClocks = 0 player.musicDuration = 0 + player.currentTrackRow = NO_TRACK_ROW player.midiSong("midi.stop_music", fadeOutSpeed = MUSIC_END_FADE) if (player.playMode == MusicPlayMode.Manual) { setEmptyMusicText(player) } } + /** Re-sends the "Playing:" text, e.g. when the music tab is (re)opened. */ + public fun updateNowPlayingText(player: Player) { + val music = repo.forId(player.currMusicId) + if (music == null) { + setEmptyMusicText(player) + } else { + player.ifSetText("component.music:now_playing_text", music.displayName) + } + } + public fun unlock(player: Player, musicRow: MusicRow) { val music = getUnlockableOrThrow(musicRow) unlock(player, music) @@ -126,12 +204,64 @@ internal constructor(private val random: GameRandom, private val repo: MusicRepo return hasUnlocked(player, music) } - private fun hasUnlocked(player: Player, music: Music): Boolean { + public fun hasUnlocked(player: Player, music: Music): Boolean { val varp = music.unlockVarp ?: return false val unlockedValue = player.vars[varp] and music.unlockBitflag return unlockedValue != 0 } + public fun isInPlaylist(player: Player, playlist: Int, music: Music): Boolean { + return playlistSlot(player, playlist, music) != null + } + + /** The tracks stored in [playlist] (`1..3`), in slot order. */ + public fun playlistTracks(player: Player, playlist: Int): List { + require(playlist in 1..PLAYLIST_COUNT) { "Playlist must be in range 1..$PLAYLIST_COUNT." } + val tracks = ArrayList() + for (slot in 1..PLAYLIST_SIZE) { + val value = player.vars[playlistSlotVarbit(playlist, slot)] + if (value == 0) { + continue + } + val music = repo.getAll().firstOrNull { it.canUnlock && it.playlistSlotValue == value } + if (music != null) { + tracks += music + } + } + return tracks + } + + /** Adds [music] to [playlist]; returns `false` when the playlist has no free slot. */ + public fun addToPlaylist(player: Player, playlist: Int, music: Music): Boolean { + require(music.canUnlock) { "Only unlockable tracks can be stored: '${music.displayName}'" } + if (isInPlaylist(player, playlist, music)) { + return true + } + val free = (1..PLAYLIST_SIZE).firstOrNull { player.vars[playlistSlotVarbit(playlist, it)] == 0 } + if (free == null) { + return false + } + VarPlayerIntMapSetter.set(player, playlistSlotVarbit(playlist, free), music.playlistSlotValue) + return true + } + + public fun removeFromPlaylist(player: Player, playlist: Int, music: Music) { + val slot = playlistSlot(player, playlist, music) ?: return + VarPlayerIntMapSetter.set(player, playlistSlotVarbit(playlist, slot), 0) + } + + private fun playlistSlot(player: Player, playlist: Int, music: Music): Int? { + require(playlist in 1..PLAYLIST_COUNT) { "Playlist must be in range 1..$PLAYLIST_COUNT." } + if (!music.canUnlock) { + return null + } + val value = music.playlistSlotValue + return (1..PLAYLIST_SIZE).firstOrNull { player.vars[playlistSlotVarbit(playlist, it)] == value } + } + + private fun playlistSlotVarbit(playlist: Int, slot: Int): String = + "varbit.music_playlist_${playlist}_track_$slot" + private fun setEmptyMusicText(player: Player) { player.ifSetText("component.music:now_playing_text", " ") } @@ -160,15 +290,20 @@ internal constructor(private val random: GameRandom, private val repo: MusicRepo when (player.playMode) { MusicPlayMode.Area -> playNextArea(player) MusicPlayMode.Random -> playNextRandom(player) - MusicPlayMode.Manual -> {} + MusicPlayMode.Manual -> loopSelected(player) } } + private fun loopSelected(player: Player) { + val music = repo.forId(player.lastMusicId) ?: return + play(player, music) + } + private fun playNextArea(player: Player) { if (player.currMusicArea == 0) { return } - val area = RSCM.getReverseMapping(RSCMType.AREA,player.currMusicArea - 1) + val area = RSCM.getReverseMapping(RSCMType.AREA, player.currMusicArea - 1) when (player.areaMode) { MusicAreaMode.Modern -> { val modernMusic = repo.getModernArea(area) @@ -193,10 +328,16 @@ internal constructor(private val random: GameRandom, private val repo: MusicRepo } private fun playNextRandom(player: Player) { - val unlocked = - repo.getAll().filter { hasUnlocked(player, it) && it.id != player.currMusicId } - val random = random.pickOrNull(unlocked) ?: repo.getAll().first() - play(player, random) + val playlist = player.currentPlaylist + val candidates = + if (playlist in 1..PLAYLIST_COUNT) playlistTracks(player, playlist) else repo.getAll() + val unlocked = candidates.filter { hasUnlocked(player, it) && it.id != player.currMusicId } + val next = + random.pickOrNull(unlocked) + ?: repo.forId(player.currMusicId) + ?: repo.forId(player.lastMusicId) + ?: repo.getAll().first() + play(player, next) } public fun enterArea(player: Player, area: String) { @@ -259,5 +400,14 @@ internal constructor(private val random: GameRandom, private val repo: MusicRepo public companion object { public const val MUSIC_PLAY_FADE: Int = 60 public const val MUSIC_END_FADE: Int = 20 + + /** The number of custom playlists the music tab offers. */ + public const val PLAYLIST_COUNT: Int = 3 + + /** The number of tracks a custom playlist can hold. */ + public const val PLAYLIST_SIZE: Int = 100 + + /** `varp.music_current_track` value the music tab treats as "nothing playing". */ + private const val NO_TRACK_ROW: Int = -1 } } diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/ui/PlayerInterfaceExtensions.kt b/api/player/src/main/kotlin/org/rsmod/api/player/ui/PlayerInterfaceExtensions.kt index 6ef8d2e6e..61f349f21 100644 --- a/api/player/src/main/kotlin/org/rsmod/api/player/ui/PlayerInterfaceExtensions.kt +++ b/api/player/src/main/kotlin/org/rsmod/api/player/ui/PlayerInterfaceExtensions.kt @@ -23,6 +23,7 @@ import net.rsprot.protocol.game.outgoing.interfaces.IfSetNpcHead import net.rsprot.protocol.game.outgoing.interfaces.IfSetNpcHeadActive import net.rsprot.protocol.game.outgoing.interfaces.IfSetObject import net.rsprot.protocol.game.outgoing.interfaces.IfSetPlayerHead +import net.rsprot.protocol.game.outgoing.interfaces.IfSetPosition import net.rsprot.protocol.game.outgoing.interfaces.IfSetText import net.rsprot.protocol.game.outgoing.misc.player.TriggerOnDialogAbort import org.rsmod.annotations.InternalApi @@ -235,6 +236,12 @@ public fun Player.ifSetEvents(internal: String, range: IntRange, vararg event: I client.write(prot) } +/** Moves a component to ([x], [y]) within its parent, keeping its position modes. */ +public fun Player.ifSetPosition(internal: String, x: Int, y: Int) { + val target = ServerCacheManager.fromComponent(internal.asRSCM(RSCMType.COMPONENT)) + client.write(IfSetPosition(target.interfaceId, target.component, x, y)) +} + public fun Player.ifSetText(internal: String, text: String) { val target = ServerCacheManager.fromComponent(internal.asRSCM(RSCMType.COMPONENT)) client.write(IfSetText(target.interfaceId, target.component, text)) diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/worn/HeldEquipOp.kt b/api/player/src/main/kotlin/org/rsmod/api/player/worn/HeldEquipOp.kt index d77558ec4..26280a064 100644 --- a/api/player/src/main/kotlin/org/rsmod/api/player/worn/HeldEquipOp.kt +++ b/api/player/src/main/kotlin/org/rsmod/api/player/worn/HeldEquipOp.kt @@ -12,6 +12,8 @@ import org.rsmod.api.invtx.select import org.rsmod.api.invtx.swap import org.rsmod.api.invtx.transfer import org.rsmod.api.player.events.interact.HeldEquipEvents +import org.rsmod.api.player.hook.PlayerRestrictions +import org.rsmod.api.player.hook.RestrictedAction import org.rsmod.api.player.righthand import org.rsmod.api.player.stat.statBase import org.rsmod.api.utils.format.addArticle @@ -23,11 +25,18 @@ import org.rsmod.game.type.getInvObj import org.rsmod.objtx.TransactionResult import org.rsmod.objtx.isErr -public class HeldEquipOp @Inject constructor(private val eventBus: EventBus) { +public class HeldEquipOp +@Inject +constructor(private val eventBus: EventBus, private val restrictions: PlayerRestrictions) { public fun equip(player: Player, invSlot: Int, inventory: Inventory): HeldEquipResult { val obj = inventory[invSlot] ?: return HeldEquipResult.Fail.InvalidObj val objType = getInvObj(obj) + val restriction = restrictions.check(player, RestrictedAction.Equip(objType)) + if (restriction != null) { + return HeldEquipResult.Fail.Restricted(restriction) + } + val result = equip(player, objType) if (result is HeldEquipResult.Success) { diff --git a/api/player/src/main/kotlin/org/rsmod/api/player/worn/HeldEquipResult.kt b/api/player/src/main/kotlin/org/rsmod/api/player/worn/HeldEquipResult.kt index 1545155e4..3914ff098 100644 --- a/api/player/src/main/kotlin/org/rsmod/api/player/worn/HeldEquipResult.kt +++ b/api/player/src/main/kotlin/org/rsmod/api/player/worn/HeldEquipResult.kt @@ -15,6 +15,9 @@ public sealed class HeldEquipResult { public class NotEnoughWornSpace(message: String) : Fail(listOf(message)) + /** A [org.rsmod.api.player.hook.PlayerRestrictionHook] forbade the equip. */ + public class Restricted(message: String) : Fail(listOf(message)) + public class StatRequirements(messages: List) : Fail(messages) { public constructor( messages: Pair diff --git a/api/repo/src/main/kotlin/org/rsmod/api/repo/world/WorldRepository.kt b/api/repo/src/main/kotlin/org/rsmod/api/repo/world/WorldRepository.kt index 28f7b9220..6c5e68a7c 100644 --- a/api/repo/src/main/kotlin/org/rsmod/api/repo/world/WorldRepository.kt +++ b/api/repo/src/main/kotlin/org/rsmod/api/repo/world/WorldRepository.kt @@ -3,9 +3,7 @@ package org.rsmod.api.repo.world import dev.openrune.rscm.RSCM.asRSCM import dev.openrune.rscm.RSCMType import dev.openrune.types.ProjAnimType -import dev.openrune.types.SequenceServerType import dev.openrune.types.aconverted.SpotanimType -import dev.openrune.types.aconverted.SynthType import jakarta.inject.Inject import org.rsmod.api.registry.zone.ZoneUpdateMap import org.rsmod.game.entity.Npc @@ -46,6 +44,27 @@ public class WorldRepository @Inject constructor(private val zoneUpdates: ZoneUp soundArea(source.coords, synth, delay, loops, radius, source.size) } + public fun soundArea( + source: CoordGrid, + synth: Int, + delay: Int = 0, + loops: Int = 1, + radius: Int = 5, + size: Int = 0, + ) { + zoneUpdates.soundArea(source, synth, delay, loops, radius, size) + } + + public fun soundArea( + source: PathingEntity, + synth: Int, + delay: Int = 0, + loops: Int = 1, + radius: Int = 5, + ) { + soundArea(source.coords, synth, delay, loops, radius, source.size) + } + public fun spotanimMap( spotanim: SpotanimType, coord: CoordGrid, diff --git a/content/drops/src/main/kotlin/org/rsmod/content/drops/tables/monsters/ZombieEntranaDungeonDropTable.kt b/content/drops/src/main/kotlin/org/rsmod/content/drops/tables/monsters/ZombieEntranaDungeonDropTable.kt new file mode 100644 index 000000000..fe94f3814 --- /dev/null +++ b/content/drops/src/main/kotlin/org/rsmod/content/drops/tables/monsters/ZombieEntranaDungeonDropTable.kt @@ -0,0 +1,49 @@ +package org.rsmod.content.drops.tables.monsters + +import dtx.rs.RSDropTable +import dtx.rs.npcs +import org.rsmod.api.droptable.DropRollItem +import org.rsmod.api.droptable.RegisterDropTable +import org.rsmod.api.droptable.ringNothing +import org.rsmod.api.droptable.rsPlayerGuaranteedTable +import org.rsmod.api.droptable.rsPlayerTertiaryTable +import org.rsmod.api.droptable.rsPlayerWeightedTable +import org.rsmod.content.drops.isOnQuest +import org.rsmod.content.drops.tables.shared.SharedDropTables +import org.rsmod.game.entity.Player + +/** + * The level 25 zombies in the Entrana Dungeon. Their bronze axe is how Lost City players, who + * cannot bring one to the island, get to chop the Dramen tree. + */ +@field:RegisterDropTable +@JvmField +public val zombieEntranaDungeonDropTable: RSDropTable = RSDropTable( + tableIdentifier = "Zombie (Entrana Dungeon) Drops", + npcs = npcs("npc.zombie_entranan", "npc.zombie_entranan2", "npc.zombie_entranan3", "npc.zombie_entranan4", "npc.zombie_entranan5"), + guaranteed = rsPlayerGuaranteedTable { + "obj.bones" count 1 + }, + mainTable = rsPlayerWeightedTable(total = 128) { + name("Zombie (Entrana Dungeon) Drops") + 50 weight "obj.bronze_axe" count 1 + 4 weight "obj.bronze_med_helm" count 1 + 1 weight "obj.bronze_longsword" count 1 + 1 weight "obj.iron_axe" count 1 + 4 weight "obj.airrune" count 13 + 4 weight "obj.bronze_arrow" count 8 + 1 weight "obj.naturerune" count 6 + 46 weight "obj.fishing_bait" count 5 + 8 weight ringNothing() + 3 weight "obj.coins" count 18 + 2 weight "obj.coins" count 28 + + 4 weight SharedDropTables.herb + }, + tertiaries = rsPlayerTertiaryTable { + 1 outOf 4 weight "obj.rag_zombie_bone" count 1 condition { + player -> player.isOnQuest("quest_ragandboneman2") + } + 1 outOf 5000 weight "obj.champions_challenge_zombie" count 1 + }, +) diff --git a/content/drops/src/main/resources/drops/tables/monsters/rat.toml b/content/drops/src/main/resources/drops/tables/monsters/rat.toml new file mode 100644 index 000000000..6e1530b9c --- /dev/null +++ b/content/drops/src/main/resources/drops/tables/monsters/rat.toml @@ -0,0 +1,10 @@ +id = "Rat Drops" +npcs = ["npc.rat", "npc.rat_indoors"] + +# Witch's Potion: every ordinary rat drops its tail, alongside its bones, while the quest is in +# progress. Outside the quest rats drop nothing but bones. +[[guaranteed]] +obj = "obj.rats_tail" +count = 1 +quest = "quest_witchspotion" +quest_mode = "during" diff --git a/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/GenericPassageScript.kt b/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/GenericPassageScript.kt new file mode 100644 index 000000000..5828d0268 --- /dev/null +++ b/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/GenericPassageScript.kt @@ -0,0 +1,492 @@ +package org.rsmod.content.generic.locs.passages + +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.ServerCacheManager +import dev.openrune.definition.constants.ConstantProvider +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ObjectServerType +import jakarta.inject.Inject +import org.rsmod.api.config.constants +import org.rsmod.api.config.refs.params +import org.rsmod.api.player.events.interact.LocDefaultEvents +import org.rsmod.api.player.events.interact.OpDefaultEvent +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.interact.LocInteractions +import org.rsmod.api.player.output.ChatType +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.script.onProtectedEvent +import org.rsmod.content.generic.locs.doors.DoorTranslations +import org.rsmod.content.generic.locs.gate.GateTranslations +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.game.map.collision.get +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 +import org.rsmod.routefinder.flag.CollisionFlag + +/** + * Catch-all for the doors, gates, ladders, staircases, trapdoors, cave mouths and stiles that no + * other script claims. It runs only when a loc op has no type, content or category handler, so + * the data-driven door, gate and ladder scripts always take precedence. + * + * Doors and gates find their open or closed twin by cache name: `metalgateclosedl` and + * `metalgateopenl` are the same panel, as are `poordoor` and `poordooropen`, whatever the twin's + * display name. A closed panel with a matching panel beside it is one half of a double door or a + * two-panel gate, and both halves swing together using the same geometry as `DoubleDoorScript` + * (each panel on its own post; the fold-to-one-side fence gates stay with `PicketGate`). A panel with no open form in the cache swings its own model instead of + * vanishing. Every panel this script opens is remembered until it resets, so closing it puts the + * original panels back exactly where they were. + * + * Ladders and stairs move the player one level, or between the surface and the dungeon copy of + * the map 6400 tiles north, and land them on the nearest free tile. Cave mouths do the same + * surface/dungeon swap. + */ +class GenericPassageScript +@Inject +constructor( + private val locRepo: LocRepository, + private val collision: CollisionFlagMap, + private val locInteractions: LocInteractions, +) : PluginScript() { + /** Open panels spawned by this script, keyed by where they stand, and how to undo them. */ + private val openedPanels = HashMap() + + /** Cache name (without the `loc.` prefix) of every named loc, by id. */ + private val locNames: Map by lazy { + // `ConstantProvider.mappings` is the loaded gameval table; `getMappingProvider()` is an + // unrelated default provider that stays empty. + val table = ConstantProvider.mappings[RSCMType.LOC.prefix].orEmpty() + HashMap(table.size).also { names -> + for ((name, id) in table) { + names[id] = name.removePrefix("loc.").lowercase() + } + } + } + + /** Every named loc in the cache grouped by [twinKey]. */ + private val locsByTwinKey: Map> by lazy { + ServerCacheManager.getObjects().values.filter { it.id in locNames }.groupBy { twinKey(it) } + } + + override fun ScriptContext.startup() { + onProtectedEvent(OpDefaultEvent.ID) { interact(it.loc, it.type, 0) } + onProtectedEvent(OpDefaultEvent.ID) { interact(it.loc, it.type, 1) } + onProtectedEvent(OpDefaultEvent.ID) { interact(it.loc, it.type, 2) } + } + + private suspend fun ProtectedAccess.interact( + loc: BoundLocInfo, + type: ObjectServerType, + opIndex: Int, + ) { + val op = type.actions.getOpOrNull(opIndex) + val action = op?.let { Passages.classify(type.name, it, loc.shape) } + if (action == null) { + mes(constants.dm_default, ChatType.Engine) + logger.debug { "No generic passage handling for loc '${type.name}' op=$op type=$type" } + return + } + arriveDelay() + when (action) { + PassageAction.OpenDoor -> openDoor(loc, type) + PassageAction.CloseDoor -> closeDoor(loc, type) + PassageAction.OpenTrapdoor -> openTrapdoor(loc, type) + PassageAction.ClimbUp -> climb(type, up = true) + PassageAction.ClimbDown -> climb(type, up = false) + PassageAction.ClimbEither -> climbEither(type) + PassageAction.Enter -> enter() + PassageAction.ClimbOver -> climbOver(loc) + } + } + + /* Doors and gates */ + + /** + * A panel as it stands in the world. [base] is the loc actually in the map (a multi-loc's + * parent for gates such as Al Kharid's) and is what gets deleted or put back; [vis] is the + * variant the player sees and is what the open or closed twin is worked out from. + */ + private data class Panel( + val coords: CoordGrid, + val shape: LocShape, + val angle: LocAngle, + val base: ObjectServerType, + val vis: ObjectServerType, + ) + + private data class PanelKey(val coords: CoordGrid, val locId: Int) + + private data class OpenedPassage(val opened: List, val closed: List) + + private fun ProtectedAccess.openDoor(loc: BoundLocInfo, type: ObjectServerType) { + // A panel swinging its own model keeps its `Open` op; a second click closes it. + if (openedPanels.containsKey(PanelKey(loc.coords, type.id))) { + closeDoor(loc, type) + return + } + val sound = type.paramOrNull(params.opensound) + if (sound != null) soundSynth(sound) else soundSynth(DEFAULT_OPEN_SOUND) + + val base = ServerCacheManager.getObject(loc.id) ?: type + val clicked = Panel(loc.coords, loc.shape, loc.angle, base, type) + val plan = planOpen(clicked) + val swingsOntoPlayer = + clicked.shape == LocShape.WallDiagonal && plan?.opened?.any { it.coords == coords } == true + if (plan == null || swingsOntoPlayer) { + // A shape we cannot swing, or a diagonal door that would land on the player: just let + // them pass. + locRepo.del(loc, DOOR_DURATION) + return + } + logger.debug { + val closed = plan.closed.joinToString { "${it.vis.internalNameOrEmpty()}@${it.coords}/${it.angle}" } + val opened = plan.opened.joinToString { "${it.vis.internalNameOrEmpty()}@${it.coords}/${it.angle}" } + "Opening passage picket=${clicked.isPicketGate()} closed=[$closed] opened=[$opened]" + } + for (panel in plan.closed) { + val info = findPanel(panel) + if (info == null) { + logger.warn { "Closed panel not found in map: ${panel.vis.internalNameOrEmpty()} at ${panel.coords}" } + continue + } + locRepo.del(info, DOOR_DURATION) + } + for (panel in plan.opened) { + val key = PanelKey(panel.coords, panel.base.id) + locRepo.add(panel.coords, panel.base, DOOR_DURATION, panel.angle, panel.shape) { + openedPanels.remove(key) + } + openedPanels[key] = plan + } + } + + /** + * Works out where the clicked panel, and its partner if it has one, go when opened. Returns + * `null` only for shapes that cannot be swung. + */ + private fun ProtectedAccess.planOpen(clicked: Panel): OpenedPassage? { + if (!clicked.shape.isTranslatable()) { + return null + } + // The partner sits at the first offset when the clicked panel is the left one. + val partnerRight = + findClosedPartner( + clicked, + DoorTranslations.translateClose(clicked.coords, clicked.shape, clicked.angle), + ) + val partnerLeft = + if (partnerRight != null) { + null + } else { + findClosedPartner( + clicked, + DoorTranslations.translateCloseOpposite( + clicked.coords, + clicked.shape, + clicked.angle, + ), + ) + } + val left = partnerLeft ?: clicked + val right = if (partnerLeft != null) clicked else partnerRight + + val picket = clicked.isPicketGate() + val leftOpened = left.opened(isLeft = true, alone = right == null, picket = picket) + val rightOpened = right?.opened(isLeft = false, alone = false, picket = picket) + return OpenedPassage( + opened = listOfNotNull(leftOpened, rightOpened), + closed = listOfNotNull(left, right), + ) + } + + /** + * Where this closed panel stands once open. Doors, metal gates and castle gates follow + * `DoorScript` for a lone panel and `DoubleDoorScript` for a pair: every panel moves one tile + * out of the doorway and turns on the post it hangs from, the left panel anticlockwise and + * the right panel clockwise. Wooden fence gates ([picket]) follow `PicketGate` instead, both + * panels folding back to the same side. With no open form in the cache the closed model + * itself is turned on its hinge. + */ + private fun Panel.opened(isLeft: Boolean, alone: Boolean, picket: Boolean): Panel { + val openType = findTwin(vis, "Close") ?: vis + val (openCoords, openAngle) = + when { + picket && isLeft -> coords + GateTranslations.leftGateOpen(shape, angle) to angle.turn(3) + picket -> coords + GateTranslations.rightGateOpen(shape, angle) to angle.turn(3) + isLeft && !alone -> DoorTranslations.translateOpen(coords, shape, angle) to angle.turn(3) + else -> DoorTranslations.translateOpen(coords, shape, angle) to angle.turn(1) + } + return Panel(openCoords, shape, openAngle, base = openType, vis = openType) + } + + /** + * Wooden fence gates fold both panels to one side, unlike metal and castle gates which swing + * like double doors. The cache names them by their fence, and the arena gate is one too. + */ + private fun Panel.isPicketGate(): Boolean { + if (shape != LocShape.WallStraight) { + return false + } + val name = vis.internalNameOrEmpty() + return PICKET_GATE_NAMES.any { name.contains(it) } + } + + /** + * A closed panel of the same kind as [like] standing at [coords], facing the same way. The + * candidate's visible variant is resolved through the player's vars so a multi-loc gate + * matches its neighbour by what the player sees, not by its unnamed parent loc. + */ + private fun ProtectedAccess.findClosedPartner(like: Panel, coords: CoordGrid): Panel? { + for (candidate in locRepo.findAll(coords)) { + if (candidate.shape != like.shape || candidate.angle != like.angle) { + continue + } + val base = ServerCacheManager.getObject(candidate.entity.id) ?: continue + val vis = visibleType(candidate, base) + if (vis.name != like.vis.name || vis.actions.getOpOrNull(0) != "Open") { + continue + } + return Panel(candidate.coords, candidate.shape, candidate.angle, base, vis) + } + return null + } + + private fun ProtectedAccess.visibleType( + info: LocInfo, + base: ObjectServerType, + ): ObjectServerType { + val bound = BoundLocInfo(info, base) + val multi = locInteractions.multiLoc(bound, base, player.vars) ?: return base + return ServerCacheManager.getObject(multi.id) ?: base + } + + private fun findPanel(panel: Panel): LocInfo? = locRepo.findExact(panel.coords, panel.base) + + private fun ProtectedAccess.closeDoor(loc: BoundLocInfo, type: ObjectServerType) { + val tracked = openedPanels[PanelKey(loc.coords, type.id)] + val restore = tracked?.closed ?: singleClosed(loc, type)?.let(::listOf) ?: return + val closesOntoPlayer = + loc.shape == LocShape.WallDiagonal && restore.any { it.coords == coords } + if (closesOntoPlayer) { + mes("You cannot close the door while you are standing in the doorway.") + return + } + val sound = type.paramOrNull(params.closesound) + if (sound != null) soundSynth(sound) else soundSynth(DEFAULT_CLOSE_SOUND) + + val remove = + tracked?.opened + ?: listOf(Panel(loc.coords, loc.shape, loc.angle, base = type, vis = type)) + for (panel in remove) { + val info = findPanel(panel) ?: continue + locRepo.del(info, DOOR_DURATION) + openedPanels.remove(PanelKey(panel.coords, panel.base.id)) + } + for (panel in restore) { + locRepo.add(panel.coords, panel.base, DOOR_DURATION, panel.angle, panel.shape) + } + } + + /** + * Where an open panel this script did not open (one the map itself left open) goes when + * closed, treating it as a lone door or gate. + */ + private fun singleClosed(loc: BoundLocInfo, type: ObjectServerType): Panel? { + val closedType = findTwin(type, "Open") ?: return null + if (!loc.shape.isTranslatable()) { + return null + } + val panel = Panel(loc.coords, loc.shape, loc.angle, base = type, vis = type) + return if (panel.isPicketGate()) { + Panel( + loc.coords + GateTranslations.leftGateClose(loc.shape, loc.angle), + loc.shape, + loc.angle.turn(-3), + base = closedType, + vis = closedType, + ) + } else { + Panel( + DoorTranslations.translateClose(loc.coords, loc.shape, loc.angle), + loc.shape, + loc.angle.turn(-1), + base = closedType, + vis = closedType, + ) + } + } + + private fun LocShape.isTranslatable(): Boolean = + this == LocShape.WallStraight || this == LocShape.WallDiagonal + + /** + * The open or closed counterpart of [type]: a loc with the same footprint whose first op is + * [firstOp] and whose cache name is the same once "open" and "closed" are taken out of it + * (`poordoor`/`poordooropen`, `metalgateclosedl`/`metalgateopenl`, `door_l`/`door_l_open`). + * Failing that, the nearest loc by id with the same display name and footprint. + */ + private fun findTwin(type: ObjectServerType, firstOp: String): ObjectServerType? { + val sameName = locsByTwinKey[twinKey(type)]?.filter { it.id != type.id && it.sameFootprint(type) } + val byName = + sameName?.firstOrNull { it.actions.getOpOrNull(0) == firstOp } + ?: sameName?.firstOrNull { candidate -> + val wantOpen = firstOp == "Close" + val looksOpen = candidate.internalNameOrEmpty().contains("open") + candidate.actions.getOpOrNull(0) == null && looksOpen == wantOpen + } + if (byName != null) { + return byName + } + for (offset in TWIN_SEARCH_OFFSETS) { + val candidate = ServerCacheManager.getObject(type.id + offset) ?: continue + if (candidate.name != type.name || !candidate.sameFootprint(type)) { + continue + } + if (candidate.actions.getOpOrNull(0) == firstOp) { + return candidate + } + } + return null + } + + private fun ObjectServerType.sameFootprint(other: ObjectServerType): Boolean = + width == other.width && length == other.length + + private fun ObjectServerType.internalNameOrEmpty(): String = locNames[id] ?: "" + + /** The cache name with every "open"/"closed" marker and underscore removed. */ + private fun twinKey(type: ObjectServerType): String { + val name = type.internalNameOrEmpty() + if (name.isEmpty()) { + return "#${type.id}" + } + return name.replace("closed", "").replace("open", "").replace("close", "").replace("_", "") + } + + /* Trapdoors */ + + private suspend fun ProtectedAccess.openTrapdoor(loc: BoundLocInfo, type: ObjectServerType) { + val opened = findTwin(type, "Climb-down") ?: findTwin(type, "Close") + if (opened != null) { + soundSynth(DEFAULT_OPEN_SOUND) + locRepo.change(loc, opened, DOOR_DURATION) + return + } + // No open form in the cache: go straight down. + climb(type, up = false) + } + + /* Ladders, stairs and ropes */ + + private suspend fun ProtectedAccess.climbEither(type: ObjectServerType) { + val up = choice2("Climb up.", true, "Climb down.", false, title = "Climb up or down?") + climb(type, up) + } + + private suspend fun ProtectedAccess.climb(type: ObjectServerType, up: Boolean) { + val dest = Passages.climbDestination(coords, up)?.let(::landing) + if (dest == null) { + mes(if (up) "You cannot see a way up from here." else "You cannot see a way down from here.") + return + } + val climbAnim = type.paramOrNull(params.climb_anim) + if (climbAnim != null) { + anim(RSCM.getReverseMapping(RSCMType.SEQ, climbAnim.id)) + } else if (type.name in LADDER_NAMES) { + anim(LADDER_ANIM) + } + delay(1) + telejump(dest, TeleportType.Exempt) + } + + /* Caves, tunnels and other mouths of the underground */ + + private suspend fun ProtectedAccess.enter() { + val dest = landing(Passages.enterDestination(coords)) + if (dest == null) { + mes("You cannot see a way through.") + return + } + delay(1) + telejump(dest, TeleportType.Exempt) + } + + /* Stiles and fences */ + + private suspend fun ProtectedAccess.climbOver(loc: BoundLocInfo) { + val dest = Passages.farSide(loc, coords) + if (dest == null || !walkable(dest)) { + mes("You cannot climb over from here.") + return + } + hopTo(dest, CLIMB_OVER_ANIM, ticks = 2) + } + + /** + * The nearest free tile to [dest], or `null` if everything around it is blocked or the map + * there is featureless filler rather than somewhere a player can be. + */ + private fun landing(dest: CoordGrid): CoordGrid? { + if (!hasSurroundings(dest)) { + return null + } + return Passages.landingCandidates(dest).firstOrNull(::walkable) + } + + private fun walkable(coords: CoordGrid): Boolean = + collision.isZoneValid(coords) && !collision.isWalkBlocked(coords) + + /** + * Whether anything at all (a wall, a loc, a blocked tile) sits within [SURROUNDINGS_RADIUS] + * tiles of [centre]. Real rooms and caves always have something close by; the black filler + * that pads out a map square has nothing, and a guessed destination that lands in it would + * strand the player. + */ + private fun hasSurroundings(centre: CoordGrid): Boolean { + for (dz in -SURROUNDINGS_RADIUS..SURROUNDINGS_RADIUS) { + for (dx in -SURROUNDINGS_RADIUS..SURROUNDINGS_RADIUS) { + val tile = centre.translate(dx, dz) + if (!collision.isZoneValid(tile)) { + continue + } + if (collision[tile] and SURROUNDINGS_MASK != 0) { + return true + } + } + } + return false + } + + private companion object { + private val logger = InlineLogger() + + /** How long (in cycles) an opened door or trapdoor stays changed before it resets. */ + private const val DOOR_DURATION = 500 + + private val TWIN_SEARCH_OFFSETS = + listOf(1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, 8, -8, 10, -10, 12, -12) + + private val LADDER_NAMES = setOf("Ladder", "Rope", "Rope ladder") + + /** Cache-name fragments of the wooden fence gates that fold to one side. */ + private val PICKET_GATE_NAMES = listOf("fence", "wooden", "pvpa_access_gate") + + private const val SURROUNDINGS_RADIUS = 8 + + /** Every collision flag except the roof marker. */ + private const val SURROUNDINGS_MASK = CollisionFlag.ROOF.inv() + + private const val DEFAULT_OPEN_SOUND = "synth.door_open" + private const val DEFAULT_CLOSE_SOUND = "synth.door_close" + private const val LADDER_ANIM = "seq.human_reachforladder" + private const val CLIMB_OVER_ANIM = "seq.human_walk_style" + } +} diff --git a/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/LinkedPassageScript.kt b/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/LinkedPassageScript.kt new file mode 100644 index 000000000..edf79cb38 --- /dev/null +++ b/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/LinkedPassageScript.kt @@ -0,0 +1,78 @@ +package org.rsmod.content.generic.locs.passages + +import jakarta.inject.Inject +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpLoc1 +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 end of a [LinkedPassage]: the loc the player clicks and where a player arriving from the + * other end is put. With no [landing] the arrival tile is the nearest free tile to the loc, + * which is fine on the surface but not at a cave mouth, where the tile behind the opening is + * unflagged black filler; those ends name their floor tile explicitly. + */ +data class PassageEnd(val loc: String, val coords: CoordGrid, val landing: CoordGrid? = null) + +/** + * A two-way passage whose ends do not sit 6400 tiles apart, so the generic surface/dungeon rule + * cannot find the far side. + */ +data class LinkedPassage(val name: String, val a: PassageEnd, val b: PassageEnd) + +/** Passages whose far side had to be read from the map rather than worked out. */ +object LinkedPassages { + val all: List = + listOf( + LinkedPassage( + name = "Wilderness Slayer Cave (south)", + a = PassageEnd("loc.wild_slayer_cave_south_entrance", CoordGrid(3259, 3664)), + b = + PassageEnd( + "loc.wild_slayer_cave_south_exit", + CoordGrid(3384, 10050), + landing = CoordGrid(3385, 10052), + ), + ), + LinkedPassage( + name = "Wilderness Slayer Cave (north)", + a = PassageEnd("loc.wild_slayer_cave_north_entrance", CoordGrid(3293, 3746)), + b = + PassageEnd( + "loc.wild_slayer_cave_north_exit", + CoordGrid(3405, 10146), + landing = CoordGrid(3407, 10145), + ), + ), + ) +} + +class LinkedPassageScript @Inject constructor(private val collision: CollisionFlagMap) : + PluginScript() { + override fun ScriptContext.startup() { + for (passage in LinkedPassages.all) { + onOpLoc1(passage.a.loc) { travel(passage.b) } + onOpLoc1(passage.b.loc) { travel(passage.a) } + } + } + + private suspend fun ProtectedAccess.travel(end: PassageEnd) { + arriveDelay() + val dest = + end.landing + ?: Passages.landingCandidates(end.coords).firstOrNull { + collision.isZoneValid(it) && !collision.isWalkBlocked(it) + } + if (dest == null) { + mes("You cannot see a way through.") + return + } + delay(1) + telejump(dest, TeleportType.Exempt) + } +} diff --git a/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/PassageMoves.kt b/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/PassageMoves.kt new file mode 100644 index 000000000..609964e91 --- /dev/null +++ b/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/PassageMoves.kt @@ -0,0 +1,54 @@ +package org.rsmod.content.generic.locs.passages + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import kotlin.math.sign +import org.rsmod.api.config.constants +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.map.CoordGrid + +/** Client cycles (20ms) in one server tick; `exactmove` delays are expressed in client cycles. */ +private const val CLIENT_CYCLES_PER_TICK = 30 + +/** + * Glides the player from their tile to [dest] over [ticks] cycles while [seq] plays, then leaves + * them standing on [dest]. Used for hopping stiles and jumping the Wilderness ditch. + */ +internal suspend fun ProtectedAccess.hopTo(dest: CoordGrid, seq: String, ticks: Int) { + val start = coords + anim(seq) + exactMove( + start = start, + end = dest, + delay1 = 0, + delay2 = ticks * CLIENT_CYCLES_PER_TICK, + dir = faceTowards(start, dest), + teleportType = TeleportType.Exempt, + ) + delay(ticks) +} + +/** Number of server ticks [seq] plays for, or [fallback] when the cache holds no duration. */ +internal fun seqTicks(seq: String, fallback: Int): Int { + val type = ServerCacheManager.getAnim(seq.asRSCM(RSCMType.SEQ)) + val ticks = type?.tickDuration ?: 0 + return if (ticks > 0) ticks else fallback +} + +private fun faceTowards(from: CoordGrid, to: CoordGrid): Int { + val dx = (to.x - from.x).sign + val dz = (to.z - from.z).sign + return when { + dx == 0 && dz > 0 -> constants.em_face_north + dx == 0 && dz < 0 -> constants.em_face_south + dx > 0 && dz == 0 -> constants.em_face_east + dx < 0 && dz == 0 -> constants.em_face_west + dx > 0 && dz > 0 -> constants.em_face_northeast + dx < 0 && dz > 0 -> constants.em_face_northwest + dx > 0 && dz < 0 -> constants.em_face_southeast + dx < 0 && dz < 0 -> constants.em_face_southwest + else -> constants.em_face_south + } +} diff --git a/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/Passages.kt b/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/Passages.kt new file mode 100644 index 000000000..a11a3a71a --- /dev/null +++ b/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/Passages.kt @@ -0,0 +1,199 @@ +package org.rsmod.content.generic.locs.passages + +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.game.loc.LocShape +import org.rsmod.map.CoordGrid + +/** What a click on a passage-like loc should do, worked out from the loc's name, op and shape. */ +enum class PassageAction { + OpenDoor, + CloseDoor, + OpenTrapdoor, + ClimbUp, + ClimbDown, + ClimbEither, + Enter, + ClimbOver, +} + +/** + * Pure rules for the generic passage handling: which locs count as doors, ladders, caves and + * stiles, where climbing leads, and which side of an obstacle a player ends up on. + */ +object Passages { + private val DOOR_NAMES = + setOf( + "Door", + "Doors", + "Gate", + "Gates", + "Large door", + "Big door", + "Wooden door", + "Metal door", + "Iron door", + "Cell door", + "Prison door", + "Wooden gate", + "Iron gate", + ) + + private val CLIMB_NAMES = + setOf( + "Ladder", + "Staircase", + "Stairs", + "Rope", + "Trapdoor", + "Hole", + "Exit", + "Steps", + "Spiral staircase", + "Rope ladder", + "Vine", + "Climbing rocks", + ) + + private val ENTER_NAMES = + setOf( + "Cave", + "Cave entrance", + "Cave exit", + "Tunnel", + "Tunnel entrance", + "Hole", + "Passage", + "Passageway", + "Crevice", + "Entrance", + "Exit", + "Opening", + "Dungeon entrance", + "Cavern", + "Cavern entrance", + "Mine entrance", + "Crack", + "Gap", + ) + + private val CLIMB_OVER_NAMES = setOf("Stile", "Fence", "Low fence", "Broken fence", "Wall") + + private val ENTER_OPS = + setOf( + "Enter", + "Exit", + "Leave", + "Pass", + "Pass-through", + "Squeeze-through", + "Crawl", + "Crawl-through", + "Go-through", + "Walk-through", + ) + + private val WALL_SHAPES = + setOf( + LocShape.WallStraight, + LocShape.WallDiagonalCorner, + LocShape.WallL, + LocShape.WallSquareCorner, + LocShape.WallDiagonal, + ) + + /** The z coordinate from which the map is the underground copy of the surface above it. */ + const val UNDERGROUND_Z = 6400 + + fun classify(name: String, op: String, shape: LocShape): PassageAction? { + val isWall = shape in WALL_SHAPES + return when (op) { + "Open" -> + when { + name == "Trapdoor" -> PassageAction.OpenTrapdoor + name in DOOR_NAMES || isWall -> PassageAction.OpenDoor + else -> null + } + "Close" -> if (name in DOOR_NAMES || isWall) PassageAction.CloseDoor else null + "Climb-up", + "Walk-up" -> if (name in CLIMB_NAMES) PassageAction.ClimbUp else null + "Climb-down", + "Walk-down" -> if (name in CLIMB_NAMES) PassageAction.ClimbDown else null + "Climb" -> if (name in CLIMB_NAMES) PassageAction.ClimbEither else null + "Climb-over" -> if (name in CLIMB_OVER_NAMES) PassageAction.ClimbOver else null + in ENTER_OPS -> + if (name in ENTER_NAMES || name in DOOR_NAMES) PassageAction.Enter else null + else -> null + } + } + + /** + * Where climbing up or down from [from] should land, before the tile is checked for space. + * Surface dungeons live 6400 tiles north of the ground they sit under, so a ground-level + * climb-down goes there and a dungeon climb-up comes back; anything else is one level. + */ + fun climbDestination(from: CoordGrid, up: Boolean): CoordGrid? { + val underground = from.z >= UNDERGROUND_Z + return if (up) { + when { + underground && from.level == 0 -> from.translateZ(-UNDERGROUND_Z) + from.level < 3 -> from.translateLevel(1) + else -> null + } + } else { + when { + from.level > 0 -> from.translateLevel(-1) + !underground -> from.translateZ(UNDERGROUND_Z) + else -> null + } + } + } + + /** The other side of a cave or tunnel mouth: the matching tile above or below ground. */ + fun enterDestination(from: CoordGrid): CoordGrid = + if (from.z >= UNDERGROUND_Z) { + from.translateZ(-UNDERGROUND_Z) + } else { + from.translateZ(UNDERGROUND_Z) + } + + /** + * The tile just past [loc] from where [from] stands, or `null` if [from] is not beside it. + * A stile or ditch is crossed along whichever axis the player is outside of it on. + */ + fun farSide(loc: BoundLocInfo, from: CoordGrid): CoordGrid? { + val minX = loc.x + val maxX = loc.x + loc.adjustedWidth - 1 + val minZ = loc.z + val maxZ = loc.z + loc.adjustedLength - 1 + val x = + when { + from.x < minX -> maxX + 1 + from.x > maxX -> minX - 1 + else -> from.x + } + val z = + when { + from.z < minZ -> maxZ + 1 + from.z > maxZ -> minZ - 1 + else -> from.z + } + if (x == from.x && z == from.z) { + return null + } + return CoordGrid(x, z, from.level) + } + + /** + * Candidate landing tiles around [dest], nearest first, so a player climbing onto a tile a + * ladder occupies is put beside it rather than inside it. + */ + fun landingCandidates(dest: CoordGrid, radius: Int = 2): List { + val tiles = mutableListOf() + for (dz in -radius..radius) { + for (dx in -radius..radius) { + tiles += dest.translate(dx, dz) + } + } + return tiles.sortedWith(compareBy({ it.chebyshevDistance(dest) }, { it.z }, { it.x })) + } +} diff --git a/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/WildernessDitchScript.kt b/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/WildernessDitchScript.kt new file mode 100644 index 000000000..81602ced0 --- /dev/null +++ b/content/generic/generic-locs/src/main/kotlin/org/rsmod/content/generic/locs/passages/WildernessDitchScript.kt @@ -0,0 +1,35 @@ +package org.rsmod.content.generic.locs.passages + +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The ditch along the southern edge of the Wilderness. `Cross` jumps the player from whichever + * side they stand on to the tile just past the far edge. + */ +class WildernessDitchScript : PluginScript() { + override fun ScriptContext.startup() { + for (ditch in DITCHES) { + onOpLoc1(ditch) { cross(it.loc) } + } + } + + private suspend fun ProtectedAccess.cross(loc: BoundLocInfo) { + arriveDelay() + val dest = Passages.farSide(loc, coords) + if (dest == null) { + mes("You cannot jump the ditch from here.") + return + } + hopTo(dest, JUMP_ANIM, ticks = seqTicks(JUMP_ANIM, fallback = 2)) + } + + private companion object { + private val DITCHES = + listOf("loc.ditch_wilderness_cover", "loc.ditch_wilderness_cover_members") + private const val JUMP_ANIM = "seq.wild_ditch_jump" + } +} diff --git a/content/generic/generic-locs/src/test/kotlin/org/rsmod/content/generic/locs/passages/PassagesTest.kt b/content/generic/generic-locs/src/test/kotlin/org/rsmod/content/generic/locs/passages/PassagesTest.kt new file mode 100644 index 000000000..f6adf4f8e --- /dev/null +++ b/content/generic/generic-locs/src/test/kotlin/org/rsmod/content/generic/locs/passages/PassagesTest.kt @@ -0,0 +1,69 @@ +package org.rsmod.content.generic.locs.passages + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.game.loc.LocAngle +import org.rsmod.game.loc.LocEntity +import org.rsmod.game.loc.LocShape +import org.rsmod.map.CoordGrid + +class PassagesTest { + @Test + fun `doors ladders caves and stiles are told apart by name and op`() { + val ground = LocShape.CentrepieceStraight + assertEquals(PassageAction.OpenDoor, Passages.classify("Door", "Open", LocShape.WallStraight)) + assertEquals(PassageAction.CloseDoor, Passages.classify("Gate", "Close", LocShape.WallStraight)) + assertEquals(PassageAction.OpenTrapdoor, Passages.classify("Trapdoor", "Open", ground)) + assertEquals(PassageAction.ClimbUp, Passages.classify("Ladder", "Climb-up", ground)) + assertEquals(PassageAction.ClimbDown, Passages.classify("Stairs", "Climb-down", ground)) + assertEquals(PassageAction.ClimbEither, Passages.classify("Staircase", "Climb", ground)) + assertEquals(PassageAction.Enter, Passages.classify("Cave entrance", "Enter", ground)) + assertEquals(PassageAction.ClimbOver, Passages.classify("Stile", "Climb-over", ground)) + assertNull(Passages.classify("Crate", "Search", ground)) + assertNull(Passages.classify("Tree", "Climb-up", ground)) + } + + @Test + fun `climbing moves one level or between the surface and its dungeon`() { + val ground = CoordGrid(3222, 3218, 0) + assertEquals(CoordGrid(3222, 3218, 1), Passages.climbDestination(ground, up = true)) + assertEquals(CoordGrid(3222, 9618, 0), Passages.climbDestination(ground, up = false)) + val upstairs = CoordGrid(3222, 3218, 2) + assertEquals(CoordGrid(3222, 3218, 1), Passages.climbDestination(upstairs, up = false)) + val dungeon = CoordGrid(3222, 9618, 0) + assertEquals(ground, Passages.climbDestination(dungeon, up = true)) + assertNull(Passages.climbDestination(dungeon, up = false)) + } + + @Test + fun `stiles and ditches are crossed to the tile past the far edge`() { + // A 1x2 loc lying across z = 3521..3522, like the Wilderness ditch. + val ditch = + BoundLocInfo( + coords = CoordGrid(3087, 3521), + entity = LocEntity(23271, LocShape.CentrepieceStraight.id, LocAngle.West.id), + layer = 2, + width = 1, + length = 2, + forceApproachFlags = 0, + ) + assertEquals(CoordGrid(3087, 3523), Passages.farSide(ditch, CoordGrid(3087, 3520))) + assertEquals(CoordGrid(3087, 3520), Passages.farSide(ditch, CoordGrid(3087, 3523))) + assertNull(Passages.farSide(ditch, CoordGrid(3087, 3521))) + + // The same loc turned a quarter, so it is 2 wide and 1 long. + val turned = + ditch.copy(entity = LocEntity(23271, LocShape.CentrepieceStraight.id, LocAngle.North.id)) + assertEquals(CoordGrid(3089, 3521), Passages.farSide(turned, CoordGrid(3086, 3521))) + } + + @Test + fun `landing candidates start at the destination and move outwards`() { + val dest = CoordGrid(3200, 3200) + val candidates = Passages.landingCandidates(dest, radius = 1) + assertEquals(dest, candidates.first()) + assertEquals(9, candidates.size) + } +} diff --git a/content/generic/generic-npcs/build.gradle.kts b/content/generic/generic-npcs/build.gradle.kts index fb1b108e6..0232ce679 100644 --- a/content/generic/generic-npcs/build.gradle.kts +++ b/content/generic/generic-npcs/build.gradle.kts @@ -3,6 +3,7 @@ plugins { } dependencies { + implementation(projects.api.combat.combatFormulas) implementation(projects.api.pluginCommons) implementation(projects.content.interfaces.bank) } diff --git a/content/generic/generic-npcs/src/main/kotlin/org/rsmod/content/generic/npcs/darkwizard/DarkWizardScript.kt b/content/generic/generic-npcs/src/main/kotlin/org/rsmod/content/generic/npcs/darkwizard/DarkWizardScript.kt new file mode 100644 index 000000000..d65fceecc --- /dev/null +++ b/content/generic/generic-npcs/src/main/kotlin/org/rsmod/content/generic/npcs/darkwizard/DarkWizardScript.kt @@ -0,0 +1,292 @@ +package org.rsmod.content.generic.npcs.darkwizard + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.area.checker.AreaChecker +import org.rsmod.api.combat.commons.npc.attackRate +import org.rsmod.api.combat.commons.player.finishNpcHit +import org.rsmod.api.combat.commons.player.queueCombatRetaliate +import org.rsmod.api.combat.formulas.AccuracyFormulae +import org.rsmod.api.combat.formulas.MaxHitFormulae +import org.rsmod.api.npc.access.StandardNpcAccess +import org.rsmod.api.npc.isInCombat +import org.rsmod.api.npc.vars.intVarn +import org.rsmod.api.npc.vars.typePlayerUidVarn +import org.rsmod.api.player.hit.modifier.PlayerHitModifier +import org.rsmod.api.player.isInPvnCombat +import org.rsmod.api.player.isInPvpCombat +import org.rsmod.api.player.isValidTarget +import org.rsmod.api.player.stat.stat +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.player.stat.statSub +import org.rsmod.api.player.vars.intVarp +import org.rsmod.api.player.vars.typeNpcUidVarp +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.api.script.onAiApPlayer2 +import org.rsmod.api.script.onAiOpPlayer2 +import org.rsmod.api.script.onPlayerSoftQueueWithArgs +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player +import org.rsmod.game.entity.npc.NpcUid +import org.rsmod.game.entity.player.PlayerUid +import org.rsmod.game.hit.HitType +import org.rsmod.game.proj.ProjAnim +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Dark wizards cast spells rather than the generic magic attack every other caster npc uses. + * + * The young wizards (combat levels 7 and 11) cast Water Strike and Confuse; the bearded and old + * wizards (levels 20, 22 and 23) cast Earth Strike and Weaken. A strike is an ordinary magic + * attack with the spell's own casting graphic, projectile, impact graphic and sounds, capped at + * the spell's max hit. A curse rolls magic accuracy like a strike but deals no damage: on a + * successful cast it lowers the player's Attack (Confuse) or Strength (Weaken) by 5% of the base + * level, and does nothing further if that stat is already lowered. + */ +class DarkWizardScript +@Inject +constructor( + private val accuracy: AccuracyFormulae, + private val maxHits: MaxHitFormulae, + private val worldRepo: WorldRepository, + private val hitModifier: PlayerHitModifier, + private val areaChecker: AreaChecker, +) : PluginScript() { + override fun ScriptContext.startup() { + for (npc in YOUNG_WIZARDS) { + register(npc, WATER_STRIKE, CONFUSE) + } + for (npc in OLD_WIZARDS) { + register(npc, EARTH_STRIKE, WEAKEN) + } + // The drain is queued to land with the projectile. A soft queue fires whatever the + // player is doing, as a spell that has already been cast should. + onPlayerSoftQueueWithArgs(CURSE_QUEUE) { player.applyCurse(args) } + } + + private fun Player.applyCurse(landing: CurseLanding) { + // The curse only bites when the stat is at its base level; a lowered stat stays as it is. + if (stat(landing.stat) >= statBase(landing.stat)) { + statSub(landing.stat, constant = 0, percent = CURSE_DRAIN_PERCENT) + } + } + + private fun ScriptContext.register(npc: String, strike: StrikeSpell, curse: CurseSpell) { + val type = ServerCacheManager.getNpc(npc.asRSCM(RSCMType.NPC)) ?: error("Missing npc: $npc") + onAiOpPlayer2(type) { attack(it.target, strike, curse) } + onAiApPlayer2(type) { attack(it.target, strike, curse) } + } + + private fun StandardNpcAccess.attack(target: Player, strike: StrikeSpell, curse: CurseSpell) { + if (!canAttack(target)) { + resetMode() + return + } + if (actionDelay > mapClock) { + return + } + if (!npc.isInCombat()) { + resetMode() + return + } + actionDelay = mapClock + npc.attackRate() + setAttackVars(target) + + if (random.of(CURSE_ONE_IN) == 0) { + castCurse(target, curse) + } else { + castStrike(target, strike) + } + } + + private fun StandardNpcAccess.castStrike(target: Player, spell: StrikeSpell) { + val proj = cast(target, spell) + val landed = accuracy.rollMagicAccuracy(npc, target, random) + if (!landed) { + splash(target, proj) + return + } + val maxHit = maxHits.getMagicMaxHit(npc, target).coerceIn(1, spell.maxHit) + val damage = random.of(0..maxHit) + target.spotanim(spell.impact, delay = proj.clientCycles, height = IMPACT_HEIGHT) + worldRepo.soundArea(target, spell.hitSound, delay = proj.clientCycles, radius = SOUND_RADIUS) + target.finishNpcHit(npc, proj.serverCycles, HitType.Magic, damage, hitModifier, proj.clientCycles) + } + + private fun StandardNpcAccess.castCurse(target: Player, spell: CurseSpell) { + val proj = cast(target, spell) + val landed = accuracy.rollMagicAccuracy(npc, target, random) + if (!landed) { + splash(target, proj) + return + } + target.spotanim(spell.impact, delay = proj.clientCycles, height = IMPACT_HEIGHT) + spell.hitSound?.let { + worldRepo.soundArea(target, it, delay = proj.clientCycles, radius = SOUND_RADIUS) + } + target.queueCombatRetaliate(npc, delay = proj.serverCycles) + target.softQueue(CURSE_QUEUE, proj.serverCycles, CurseLanding(spell.stat)) + } + + /** Plays the cast animation, graphic and sound, then launches the spell's projectile. */ + private fun StandardNpcAccess.cast(target: Player, spell: Spell): ProjAnim { + anim(CAST_ANIM) + spotanim(spell.casting, height = CAST_HEIGHT) + worldRepo.soundArea(npc, spell.castSound, radius = SOUND_RADIUS) + val projType = + ServerCacheManager.getProjectile(spell.projanim.asRSCM(RSCMType.PROJANIM)) + ?: error("Missing projanim: ${spell.projanim}") + val proj = ProjAnim.fromNpcToPlayer(npc, target, spell.travel.asRSCM(RSCMType.SPOTANIM), projType) + worldRepo.projAnim(proj) + return proj + } + + private fun StandardNpcAccess.splash(target: Player, proj: ProjAnim) { + target.spotanim(SPLASH_SPOT, delay = proj.clientCycles, height = IMPACT_HEIGHT) + worldRepo.soundArea(target, SPLASH_SOUND, delay = proj.clientCycles, radius = SOUND_RADIUS) + target.queueCombatRetaliate(npc, delay = proj.serverCycles) + } + + private fun StandardNpcAccess.canAttack(target: Player): Boolean { + if (!target.isValidTarget()) { + return false + } + val singleCombat = !mapMultiway(areaChecker) + if (singleCombat) { + if (target.isInPvpCombat()) { + return false + } + if (target.isInPvnCombat()) { + val aggressor = target.aggressiveNpc + if (aggressor != null && aggressor != npc.uid) { + return false + } + } + } + return true + } + + private fun StandardNpcAccess.setAttackVars(target: Player) { + npc.lastAttack = mapClock + npc.attackingPlayer = target.uid + target.lastCombat = mapClock + target.aggressiveNpc = npc.uid + } + + /** What a curse does to its target once its projectile lands. */ + private data class CurseLanding(val stat: String) + + private sealed interface Spell { + val casting: String + val travel: String + val impact: String + val projanim: String + val castSound: String + } + + private data class StrikeSpell( + override val casting: String, + override val travel: String, + override val impact: String, + override val castSound: String, + val hitSound: String, + val maxHit: Int, + ) : Spell { + override val projanim: String = "projanim.magic_spell" + } + + private data class CurseSpell( + override val casting: String, + override val travel: String, + override val impact: String, + override val castSound: String, + val hitSound: String?, + val stat: String, + ) : Spell { + override val projanim: String = "projanim.confuse" + } + + private companion object { + private const val CAST_ANIM = "seq.human_caststrike" + private const val CAST_HEIGHT = 92 + private const val IMPACT_HEIGHT = 124 + private const val SOUND_RADIUS = 10 + private const val SPLASH_SPOT = "spotanim.failedspell_impact" + private const val SPLASH_SOUND = "synth.spellfail" + private const val CURSE_QUEUE = "queue.dark_wizard_curse" + + /** One cast in this many is a curse; the rest are strikes. */ + private const val CURSE_ONE_IN = 3 + + /** Confuse and Weaken lower their stat by 5% of the base level. */ + private const val CURSE_DRAIN_PERCENT = 5 + + private val WATER_STRIKE = + StrikeSpell( + casting = "spotanim.waterstrike_casting", + travel = "spotanim.waterstrike_travel", + impact = "spotanim.waterstrike_impact", + castSound = "synth.waterstrike_cast_and_fire", + hitSound = "synth.waterstrike_hit", + maxHit = 4, + ) + + private val EARTH_STRIKE = + StrikeSpell( + casting = "spotanim.earthstrike_casting", + travel = "spotanim.earthstrike_travel", + impact = "spotanim.earthstrike_impact", + castSound = "synth.earthstrike_cast_and_fire", + hitSound = "synth.earthstrike_hit", + maxHit = 6, + ) + + private val CONFUSE = + CurseSpell( + casting = "spotanim.confuse_casting", + travel = "spotanim.confuse_travel", + impact = "spotanim.confuse_impact", + castSound = "synth.confuse_cast_and_fire", + hitSound = "synth.confuse_hit", + stat = "stat.attack", + ) + + private val WEAKEN = + CurseSpell( + casting = "spotanim.weaken_casting", + travel = "spotanim.weaken_travel", + impact = "spotanim.weaken_impact", + castSound = "synth.weaken_all", + hitSound = null, + stat = "stat.strength", + ) + + /** Combat levels 7 and 11: Water Strike and Confuse. */ + private val YOUNG_WIZARDS = + listOf( + "npc.young_dark_wizard", + "npc.fai_dark_wizard_young_1", + "npc.fai_dark_wizard_young_2", + "npc.qip_ds_young_dark_wizard1", + "npc.qip_ds_young_dark_wizard2", + ) + + /** Combat levels 20, 22 and 23: Earth Strike and Weaken. */ + private val OLD_WIZARDS = + listOf( + "npc.bearded_dark_wizard", + "npc.fai_dark_wizard_old_1", + "npc.fai_dark_wizard_old_2", + "npc.qip_ds_young_dark_wizard3", + "npc.qip_ds_young_dark_wizard4", + ) + + private var Npc.lastAttack: Int by intVarn("varn.lastattack") + private var Npc.attackingPlayer: PlayerUid? by typePlayerUidVarn("varn.attacking_player") + private var Player.lastCombat: Int by intVarp("varp.lastcombat") + private var Player.aggressiveNpc: NpcUid? by typeNpcUidVarp("varp.aggressive_npc") + } +} diff --git a/content/interfaces/bank/src/main/kotlin/org/rsmod/content/interfaces/bank/scripts/BankInvScript.kt b/content/interfaces/bank/src/main/kotlin/org/rsmod/content/interfaces/bank/scripts/BankInvScript.kt index 30b2779e5..6fc202255 100644 --- a/content/interfaces/bank/src/main/kotlin/org/rsmod/content/interfaces/bank/scripts/BankInvScript.kt +++ b/content/interfaces/bank/src/main/kotlin/org/rsmod/content/interfaces/bank/scripts/BankInvScript.kt @@ -99,7 +99,7 @@ constructor( onIfModalDrag("component.bankside:wornops") { dragSideInv(it) } onIfModalDrag("component.bankmain:items", "component.bankmain:tabs") { dragIntoTab(it) } - val wornComponents = bank_equipment_tab_to_slots_map.filterValuesNotNull().map { it.key to RSCM.getReverseMapping(RSCMType.COMPONENT,it.value.packed) } + val wornComponents = bank_equipment_tab_to_slots_map.filterValuesNotNull().map { it.key to RSCM.getReverseMapping(RSCMType.COMPONENT, it.value.packed) } for ((slot, component) in wornComponents) { onIfModalButton(component) { wornOp(slot, it.op) } } @@ -203,8 +203,11 @@ constructor( input } IfButtonOp.Op8 -> Int.MAX_VALUE + // The client draws "View" (looting bag) and "Configure" (rune pouch) as op1 on + // bankside objs; they take the same content hook as the op9 extra op. + IfButtonOp.Op1, IfButtonOp.Op9 -> { - clickBanksideExtraOp(slot) + clickBanksideExtraOp(slot, op) return } else -> throw IllegalStateException("Invalid main inv op: $op") @@ -1099,7 +1102,7 @@ constructor( player.setBankWornBonuses(wornBonuses, weaponSpeeds) } - private suspend fun ProtectedAccess.clickBanksideExtraOp(slot: Int) { + private suspend fun ProtectedAccess.clickBanksideExtraOp(slot: Int, op: IfButtonOp) { val obj = inv[slot] ?: return resendSlot(inv, 0) val type = getInvObj(obj) @@ -1128,7 +1131,7 @@ constructor( return } - val event = HeldBanksideEvents.Type(player, slot, type) + val event = HeldBanksideEvents.Type(player, slot, type, op) publish(event) } diff --git a/content/interfaces/music-tab/build.gradle.kts b/content/interfaces/music-tab/build.gradle.kts new file mode 100644 index 000000000..f6b5296da --- /dev/null +++ b/content/interfaces/music-tab/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.music) + implementation(projects.api.pluginCommons) +} diff --git a/content/interfaces/music-tab/src/main/kotlin/org/rsmod/content/interfaces/music/tab/MusicTabScript.kt b/content/interfaces/music-tab/src/main/kotlin/org/rsmod/content/interfaces/music/tab/MusicTabScript.kt new file mode 100644 index 000000000..7bc7ecd11 --- /dev/null +++ b/content/interfaces/music-tab/src/main/kotlin/org/rsmod/content/interfaces/music/tab/MusicTabScript.kt @@ -0,0 +1,150 @@ +package org.rsmod.content.interfaces.music.tab + +import dev.openrune.definition.type.widget.IfEvent +import dev.openrune.types.aconverted.interf.IfButtonOp +import jakarta.inject.Inject +import org.rsmod.api.music.Music +import org.rsmod.api.music.MusicRepository +import org.rsmod.api.player.music.MusicPlayMode +import org.rsmod.api.player.music.MusicPlayer +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.ui.ifSetEvents +import org.rsmod.api.player.vars.boolVarBit +import org.rsmod.api.player.vars.enumVarp +import org.rsmod.api.player.vars.intVarBit +import org.rsmod.api.script.onIfOpen +import org.rsmod.api.script.onIfOverlayButton +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Server side of the music player side tab (`interface.music`). + * + * The client draws the track list, the unlocked counter and the button states on its own from + * `dbtable.music` and the `varp.musicmulti_*` unlock varps. What it cannot do alone is send a + * button press anywhere, so this script enables the button events, answers the presses and keeps + * the varps the client scripts write locally (`varp.musicplay`, the selected playlist) in sync. + * + * Findings from the cache's client scripts that the handlers below rely on: + * - The jukebox children are created from `db_find(dbcol.music:hidden = 0)`, so the comsub of a + * pressed track is its index in that result. [MusicRepository.jukeboxTracks] mirrors the order. + * - Track ops are `Play`, `Unlock hint` and `Add to`/`Remove from playlist 1..3`. + * - The mode buttons store `0` (area), `1` (shuffle) and `2` (single) in `varp.musicplay`. + * - The playlist dropdown entries live at odd comsubs (`1 + 2 * index`) of `dropdown_content` + * and store the picked index in `varbit.music_current_playlist`. + */ +class MusicTabScript +@Inject +constructor(private val repo: MusicRepository, private val musicPlayer: MusicPlayer) : + PluginScript() { + private val Player.playMode by enumVarp("varp.musicplay") + private var Player.currentPlaylist by intVarBit("varbit.music_current_playlist") + private val Player.keepModeOnPlaylistChange by + boolVarBit("varbit.dont_update_music_on_playlist_change") + + override fun ScriptContext.startup() { + onIfOpen("interface.music") { player.openTab() } + onIfOverlayButton("component.music:jukebox") { player.selectTrack(it.comsub, it.op) } + onIfOverlayButton("component.music:area") { + musicPlayer.selectMode(player, MusicPlayMode.Area) + } + onIfOverlayButton("component.music:shuffle") { + musicPlayer.selectMode(player, MusicPlayMode.Random) + } + onIfOverlayButton("component.music:single") { + musicPlayer.selectMode(player, MusicPlayMode.Manual) + } + onIfOverlayButton("component.music:skip") { musicPlayer.skipTrack(player) } + onIfOverlayButton("component.music:dropdown_content") { + player.selectDropdownEntry(it.comsub) + } + } + + private fun Player.openTab() { + val trackCount = repo.jukeboxTracks().size + ifSetEvents( + "component.music:jukebox", + 0 until trackCount, + IfEvent.Op1, + IfEvent.Op2, + IfEvent.Op3, + IfEvent.Op4, + IfEvent.Op5, + ) + // The "Expand" op on the playlist dropdown button is set by the tab's own client script; + // the cache component carries no events, so the press only reaches the script if we + // enable it. + ifSetEvents("component.music:playlist", -1..-1, IfEvent.Op1) + ifSetEvents("component.music:dropdown_content", 0 until DROPDOWN_CHILD_COUNT, IfEvent.Op1) + musicPlayer.updateNowPlayingText(this) + } + + private fun Player.selectTrack(comsub: Int, op: IfButtonOp) { + val music = repo.jukeboxTrack(comsub) ?: return + when (op) { + IfButtonOp.Op1 -> playTrack(music) + IfButtonOp.Op2 -> mes("This track unlocks ${music.unlockHint}") + IfButtonOp.Op3 -> togglePlaylist(1, music) + IfButtonOp.Op4 -> togglePlaylist(2, music) + IfButtonOp.Op5 -> togglePlaylist(3, music) + else -> {} + } + } + + private fun Player.playTrack(music: Music) { + val played = musicPlayer.playSelected(this, music) + if (!played) { + mes("You have not unlocked this piece of music yet!") + } + } + + private fun Player.togglePlaylist(playlist: Int, music: Music) { + if (musicPlayer.isInPlaylist(this, playlist, music)) { + musicPlayer.removeFromPlaylist(this, playlist, music) + return + } + if (!music.canUnlock || !musicPlayer.hasUnlocked(this, music)) { + mes("You can only add music tracks you have unlocked to a playlist.") + return + } + val added = musicPlayer.addToPlaylist(this, playlist, music) + if (!added) { + mes("Playlist $playlist is full.") + } + } + + private fun Player.selectDropdownEntry(comsub: Int) { + // Each dropdown entry is drawn as a pair of children; the clickable one is the odd child. + if (comsub % 2 == 0) { + return + } + val playlist = (comsub - 1) / 2 + if (playlist !in 0..MusicPlayer.PLAYLIST_COUNT) { + return + } + selectPlaylist(playlist) + } + + /** + * Mirrors the client's `music_playlist_select` script: picking a playlist while in area mode + * moves the player to shuffle mode so the playlist actually plays, and picking "all tracks" + * returns them to area mode, unless the "don't update music on playlist change" setting is on. + */ + private fun Player.selectPlaylist(playlist: Int) { + currentPlaylist = playlist + if (keepModeOnPlaylistChange) { + return + } + if (playlist == 0) { + musicPlayer.selectMode(this, MusicPlayMode.Area) + } else if (playMode == MusicPlayMode.Area) { + musicPlayer.selectMode(this, MusicPlayMode.Random) + } + } + + private companion object { + /** "All tracks" plus the three playlists, two children per dropdown entry. */ + private const val DROPDOWN_CHILD_COUNT = (MusicPlayer.PLAYLIST_COUNT + 1) * 2 + } +} diff --git a/content/interfaces/prayer-tab/src/main/kotlin/org/rsmod/content/interfaces/prayer/tab/scripts/PrayerTabScript.kt b/content/interfaces/prayer-tab/src/main/kotlin/org/rsmod/content/interfaces/prayer/tab/scripts/PrayerTabScript.kt index 509b7dc2e..56bd95807 100644 --- a/content/interfaces/prayer-tab/src/main/kotlin/org/rsmod/content/interfaces/prayer/tab/scripts/PrayerTabScript.kt +++ b/content/interfaces/prayer-tab/src/main/kotlin/org/rsmod/content/interfaces/prayer/tab/scripts/PrayerTabScript.kt @@ -7,6 +7,8 @@ import org.rsmod.api.player.output.ClientScripts import org.rsmod.api.player.output.mes import org.rsmod.api.player.output.soundSynth import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.hook.PlayerRestrictions +import org.rsmod.api.player.hook.RestrictedAction import org.rsmod.api.player.protect.ProtectedAccessLauncher import org.rsmod.api.player.stat.prayerLvl import org.rsmod.api.player.ui.ifClose @@ -30,6 +32,7 @@ private constructor( private val repo: PrayerRepository, private val eventBus: EventBus, private val protectedAccess: ProtectedAccessLauncher, + private val restrictions: PlayerRestrictions, ) : PluginScript() { override fun ScriptContext.startup() { for ((component, prayer) in repo.prayerComponents.map { RSCM.getReverseMapping(RSCMType.COMPONENT,it.key.packed) to it.value }) { @@ -62,7 +65,16 @@ private constructor( } private suspend fun ProtectedAccess.enablePrayer(prayer: Prayer) { + val restriction = restrictions.check(player, RestrictedAction.Prayer) + if (restriction != null) { + player.resyncVar(prayer.enabled) + mes(restriction) + return + } if (player.prayerLvl == 0) { + // The client lit the button when it was clicked; put it back out. + player.resyncVar(prayer.enabled) + mes("You have run out of prayer points, you can recharge at an altar.") // Note: This is probably implicitly called by some other function, but as of now, we do // not know what that is. ClientScripts.pvpIconsComLevelRange(player, player.combatLevel) diff --git a/content/interfaces/prayer-tab/src/main/kotlin/org/rsmod/content/interfaces/prayer/tab/scripts/QuickPrayerScript.kt b/content/interfaces/prayer-tab/src/main/kotlin/org/rsmod/content/interfaces/prayer/tab/scripts/QuickPrayerScript.kt index e3ba82a32..12c3706ea 100644 --- a/content/interfaces/prayer-tab/src/main/kotlin/org/rsmod/content/interfaces/prayer/tab/scripts/QuickPrayerScript.kt +++ b/content/interfaces/prayer-tab/src/main/kotlin/org/rsmod/content/interfaces/prayer/tab/scripts/QuickPrayerScript.kt @@ -7,6 +7,8 @@ import jakarta.inject.Inject import org.rsmod.api.config.constants import org.rsmod.api.player.output.mes import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.hook.PlayerRestrictions +import org.rsmod.api.player.hook.RestrictedAction import org.rsmod.api.player.protect.ProtectedAccessLauncher import org.rsmod.api.player.stat.prayerLvl import org.rsmod.api.player.ui.ifClose @@ -38,6 +40,7 @@ constructor( private val repo: PrayerRepository, private val eventBus: EventBus, private val protectedAccess: ProtectedAccessLauncher, + private val restrictions: PlayerRestrictions, ) : PluginScript() { override fun ScriptContext.startup() { onPlayerLogin { player.disableQuickPrayers() } @@ -83,6 +86,13 @@ constructor( } private fun ProtectedAccess.enableQuickPrayers() { + val restriction = restrictions.check(player, RestrictedAction.Prayer) + if (restriction != null) { + mes(restriction) + player.resyncVar("varbit.quickprayer_active") + return + } + val quickPrayerVars = vars["varbit.quickprayer_selected"] if (quickPrayerVars == 0) { diff --git a/content/interfaces/spellbook/pack/build.gradle.kts b/content/interfaces/spellbook/pack/build.gradle.kts new file mode 100644 index 000000000..2693e4696 --- /dev/null +++ b/content/interfaces/spellbook/pack/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(libs.or2.all.cache) + implementation(libs.or2.tools) + implementation(libs.or2.definition) +} diff --git a/content/interfaces/spellbook/pack/src/main/kotlin/org/rsmod/content/interfaces/spellbook/pack/SpellbookPluginPack.kt b/content/interfaces/spellbook/pack/src/main/kotlin/org/rsmod/content/interfaces/spellbook/pack/SpellbookPluginPack.kt new file mode 100644 index 000000000..b2d6e9a56 --- /dev/null +++ b/content/interfaces/spellbook/pack/src/main/kotlin/org/rsmod/content/interfaces/spellbook/pack/SpellbookPluginPack.kt @@ -0,0 +1,11 @@ +package org.rsmod.content.interfaces.spellbook.pack + +import dev.openrune.pack.PluginPack + +/** + * Packs the spellbook clientscript overrides in `pack/cs2`. The only override replaces the + * client's per-spell quest requirement check (`script4128`, called from + * `magic_spellbook_unlocked`) so no spell is greyed out or blocked for a quest the player has not + * completed - the server does not enforce spell quest requirements either. + */ +class SpellbookPluginPack : PluginPack() diff --git a/content/interfaces/spellbook/pack/src/main/resources/pack/cs2/script/[proc,script4128].cs2 b/content/interfaces/spellbook/pack/src/main/resources/pack/cs2/script/[proc,script4128].cs2 new file mode 100644 index 000000000..fffaa1d4d --- /dev/null +++ b/content/interfaces/spellbook/pack/src/main/resources/pack/cs2/script/[proc,script4128].cs2 @@ -0,0 +1,5 @@ +// 4128 +// Overrides the vanilla per-spell quest requirement check. Spells are not locked behind quests on +// this server, so the spell's `spell_questreq_struct` param is ignored and every spell is unlocked. +[proc,script4128](obj $obj)(boolean) +return(true); diff --git a/content/other/cheat-menu/build.gradle.kts b/content/other/cheat-menu/build.gradle.kts new file mode 100644 index 000000000..8036cff08 --- /dev/null +++ b/content/other/cheat-menu/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) + implementation(projects.api.spellsAutocast) +} diff --git a/content/other/cheat-menu/src/main/kotlin/org/rsmod/content/other/cheatmenu/CheatAppearance.kt b/content/other/cheat-menu/src/main/kotlin/org/rsmod/content/other/cheatmenu/CheatAppearance.kt new file mode 100644 index 000000000..31e72fa9d --- /dev/null +++ b/content/other/cheat-menu/src/main/kotlin/org/rsmod/content/other/cheatmenu/CheatAppearance.kt @@ -0,0 +1,114 @@ +package org.rsmod.content.other.cheatmenu + +import org.rsmod.game.entity.player.Appearance + +/** + * The ident-kit slots that make up a player's model, in the order the appearance block sends them. + * + * The valid style ids per slot were read out of the revision 240 client cache (config index 2, + * `IDENTKIT` archive 3), grouping every definition by its `bodyPartId`: parts `0..6` are body type + * A and parts `7..13` are body type B, in this same slot order. They are far from contiguous - + * styles added over the years were appended well past the original blocks - which is why they are + * listed as explicit ranges rather than a single span. + * + * The server cache does not decode ident-kit definitions, so there is nothing to read these from at + * runtime. `CheatMenuDataTest` cross-checks the table against the engine's own default appearance + * to catch it drifting from the cache. + */ +internal enum class AppearancePart( + val label: String, + val slot: Int, + private val bodyTypeA: List, + private val bodyTypeB: List, +) { + Hair( + "Hair", + 0, + listOf(0..9, 129..134, 144..151, 201..247), + listOf(45..55, 118..128, 141..143, 152..152, 154..200), + ), + Jaw("Jaw / beard", 1, listOf(10..17, 111..117, 153..153), listOf(292..306)), + Torso("Torso", 2, listOf(18..25, 105..110, 254..259), listOf(56..60, 89..94, 265..273)), + Arms("Arms", 3, listOf(26..32, 84..88, 260..264), listOf(61..66, 95..99, 248..253)), + Hands("Hands", 4, listOf(33..35), listOf(67..69)), + Legs("Legs", 5, listOf(36..41, 100..104, 281..291), listOf(70..78, 135..140, 274..280)), + Feet("Feet", 6, listOf(42..44, 82..82), listOf(79..81, 83..83)); + + /** Every style id this slot accepts for [bodyType]. */ + fun stylesFor(bodyType: Int): List = rangesFor(bodyType).flatten() + + /** + * Whether this slot may be left empty. Facial hair is the only optional part - every id in the + * jaw slot is an actual beard or moustache model, so "clean shaven" is the absence of a model + * rather than a blank one. + */ + val optional: Boolean + get() = this == Jaw + + /** + * The style this slot falls back to when switching to [bodyType]. + * + * Body type B defaults to no facial hair; every jaw id would otherwise put a beard on it. + */ + fun defaultFor(bodyType: Int): Int = + if (optional && bodyType == Appearance.BODY_TYPE_B) { + Appearance.NO_IDENT_KIT + } else { + rangesFor(bodyType).first().first + } + + /** + * Where [style] sits in this slot's list for [bodyType], as `3/71` - or a plain description + * when it is not in the list at all. Style ids mean nothing to a reader, so the menus show a + * position instead. + */ + fun positionOf(style: Int, bodyType: Int): String { + if (style == Appearance.NO_IDENT_KIT) { + return "none" + } + val styles = stylesFor(bodyType) + val index = styles.indexOf(style) + return if (index < 0) "id $style (not body type ${bodyTypeLabel(bodyType)})" else "${index + 1}/${styles.size}" + } + + /** Whether [style] renders correctly on [bodyType]. */ + fun accepts(style: Int, bodyType: Int): Boolean = + (optional && style == Appearance.NO_IDENT_KIT) || style in stylesFor(bodyType) + + private fun bodyTypeLabel(bodyType: Int): String = + if (bodyType == Appearance.BODY_TYPE_B) "B" else "A" + + private fun rangesFor(bodyType: Int): List = + if (bodyType == Appearance.BODY_TYPE_B) bodyTypeB else bodyTypeA +} + +/** The recolourable areas of a player's model, with the vanilla palette size for each. */ +internal enum class AppearanceColour(val label: String, val index: Int, val paletteSize: Int) { + Hair("Hair colour", 0, 25), + Torso("Torso colour", 1, 29), + Legs("Leg colour", 2, 29), + Feet("Boot colour", 3, 6), + Skin("Skin colour", 4, 8), +} + +/** Ident-kit and colour values a freshly created [Appearance] starts with. */ +internal object DefaultAppearance { + val identKit: IntArray = intArrayOf(9, 14, 109, 26, 33, 36, 42) + val colours: IntArray = intArrayOf(0, 3, 2, 0, 0) + + /** + * A complete style set for [bodyType], indexed by [AppearancePart.slot]. + * + * The appearance block sends explicit ident-kit model ids, so `Appearance.bodyType` on its own + * only flips a flag - the character keeps whichever models it was already wearing. Switching + * body type has to swap every slot, which is what this is for. + */ + fun stylesFor(bodyType: Int): IntArray = + IntArray(AppearancePart.entries.size) { slot -> + AppearancePart.entries.first { it.slot == slot }.defaultFor(bodyType) + } +} + +internal const val MAX_IDENT_KIT: Int = 32767 + +internal const val MAX_COLOUR: Int = 255 diff --git a/content/other/cheat-menu/src/main/kotlin/org/rsmod/content/other/cheatmenu/CheatMenuScript.kt b/content/other/cheat-menu/src/main/kotlin/org/rsmod/content/other/cheatmenu/CheatMenuScript.kt new file mode 100644 index 000000000..70bd7d55c --- /dev/null +++ b/content/other/cheat-menu/src/main/kotlin/org/rsmod/content/other/cheatmenu/CheatMenuScript.kt @@ -0,0 +1,862 @@ +package org.rsmod.content.other.cheatmenu + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import dev.openrune.types.StatType +import dev.or2.central.account.Rights +import jakarta.inject.Inject +import kotlin.math.max +import org.rsmod.api.combat.commons.magic.Spellbook +import org.rsmod.api.config.Constants +import org.rsmod.api.invtx.invAdd +import org.rsmod.api.player.cheat.adminGodMode +import org.rsmod.api.player.cheat.adminInfiniteRunes +import org.rsmod.api.player.cheat.adminMaxHit +import org.rsmod.api.player.cheat.adminNoClip +import org.rsmod.api.player.cheat.adminOneHitKill +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.output.UpdateRun +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.protect.ProtectedAccessLauncher +import org.rsmod.api.player.stat.PlayerSkillXP +import org.rsmod.api.player.stat.stat +import org.rsmod.api.player.stat.statAdvance +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.player.stat.statRestore +import org.rsmod.api.player.stat.statSub +import org.rsmod.api.player.ui.PlayerInterfaceUpdates +import org.rsmod.api.random.GameRandom +import org.rsmod.api.script.onCommand +import org.rsmod.api.spells.autocast.MagicSpellbookManager +import org.rsmod.game.cheat.Cheat +import org.rsmod.game.entity.Player +import org.rsmod.game.entity.player.Appearance +import org.rsmod.game.stat.PlayerSkillXPTable +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * An administrator cheat menu: a single `::cheat` command that opens a nested menu for spawning + * objs, teleporting, restoring stats, editing skills and appearance, switching spellbook, and + * toggling the god mode / one-hit-kill / no-rune-cost / no-clip cheats. + * + * Everything is presented through the chatbox option dialogue ([ProtectedAccess.choice2] and + * friends) rather than [ProtectedAccess.menu], which opens a main modal over the middle of the + * screen. That dialogue holds at most five options, so menus are grouped into screens of four plus + * a navigation entry, and the long lists (skills, teleport destinations) are reached by typing a + * name instead of paging. + * + * Every toggle is backed by an attribute in `org.rsmod.api.player.cheat`, so the cheats stay active + * until switched off (or until the player logs out) rather than only for the duration of the menu. + */ +class CheatMenuScript +@Inject +constructor( + private val protectedAccess: ProtectedAccessLauncher, + private val spellbooks: MagicSpellbookManager, +) : PluginScript() { + override fun ScriptContext.startup() { + adminCommand("cheat", "Open the admin cheat menu", ::openMenu) + adminCommand("cheatmenu", "Open the admin cheat menu", ::openMenu) + adminCommand("spellbook", "Switch spellbook: standard, ancient, lunar or arceuus") { + switchSpellbook(this) + } + adminCommand("ohk", "Toggle one-hit-kill on npcs", ::toggleOneHitKill) + adminCommand("norunes", "Toggle casting spells without runes", ::toggleInfiniteRunes) + adminCommand("noclip", "Toggle walking through walls and objects", ::toggleNoClip) + adminCommand("heal", "Fully restore stats, hitpoints, prayer and run energy", ::fullHeal) + } + + private fun ScriptContext.adminCommand( + command: String, + desc: String, + action: Cheat.() -> Unit, + ) = onCommand(command) { + this.desc = desc + this.requiredRights = Rights.ADMINISTRATOR + this.cheat(action) + } + + /* Commands */ + + private fun openMenu(cheat: Cheat) = + with(cheat) { protectedAccess.launch(player) { mainMenu() } } + + private fun toggleOneHitKill(cheat: Cheat) = + with(cheat) { + player.adminOneHitKill = !player.adminOneHitKill + player.mes("One-hit-kill ${enabledText(player.adminOneHitKill)}.") + } + + private fun toggleInfiniteRunes(cheat: Cheat) = + with(cheat) { + player.adminInfiniteRunes = !player.adminInfiniteRunes + player.mes("Magic rune cost ${runeCostText(player.adminInfiniteRunes)}.") + } + + private fun toggleNoClip(cheat: Cheat) = + with(cheat) { + player.adminNoClip = !player.adminNoClip + player.mes("No clip ${enabledText(player.adminNoClip)}.") + } + + private fun fullHeal(cheat: Cheat) = + with(cheat) { + player.fullRestore() + player.mes("Stats, hitpoints, prayer points and run energy restored.") + } + + private fun switchSpellbook(cheat: Cheat) = + with(cheat) { + val query = args.firstOrNull().orEmpty() + val spellbook = findSpellbook(query) + if (spellbook == null) { + player.mes("Usage: ::spellbook standard|ancient|lunar|arceuus") + return@with + } + player.setSpellbook(spellbook) + } + + /* Chatbox dialogue plumbing */ + + /** + * Asks a single chatbox option dialogue and returns the zero-based index of the chosen label. + * + * The chatbox dialogue is fixed at two to five options, which is why [select] pages rather than + * listing everything at once. + */ + private suspend fun ProtectedAccess.ask(title: String, labels: List): Int = + when (labels.size) { + 2 -> choice2(labels[0], 0, labels[1], 1, title = title) + 3 -> choice3(labels[0], 0, labels[1], 1, labels[2], 2, title = title) + 4 -> + choice4(labels[0], 0, labels[1], 1, labels[2], 2, labels[3], 3, title = title) + 5 -> + choice5( + labels[0], + 0, + labels[1], + 1, + labels[2], + 2, + labels[3], + 3, + labels[4], + 4, + title = title, + ) + else -> error("Chatbox dialogues hold 2-5 options. (size=${labels.size})") + } + + /** + * Presents [choices] through the chatbox dialogue, four at a time. The fifth slot advances to + * the next page, or shows [exitLabel] on the final page. + * + * @return the value behind the chosen entry, or `null` if the player picked [exitLabel]. + */ + private suspend fun ProtectedAccess.select( + title: String, + choices: List>, + exitLabel: String = BACK, + ): T? { + require(choices.isNotEmpty()) { "`choices` must not be empty." } + val pages = choices.chunked(PAGE_SIZE) + var pageIndex = 0 + while (true) { + val page = pages[pageIndex] + val finalPage = pageIndex == pages.lastIndex + val labels = page.map(Choice::label) + if (finalPage) exitLabel else MORE + val picked = ask(title, labels) + if (picked < page.size) { + return page[picked].value + } + if (finalPage) { + return null + } + pageIndex++ + } + } + + /** Asks a two-option confirmation, returning `true` for "Yes". */ + private suspend fun ProtectedAccess.confirm(title: String): Boolean = + ask(title, listOf("Yes", "No")) == 0 + + /* Main menu */ + + private suspend fun ProtectedAccess.mainMenu() { + while (true) { + val choice = + select( + title = "Cheat Menu", + choices = + listOf( + Choice("Cheat toggles...", MainOption.Toggles), + Choice("Teleport...", MainOption.Teleport), + Choice("Skills and experience...", MainOption.Skills), + Choice("Items and appearance...", MainOption.Items), + Choice( + "Spellbook: ${spellbookName(spellbooks.activeSpellbook(player))}", + MainOption.Spellbook, + ), + ), + exitLabel = "Close", + ) ?: return + when (choice) { + MainOption.Toggles -> togglesMenu() + MainOption.Teleport -> teleportMenu() + MainOption.Skills -> skillsMenu() + MainOption.Items -> itemsMenu() + MainOption.Spellbook -> spellbookMenu() + } + } + } + + /* Toggles */ + + private suspend fun ProtectedAccess.togglesMenu() { + while (true) { + val choices = + listOf( + Choice("God mode: ${state(player.adminGodMode)}", ToggleOption.God), + Choice("One-hit-kill: ${state(player.adminOneHitKill)}", ToggleOption.OneHitKill), + Choice("Always max hit: ${state(player.adminMaxHit)}", ToggleOption.MaxHit), + Choice("No rune cost: ${state(player.adminInfiniteRunes)}", ToggleOption.Runes), + Choice("No clip: ${state(player.adminNoClip)}", ToggleOption.NoClip), + Choice("Turn every cheat off", ToggleOption.AllOff), + ) + when (select("Cheat toggles", choices) ?: return) { + ToggleOption.God -> { + player.adminGodMode = !player.adminGodMode + mes("God mode ${enabledText(player.adminGodMode)}.") + } + ToggleOption.OneHitKill -> { + player.adminOneHitKill = !player.adminOneHitKill + mes("One-hit-kill ${enabledText(player.adminOneHitKill)}.") + } + ToggleOption.MaxHit -> { + player.adminMaxHit = !player.adminMaxHit + mes("Always max hit ${enabledText(player.adminMaxHit)}.") + } + ToggleOption.Runes -> { + player.adminInfiniteRunes = !player.adminInfiniteRunes + mes("Magic rune cost ${runeCostText(player.adminInfiniteRunes)}.") + } + ToggleOption.NoClip -> { + player.adminNoClip = !player.adminNoClip + mes("No clip ${enabledText(player.adminNoClip)}.") + } + ToggleOption.AllOff -> { + player.adminGodMode = false + player.adminOneHitKill = false + player.adminMaxHit = false + player.adminInfiniteRunes = false + player.adminNoClip = false + mes("Every cheat has been switched off.") + } + } + } + } + + /* Teleport */ + + private suspend fun ProtectedAccess.teleportMenu() { + while (true) { + val choice = + select( + title = "Teleport", + choices = + listOf( + Choice("Search by name", TeleportOption.Search), + Choice("Browse by region...", TeleportOption.Browse), + Choice("Enter coordinates", TeleportOption.Coords), + ), + ) ?: return + val teleported = + when (choice) { + TeleportOption.Search -> searchTeleport() + TeleportOption.Browse -> browseTeleport() + TeleportOption.Coords -> customTeleport() + } + if (teleported) { + return + } + } + } + + /** @return `true` if the player teleported. */ + private suspend fun ProtectedAccess.searchTeleport(): Boolean { + val query = stringDialog("Enter a destination name:") + val destination = findDestination(query) + if (destination == null) { + mes("No teleport destination matching '${query.trim()}'.") + return false + } + goTo(destination) + return true + } + + /** @return `true` if the player teleported. */ + private suspend fun ProtectedAccess.browseTeleport(): Boolean { + while (true) { + val regions = TeleportRegion.entries.map { Choice(it.label, it) } + val region = select("Which region?", regions) ?: return false + val destinations = region.destinations.map { Choice(it.name, it) } + val destination = select(region.label, destinations) ?: continue + goTo(destination) + return true + } + } + + /** @return `true` if the player teleported. */ + private suspend fun ProtectedAccess.customTeleport(): Boolean { + val x = countDialog("Enter the destination x coordinate:") + val z = countDialog("Enter the destination z coordinate:") + val level = countDialog("Enter the destination level (0-3):") + if (x !in 0..CoordGrid.X_BIT_MASK || z !in 0..CoordGrid.Z_BIT_MASK) { + mes("Those coordinates are out of bounds.") + return false + } + val dest = CoordGrid(x, z, level.coerceIn(0, CoordGrid.LEVEL_BIT_MASK)) + telejump(dest, TeleportType.Exempt) + mes("Teleported to $dest.") + return true + } + + private fun ProtectedAccess.goTo(destination: TeleportDestination) { + telejump(destination.coords, TeleportType.Exempt) + mes("Teleported to ${destination.name} (${destination.coords}).") + } + + /* Skills */ + + private suspend fun ProtectedAccess.skillsMenu() { + while (true) { + val choice = + select( + title = "Skills and experience", + choices = + listOf( + Choice("Give experience", SkillOption.GiveXp), + Choice("Set a skill level", SkillOption.SetLevel), + Choice("Reset a skill", SkillOption.ResetOne), + Choice("Restore stats, prayer and run", SkillOption.Restore), + Choice("Max every skill", SkillOption.MaxAll), + Choice("Reset every skill", SkillOption.ResetAll), + ), + ) ?: return + when (choice) { + SkillOption.GiveXp -> giveXp() + SkillOption.SetLevel -> setLevel() + SkillOption.ResetOne -> resetSkill() + SkillOption.Restore -> { + player.fullRestore() + mes("Stats, hitpoints, prayer points and run energy restored.") + } + SkillOption.MaxAll -> { + if (confirm("Max every skill?")) { + player.setAllStatLevels(MAX_LEVEL) + mes("Every skill has been maxed.") + } + } + SkillOption.ResetAll -> { + if (confirm("Reset every skill?")) { + player.setAllStatLevels(1) + mes("Every skill has been reset.") + } + } + } + } + } + + private suspend fun ProtectedAccess.giveXp() { + val stat = pickStat() ?: return + val internal = stat.internal() + val amount = countDialog("Enter the amount of experience to add:") + if (amount <= 0) { + return + } + val added = player.statAdvance(internal, amount.toDouble(), rate = 1.0, globalRate = 1.0) + mes("Added $added ${stat.displayName} xp (now level ${player.statBase(internal)}).") + } + + private suspend fun ProtectedAccess.setLevel() { + val stat = pickStat() ?: return + val requested = countDialog("Enter the level (${stat.minLevel}-${stat.maxLevel}):") + val level = requested.coerceIn(stat.minLevel, stat.maxLevel) + player.setStatLevel(stat, level) + mes("${stat.displayName} set to level $level.") + } + + private suspend fun ProtectedAccess.resetSkill() { + val stat = pickStat() ?: return + player.setStatLevel(stat, stat.minLevel) + mes("${stat.displayName} reset to level ${stat.minLevel}.") + } + + /** + * There are more skills than the chatbox dialogue can list, so the skill is typed rather than + * picked from a paged menu. + */ + private suspend fun ProtectedAccess.pickStat(): StatType? { + val query = stringDialog("Enter a skill name:") + val stat = findStat(query) + if (stat == null) { + mes("No skill matching '${query.trim()}'.") + } + return stat + } + + /* Items and appearance */ + + private suspend fun ProtectedAccess.itemsMenu() { + while (true) { + val choice = + select( + title = "Items and appearance", + choices = + listOf( + Choice("Spawn an item", ItemOption.Spawn), + Choice("Change appearance...", ItemOption.Appearance), + ), + ) ?: return + when (choice) { + ItemOption.Spawn -> spawnItems() + ItemOption.Appearance -> appearanceMenu() + } + } + } + + private suspend fun ProtectedAccess.spawnItems() { + while (true) { + val item = + objDialog( + title = "Search for an item to spawn:", + stockMarketRestriction = false, + showLastSearched = true, + ) + val amount = countDialog("Enter spawn quantity:") + if (amount <= 0) { + return + } + give(item, amount) + if (!confirm("Spawn another item?")) { + return + } + } + } + + private fun ProtectedAccess.give(item: ItemServerType, count: Int) { + val spawned = player.invAdd(player.inv, item.id, count, strict = false).completed() + if (spawned <= 0) { + mes("You don't have enough inventory space.") + return + } + mes("Spawned '${item.name}' x $spawned.") + } + + private suspend fun ProtectedAccess.appearanceMenu() { + while (true) { + val appearance = player.appearance + val choice = + select( + title = "Change appearance", + choices = + listOf( + Choice( + "Body type: ${bodyTypeName(appearance.bodyType)}", + AppearanceOption.BodyType, + ), + Choice( + "Pronoun: ${pronounName(appearance.pronoun)}", + AppearanceOption.Pronoun, + ), + Choice("Change a body part style", AppearanceOption.Part), + Choice("Change a colour", AppearanceOption.Colour), + Choice("Randomise appearance", AppearanceOption.Randomise), + Choice("Reset to default", AppearanceOption.Reset), + ), + ) ?: return + when (choice) { + AppearanceOption.BodyType -> { + val flipped = + if (appearance.bodyType == Appearance.BODY_TYPE_A) { + Appearance.BODY_TYPE_B + } else { + Appearance.BODY_TYPE_A + } + player.setBodyType(flipped) + mes("Body type set to ${bodyTypeName(flipped)}.") + } + AppearanceOption.Pronoun -> { + appearance.pronoun = (appearance.pronoun + 1) % PRONOUN_COUNT + mes("Pronoun set to ${pronounName(appearance.pronoun)}.") + } + AppearanceOption.Part -> bodyPartMenu() + AppearanceOption.Colour -> colourMenu() + AppearanceOption.Randomise -> { + player.randomiseAppearance(random) + mes("Appearance randomised.") + } + AppearanceOption.Reset -> { + player.resetAppearance() + mes("Appearance reset to default.") + } + } + } + } + + private suspend fun ProtectedAccess.bodyPartMenu() { + while (true) { + val bodyType = player.appearance.bodyType + val identKit = player.appearance.identKitSnapshot() + val choices = + AppearancePart.entries.map { part -> + val current = identKit.getOrNull(part.slot)?.toInt() ?: Appearance.NO_IDENT_KIT + Choice("${part.label}: ${part.positionOf(current, bodyType)}", part) + } + val part = select("Body type ${bodyTypeName(bodyType)} parts", choices) ?: return + cyclePart(part) + } + } + + /** + * Steps through a slot's styles the way the in-game makeover does, applying each one + * immediately so the change is visible on the character rather than described in chat. + */ + private suspend fun ProtectedAccess.cyclePart(part: AppearancePart) { + while (true) { + val bodyType = player.appearance.bodyType + val styles = part.stylesFor(bodyType) + val current = + player.appearance.identKitSnapshot().getOrNull(part.slot)?.toInt() + ?: Appearance.NO_IDENT_KIT + val index = styles.indexOf(current) + + val choices = buildList { + add(Choice("Next", StyleStep.Next)) + add(Choice("Previous", StyleStep.Previous)) + add(Choice("Random", StyleStep.Random)) + if (part.optional) { + add(Choice("None (clean shaven)", StyleStep.None)) + } + } + val title = "${part.label}: ${part.positionOf(current, bodyType)}" + val step = select(title, choices, exitLabel = "Done") ?: return + + val next = + when (step) { + // A current style outside the list (none, or one from the other body type) + // has no position, so stepping from it enters the list at either end. + StyleStep.Next -> + if (index < 0) styles.first() else styles[(index + 1).mod(styles.size)] + StyleStep.Previous -> + if (index < 0) styles.last() else styles[(index - 1).mod(styles.size)] + StyleStep.Random -> random.pick(styles) + StyleStep.None -> Appearance.NO_IDENT_KIT + } + player.appearance.setIdentKit(part.slot, next) + } + } + + private suspend fun ProtectedAccess.colourMenu() { + while (true) { + val current = player.appearance.coloursSnapshot() + val choices = + AppearanceColour.entries.map { colour -> + val value = current.getOrNull(colour.index)?.toInt() ?: 0 + Choice("${colour.label}: ${value + 1}/${colour.paletteSize}", colour) + } + val colour = select("Which colour?", choices) ?: return + cycleColour(colour) + } + } + + private suspend fun ProtectedAccess.cycleColour(colour: AppearanceColour) { + while (true) { + val current = player.appearance.coloursSnapshot().getOrNull(colour.index)?.toInt() ?: 0 + val choices = + listOf( + Choice("Next", StyleStep.Next), + Choice("Previous", StyleStep.Previous), + Choice("Random", StyleStep.Random), + ) + val title = "${colour.label}: ${current + 1}/${colour.paletteSize}" + val step = select(title, choices, exitLabel = "Done") ?: return + + val next = + when (step) { + StyleStep.Next -> (current + 1).mod(colour.paletteSize) + StyleStep.Previous -> (current - 1).mod(colour.paletteSize) + else -> random.of(0..(colour.paletteSize - 1)) + } + player.appearance.setColour(colour.index, next) + } + } + + /* Spellbook */ + + /** + * Switches the player's active spellbook. [MagicSpellbookManager] also clears any autocast + * selection, since the autocast spell belongs to the previous book. + */ + private suspend fun ProtectedAccess.spellbookMenu() { + val current = spellbooks.activeSpellbook(player) + val choices = + Spellbook.entries.map { book -> + val label = if (book == current) "${spellbookName(book)} (current)" else spellbookName(book) + Choice(label, book) + } + val chosen = select("Which spellbook?", choices) ?: return + player.setSpellbook(chosen) + } + + private fun Player.setSpellbook(spellbook: Spellbook) { + when (val result = spellbooks.setSpellbook(this, spellbook)) { + is MagicSpellbookManager.ChangeResult.Changed -> + mes("Spellbook switched to ${spellbookName(result.current)}.") + is MagicSpellbookManager.ChangeResult.Unchanged -> + mes("Your spellbook is already ${spellbookName(result.current)}.") + } + } + + /* Player helpers */ + + private fun Player.fullRestore() { + restoreAllStats() + restoreRunEnergy() + } + + private fun Player.restoreAllStats() { + for (stat in releasedStats()) { + statRestore(stat.internal()) + } + } + + private fun Player.restoreRunEnergy() { + runEnergy = Constants.run_max_energy + UpdateRun.energy(this, runEnergy) + } + + private fun Player.setAllStatLevels(level: Int) { + for (stat in releasedStats()) { + setStatLevel(stat, level) + } + } + + private fun Player.setStatLevel(stat: StatType, level: Int) { + val internal = stat.internal() + val target = level.coerceIn(stat.minLevel, stat.maxLevel) + val targetXp = PlayerSkillXPTable.getXPFromLevel(target) + if (statBase(internal) > target) { + statRevert(internal, target, targetXp) + return + } + val xpDelta = targetXp - statMap.getXP(internal) + statMap.setCurrentLevel(internal, target.toByte()) + statAdvance(internal, xpDelta.toDouble(), rate = 1.0, globalRate = 1.0) + } + + /** + * Lowers [stat] to [targetLevel] and [targetXp]. There is deliberately no shared helper for + * this: xp reduction is not a standard gameplay operation and only exists for admin tooling. + */ + private fun Player.statRevert(stat: String, targetLevel: Int, targetXp: Int) { + statMap.setCurrentLevel(stat, statBase(stat).toByte()) + val levelDelta = stat(stat) - targetLevel + statMap.setXP(stat, targetXp) + statMap.setBaseLevel(stat, targetLevel.toByte()) + statSub(stat, constant = max(0, levelDelta), percent = 0) + appearance.combatLevel = PlayerSkillXP.calculateCombatLevel(this) + PlayerInterfaceUpdates.updateCombatLevel(this) + } + + /** + * Switches body type and swaps every ident-kit slot to that body type's styles. + * + * The appearance block sends explicit model ids (see `RspCycle.syncAppearance`), so setting + * `Appearance.bodyType` alone flips the flag while the character keeps wearing the old body's + * models - which reads in-game as the switch having done nothing. + */ + private fun Player.setBodyType(bodyType: Int) { + appearance.bodyType = bodyType + val styles = DefaultAppearance.stylesFor(bodyType) + for (part in AppearancePart.entries) { + appearance.setIdentKit(part.slot, styles[part.slot]) + } + } + + private fun Player.randomiseAppearance(random: GameRandom) { + val bodyType = appearance.bodyType + for (part in AppearancePart.entries) { + val pool = + if (part.optional) { + part.stylesFor(bodyType) + Appearance.NO_IDENT_KIT + } else { + part.stylesFor(bodyType) + } + appearance.setIdentKit(part.slot, random.pick(pool)) + } + for (colour in AppearanceColour.entries) { + appearance.setColour(colour.index, random.of(0..(colour.paletteSize - 1))) + } + } + + private fun Player.resetAppearance() { + for (part in AppearancePart.entries) { + appearance.setIdentKit(part.slot, DefaultAppearance.identKit[part.slot]) + } + for (colour in AppearanceColour.entries) { + appearance.setColour(colour.index, DefaultAppearance.colours[colour.index]) + } + appearance.bodyType = Appearance.BODY_TYPE_A + appearance.pronoun = Appearance.PRONOUN_HE + } + + private data class Choice(val label: String, val value: T) + + private enum class MainOption { + Toggles, + Teleport, + Skills, + Items, + Spellbook, + } + + private enum class ToggleOption { + God, + OneHitKill, + MaxHit, + Runes, + NoClip, + AllOff, + } + + private enum class TeleportOption { + Search, + Browse, + Coords, + } + + private enum class SkillOption { + GiveXp, + SetLevel, + ResetOne, + Restore, + MaxAll, + ResetAll, + } + + private enum class ItemOption { + Spawn, + Appearance, + } + + private enum class StyleStep { + Next, + Previous, + Random, + None, + } + + private enum class AppearanceOption { + BodyType, + Pronoun, + Part, + Colour, + Randomise, + Reset, + } + + internal companion object { + /** The chatbox dialogue holds five options; the fifth is reserved for navigation. */ + const val PAGE_SIZE = 4 + const val MORE = "More options..." + const val BACK = "Back" + const val MAX_LEVEL = 99 + const val PRONOUN_COUNT = 3 + + const val GREEN = "0dc10d" + const val RED = "ff0000" + + fun releasedStats(): List = + ServerCacheManager.getStats() + .values + .filterNot(StatType::unreleased) + .sortedBy(StatType::displayName) + + fun StatType.internal(): String = RSCM.getReverseMapping(RSCMType.STAT, id) + + /** Matches a typed skill name: exact, then gameval, then prefix, then substring. */ + fun findStat(input: String): StatType? { + val query = input.trim().lowercase() + if (query.isEmpty()) { + return null + } + val gameval = query.replace(' ', '_') + val stats = releasedStats() + return stats.firstOrNull { it.displayName.lowercase() == query } + ?: stats.firstOrNull { it.internal().removePrefix("stat.").lowercase() == gameval } + ?: stats.firstOrNull { it.displayName.lowercase().startsWith(query) } + ?: stats.firstOrNull { it.displayName.lowercase().contains(query) } + } + + /** Matches a typed teleport name: exact, then prefix, then substring. */ + fun findDestination(input: String): TeleportDestination? { + val query = input.trim().lowercase() + if (query.isEmpty()) { + return null + } + val all = TeleportRegion.entries.flatMap(TeleportRegion::destinations) + return all.firstOrNull { it.name.lowercase() == query } + ?: all.firstOrNull { it.name.lowercase().startsWith(query) } + ?: all.firstOrNull { it.name.lowercase().contains(query) } + } + + /** + * Colours the toggle state, matching the confirm/cancel labels the bank interface uses for + * its own option text. + */ + fun styleName(style: Int): String = + if (style == Appearance.NO_IDENT_KIT) "none" else "$style" + + fun state(enabled: Boolean): String = + if (enabled) "ON" else "OFF" + + fun enabledText(enabled: Boolean): String = if (enabled) "enabled" else "disabled" + + fun runeCostText(infinite: Boolean): String = if (infinite) "removed" else "restored" + + fun spellbookName(spellbook: Spellbook): String = + when (spellbook) { + Spellbook.Standard -> "Standard" + Spellbook.Ancients -> "Ancient" + Spellbook.Lunars -> "Lunar" + Spellbook.Arceuus -> "Arceuus" + } + + /** Matches a typed spellbook name: `ancient`, `ancients`, `lunar`, `arc`... */ + fun findSpellbook(input: String): Spellbook? { + val query = input.trim().lowercase() + if (query.isEmpty()) { + return null + } + return Spellbook.entries.firstOrNull { spellbookName(it).lowercase() == query } + ?: Spellbook.entries.firstOrNull { it.name.lowercase() == query } + ?: Spellbook.entries.firstOrNull { spellbookName(it).lowercase().startsWith(query) } + } + + fun bodyTypeName(bodyType: Int): String = + if (bodyType == Appearance.BODY_TYPE_B) "B" else "A" + + fun pronounName(pronoun: Int): String = + when (pronoun) { + Appearance.PRONOUN_SHE -> "She" + Appearance.PRONOUN_THEY -> "They" + else -> "He" + } + } +} diff --git a/content/other/cheat-menu/src/main/kotlin/org/rsmod/content/other/cheatmenu/CheatTeleports.kt b/content/other/cheat-menu/src/main/kotlin/org/rsmod/content/other/cheatmenu/CheatTeleports.kt new file mode 100644 index 000000000..603270c2d --- /dev/null +++ b/content/other/cheat-menu/src/main/kotlin/org/rsmod/content/other/cheatmenu/CheatTeleports.kt @@ -0,0 +1,152 @@ +package org.rsmod.content.other.cheatmenu + +import org.rsmod.map.CoordGrid + +/** A single named teleport target listed under a [TeleportRegion]. */ +internal data class TeleportDestination(val name: String, val coords: CoordGrid) + +/** + * Curated town/city destinations grouped by region so that no single menu grows past a comfortable + * length. The cheat menu also offers a "custom coordinates" entry for anywhere not listed here. + */ +internal enum class TeleportRegion(val label: String, val destinations: List) { + Misthalin( + "Misthalin", + listOf( + TeleportDestination("Lumbridge", CoordGrid(3222, 3218)), + TeleportDestination("Varrock Square", CoordGrid(3213, 3428)), + TeleportDestination("Grand Exchange", CoordGrid(3164, 3486)), + TeleportDestination("Draynor Village", CoordGrid(3093, 3244)), + TeleportDestination("Edgeville", CoordGrid(3087, 3496)), + TeleportDestination("Barbarian Village", CoordGrid(3082, 3420)), + TeleportDestination("Champions' Guild", CoordGrid(3191, 3363)), + TeleportDestination("Wizards' Tower", CoordGrid(3113, 3177)), + TeleportDestination("Lumbridge Swamp", CoordGrid(3200, 3170)), + ), + ), + Asgarnia( + "Asgarnia", + listOf( + TeleportDestination("Falador", CoordGrid(2965, 3380)), + TeleportDestination("Port Sarim", CoordGrid(3022, 3217)), + TeleportDestination("Rimmington", CoordGrid(2957, 3214)), + TeleportDestination("Taverley", CoordGrid(2894, 3443)), + TeleportDestination("Burthorpe", CoordGrid(2899, 3544)), + TeleportDestination("Goblin Village", CoordGrid(2957, 3506)), + TeleportDestination("Warriors' Guild", CoordGrid(2845, 3540)), + TeleportDestination("Crafting Guild", CoordGrid(2933, 3288)), + TeleportDestination("Dwarven Mine", CoordGrid(3018, 9740)), + ), + ), + Kandarin( + "Kandarin", + listOf( + TeleportDestination("Seers' Village / Camelot", CoordGrid(2757, 3477)), + TeleportDestination("Catherby", CoordGrid(2809, 3435)), + TeleportDestination("East Ardougne", CoordGrid(2662, 3305)), + TeleportDestination("Yanille", CoordGrid(2605, 3093)), + TeleportDestination("Port Khazard", CoordGrid(2661, 3161)), + TeleportDestination("Fishing Guild", CoordGrid(2611, 3393)), + TeleportDestination("Ranging Guild", CoordGrid(2658, 3439)), + TeleportDestination("Barbarian Outpost", CoordGrid(2519, 3571)), + TeleportDestination("Tree Gnome Stronghold", CoordGrid(2461, 3444)), + ), + ), + KharidianDesert( + "Kharidian Desert", + listOf( + TeleportDestination("Al Kharid", CoordGrid(3293, 3174)), + TeleportDestination("Shantay Pass", CoordGrid(3304, 3124)), + TeleportDestination("Bedabin Camp", CoordGrid(3181, 3046)), + TeleportDestination("Bandit Camp", CoordGrid(3174, 2987)), + TeleportDestination("Pollnivneach", CoordGrid(3358, 2965)), + TeleportDestination("Nardah", CoordGrid(3427, 2891)), + TeleportDestination("Sophanem", CoordGrid(3288, 2785)), + TeleportDestination("Uzer", CoordGrid(3489, 3089)), + ), + ), + Morytania( + "Morytania", + listOf( + TeleportDestination("Canifis", CoordGrid(3494, 3489)), + TeleportDestination("Slayer Tower", CoordGrid(3428, 3535)), + TeleportDestination("Port Phasmatys", CoordGrid(3676, 3479)), + TeleportDestination("Mort'ton", CoordGrid(3488, 3288)), + TeleportDestination("Burgh de Rott", CoordGrid(3496, 3235)), + TeleportDestination("Barrows", CoordGrid(3565, 3306)), + TeleportDestination("Darkmeyer", CoordGrid(3626, 3363)), + TeleportDestination("Mos Le'Harmless", CoordGrid(3682, 2963)), + ), + ), + Karamja( + "Karamja", + listOf( + TeleportDestination("Musa Point", CoordGrid(2956, 3143)), + TeleportDestination("Brimhaven", CoordGrid(2802, 3178)), + TeleportDestination("Tai Bwo Wannai", CoordGrid(2795, 3065)), + TeleportDestination("Shilo Village", CoordGrid(2852, 2954)), + TeleportDestination("TzHaar City", CoordGrid(2480, 5175)), + ), + ), + Fremennik( + "Fremennik & Isles", + listOf( + TeleportDestination("Rellekka", CoordGrid(2660, 3657)), + TeleportDestination("Piscatoris", CoordGrid(2340, 3650)), + TeleportDestination("Waterbirth Island", CoordGrid(2544, 3741)), + TeleportDestination("Neitiznot", CoordGrid(2337, 3807)), + TeleportDestination("Jatizso", CoordGrid(2416, 3801)), + TeleportDestination("Miscellania", CoordGrid(2515, 3862)), + TeleportDestination("Lunar Isle", CoordGrid(2113, 3915)), + TeleportDestination("Keldagrim", CoordGrid(2845, 10210)), + ), + ), + Kourend( + "Great Kourend", + listOf( + TeleportDestination("Kourend Castle", CoordGrid(1608, 3670)), + TeleportDestination("Arceuus", CoordGrid(1698, 3743)), + TeleportDestination("Port Piscarilius", CoordGrid(1800, 3749)), + TeleportDestination("Hosidius", CoordGrid(1743, 3517)), + TeleportDestination("Shayzien", CoordGrid(1504, 3623)), + TeleportDestination("Lovakengj", CoordGrid(1508, 3789)), + TeleportDestination("Woodcutting Guild", CoordGrid(1591, 3475)), + TeleportDestination("Mount Karuulm", CoordGrid(1311, 3808)), + TeleportDestination("Farming Guild", CoordGrid(1249, 3718)), + ), + ), + Tirannwn( + "Tirannwn", + listOf( + TeleportDestination("Lletya", CoordGrid(2330, 3170)), + TeleportDestination("Port Tyras", CoordGrid(2145, 3122)), + TeleportDestination("Prifddinas", CoordGrid(3243, 6053)), + ), + ), + Wilderness( + "Wilderness", + listOf( + TeleportDestination("Edgeville ditch", CoordGrid(3087, 3520)), + TeleportDestination("Ferox Enclave", CoordGrid(3151, 3635)), + TeleportDestination("Revenant Caves entrance", CoordGrid(3130, 3832)), + TeleportDestination("Lava Dragon Isle", CoordGrid(3200, 3807)), + TeleportDestination("Chaos Temple (level 38)", CoordGrid(2947, 3821)), + TeleportDestination("Wilderness Agility Course", CoordGrid(2998, 3916)), + TeleportDestination("Mage Arena bank", CoordGrid(3094, 3956)), + ), + ), + RooftopCourses( + "Rooftop courses", + listOf( + TeleportDestination("Draynor Village Rooftop (1)", CoordGrid(3104, 3279)), + TeleportDestination("Al Kharid Rooftop (20)", CoordGrid(3273, 3196)), + TeleportDestination("Varrock Rooftop (30)", CoordGrid(3222, 3414)), + TeleportDestination("Canifis Rooftop (40)", CoordGrid(3506, 3487)), + TeleportDestination("Falador Rooftop (50)", CoordGrid(3036, 3340)), + TeleportDestination("Seers' Village Rooftop (60)", CoordGrid(2729, 3488)), + TeleportDestination("Pollnivneach Rooftop (70)", CoordGrid(3351, 2960)), + TeleportDestination("Rellekka Rooftop (80)", CoordGrid(2625, 3678)), + TeleportDestination("Ardougne Rooftop (90)", CoordGrid(2673, 3297)), + ), + ), +} diff --git a/content/other/cheat-menu/src/test/kotlin/org/rsmod/content/other/cheatmenu/CheatMenuDataTest.kt b/content/other/cheat-menu/src/test/kotlin/org/rsmod/content/other/cheatmenu/CheatMenuDataTest.kt new file mode 100644 index 000000000..4cd48d609 --- /dev/null +++ b/content/other/cheat-menu/src/test/kotlin/org/rsmod/content/other/cheatmenu/CheatMenuDataTest.kt @@ -0,0 +1,402 @@ +package org.rsmod.content.other.cheatmenu + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.rsmod.api.combat.commons.magic.Spellbook +import org.rsmod.api.player.cheat.adminGodMode +import org.rsmod.api.player.cheat.adminInfiniteRunes +import org.rsmod.api.player.cheat.adminMaxHit +import org.rsmod.api.player.cheat.adminNoClip +import org.rsmod.api.player.cheat.adminOneHitKill +import org.rsmod.game.entity.Player +import org.rsmod.game.entity.player.Appearance +import org.rsmod.map.CoordGrid + +class CheatMenuDataTest { + @Test + fun `cheat toggles default to off and flip independently`() { + val player = Player() + + assertFalse(player.adminGodMode) + assertFalse(player.adminOneHitKill) + assertFalse(player.adminMaxHit) + assertFalse(player.adminInfiniteRunes) + assertFalse(player.adminNoClip) + + player.adminOneHitKill = true + assertTrue(player.adminOneHitKill) + assertFalse(player.adminGodMode) + assertFalse(player.adminInfiniteRunes) + assertFalse(player.adminNoClip) + + player.adminOneHitKill = false + assertFalse(player.adminOneHitKill) + } + + /** + * `ifChoice` encodes its options as a single pipe-delimited string, so a label containing a + * pipe would silently split into extra entries and shift every selection index after it. + */ + @Test + fun `no menu label contains the choice delimiter`() { + val labels = + TeleportRegion.entries.flatMap { region -> + listOf(region.label) + region.destinations.map(TeleportDestination::name) + } + AppearancePart.entries.map(AppearancePart::label) + + AppearanceColour.entries.map(AppearanceColour::label) + + for (label in labels) { + assertFalse(label.contains('|'), "Label must not contain '|': $label") + assertTrue(label.isNotBlank(), "Label must not be blank.") + } + } + + @Test + fun `toggle states are colour tagged and safe to put in an option label`() { + val on = CheatMenuScript.state(true) + val off = CheatMenuScript.state(false) + + assertEquals("ON", on) + assertEquals("OFF", off) + + // A stray pipe would split the option into two entries; an unclosed tag would bleed the + // colour into every label after it. + for (label in listOf(on, off)) { + assertFalse(label.contains('|'), "State must not contain '|': $label") + assertEquals( + label.count { it == '<' }, + label.count { it == '>' }, + "State must have balanced colour markup: $label", + ) + assertTrue(label.endsWith(""), "State must close its colour tag: $label") + } + } + + /** + * The chatbox dialogue only exists in two- to five-option flavours, so a page of choices plus + * its navigation entry has to land inside that range. + */ + @Test + fun `a full page plus its navigation entry fits the chatbox dialogue`() { + assertTrue(CheatMenuScript.PAGE_SIZE + 1 in 2..5) + } + + @Test + fun `destination search matches exactly, by prefix, and by substring`() { + assertEquals( + CoordGrid(3222, 3218), + CheatMenuScript.findDestination("Lumbridge")?.coords, + "Exact name should match.", + ) + assertEquals( + CoordGrid(3222, 3218), + CheatMenuScript.findDestination(" lumbridge ")?.coords, + "Match should be case-insensitive and trimmed.", + ) + assertEquals( + "Grand Exchange", + CheatMenuScript.findDestination("grand")?.name, + "Prefix should match.", + ) + assertEquals( + "Grand Exchange", + CheatMenuScript.findDestination("exchange")?.name, + "Substring should match.", + ) + } + + @Test + fun `spellbook search accepts display names, enum names and prefixes`() { + assertEquals(Spellbook.Standard, CheatMenuScript.findSpellbook("standard")) + assertEquals(Spellbook.Ancients, CheatMenuScript.findSpellbook("Ancient")) + assertEquals(Spellbook.Ancients, CheatMenuScript.findSpellbook("ancients")) + assertEquals(Spellbook.Lunars, CheatMenuScript.findSpellbook("lun")) + assertEquals(Spellbook.Arceuus, CheatMenuScript.findSpellbook("arc")) + assertNull(CheatMenuScript.findSpellbook("")) + assertNull(CheatMenuScript.findSpellbook("necromancy")) + } + + @Test + fun `every spellbook has a distinct display name`() { + val names = Spellbook.entries.map(CheatMenuScript::spellbookName) + assertEquals(names.size, names.toSet().size) + assertTrue(names.none { it.contains('|') }) + } + + @Test + fun `destination search rejects blank and unknown input`() { + assertNull(CheatMenuScript.findDestination("")) + assertNull(CheatMenuScript.findDestination(" ")) + assertNull(CheatMenuScript.findDestination("definitely not a real place")) + } + + /** + * An exact name must win over a destination that merely contains it, otherwise typing a short + * name would land somewhere unexpected. + */ + @Test + fun `exact destination match wins over a longer containing name`() { + for (destination in TeleportRegion.entries.flatMap(TeleportRegion::destinations)) { + assertEquals( + destination.name, + CheatMenuScript.findDestination(destination.name)?.name, + "Typing an exact destination name must select that destination.", + ) + } + } + + @Test + fun `teleport destinations are unique and in bounds`() { + val names = mutableSetOf() + for (region in TeleportRegion.entries) { + assertTrue(region.destinations.isNotEmpty(), "Region ${region.label} is empty.") + for (destination in region.destinations) { + assertTrue( + names.add(destination.name), + "Duplicate destination name: ${destination.name}", + ) + val coords = destination.coords + assertTrue( + coords.x in 1..CoordGrid.X_BIT_MASK, + "${destination.name} has an out-of-bounds x: ${coords.x}", + ) + assertTrue( + coords.z in 1..CoordGrid.Z_BIT_MASK, + "${destination.name} has an out-of-bounds z: ${coords.z}", + ) + assertTrue( + coords.level in 0..CoordGrid.LEVEL_BIT_MASK, + "${destination.name} has an out-of-bounds level: ${coords.level}", + ) + } + } + } + + @Test + fun `appearance slots map one to one onto the default arrays`() { + val partSlots = AppearancePart.entries.map(AppearancePart::slot) + assertEquals(partSlots.distinct(), partSlots, "Ident-kit slots must be unique.") + assertEquals(DefaultAppearance.identKit.size, AppearancePart.entries.size) + assertEquals((0 until DefaultAppearance.identKit.size).toList(), partSlots.sorted()) + + val colourIndices = AppearanceColour.entries.map(AppearanceColour::index) + assertEquals(colourIndices.distinct(), colourIndices, "Colour indices must be unique.") + assertEquals(DefaultAppearance.colours.size, AppearanceColour.entries.size) + assertEquals((0 until DefaultAppearance.colours.size).toList(), colourIndices.sorted()) + } + + @Test + fun `appearance styles are non-empty and within the api limits`() { + for (part in AppearancePart.entries) { + for (bodyType in listOf(Appearance.BODY_TYPE_A, Appearance.BODY_TYPE_B)) { + val styles = part.stylesFor(bodyType) + assertTrue( + styles.isNotEmpty(), + "${part.label} has no styles for body type $bodyType.", + ) + assertTrue(styles.min() >= 0, "${part.label} has a negative style id.") + assertTrue( + styles.max() <= MAX_IDENT_KIT, + "${part.label} exceeds the ident-kit limit.", + ) + assertEquals( + styles.distinct(), + styles, + "${part.label} lists a style twice for body type $bodyType.", + ) + } + } + for (colour in AppearanceColour.entries) { + assertTrue(colour.paletteSize > 0, "${colour.label} palette is empty.") + assertTrue( + colour.paletteSize - 1 <= MAX_COLOUR, + "${colour.label} palette exceeds the colour limit.", + ) + } + } + + /** + * The style table is transcribed from the client cache, so it cannot be read back at runtime to + * verify. The engine's own default appearance is a known-good body type A set, so every one of + * its ident-kit values must appear in the matching slot - if the table drifts from the cache, + * this is what catches it. + */ + @Test + fun `engine default ident-kit values are valid body type A styles`() { + for (part in AppearancePart.entries) { + val engineDefault = DefaultAppearance.identKit[part.slot] + assertTrue( + engineDefault in part.stylesFor(Appearance.BODY_TYPE_A), + "Engine default ${part.label} style $engineDefault is not a known body type A " + + "style - the ident-kit table is out of step with the cache.", + ) + } + } + + /** + * A style belonging to the other body type renders as the wrong model on the wrong part - a + * female torso in the jaw slot, for instance, hangs off the character's chin. + */ + @Test + fun `no style is shared between the two body types`() { + val a = AppearancePart.entries.flatMap { it.stylesFor(Appearance.BODY_TYPE_A) } + val b = AppearancePart.entries.flatMap { it.stylesFor(Appearance.BODY_TYPE_B) } + assertTrue( + a.intersect(b.toSet()).isEmpty(), + "Body types must not share ident-kit styles.", + ) + } + + /** Two slots claiming the same style id would mean one of them is reading the wrong part. */ + @Test + fun `no style is claimed by two different slots`() { + for (bodyType in listOf(Appearance.BODY_TYPE_A, Appearance.BODY_TYPE_B)) { + val seen = mutableMapOf() + for (part in AppearancePart.entries) { + for (style in part.stylesFor(bodyType)) { + val clash = seen.put(style, part) + assertNull( + clash, + "Style $style is claimed by both ${clash?.label} and ${part.label}.", + ) + } + } + } + } + + /** + * Switching body type has to rewrite every ident-kit slot, so each body type needs a complete + * style set and each style has to belong to that body type's range - otherwise the character + * ends up wearing a mix of both bodies. + */ + @Test + fun `each body type has a complete style set it accepts`() { + for (bodyType in listOf(Appearance.BODY_TYPE_A, Appearance.BODY_TYPE_B)) { + val styles = DefaultAppearance.stylesFor(bodyType) + assertEquals( + AppearancePart.entries.size, + styles.size, + "Body type $bodyType must define a style for every part.", + ) + for (part in AppearancePart.entries) { + val style = styles[part.slot] + assertTrue( + part.accepts(style, bodyType), + "${part.label} style $style is not valid for body type $bodyType.", + ) + } + } + } + + /** + * Every jaw id is an actual beard or moustache model - there is no blank one - so body type B + * has to default to no model at all, or switching to it leaves a beard on the character. + */ + @Test + fun `body type B defaults to no facial hair`() { + assertEquals( + Appearance.NO_IDENT_KIT, + AppearancePart.Jaw.defaultFor(Appearance.BODY_TYPE_B), + "Body type B must default to no jaw model.", + ) + assertTrue( + AppearancePart.Jaw.accepts(Appearance.NO_IDENT_KIT, Appearance.BODY_TYPE_B), + "The jaw slot must accept being left empty.", + ) + } + + /** Only facial hair is optional; a body with no torso or legs would render as gaps. */ + @Test + fun `only the jaw slot may be left empty`() { + for (part in AppearancePart.entries) { + assertEquals( + part == AppearancePart.Jaw, + part.optional, + "${part.label} optionality is wrong.", + ) + if (part.optional) { + continue + } + for (bodyType in listOf(Appearance.BODY_TYPE_A, Appearance.BODY_TYPE_B)) { + assertFalse( + part.accepts(Appearance.NO_IDENT_KIT, bodyType), + "${part.label} must not accept an empty slot.", + ) + assertTrue( + part.defaultFor(bodyType) >= 0, + "${part.label} must default to a real model.", + ) + } + } + } + + /** + * The cycling menus show a position rather than a raw id, and step by index - so every style + * must be findable in its own list, and stepping must wrap rather than run off either end. + */ + @Test + fun `every style has a position in its own list`() { + for (part in AppearancePart.entries) { + for (bodyType in listOf(Appearance.BODY_TYPE_A, Appearance.BODY_TYPE_B)) { + val styles = part.stylesFor(bodyType) + styles.forEachIndexed { index, style -> + assertEquals( + "${index + 1}/${styles.size}", + part.positionOf(style, bodyType), + "${part.label} style $style should report its position.", + ) + } + } + } + } + + @Test + fun `an empty or foreign style reports no position`() { + val jaw = AppearancePart.Jaw + assertEquals("none", jaw.positionOf(Appearance.NO_IDENT_KIT, Appearance.BODY_TYPE_B)) + + // A body type A jaw held by a body type B character is exactly the mix-up that started + // this: it must be reported, not silently shown as a valid position. + val foreign = jaw.stylesFor(Appearance.BODY_TYPE_A).first() + assertTrue( + jaw.positionOf(foreign, Appearance.BODY_TYPE_B).contains("not body type"), + "A style from the other body type must be flagged.", + ) + } + + @Test + fun `stepping wraps around both ends of a style list`() { + for (part in AppearancePart.entries) { + for (bodyType in listOf(Appearance.BODY_TYPE_A, Appearance.BODY_TYPE_B)) { + val styles = part.stylesFor(bodyType) + val last = styles.size - 1 + assertEquals(styles.first(), styles[(last + 1).mod(styles.size)], "Next must wrap.") + assertEquals(styles.last(), styles[(0 - 1).mod(styles.size)], "Previous must wrap.") + } + } + } + + /** `Appearance.setIdentKit` has to accept the sentinel the cheat menu relies on. */ + @Test + fun `the appearance api accepts the no-model sentinel`() { + val appearance = Appearance() + appearance.setIdentKit(AppearancePart.Jaw.slot, Appearance.NO_IDENT_KIT) + assertEquals( + Appearance.NO_IDENT_KIT, + appearance.identKitSnapshot()[AppearancePart.Jaw.slot].toInt(), + ) + } + + @Test + fun `default appearance values fall inside their own limits`() { + for (identKit in DefaultAppearance.identKit) { + assertTrue(identKit in 0..MAX_IDENT_KIT, "Default ident-kit out of range: $identKit") + } + for (colour in DefaultAppearance.colours) { + assertTrue(colour in 0..MAX_COLOUR, "Default colour out of range: $colour") + } + } +} diff --git a/content/other/consumables/src/main/kotlin/org.rsmod.content.other.consumables/food/ConsumeFoodScript.kt b/content/other/consumables/src/main/kotlin/org.rsmod.content.other.consumables/food/ConsumeFoodScript.kt index ce390338f..658b39c66 100644 --- a/content/other/consumables/src/main/kotlin/org.rsmod.content.other.consumables/food/ConsumeFoodScript.kt +++ b/content/other/consumables/src/main/kotlin/org.rsmod.content.other.consumables/food/ConsumeFoodScript.kt @@ -5,6 +5,8 @@ import jakarta.inject.Inject import org.rsmod.api.area.checker.AreaChecker import org.rsmod.api.area.checker.isInWilderness import org.rsmod.api.player.output.UpdateRun +import org.rsmod.api.player.hook.PlayerRestrictions +import org.rsmod.api.player.hook.RestrictedAction import org.rsmod.api.player.protect.ProtectedAccess import org.rsmod.api.player.stat.baseHitpointsLvl import org.rsmod.api.player.stat.hitpoints @@ -23,6 +25,7 @@ class ConsumeFoodScript @Inject constructor( private val areaChecker: AreaChecker, + private val restrictions: PlayerRestrictions, private val effects: FoodEffectService, private val specialEffects: FoodSpecialEffectService, ) : PluginScript() { @@ -180,6 +183,13 @@ constructor( type: ItemServerType, inventory: Inventory, ) { + val restriction = + restrictions.check(player, RestrictedAction.Food) + if (restriction != null) { + mes(restriction) + return + } + val consumableType = food.consumableType() diff --git a/content/other/consumables/src/main/kotlin/org.rsmod.content.other.consumables/potion/ConsumePotionScript.kt b/content/other/consumables/src/main/kotlin/org.rsmod.content.other.consumables/potion/ConsumePotionScript.kt index 09a4fbfa6..adae83639 100644 --- a/content/other/consumables/src/main/kotlin/org.rsmod.content.other.consumables/potion/ConsumePotionScript.kt +++ b/content/other/consumables/src/main/kotlin/org.rsmod.content.other.consumables/potion/ConsumePotionScript.kt @@ -4,6 +4,8 @@ import dev.openrune.types.ItemServerType import jakarta.inject.Inject import org.rsmod.api.area.checker.AreaChecker import org.rsmod.api.area.checker.isInWilderness +import org.rsmod.api.player.hook.PlayerRestrictions +import org.rsmod.api.player.hook.RestrictedAction import org.rsmod.api.player.protect.ProtectedAccess import org.rsmod.api.script.onOpHeld1 import org.rsmod.api.script.onOpHeld2 @@ -21,6 +23,7 @@ class ConsumePotionScript @Inject constructor( private val areaChecker: AreaChecker, + private val restrictions: PlayerRestrictions, private val effects: PotionEffectService, private val activityAccess: ConsumableActivityAccess, ) : PluginScript() { @@ -168,6 +171,13 @@ constructor( return } + val restriction = + restrictions.check(player, RestrictedAction.Drink) + if (restriction != null) { + mes(restriction) + return + } + val doseIndex = potion.items.indexOfFirst { item -> item.id == type.id diff --git a/content/other/defend-anims/build.gradle.kts b/content/other/defend-anims/build.gradle.kts new file mode 100644 index 000000000..a7e5cb889 --- /dev/null +++ b/content/other/defend-anims/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) +} diff --git a/content/other/defend-anims/src/main/kotlin/org/rsmod/content/other/defendanims/DefendAnims.kt b/content/other/defend-anims/src/main/kotlin/org/rsmod/content/other/defendanims/DefendAnims.kt new file mode 100644 index 000000000..a997267d5 --- /dev/null +++ b/content/other/defend-anims/src/main/kotlin/org/rsmod/content/other/defendanims/DefendAnims.kt @@ -0,0 +1,119 @@ +package org.rsmod.content.other.defendanims + +import dev.openrune.util.WeaponCategory +import dev.openrune.util.Wearpos + +/** + * The block animation an equipped item should give the player when they are hit, for every item + * the cache does not say so itself. + * + * The combat api resolves a defender's animation from the `param.defend_anim` of what they are + * holding: the left hand wins unless it is the bare-handed block, then the right hand, then + * `seq.human_unarmedblock`. The server item data leaves that param off two groups of items, and + * both fall through to the bare-handed block: + * + * - **Every shield, defender, book, tome and ward.** No off-hand item carries a block animation, + * so a player with a kiteshield blocks with their weapon, and a player with a defender or a book + * blocks with their fists. + * - **A handful of weapons.** All scimitars, the blade of Saeldor, the bone dagger, Verac's flail + * and a few others have attack animations but no block animation. + * + * This table is pure data so it can be tested without a cache. [resolve] answers with the + * animation to assign, or `null` when the item should be left alone. Items that already declare a + * `param.defend_anim` are never touched - `seq.human_unarmedblock` on an off-hand item is a + * deliberate "use the weapon's block" marker (the bruma torch, for one) and must survive. + */ +object DefendAnims { + const val UNARMED_BLOCK = "seq.human_unarmedblock" + const val SHIELD_BLOCK = "seq.human_shield_defence" + const val DEFENDER_BLOCK = "seq.warguild_parry_defend" + + /** + * Off-hand items whose lowercase display name contains one of these play [SHIELD_BLOCK] (or + * [DEFENDER_BLOCK] for defenders). Anything else in the shield slot - lanterns, torches, + * satchels - keeps the weapon's block animation, as it does on Old School RuneScape. + */ + private val shieldKeywords = + listOf("shield", "defender", "ward", "book", "tome", "buckler", "deflector", "ket-xil") + + /** + * Block animation per weapon category for weapons that declare none. Categories that block + * bare-handed on Old School RuneScape (bows, crossbows, thrown, guns) are left out on purpose: + * the resolver's fallback already gives them [UNARMED_BLOCK]. + */ + private val weaponCategoryBlocks: Map = + mapOf( + WeaponCategory.Axe to "seq.human_axe_block", + WeaponCategory.Pickaxe to "seq.human_axe_block", + WeaponCategory.Claw to "seq.human_axe_block", + WeaponCategory.Blunt to "seq.human_blunt_block", + WeaponCategory.Spiked to "seq.human_blunt_block", + WeaponCategory.Bludgeon to "seq.slayer_granite_maul_defend", + WeaponCategory.SlashSword to "seq.human_sword_def", + WeaponCategory.StabSword to "seq.human_sword_def", + WeaponCategory.Salamander to "seq.human_sword_def", + WeaponCategory.TwoHandedSword to "seq.human_dhsword_block", + WeaponCategory.GodSword to "seq.dh_sword_update_defend", + WeaponCategory.Polearm to "seq.human_spear_block", + WeaponCategory.Spear to "seq.human_spear_block", + WeaponCategory.Polestaff to "seq.human_staff_block", + WeaponCategory.Staff to "seq.human_staff_block", + WeaponCategory.BladedStaff to "seq.human_stafforb_block", + WeaponCategory.PoweredStaff to "seq.human_stafforb_block", + WeaponCategory.Scythe to "seq.human_scythe_block", + WeaponCategory.Whip to "seq.slayer_abyssal_whip_defend", + WeaponCategory.Banner to "seq.human_banner_block", + WeaponCategory.Bulwark to "seq.human_dinhs_bulwark_block", + WeaponCategory.Chinchompas to "seq.human_chinchompa_defend", + ) + + /** + * Exact-name overrides, checked before the keyword and category rules. Keys are lowercase + * display names. Empty today; this is where a single item that the rules get wrong goes. + */ + private val itemOverrides: Map = emptyMap() + + /** Every animation this table can hand out, for boot-time validation. */ + val allAnimations: Set = + buildSet { + add(SHIELD_BLOCK) + add(DEFENDER_BLOCK) + addAll(weaponCategoryBlocks.values) + addAll(itemOverrides.values) + } + + /** + * The `seq.*` name to assign as `param.defend_anim`, or `null` to leave the item as it is. + * + * @param name the item's display name. + * @param wearpos the item's primary wear position slot ([Wearpos.slot]), `-1` if not worn. + * @param category the item's weapon category. + * @param hasDefendAnim whether the item already declares `param.defend_anim`. + */ + fun resolve( + name: String, + wearpos: Int, + category: WeaponCategory, + hasDefendAnim: Boolean, + ): String? { + if (hasDefendAnim) { + return null + } + val lowercase = name.lowercase() + itemOverrides[lowercase]?.let { + return it + } + return when (wearpos) { + Wearpos.LeftHand.slot -> resolveOffhand(lowercase) + Wearpos.RightHand.slot -> weaponCategoryBlocks[category] + else -> null + } + } + + private fun resolveOffhand(lowercaseName: String): String? { + if (shieldKeywords.none { it in lowercaseName }) { + return null + } + return if ("defender" in lowercaseName) DEFENDER_BLOCK else SHIELD_BLOCK + } +} diff --git a/content/other/defend-anims/src/main/kotlin/org/rsmod/content/other/defendanims/DefendAnimsScript.kt b/content/other/defend-anims/src/main/kotlin/org/rsmod/content/other/defendanims/DefendAnimsScript.kt new file mode 100644 index 000000000..d88debf7b --- /dev/null +++ b/content/other/defend-anims/src/main/kotlin/org/rsmod/content/other/defendanims/DefendAnimsScript.kt @@ -0,0 +1,67 @@ +package org.rsmod.content.other.defendanims + +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.ParamMap +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import org.rsmod.api.config.refs.params +import org.rsmod.api.script.onGameStartup +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Fills in `param.defend_anim` on every cached item that [DefendAnims] has an answer for, so the + * combat api's existing resolver plays the right block animation when a player is hit. + * + * This runs once, on [onGameStartup], after the cache is loaded and before the first game cycle. + * It edits the shared [ItemServerType] instances in place - the same objects that + * `getOrNull(player.righthand)` hands to the resolver - so no combat code needs to change and no + * cache rebuild is needed. A pack could ship the same params, but this way a new item added to the + * cache is covered on the next boot without touching a TOML file. + * + * Only items with no `param.defend_anim` are changed. Certificates and placeholders are skipped; + * they are never worn. + */ +class DefendAnimsScript : PluginScript() { + override fun ScriptContext.startup() { + onGameStartup { applyCorrections() } + } + + private fun applyCorrections() { + val sequenceIds = DefendAnims.allAnimations.associateWith { it.asRSCM(RSCMType.SEQ) } + for ((seq, id) in sequenceIds) { + checkNotNull(ServerCacheManager.getAnim(id)) { "Missing sequence in cache: $seq ($id)" } + } + + var corrected = 0 + for (item in ServerCacheManager.getItems().values) { + val seq = correctionFor(item) ?: continue + item.paramMap = item.paramMap.withDefendAnim(sequenceIds.getValue(seq)) + corrected++ + } + logger.info { "Corrected the block animation of $corrected items." } + } + + private fun correctionFor(item: ItemServerType): String? { + if (item.isCert || item.isPlaceholder) { + return null + } + val hasDefendAnim = item.paramMap?.contains(params.defend_anim) == true + return DefendAnims.resolve(item.name, item.wearpos1, item.weaponCategory, hasDefendAnim) + } + + /** + * A new map rather than [ParamMap.plus]: `plus` keeps this map's already-decoded typed values + * and would not include the new key, so the resolver would still see nothing. + */ + private fun ParamMap?.withDefendAnim(sequenceId: Int): ParamMap { + val primitives = this?.primitiveMap.orEmpty() + (params.defend_anim.id to sequenceId) + return ParamMap(primitiveMap = primitives) + } + + private companion object { + private val logger = InlineLogger() + } +} diff --git a/content/other/defend-anims/src/test/kotlin/org/rsmod/content/other/defendanims/DefendAnimsTest.kt b/content/other/defend-anims/src/test/kotlin/org/rsmod/content/other/defendanims/DefendAnimsTest.kt new file mode 100644 index 000000000..5f4592d05 --- /dev/null +++ b/content/other/defend-anims/src/test/kotlin/org/rsmod/content/other/defendanims/DefendAnimsTest.kt @@ -0,0 +1,100 @@ +package org.rsmod.content.other.defendanims + +import dev.openrune.util.WeaponCategory +import dev.openrune.util.Wearpos +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class DefendAnimsTest { + @Test + fun `items that already declare a block animation are left alone`() { + assertNull(offhand("Rune kiteshield", hasDefendAnim = true)) + assertNull(weapon("Dragon scimitar", WeaponCategory.SlashSword, hasDefendAnim = true)) + // `seq.human_unarmedblock` on an off-hand is a "use the weapon's block" marker. + assertNull(offhand("Bruma torch", hasDefendAnim = true)) + } + + @Test + fun `shields, books, tomes and wards block with the shield animation`() { + val names = + listOf( + "Rune kiteshield", + "Bronze sq shield", + "Dragonfire shield", + "Elysian spirit shield", + "Toktz-ket-xil", + "Book of law", + "Tome of fire", + "Mage's book", + "Dragonfire ward", + "Twisted buckler", + "Dragonfire deflector", + ) + for (name in names) { + assertEquals(DefendAnims.SHIELD_BLOCK, offhand(name), name) + } + } + + @Test + fun `defenders block with the defender parry`() { + val names = + listOf("Bronze defender", "Dragon defender", "Avernic defender", "Ghommal's avernic defender") + for (name in names) { + assertEquals(DefendAnims.DEFENDER_BLOCK, offhand(name), name) + } + } + + @Test + fun `other off-hand items keep the weapon's block`() { + for (name in listOf("Bullseye lantern", "Unlit bug lantern", "Cabbage")) { + assertNull(offhand(name), name) + } + } + + @Test + fun `weapons without a block animation fall back to their category`() { + assertEquals("seq.human_sword_def", weapon("Dragon scimitar", WeaponCategory.SlashSword)) + assertEquals("seq.human_sword_def", weapon("Blade of saeldor", WeaponCategory.SlashSword)) + assertEquals("seq.human_sword_def", weapon("Bone dagger", WeaponCategory.StabSword)) + assertEquals("seq.human_blunt_block", weapon("Verac's flail", WeaponCategory.Spiked)) + assertEquals("seq.human_axe_block", weapon("Rune axe", WeaponCategory.Axe)) + assertEquals("seq.human_spear_block", weapon("Dragon halberd", WeaponCategory.Polearm)) + assertEquals("seq.slayer_abyssal_whip_defend", weapon("Abyssal whip", WeaponCategory.Whip)) + assertEquals("seq.dh_sword_update_defend", weapon("Armadyl godsword", WeaponCategory.GodSword)) + } + + @Test + fun `ranged weapons and unarmed keep the bare-handed block`() { + assertNull(weapon("Magic shortbow", WeaponCategory.Bow)) + assertNull(weapon("Rune crossbow", WeaponCategory.Crossbow)) + assertNull(weapon("Rune knife", WeaponCategory.Thrown)) + assertNull(weapon("Lyre", WeaponCategory.Unarmed)) + } + + @Test + fun `only hand slots are considered`() { + val unarmed = WeaponCategory.Unarmed + assertNull(DefendAnims.resolve("Rune full helm", Wearpos.Hat.slot, unarmed, false)) + assertNull(DefendAnims.resolve("Rune platebody", Wearpos.Torso.slot, unarmed, false)) + assertNull(DefendAnims.resolve("Coins", -1, unarmed, false)) + } + + @Test + fun `every animation the table hands out is a seq reference`() { + assertTrue(DefendAnims.allAnimations.isNotEmpty()) + for (seq in DefendAnims.allAnimations) { + assertTrue(seq.startsWith("seq."), seq) + } + } + + private fun offhand(name: String, hasDefendAnim: Boolean = false): String? = + DefendAnims.resolve(name, Wearpos.LeftHand.slot, WeaponCategory.Unarmed, hasDefendAnim) + + private fun weapon( + name: String, + category: WeaponCategory, + hasDefendAnim: Boolean = false, + ): String? = DefendAnims.resolve(name, Wearpos.RightHand.slot, category, hasDefendAnim) +} diff --git a/content/other/emirs-arena/build.gradle.kts b/content/other/emirs-arena/build.gradle.kts new file mode 100644 index 000000000..fea77c306 --- /dev/null +++ b/content/other/emirs-arena/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.areaChecker) + implementation(projects.api.attr) + implementation(projects.api.combat.combatCommons) + implementation(projects.api.combat.combatManager) + implementation(projects.api.death) + implementation(projects.api.mechanics.toxins) + implementation(projects.api.pluginCommons) + implementation(projects.api.scriptAdvanced) + implementation(projects.api.shops) + implementation(projects.content.interfaces.bank) +} diff --git a/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArena.kt b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArena.kt new file mode 100644 index 000000000..742211c4a --- /dev/null +++ b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArena.kt @@ -0,0 +1,117 @@ +package org.rsmod.content.other.emirsarena + +import org.rsmod.api.attr.AttributeKey +import org.rsmod.content.other.emirsarena.duel.Duel +import org.rsmod.game.entity.Player +import org.rsmod.map.CoordGrid + +/** Names, places and player attributes shared by the Emir's Arena scripts. */ +object EmirsArena { + const val AREA: String = "area.emirs_arena" + + /** Inside the hospital, between the beds; where duellists are taken when a duel ends. */ + val LOBBY: CoordGrid = CoordGrid(3365, 3275, 0) + const val LOBBY_RADIUS: Int = 2 + + const val OPTIONS_INTERFACE: String = "interface.pvp_arena_legacyduel_options" + const val CONFIRM_INTERFACE: String = "interface.pvp_arena_legacyduel_confirm" + const val SCOREBOARD_INTERFACE: String = "interface.pvp_arena_scoreboard" + + const val OPTIONS_PREFIX: String = "component.pvp_arena_legacyduel_options:" + const val CONFIRM_PREFIX: String = "component.pvp_arena_legacyduel_confirm:" + const val SCOREBOARD_TEXT: String = "component.pvp_arena_scoreboard:text" + + /** + * The cache has no Duel Arena stake screen any more, so stakes reuse the trade screens: the + * trade main window for the two offers, the party-drop side panel (whose script draws the + * backpack with "Offer 1/5/X/All") to add items, and the trade confirmation screen. + */ + const val STAKE_INTERFACE: String = "interface.trademain" + const val STAKE_SIDE_INTERFACE: String = "interface.partydrop_side" + const val STAKE_CONFIRM_INTERFACE: String = "interface.tradeconfirm" + const val STAKE_PREFIX: String = "component.trademain:" + const val STAKE_SIDE_ITEMS: String = "component.partydrop_side:items" + const val STAKE_CONFIRM_PREFIX: String = "component.tradeconfirm:" + /** Persisted holding inventory for stakes; survives crashes and restarts mid-duel. */ + const val STAKE_INV: String = "inv.emirs_arena_stake" + + /** The inv id the trade screens are hard-wired to; the stake inv is shown under this id. */ + const val STAKE_DISPLAY_INV: String = "inv.tradeoffer" + const val STAKE_MODIFIED_MINE_VARBIT: String = "varbit.trade_this_player_removed" + const val STAKE_MODIFIED_OTHER_VARBIT: String = "varbit.trade_other_player_removed" + const val OFFER_QUANTITY_VARBIT: String = "varbit.farming_tools_selectedquantity" + + /** The client mirrors this varp into the option and confirm screens (clientscripts 6169/6181). */ + const val RULES_VARP: String = "varp.dueloptions" + const val ACCEPT_OPTIONS_VARBIT: String = "varbit.pvpa_legacyduel_transmit_iacceptoptions" + const val ACCEPT_CONFIRM_VARBIT: String = "varbit.pvpa_legacyduel_transmit_iacceptconfirm" + + const val RANK_POINTS_VARBIT: String = "varbit.pvpa_rank_points" + const val REWARD_POINTS_VARBIT: String = "varbit.pvpa_points_currency" + const val REWARD_CURRENCY: String = "currency.pvp_arena_points" + const val REWARD_SHOP_INV: String = "inv.pvpa_shop_inv" + + /** + * Inside the arena the attack slot reads "Challenge"; clicking it (or the chat request, which + * the client turns into the same option) is turned into a challenge by the arena's attack + * hook. During a fight the slot reads "Fight". + */ + const val CHALLENGE_SLOT: Int = 1 + const val FIGHT_SLOT: Int = 1 + + const val START_QUEUE: String = "queue.emirs_arena_start" + const val LEAVE_QUEUE: String = "queue.emirs_arena_leave" + + const val TRAPDOOR_LOC: String = "loc.pvpa_battlearea_trapdoor" + + /** + * Js5 index 11 groups (the ids the client plays, listed on the OSRS wiki jingle catalogue): + * "Commence The Fight!" when the duellists are placed in the arena, "You Are Victorious!" for + * the winner. The loser's death jingle comes from the normal death sequence. + */ + const val DUEL_START_JINGLE: Int = 97 + const val DUEL_WIN_JINGLE: Int = 98 + + /** Worn slots the options screen has a toggle for (6, 8 and 11 are client-only slots). */ + val WORN_OPTION_SLOTS: List = listOf(0, 1, 2, 3, 4, 5, 7, 9, 10, 12, 13) + + /** Weapons with negative stats that the "Fun Weapons" rule allows. */ + val FUN_WEAPONS: List = + listOf( + "obj.rubber_chicken", + "obj.flowers_waterfall_quest", + "obj.flowers_waterfall_quest_red", + "obj.flowers_waterfall_quest_blue", + "obj.flowers_waterfall_quest_yellow", + "obj.flowers_waterfall_quest_purple", + "obj.flowers_waterfall_quest_orange", + "obj.flowers_waterfall_quest_mixed", + "obj.flowers_waterfall_quest_white", + "obj.flowers_waterfall_quest_black", + "obj.stale_baguette", + "obj.golden_tench", + "obj.birthday_cake", + "obj.twocats_mouse_toy", + ) + + const val SCOREBOARD_SIZE: Int = 50 + + const val SUPPLIES_MESSAGE: String = + "The supplies chest is only stocked on official PvP Arena worlds." +} + +/** Session and saved state kept on the player. */ +object EmirsArenaAttributes { + val DUEL: AttributeKey = AttributeKey() + val CHALLENGE_TARGET: AttributeKey = AttributeKey() + + val LEGACY_WINS: AttributeKey = AttributeKey(persistenceKey = "emirs_arena_legacy_wins") + val LEGACY_LOSSES: AttributeKey = + AttributeKey(persistenceKey = "emirs_arena_legacy_losses") + val RANKED_WINS: AttributeKey = AttributeKey(persistenceKey = "emirs_arena_ranked_wins") + val RANKED_LOSSES: AttributeKey = + AttributeKey(persistenceKey = "emirs_arena_ranked_losses") + val WIN_STREAK: AttributeKey = AttributeKey(persistenceKey = "emirs_arena_win_streak") + val SAVED_PRESET: AttributeKey = AttributeKey(persistenceKey = "emirs_arena_duel_preset") + val LAST_RULES: AttributeKey = AttributeKey(persistenceKey = "emirs_arena_last_rules") +} diff --git a/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaHooks.kt b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaHooks.kt new file mode 100644 index 000000000..539a4fffd --- /dev/null +++ b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaHooks.kt @@ -0,0 +1,185 @@ +package org.rsmod.content.other.emirsarena + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.util.Wearpos +import jakarta.inject.Inject +import org.rsmod.api.area.checker.AreaChecker +import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.api.combat.commons.hook.PvPAttackRestrictionHook +import org.rsmod.api.death.PlayerDeathCleanupHook +import org.rsmod.api.death.PlayerDeathContext +import org.rsmod.api.death.PlayerDeathHandling +import org.rsmod.api.death.PlayerDeathHook +import org.rsmod.api.death.PlayerRespawnHook +import org.rsmod.api.death.PvPAttackValidateHook +import org.rsmod.api.death.PvPAttackValidateResult +import org.rsmod.api.death.UntradeableHandling +import org.rsmod.api.player.hook.PlayerRestrictionHook +import org.rsmod.api.player.hook.PlayerTeleportValidateHook +import org.rsmod.api.player.hook.RestrictedAction +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.content.other.emirsarena.duel.DuelManager +import org.rsmod.content.other.emirsarena.duel.DuelRule +import org.rsmod.content.other.emirsarena.duel.DuelStage +import org.rsmod.game.entity.Player +import org.rsmod.map.CoordGrid + +/** + * Everything the engine asks the arena about while a duel is running: who may attack whom, which + * attacks and actions the rules forbid, that nobody teleports out, and that dying in a duel costs + * no items and wakes you up in the hospital. + */ +public class EmirsArenaHooks +@Inject +constructor(private val manager: DuelManager, private val areaChecker: AreaChecker) : + PvPAttackValidateHook, + PvPAttackRestrictionHook, + PlayerRestrictionHook, + PlayerTeleportValidateHook, + PlayerDeathHook, + PlayerDeathCleanupHook, + PlayerRespawnHook { + private val funWeapons: Set by lazy { + EmirsArena.FUN_WEAPONS.map { it.asRSCM(RSCMType.OBJ) }.toSet() + } + + override fun validate(attacker: Player, target: Player): PvPAttackValidateResult { + val duel = manager.duelOf(attacker) + if (duel == null) { + if (manager.duelOf(target)?.isActive == true) { + return PvPAttackValidateResult.Deny("${target.displayName} is in a duel.") + } + // Inside the arena the attack option is "Challenge": clicking it, or the chat request + // (which the client sends as the same option), issues or accepts a challenge instead + // of starting a fight. Fights only happen inside a duel here. + if (areaChecker.inArea(EmirsArena.AREA, attacker.coords)) { + manager.challenge(attacker, target) + return PvPAttackValidateResult.Silent + } + return PvPAttackValidateResult.Pass + } + if (!duel.contains(target)) { + return PvPAttackValidateResult.Deny("You can only attack your duel opponent.") + } + return when (duel.stage) { + DuelStage.Fighting -> PvPAttackValidateResult.Pass + DuelStage.Countdown -> PvPAttackValidateResult.Deny("The duel hasn't started yet!") + else -> PvPAttackValidateResult.Deny("You are not fighting a duel right now.") + } + } + + override fun restriction( + attacker: Player, + target: Player, + attack: CombatAttack.PlayerAttack, + special: Boolean, + ): String? { + val duel = manager.duelOf(attacker) ?: return null + if (duel.stage != DuelStage.Fighting) { + return null + } + val rules = duel.rules + if (special && rules.has(DuelRule.NoSpecialAttacks)) { + return "Special attacks have been disabled for this duel." + } + val styleMessage = + when (attack) { + is CombatAttack.Melee -> + "Melee attacks have been disabled for this duel.".takeIf { + rules.has(DuelRule.NoMelee) + } + is CombatAttack.Ranged -> + "Ranged attacks have been disabled for this duel.".takeIf { + rules.has(DuelRule.NoRanged) + } + is CombatAttack.Magic -> + "Magic attacks have been disabled for this duel.".takeIf { + rules.has(DuelRule.NoMagic) + } + } + if (styleMessage != null) { + return styleMessage + } + if (rules.has(DuelRule.FunWeapons)) { + val weapon = + when (attack) { + is CombatAttack.Melee -> attack.weapon + is CombatAttack.Ranged -> attack.weapon + is CombatAttack.Spell -> attack.weapon + is CombatAttack.Staff -> attack.weapon + } + if (weapon == null || weapon.id !in funWeapons) { + return "You can only use fun weapons in this duel." + } + } + return null + } + + override fun restriction(player: Player, action: RestrictedAction): String? { + val duel = manager.duelOf(player) ?: return null + if (!duel.isActive) { + return null + } + val rules = duel.rules + return when (action) { + RestrictedAction.Walk -> + when { + duel.stage == DuelStage.Countdown -> "The duel hasn't started yet!" + rules.has(DuelRule.NoMovement) -> "Movement has been disabled for this duel." + else -> null + } + RestrictedAction.Prayer -> + "Prayer has been disabled for this duel.".takeIf { rules.has(DuelRule.NoPrayer) } + RestrictedAction.Food -> + "Food has been disabled for this duel.".takeIf { rules.has(DuelRule.NoFood) } + RestrictedAction.Drink -> + "Drinks have been disabled for this duel.".takeIf { rules.has(DuelRule.NoDrinks) } + is RestrictedAction.Equip -> { + val slots = + listOf(action.obj.wearpos1, action.obj.wearpos2, action.obj.wearpos3).filter { + it >= 0 + } + when { + rules.has(DuelRule.NoWeaponSwitch) && Wearpos.RightHand.slot in slots -> + "Weapon switching has been disabled for this duel." + slots.any { rules.isSlotDisabled(it) } -> + "You can't wear that in this duel." + else -> null + } + } + } + } + + override fun validate(player: Player, type: TeleportType, areaChecker: AreaChecker): String? { + val duel = manager.duelOf(player) ?: return null + if (!duel.isActive || type == TeleportType.Exempt) { + return null + } + return "You can't teleport out of a duel!" + } + + override fun handleDeath(context: PlayerDeathContext): PlayerDeathHandling? { + val duel = manager.duelOf(context.player) ?: return null + if (!duel.isActive) { + return null + } + return PlayerDeathHandling( + keepCount = Int.MAX_VALUE, + dropReceiver = null, + dropDuration = 0, + revealDelay = 0, + supplyPile = false, + untradeableHandling = UntradeableHandling.KEEP, + ) + } + + override fun respawn(player: Player): CoordGrid? { + val duel = manager.duelOf(player) ?: return null + return if (duel.isActive) EmirsArena.LOBBY else null + } + + override fun cleanup(player: Player) { + manager.finishAfterDeath(player) + } +} diff --git a/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaModule.kt b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaModule.kt new file mode 100644 index 000000000..8a6ec44ce --- /dev/null +++ b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaModule.kt @@ -0,0 +1,24 @@ +package org.rsmod.content.other.emirsarena + +import org.rsmod.api.combat.commons.hook.PvPAttackRestrictionHook +import org.rsmod.api.death.PlayerDeathCleanupHook +import org.rsmod.api.death.PlayerDeathHook +import org.rsmod.api.death.PlayerRespawnHook +import org.rsmod.api.death.PvPAttackValidateHook +import org.rsmod.api.player.hook.PlayerRestrictionHook +import org.rsmod.api.player.hook.PlayerTeleportValidateHook +import org.rsmod.content.other.emirsarena.duel.DuelManager +import org.rsmod.plugin.module.PluginModule + +public class EmirsArenaModule : PluginModule() { + override fun bind() { + bindInstance() + addSetBinding(EmirsArenaHooks::class.java) + addSetBinding(EmirsArenaHooks::class.java) + addSetBinding(EmirsArenaHooks::class.java) + addSetBinding(EmirsArenaHooks::class.java) + addSetBinding(EmirsArenaHooks::class.java) + addSetBinding(EmirsArenaHooks::class.java) + addSetBinding(EmirsArenaHooks::class.java) + } +} diff --git a/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaNpcScript.kt b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaNpcScript.kt new file mode 100644 index 000000000..2f8be824c --- /dev/null +++ b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaNpcScript.kt @@ -0,0 +1,584 @@ +package org.rsmod.content.other.emirsarena + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.dialogue.mesanims +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.stat +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.player.stat.statRestore +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpc3 +import org.rsmod.api.script.onOpNpc4 +import org.rsmod.api.script.onOpNpc5 +import org.rsmod.api.shops.Shops +import org.rsmod.api.shops.operation.ShopOperationMap +import org.rsmod.content.interfaces.bank.openBank +import org.rsmod.content.other.emirsarena.duel.DuelManager +import org.rsmod.content.other.emirsarena.ranked.ArenaRanking +import org.rsmod.events.EventBus +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The people of Emir's Arena: Mubariz and his reward shop, the two guides, the arena guards, the + * hospital staff who heal for free, Fadli's bank and rotten fruit stall, Chris, and the crowd. + */ +class EmirsArenaNpcScript +@Inject +constructor( + private val shops: Shops, + private val shopOperations: ShopOperationMap, + private val eventBus: EventBus, + private val manager: DuelManager, +) : PluginScript() { + override fun ScriptContext.startup() { + shopOperations.costOf(EmirsArena.REWARD_CURRENCY) { type -> REWARD_PRICES[type.id] ?: type.cost } + + onOpNpc1("npc.duel_mubariz") { startDialogue(it.npc) { mubariz(it.npc) } } + onOpNpc3("npc.duel_mubariz") { openRewardShop(it.npc) } + + onOpNpc1("npc.pvpa_duel_guide") { startDialogue(it.npc) { duelGuide() } } + onOpNpc1("npc.pvpa_1v1_guide") { startDialogue(it.npc) { tournamentGuide() } } + + for (guard in GUARDS) { + onOpNpc1(guard) { startDialogue(it.npc) { arenaGuard() } } + } + + for (nurse in NURSES) { + onOpNpc1(nurse) { startDialogue(it.npc) { nurse(it.npc) } } + onOpNpc3(nurse) { heal(it.npc) } + } + onOpNpc1("npc.duel_jaraah") { startDialogue(it.npc) { jaraah() } } + onOpNpc3("npc.duel_jaraah") { heal(it.npc) } + onOpNpc1("npc.duel_monk") { startDialogue(it.npc) { hamid() } } + onOpNpc1("npc.duel_injured1") { startDialogue(it.npc) { zahwa() } } + + onOpNpc1("npc.duel_fadli") { startDialogue(it.npc) { fadli(it.npc) } } + onOpNpc3("npc.duel_fadli") { player.openBank(eventBus) } + onOpNpc4("npc.duel_fadli") { mes("The Grand Exchange collection service isn't available here.") } + onOpNpc5("npc.duel_fadli") { openFruitStall(it.npc) } + + onOpNpc1("npc.pvpa_repairman") { startDialogue(it.npc) { chris() } } + onOpNpc3("npc.pvpa_repairman") { chatNpc(it.npc, mesanims.sad, "Sorry, nothing's been handed in today.") } + + for (spectator in SPECTATORS) { + onOpNpc1(spectator) { startDialogue(it.npc) { spectator() } } + } + } + + /* Mubariz */ + + private suspend fun Dialogue.mubariz(npc: Npc) { + chatNpc(happy, "Welcome to the Emir's Arena! What can I do for you?") + mubarizMenu(npc) + } + + private suspend fun Dialogue.mubarizMenu(npc: Npc) { + val choice = + choice5( + "What is this place?", + 1, + "How do I get a fight?", + 2, + "What kind of fights are happening today?", + 3, + "Can I see your shop?", + 4, + "I'll be off.", + 5, + ) + when (choice) { + 1 -> { + chatPlayer(quiz, "What is this place?") + chatNpc( + happy, + "This is the Emir's Arena. Fighters from all over Gielinor come here to " + + "test themselves against one another in the combat areas, with the " + + "Emir's guards making sure everything stays fair.", + ) + val more = choice2("Where did this Arena come from?", true, "Thanks.", false) + if (more) { + chatPlayer(quiz, "Where did this Arena come from?") + chatNpc( + neutral, + "It was built in the Second Age and lay buried until the Varrock " + + "Museum dug it out. For years it ran as the Duel Arena, until the " + + "Calamity Coven got their claws into it and rigged the fights.", + ) + chatNpc( + happy, + "The Emir bought it, threw the Coven out and reopened it under his own " + + "name and his own guards. Nobody rigs anything here now.", + ) + } + mubarizMenu(npc) + } + 2 -> { + chatPlayer(quiz, "How do I get a fight?") + chatNpc( + neutral, + "For a ranked duel, sign up on one of the recruitment boards by the hospital " + + "and the staff will match you with the next fighter who signs up. Win and " + + "your rank climbs; either way you earn reward points.", + ) + chatNpc( + neutral, + "If you'd rather pick your own opponent and your own rules, just challenge " + + "them: right-click anyone inside the Arena and choose Challenge.", + ) + val challenge = choice2("I challenge you!", true, "Thanks.", false) + if (challenge) { + chatPlayer(angry, "I challenge you!") + chatNpc(laugh, "Ha! I've been retired a long time, friend. Find someone your own size.") + } + mubarizMenu(npc) + } + 3 -> { + chatPlayer(quiz, "What kind of fights are happening today?") + chatNpc( + neutral, + "Legacy duels and ranked duels, same as every day. Bring your own gear; the " + + "supplies chests are only stocked on the official arena worlds.", + ) + mubarizMenu(npc) + } + 4 -> { + chatPlayer(quiz, "Can I see your shop?") + chatNpc(happy, "Of course. Everything here is bought with the reward points you earn in ranked duels.") + player.openRewardShop(npc) + } + else -> { + chatPlayer(neutral, "I'll be off.") + chatNpc(happy, "See you in the Arena!") + } + } + } + + /** Reward points are spent at face value: no mark-up and no price drift as stock changes. */ + @Suppress("UNUSED_PARAMETER") + private fun Player.openRewardShop(npc: Npc) { + shops.open( + player = this, + title = "PvP Arena Rewards", + shopInv = EmirsArena.REWARD_SHOP_INV, + buyPercentage = 100.0, + sellPercentage = 0.0, + changePercentage = 0.0, + currency = EmirsArena.REWARD_CURRENCY, + ) + } + + private fun ProtectedAccess.openRewardShop(npc: Npc) = player.openRewardShop(npc) + + /* Guides */ + + private suspend fun Dialogue.duelGuide() { + chatNpc( + happy, + "Welcome to the Emir's Arena! Are you interested in a Duel? We have ranked and " + + "unranked Duels available.", + ) + duelGuideMenu() + } + + private suspend fun Dialogue.duelGuideMenu() { + val choice = + choice5( + "What's a Duel?", + 1, + "Ranked or unranked? What does that mean?", + 2, + "How do I get invited to a Duel?", + 3, + "I want to organise my own Duel.", + 4, + "I'm okay, thanks.", + 5, + ) + when (choice) { + 1 -> { + chatPlayer(quiz, "What's a Duel?") + chatNpc(neutral, "Two fighters are placed in a combat arena together, to battle to the death.") + chatNpc(happy, "Don't worry: nobody actually dies here. The hospital patches you up and you keep everything you brought.") + duelGuideMenu() + } + 2 -> { + chatPlayer(quiz, "Ranked or unranked? What does that mean?") + chatNpc( + neutral, + "Every fighter has a rank. Win a ranked Duel and your rank goes up; lose one " + + "and it goes down. The Arena staff arrange ranked matchups, so you never " + + "choose your opponent.", + ) + chatNpc( + neutral, + "An unranked Duel is a casual fight with no change to your rank. Only ranked " + + "Duels earn reward points.", + ) + duelGuideMenu() + } + 3 -> { + chatPlayer(quiz, "How do I get invited to a Duel?") + chatNpc( + neutral, + "Sign up on one of the recruitment boards next to the hospital. You can carry " + + "on with whatever you were doing inside the Arena while you wait; when " + + "another fighter signs up the two of you will be matched.", + ) + duelGuideMenu() + } + 4 -> { + chatPlayer(neutral, "I want to organise my own Duel.") + chatNpc( + neutral, + "Challenge someone directly and you'll get an unranked Duel with whatever rules " + + "the two of you agree on. Right-click a fighter inside the Arena and choose " + + "Challenge; if they challenge you back, the options screen opens.", + ) + chatNpc( + neutral, + "Remember the supplies chests are only stocked on the official arena worlds, " + + "so bring your own equipment.", + ) + duelGuideMenu() + } + else -> chatPlayer(neutral, "I'm okay, thanks.") + } + } + + private suspend fun Dialogue.tournamentGuide() { + chatNpc( + happy, + "Welcome to the Emir's Arena! Are you interested in a 1v1 Tournament? We have " + + "ranked and unranked Tournaments available.", + ) + tournamentGuideMenu() + } + + private suspend fun Dialogue.tournamentGuideMenu() { + val choice = + choice5( + "What's a 1v1 Tournament?", + 1, + "Ranked or unranked? What does that mean?", + 2, + "How do I get invited into a Tournament?", + 3, + "I want to organise my own Tournament.", + 4, + "I'm okay, thanks.", + 5, + ) + when (choice) { + 1 -> { + chatPlayer(quiz, "What's a 1v1 Tournament?") + chatNpc( + neutral, + "A group of fighters is divided into pairs, and each pair fights. The winners " + + "are paired up again and fight, and so on, until eventually only one " + + "person is left standing.", + ) + tournamentGuideMenu() + } + 2 -> { + chatPlayer(quiz, "Ranked or unranked? What does that mean?") + chatNpc( + neutral, + "Ranked Tournaments count towards your standing and pay out rewards; " + + "unranked ones are just for fun.", + ) + tournamentGuideMenu() + } + 3 -> { + chatPlayer(quiz, "How do I get invited into a Tournament?") + chatNpc( + sad, + "I'm afraid the Tournament brackets aren't running at the moment. Sign up " + + "for a ranked Duel on the recruitment board instead, or challenge a " + + "fighter yourself.", + ) + tournamentGuideMenu() + } + 4 -> { + chatPlayer(neutral, "I want to organise my own Tournament.") + chatNpc( + sad, + "Not today, I'm afraid. Until the brackets are running again, the Duel Guide " + + "over there can set you up with a one-on-one.", + ) + tournamentGuideMenu() + } + else -> chatPlayer(neutral, "I'm okay, thanks.") + } + } + + /* Guards */ + + private suspend fun Dialogue.arenaGuard() { + chatNpc(neutral, "Can I help you?") + arenaGuardMenu() + } + + private suspend fun Dialogue.arenaGuardMenu() { + val choice = + choice4( + "What is this place?", + 1, + "What do you do here?", + 2, + "Could you come help me with something?", + 3, + "I'm fine, thanks.", + 4, + ) + when (choice) { + 1 -> { + chatPlayer(quiz, "What is this place?") + chatNpc( + neutral, + "This is the Emir's Arena. If you want a fight arranged, the recruitment " + + "boards are by the hospital to the north.", + ) + arenaGuardMenu() + } + 2 -> { + chatPlayer(quiz, "What do you do here?") + chatNpc( + neutral, + "The Emir put us here to protect the Arena and everyone in it. After what " + + "the Calamity Coven did, he wasn't taking any chances.", + ) + val more = choice2("The Calamity Coven?", true, "I see.", false) + if (more) { + chatPlayer(quiz, "The Calamity Coven?") + chatNpc( + angry, + "Three brigands who ran this place into the ground. Maoma rigged the " + + "fights, Saika forged the accounts and Koriff whipped up the crowds.", + ) + chatNpc(neutral, "They're gone now, and we make sure nothing like them ever comes back.") + } + arenaGuardMenu() + } + 3 -> { + chatPlayer(quiz, "Could you come help me with something?") + chatNpc(neutral, "If the problem isn't in the Arena, it's not my problem.") + arenaGuardMenu() + } + else -> chatPlayer(neutral, "I'm fine, thanks.") + } + } + + /* Hospital */ + + private suspend fun Dialogue.nurse(npc: Npc) { + chatPlayer(happy, "Hi!") + chatNpc(happy, "Hi. How can I help?") + val choice = + choice3( + "Can you heal me?", + 1, + "Do many fighters get badly hurt?", + 2, + "Nothing, thanks.", + 3, + ) + when (choice) { + 1 -> { + chatPlayer(quiz, "Can you heal me?") + access.heal(npc) + } + 2 -> { + chatPlayer(quiz, "Do many fighters get badly hurt?") + chatNpc( + neutral, + "Now and then. Jaraah takes the worst of them. They call him 'The Butcher', " + + "but honestly, most of his patients walk out again.", + ) + } + else -> chatPlayer(neutral, "Nothing, thanks.") + } + } + + private suspend fun ProtectedAccess.heal(npc: Npc) { + if (manager.duelOf(player)?.isActive == true) { + chatNpc(npc, mesanims.angry, "Get back in there and finish your fight first!") + return + } + val hurt = player.statBase("stat.hitpoints") > player.stat("stat.hitpoints") + if (!hurt) { + chatNpc(npc, mesanims.happy, "You look healthy to me!") + return + } + player.statRestore("stat.hitpoints") + chatNpc(npc, mesanims.happy, "There you go. Try not to bleed on the floor next time.") + } + + private suspend fun Dialogue.jaraah() { + chatNpc(shocked, "Ah! You aren't bleeding. Are you sure you need a surgeon?") + val choice = choice2("Can you heal me?", true, "I'll leave you to it.", false) + if (choice) { + chatPlayer(quiz, "Can you heal me?") + access.heal(npc ?: return) + } else { + chatPlayer(neutral, "I'll leave you to it.") + } + } + + private suspend fun Dialogue.hamid() { + chatPlayer(happy, "Hi!") + chatNpc(happy, "Hello traveller. How can I be of assistance?") + val choice = + choice3( + "Can you heal me?", + 1, + "What's a Monk doing in a place such as this?", + 2, + "Which monastery do you come from?", + 3, + ) + when (choice) { + 1 -> { + chatPlayer(quiz, "Can you heal me?") + chatNpc(happy, "You'd be better off speaking to one of the nurses. They are so... nice... after all!") + } + 2 -> { + chatPlayer(quiz, "What's a Monk doing in a place such as this?") + chatNpc(happy, "Well don't tell anyone but I came here because of the nurses!") + chatPlayer(shocked, "Really?") + chatNpc(happy, "It beats being stuck in the monastery!") + } + else -> { + chatPlayer(quiz, "Which monastery do you come from?") + chatNpc(neutral, "I belong to the monastery north of Falador.") + chatPlayer(quiz, "You're a long way from home?") + chatNpc(sad, "Yeh. I miss the guys.") + } + } + } + + private suspend fun Dialogue.zahwa() { + chatNpc(sad, "Ow... please, keep your voice down. Every part of me hurts.") + chatPlayer(quiz, "What happened to you?") + chatNpc(sad, "I signed up for a ranked duel with no armour. Learn from my mistakes, friend.") + } + + /* Fadli and Chris */ + + private suspend fun Dialogue.fadli(npc: Npc) { + chatNpc(bored, "What?") + val choice = + choice5( + "What do you do?", + 1, + "I'd like to access my bank, please.", + 2, + "I'd like to collect items.", + 3, + "Do you watch any matches?", + 4, + "Goodbye, Fadli.", + 5, + ) + when (choice) { + 1 -> { + chatPlayer(quiz, "What do you do?") + chatNpc( + angry, + "I look after everyone's valuables while they fight. Me! A man of my " + + "talents, stuck behind a counter while lesser fighters get all the glory!", + ) + chatPlayer(shocked, "Easy, tiger!") + } + 2 -> { + chatPlayer(neutral, "I'd like to access my bank, please.") + chatNpc(bored, "Sure.") + player.openBank(eventBus) + } + 3 -> { + chatPlayer(neutral, "I'd like to collect items.") + chatNpc(bored, "The Grand Exchange collection service isn't available here.") + } + 4 -> { + chatPlayer(quiz, "Do you watch any matches?") + chatNpc( + happy, + "When I can. I sell rotten fruit on the side, if you'd like to make your " + + "opinion of the fighters known.", + ) + val buy = choice2("Let me see the fruit.", true, "No thanks.", false) + if (buy) { + access.openFruitStall(npc) + } else { + chatNpc(bored, "Suit yourself.") + } + } + else -> chatPlayer(neutral, "Goodbye, Fadli.") + } + } + + private fun ProtectedAccess.openFruitStall(npc: Npc) { + shops.open(player, npc, "Pelters' Veg Stall", "inv.duel_rottenfruitshop") + } + + private suspend fun Dialogue.chris() { + chatNpc(happy, "Lost something in the Arena? Anything the guards find ends up with me.") + chatPlayer(quiz, "Have you found anything of mine?") + chatNpc(sad, "Sorry, nothing's been handed in today. Duels here are safe, so there isn't much to find.") + } + + /* Crowd */ + + private suspend fun Dialogue.spectator() { + chatNpc(happy, CROWD_LINES.random()) + } + + private companion object { + private val GUARDS = + listOf( + "npc.pvpa_guard_1", + "npc.pvpa_guard_2_model", + "npc.pvpa_guard_3_model", + "npc.pvpa_guard_4", + "npc.pvpa_guard_5", + "npc.pvpa_guard_6", + "npc.pvpa_guard_7", + "npc.pvpa_guard_8", + ) + + private val NURSES = listOf("npc.duel_nurse1", "npc.duel_nurse2", "npc.duel_nurse3") + + private val SPECTATORS = + listOf( + "npc.duel_crowdmale1", + "npc.duel_crowdmale2", + "npc.duel_crowdmale3", + "npc.duel_crowdfemale1", + "npc.duel_crowdfemale2", + "npc.duel_crowdfemale3", + ) + + private val CROWD_LINES = + listOf( + "Did you see that last fight? Over in three hits!", + "I've got a rotten tomato with someone's name on it.", + "Shh! I'm trying to watch the arena.", + "My money's on the one with the whip. It's always the one with the whip.", + "The Emir cleaned this place up nicely, didn't he?", + ) + + /** Reward point prices, keyed by obj id. */ + private val REWARD_PRICES: Map = + mapOf( + "obj.blighted_sack_surge".objId() to 10, + "obj.pvpa_imbuing_scroll".objId() to 200, + ) + + private fun String.objId(): Int = asRSCM(RSCMType.OBJ) + } +} diff --git a/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaScript.kt b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaScript.kt new file mode 100644 index 000000000..4654f6287 --- /dev/null +++ b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/EmirsArenaScript.kt @@ -0,0 +1,301 @@ +package org.rsmod.content.other.emirsarena + +import dev.openrune.types.aconverted.interf.IfButtonOp +import jakarta.inject.Inject +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.midiJingle +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onArea +import org.rsmod.api.script.onAreaExit +import org.rsmod.api.script.onIfClose +import org.rsmod.api.script.onIfModalButton +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpLoc2 +import org.rsmod.api.script.onOpLoc3 +import org.rsmod.api.script.onPlayerLogin +import org.rsmod.api.script.onPlayerLogout +import org.rsmod.api.script.onPlayerQueue +import org.rsmod.content.other.emirsarena.duel.Duel +import org.rsmod.content.other.emirsarena.duel.DuelManager +import org.rsmod.content.other.emirsarena.duel.DuelRule +import org.rsmod.content.other.emirsarena.duel.DuelRules +import org.rsmod.content.other.emirsarena.duel.DuelStage +import org.rsmod.content.other.emirsarena.ranked.ArenaRanking +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Emir's Arena: the safe player-versus-player minigame in the desert north-east of Al Kharid. + * + * What is wired up here: + * - Inside `area.emirs_arena` the attack option reads `Challenge` (handled by the arena's attack + * hook, see [EmirsArenaHooks]). Two players who challenge each other get the legacy duel options screen (`interface.pvp_arena_legacyduel_options`), then + * the confirmation screen, and are then dropped into one of the four walled combat areas for a + * "3, 2, 1, FIGHT!" countdown. Death costs no items and respawns in the hospital; forfeiting is + * done through the trapdoors against the arena walls. + * - The rule toggles write `varp.dueloptions` exactly as the client expects (see [DuelRules]), so + * the cache's own clientscripts draw the ticks, the disabled worn slots and the confirm summary. + * - The recruitment boards run a simple ranked queue: two signed-up players are matched straight + * into a ranked duel that moves `varbit.pvpa_rank_points` and pays reward points into + * `varbit.pvpa_points_currency`, which Mubariz's reward shop spends. + * - The scoreboards show the last fifty results. + * + * Everything that changes duel state lives in [DuelManager]; this script only routes events. + */ +class EmirsArenaScript @Inject constructor(private val manager: DuelManager) : PluginScript() { + override fun ScriptContext.startup() { + onArea(EmirsArena.AREA) { manager.enterArena(player) } + onAreaExit(EmirsArena.AREA) { manager.exitArena(player) } + onPlayerLogout { manager.onLogout(player) } + onPlayerLogin { manager.recoverStake(player) } + + // Duel options screen. + for ((component, rule) in DuelRule.BY_COMPONENT) { + onIfModalButton(EmirsArena.OPTIONS_PREFIX + component) { toggleRule(rule) } + } + for (slot in EmirsArena.WORN_OPTION_SLOTS) { + onIfModalButton(EmirsArena.OPTIONS_PREFIX + "duel_wornoption$slot") { toggleSlot(slot) } + } + onIfModalButton(EmirsArena.OPTIONS_PREFIX + "accept") { acceptOptions() } + onIfModalButton(EmirsArena.OPTIONS_PREFIX + "decline") { decline() } + onIfModalButton(EmirsArena.OPTIONS_PREFIX + "save") { savePreset() } + onIfModalButton(EmirsArena.OPTIONS_PREFIX + "load") { loadPreset() } + onIfModalButton(EmirsArena.OPTIONS_PREFIX + "previous") { loadLastDuel() } + onIfModalButton(EmirsArena.OPTIONS_PREFIX + "whip") { setRules(DuelRules.WHIP) } + onIfModalButton(EmirsArena.OPTIONS_PREFIX + "box") { setRules(DuelRules.BOXING) } + onIfClose(EmirsArena.OPTIONS_INTERFACE) { manager.onScreenClosed(player, DuelStage.Options) } + + // Stake screens. + onIfModalButton(EmirsArena.STAKE_SIDE_ITEMS) { offerStake(it.comsub, it.op) } + onIfModalButton(EmirsArena.STAKE_PREFIX + "your_offer") { removeStake(it.comsub, it.op) } + onIfModalButton(EmirsArena.STAKE_PREFIX + "accept") { acceptStakes() } + onIfModalButton(EmirsArena.STAKE_PREFIX + "decline") { decline() } + onIfClose(EmirsArena.STAKE_INTERFACE) { manager.onScreenClosed(player, DuelStage.Stakes) } + onIfModalButton(EmirsArena.STAKE_CONFIRM_PREFIX + "trade2accept") { acceptStakeConfirm() } + onIfModalButton(EmirsArena.STAKE_CONFIRM_PREFIX + "trade2decline") { decline() } + onIfClose(EmirsArena.STAKE_CONFIRM_INTERFACE) { + manager.onScreenClosed(player, DuelStage.StakeConfirm) + } + + // Confirmation screen. + onIfModalButton(EmirsArena.CONFIRM_PREFIX + "accept") { acceptConfirm() } + onIfModalButton(EmirsArena.CONFIRM_PREFIX + "decline") { decline() } + onIfClose(EmirsArena.CONFIRM_INTERFACE) { manager.onScreenClosed(player, DuelStage.Confirm) } + + // Arena. + onPlayerQueue(EmirsArena.START_QUEUE) { enterArenaAndCountDown() } + onPlayerQueue(EmirsArena.LEAVE_QUEUE) { leaveArena() } + onOpLoc1(EmirsArena.TRAPDOOR_LOC) { forfeit() } + + // Boards and chests. + onOpLoc1("loc.pvpa_scoreboard") { openScoreboard() } + for (board in RECRUITMENT_BOARDS) { + onOpLoc1(board) { recruitmentBoard() } + onOpLoc2(board) { mes("There are no duels to spectate right now.") } + onOpLoc3(board) { showRank() } + } + onOpLoc1("loc.pvpa_lobby_supplies_chest") { mes(EmirsArena.SUPPLIES_MESSAGE) } + onOpLoc1("loc.pvpa_stagingarea_supplies_chest") { mes(EmirsArena.SUPPLIES_MESSAGE) } + } + + /* Options screen */ + + private fun ProtectedAccess.currentDuel(stage: DuelStage): Duel? { + val duel = manager.duelOf(player) ?: return null + return duel.takeIf { it.stage == stage } + } + + private fun ProtectedAccess.toggleRule(rule: DuelRule) { + val duel = currentDuel(DuelStage.Options) ?: return + manager.changeRules(duel, player, duel.rules.toggle(rule)) + } + + private fun ProtectedAccess.toggleSlot(slot: Int) { + val duel = currentDuel(DuelStage.Options) ?: return + manager.changeRules(duel, player, duel.rules.toggleSlot(slot)) + } + + private fun ProtectedAccess.setRules(rules: DuelRules) { + val duel = currentDuel(DuelStage.Options) ?: return + manager.changeRules(duel, player, rules) + } + + private fun ProtectedAccess.savePreset() { + val duel = currentDuel(DuelStage.Options) ?: return + player.attr[EmirsArenaAttributes.SAVED_PRESET] = duel.rules.packed + mes("Your duel settings have been saved as a preset.") + } + + private fun ProtectedAccess.loadPreset() { + val saved = player.attr[EmirsArenaAttributes.SAVED_PRESET] + if (saved == null) { + mes("You haven't saved a duel preset yet.") + return + } + setRules(DuelRules(saved)) + } + + private fun ProtectedAccess.loadLastDuel() { + val last = player.attr[EmirsArenaAttributes.LAST_RULES] + if (last == null) { + mes("You haven't fought a duel yet.") + return + } + setRules(DuelRules(last)) + } + + private fun ProtectedAccess.acceptOptions() { + val duel = currentDuel(DuelStage.Options) ?: return + manager.acceptOptions(duel, player) + } + + private suspend fun ProtectedAccess.offerStake(slot: Int, op: IfButtonOp) { + val duel = currentDuel(DuelStage.Stakes) ?: return + manager.offerStake(this, duel, slot, op) + } + + private suspend fun ProtectedAccess.removeStake(slot: Int, op: IfButtonOp) { + val duel = currentDuel(DuelStage.Stakes) ?: return + manager.removeStake(this, duel, slot, op) + } + + private fun ProtectedAccess.acceptStakes() { + val duel = currentDuel(DuelStage.Stakes) ?: return + manager.acceptStakes(duel, player) + } + + private fun ProtectedAccess.acceptStakeConfirm() { + val duel = currentDuel(DuelStage.StakeConfirm) ?: return + manager.acceptStakeConfirm(duel, player) + } + + private fun ProtectedAccess.acceptConfirm() { + val duel = currentDuel(DuelStage.Confirm) ?: return + manager.acceptConfirm(duel, player) + } + + private fun ProtectedAccess.decline() { + val duel = manager.duelOf(player) ?: return + manager.decline(duel, player) + } + + /* Arena */ + + private suspend fun ProtectedAccess.enterArenaAndCountDown() { + val duel = manager.duelOf(player) ?: return + val arena = duel.arena ?: return + if (duel.stage != DuelStage.Countdown) { + return + } + val (west, east) = arena.startPositions(duel.rules.has(DuelRule.NoMovement)) + val mine = if (duel.side(player) == 0) west else east + val theirs = if (duel.side(player) == 0) east else west + + ifClose() + telejump(mine, TeleportType.Exempt) + faceSquare(theirs) + player.midiJingle(EmirsArena.DUEL_START_JINGLE) + delay(2) + for (count in COUNTDOWN_FROM downTo 1) { + if (duel.stage != DuelStage.Countdown) { + return + } + say("$count") + delay(COUNTDOWN_INTERVAL) + } + if (duel.stage != DuelStage.Countdown) { + return + } + say("FIGHT!") + manager.beginFight(duel) + } + + private fun ProtectedAccess.leaveArena() { + ifClose() + val spot = + mapFindSquareLineOfWalk(EmirsArena.LOBBY, minRadius = 0, maxRadius = EmirsArena.LOBBY_RADIUS) + telejump(spot ?: EmirsArena.LOBBY, TeleportType.Exempt) + } + + private suspend fun ProtectedAccess.forfeit() { + val duel = manager.duelOf(player) + if (duel == null || duel.stage != DuelStage.Fighting) { + mes("You can only use the trapdoor to forfeit a duel.") + return + } + if (!manager.canForfeit(duel)) { + mes("Forfeiting has been disabled for this duel.") + return + } + val leave = + choice2("Yes, forfeit the duel.", true, "No, keep fighting.", false, title = "Forfeit the duel?") + if (!leave) { + return + } + // The dialogue took a few ticks; the duel may have ended in the meantime. + if (duel.stage == DuelStage.Fighting) { + manager.forfeit(duel, player) + } + } + + /* Boards */ + + private fun ProtectedAccess.openScoreboard() { + val lines = manager.scoreboardLines + val text = + if (lines.isEmpty()) { + "No duels have been fought here yet." + } else { + lines.joinToString("
") + } + ifOpenMainModal(EmirsArena.SCOREBOARD_INTERFACE) + ifSetText(EmirsArena.SCOREBOARD_TEXT, text) + } + + private suspend fun ProtectedAccess.recruitmentBoard() { + val choices = + listOf( + "Sign up for a ranked 1v1 duel", + "Withdraw from ranked matchmaking", + "How does ranked duelling work?", + "Cancel", + ) + when (menu("Arena recruitment board", hotkeys = true, choices = choices)) { + 0 -> mes(manager.signUpForRanked(player)) + 1 -> mes(manager.withdrawFromRanked(player)) + 2 -> + mesbox( + "Sign up on a recruitment board and the arena staff will pair you with the " + + "next fighter who signs up. Ranked duels are fought to the death with " + + "everything allowed; the winner gains rank points and both fighters earn " + + "reward points to spend with Mubariz." + ) + else -> Unit + } + } + + private suspend fun ProtectedAccess.showRank() { + val rank = ArenaRanking.effectiveRank(player.vars[EmirsArena.RANK_POINTS_VARBIT]) + val tier = ArenaRanking.tier(rank) + val rewards = player.vars[EmirsArena.REWARD_POINTS_VARBIT] + val rankedWins = player.attr.getOrDefault(EmirsArenaAttributes.RANKED_WINS, 0) + val rankedLosses = player.attr.getOrDefault(EmirsArenaAttributes.RANKED_LOSSES, 0) + val legacyWins = player.attr.getOrDefault(EmirsArenaAttributes.LEGACY_WINS, 0) + val legacyLosses = player.attr.getOrDefault(EmirsArenaAttributes.LEGACY_LOSSES, 0) + val queued = if (manager.isQueuedForRanked(player)) "
You are waiting for a ranked opponent." else "" + mesbox( + "Rank: ${tier.label} ($rank rank points)
" + + "Reward points: $rewards
" + + "Ranked duels: $rankedWins won, $rankedLosses lost
" + + "Legacy duels: $legacyWins won, $legacyLosses lost$queued" + ) + } + + private companion object { + private const val COUNTDOWN_FROM = 3 + private const val COUNTDOWN_INTERVAL = 2 + + private val RECRUITMENT_BOARDS = + listOf("loc.pvpa_recruitment_board", "loc.pvpa_recruitment_board02") + } +} diff --git a/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/Duel.kt b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/Duel.kt new file mode 100644 index 000000000..4c8d200db --- /dev/null +++ b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/Duel.kt @@ -0,0 +1,67 @@ +package org.rsmod.content.other.emirsarena.duel + +import org.rsmod.game.entity.Player + +enum class DuelStage { + /** Both players are picking rules on the duel options screen. */ + Options, + /** Both players are offering items on the stake screen (legacy duels only). */ + Stakes, + /** Both players are checking the stake summary. */ + StakeConfirm, + /** Both players are looking at the confirmation screen. */ + Confirm, + /** The players stand in the arena while the "3, 2, 1, FIGHT!" countdown runs. */ + Countdown, + Fighting, + Finished, +} + +enum class DuelEnd { + Death, + Forfeit, + Logout, +} + +/** + * One duel between two players, from the moment both accepted the challenge until one of them + * leaves the arena. The same instance is attached to both players. + */ +class Duel(val challenger: Player, val opponent: Player, val ranked: Boolean) { + var rules: DuelRules = if (ranked) RANKED_RULES else DuelRules.NONE + var stage: DuelStage = DuelStage.Options + var arena: DuelArena? = null + var winner: Player? = null + + private val accepted = BooleanArray(2) + + val players: List + get() = listOf(challenger, opponent) + + fun contains(player: Player): Boolean = player === challenger || player === opponent + + fun other(player: Player): Player = if (player === challenger) opponent else challenger + + /** 0 for the challenger, 1 for the opponent; decides which side of the arena each starts on. */ + fun side(player: Player): Int = if (player === challenger) 0 else 1 + + fun hasAccepted(player: Player): Boolean = accepted[side(player)] + + /** Marks [player] as accepting the current screen and returns `true` once both have. */ + fun accept(player: Player): Boolean { + accepted[side(player)] = true + return accepted[0] && accepted[1] + } + + fun resetAcceptance() { + accepted.fill(false) + } + + val isActive: Boolean + get() = stage == DuelStage.Countdown || stage == DuelStage.Fighting + + companion object { + /** Ranked duels are fought with everything allowed and both inventories visible. */ + val RANKED_RULES: DuelRules = DuelRules.NONE.with(DuelRule.ShowInventories) + } +} diff --git a/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelArena.kt b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelArena.kt new file mode 100644 index 000000000..ac95734dd --- /dev/null +++ b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelArena.kt @@ -0,0 +1,55 @@ +package org.rsmod.content.other.emirsarena.duel + +import org.rsmod.map.CoordGrid + +/** + * One of the four walled combat areas at Emir's Arena. Bounds are the walkable interior read from + * the map's collision flags (the walls are two tiles thick on the long sides, so the interior is a + * little smaller than the wall outline). + */ +data class DuelArena( + val name: String, + val minX: Int, + val maxX: Int, + val minZ: Int, + val maxZ: Int, + val level: Int = 0, +) { + val centreX: Int + get() = (minX + maxX) / 2 + + val centreZ: Int + get() = (minZ + maxZ) / 2 + + fun contains(coords: CoordGrid): Boolean = + coords.level == level && coords.x in minX..maxX && coords.z in minZ..maxZ + + /** + * Where the two duellists start. Adjacent for "No Movement" duels so melee can connect, a few + * tiles apart otherwise. + */ + fun startPositions(noMovement: Boolean): Pair { + val gap = if (noMovement) 1 else START_GAP + val westX = centreX - gap / 2 + val eastX = westX + gap + return CoordGrid(westX, centreZ, level) to CoordGrid(eastX, centreZ, level) + } + + /** The forfeit trapdoors: a 1x3 loc against the west and east walls. */ + val trapdoors: List + get() = listOf(CoordGrid(minX, centreZ - 1, level), CoordGrid(maxX, centreZ - 1, level)) + + companion object { + private const val START_GAP = 6 + + val ALL: List = + listOf( + DuelArena("north-west", minX = 3334, maxX = 3351, minZ = 3246, maxZ = 3256), + DuelArena("north-east", minX = 3370, maxX = 3387, minZ = 3246, maxZ = 3256), + DuelArena("south-west", minX = 3334, maxX = 3351, minZ = 3208, maxZ = 3218), + DuelArena("south-east", minX = 3370, maxX = 3387, minZ = 3208, maxZ = 3218), + ) + + fun at(coords: CoordGrid): DuelArena? = ALL.firstOrNull { it.contains(coords) } + } +} diff --git a/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelManager.kt b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelManager.kt new file mode 100644 index 000000000..0eb908431 --- /dev/null +++ b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelManager.kt @@ -0,0 +1,639 @@ +package org.rsmod.content.other.emirsarena.duel + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.util.Wearpos +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.area.checker.AreaChecker +import org.rsmod.api.combat.manager.PlayerAttackManager +import org.rsmod.api.config.constants +import org.rsmod.api.mechanics.toxins.Toxin.cureAllToxins +import org.rsmod.api.player.disablePrayers +import org.rsmod.api.player.ironman.IronmanRestrictions +import org.rsmod.api.player.midiJingle +import org.rsmod.api.player.output.ChatType +import org.rsmod.api.player.output.GameMessage +import org.rsmod.api.player.output.MiscOutput +import org.rsmod.api.player.output.UpdateRun +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.player.stat.statRestoreAll +import org.rsmod.api.player.ui.ifClose +import org.rsmod.api.player.ui.ifOpenMainModal +import org.rsmod.api.player.ui.ifSetObj +import org.rsmod.api.player.ui.ifSetPosition +import org.rsmod.api.player.ui.ifSetText +import org.rsmod.api.player.vars.VarPlayerIntMapSetter +import org.rsmod.api.player.worn.WornUnequipOp +import org.rsmod.api.player.worn.WornUnequipResult +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.content.other.emirsarena.EmirsArena +import org.rsmod.content.other.emirsarena.EmirsArenaAttributes +import org.rsmod.content.other.emirsarena.ranked.ArenaRanking +import org.rsmod.events.EventBus +import org.rsmod.game.entity.Player +import org.rsmod.game.loc.LocAngle +import org.rsmod.game.loc.LocShape + +/** + * Owns every duel in progress: the challenge handshake, the two interface screens, arena + * allocation, the fight itself and the bookkeeping when it ends. The scripts and hooks are thin + * wrappers that route events here. + * + * Anything that needs protected access on a player (teleporting, the countdown chatter) is done + * through the `queue.emirs_arena_start` / `queue.emirs_arena_leave` strong queues, because a + * player looking at a duel screen has a modal open and cannot be launched into protected access + * directly. + */ +@Singleton +class DuelManager +@Inject +constructor( + private val eventBus: EventBus, + private val attackManager: PlayerAttackManager, + private val unequipOp: WornUnequipOp, + private val locRepo: LocRepository, + private val areaChecker: AreaChecker, + private val stakes: DuelStakes, +) { + private val arenasInUse = HashSet() + private val trapdoorsSpawned = HashSet() + + /** Players waiting for a ranked opponent, oldest first. */ + private val rankedQueue = ArrayDeque() + + /** Most recent duel results, newest first, for the arena scoreboards. */ + private val scoreboard = ArrayDeque() + + private val allStats: List by lazy { + ServerCacheManager.getStats().values.map { RSCM.getReverseMapping(RSCMType.STAT, it.id) } + } + + fun duelOf(player: Player): Duel? = player.attr[EmirsArenaAttributes.DUEL] + + fun inArena(player: Player): Boolean = areaChecker.inArea(EmirsArena.AREA, player.coords) + + val scoreboardLines: List + get() = scoreboard.toList() + + /* Area presence */ + + fun enterArena(player: Player) { + MiscOutput.setPlayerOp(player, EmirsArena.CHALLENGE_SLOT, "Challenge") + recoverStake(player) + } + + /** Hands back a stake left behind by a crash or restart; a no-op while a duel is pending. */ + fun recoverStake(player: Player) { + if (duelOf(player) == null) { + stakes.recover(player) + } + } + + fun exitArena(player: Player) { + MiscOutput.setPlayerOp(player, EmirsArena.CHALLENGE_SLOT, null) + player.attr.remove(EmirsArenaAttributes.CHALLENGE_TARGET) + rankedQueue.remove(player) + val duel = duelOf(player) ?: return + if (!duel.isActive) { + cancel(duel, "${player.displayName} has left the arena.") + } + } + + fun onLogout(player: Player) { + rankedQueue.remove(player) + player.attr.remove(EmirsArenaAttributes.CHALLENGE_TARGET) + val duel = duelOf(player) ?: return + when (duel.stage) { + DuelStage.Countdown, + DuelStage.Fighting -> finish(duel, winner = duel.other(player), loser = player, DuelEnd.Logout) + DuelStage.Finished -> Unit + else -> cancel(duel, "${player.displayName} has left the arena.") + } + } + + /* Challenges */ + + fun challenge(challenger: Player, target: Player) { + if (target === challenger) { + return + } + if (duelOf(challenger) != null) { + challenger.mes("You are already in a duel.") + return + } + if (duelOf(target) != null) { + challenger.mes("${target.displayName} is already in a duel.") + return + } + if (!inArena(target)) { + challenger.mes("${target.displayName} is not inside the arena.") + return + } + if (target.isAccessProtected) { + challenger.mes("${target.displayName} is busy at the moment.") + return + } + if (target.attr[EmirsArenaAttributes.CHALLENGE_TARGET] === challenger) { + target.attr.remove(EmirsArenaAttributes.CHALLENGE_TARGET) + challenger.attr.remove(EmirsArenaAttributes.CHALLENGE_TARGET) + openOptions(Duel(challenger = target, opponent = challenger, ranked = false)) + return + } + challenger.attr[EmirsArenaAttributes.CHALLENGE_TARGET] = target + challenger.mes("Sending duel request...") + // The client prints the text as-is and only uses the name to find the player when the line + // is clicked (which arrives as the attack/Challenge option). + GameMessage.requestMes( + target, + "${challenger.displayName} wishes to duel with you.", + challenger.displayName, + ChatType.ChalReqTrade, + ) + } + + /* Options screen */ + + private fun openOptions(duel: Duel) { + for (player in duel.players) { + player.attr[EmirsArenaAttributes.DUEL] = duel + resetAcceptVars(player) + syncRules(player, duel.rules) + player.ifOpenMainModal(EmirsArena.OPTIONS_INTERFACE, eventBus) + // The cache places the opponent panel at x=63, overlapping the option list; every + // other middle-column component sits at x=140, so line it up with them. + player.ifSetPosition(EmirsArena.OPTIONS_PREFIX + "opponent_detail", OPPONENT_PANEL_X, OPPONENT_PANEL_Y) + showOpponentDetails(player, duel.other(player)) + player.ifSetText(EmirsArena.OPTIONS_PREFIX + "duelstatus", "") + player.ifSetText(EmirsArena.OPTIONS_PREFIX + "acceptwarning", "") + } + } + + private fun showOpponentDetails(player: Player, opponent: Player) { + player.ifSetText(EmirsArena.OPTIONS_PREFIX + "opponent_name", opponent.displayName) + player.ifSetText( + EmirsArena.OPTIONS_PREFIX + "opponent_level", + "Combat level: ${opponent.appearance.combatLevel}", + ) + for ((component, stat) in OPPONENT_STATS) { + player.ifSetText(EmirsArena.OPTIONS_PREFIX + component, "${opponent.statBase(stat)}") + } + } + + fun changeRules(duel: Duel, changedBy: Player, rules: DuelRules) { + if (duel.stage != DuelStage.Options || !rules.isValid) { + return + } + duel.rules = rules + duel.resetAcceptance() + for (player in duel.players) { + syncRules(player, rules) + VarPlayerIntMapSetter.set(player, EmirsArena.ACCEPT_OPTIONS_VARBIT, 0) + val status = if (player === changedBy) "" else "Duel options have been changed." + player.ifSetText(EmirsArena.OPTIONS_PREFIX + "duelstatus", status) + } + } + + fun acceptOptions(duel: Duel, player: Player) { + if (duel.stage != DuelStage.Options || duel.hasAccepted(player)) { + return + } + val bothAccepted = duel.accept(player) + VarPlayerIntMapSetter.set(player, EmirsArena.ACCEPT_OPTIONS_VARBIT, 1) + player.ifSetText(EmirsArena.OPTIONS_PREFIX + "duelstatus", "Waiting for other player...") + val other = duel.other(player) + other.ifSetText(EmirsArena.OPTIONS_PREFIX + "duelstatus", "Other player has accepted.") + if (bothAccepted) { + if (duel.ranked) { + openConfirm(duel) + } else { + openStakes(duel) + } + } + } + + /* Stake screens (legacy duels only) */ + + private fun openStakes(duel: Duel) { + // Ironmen cannot stake, so their duels go straight to the rules confirmation. + if (IronmanRestrictions.blockTrade(duel.challenger, duel.opponent)) { + for (player in duel.players) { + player.mes("This duel will be fought without stakes.") + } + openConfirm(duel) + return + } + duel.stage = DuelStage.Stakes + duel.resetAcceptance() + for (player in duel.players) { + resetAcceptVars(player) + } + stakes.open(duel) + } + + suspend fun offerStake(access: org.rsmod.api.player.protect.ProtectedAccess, duel: Duel, slot: Int, op: dev.openrune.types.aconverted.interf.IfButtonOp) { + if (duel.stage != DuelStage.Stakes) { + return + } + stakes.offer(access, duel, slot, op) + } + + suspend fun removeStake(access: org.rsmod.api.player.protect.ProtectedAccess, duel: Duel, slot: Int, op: dev.openrune.types.aconverted.interf.IfButtonOp) { + if (duel.stage != DuelStage.Stakes) { + return + } + stakes.remove(access, duel, slot, op) + } + + fun acceptStakes(duel: Duel, player: Player) { + if (duel.stage != DuelStage.Stakes) { + return + } + if (stakes.accept(duel, player)) { + duel.stage = DuelStage.StakeConfirm + stakes.openConfirm(duel) + } + } + + fun acceptStakeConfirm(duel: Duel, player: Player) { + if (duel.stage != DuelStage.StakeConfirm || duel.hasAccepted(player)) { + return + } + if (duel.accept(player)) { + for (duellist in duel.players) { + stakes.close(duellist) + } + openConfirm(duel) + } + } + + /* Confirm screen */ + + fun openConfirm(duel: Duel) { + duel.stage = DuelStage.Confirm + duel.resetAcceptance() + for (player in duel.players) { + player.attr[EmirsArenaAttributes.DUEL] = duel + resetAcceptVars(player) + syncRules(player, duel.rules) + player.ifOpenMainModal(EmirsArena.CONFIRM_INTERFACE, eventBus) + player.ifSetText(EmirsArena.CONFIRM_PREFIX + "status", "") + showOpponentItems(player, duel.other(player), duel.rules) + } + } + + private fun showOpponentItems(player: Player, opponent: Player, rules: DuelRules) { + if (!rules.has(DuelRule.ShowInventories)) { + return + } + for (slot in EmirsArena.WORN_OPTION_SLOTS) { + val obj = opponent.worn[slot] ?: continue + player.ifSetObj(EmirsArena.CONFIRM_PREFIX + "otherworn$slot", obj, obj.count) + } + val backpack = + opponent.inv + .filterNotNull() + .groupBy { it.id } + .map { (id, objs) -> + val name = ServerCacheManager.getItem(id)?.name ?: "Unknown item" + val count = objs.sumOf { it.count } + if (count > 1) "$count x $name" else name + } + val text = + if (backpack.isEmpty()) { + "${opponent.displayName}'s backpack is empty." + } else { + "${opponent.displayName}'s backpack:
" + backpack.joinToString(", ") + } + player.ifSetText(EmirsArena.CONFIRM_PREFIX + "otherhidden", text) + } + + fun acceptConfirm(duel: Duel, player: Player) { + if (duel.stage != DuelStage.Confirm || duel.hasAccepted(player)) { + return + } + val bothAccepted = duel.accept(player) + VarPlayerIntMapSetter.set(player, EmirsArena.ACCEPT_CONFIRM_VARBIT, 1) + player.ifSetText(EmirsArena.CONFIRM_PREFIX + "status", "Waiting for other player...") + duel.other(player).ifSetText(EmirsArena.CONFIRM_PREFIX + "status", "Other player has accepted.") + if (bothAccepted) { + start(duel) + } + } + + /** + * Called from the interface close events: a player closing a duel screen (escape key, another + * interface) declines, unless the duel has already moved on to the next screen. + */ + fun onScreenClosed(player: Player, stage: DuelStage) { + val duel = duelOf(player) ?: return + if (duel.stage == stage) { + decline(duel, player) + } + } + + fun decline(duel: Duel, player: Player) { + if (!duel.isActive && duel.stage != DuelStage.Finished) { + cancel(duel, "${player.displayName} has declined the duel.") + } + } + + private fun cancel(duel: Duel, reason: String) { + if (duel.stage == DuelStage.Finished) { + return + } + duel.stage = DuelStage.Finished + releaseArena(duel) + for (player in duel.players) { + detach(player) + resetAcceptVars(player) + player.ifClose(eventBus) + stakes.close(player) + stakes.refund(player) + player.mes(reason) + } + } + + /* Starting the fight */ + + private fun start(duel: Duel) { + val arena = DuelArena.ALL.firstOrNull { it !in arenasInUse } + if (arena == null) { + cancel(duel, "All of the arenas are in use right now. Please try again shortly.") + return + } + for (player in duel.players) { + if (!removeDisallowedWorn(player, duel.rules)) { + cancel( + duel, + "${player.displayName} does not have enough inventory space to remove the " + + "items this duel disallows.", + ) + return + } + } + duel.arena = arena + duel.stage = DuelStage.Countdown + arenasInUse += arena + spawnTrapdoors(arena) + for (player in duel.players) { + player.attr[EmirsArenaAttributes.LAST_RULES] = duel.rules.packed + resetAcceptVars(player) + if (duel.rules.has(DuelRule.NoPrayer)) { + player.disablePrayers() + } + if (duel.rules.has(DuelRule.NoDrinks)) { + player.statRestoreAll(BOOSTABLE_COMBAT_STATS) + } + player.strongQueue(EmirsArena.START_QUEUE, 1) + } + } + + /** Moves worn items out of the slots the rules disable. False when a backpack is too full. */ + private fun removeDisallowedWorn(player: Player, rules: DuelRules): Boolean { + for (wearpos in rules.disabledSlots) { + if (player.worn[wearpos.slot] == null) { + continue + } + val result = unequipOp.unequip(player, wearpos.slot, player.worn, player.inv) + if (result is WornUnequipResult.Fail.NotEnoughInvSpace) { + return false + } + } + return true + } + + private fun spawnTrapdoors(arena: DuelArena) { + if (!trapdoorsSpawned.add(arena)) { + return + } + for (coords in arena.trapdoors) { + locRepo.add( + coords = coords, + internal = EmirsArena.TRAPDOOR_LOC, + duration = Int.MAX_VALUE, + angle = LocAngle.West, + shape = LocShape.CentrepieceStraight, + ) + } + } + + /** Both countdown coroutines call this; the first one flips the duel into the fight. */ + fun beginFight(duel: Duel) { + if (duel.stage != DuelStage.Countdown) { + return + } + duel.stage = DuelStage.Fighting + for (player in duel.players) { + MiscOutput.setPlayerOp(player, EmirsArena.FIGHT_SLOT, "Fight", priority = true) + } + } + + /* Ending the fight */ + + fun canForfeit(duel: Duel): Boolean = !duel.rules.has(DuelRule.NoForfeit) + + fun forfeit(duel: Duel, player: Player) { + if (duel.stage != DuelStage.Fighting) { + return + } + finish(duel, winner = duel.other(player), loser = player, DuelEnd.Forfeit) + } + + /** + * Runs from the loser's death sequence, after they have been moved to the hospital and had + * their stats restored; settles the duel for both players. + */ + fun finishAfterDeath(loser: Player) { + val duel = duelOf(loser) ?: return + if (!duel.isActive) { + return + } + finish(duel, winner = duel.other(loser), loser = loser, DuelEnd.Death) + } + + private fun finish(duel: Duel, winner: Player, loser: Player, end: DuelEnd) { + if (duel.stage == DuelStage.Finished) { + return + } + duel.stage = DuelStage.Finished + duel.winner = winner + releaseArena(duel) + + for (player in duel.players) { + detach(player) + val op = if (inArena(player)) "Challenge" else null + MiscOutput.setPlayerOp(player, EmirsArena.FIGHT_SLOT, op) + } + + // The loser is either mid-death (already being moved and restored by the death sequence) + // or logging out; only a forfeiting loser still needs walking out of the arena. + restore(winner) + winner.midiJingle(EmirsArena.DUEL_WIN_JINGLE) + winner.strongQueue(EmirsArena.LEAVE_QUEUE, 1) + if (end == DuelEnd.Forfeit) { + restore(loser) + loser.strongQueue(EmirsArena.LEAVE_QUEUE, 1) + } + + when (end) { + DuelEnd.Death -> { + winner.mes("Well done! You have defeated ${loser.displayName}!") + loser.mes("You have been defeated by ${winner.displayName}.") + } + DuelEnd.Forfeit -> { + winner.mes("${loser.displayName} has forfeited the duel. You win!") + loser.mes("You have forfeited the duel.") + } + DuelEnd.Logout -> { + winner.mes("${loser.displayName} has left the arena. You win by forfeit!") + } + } + + awardStakes(duel, winner, loser) + record(duel, winner, loser) + } + + private fun awardStakes(duel: Duel, winner: Player, loser: Player) { + if (duel.ranked) { + return + } + val staked = stakes.hasStake(winner) || stakes.hasStake(loser) + stakes.award(winner, loser) + if (staked) { + winner.mes("You receive the stakes of the duel.") + loser.mes("You have lost your stake.") + } + } + + private fun releaseArena(duel: Duel) { + duel.arena?.let { arenasInUse -= it } + } + + private fun detach(player: Player) { + player.attr.remove(EmirsArenaAttributes.DUEL) + } + + private fun restore(player: Player) { + attackManager.stopCombat(player) + player.statRestoreAll(allStats) + player.cureAllToxins() + player.runEnergy = constants.run_max_energy + UpdateRun.energy(player, player.runEnergy) + VarPlayerIntMapSetter.set(player, "varp.sa_energy", FULL_SPECIAL_ENERGY) + } + + /* Records and points */ + + private fun record(duel: Duel, winner: Player, loser: Player) { + val kind = if (duel.ranked) "Ranked duel" else "Legacy duel" + scoreboard.addFirst("${winner.displayName} defeated ${loser.displayName} ($kind)") + while (scoreboard.size > EmirsArena.SCOREBOARD_SIZE) { + scoreboard.removeLast() + } + + if (!duel.ranked) { + winner.attr.increment(EmirsArenaAttributes.LEGACY_WINS) + loser.attr.increment(EmirsArenaAttributes.LEGACY_LOSSES) + return + } + + winner.attr.increment(EmirsArenaAttributes.RANKED_WINS) + loser.attr.increment(EmirsArenaAttributes.RANKED_LOSSES) + + val winnerRank = ArenaRanking.effectiveRank(winner.vars[EmirsArena.RANK_POINTS_VARBIT]) + val loserRank = ArenaRanking.effectiveRank(loser.vars[EmirsArena.RANK_POINTS_VARBIT]) + val stake = ArenaRanking.rankStake(winnerRank, loserRank) + + val streak = winner.attr.getOrDefault(EmirsArenaAttributes.WIN_STREAK, 0) + val winnerReward = ArenaRanking.winRewardPoints(streak) + winner.attr[EmirsArenaAttributes.WIN_STREAK] = streak + 1 + loser.attr[EmirsArenaAttributes.WIN_STREAK] = 0 + + awardPoints(winner, ArenaRanking.applyWin(winnerRank, stake), winnerReward) + awardPoints(loser, ArenaRanking.applyLoss(loserRank, stake), ArenaRanking.LOSS_REWARD_POINTS) + + winner.mes("You gain $stake rank points and $winnerReward reward points.") + loser.mes("You lose $stake rank points and gain ${ArenaRanking.LOSS_REWARD_POINTS} reward points.") + } + + private fun awardPoints(player: Player, rankPoints: Int, rewardPoints: Int) { + VarPlayerIntMapSetter.set(player, EmirsArena.RANK_POINTS_VARBIT, rankPoints) + val rewards = ArenaRanking.addRewardPoints(player.vars[EmirsArena.REWARD_POINTS_VARBIT], rewardPoints) + VarPlayerIntMapSetter.set(player, EmirsArena.REWARD_POINTS_VARBIT, rewards) + } + + /* Ranked matchmaking */ + + fun isQueuedForRanked(player: Player): Boolean = player in rankedQueue + + /** Returns a message for the player, and pairs them up straight away when someone is waiting. */ + fun signUpForRanked(player: Player): String { + if (duelOf(player) != null) { + return "You can't sign up while you are in a duel." + } + if (player in rankedQueue) { + return "You are already signed up for a ranked duel." + } + val waiting = rankedQueue.firstOrNull { it !== player && duelOf(it) == null && inArena(it) } + if (waiting == null) { + rankedQueue.addLast(player) + return "You have signed up for a ranked duel. You'll be matched as soon as another " + + "fighter signs up." + } + rankedQueue.remove(waiting) + val duel = Duel(challenger = waiting, opponent = player, ranked = true) + for (duellist in duel.players) { + duellist.mes("You have been matched with ${duel.other(duellist).displayName} for a ranked duel.") + } + openConfirm(duel) + return "A ranked opponent was found!" + } + + fun withdrawFromRanked(player: Player): String = + if (rankedQueue.remove(player)) { + "You are no longer waiting for a ranked duel." + } else { + "You are not signed up for a ranked duel." + } + + /* Helpers */ + + private fun syncRules(player: Player, rules: DuelRules) { + VarPlayerIntMapSetter.set(player, EmirsArena.RULES_VARP, rules.packed) + } + + private fun resetAcceptVars(player: Player) { + VarPlayerIntMapSetter.set(player, EmirsArena.ACCEPT_OPTIONS_VARBIT, 0) + VarPlayerIntMapSetter.set(player, EmirsArena.ACCEPT_CONFIRM_VARBIT, 0) + } + + private fun org.rsmod.api.attr.AttributeMap.increment(key: org.rsmod.api.attr.AttributeKey) { + this[key] = getOrDefault(key, 0) + 1 + } + + private companion object { + private const val FULL_SPECIAL_ENERGY = 1000 + private const val OPPONENT_PANEL_X = 140 + private const val OPPONENT_PANEL_Y = 2 + + private val OPPONENT_STATS = + listOf( + "attack_level" to "stat.attack", + "strength_level" to "stat.strength", + "defence_level" to "stat.defence", + "hitpoints_level" to "stat.hitpoints", + "prayer_level" to "stat.prayer", + "ranged_level" to "stat.ranged", + "magic_level" to "stat.magic", + ) + + /** Stats a potion can boost; "No Drinks" resets them when the fight starts. */ + private val BOOSTABLE_COMBAT_STATS = + listOf("stat.attack", "stat.strength", "stat.defence", "stat.ranged", "stat.magic") + + @Suppress("unused") private val WEAPON_SLOT = Wearpos.RightHand + } +} diff --git a/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelRules.kt b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelRules.kt new file mode 100644 index 000000000..ca7ba8ae0 --- /dev/null +++ b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelRules.kt @@ -0,0 +1,134 @@ +package org.rsmod.content.other.emirsarena.duel + +import dev.openrune.util.Wearpos + +/** + * The rule set of a legacy duel, packed exactly the way the client's `varp.dueloptions` (varp 286) + * stores it so the same value can be written to both players and read back from the interface. + * + * Bit numbers come from clientscript 6169 (the option buttons of + * `interface.pvp_arena_legacyduel_options`), and bits 14..27 are `varbit.duelwornoptions0..13`: + * one per [Wearpos] slot, set when items may not be worn in that slot. + */ +@JvmInline +value class DuelRules(val packed: Int) { + fun has(rule: DuelRule): Boolean = (packed shr rule.bit) and 1 == 1 + + fun toggle(rule: DuelRule): DuelRules = DuelRules(packed xor (1 shl rule.bit)) + + fun with(rule: DuelRule): DuelRules = DuelRules(packed or (1 shl rule.bit)) + + fun isSlotDisabled(wearpos: Wearpos): Boolean = isSlotDisabled(wearpos.slot) + + fun isSlotDisabled(slot: Int): Boolean = (packed shr (WORN_BIT_OFFSET + slot)) and 1 == 1 + + fun toggleSlot(slot: Int): DuelRules = DuelRules(packed xor (1 shl (WORN_BIT_OFFSET + slot))) + + fun withSlotDisabled(wearpos: Wearpos): DuelRules = + DuelRules(packed or (1 shl (WORN_BIT_OFFSET + wearpos.slot))) + + val enabledRules: List + get() = DuelRule.entries.filter { has(it) } + + val disabledSlots: List + get() = Wearpos.entries.filter { !it.isClientOnly && isSlotDisabled(it) } + + /** True when the packed value only uses bits this interface understands. */ + val isValid: Boolean + get() = packed and VALID_MASK.inv() == 0 + + companion object { + const val WORN_BIT_OFFSET: Int = 14 + + val NONE: DuelRules = DuelRules(0) + + private val VALID_MASK: Int = + DuelRule.entries.fold(0) { acc, rule -> acc or (1 shl rule.bit) } or + Wearpos.entries.fold(0) { acc, pos -> acc or (1 shl (WORN_BIT_OFFSET + pos.slot)) } + + /** Bits the "Whip" preset button ticks: melee only, no helping items, weapon slot only. */ + val WHIP: DuelRules = + listOf( + DuelRule.NoRanged, + DuelRule.NoMagic, + DuelRule.NoSpecialAttacks, + DuelRule.NoPrayer, + DuelRule.NoDrinks, + DuelRule.NoFood, + DuelRule.NoWeaponSwitch, + ) + .fold(NONE) { acc, rule -> acc.with(rule) } + .let { rules -> + Wearpos.entries + .filter { !it.isClientOnly && it != Wearpos.RightHand } + .fold(rules) { acc, pos -> acc.withSlotDisabled(pos) } + } + + /** Bits the "Boxing" preset button ticks: bare fists, no helping items, nothing worn. */ + val BOXING: DuelRules = + listOf( + DuelRule.NoRanged, + DuelRule.NoMagic, + DuelRule.NoSpecialAttacks, + DuelRule.NoPrayer, + DuelRule.NoDrinks, + DuelRule.NoFood, + ) + .fold(NONE) { acc, rule -> acc.with(rule) } + .let { rules -> + Wearpos.entries + .filter { !it.isClientOnly } + .fold(rules) { acc, pos -> acc.withSlotDisabled(pos) } + } + } +} + +enum class DuelRule(val bit: Int, val label: String, val description: String) { + NoForfeit(0, "No Forfeit", "Neither player is allowed to forfeit the duel."), + NoMovement( + 1, + "No Movement", + "Players stand next to each other and aren't allowed to move or use hold spells.", + ), + NoWeaponSwitch( + 2, + "No Weapon Switch", + "Neither player is allowed to swap their weapons during the duel.", + ), + ShowInventories( + 3, + "Show Inventories", + "Show your opponent your worn and back pack inventory, and view theirs.", + ), + NoRanged(4, "No Ranged", "Neither player is allowed to use ranged attacks."), + NoMelee(5, "No Melee", "Neither player is allowed to use melee attacks."), + NoMagic(6, "No Magic", "Neither player is allowed to use magic attacks."), + NoDrinks(7, "No Drinks", "Neither player is allowed to use drinks."), + NoFood(8, "No Food", "Neither player is allowed to use food."), + NoPrayer(9, "No Prayer", "Neither player is allowed to use prayer."), + FunWeapons( + 12, + "Fun Weapons", + "Both players must use a 'fun weapon', such as flowers or a rubber chicken.", + ), + NoSpecialAttacks(13, "No Special Attacks", "Neither player is allowed to use special attacks."); + + companion object { + /** The option buttons of the duel options interface, keyed by component suffix. */ + val BY_COMPONENT: Map = + mapOf( + "option_ranged" to NoRanged, + "option_melee" to NoMelee, + "option_magic" to NoMagic, + "option_specialattacks" to NoSpecialAttacks, + "option_funweapons" to FunWeapons, + "option_forfeit" to NoForfeit, + "option_prayer" to NoPrayer, + "option_potions" to NoDrinks, + "option_food" to NoFood, + "option_movement" to NoMovement, + "option_noswitch" to NoWeaponSwitch, + "option_showinv" to ShowInventories, + ) + } +} diff --git a/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelStakes.kt b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelStakes.kt new file mode 100644 index 000000000..caec3df04 --- /dev/null +++ b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/duel/DuelStakes.kt @@ -0,0 +1,313 @@ +package org.rsmod.content.other.emirsarena.duel + +import dev.openrune.ServerCacheManager +import dev.openrune.definition.type.widget.IfEvent +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.aconverted.interf.IfButtonOp +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.invtx.invAddOrDrop +import org.rsmod.api.invtx.invTransfer +import org.rsmod.api.player.output.UpdateInventory +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.protect.ProtectedAccess +import dev.openrune.rscm.RSCM.asRSCM +import org.rsmod.api.player.ui.ifCloseSub +import org.rsmod.api.player.ui.ifOpenMainModal +import org.rsmod.api.player.ui.ifOpenMainSidePair +import org.rsmod.api.player.ui.ifSetEvents +import org.rsmod.api.player.ui.ifSetText +import org.rsmod.api.player.vars.VarPlayerIntMapSetter +import org.rsmod.api.repo.obj.ObjRepository +import org.rsmod.content.other.emirsarena.EmirsArena +import org.rsmod.events.EventBus +import org.rsmod.game.entity.Player +import org.rsmod.game.inv.Inventory + +/** + * The stake step of a legacy duel, between the rules screen and the confirmation screen. + * + * Each player's stake lives in `inv.emirs_arena_stake`, a persisted inventory, from the moment + * it is offered until the duel ends, so a crash or restart mid-duel cannot lose it: whatever is + * still in there at login is handed back (see [recover]). The trade screens are hard-wired to + * `inv.tradeoffer`, so the holding inventory is transmitted to the client under that id, and to + * the opponent as a mirrored copy. On any outcome the manager either hands both stakes to the + * winner or gives everything back. + */ +@Singleton +class DuelStakes +@Inject +constructor(private val eventBus: EventBus, private val objRepo: ObjRepository) { + fun stakeInv(player: Player): Inventory = player.invMap.getOrPut(EmirsArena.STAKE_INV) + + fun hasStake(player: Player): Boolean = stakeInv(player).isNotEmpty() + + private val displayInvId: Int by lazy { EmirsArena.STAKE_DISPLAY_INV.asRSCM(RSCMType.INV) } + + private fun showOwn(player: Player) { + UpdateInventory.updateInvFullAs(player, stakeInv(player), displayInvId) + } + + private fun showOther(viewer: Player, owner: Player) { + UpdateInventory.updateInvFullMirrorAs(viewer, stakeInv(owner), displayInvId) + } + + /* Stake screen */ + + fun open(duel: Duel) { + for (player in duel.players) { + val other = duel.other(player) + val stake = stakeInv(player) + player.ifOpenMainSidePair( + EmirsArena.STAKE_INTERFACE, + EmirsArena.STAKE_SIDE_INTERFACE, + -1, + -2, + eventBus, + ) + player.ifSetEvents(EmirsArena.STAKE_SIDE_ITEMS, 0 until player.inv.size, *ITEM_OPS) + player.ifSetEvents(EmirsArena.STAKE_PREFIX + "your_offer", 0 until stake.size, *ITEM_OPS) + showOwn(player) + showOther(player, other) + clearModifiedFlags(player) + player.ifSetText(EmirsArena.STAKE_PREFIX + "title", "Stake for your duel with ${other.displayName}") + player.ifSetText(EmirsArena.STAKE_PREFIX + "your_offer_header", "Your stake:") + player.ifSetText(EmirsArena.STAKE_PREFIX + "other_offer_header", "${other.displayName}'s stake:") + player.ifSetText(EmirsArena.STAKE_PREFIX + "status", "") + showFreeSpace(player, other) + } + } + + /** The black box in the middle column: how much room the opponent has for what you stake. */ + private fun showFreeSpace(viewer: Player, other: Player) { + viewer.ifSetText( + EmirsArena.STAKE_PREFIX + "free_space_text", + "${other.displayName} has ${other.inv.freeSpace()} free
inventory slots.", + ) + } + + /** Handles an "Offer" op on the backpack panel. */ + suspend fun offer(access: ProtectedAccess, duel: Duel, slot: Int, op: IfButtonOp) { + val player = access.player + val obj = player.inv[slot] ?: return + if (op == IfButtonOp.Op10) { + access.mes(ServerCacheManager.getItem(obj.id)?.examine ?: "") + return + } + val type = ServerCacheManager.getItem(obj.id) + if (type == null || !type.tradeable) { + access.mes("That item can't be staked.") + return + } + val requested = + when (op) { + IfButtonOp.Op1 -> preferredQuantity(player) + IfButtonOp.Op2 -> 1 + IfButtonOp.Op3 -> 5 + IfButtonOp.Op4 -> QUANTITY_X + IfButtonOp.Op5 -> QUANTITY_ALL + else -> return + } + val count = + when (requested) { + QUANTITY_X -> access.countDialog("Enter amount:") + QUANTITY_ALL -> Int.MAX_VALUE + else -> requested + } + if (count <= 0 || duel.stage != DuelStage.Stakes) { + return + } + val moved = moveMatching(player, from = player.inv, into = stakeInv(player), objId = obj.id, firstSlot = slot, count = count) + if (moved == 0) { + access.mes("Your stake is full.") + return + } + onChanged(duel, player, removed = false) + } + + /** Handles a "Remove" op on the player's own offer. */ + suspend fun remove(access: ProtectedAccess, duel: Duel, slot: Int, op: IfButtonOp) { + val player = access.player + val stake = stakeInv(player) + val obj = stake[slot] ?: return + if (op == IfButtonOp.Op10) { + access.mes(ServerCacheManager.getItem(obj.id)?.examine ?: "") + return + } + val count = + when (op) { + IfButtonOp.Op1 -> 1 + IfButtonOp.Op2 -> 5 + IfButtonOp.Op3 -> 10 + IfButtonOp.Op4 -> Int.MAX_VALUE + IfButtonOp.Op5 -> access.countDialog("Enter amount:") + else -> return + } + if (count <= 0 || duel.stage != DuelStage.Stakes) { + return + } + val moved = moveMatching(player, from = stake, into = player.inv, objId = obj.id, firstSlot = slot, count = count) + if (moved == 0) { + access.mes("You don't have enough inventory space to take that back.") + return + } + onChanged(duel, player, removed = true) + } + + private fun onChanged(duel: Duel, changedBy: Player, removed: Boolean) { + duel.resetAcceptance() + val other = duel.other(changedBy) + showOwn(changedBy) + showOther(other, changedBy) + if (removed) { + VarPlayerIntMapSetter.set(changedBy, EmirsArena.STAKE_MODIFIED_MINE_VARBIT, 1) + VarPlayerIntMapSetter.set(other, EmirsArena.STAKE_MODIFIED_OTHER_VARBIT, 1) + } + changedBy.ifSetText(EmirsArena.STAKE_PREFIX + "status", "") + other.ifSetText(EmirsArena.STAKE_PREFIX + "status", "${changedBy.displayName} has changed the stake.") + showFreeSpace(other, changedBy) + } + + /** Returns true once both players have accepted the stake screen. */ + fun accept(duel: Duel, player: Player): Boolean { + if (duel.hasAccepted(player)) { + return false + } + val both = duel.accept(player) + player.ifSetText(EmirsArena.STAKE_PREFIX + "status", "Waiting for other player...") + duel.other(player).ifSetText(EmirsArena.STAKE_PREFIX + "status", "Other player has accepted.") + return both + } + + /* Confirmation */ + + fun openConfirm(duel: Duel) { + duel.resetAcceptance() + for (player in duel.players) { + val other = duel.other(player) + clearModifiedFlags(player) + showOwn(player) + showOther(player, other) + player.ifCloseSub(EmirsArena.STAKE_SIDE_INTERFACE, eventBus) + player.ifOpenMainModal(EmirsArena.STAKE_CONFIRM_INTERFACE, eventBus) + player.ifSetText(EmirsArena.STAKE_CONFIRM_PREFIX + "tradeopponent", "Staking against: ${other.displayName}") + player.ifSetText(EmirsArena.STAKE_CONFIRM_PREFIX + "you_will_give", "You are staking:") + player.ifSetText(EmirsArena.STAKE_CONFIRM_PREFIX + "you_will_receive", "${other.displayName} is staking:") + } + } + + /* Leaving the stake screens */ + + fun close(player: Player) { + UpdateInventory.updateInvStopTransmit(player, displayInvId) + clearModifiedFlags(player) + } + + /** + * Returns whatever an interrupted duel left in the holding inventory. Only the backpack is + * used (nothing is dropped; the player may be nowhere near the arena); anything that does not + * fit stays put and is retried at the next login or when the player walks into the arena. + */ + fun recover(player: Player) { + val stake = stakeInv(player) + if (stake.isEmpty()) { + return + } + var leftover = false + for (slot in stake.indices) { + val obj = stake[slot] ?: continue + val result = player.invTransfer(from = stake, fromSlot = slot, count = obj.count, into = player.inv) + if (result.failure) { + leftover = true + } + } + if (leftover) { + player.mes( + "Part of your stake from an interrupted duel is still being held for you. Free " + + "up some backpack space to collect the rest." + ) + } else { + player.mes("Your stake from an interrupted duel has been returned to you.") + } + } + + /** Gives [player] their own stake back, dropping anything the backpack cannot hold. */ + fun refund(player: Player) { + moveAllOut(stakeInv(player), player) + } + + /** Hands both stakes to [winner]. */ + fun award(winner: Player, loser: Player) { + moveAllOut(stakeInv(loser), winner) + moveAllOut(stakeInv(winner), winner) + } + + private fun moveAllOut(stake: Inventory, receiver: Player) { + for (slot in stake.indices) { + val obj = stake[slot] ?: continue + val name = + try { + RSCM.getReverseMapping(RSCMType.OBJ, obj.id) + } catch (e: Throwable) { + continue + } + stake.objs[slot] = null + stake.modifiedSlots.set(slot) + receiver.invAddOrDrop(objRepo, name, obj.count) + } + } + + /* Helpers */ + + /** + * Moves up to [count] of [objId] from [from] into [into], starting at [firstSlot]; stackables + * move in one go, unstackables one slot at a time. Returns how many objs moved. + */ + private fun moveMatching( + player: Player, + from: Inventory, + into: Inventory, + objId: Int, + firstSlot: Int, + count: Int, + ): Int { + var remaining = count + var moved = 0 + val slots = listOf(firstSlot) + from.indices.filter { it != firstSlot } + for (slot in slots) { + if (remaining <= 0) break + val obj = from[slot] ?: continue + if (obj.id != objId) continue + val take = minOf(remaining, obj.count) + val result = player.invTransfer(from = from, fromSlot = slot, count = take, into = into) + if (result.failure) { + break + } + remaining -= take + moved += take + } + return moved + } + + private fun preferredQuantity(player: Player): Int = + when (player.vars[EmirsArena.OFFER_QUANTITY_VARBIT]) { + 1 -> 5 + 2 -> QUANTITY_X + 3 -> QUANTITY_ALL + else -> 1 + } + + private fun clearModifiedFlags(player: Player) { + VarPlayerIntMapSetter.set(player, EmirsArena.STAKE_MODIFIED_MINE_VARBIT, 0) + VarPlayerIntMapSetter.set(player, EmirsArena.STAKE_MODIFIED_OTHER_VARBIT, 0) + } + + private companion object { + private const val QUANTITY_X = -1 + private const val QUANTITY_ALL = -2 + + private val ITEM_OPS = + arrayOf(IfEvent.Op1, IfEvent.Op2, IfEvent.Op3, IfEvent.Op4, IfEvent.Op5, IfEvent.Op10) + } +} diff --git a/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/ranked/ArenaRanking.kt b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/ranked/ArenaRanking.kt new file mode 100644 index 000000000..fb847dadf --- /dev/null +++ b/content/other/emirs-arena/src/main/kotlin/org/rsmod/content/other/emirsarena/ranked/ArenaRanking.kt @@ -0,0 +1,78 @@ +package org.rsmod.content.other.emirsarena.ranked + +/** + * Point bookkeeping for ranked duels. + * + * Reward points follow the official numbers: 16 for a win, 12 for a loss, and a win-streak bonus of + * one extra point per consecutive win up to ten (26 per win at most). Rank points start at 2,500, + * cap at 15,000 and move by a fixed stake that is nudged towards the underdog, so beating a much + * higher rank pays more than beating a much lower one. + */ +object ArenaRanking { + const val STARTING_RANK_POINTS: Int = 2500 + const val MAX_POINTS: Int = 15_000 + + const val WIN_REWARD_POINTS: Int = 16 + const val LOSS_REWARD_POINTS: Int = 12 + const val MAX_STREAK_BONUS: Int = 10 + + private const val BASE_STAKE = 25 + private const val STAKE_SWING = 15 + private const val SWING_RANGE = 1000 + + /** Reward points for a win given the number of wins already in the streak before this one. */ + fun winRewardPoints(previousStreak: Int): Int = + WIN_REWARD_POINTS + previousStreak.coerceIn(0, MAX_STREAK_BONUS) + + fun addRewardPoints(current: Int, gained: Int): Int = (current + gained).coerceIn(0, MAX_POINTS) + + /** Rank points the winner gains; the loser loses the same amount (floored at zero). */ + fun rankStake(winnerRank: Int, loserRank: Int): Int { + val diff = (loserRank - winnerRank).coerceIn(-SWING_RANGE, SWING_RANGE) + return BASE_STAKE + diff * STAKE_SWING / SWING_RANGE + } + + fun applyWin(rank: Int, stake: Int): Int = (rank + stake).coerceIn(0, MAX_POINTS) + + fun applyLoss(rank: Int, stake: Int): Int = (rank - stake).coerceIn(0, MAX_POINTS) + + /** A stored rank of zero means the player has never fought ranked; they start at 2,500. */ + fun effectiveRank(stored: Int): Int = if (stored <= 0) STARTING_RANK_POINTS else stored + + fun tier(rankPoints: Int): RankTier = RankTier.of(rankPoints) +} + +/** + * The rank ladder, Bronze I at the bottom up to Dragon Grandmaster at 9,000 points. Each metal has + * three numbered steps of 250 points; the dragon ranks above are named steps. + */ +enum class RankTier(val label: String, val minPoints: Int) { + BronzeI("Bronze I", 0), + BronzeII("Bronze II", 250), + BronzeIII("Bronze III", 500), + IronI("Iron I", 750), + IronII("Iron II", 1000), + IronIII("Iron III", 1250), + SteelI("Steel I", 1500), + SteelII("Steel II", 1750), + SteelIII("Steel III", 2000), + MithrilI("Mithril I", 2250), + MithrilII("Mithril II", 2500), + MithrilIII("Mithril III", 2750), + AdamantI("Adamant I", 3000), + AdamantII("Adamant II", 3250), + AdamantIII("Adamant III", 3500), + RuneI("Rune I", 3750), + RuneII("Rune II", 4250), + RuneIII("Rune III", 4750), + DragonI("Dragon I", 5250), + DragonII("Dragon II", 5750), + DragonIII("Dragon III", 6250), + DragonElite("Dragon Elite", 6750), + DragonMaster("Dragon Master", 7750), + DragonGrandmaster("Dragon Grandmaster", 9000); + + companion object { + fun of(rankPoints: Int): RankTier = entries.last { rankPoints >= it.minPoints } + } +} diff --git a/content/other/emirs-arena/src/main/resources/gamevals.toml b/content/other/emirs-arena/src/main/resources/gamevals.toml new file mode 100644 index 000000000..e38ecc723 --- /dev/null +++ b/content/other/emirs-arena/src/main/resources/gamevals.toml @@ -0,0 +1,3 @@ +[gamevals.inv] +# Persisted holding inventory for duel stakes (see .data/raw-cache/server/inv.toml). +emirs_arena_stake = 1101 diff --git a/content/other/emirs-arena/src/test/kotlin/org/rsmod/content/other/emirsarena/duel/DuelArenaTest.kt b/content/other/emirs-arena/src/test/kotlin/org/rsmod/content/other/emirsarena/duel/DuelArenaTest.kt new file mode 100644 index 000000000..1445da84b --- /dev/null +++ b/content/other/emirs-arena/src/test/kotlin/org/rsmod/content/other/emirsarena/duel/DuelArenaTest.kt @@ -0,0 +1,62 @@ +package org.rsmod.content.other.emirsarena.duel + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.rsmod.map.CoordGrid + +class DuelArenaTest { + @Test + fun `the four arenas do not overlap and sit inside the arena complex`() { + val arenas = DuelArena.ALL + assertEquals(4, arenas.size) + for (a in arenas) { + for (b in arenas) { + if (a === b) continue + val overlapX = a.minX <= b.maxX && b.minX <= a.maxX + val overlapZ = a.minZ <= b.maxZ && b.minZ <= a.maxZ + assertTrue(!(overlapX && overlapZ), "${a.name} overlaps ${b.name}") + } + assertTrue(a.minX >= 3308 && a.maxX <= 3392, a.name) + assertTrue(a.minZ >= 3200 && a.maxZ <= 3292, a.name) + } + } + + @Test + fun `start positions face each other inside the arena`() { + for (arena in DuelArena.ALL) { + val (west, east) = arena.startPositions(noMovement = false) + assertTrue(arena.contains(west) && arena.contains(east), arena.name) + assertEquals(west.z, east.z) + assertEquals(6, east.x - west.x) + + val (adjacentWest, adjacentEast) = arena.startPositions(noMovement = true) + assertEquals(1, adjacentEast.x - adjacentWest.x) + assertTrue(arena.contains(adjacentWest) && arena.contains(adjacentEast), arena.name) + } + } + + @Test + fun `trapdoors hug the west and east walls`() { + for (arena in DuelArena.ALL) { + val (west, east) = arena.trapdoors + assertEquals(arena.minX, west.x) + assertEquals(arena.maxX, east.x) + // A 1x3 loc spans three tiles north of its origin; all must be inside the arena. + for (trapdoor in arena.trapdoors) { + for (dz in 0 until 3) { + assertTrue(arena.contains(CoordGrid(trapdoor.x, trapdoor.z + dz, arena.level)), arena.name) + } + } + } + } + + @Test + fun `lookup by coordinate`() { + assertEquals("north-west", DuelArena.at(CoordGrid(3340, 3250, 0))?.name) + assertEquals("south-east", DuelArena.at(CoordGrid(3380, 3210, 0))?.name) + assertNull(DuelArena.at(CoordGrid(3360, 3230, 0))) + assertNull(DuelArena.at(CoordGrid(3340, 3250, 1))) + } +} diff --git a/content/other/emirs-arena/src/test/kotlin/org/rsmod/content/other/emirsarena/duel/DuelRulesTest.kt b/content/other/emirs-arena/src/test/kotlin/org/rsmod/content/other/emirsarena/duel/DuelRulesTest.kt new file mode 100644 index 000000000..64b3259fd --- /dev/null +++ b/content/other/emirs-arena/src/test/kotlin/org/rsmod/content/other/emirsarena/duel/DuelRulesTest.kt @@ -0,0 +1,80 @@ +package org.rsmod.content.other.emirsarena.duel + +import dev.openrune.util.Wearpos +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class DuelRulesTest { + @Test + fun `rule bits match the client's dueloptions layout`() { + assertEquals(0, DuelRule.NoForfeit.bit) + assertEquals(1, DuelRule.NoMovement.bit) + assertEquals(2, DuelRule.NoWeaponSwitch.bit) + assertEquals(3, DuelRule.ShowInventories.bit) + assertEquals(4, DuelRule.NoRanged.bit) + assertEquals(5, DuelRule.NoMelee.bit) + assertEquals(6, DuelRule.NoMagic.bit) + assertEquals(7, DuelRule.NoDrinks.bit) + assertEquals(8, DuelRule.NoFood.bit) + assertEquals(9, DuelRule.NoPrayer.bit) + assertEquals(12, DuelRule.FunWeapons.bit) + assertEquals(13, DuelRule.NoSpecialAttacks.bit) + } + + @Test + fun `toggling a rule flips only its bit`() { + val rules = DuelRules.NONE.toggle(DuelRule.NoMagic) + assertTrue(rules.has(DuelRule.NoMagic)) + assertEquals(1 shl 6, rules.packed) + assertEquals(DuelRules.NONE, rules.toggle(DuelRule.NoMagic)) + } + + @Test + fun `worn slot toggles live in bits 14 to 27`() { + val rules = DuelRules.NONE.toggleSlot(Wearpos.LeftHand.slot) + assertTrue(rules.isSlotDisabled(Wearpos.LeftHand)) + assertFalse(rules.isSlotDisabled(Wearpos.RightHand)) + assertEquals(1 shl (14 + 5), rules.packed) + assertEquals(listOf(Wearpos.LeftHand), rules.disabledSlots) + } + + @Test + fun `whip preset keeps only the weapon slot and melee`() { + val whip = DuelRules.WHIP + assertFalse(whip.isSlotDisabled(Wearpos.RightHand)) + assertTrue(whip.isSlotDisabled(Wearpos.LeftHand)) + assertTrue(whip.isSlotDisabled(Wearpos.Torso)) + assertFalse(whip.isSlotDisabled(Wearpos.Arms), "client-only slots are never disabled") + assertTrue(whip.has(DuelRule.NoRanged)) + assertTrue(whip.has(DuelRule.NoMagic)) + assertTrue(whip.has(DuelRule.NoWeaponSwitch)) + assertFalse(whip.has(DuelRule.NoMelee)) + assertFalse(whip.has(DuelRule.NoForfeit)) + } + + @Test + fun `boxing preset disables every worn slot`() { + val boxing = DuelRules.BOXING + for (pos in Wearpos.entries.filter { !it.isClientOnly }) { + assertTrue(boxing.isSlotDisabled(pos), pos.name) + } + assertFalse(boxing.has(DuelRule.NoWeaponSwitch)) + assertFalse(boxing.has(DuelRule.NoMelee)) + } + + @Test + fun `packed values outside the known bits are rejected`() { + assertTrue(DuelRules.WHIP.isValid) + assertTrue(DuelRules.BOXING.isValid) + assertFalse(DuelRules(1 shl 10).isValid) + assertFalse(DuelRules(1 shl 28).isValid) + } + + @Test + fun `every option button maps to a distinct rule`() { + assertEquals(DuelRule.entries.size, DuelRule.BY_COMPONENT.size) + assertEquals(DuelRule.entries.toSet(), DuelRule.BY_COMPONENT.values.toSet()) + } +} diff --git a/content/other/emirs-arena/src/test/kotlin/org/rsmod/content/other/emirsarena/ranked/ArenaRankingTest.kt b/content/other/emirs-arena/src/test/kotlin/org/rsmod/content/other/emirsarena/ranked/ArenaRankingTest.kt new file mode 100644 index 000000000..784c0b0e0 --- /dev/null +++ b/content/other/emirs-arena/src/test/kotlin/org/rsmod/content/other/emirsarena/ranked/ArenaRankingTest.kt @@ -0,0 +1,60 @@ +package org.rsmod.content.other.emirsarena.ranked + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class ArenaRankingTest { + @Test + fun `reward points follow the official win and loss values with a capped streak bonus`() { + assertEquals(16, ArenaRanking.winRewardPoints(previousStreak = 0)) + assertEquals(19, ArenaRanking.winRewardPoints(previousStreak = 3)) + assertEquals(26, ArenaRanking.winRewardPoints(previousStreak = 10)) + assertEquals(26, ArenaRanking.winRewardPoints(previousStreak = 40)) + assertEquals(12, ArenaRanking.LOSS_REWARD_POINTS) + } + + @Test + fun `reward points never exceed the cap`() { + assertEquals(15_000, ArenaRanking.addRewardPoints(14_990, 26)) + assertEquals(15_000, ArenaRanking.addRewardPoints(15_000, 12)) + } + + @Test + fun `unranked players start at two and a half thousand`() { + assertEquals(2500, ArenaRanking.effectiveRank(0)) + assertEquals(2500, ArenaRanking.effectiveRank(-5)) + assertEquals(3100, ArenaRanking.effectiveRank(3100)) + } + + @Test + fun `beating a higher rank is worth more than beating a lower one`() { + val even = ArenaRanking.rankStake(winnerRank = 2500, loserRank = 2500) + val upset = ArenaRanking.rankStake(winnerRank = 2500, loserRank = 3500) + val expected = ArenaRanking.rankStake(winnerRank = 3500, loserRank = 2500) + assertEquals(25, even) + assertTrue(upset > even) + assertTrue(expected < even) + assertEquals(40, ArenaRanking.rankStake(winnerRank = 0, loserRank = 10_000)) + assertEquals(10, ArenaRanking.rankStake(winnerRank = 10_000, loserRank = 0)) + } + + @Test + fun `rank points stay within zero and the cap`() { + assertEquals(0, ArenaRanking.applyLoss(10, 25)) + assertEquals(15_000, ArenaRanking.applyWin(14_990, 25)) + } + + @Test + fun `rank tiers run from Bronze I to Dragon Grandmaster`() { + assertEquals(RankTier.BronzeI, RankTier.of(0)) + assertEquals(RankTier.BronzeI, RankTier.of(249)) + assertEquals(RankTier.BronzeII, RankTier.of(250)) + assertEquals(RankTier.MithrilII, RankTier.of(2500)) + assertEquals(RankTier.DragonMaster, RankTier.of(8999)) + assertEquals(RankTier.DragonGrandmaster, RankTier.of(9000)) + assertEquals(RankTier.DragonGrandmaster, RankTier.of(15_000)) + val thresholds = RankTier.entries.map { it.minPoints } + assertEquals(thresholds.sorted(), thresholds, "tiers must be declared in ascending order") + } +} diff --git a/content/other/grand-exchange/build.gradle.kts b/content/other/grand-exchange/build.gradle.kts new file mode 100644 index 000000000..4ccb264e3 --- /dev/null +++ b/content/other/grand-exchange/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(libs.rsprot.api) + implementation(libs.jackson.databind) + implementation(libs.jackson.module.kotlin) + implementation(projects.api.pluginCommons) + implementation(projects.content.interfaces.bank) +} diff --git a/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeConfig.kt b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeConfig.kt new file mode 100644 index 000000000..7bdd5f6a5 --- /dev/null +++ b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeConfig.kt @@ -0,0 +1,189 @@ +package org.rsmod.content.other.grandexchange + +/** + * Names and numbers the Grand Exchange plugin depends on. Everything here was read from the + * revision-240 cache (`interface.ge_offers` and friends, their clientscripts) or the OSRS wiki. + */ +object GeConfig { + /** Offer slots per player. Members have eight; this server treats every account as a member. */ + const val SLOT_COUNT = 8 + + /** The convenience fee sellers pay, in thousandths: 20 = 2%. */ + const val TAX_PER_MILLE = 20 + + /** The fee is capped per item sold. */ + const val TAX_CAP_PER_ITEM = 5_000_000 + + /** Buy limits reset four hours after the first purchase of the window. */ + const val BUY_LIMIT_WINDOW_MILLIS: Long = 4L * 60 * 60 * 1000 + + /** How many completed trades the history screen keeps per player. */ + const val HISTORY_SIZE = 50 + + /** + * Archive group in the client's jingle index (11) of "Grand Transaction", the jingle that plays + * when an offer completes. The `jingle.grand_exchange_trade_jingle` gameval resolves to a + * different number space, so the group was matched by decoding the cache's jingles and finding + * the one whose horn line is a note-for-note copy of The Trade Parade (10.7 s, as on the wiki). + */ + const val COMPLETION_JINGLE_GROUP = 86 + + /** Where the world's offer book is written, relative to the server working directory. */ + const val SAVE_PATH = ".data/grand-exchange.json" + + /** Ticks between background saves while there are unsaved changes. */ + const val SAVE_INTERVAL_TICKS = 50 + + const val COINS = "obj.coins" + const val BOND = "obj.osrs_bond" + + const val OFFERS = "interface.ge_offers" + const val OFFERS_SIDE = "interface.ge_offers_side" + const val COLLECT = "interface.ge_collect" + const val HISTORY = "interface.ge_history" + + const val PREFIX = "component.ge_offers:" + const val COMP_HISTORY = PREFIX + "history" + const val COMP_BACK = PREFIX + "back" + const val COMP_COLLECT_ALL = PREFIX + "collectall" + const val COMP_DETAILS = PREFIX + "details" + const val COMP_DETAILS_COLLECT = PREFIX + "details_collect" + const val COMP_SETUP = PREFIX + "setup" + const val COMP_SETUP_CONFIRM = PREFIX + "setup_confirm" + const val COMP_SIDE_ITEMS = "component.ge_offers_side:items" + const val COMP_COLLECT_INV = "component.ge_collect:collect_inv" + const val COMP_COLLECT_BANK = "component.ge_collect:collect_bank" + const val COMP_HISTORY_LIST = "component.ge_history:list" + const val COMP_HISTORY_EXCHANGE = "component.ge_history:exchange" + + /** `ge_history_addline` creates this many children per trade, so comsub / 6 is the line. */ + const val HISTORY_LINE_CHILDREN = 6 + + fun indexSlot(slot: Int): String = PREFIX + "index_$slot" + + fun collectSlot(slot: Int): String = "component.ge_collect:collect_$slot" + + /** Index slot box children created by `ge_offers_index_initslot`. */ + const val INDEX_CHILD_VIEW = 2 + const val INDEX_CHILD_BUY = 3 + const val INDEX_CHILD_SELL = 4 + + /** Setup panel children created by `ge_offers_setup_init`, in creation order. */ + const val SETUP_CHOOSE_ITEM = 0 + const val SETUP_QTY_MINUS_1 = 1 + const val SETUP_QTY_PLUS_1_MOBILE = 2 + const val SETUP_QTY_PLUS_1 = 3 + const val SETUP_QTY_PLUS_10 = 4 + const val SETUP_QTY_PLUS_100 = 5 + const val SETUP_QTY_PLUS_1K_OR_ALL = 6 + const val SETUP_QTY_ENTER = 7 + const val SETUP_PRICE_MINUS_1 = 8 + const val SETUP_PRICE_PLUS_1 = 9 + const val SETUP_PRICE_MINUS_5PCT = 10 + const val SETUP_PRICE_GUIDE = 11 + const val SETUP_PRICE_ENTER = 12 + const val SETUP_PRICE_PLUS_5PCT = 13 + const val SETUP_PRICE_MINUS_CUSTOM = 14 + const val SETUP_PRICE_PLUS_CUSTOM = 15 + const val SETUP_CHILD_COUNT = 18 + + /** Details panel children created by script 819. */ + const val DETAILS_ABORT = 0 + const val DETAILS_MODIFY = 1 + + /** The two collect boxes of an offer: slot 0 of its inventory holds items, slot 1 coins. */ + const val COLLECT_BOX_ITEMS = 0 + const val COLLECT_BOX_COINS = 1 + + /** + * Clickable children of `details_collect` (script 816): two background boxes come first, then + * the item icon and the coins icon. + */ + const val DETAILS_COLLECT_ITEMS_CHILD = 2 + const val DETAILS_COLLECT_COINS_CHILD = 3 + + /** Clickable children of `ge_collect:collect_N` (script 789): three frames, then the icons. */ + const val COLLECT_SLOT_ITEMS_CHILD = 3 + const val COLLECT_SLOT_COINS_CHILD = 4 + + /** Maps a collect-box child comsub to [COLLECT_BOX_ITEMS] / [COLLECT_BOX_COINS], or null. */ + fun collectBoxOf(comsub: Int, itemsChild: Int, coinsChild: Int): Int? = + when (comsub) { + itemsChild -> COLLECT_BOX_ITEMS + coinsChild -> COLLECT_BOX_COINS + else -> null + } + + /** `ge_offers_index_inittop` children on the collect-all layer. */ + const val COLLECT_ALL_BUTTON = 0 + const val REPEAT_OFFER_BUTTON = 1 + + /** The two-slot inventories the client draws each slot's collectable items and coins from. */ + val COLLECT_INVS: List = + listOf( + "inv.tradingpost_sell_0", + "inv.tradingpost_sell_1", + "inv.tradingpost_sell_2", + "inv.tradingpost_sell_3", + "inv.tradingpost_sell_4", + "inv.tradingpost_sell_5", + "inv.ge_collect_6", + "inv.ge_collect_7", + ) + + /** + * Varps the client reads the fee already paid on each slot from. Slots 2-5 point at music varps + * in the cache's script (`currentsong`, `musiclength`, ...), so only the others are written. + */ + val TAX_VARPS: List = + listOf( + "varp.ge_tax_slot_0", + "varp.ge_tax_slot_1", + null, + null, + null, + null, + "varp.ge_tax_slot_6", + "varp.ge_tax_slot_7", + ) + + fun itemSinkObjVarp(slot: Int): String = "varp.ge_itemsink_obj_$slot" + + fun itemSinkPriceVarp(slot: Int): String = "varp.ge_itemsink_price_$slot" + + const val VARP_ITEM = "varp.tradingpost_search" + const val VARP_LAST_SEARCHED = "varp.ge_last_searched" + const val VARP_LAST_OFFER_ITEM = "varp.ge_last_offer_item" + const val VARP_LAST_OFFER_QUANTITY = "varp.ge_last_offer_quantity" + const val VARP_LAST_OFFER_PRICE = "varp.ge_last_offer_price" + const val VARP_LAST_OFFER_TYPE = "varp.ge_last_offer_type" + const val VARBIT_SELECTED_SLOT = "varbit.ge_selectedslot" + const val VARBIT_TYPE = "varbit.ge_newoffer_type" + const val VARBIT_QUANTITY = "varbit.ge_newoffer_quantity" + const val VARBIT_PRICE = "varbit.ge_newoffer_price" + const val VARBIT_CUSTOM_PERCENT = "varbit.ge_price_custom" + const val VARBIT_TAX_RATE = "varbit.ge_transmit_taxrate" + + /** Clientscripts that fill the history screen. */ + const val CS2_HISTORY_INIT = 1644 + const val CS2_HISTORY_ADD_LINE = 1645 + const val CS2_HISTORY_FINISH = 1646 + + const val LOC_BOOTH_EXCHANGE = "loc.exchange_bank_wall_exchange" + const val LOC_BOOTH_BANK = "loc.exchange_bank_wall_bank" + val CLERKS: List = listOf("npc.ge_clerk_1", "npc.ge_clerk_2", "npc.ge_clerk_3", "npc.ge_clerk_4") + + /** Pricing experts: npc to the family of goods they quote. */ + val EXPERTS: Map = + mapOf( + "npc.ge_boss" to "anything", + "npc.ge_expert_ores" to "ores, bars and gems", + "npc.ge_expert_herbs" to "herbs and potions", + "npc.ge_expert_runes" to "runes", + "npc.ge_expert_logs" to "logs", + "npc.ge_expert_combat" to "weapons and armour", + ) + + const val MAX_CUSTOM_PERCENT = 100 + const val DEFAULT_CUSTOM_PERCENT = 5 +} diff --git a/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeExchange.kt b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeExchange.kt new file mode 100644 index 000000000..88923fc39 --- /dev/null +++ b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeExchange.kt @@ -0,0 +1,375 @@ +package org.rsmod.content.other.grandexchange + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.github.michaelbull.logging.InlineLogger +import jakarta.inject.Inject +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import kotlin.math.min + +/** + * The world's order book: every player's offer slots, the matching between them, buy-limit + * windows and trade history. Nothing in here touches a [org.rsmod.game.entity.Player]; the + * service layer turns the [Listener] callbacks into packets, messages and inventories. + * + * Matching follows the wiki's description: a new buy offer trades against the cheapest existing + * sell offers at *their* price, refunding the buyer the difference; a new sell offer trades + * against the highest existing buy offers at *their* price, so the seller earns more than asked. + * Older offers win ties. The seller pays the convenience fee on every item sold. + */ +class GeExchange(private val items: GeItemData, private val clock: () -> Long) { + @Inject constructor(items: GeItemData) : this(items, System::currentTimeMillis) + + interface Listener { + /** A trade moved [offer] forward; [trade] is what the offer's owner bought or sold. */ + fun onOfferTraded(offer: GeOffer, trade: GeTrade) + + /** [offer] finished: completed by trades or aborted by its owner. */ + fun onOfferFinished(offer: GeOffer, aborted: Boolean) + } + + var listener: Listener? = null + + private val offers = LinkedHashMap() + private val slotsByOwner = HashMap>() + private val activeByItem = HashMap>() + private val history = HashMap>() + private val buyWindows = HashMap>() + private var nextId = 1L + + var dirty: Boolean = false + private set + + val offerCount: Int + get() = offers.size + + fun allOffers(): List = offers.values.toList() + + fun slots(owner: Int): List = slotsByOwner[owner]?.toList() ?: List(GeConfig.SLOT_COUNT) { null } + + fun offer(owner: Int, slot: Int): GeOffer? = slotsByOwner[owner]?.getOrNull(slot) + + fun firstEmptySlot(owner: Int): Int? { + val slots = slotsByOwner[owner] ?: return 0 + return slots.indices.firstOrNull { slots[it] == null } + } + + fun history(owner: Int): List = history[owner]?.toList() ?: emptyList() + + /** How many more of [item] [owner] may buy before the current four-hour window ends. */ + fun remainingBuyLimit(owner: Int, item: Int, now: Long = clock()): Int { + val limit = items.buyLimit(item) + if (limit == GeItemData.NO_LIMIT) { + return limit + } + val window = buyWindows[owner]?.get(item) ?: return limit + if (now - window.start >= GeConfig.BUY_LIMIT_WINDOW_MILLIS) { + return limit + } + return maxOf(0, limit - window.bought) + } + + /** Millis until [owner]'s buy limit on [item] resets, or 0 when no window is open. */ + fun buyLimitResetIn(owner: Int, item: Int, now: Long = clock()): Long { + val window = buyWindows[owner]?.get(item) ?: return 0 + val remaining = GeConfig.BUY_LIMIT_WINDOW_MILLIS - (now - window.start) + return if (remaining > 0) remaining else 0 + } + + /** + * Creates an offer in [slot] and matches it against the book straight away. The caller has + * already taken the coins or items from the player. + */ + fun place( + owner: Int, + ownerName: String, + slot: Int, + type: GeOfferType, + item: Int, + price: Int, + quantity: Int, + ): GeOffer { + require(slot in 0 until GeConfig.SLOT_COUNT) { "Invalid slot: $slot" } + require(price > 0) { "Price must be positive: $price" } + require(quantity > 0) { "Quantity must be positive: $quantity" } + require(price.toLong() * quantity <= Int.MAX_VALUE) { "Offer value overflows: $price x $quantity" } + val slots = slotsByOwner.getOrPut(owner) { arrayOfNulls(GeConfig.SLOT_COUNT) } + require(slots[slot] == null) { "Slot $slot of $owner is occupied" } + + val offer = GeOffer(nextId++, owner, ownerName, slot, type, item, price, quantity, clock()) + offers[offer.id] = offer + slots[slot] = offer + activeByItem.getOrPut(item) { ArrayList() } += offer + dirty = true + match(offer) + return offer + } + + /** Stops an active offer; what it has not traded moves to its collect box. */ + fun abort(offer: GeOffer): Boolean { + if (offer.finished) { + return false + } + val remaining = offer.remaining + when (offer.type) { + GeOfferType.Buy -> offer.collectCoins += remaining * offer.price + GeOfferType.Sell -> offer.collectItems += remaining + } + finish(offer, aborted = true) + return true + } + + /** + * Takes up to [count] items waiting in the offer's collect box and returns how many were + * taken. Frees the slot once nothing is left to collect on a finished offer. + */ + fun takeItems(offer: GeOffer, count: Int = offer.collectItems): Int { + val taken = count.coerceIn(0, offer.collectItems) + offer.collectItems -= taken + dirty = true + releaseIfDone(offer) + return taken + } + + /** Takes up to [count] coins waiting in the offer's collect box; see [takeItems]. */ + fun takeCoins(offer: GeOffer, count: Int = offer.collectCoins): Int { + val taken = count.coerceIn(0, offer.collectCoins) + offer.collectCoins -= taken + dirty = true + releaseIfDone(offer) + return taken + } + + private fun match(offer: GeOffer) { + val active = activeByItem[offer.item] ?: return + val candidates = + active + .filter { it !== offer && it.type != offer.type && it.isActive && it.remaining > 0 } + .filter { other -> + if (offer.type == GeOfferType.Buy) other.price <= offer.price else other.price >= offer.price + } + .sortedWith( + if (offer.type == GeOfferType.Buy) { + compareBy({ it.price }, { it.createdAt }, { it.id }) + } else { + compareBy({ -it.price }, { it.createdAt }, { it.id }) + } + ) + for (other in candidates) { + if (offer.remaining <= 0) { + break + } + val buy = if (offer.type == GeOfferType.Buy) offer else other + val sell = if (offer.type == GeOfferType.Sell) offer else other + var quantity = min(buy.remaining, sell.remaining) + quantity = min(quantity, remainingBuyLimit(buy.owner, buy.item)) + if (quantity <= 0) { + if (buy === offer) { + // The new buyer is at their limit; nothing else on the book can help. + break + } + continue + } + trade(buy, sell, quantity, other.price) + } + } + + private fun trade(buy: GeOffer, sell: GeOffer, quantity: Int, price: Int) { + val now = clock() + val gross = quantity * price + val tax = items.taxPerItem(buy.item, price) * quantity + + buy.completed += quantity + buy.gold += gross + buy.collectItems += quantity + buy.collectCoins += quantity * (buy.price - price) + + sell.completed += quantity + sell.gold += gross + sell.taxPaid += tax + sell.collectCoins += gross - tax + + recordPurchase(buy.owner, buy.item, quantity, now) + val bought = GeTrade(now, GeOfferType.Buy, buy.item, quantity, gross, 0) + val sold = GeTrade(now, GeOfferType.Sell, sell.item, quantity, gross, tax) + addHistory(buy.owner, bought) + addHistory(sell.owner, sold) + dirty = true + + listener?.onOfferTraded(buy, bought) + listener?.onOfferTraded(sell, sold) + if (buy.remaining <= 0) { + finish(buy, aborted = false) + } + if (sell.remaining <= 0) { + finish(sell, aborted = false) + } + } + + private fun finish(offer: GeOffer, aborted: Boolean) { + offer.finished = true + activeByItem[offer.item]?.remove(offer) + dirty = true + listener?.onOfferFinished(offer, aborted) + releaseIfDone(offer) + } + + private fun releaseIfDone(offer: GeOffer) { + if (!offer.finished || offer.hasCollectables) { + return + } + offers.remove(offer.id) + slotsByOwner[offer.owner]?.let { slots -> + if (slots.getOrNull(offer.slot) === offer) { + slots[offer.slot] = null + } + } + dirty = true + } + + private fun recordPurchase(owner: Int, item: Int, quantity: Int, now: Long) { + if (items.buyLimit(item) == GeItemData.NO_LIMIT) { + return + } + val windows = buyWindows.getOrPut(owner) { HashMap() } + val window = windows[item] + windows[item] = + if (window == null || now - window.start >= GeConfig.BUY_LIMIT_WINDOW_MILLIS) { + GeBuyWindow(now, quantity) + } else { + window.copy(bought = window.bought + quantity) + } + } + + private fun addHistory(owner: Int, trade: GeTrade) { + val list = history.getOrPut(owner) { ArrayDeque() } + list.addFirst(trade) + while (list.size > GeConfig.HISTORY_SIZE) { + list.removeLast() + } + } + + /* Persistence */ + + fun save(path: Path) { + val data = + GeSaveData( + nextId = nextId, + offers = offers.values.map(GeOfferData::from), + history = history.mapValues { it.value.toList() }, + buyWindows = + buyWindows.flatMap { (owner, windows) -> + windows.map { (item, window) -> GeBuyWindowData(owner, item, window.start, window.bought) } + }, + ) + Files.createDirectories(path.toAbsolutePath().parent) + val tmp = path.resolveSibling(path.fileName.toString() + ".tmp") + mapper.writerWithDefaultPrettyPrinter().writeValue(tmp.toFile(), data) + Files.move(tmp, path, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE) + dirty = false + } + + fun saveIfDirty(path: Path) { + if (dirty) { + save(path) + } + } + + fun load(path: Path): Boolean { + if (!Files.exists(path)) { + return false + } + val data = mapper.readValue(path.toFile()) + offers.clear() + slotsByOwner.clear() + activeByItem.clear() + history.clear() + buyWindows.clear() + nextId = data.nextId + for (saved in data.offers) { + val offer = saved.toOffer() + offers[offer.id] = offer + slotsByOwner.getOrPut(offer.owner) { arrayOfNulls(GeConfig.SLOT_COUNT) }[offer.slot] = offer + if (offer.isActive) { + activeByItem.getOrPut(offer.item) { ArrayList() } += offer + } + if (offer.id >= nextId) { + nextId = offer.id + 1 + } + } + for ((owner, trades) in data.history) { + history[owner] = ArrayDeque(trades.take(GeConfig.HISTORY_SIZE)) + } + for (window in data.buyWindows) { + buyWindows.getOrPut(window.owner) { HashMap() }[window.item] = GeBuyWindow(window.start, window.bought) + } + dirty = false + logger.info { "Loaded ${offers.size} Grand Exchange offers from $path." } + return true + } + + private companion object { + private val logger = InlineLogger() + private val mapper = jacksonObjectMapper() + } +} + +data class GeSaveData( + val nextId: Long = 1, + val offers: List = emptyList(), + val history: Map> = emptyMap(), + val buyWindows: List = emptyList(), +) + +data class GeBuyWindowData(val owner: Int = 0, val item: Int = 0, val start: Long = 0, val bought: Int = 0) + +data class GeOfferData( + val id: Long = 0, + val owner: Int = 0, + val ownerName: String = "", + val slot: Int = 0, + val type: GeOfferType = GeOfferType.Buy, + val item: Int = 0, + val price: Int = 0, + val total: Int = 0, + val createdAt: Long = 0, + val completed: Int = 0, + val gold: Int = 0, + val taxPaid: Int = 0, + val collectItems: Int = 0, + val collectCoins: Int = 0, + val finished: Boolean = false, +) { + fun toOffer(): GeOffer = + GeOffer(id, owner, ownerName, slot, type, item, price, total, createdAt).also { + it.completed = completed + it.gold = gold + it.taxPaid = taxPaid + it.collectItems = collectItems + it.collectCoins = collectCoins + it.finished = finished + } + + companion object { + fun from(offer: GeOffer): GeOfferData = + GeOfferData( + id = offer.id, + owner = offer.owner, + ownerName = offer.ownerName, + slot = offer.slot, + type = offer.type, + item = offer.item, + price = offer.price, + total = offer.total, + createdAt = offer.createdAt, + completed = offer.completed, + gold = offer.gold, + taxPaid = offer.taxPaid, + collectItems = offer.collectItems, + collectCoins = offer.collectCoins, + finished = offer.finished, + ) + } +} diff --git a/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeItemData.kt b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeItemData.kt new file mode 100644 index 000000000..57276d8cd --- /dev/null +++ b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeItemData.kt @@ -0,0 +1,136 @@ +package org.rsmod.content.other.grandexchange + +import dev.openrune.ServerCacheManager +import kotlin.math.min + +/** + * Guide prices, buy limits and tax exemptions for every item the Grand Exchange trades. + * + * The data ships as `ge-items.tsv` (one `id buy_limit guide_price name` line per tradeable + * item), dumped from the OSRS wiki's prices API and its `Module:GELimits` page. Items missing from + * the file fall back to their cache shop value and no buy limit. + */ +class GeItemData private constructor(private val entries: Map) { + class Entry(val id: Int, val buyLimit: Int, val guidePrice: Int, val name: String) + + private val taxExempt: Set = + entries.values.filter { isTaxExemptName(it.name) }.map { it.id }.toHashSet() + + val size: Int + get() = entries.size + + fun entry(item: Int): Entry? = entries[item] + + /** The guide price the offer setup screen defaults to. Never below one coin. */ + fun guidePrice(item: Int): Int { + val known = entries[item]?.guidePrice + if (known != null && known > 0) { + return known + } + val cost = ServerCacheManager.getItem(item)?.cost ?: 0 + return if (cost > 0) cost else 1 + } + + /** Items a player may buy every four hours, or [NO_LIMIT] when the wiki lists none. */ + fun buyLimit(item: Int): Int { + val limit = entries[item]?.buyLimit ?: 0 + return if (limit > 0) limit else NO_LIMIT + } + + fun isTaxExempt(item: Int): Boolean = item in taxExempt + + /** The fee taken from the seller on every item sold at [price]: 2% rounded down, capped. */ + fun taxPerItem(item: Int, price: Int): Int { + if (isTaxExempt(item)) { + return 0 + } + val tax = price.toLong() * GeConfig.TAX_PER_MILLE / 1000 + return min(tax, GeConfig.TAX_CAP_PER_ITEM.toLong()).toInt() + } + + companion object { + const val NO_LIMIT: Int = Int.MAX_VALUE + private const val RESOURCE = "/ge-items.tsv" + + /** Item names the wiki lists as exempt from the tax; potion names cover every dose. */ + private val EXACT_EXEMPT: Set = + setOf( + "old school bond", + "bronze arrow", + "bronze dart", + "iron arrow", + "iron dart", + "mind rune", + "steel arrow", + "steel dart", + "bass", + "bread", + "cake", + "cooked chicken", + "cooked meat", + "herring", + "lobster", + "mackerel", + "meat pie", + "pike", + "salmon", + "shrimps", + "tuna", + "ardougne teleport", + "camelot teleport", + "civitas illa fortis teleport", + "falador teleport", + "games necklace(8)", + "kourend castle teleport", + "lumbridge teleport", + "ring of dueling(8)", + "teleport to house", + "varrock teleport", + "chisel", + "gardening trowel", + "glassblowing pipe", + "hammer", + "needle", + "pestle and mortar", + "rake", + "saw", + "secateurs", + "seed dibber", + "shears", + "spade", + "watering can", + ) + + fun isTaxExemptName(name: String): Boolean { + val lower = name.lowercase() + return lower in EXACT_EXEMPT || lower.startsWith("energy potion(") + } + + fun load(): GeItemData { + val stream = + GeItemData::class.java.getResourceAsStream(RESOURCE) + ?: error("Missing Grand Exchange item data resource: $RESOURCE") + val lines = stream.bufferedReader().use { it.readLines() } + return fromLines(lines) + } + + fun fromLines(lines: Iterable): GeItemData { + val entries = HashMap() + for (raw in lines) { + val line = raw.trim() + if (line.isEmpty() || line.startsWith("#")) { + continue + } + val parts = line.split('\t') + if (parts.size < 4) { + continue + } + val id = parts[0].toIntOrNull() ?: continue + val limit = parts[1].toIntOrNull() ?: 0 + val price = parts[2].toIntOrNull() ?: 0 + entries[id] = Entry(id, limit, price, parts[3]) + } + return GeItemData(entries) + } + } +} diff --git a/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeOffer.kt b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeOffer.kt new file mode 100644 index 000000000..2b12c0ca9 --- /dev/null +++ b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeOffer.kt @@ -0,0 +1,82 @@ +package org.rsmod.content.other.grandexchange + +enum class GeOfferType(val clientId: Int) { + Buy(0), + Sell(1); + + companion object { + fun fromClientId(id: Int): GeOfferType = if (id == Sell.clientId) Sell else Buy + } +} + +/** + * One offer slot. An offer stays in its slot after it finishes (completed or aborted) until the + * owner has collected everything it holds; only then is the slot free again. + * + * [gold] is the gross value traded so far: coins spent for a buy offer, coins earned before tax + * for a sell offer. Refunds and proceeds waiting to be picked up sit in [collectCoins], items in + * [collectItems] (always the unnoted item; the collect ops decide the form). + */ +class GeOffer( + val id: Long, + val owner: Int, + val ownerName: String, + val slot: Int, + val type: GeOfferType, + val item: Int, + val price: Int, + val total: Int, + val createdAt: Long, +) { + var completed: Int = 0 + var gold: Int = 0 + var taxPaid: Int = 0 + var collectItems: Int = 0 + var collectCoins: Int = 0 + var finished: Boolean = false + + val remaining: Int + get() = total - completed + + val isActive: Boolean + get() = !finished + + val hasCollectables: Boolean + get() = collectItems > 0 || collectCoins > 0 + + val isCancelled: Boolean + get() = finished && completed < total + + /** + * The status byte of the client's stock market slot packet: bit 1 live, bit 2 finished, bit 3 + * sell. A finished offer whose completed count is short of the total draws as cancelled. + */ + fun clientStatus(): Int { + val base = if (finished) STATUS_FINISHED else STATUS_LIVE + return if (type == GeOfferType.Sell) base or STATUS_SELL else base + } + + override fun toString(): String = + "GeOffer(id=$id, owner=$owner, slot=$slot, type=$type, item=$item, price=$price, " + + "total=$total, completed=$completed, gold=$gold, tax=$taxPaid, collectItems=$collectItems, " + + "collectCoins=$collectCoins, finished=$finished)" + + companion object { + const val STATUS_LIVE = 2 + const val STATUS_FINISHED = 5 + const val STATUS_SELL = 8 + } +} + +/** A completed trade as the history screen lists it. [total] is gross; [tax] was taken from it. */ +data class GeTrade( + val time: Long = 0, + val type: GeOfferType = GeOfferType.Buy, + val item: Int = 0, + val quantity: Int = 0, + val total: Int = 0, + val tax: Int = 0, +) + +/** Purchases counted against an item's buy limit since the first purchase of the window. */ +data class GeBuyWindow(val start: Long = 0, val bought: Int = 0) diff --git a/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeService.kt b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeService.kt new file mode 100644 index 000000000..e339329d3 --- /dev/null +++ b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GeService.kt @@ -0,0 +1,795 @@ +package org.rsmod.content.other.grandexchange + +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.ServerCacheManager +import dev.openrune.definition.type.widget.IfEvent +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import jakarta.inject.Inject +import java.nio.file.Path +import kotlin.math.max +import kotlin.math.min +import net.rsprot.protocol.game.outgoing.misc.player.UpdateStockMarketSlot +import org.rsmod.api.invtx.invAdd +import org.rsmod.api.invtx.invDel +import org.rsmod.api.player.ironman.isSoloIronman +import org.rsmod.api.player.midiJingle +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.output.runClientScript +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.startInvTransmit +import org.rsmod.api.player.stopInvTransmit +import org.rsmod.api.player.ui.ifSetEvents +import org.rsmod.api.player.vars.VarPlayerIntMapSetter +import org.rsmod.api.utils.format.formatAmount +import org.rsmod.game.entity.Player +import org.rsmod.game.entity.PlayerList +import org.rsmod.game.inv.InvObj +import org.rsmod.game.inv.Inventory +import org.rsmod.game.type.uncert + +/** + * Everything the Grand Exchange does *to a player*: driving the cache's offer, collection and + * history screens, moving coins and items in and out of the [GeExchange] book, and telling + * players when their offers move. + * + * Client contract (read from the revision-240 clientscripts, see [GeConfig]): + * - Each slot's state arrives in an `UpdateStockMarketSlot` packet; the screens redraw from it. + * - The setup panel reads the chosen item from `varp.tradingpost_search` and the quantity, price + * and type from the `ge_newoffer_*` varbits. The panel's buttons change those varbits client + * side, and also reach the server as buttons on `ge_offers:setup`, so the same numbers are + * kept here; a mismatch would make the client snap back to the server value. + * - Collectable items and coins are drawn from the two-slot `tradingpost_sell_N` / `ge_collect_N` + * inventories (slot 0 items, slot 1 coins). + * - The `ge_itemsink_obj_N` varps must be -1 or the client shows "Offer status" on empty slots. + */ +class GeService +@Inject +constructor( + private val exchange: GeExchange, + private val items: GeItemData, + private val playerList: PlayerList, +) : GeExchange.Listener { + private val savePath: Path = Path.of(GeConfig.SAVE_PATH) + private var saveCountdown = GeConfig.SAVE_INTERVAL_TICKS + private val coinsId: Int by lazy { GeConfig.COINS.asRSCM(RSCMType.OBJ) } + private val bondId: Int by lazy { GeConfig.BOND.asRSCM(RSCMType.OBJ) } + + init { + exchange.listener = this + } + + /* Lifecycle */ + + fun load() { + try { + exchange.load(savePath) + } catch (t: Throwable) { + logger.error(t) { "Could not load the Grand Exchange book from $savePath; starting empty." } + } + } + + fun tick() { + if (!exchange.dirty) { + return + } + if (--saveCountdown <= 0) { + save() + } + } + + fun save() { + saveCountdown = GeConfig.SAVE_INTERVAL_TICKS + if (!exchange.dirty) { + return + } + try { + exchange.save(savePath) + } catch (t: Throwable) { + logger.error(t) { "Could not save the Grand Exchange book to $savePath." } + } + } + + fun onLogin(player: Player) { + for (slot in 0 until GeConfig.SLOT_COUNT) { + setVar(player, GeConfig.itemSinkObjVarp(slot), -1) + setVar(player, GeConfig.itemSinkPriceVarp(slot), -1) + } + setVar(player, GeConfig.VARBIT_TAX_RATE, GeConfig.TAX_PER_MILLE) + setVar(player, GeConfig.VARBIT_SELECTED_SLOT, 0) + setVar(player, GeConfig.VARP_ITEM, -1) + refreshAll(player) + } + + /* Listener: offers moving while the owner may or may not be online. */ + + override fun onOfferTraded(offer: GeOffer, trade: GeTrade) { + val player = online(offer.owner) ?: return + refresh(player, offer.slot) + } + + override fun onOfferFinished(offer: GeOffer, aborted: Boolean) { + val player = online(offer.owner) ?: return + refresh(player, offer.slot) + if (aborted) { + return + } + val verb = if (offer.type == GeOfferType.Buy) "buying" else "selling" + player.mes("Grand Exchange: Finished $verb ${offer.total.formatAmount} x ${name(offer.item)}.") + player.midiJingle(GeConfig.COMPLETION_JINGLE_GROUP) + } + + private fun online(accountId: Int): Player? = playerList.firstOrNull { it.accountId == accountId } + + /* Client state */ + + fun refreshAll(player: Player) { + for (slot in 0 until GeConfig.SLOT_COUNT) { + refresh(player, slot) + } + } + + fun refresh(player: Player, slot: Int) { + val offer = exchange.offer(player.accountId, slot) + val update = + if (offer == null) { + UpdateStockMarketSlot.ResetStockMarketSlot + } else { + UpdateStockMarketSlot.SetStockMarketSlot( + status = offer.clientStatus(), + obj = offer.item, + price = offer.price, + count = offer.total, + completedCount = offer.completed, + completedGold = offer.gold, + ) + } + player.client.write(UpdateStockMarketSlot(slot, update)) + GeConfig.TAX_VARPS[slot]?.let { setVar(player, it, offer?.taxPaid ?: 0) } + syncCollectInv(player, slot) + } + + private fun collectInv(player: Player, slot: Int): Inventory = player.invMap.getOrPut(GeConfig.COLLECT_INVS[slot]) + + private fun syncCollectInv(player: Player, slot: Int) { + val inv = collectInv(player, slot) + val offer = exchange.offer(player.accountId, slot) + val itemsObj = + offer?.takeIf { it.collectItems > 0 }?.let { InvObj(ServerCacheManager.getItemOrDefault(it.item), it.collectItems) } + val coinsObj = offer?.takeIf { it.collectCoins > 0 }?.let { InvObj(ServerCacheManager.getItemOrDefault(coinsId), it.collectCoins) } + if (inv[GeConfig.COLLECT_BOX_ITEMS] != itemsObj) { + inv[GeConfig.COLLECT_BOX_ITEMS] = itemsObj + } + if (inv[GeConfig.COLLECT_BOX_COINS] != coinsObj) { + inv[GeConfig.COLLECT_BOX_COINS] = coinsObj + } + } + + /* Offers screen */ + + fun open(access: ProtectedAccess) { + val player = access.player + if (player.isSoloIronman) { + player.mes("As an Ironman, you can only use the Grand Exchange to buy bonds.") + } + setVar(player, GeConfig.VARBIT_SELECTED_SLOT, 0) + clearSetup(player) + access.ifOpenMainSidePair(GeConfig.OFFERS, GeConfig.OFFERS_SIDE) + } + + /** Called when `interface.ge_offers` opens, however it was opened. */ + fun onOffersOpened(player: Player) { + for (slot in 0 until GeConfig.SLOT_COUNT) { + player.ifSetEvents( + GeConfig.indexSlot(slot), + GeConfig.INDEX_CHILD_VIEW..GeConfig.INDEX_CHILD_SELL, + IfEvent.Op1, + ) + } + player.ifSetEvents(GeConfig.COMP_SETUP, 0 until GeConfig.SETUP_CHILD_COUNT, IfEvent.Op1, IfEvent.Op2) + player.ifSetEvents(GeConfig.COMP_DETAILS, GeConfig.DETAILS_ABORT..GeConfig.DETAILS_MODIFY, IfEvent.Op1) + player.ifSetEvents( + GeConfig.COMP_DETAILS_COLLECT, + GeConfig.DETAILS_COLLECT_ITEMS_CHILD..GeConfig.DETAILS_COLLECT_COINS_CHILD, + IfEvent.Op1, + IfEvent.Op2, + IfEvent.Op3, + IfEvent.Op10, + ) + player.ifSetEvents( + GeConfig.COMP_COLLECT_ALL, + GeConfig.COLLECT_ALL_BUTTON..GeConfig.REPEAT_OFFER_BUTTON, + IfEvent.Op1, + IfEvent.Op2, + ) + player.ifSetEvents(GeConfig.COMP_SIDE_ITEMS, player.inv.indices, IfEvent.Op1, IfEvent.Op10) + startTransmits(player) + refreshAll(player) + } + + fun onOffersClosed(player: Player) { + stopTransmits(player) + setVar(player, GeConfig.VARBIT_SELECTED_SLOT, 0) + clearSetup(player) + } + + /** Called when `interface.ge_collect` (the collection box) opens. */ + fun onCollectionBoxOpened(player: Player) { + for (slot in 0 until GeConfig.SLOT_COUNT) { + player.ifSetEvents( + GeConfig.collectSlot(slot), + GeConfig.COLLECT_SLOT_ITEMS_CHILD..GeConfig.COLLECT_SLOT_COINS_CHILD, + IfEvent.Op1, + IfEvent.Op2, + IfEvent.Op3, + IfEvent.Op10, + ) + } + player.ifSetEvents(GeConfig.COMP_COLLECT_INV, 0..0, IfEvent.Op1) + player.ifSetEvents(GeConfig.COMP_COLLECT_BANK, 0..0, IfEvent.Op1) + startTransmits(player) + refreshAll(player) + } + + fun onCollectionBoxClosed(player: Player) { + stopTransmits(player) + } + + private fun startTransmits(player: Player) { + for (slot in 0 until GeConfig.SLOT_COUNT) { + player.startInvTransmit(collectInv(player, slot)) + } + } + + private fun stopTransmits(player: Player) { + for (slot in 0 until GeConfig.SLOT_COUNT) { + player.stopInvTransmit(collectInv(player, slot)) + } + } + + /* Slot selection */ + + fun viewSlot(access: ProtectedAccess, slot: Int) { + val player = access.player + if (exchange.offer(player.accountId, slot) == null) { + clearSetup(player) + } + setVar(player, GeConfig.VARBIT_SELECTED_SLOT, slot + 1) + } + + fun back(player: Player) { + setVar(player, GeConfig.VARBIT_SELECTED_SLOT, 0) + clearSetup(player) + } + + suspend fun startBuy(access: ProtectedAccess, slot: Int) { + val player = access.player + if (exchange.offer(player.accountId, slot) != null) { + viewSlot(access, slot) + return + } + beginSetup(player, slot, GeOfferType.Buy, item = -1, quantity = 0, price = 0) + chooseItem(access) + } + + fun startSell(access: ProtectedAccess, slot: Int) { + val player = access.player + if (exchange.offer(player.accountId, slot) != null) { + viewSlot(access, slot) + return + } + if (player.isSoloIronman) { + player.mes("As an Ironman, you cannot sell items on the Grand Exchange.") + return + } + beginSetup(player, slot, GeOfferType.Sell, item = -1, quantity = 0, price = 0) + } + + private fun beginSetup(player: Player, slot: Int, type: GeOfferType, item: Int, quantity: Int, price: Int) { + setVar(player, GeConfig.VARBIT_SELECTED_SLOT, slot + 1) + setVar(player, GeConfig.VARBIT_TYPE, type.clientId) + setVar(player, GeConfig.VARP_ITEM, item) + setVar(player, GeConfig.VARBIT_QUANTITY, quantity) + setVar(player, GeConfig.VARBIT_PRICE, price) + } + + private fun clearSetup(player: Player) { + setVar(player, GeConfig.VARP_ITEM, -1) + setVar(player, GeConfig.VARBIT_QUANTITY, 0) + setVar(player, GeConfig.VARBIT_PRICE, 0) + } + + /** The slot an offer is being set up in, or null when the setup panel is not showing. */ + private fun setupSlot(player: Player): Int? { + val selected = player.vars[GeConfig.VARBIT_SELECTED_SLOT] - 1 + if (selected !in 0 until GeConfig.SLOT_COUNT) { + return null + } + return selected.takeIf { exchange.offer(player.accountId, it) == null } + } + + /** The offer whose status panel is showing, or null. */ + private fun selectedOffer(player: Player): GeOffer? { + val selected = player.vars[GeConfig.VARBIT_SELECTED_SLOT] - 1 + if (selected !in 0 until GeConfig.SLOT_COUNT) { + return null + } + return exchange.offer(player.accountId, selected) + } + + private fun setupType(player: Player): GeOfferType = GeOfferType.fromClientId(player.vars[GeConfig.VARBIT_TYPE]) + + private fun setupItem(player: Player): ItemServerType? { + val id = player.vars[GeConfig.VARP_ITEM] + if (id <= 0) { + return null + } + return ServerCacheManager.getItem(id) + } + + /* Setup panel */ + + suspend fun chooseItem(access: ProtectedAccess) { + val player = access.player + val slot = setupSlot(player) ?: return + if (setupType(player) != GeOfferType.Buy) { + player.mes("Choose the item you want to sell from your inventory.") + return + } + val chosen = access.objDialog("What would you like to buy?", stockMarketRestriction = true, showLastSearched = true) + // Anything can have changed while the search was open. + if (setupSlot(player) != slot || setupType(player) != GeOfferType.Buy) { + return + } + val item = uncert(chosen) + if (!canTrade(player, item, GeOfferType.Buy)) { + return + } + setVar(player, GeConfig.VARP_LAST_SEARCHED, item.id) + setVar(player, GeConfig.VARP_ITEM, item.id) + setVar(player, GeConfig.VARBIT_QUANTITY, 1) + setVar(player, GeConfig.VARBIT_PRICE, items.guidePrice(item.id)) + } + + /** "Offer" on an inventory item: sell it, in the open sell setup or the first free slot. */ + fun offerFromInventory(access: ProtectedAccess, invSlot: Int) { + val player = access.player + val obj = player.inv[invSlot] ?: return + val item = uncert(ServerCacheManager.getItemOrDefault(obj.id)) + if (!canTrade(player, item, GeOfferType.Sell)) { + return + } + val current = setupSlot(player) + val slot = + if (current != null && setupType(player) == GeOfferType.Sell) { + current + } else { + exchange.firstEmptySlot(player.accountId) + } + if (slot == null) { + player.mes("You have no free offer slots.") + return + } + val available = available(player, item) + beginSetup(player, slot, GeOfferType.Sell, item.id, available, items.guidePrice(item.id)) + } + + fun examineInventory(player: Player, invSlot: Int) { + val obj = player.inv[invSlot] ?: return + player.mes(ServerCacheManager.getItemOrDefault(obj.id).examine) + } + + fun adjustQuantity(player: Player, delta: Int) { + val current = player.vars[GeConfig.VARBIT_QUANTITY] + setQuantity(player, current.toLong() + delta) + } + + fun setQuantity(player: Player, value: Long) { + setupSlot(player) ?: return + val item = setupItem(player) ?: return + val max = maxQuantity(player, item) + setVar(player, GeConfig.VARBIT_QUANTITY, value.coerceIn(1L, max.toLong()).toInt()) + } + + fun setAllOrThousand(player: Player) { + setupSlot(player) ?: return + val item = setupItem(player) ?: return + if (setupType(player) == GeOfferType.Sell) { + setQuantity(player, available(player, item).toLong()) + } else { + adjustQuantity(player, 1000) + } + } + + suspend fun enterQuantity(access: ProtectedAccess) { + setupSlot(access.player) ?: return + val count = access.countDialog("How many do you wish to trade?") + setQuantity(access.player, count.toLong()) + } + + fun adjustPrice(player: Player, delta: Int) { + val current = player.vars[GeConfig.VARBIT_PRICE] + setPrice(player, current.toLong() + delta) + } + + fun adjustPricePercent(player: Player, percent: Int) { + val current = player.vars[GeConfig.VARBIT_PRICE].toLong() + setPrice(player, current + current * percent / 100) + } + + fun customPercent(player: Player): Int { + val custom = player.vars[GeConfig.VARBIT_CUSTOM_PERCENT] + return if (custom in 1..GeConfig.MAX_CUSTOM_PERCENT) custom else GeConfig.DEFAULT_CUSTOM_PERCENT + } + + fun guidePrice(player: Player) { + val item = setupItem(player) ?: return + setPrice(player, items.guidePrice(item.id).toLong()) + } + + fun setPrice(player: Player, value: Long) { + setupSlot(player) ?: return + setupItem(player) ?: return + setVar(player, GeConfig.VARBIT_PRICE, value.coerceIn(1L, Int.MAX_VALUE.toLong()).toInt()) + } + + suspend fun enterPrice(access: ProtectedAccess) { + setupSlot(access.player) ?: return + val price = access.countDialog("Set a price for each item:") + setPrice(access.player, price.toLong()) + } + + suspend fun enterCustomPercent(access: ProtectedAccess) { + val percent = access.countDialog("Set a custom percentage (1-${GeConfig.MAX_CUSTOM_PERCENT}):") + setVar(access.player, GeConfig.VARBIT_CUSTOM_PERCENT, percent.coerceIn(1, GeConfig.MAX_CUSTOM_PERCENT)) + } + + fun confirm(access: ProtectedAccess) { + val player = access.player + val slot = setupSlot(player) ?: return + val item = setupItem(player) + if (item == null) { + player.mes("You must choose an item first.") + return + } + val type = setupType(player) + val quantity = player.vars[GeConfig.VARBIT_QUANTITY] + val price = player.vars[GeConfig.VARBIT_PRICE] + if (quantity <= 0 || price <= 0) { + player.mes("Set a quantity and a price for your offer first.") + return + } + if (price.toLong() * quantity > Int.MAX_VALUE) { + player.mes("Too much money! Your offer cannot be worth more than ${Int.MAX_VALUE.formatAmount} coins.") + return + } + if (!canTrade(player, item, type)) { + return + } + when (type) { + GeOfferType.Buy -> { + val cost = price * quantity + val paid = player.invDel(player.inv, coinsId, cost) + if (!paid.success) { + player.mes("You don't have enough coins.") + return + } + } + GeOfferType.Sell -> { + if (!removeForSale(player, item, quantity)) { + player.mes("You don't have enough of that item to sell.") + return + } + } + } + val offer = exchange.place(player.accountId, player.displayName, slot, type, item.id, price, quantity) + setVar(player, GeConfig.VARP_LAST_OFFER_ITEM, item.id) + setVar(player, GeConfig.VARP_LAST_OFFER_QUANTITY, quantity) + setVar(player, GeConfig.VARP_LAST_OFFER_PRICE, price) + setVar(player, GeConfig.VARP_LAST_OFFER_TYPE, type.clientId) + back(player) + refresh(player, slot) + if (type == GeOfferType.Buy && offer.isActive && exchange.remainingBuyLimit(player.accountId, item.id) <= 0) { + val minutes = (exchange.buyLimitResetIn(player.accountId, item.id) + 59_999) / 60_000 + player.mes("You've reached the buy limit for ${item.name}; your offer will continue in about $minutes minutes.") + } + } + + private fun maxQuantity(player: Player, item: ItemServerType): Int { + if (setupType(player) == GeOfferType.Sell) { + return max(1, available(player, item)) + } + val price = max(1, player.vars[GeConfig.VARBIT_PRICE]) + return max(1, Int.MAX_VALUE / price) + } + + /** How many of [item] the player carries, counting both the item and its banknote. */ + private fun available(player: Player, item: ItemServerType): Int { + val cert = item.certlink.takeIf { item.canCert } + var total = 0L + for (obj in player.inv.objs) { + if (obj == null) { + continue + } + if (obj.id == item.id || obj.id == cert) { + total += obj.count + } + } + return min(total, Int.MAX_VALUE.toLong()).toInt() + } + + private fun removeForSale(player: Player, item: ItemServerType, quantity: Int): Boolean { + if (available(player, item) < quantity) { + return false + } + var remaining = quantity + val unnoted = player.inv.objs.filterNotNull().filter { it.id == item.id }.sumOf { it.count.toLong() } + val takeUnnoted = min(remaining.toLong(), unnoted).toInt() + if (takeUnnoted > 0) { + val result = player.invDel(player.inv, item.id, takeUnnoted) + if (!result.success) { + return false + } + remaining -= takeUnnoted + } + if (remaining > 0) { + val result = player.invDel(player.inv, item.certlink, remaining) + if (!result.success) { + // Put the unnoted ones back rather than leave the player short. + if (takeUnnoted > 0) { + player.invAdd(player.inv, item.id, takeUnnoted) + } + return false + } + } + return true + } + + private fun canTrade(player: Player, item: ItemServerType, type: GeOfferType): Boolean { + if (item.id == coinsId || !item.stockmarket || !item.tradeable || item.isCert) { + player.mes("You can't trade that item on the Grand Exchange.") + return false + } + if (player.isSoloIronman && (type != GeOfferType.Buy || item.id != bondId)) { + player.mes("As an Ironman, you can only use the Grand Exchange to buy bonds.") + return false + } + return true + } + + /* Status panel */ + + fun abort(player: Player) { + val offer = selectedOffer(player) ?: return + if (!exchange.abort(offer)) { + player.mes("That offer has already finished.") + return + } + refresh(player, offer.slot) + } + + /** Aborts the selected offer, pockets what it holds and sets the slot up again from it. */ + fun modify(access: ProtectedAccess) { + val player = access.player + val offer = selectedOffer(player) ?: return + val type = offer.type + val remaining = offer.remaining + exchange.abort(offer) + collectBox(player, offer, GeConfig.COLLECT_BOX_ITEMS, CollectMode.Default) + collectBox(player, offer, GeConfig.COLLECT_BOX_COINS, CollectMode.Default) + refresh(player, offer.slot) + if (exchange.offer(player.accountId, offer.slot) != null) { + player.mes("Collect what the offer holds before modifying it.") + return + } + val quantity = + if (type == GeOfferType.Sell) { + min(remaining, available(player, ServerCacheManager.getItemOrDefault(offer.item))) + } else { + remaining + } + beginSetup(player, offer.slot, type, offer.item, max(1, quantity), offer.price) + } + + enum class CollectMode { + /** Left click: notes when several non-stackable items wait, the item otherwise. */ + Default, + Item, + Note, + Bank, + } + + fun collectSelected(player: Player, box: Int, mode: CollectMode) { + val offer = selectedOffer(player) ?: return + collectBox(player, offer, box, mode) + refresh(player, offer.slot) + } + + fun collectSlot(player: Player, slot: Int, box: Int, mode: CollectMode) { + val offer = exchange.offer(player.accountId, slot) ?: return + collectBox(player, offer, box, mode) + refresh(player, slot) + } + + fun examineBox(player: Player, slot: Int?, box: Int) { + val offer = (if (slot == null) selectedOffer(player) else exchange.offer(player.accountId, slot)) ?: return + val item = if (box == GeConfig.COLLECT_BOX_COINS) coinsId else offer.item + player.mes(ServerCacheManager.getItemOrDefault(item).examine) + } + + fun collectAll(player: Player, toBank: Boolean) { + val mode = if (toBank) CollectMode.Bank else CollectMode.Default + var any = false + for (slot in 0 until GeConfig.SLOT_COUNT) { + val offer = exchange.offer(player.accountId, slot) ?: continue + if (!offer.hasCollectables) { + continue + } + any = true + collectBox(player, offer, GeConfig.COLLECT_BOX_ITEMS, mode) + collectBox(player, offer, GeConfig.COLLECT_BOX_COINS, mode) + refresh(player, slot) + } + if (!any) { + player.mes("You have nothing to collect.") + } + } + + private fun collectBox(player: Player, offer: GeOffer, box: Int, mode: CollectMode) { + if (box == GeConfig.COLLECT_BOX_COINS) { + collectCoins(player, offer, mode == CollectMode.Bank) + } else { + collectItems(player, offer, mode) + } + } + + private fun collectCoins(player: Player, offer: GeOffer, toBank: Boolean) { + val count = offer.collectCoins + if (count <= 0) { + return + } + val inv = if (toBank) bank(player) else player.inv + val result = player.invAdd(inv, coinsId, count) + if (!result.success) { + player.mes(if (toBank) "Your bank is full." else "You don't have enough inventory space.") + return + } + exchange.takeCoins(offer, count) + } + + private fun collectItems(player: Player, offer: GeOffer, mode: CollectMode) { + val count = offer.collectItems + if (count <= 0) { + return + } + val item = ServerCacheManager.getItemOrDefault(offer.item) + if (mode == CollectMode.Bank) { + val result = player.invAdd(bank(player), item.id, count) + if (!result.success) { + player.mes("Your bank is full.") + return + } + exchange.takeItems(offer, count) + return + } + val asNote = + when (mode) { + CollectMode.Note -> item.canCert + CollectMode.Item -> false + else -> item.canCert && !item.isStackable && count > 1 + } + val take = + if (asNote || item.isStackable) { + count + } else { + min(count, player.inv.freeSpace()) + } + if (take <= 0) { + player.mes("You don't have enough inventory space.") + return + } + val result = player.invAdd(player.inv, item.id, take, cert = asNote) + if (!result.success) { + player.mes("You don't have enough inventory space.") + return + } + exchange.takeItems(offer, take) + } + + private fun bank(player: Player): Inventory = player.invMap.getOrPut("inv.bank") + + /* Collect-all layer */ + + fun repeatOffer(access: ProtectedAccess) { + val player = access.player + val item = player.vars[GeConfig.VARP_LAST_OFFER_ITEM] + if (item <= 0) { + player.mes("You haven't made an offer to repeat yet.") + return + } + val type = GeOfferType.fromClientId(player.vars[GeConfig.VARP_LAST_OFFER_TYPE]) + val itemType = ServerCacheManager.getItem(item) ?: return + if (!canTrade(player, itemType, type)) { + return + } + val slot = exchange.firstEmptySlot(player.accountId) + if (slot == null) { + player.mes("You have no free offer slots.") + return + } + var quantity = player.vars[GeConfig.VARP_LAST_OFFER_QUANTITY] + if (type == GeOfferType.Sell) { + quantity = min(quantity, available(player, itemType)) + if (quantity <= 0) { + player.mes("You don't have any of that item to sell.") + return + } + } + beginSetup(player, slot, type, item, max(1, quantity), max(1, player.vars[GeConfig.VARP_LAST_OFFER_PRICE])) + } + + /* History */ + + fun openHistory(access: ProtectedAccess) { + val player = access.player + access.ifOpenMainModal(GeConfig.HISTORY) + val trades = exchange.history(player.accountId) + player.runClientScript(GeConfig.CS2_HISTORY_INIT) + for ((index, trade) in trades.withIndex()) { + player.runClientScript( + GeConfig.CS2_HISTORY_ADD_LINE, + index, + trade.item, + trade.type.clientId, + trade.quantity, + trade.total, + trade.tax, + ) + } + player.runClientScript(GeConfig.CS2_HISTORY_FINISH) + if (trades.isNotEmpty()) { + player.ifSetEvents(GeConfig.COMP_HISTORY_LIST, 0 until trades.size * GeConfig.HISTORY_LINE_CHILDREN, IfEvent.Op1, IfEvent.Op10) + } + } + + /** The trade a history line child belongs to, or null. */ + fun historyTrade(player: Player, comsub: Int): GeTrade? = + exchange.history(player.accountId).getOrNull(comsub / GeConfig.HISTORY_LINE_CHILDREN) + + /** "Buy-offer" on a history line: open the exchange with a buy setup for that item. */ + fun buyAgain(access: ProtectedAccess, trade: GeTrade) { + val player = access.player + val item = ServerCacheManager.getItem(trade.item) ?: return + if (!canTrade(player, item, GeOfferType.Buy)) { + return + } + val slot = exchange.firstEmptySlot(player.accountId) + if (slot == null) { + player.mes("You have no free offer slots.") + return + } + open(access) + beginSetup(player, slot, GeOfferType.Buy, item.id, 1, items.guidePrice(item.id)) + } + + /* Prices */ + + fun guidePriceText(item: ItemServerType): String { + val base = uncert(item) + val price = items.guidePrice(base.id) + val limit = items.buyLimit(base.id) + val limitText = if (limit == GeItemData.NO_LIMIT) "" else " You can buy up to ${limit.formatAmount} every four hours." + return "${base.name}: ${price.formatAmount} coin${if (price == 1) "" else "s"}.$limitText" + } + + private fun name(item: Int): String = ServerCacheManager.getItemOrDefault(item).name + + private fun setVar(player: Player, name: String, value: Int) { + VarPlayerIntMapSetter.set(player, name, value) + } + + private companion object { + private val logger = InlineLogger() + } +} diff --git a/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GrandExchangeModule.kt b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GrandExchangeModule.kt new file mode 100644 index 000000000..b17f18eb7 --- /dev/null +++ b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GrandExchangeModule.kt @@ -0,0 +1,11 @@ +package org.rsmod.content.other.grandexchange + +import org.rsmod.plugin.module.PluginModule + +public class GrandExchangeModule : PluginModule() { + override fun bind() { + bindSingleton(GeItemData.load()) + bindInstance() + bindInstance() + } +} diff --git a/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GrandExchangeScript.kt b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GrandExchangeScript.kt new file mode 100644 index 000000000..3b8ca7447 --- /dev/null +++ b/content/other/grand-exchange/src/main/kotlin/org/rsmod/content/other/grandexchange/GrandExchangeScript.kt @@ -0,0 +1,252 @@ +package org.rsmod.content.other.grandexchange + +import dev.openrune.types.aconverted.interf.IfButtonOp +import jakarta.inject.Inject +import org.rsmod.api.game.process.GameLifecycle +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.ironman.isSoloIronman +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onEvent +import org.rsmod.api.script.onIfClose +import org.rsmod.api.script.onIfModalButton +import org.rsmod.api.script.onIfOpen +import org.rsmod.api.script.onOpContentLoc3 +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpLoc2 +import org.rsmod.api.script.onOpLoc3 +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpc3 +import org.rsmod.api.script.onOpNpc4 +import org.rsmod.api.script.onOpNpc5 +import org.rsmod.api.script.onPlayerLogin +import org.rsmod.api.script.onPlayerLogout +import org.rsmod.content.interfaces.bank.tryOpenBank +import org.rsmod.game.entity.Npc +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The Grand Exchange in north-west Varrock. + * + * Routes every interaction to [GeService]: the exchange booths and clerks open the offers + * screen, any bank booth or banker's "Collect" opens the collection box, the pricing experts + * quote guide prices, and the offer screens' buttons (slot boxes, the setup panel's quantity and + * price buttons, abort, collect, collect-all, repeat offer, history) are mapped from the cache's + * clientscripts. Offers persist across restarts in `.data/grand-exchange.json` and match against + * every other player's offers the moment they are placed, with "The Trade Parade" playing in the + * exchange and its jingle playing when an offer completes. + */ +class GrandExchangeScript @Inject constructor(private val service: GeService) : PluginScript() { + override fun ScriptContext.startup() { + // The book must be in memory before the first login, and scripts start before the + // network opens; the file is small enough that reading it here costs nothing noticeable. + service.load() + onEvent { service.tick() } + onEvent { service.save() } + onPlayerLogin { service.onLogin(player) } + onPlayerLogout { service.save() } + + // Booths, clerks and bankers. + onOpLoc1(GeConfig.LOC_BOOTH_EXCHANGE) { service.open(this) } + onOpLoc3(GeConfig.LOC_BOOTH_EXCHANGE) { openCollectionBox() } + onOpLoc2(GeConfig.LOC_BOOTH_BANK) { tryOpenBank() } + onOpLoc3(GeConfig.LOC_BOOTH_BANK) { openCollectionBox() } + onOpContentLoc3("content.bank_booth") { openCollectionBox() } + for (clerk in GeConfig.CLERKS) { + onOpNpc1(clerk) { talkToClerk(it.npc) } + onOpNpc3(clerk) { service.open(this) } + onOpNpc4(clerk) { service.openHistory(this) } + onOpNpc5(clerk) { mes("Item sets are not available on this world.") } + } + for ((npc, goods) in GeConfig.EXPERTS) { + onOpNpc1(npc) { talkToExpert(it.npc, goods) } + onOpNpc3(npc) { checkPrice(it.npc) } + } + + // Screens opening and closing. + onIfOpen(GeConfig.OFFERS) { service.onOffersOpened(player) } + onIfClose(GeConfig.OFFERS) { service.onOffersClosed(player) } + onIfOpen(GeConfig.COLLECT) { service.onCollectionBoxOpened(player) } + onIfClose(GeConfig.COLLECT) { service.onCollectionBoxClosed(player) } + + // Offer slots: view, buy, sell. + for (slot in 0 until GeConfig.SLOT_COUNT) { + onIfModalButton(GeConfig.indexSlot(slot)) { + when (it.comsub) { + GeConfig.INDEX_CHILD_VIEW -> service.viewSlot(this, slot) + GeConfig.INDEX_CHILD_BUY -> service.startBuy(this, slot) + GeConfig.INDEX_CHILD_SELL -> service.startSell(this, slot) + } + } + } + onIfModalButton(GeConfig.COMP_BACK) { service.back(player) } + onIfModalButton(GeConfig.COMP_HISTORY) { service.openHistory(this) } + onIfModalButton(GeConfig.COMP_SIDE_ITEMS) { + if (it.op == IfButtonOp.Op10) { + service.examineInventory(player, it.comsub) + } else { + service.offerFromInventory(this, it.comsub) + } + } + + // Setup panel. + onIfModalButton(GeConfig.COMP_SETUP) { setupButton(it.comsub, it.op) } + onIfModalButton(GeConfig.COMP_SETUP_CONFIRM) { service.confirm(this) } + + // Status panel. + onIfModalButton(GeConfig.COMP_DETAILS) { + when (it.comsub) { + GeConfig.DETAILS_ABORT -> service.abort(player) + GeConfig.DETAILS_MODIFY -> service.modify(this) + } + } + onIfModalButton(GeConfig.COMP_DETAILS_COLLECT) { + val box = + GeConfig.collectBoxOf( + it.comsub, + GeConfig.DETAILS_COLLECT_ITEMS_CHILD, + GeConfig.DETAILS_COLLECT_COINS_CHILD, + ) ?: return@onIfModalButton + if (it.op == IfButtonOp.Op10) { + service.examineBox(player, slot = null, box = box) + } else { + service.collectSelected(player, box, collectMode(it.op)) + } + } + + // Collect-all layer. + onIfModalButton(GeConfig.COMP_COLLECT_ALL) { + when (it.comsub) { + GeConfig.COLLECT_ALL_BUTTON -> service.collectAll(player, toBank = it.op == IfButtonOp.Op2) + GeConfig.REPEAT_OFFER_BUTTON -> service.repeatOffer(this) + } + } + + // Collection box (any bank). + for (slot in 0 until GeConfig.SLOT_COUNT) { + onIfModalButton(GeConfig.collectSlot(slot)) { + val box = + GeConfig.collectBoxOf( + it.comsub, + GeConfig.COLLECT_SLOT_ITEMS_CHILD, + GeConfig.COLLECT_SLOT_COINS_CHILD, + ) ?: return@onIfModalButton + if (it.op == IfButtonOp.Op10) { + service.examineBox(player, slot, box) + } else { + service.collectSlot(player, slot, box, collectMode(it.op)) + } + } + } + onIfModalButton(GeConfig.COMP_COLLECT_INV) { service.collectAll(player, toBank = false) } + onIfModalButton(GeConfig.COMP_COLLECT_BANK) { service.collectAll(player, toBank = true) } + + // History screen. + onIfModalButton(GeConfig.COMP_HISTORY_EXCHANGE) { service.open(this) } + onIfModalButton(GeConfig.COMP_HISTORY_LIST) { + val trade = service.historyTrade(player, it.comsub) ?: return@onIfModalButton + if (it.op == IfButtonOp.Op10) { + service.examineBox(player, slot = null, box = GeConfig.COLLECT_BOX_ITEMS) + } else { + service.buyAgain(this, trade) + } + } + } + + private suspend fun ProtectedAccess.setupButton(comsub: Int, op: IfButtonOp) { + when (comsub) { + GeConfig.SETUP_CHOOSE_ITEM -> service.chooseItem(this) + GeConfig.SETUP_QTY_MINUS_1 -> service.adjustQuantity(player, -1) + GeConfig.SETUP_QTY_PLUS_1_MOBILE, + GeConfig.SETUP_QTY_PLUS_1 -> service.adjustQuantity(player, 1) + GeConfig.SETUP_QTY_PLUS_10 -> service.adjustQuantity(player, 10) + GeConfig.SETUP_QTY_PLUS_100 -> service.adjustQuantity(player, 100) + GeConfig.SETUP_QTY_PLUS_1K_OR_ALL -> service.setAllOrThousand(player) + GeConfig.SETUP_QTY_ENTER -> service.enterQuantity(this) + GeConfig.SETUP_PRICE_MINUS_1 -> service.adjustPrice(player, -1) + GeConfig.SETUP_PRICE_PLUS_1 -> service.adjustPrice(player, 1) + GeConfig.SETUP_PRICE_MINUS_5PCT -> service.adjustPricePercent(player, -5) + GeConfig.SETUP_PRICE_GUIDE -> service.guidePrice(player) + GeConfig.SETUP_PRICE_ENTER -> service.enterPrice(this) + GeConfig.SETUP_PRICE_PLUS_5PCT -> service.adjustPricePercent(player, 5) + GeConfig.SETUP_PRICE_MINUS_CUSTOM -> + if (op == IfButtonOp.Op2) service.enterCustomPercent(this) + else service.adjustPricePercent(player, -service.customPercent(player)) + GeConfig.SETUP_PRICE_PLUS_CUSTOM -> + if (op == IfButtonOp.Op2) service.enterCustomPercent(this) + else service.adjustPricePercent(player, service.customPercent(player)) + } + } + + private fun collectMode(op: IfButtonOp): GeService.CollectMode = + when (op) { + IfButtonOp.Op1 -> GeService.CollectMode.Default + IfButtonOp.Op2 -> GeService.CollectMode.Note + IfButtonOp.Op3 -> GeService.CollectMode.Bank + else -> GeService.CollectMode.Default + } + + private fun ProtectedAccess.openCollectionBox() { + ifOpenMainModal(GeConfig.COLLECT) + } + + private suspend fun ProtectedAccess.talkToClerk(npc: Npc) { + startDialogue(npc, faceFar = true) { + chatNpc(happy, "Welcome to the Grand Exchange! Would you like to see your offers?") + if (player.isSoloIronman) { + chatNpc(neutral, "As an Ironman you can only use the Exchange to buy bonds, mind you.") + } + val choice = + choice3( + "Yes, show me my offers.", + ClerkChoice.Offers, + "Show me my trade history.", + ClerkChoice.History, + "No thanks.", + ClerkChoice.Nothing, + ) + when (choice) { + ClerkChoice.Offers -> { + chatPlayer(happy, "Yes, show me my offers.") + service.open(access) + } + ClerkChoice.History -> { + chatPlayer(neutral, "Show me my trade history.") + service.openHistory(access) + } + ClerkChoice.Nothing -> { + chatPlayer(neutral, "No thanks.") + chatNpc(happy, "Come back any time!") + } + } + } + } + + private enum class ClerkChoice { + Offers, + History, + Nothing, + } + + private suspend fun ProtectedAccess.talkToExpert(npc: Npc, goods: String) { + startDialogue(npc, faceFar = true) { + chatNpc(happy, "Hello there. I keep an eye on the going rate for $goods here at the Grand Exchange.") + val check = choice2("Can you check a price for me?", true, "No thanks.", false) + if (check) { + chatPlayer(quiz, "Can you check a price for me?") + priceCheck() + } else { + chatPlayer(neutral, "No thanks.") + } + } + } + + private suspend fun ProtectedAccess.checkPrice(npc: Npc) { + startDialogue(npc, faceFar = true) { priceCheck() } + } + + private suspend fun Dialogue.priceCheck() { + val item = access.objDialog("Which item would you like a guide price for?", stockMarketRestriction = true) + chatNpc(neutral, service.guidePriceText(item)) + } +} diff --git a/content/other/grand-exchange/src/main/resources/ge-items.tsv b/content/other/grand-exchange/src/main/resources/ge-items.tsv new file mode 100644 index 000000000..94788695d --- /dev/null +++ b/content/other/grand-exchange/src/main/resources/ge-items.tsv @@ -0,0 +1,4663 @@ +# id buy_limit guide_price name (OSRS wiki prices API + Module:GELimits) +2 11000 243 Steel cannonball +6 70 196416 Cannon base +8 70 182936 Cannon stand +10 70 166004 Cannon barrels +12 70 199851 Cannon furnace +28 15 173 Insect repellent +30 15 1250 Bucket of wax +36 40 249 Candle +39 10000 4 Bronze arrowtips +40 10000 12 Iron arrowtips +41 10000 52 Steel arrowtips +42 10000 83 Mithril arrowtips +43 10000 104 Adamant arrowtips +44 10000 210 Rune arrowtips +45 11000 39 Opal bolt tips +46 11000 6 Pearl bolt tips +47 11000 79 Barb bolttips +48 10000 6 Longbow (u) +50 10000 6 Shortbow (u) +52 7000 1 Arrow shaft +53 11000 10 Headless arrow +54 10000 7 Oak shortbow (u) +56 10000 8 Oak longbow (u) +58 10000 16 Willow longbow (u) +60 10000 6 Willow shortbow (u) +62 10000 61 Maple longbow (u) +64 10000 15 Maple shortbow (u) +66 10000 147 Yew longbow (u) +68 10000 114 Yew shortbow (u) +70 10000 756 Magic longbow (u) +72 10000 505 Magic shortbow (u) +91 10000 414 Guam potion (unf) +93 10000 418 Marrentill potion (unf) +95 10000 366 Tarromin potion (unf) +97 10000 588 Harralander potion (unf) +99 10000 6121 Ranarr potion (unf) +101 10000 1707 Irit potion (unf) +103 10000 2050 Avantoe potion (unf) +105 10000 2629 Kwuarm potion (unf) +107 10000 2526 Cadantine potion (unf) +109 10000 1714 Dwarf weed potion (unf) +111 10000 3755 Torstol potion (unf) +113 2000 106 Strength potion(4) +115 2000 71 Strength potion(3) +117 2000 42 Strength potion(2) +119 2000 25 Strength potion(1) +121 2000 10 Attack potion(3) +123 2000 10 Attack potion(2) +125 2000 6 Attack potion(1) +127 2000 25 Restore potion(3) +129 2000 21 Restore potion(2) +131 2000 1 Restore potion(1) +133 2000 355 Defence potion(3) +135 2000 177 Defence potion(2) +137 2000 98 Defence potion(1) +139 2000 6816 Prayer potion(3) +141 2000 4762 Prayer potion(2) +143 2000 2434 Prayer potion(1) +145 2000 1480 Super attack(3) +147 2000 960 Super attack(2) +149 2000 475 Super attack(1) +151 2000 20 Fishing potion(3) +153 2000 64 Fishing potion(2) +155 2000 13 Fishing potion(1) +157 2000 2378 Super strength(3) +159 2000 1450 Super strength(2) +161 2000 711 Super strength(1) +163 2000 2412 Super defence(3) +165 2000 1064 Super defence(2) +167 2000 947 Super defence(1) +169 2000 1457 Ranging potion(3) +171 2000 1087 Ranging potion(2) +173 2000 478 Ranging potion(1) +175 2000 292 Antipoison(3) +177 2000 134 Antipoison(2) +179 2000 126 Antipoison(1) +181 2000 1319 Superantipoison(3) +183 2000 608 Superantipoison(2) +185 2000 750 Superantipoison(1) +187 2000 294 Weapon poison +189 2000 421 Zamorak brew(3) +191 2000 241 Zamorak brew(2) +193 2000 226 Zamorak brew(1) +197 2000 2249 Poison chalice +199 13000 235 Grimy guam leaf +201 13000 204 Grimy marrentill +203 13000 219 Grimy tarromin +205 13000 328 Grimy harralander +207 11000 5707 Grimy ranarr weed +209 13000 1362 Grimy irit leaf +211 13000 1655 Grimy avantoe +213 13000 2151 Grimy kwuarm +215 11000 2084 Grimy cadantine +217 11000 1266 Grimy dwarf weed +219 11000 3403 Grimy torstol +221 13000 5 Eye of newt +223 13000 186 Red spiders' eggs +225 13000 230 Limpwurt root +227 13000 4 Vial of water +229 13000 2 Vial +231 13000 776 Snape grass +233 40 204 Pestle and mortar +235 13000 701 Unicorn horn dust +237 13000 574 Unicorn horn +239 13000 686 White berries +241 13000 74 Dragon scale dust +243 13000 20 Blue dragon scale +245 11000 950 Wine of Zamorak +247 13000 348 Jangerberries +249 13000 350 Guam leaf +251 13000 304 Marrentill +253 13000 380 Tarromin +255 13000 385 Harralander +257 11000 5882 Ranarr weed +259 13000 1459 Irit leaf +261 13000 1838 Avantoe +263 13000 2361 Kwuarm +265 11000 2226 Cadantine +267 11000 1365 Dwarf weed +269 11000 3527 Torstol +272 15 175 Fish food +273 15 672 Poison (item) +288 15 225 Goblin mail +299 18000 705 Mithril seeds +301 40 124 Lobster pot +303 40 174 Small fishing net +305 40 104 Big fishing net +307 40 96 Fishing rod +309 40 96 Fly fishing rod +311 40 89 Harpoon +313 8000 5 Fishing bait +314 30000 2 Feather +315 6000 24 Shrimps +317 15000 24 Raw shrimps +319 15000 40 Anchovies +321 13000 15 Raw anchovies +325 6000 17 Sardine +327 15000 25 Raw sardine +329 6000 27 Salmon +331 15000 29 Raw salmon +333 6000 20 Trout +335 15000 43 Raw trout +339 6000 22 Cod +341 15000 30 Raw cod +345 15000 51 Raw herring +347 6000 13 Herring +349 15000 112 Raw pike +351 6000 51 Pike +353 15000 12 Raw mackerel +355 6000 6 Mackerel +359 15000 41 Raw tuna +361 6000 33 Tuna +363 15000 9 Raw bass +365 6000 39 Bass +371 15000 192 Raw swordfish +373 6000 197 Swordfish +377 15000 114 Raw lobster +379 6000 98 Lobster +383 15000 716 Raw shark +385 10000 1000 Shark +389 15000 1040 Raw manta ray +391 10000 1404 Manta ray +395 15000 658 Raw sea turtle +397 10000 1045 Sea turtle +401 13000 6 Seaweed +403 13000 145 Edible seaweed +405 50 610 Casket +407 11000 27 Oyster +411 11000 30 Oyster pearl +413 11000 569 Oyster pearls +426 150 17 Priest gown (top) +428 150 17 Priest gown (bottom) +434 13000 74 Clay +436 13000 12 Copper ore +438 13000 14 Tin ore +440 13000 71 Iron ore +442 13000 69 Silver ore +444 30000 152 Gold ore +447 13000 157 Mithril ore +449 4500 554 Adamantite ore +451 4500 10121 Runite ore +453 13000 137 Coal +464 2000 448 Strange fruit +526 3000 37 Bones +528 3000 538 Burnt bones +530 3000 775 Bat bones +532 3000 276 Big bones +534 3000 1072 Babydragon bones +536 7500 3438 Dragon bones +538 125 15763 Druid's robe +540 125 11944 Druid's robe top +542 125 106 Monk's robe +544 125 351 Monk's robe top +546 125 359 Shade robe top +548 125 60 Shade robe +554 50000 5 Fire rune +555 50000 5 Water rune +556 50000 5 Air rune +557 50000 5 Earth rune +558 18000 2 Mind rune +559 18000 4 Body rune +560 25000 191 Death rune +561 18000 136 Nature rune +562 18000 105 Chaos rune +563 18000 126 Law rune +564 18000 118 Cosmic rune +565 25000 347 Blood rune +566 25000 379 Soul rune +567 10000 74 Unpowered orb +569 11000 1602 Fire orb +571 11000 1087 Water orb +573 11000 1697 Air orb +575 11000 1221 Earth orb +577 125 1149 Blue wizard robe +579 125 319 Blue wizard hat +581 125 475 Black robe +590 40 98 Tinderbox +592 13000 25 Ashes +596 40 28 Unlit torch +621 18000 460 Ship ticket +626 150 1704 Pink boots +628 150 260 Green boots +630 150 1399 Blue boots +632 150 1943 Cream boots +634 150 147 Turquoise boots +636 150 980 Pink robe top +638 150 1575 Green robe top +640 150 3140 Blue robe top +642 150 1258 Cream robe top +644 150 710 Turquoise robe top +646 150 6500 Pink robe bottoms +648 150 782 Green robe bottoms +650 150 670 Blue robe bottoms +652 150 1132 Cream robe bottoms +654 150 1350 Turquoise robe bottoms +656 150 538 Pink hat +658 150 213 Green hat +660 150 449 Blue hat +662 150 1230 Cream hat +664 150 591 Turquoise hat +751 150 31392 Gnomeball +753 600 64 Cadava berries +800 7000 4 Bronze thrownaxe +801 7000 15 Iron thrownaxe +802 7000 4240 Steel thrownaxe +803 7000 43 Mithril thrownaxe +804 11000 141 Adamant thrownaxe +805 11000 166 Rune thrownaxe +806 7000 22 Bronze dart +807 7000 7 Iron dart +808 7000 4 Steel dart +809 7000 7 Mithril dart +810 11000 28 Adamant dart +811 11000 239 Rune dart +812 7000 39 Bronze dart(p) +813 7000 35027 Iron dart (p) +814 7000 42 Steel dart(p) +815 7000 166 Mithril dart(p) +816 11000 25 Adamant dart(p) +817 11000 244 Rune dart(p) +819 13000 36 Bronze dart tip +820 13000 51 Iron dart tip +821 13000 84 Steel dart tip +822 20000 109 Mithril dart tip +823 20000 191 Adamant dart tip +824 11000 1070 Rune dart tip +825 7000 10 Bronze javelin +826 7000 3 Iron javelin +827 7000 2 Steel javelin +828 7000 4 Mithril javelin +829 11000 39 Adamant javelin +830 11000 134 Rune javelin +831 7000 751 Bronze javelin(p) +832 7000 1 Iron javelin(p) +833 7000 125047 Steel javelin(p) +834 7000 75012 Mithril javelin(p) +835 11000 37604 Adamant javelin(p) +836 7000 114 Rune javelin(p) +837 125 5132 Crossbow +839 18000 61 Longbow +841 18000 51 Shortbow +843 18000 21 Oak shortbow +845 18000 37 Oak longbow +847 18000 107 Willow longbow +849 18000 95 Willow shortbow +851 18000 270 Maple longbow +853 18000 163 Maple shortbow +855 18000 532 Yew longbow +857 18000 215 Yew shortbow +859 18000 1127 Magic longbow +861 18000 728 Magic shortbow +863 7000 36 Iron knife +864 7000 51 Bronze knife +865 7000 61 Steel knife +866 7000 180 Mithril knife +867 11000 312 Adamant knife +868 11000 113 Rune knife +869 7000 37 Black knife +870 7000 12641 Bronze knife(p) +871 7000 253 Iron knife(p) +872 7000 181 Steel knife(p) +873 7000 400 Mithril knife(p) +874 7000 277 Black knife(p) +875 11000 290 Adamant knife(p) +876 7000 392 Rune knife(p) +877 7000 2 Bronze bolts +878 11000 560 Bronze bolts (p) +879 11000 29 Opal bolts +880 11000 34 Pearl bolts +881 7000 47 Barbed bolts +882 7000 3 Bronze arrow +883 7000 69 Bronze arrow(p) +884 7000 4 Iron arrow +885 7000 2642 Iron arrow(p) +886 7000 5 Steel arrow +887 7000 75 Steel arrow(p) +888 7000 4 Mithril arrow +889 7000 34 Mithril arrow(p) +890 11000 22 Adamant arrow +891 11000 30 Adamant arrow(p) +892 11000 44 Rune arrow +893 11000 193 Rune arrow(p) +946 40 84 Knife +948 18000 7 Bear fur +950 18000 242 Silk +952 40 137 Spade +954 250 47 Rope +958 18000 10 Grey wolf fur +960 13000 137 Plank +962 4 75247 Christmas cracker +970 13000 76 Papyrus +973 13000 175 Charcoal +975 15 5 Machete +981 50 13261 Disk of Returning +983 15 205 Brass key +985 11000 9167 Tooth half of key +987 11000 9465 Loop half of key +989 11000 20095 Crystal key +991 11000 6133 Muddy key +993 11000 21797 Sinister key +1005 150 37 White apron +1007 150 79 Red cape +1009 150 248 Brass necklace +1011 150 39 Blue skirt +1013 150 1 Pink skirt +1015 150 11 Black skirt +1017 125 230 Wizard hat +1019 150 378 Black cape +1021 150 79 Blue cape +1023 150 78 Yellow cape +1025 150 200 Right eye patch +1027 150 566 Green cape +1029 150 748 Purple cape +1031 150 678 Orange cape +1033 15 2411 Zamorak monk bottom +1035 15 764 Zamorak monk top +1038 5 12711 Red partyhat +1040 5 10830 Yellow partyhat +1042 5 20622 Blue partyhat +1044 5 12976 Green partyhat +1046 5 19440 Purple partyhat +1048 5 25940 White partyhat +1050 5 27138 Santa hat +1053 5 8163 Green halloween mask +1055 5 7790 Blue halloween mask +1057 5 8599 Red halloween mask +1059 125 8 Leather gloves +1061 125 29 Leather boots +1063 125 13 Leather vambraces +1065 125 1354 Green d'hide vambraces +1067 125 52 Iron platelegs +1069 125 336 Steel platelegs +1071 125 1163 Mithril platelegs +1073 125 3496 Adamant platelegs +1075 125 265 Bronze platelegs +1077 125 1143 Black platelegs +1079 70 37599 Rune platelegs +1081 125 12 Iron plateskirt +1083 125 344 Steel plateskirt +1085 125 1332 Mithril plateskirt +1087 125 45 Bronze plateskirt +1089 125 2034 Black plateskirt +1091 125 3576 Adamant plateskirt +1093 70 37900 Rune plateskirt +1095 125 13 Leather chaps +1097 125 280 Studded chaps +1099 125 2099 Green d'hide chaps +1101 125 175 Iron chainbody +1103 125 26 Bronze chainbody +1105 125 200 Steel chainbody +1107 125 3101 Black chainbody +1109 125 973 Mithril chainbody +1111 125 2517 Adamant chainbody +1113 70 29578 Rune chainbody +1115 125 60 Iron platebody +1117 125 61 Bronze platebody +1119 125 1028 Steel platebody +1121 125 2890 Mithril platebody +1123 125 9264 Adamant platebody +1125 125 2227 Black platebody +1127 70 38164 Rune platebody +1129 125 5 Leather body +1131 125 15 Hardleather body +1133 125 210 Studded body +1135 125 4403 Green d'hide body +1137 125 19 Iron med helm +1139 125 110 Bronze med helm +1141 125 77 Steel med helm +1143 125 275 Mithril med helm +1145 125 854 Adamant med helm +1147 70 10898 Rune med helm +1149 8 59666 Dragon med helm +1151 125 2155 Black med helm +1153 125 30 Iron full helm +1155 125 92 Bronze full helm +1157 125 143 Steel full helm +1159 125 536 Mithril full helm +1161 125 1696 Adamant full helm +1163 70 20474 Rune full helm +1165 125 789 Black full helm +1167 125 5 Leather cowl +1169 125 40 Coif +1171 125 230 Wooden shield +1173 125 36 Bronze sq shield +1175 125 35 Iron sq shield +1177 125 113 Steel sq shield +1179 125 261 Black sq shield +1181 125 526 Mithril sq shield +1183 125 1911 Adamant sq shield +1185 70 22127 Rune sq shield +1187 70 301021 Dragon sq shield +1189 125 129 Bronze kiteshield +1191 125 60 Iron kiteshield +1193 125 205 Steel kiteshield +1195 125 447 Black kiteshield +1197 125 1047 Mithril kiteshield +1199 125 2958 Adamant kiteshield +1201 70 31843 Rune kiteshield +1203 125 39 Iron dagger +1205 125 101 Bronze dagger +1207 125 54 Steel dagger +1209 125 126 Mithril dagger +1211 125 178 Adamant dagger +1213 70 4299 Rune dagger +1215 70 17148 Dragon dagger +1217 125 337 Black dagger +1219 125 1 Iron dagger(p) +1221 125 268 Bronze dagger(p) +1223 125 344 Steel dagger(p) +1225 125 20 Mithril dagger(p) +1227 125 209 Adamant dagger(p) +1229 70 5152 Rune dagger(p) +1231 70 18090 Dragon dagger(p) +1233 125 91 Black dagger(p) +1237 125 104 Bronze spear +1239 125 469 Iron spear +1241 125 5178 Steel spear +1243 125 188 Mithril spear +1245 125 635 Adamant spear +1247 70 11503 Rune spear +1249 70 42180 Dragon spear +1251 125 1088 Bronze spear(p) +1253 125 277 Iron spear(p) +1255 125 28 Steel spear(p) +1257 125 119 Mithril spear(p) +1259 125 730 Adamant spear(p) +1261 70 12680 Rune spear(p) +1263 70 39312 Dragon spear(p) +1265 40 82 Bronze pickaxe +1267 40 53 Iron pickaxe +1269 40 157 Steel pickaxe +1271 40 1587 Adamant pickaxe +1273 40 492 Mithril pickaxe +1275 40 18326 Rune pickaxe +1277 125 54 Bronze sword +1279 125 32 Iron sword +1281 125 19 Steel sword +1283 125 685 Black sword +1285 125 217 Mithril sword +1287 125 976 Adamant sword +1289 70 11704 Rune sword +1291 125 63 Bronze longsword +1293 125 33 Iron longsword +1295 125 117 Steel longsword +1297 125 301 Black longsword +1299 125 354 Mithril longsword +1301 125 1501 Adamant longsword +1303 70 18417 Rune longsword +1305 70 59279 Dragon longsword +1307 125 547 Bronze 2h sword +1309 125 79 Iron 2h sword +1311 125 341 Steel 2h sword +1313 125 1033 Black 2h sword +1315 125 1338 Mithril 2h sword +1317 125 3364 Adamant 2h sword +1319 70 37738 Rune 2h sword +1321 125 87 Bronze scimitar +1323 125 33 Iron scimitar +1325 125 10 Steel scimitar +1327 125 1414 Black scimitar +1329 125 336 Mithril scimitar +1331 125 1435 Adamant scimitar +1333 70 14978 Rune scimitar +1335 125 36 Iron warhammer +1337 125 30 Bronze warhammer +1339 125 115 Steel warhammer +1341 125 830 Black warhammer +1343 125 802 Mithril warhammer +1345 125 2385 Adamant warhammer +1347 70 24235 Rune warhammer +1349 40 197 Iron axe +1351 40 91 Bronze axe +1353 40 173 Steel axe +1355 40 123 Mithril axe +1357 40 347 Adamant axe +1359 40 7087 Rune axe +1361 40 1045 Black axe +1363 125 30 Iron battleaxe +1365 125 141 Steel battleaxe +1367 125 571 Black battleaxe +1369 125 861 Mithril battleaxe +1371 125 2546 Adamant battleaxe +1373 70 24141 Rune battleaxe +1375 125 69 Bronze battleaxe +1377 70 119315 Dragon battleaxe +1379 125 27 Staff +1381 125 1391 Staff of air +1383 125 842 Staff of water +1385 125 950 Staff of earth +1387 125 902 Staff of fire +1389 125 365 Magic staff +1391 11000 7776 Battlestaff +1393 18000 8874 Fire battlestaff +1395 18000 8799 Water battlestaff +1397 18000 8737 Air battlestaff +1399 18000 8805 Earth battlestaff +1401 18000 25241 Mystic fire staff +1403 18000 25425 Mystic water staff +1405 18000 25174 Mystic air staff +1407 18000 25248 Mystic earth staff +1420 125 25 Iron mace +1422 125 59 Bronze mace +1424 125 14 Steel mace +1426 125 801 Black mace +1428 125 96 Mithril mace +1430 125 625 Adamant mace +1432 70 8340 Rune mace +1434 70 29120 Dragon mace +1436 20000 12 Rune essence +1438 11000 140 Air talisman +1440 11000 622 Earth talisman +1442 11000 2589 Fire talisman +1444 11000 276 Water talisman +1446 11000 1420 Body talisman +1448 11000 1415 Mind talisman +1450 0 140145 Blood talisman +1452 11000 225 Chaos talisman +1454 11000 256 Cosmic talisman +1456 11000 2350 Death talisman +1462 11000 181 Nature talisman +1464 18000 22 Archery ticket +1470 11000 1582 Red bead +1472 11000 1036 Yellow bead +1474 11000 1121 Black bead +1476 11000 1037 White bead +1478 5 2015 Amulet of accuracy +1511 15000 23 Logs +1513 12000 730 Magic logs +1515 12000 106 Yew logs +1517 15000 11 Maple logs +1519 15000 22 Willow logs +1521 15000 38 Oak logs +1523 250 229 Lockpick +1539 13000 39 Steel nails +1540 125 161 Anti-dragon shield +1550 11000 19 Garlic +1552 15 500 Seasoned sardine +1573 15 130 Doogle leaves +1592 40 163 Ring mould +1595 40 210 Amulet mould +1597 40 450 Necklace mould +1599 40 11 Holy mould +1601 11000 1628 Diamond +1603 13000 719 Ruby +1605 13000 296 Emerald +1607 13000 176 Sapphire +1609 13000 227 Opal +1611 13000 327 Jade +1613 11000 1848 Red topaz +1615 11000 10715 Dragonstone +1617 10000 2431 Uncut diamond +1619 10000 1021 Uncut ruby +1621 10000 397 Uncut emerald +1623 10000 215 Uncut sapphire +1625 10000 77 Uncut opal +1627 10000 113 Uncut jade +1629 10000 1427 Uncut red topaz +1631 10000 14799 Uncut dragonstone +1635 18000 135 Gold ring +1637 10000 443 Sapphire ring +1639 10000 628 Emerald ring +1641 10000 1014 Ruby ring +1643 10000 1924 Diamond ring +1645 10000 10403 Dragonstone ring +1654 18000 158 Gold necklace +1656 18000 553 Sapphire necklace +1658 18000 707 Emerald necklace +1660 18000 1129 Ruby necklace +1662 18000 2017 Diamond necklace +1664 10000 11132 Dragon necklace +1673 18000 145 Gold amulet (u) +1675 10000 395 Sapphire amulet (u) +1677 10000 600 Emerald amulet (u) +1679 10000 1041 Ruby amulet (u) +1681 10000 1935 Diamond amulet (u) +1683 10000 11019 Dragonstone amulet (u) +1692 18000 136 Gold amulet +1694 10000 449 Sapphire amulet +1696 10000 650 Emerald amulet +1698 10000 1161 Ruby amulet +1700 10000 2034 Diamond amulet +1702 10000 12249 Dragonstone amulet +1704 10000 11922 Amulet of glory +1712 10000 12477 Amulet of glory(4) +1714 10000 50 Unstrung symbol +1716 10000 46 Unblessed symbol +1718 125 249 Holy symbol +1720 10000 100 Unstrung emblem +1722 10000 226 Unpowered symbol +1724 125 425 Unholy symbol +1725 125 1068 Amulet of strength +1727 125 665 Amulet of magic +1729 125 655 Amulet of defence +1731 125 1875 Amulet of power +1733 40 102 Needle +1734 18000 6 Thread +1735 40 96 Shears +1737 13000 16 Wool +1739 13000 130 Cowhide +1741 13000 191 Leather +1743 13000 187 Hard leather +1745 13000 1830 Green dragon leather +1747 11000 3761 Black dragonhide +1749 11000 2263 Red dragonhide +1751 13000 1851 Blue dragonhide +1753 13000 1522 Green dragonhide +1755 40 88 Chisel +1757 40 37 Brown apron +1759 13000 51 Ball of wool +1761 13000 132 Soft clay +1763 150 1000 Red dye +1765 150 270 Yellow dye +1767 150 305 Blue dye +1769 150 709 Orange dye +1771 150 195 Green dye +1773 150 441 Purple dye +1775 13000 84 Molten glass +1777 13000 231 Bow string +1779 13000 2 Flax +1781 13000 21 Soda ash +1783 13000 7 Bucket of sand +1785 40 55 Glassblowing pipe +1787 10000 61 Unfired pot +1789 10000 100 Unfired pie dish +1791 10000 375 Unfired bowl +1793 11000 80 Woad leaf +1794 15 120 Bronze wire +1823 2000 78 Waterskin(4) +1831 10000 2 Waterskin(0) +1833 150 258 Desert shirt +1835 150 194 Desert robe +1837 150 139 Desert boots +1854 18000 26 Shantay pass (item) +1859 11000 956 Raw ugthanki meat +1861 11000 4185 Ugthanki meat +1865 11000 74 Pitta bread +1869 11000 334 Chopped tomato +1871 11000 127 Chopped onion +1873 11000 6528 Chopped ugthanki +1875 11000 10 Onion & tomato +1877 11000 200 Ugthanki & onion +1879 11000 25006 Ugthanki & tomato +1881 11000 2068 Kebab mix +1885 10000 744 Ugthanki kebab +1887 40 53 Cake tin +1891 6000 123 Cake +1897 6000 146 Chocolate cake +1905 2000 126 Asgarnian ale +1907 2000 219 Wizard's mind bomb +1909 2000 275 Greenman's ale +1911 2000 623 Dragon bitter +1913 2000 149 Dwarven stout +1915 2000 1608 Grog +1917 2000 82 Beer +1919 10000 26 Beer glass +1921 13000 35 Bowl of water +1923 13000 21 Bowl +1925 13000 8 Bucket +1927 13000 38 Bucket of milk +1929 13000 30 Bucket of water +1931 13000 11 Pot +1933 13000 88 Pot of flour +1935 13000 2 Jug +1937 13000 14 Jug of water +1939 13000 3 Swamp tar +1941 13000 17 Swamp paste +1942 13000 15 Potato +1944 13000 22 Egg +1947 13000 10 Grain +1949 150 319 Chef's hat +1951 13000 53 Redberries +1953 13000 137 Pastry dough +1955 13000 35 Cooking apple +1957 13000 24 Onion +1959 50 9108 Pumpkin +1961 50 5276 Easter egg +1963 13000 63 Banana +1965 6000 25 Cabbage +1969 50 375 Spinach roll +1971 6000 68 Kebab +1973 13000 28 Chocolate bar +1975 13000 56 Chocolate dust +1978 2000 62 Cup of tea +1980 13000 4 Empty cup +1982 13000 21 Tomato +1985 13000 58 Cheese +1987 20000 69 Grapes +1989 50 12483 Half full wine jug +1993 6000 3 Jug of wine +2003 10000 129 Stew +2007 11000 28 Spice +2011 10000 420 Curry +2015 13000 75 Vodka +2017 13000 151 Whisky +2019 13000 189 Gin +2021 13000 138 Brandy +2023 15 161 Cocktail guide +2025 40 50 Cocktail shaker +2026 13000 89 Cocktail glass +2028 2000 698 Premade blurb' sp. +2030 2000 1060 Premade choc s'dy +2032 2000 49 Premade dr' dragon +2034 2000 52 Premade fr' blast +2036 2000 1017 Premade p' punch +2038 2000 70 Premade sgg +2040 2000 42 Premade wiz blz'd +2048 2000 4889 Pineapple punch +2054 2000 7249 Wizard blizzard +2064 2000 2450 Blurberry special +2074 2000 2593 Choc saturday +2080 2000 3725 Short green guy +2084 2000 1925 Fruit blast +2092 2000 1877 Drunk dragon +2102 13000 135 Lemon +2104 13000 28 Lemon chunks +2106 13000 115 Lemon slices +2108 13000 226 Orange +2110 13000 196 Orange chunks +2112 13000 178 Orange slices +2114 13000 168 Pineapple +2116 13000 1165 Pineapple chunks +2118 13000 96 Pineapple ring +2120 13000 120 Lime +2122 13000 208 Lime chunks +2124 13000 257 Lime slices +2126 13000 32 Dwellberries +2128 13000 195 Equa leaves +2130 13000 27 Pot of cream +2132 13000 45 Raw beef +2134 13000 55 Raw rat meat +2136 13000 284 Raw bear meat +2138 13000 25 Raw chicken +2140 6000 81 Cooked chicken +2142 6000 70 Cooked meat +2150 13000 225 Swamp toad (item) +2152 13000 301 Toad's legs +2162 13000 482 King worm +2164 40 155 Batta tin +2165 40 150 Crunchy tray +2166 40 208 Gnomebowl mould +2167 15 114 Gianne's cook book +2169 13000 200 Gnome spice +2171 13000 13 Gianne dough +2185 6000 541 Chocolate bomb +2187 6000 7408 Tangled toad's legs +2191 6000 9250 Worm hole +2195 6000 6780 Veg ball +2203 0 1 Rock-climbing boots +2205 6000 3331 Worm crunchies +2209 6000 839 Chocchip crunchies +2213 6000 6105 Spicy crunchies +2217 6000 3344 Toad crunchies +2219 6000 25038 Premade w'm batta +2221 6000 2522 Premade t'd batta +2223 6000 25175 Premade c+t batta +2225 6000 3535 Premade fr't batta +2227 6000 25053 Premade veg batta +2229 6000 10175 Premade choc bomb +2231 6000 479 Premade ttl +2233 6000 2533 Premade worm hole +2235 6000 25030 Premade veg ball +2237 6000 25051 Premade w'm crun' +2239 6000 3906 Premade ch' crunch +2241 6000 4750 Premade s'y crunch +2243 6000 446 Premade t'd crunch +2253 6000 11595 Worm batta +2255 6000 1471 Toad batta +2259 6000 1147 Cheese+tom batta +2277 6000 5068 Fruit batta +2281 6000 5202 Vegetable batta +2283 11000 123 Pizza base +2289 10000 449 Plain pizza +2293 10000 255 Meat pizza +2297 10000 467 Anchovy pizza +2301 10000 819 Pineapple pizza +2307 13000 132 Bread dough +2309 6000 92 Bread +2313 500 34 Pie dish +2315 13000 290 Pie shell +2317 13000 432 Uncooked apple pie +2319 13000 400 Uncooked meat pie +2321 13000 368 Uncooked berry pie +2323 10000 79 Apple pie +2325 15 368 Redberry pie +2327 10000 43 Meat pie +2337 15 2195 Raw oomlie +2341 15 5050 Wrapped oomlie +2343 15 1404 Cooked oomlie wrap +2347 40 104 Hammer +2349 10000 57 Bronze bar +2351 10000 170 Iron bar +2353 10000 569 Steel bar +2355 10000 108 Silver bar +2357 10000 65 Gold bar +2359 10000 966 Mithril bar +2361 10000 1876 Adamantite bar +2363 10000 12244 Runite bar +2366 50 65247 Shield left half +2368 50 782499 Shield right half +2370 13000 477 Steel studs +2428 2000 15 Attack potion(4) +2430 2000 50 Restore potion(4) +2432 2000 600 Defence potion(4) +2434 2000 9348 Prayer potion(4) +2436 2000 2141 Super attack(4) +2438 2000 37 Fishing potion(4) +2440 2000 3154 Super strength(4) +2442 2000 3313 Super defence(4) +2444 2000 2005 Ranging potion(4) +2446 2000 423 Antipoison(4) +2448 2000 1612 Superantipoison(4) +2450 2000 579 Zamorak brew(4) +2452 2000 880 Antifire potion(4) +2454 2000 677 Antifire potion(3) +2456 2000 275 Antifire potion(2) +2458 2000 146 Antifire potion(1) +2460 150 316 Assorted flowers +2462 150 82 Red flowers +2464 150 57 Blue flowers +2466 150 130 Yellow flowers +2468 150 232 Purple flowers +2470 150 150 Orange flowers +2472 150 150 Mixed flowers +2474 150 102455 White flowers +2476 150 34426 Black flowers +2481 11000 1280 Lantadyme +2483 10000 1548 Lantadyme potion (unf) +2485 11000 1160 Grimy lantadyme +2487 125 1292 Blue d'hide vambraces +2489 70 1588 Red d'hide vambraces +2491 70 2482 Black d'hide vambraces +2493 125 1982 Blue d'hide chaps +2495 70 2565 Red d'hide chaps +2497 70 3396 Black d'hide chaps +2499 125 5005 Blue d'hide body +2501 70 5647 Red d'hide body +2503 70 7307 Black d'hide body +2505 13000 2103 Blue dragon leather +2507 11000 2609 Red dragon leather +2509 11000 4125 Black dragon leather +2520 150 2881 Brown toy horsey +2522 150 3629 White toy horsey +2524 150 3150 Black toy horsey +2526 150 301 Grey toy horsey +2550 15000 1218 Ring of recoil +2552 15000 883 Ring of dueling(8) +2568 10000 1037 Ring of forging +2570 10000 1761 Ring of life +2572 10000 10826 Ring of wealth +2577 8 35170019 Ranger boots +2579 8 122722 Wizard boots +2581 8 5738000 Robin hood hat +2583 8 11364 Black platebody (t) +2585 8 6042 Black platelegs (t) +2587 8 12919 Black full helm (t) +2589 8 7283 Black kiteshield (t) +2591 8 108148 Black platebody (g) +2593 8 21749 Black platelegs (g) +2595 8 62343 Black full helm (g) +2597 8 98870 Black kiteshield (g) +2599 8 9450 Adamant platebody (t) +2601 8 3647 Adamant platelegs (t) +2603 8 6583 Adamant kiteshield (t) +2605 8 6925 Adamant full helm (t) +2607 8 18357 Adamant platebody (g) +2609 8 7355 Adamant platelegs (g) +2611 8 14102 Adamant kiteshield (g) +2613 8 10368 Adamant full helm (g) +2615 8 43202 Rune platebody (g) +2617 8 41535 Rune platelegs (g) +2619 8 76374 Rune full helm (g) +2621 8 65676 Rune kiteshield (g) +2623 8 38837 Rune platebody (t) +2625 8 37304 Rune platelegs (t) +2627 8 21568 Rune full helm (t) +2629 8 32596 Rune kiteshield (t) +2631 4 56487 Highwayman mask +2633 4 851 Blue beret +2635 4 26242 Black beret +2637 4 19354 White beret +2639 4 2870 Tan cavalier +2641 4 2132 Dark cavalier +2643 4 31571 Black cavalier +2645 4 1156 Red headband +2647 4 4224 Black headband +2649 4 373 Brown headband +2651 4 25298 Pirate's hat +2653 8 38942 Zamorak platebody +2655 8 39550 Zamorak platelegs +2657 8 420624 Zamorak full helm +2659 8 34235 Zamorak kiteshield +2661 8 56119 Saradomin platebody +2663 8 41832 Saradomin platelegs +2665 8 72500 Saradomin full helm +2667 8 50000 Saradomin kiteshield +2669 8 46825 Guthix platebody +2671 8 42101 Guthix platelegs +2673 8 24606 Guthix full helm +2675 8 33147 Guthix kiteshield +2859 13000 480 Wolf bones +2861 7000 123 Wolfbone arrowtips +2862 13000 631 Achey tree logs +2864 7000 108 Ogre arrow shaft +2865 7000 138 Flighted ogre arrow +2866 7000 179 Ogre arrow +2876 13000 1330 Raw chompy +2878 6000 1339 Cooked chompy +2890 70 7594 Elemental shield +2894 150 1225 Grey boots +2896 150 626 Grey robe top +2898 150 519 Grey robe bottoms +2900 150 739 Grey hat +2902 150 1994 Grey gloves +2904 150 1300 Red boots +2906 150 1941 Red robe top +2908 150 6399 Red robe bottoms +2910 150 2800 Red hat +2912 150 679 Red gloves +2914 150 2149 Yellow boots +2916 150 500 Yellow robe top +2918 150 402 Yellow robe bottoms +2920 150 1535 Yellow hat +2922 150 909 Yellow gloves +2924 150 1907 Teal boots +2926 150 662 Teal robe top +2928 150 673 Teal robe bottoms +2930 150 1825 Teal hat +2932 150 517 Teal gloves +2934 150 1589 Purple boots +2936 150 412 Purple robe top +2938 150 496 Purple robe bottoms +2940 150 2427 Purple hat +2942 150 934 Purple gloves +2955 2000 214 Moonlight mead +2961 15 215 Silver sickle +2970 13000 193 Mort myre fungus +2972 13000 674 Mort myre stem +2974 13000 8200 Mort myre pear +2976 40 51 Sickle mould +2997 4 957169 Pirate's hook +2998 13000 2191 Toadflax +3000 11000 7545 Snapdragon +3002 10000 2471 Toadflax potion (unf) +3004 10000 8001 Snapdragon potion (unf) +3008 2000 178 Energy potion(4) +3010 2000 126 Energy potion(3) +3012 2000 85 Energy potion(2) +3014 2000 42 Energy potion(1) +3016 2000 2621 Super energy(4) +3018 2000 1836 Super energy(3) +3020 2000 1040 Super energy(2) +3022 2000 535 Super energy(1) +3024 2000 10184 Super restore(4) +3026 2000 7482 Super restore(3) +3028 2000 5038 Super restore(2) +3030 2000 2513 Super restore(1) +3032 2000 686 Agility potion(4) +3034 2000 1103 Agility potion(3) +3036 2000 121 Agility potion(2) +3038 2000 53 Agility potion(1) +3040 2000 138 Magic potion(4) +3042 2000 78 Magic potion(3) +3044 2000 74 Magic potion(2) +3046 2000 32 Magic potion(1) +3049 13000 1976 Grimy toadflax +3051 11000 7520 Grimy snapdragon +3053 8 9210 Lava battlestaff +3054 8 26845 Mystic lava staff +3093 7000 2420 Black dart +3094 7000 50049 Black dart(p) +3095 125 25151 Bronze claws +3096 125 155 Iron claws +3097 125 368 Steel claws +3098 125 3027 Black claws +3099 125 1544 Mithril claws +3100 125 782 Adamant claws +3101 70 6501 Rune claws +3105 200 789 Climbing boots +3107 15 4454 Spiked boots +3122 70 35631 Granite shield +3123 7500 4872 Shaikahan bones +3125 3000 941 Jogre bones +3138 11000 63 Potato cactus +3140 70 187049 Dragon chainbody +3142 13000 267 Raw karambwan +3144 10000 417 Cooked karambwan +3157 15 3470 Karambwan vessel +3159 15 3356 Karambwan vessel (baited) +3162 15 199 Sliced banana +3183 3000 254 Monkey bones +3188 40 505 Cleaning cloth +3190 125 436 Bronze halberd +3192 125 5 Iron halberd +3194 125 2438 Steel halberd +3196 125 4687 Black halberd +3198 125 53386 Mithril halberd +3200 125 3685 Adamant halberd +3202 70 37406 Rune halberd +3204 70 151360 Dragon halberd +3211 13000 161 Limestone +3216 15 353 Barrel +3226 13000 104 Raw rabbit +3228 6000 650 Cooked rabbit +3239 13000 62 Bark +3325 11000 1155 Vampyre dust +3327 150 370 Myre snelm +3329 150 13004 Blood'n'tar snelm +3331 150 53 Ochre snelm +3333 150 999 Bruise blue snelm +3335 150 96 Broken bark snelm +3337 150 41 Pointed myre snelm +3339 150 1100 Pointed blood'n'tar snelm +3341 150 44 Pointed ochre snelm +3343 150 2693 Pointed bruise blue snelm +3345 13000 197 Blamish myre shell (round) +3347 13000 7000 Blamish red shell (round) +3349 13000 234 Blamish ochre shell (round) +3351 13000 22450 Blamish blue shell (round) +3353 13000 1620 Blamish bark shell +3355 13000 208 Blamish myre shell (pointed) +3357 13000 708 Blamish red shell (pointed) +3359 13000 165 Blamish ochre shell (pointed) +3361 13000 5749 Blamish blue shell (pointed) +3363 13000 1141 Thin snail +3365 13000 2467 Lean snail +3367 13000 1643 Fat snail +3369 6000 1416 Thin snail meat +3371 6000 1665 Lean snail meat +3373 6000 1450 Fat snail meat +3379 13000 68 Raw slimy eel +3381 6000 23 Cooked slimy eel +3385 70 5305 Splitbark helm +3387 70 29525 Splitbark body +3389 70 23126 Splitbark legs +3391 70 2635 Splitbark gauntlets +3393 70 10450 Splitbark boots +3396 7500 208 Loar remains +3398 7500 4612 Phrin remains +3400 7500 797 Riyl remains +3402 7500 2290 Asyn remains +3404 7500 6047 Fiyr remains +3406 10000 295 Unfinished potion +3408 2000 42 Serum 207 (4) +3410 2000 16 Serum 207 (3) +3412 2000 4 Serum 207 (2) +3414 2000 81 Serum 207 (1) +3420 10000 168 Limestone brick +3422 10000 56 Olive oil(4) +3424 10000 30 Olive oil(3) +3426 10000 27 Olive oil(2) +3428 10000 31 Olive oil(1) +3430 2000 3445 Sacred oil(4) +3432 2000 2685 Sacred oil(3) +3434 2000 1418 Sacred oil(2) +3436 2000 839 Sacred oil(1) +3438 11000 3405 Pyre logs +3440 11000 3179 Oak pyre logs +3442 11000 2556 Willow pyre logs +3444 11000 4590 Maple pyre logs +3446 11000 4063 Yew pyre logs +3448 11000 4623 Magic pyre logs +3470 11000 7277 Fine cloth +3472 8 1618 Black plateskirt (t) +3473 8 4706 Black plateskirt (g) +3474 8 4205 Adamant plateskirt (t) +3475 8 3776 Adamant plateskirt (g) +3476 8 38424 Rune plateskirt (g) +3477 8 43500 Rune plateskirt (t) +3478 8 37497 Zamorak plateskirt +3479 8 45443 Saradomin plateskirt +3480 8 39313 Guthix plateskirt +3481 8 9096661 Gilded platebody +3483 8 6297559 Gilded platelegs +3485 8 767116 Gilded plateskirt +3486 8 7592481 Gilded full helm +3488 8 6663750 Gilded kiteshield +3678 40 5095 Flamtaer hammer +3749 70 40527 Archer helm +3751 70 40258 Berserker helm +3753 70 39201 Warrior helm +3755 70 40615 Farseer helm +3759 150 2215 Fremennik cyan cloak +3761 150 17 Fremennik brown cloak +3763 150 472 Fremennik blue cloak +3765 150 198282 Fremennik green cloak +3767 150 5035 Fremennik brown shirt +3769 150 9613 Fremennik grey shirt +3771 150 10235 Fremennik beige shirt +3773 150 325 Fremennik red shirt +3775 150 11985 Fremennik blue shirt +3777 150 5905 Fremennik red cloak +3779 150 1000 Fremennik grey cloak +3781 150 581 Fremennik yellow cloak +3783 150 25340 Fremennik teal cloak +3785 150 507 Fremennik purple cloak +3787 150 1056 Fremennik pink cloak +3789 150 1677 Fremennik black cloak +3791 150 33 Fremennik boots +3793 150 366 Fremennik robe +3795 150 758 Fremennik skirt +3797 150 3665 Fremennik hat +3799 150 216 Fremennik gloves +3801 2000 554 Keg of beer +3803 2000 356 Beer tankard +3827 5 1013 Saradomin page 1 +3828 5 813 Saradomin page 2 +3829 5 921 Saradomin page 3 +3830 5 755 Saradomin page 4 +3831 5 2894 Zamorak page 1 +3832 5 3569 Zamorak page 2 +3833 5 3454 Zamorak page 3 +3834 5 6473 Zamorak page 4 +3835 5 738 Guthix page 1 +3836 5 703 Guthix page 2 +3837 5 740 Guthix page 3 +3838 5 989 Guthix page 4 +3853 10000 1010 Games necklace(8) +4012 6000 131 Monkey nuts +4014 6000 1250 Monkey bar +4016 6000 2272 Banana stew +4087 70 163331 Dragon platelegs +4089 125 11099 Mystic hat +4091 125 71220 Mystic robe top +4093 125 47180 Mystic robe bottom +4095 125 5659 Mystic gloves +4097 125 5649 Mystic boots +4099 70 40089 Mystic hat (dark) +4101 70 71804 Mystic robe top (dark) +4103 70 48693 Mystic robe bottom (dark) +4105 70 5252 Mystic gloves (dark) +4107 70 38108 Mystic boots (dark) +4109 70 8900 Mystic hat (light) +4111 70 71298 Mystic robe top (light) +4113 70 47233 Mystic robe bottom (light) +4115 70 5310 Mystic gloves (light) +4117 70 7822 Mystic boots (light) +4119 125 160 Bronze boots +4121 125 6240 Iron boots +4123 125 1308 Steel boots +4125 125 346 Black boots +4127 125 999 Mithril boots +4129 125 746 Adamant boots +4131 70 7113 Rune boots +4151 70 803441 Abyssal whip +4153 70 90812 Granite maul +4156 40 4907 Mirror shield +4161 8000 26 Bag of salt +4162 40 26 Rock hammer +4164 70 186 Facemask +4166 70 815 Earmuffs +4168 70 319 Nose peg +4170 70 23483 Slayer's staff +4207 70 334919 Crystal weapon seed +4298 15 2 Ham shirt +4300 15 1879 Ham robe +4302 15 344 Ham hood +4304 15 2027 Ham cloak +4306 15 2111 Ham logo +4308 15 1832 Ham gloves +4310 15 2685 Ham boots +4315 150 1058 Team-1 cape +4317 150 505 Team-2 cape +4319 150 150 Team-3 cape +4321 150 342 Team-4 cape +4323 150 240 Team-5 cape +4325 150 148 Team-6 cape +4327 150 229 Team-7 cape +4329 150 505 Team-8 cape +4331 150 54 Team-9 cape +4333 150 208 Team-10 cape +4335 150 673 Team-11 cape +4337 150 341 Team-12 cape +4339 150 135 Team-13 cape +4341 150 676 Team-14 cape +4343 150 979 Team-15 cape +4345 150 75 Team-16 cape +4347 150 275 Team-17 cape +4349 150 555 Team-18 cape +4351 150 169 Team-19 cape +4353 150 144 Team-20 cape +4355 150 278 Team-21 cape +4357 150 206 Team-22 cape +4359 150 381 Team-23 cape +4361 150 6238 Team-24 cape +4363 150 400 Team-25 cape +4365 150 209 Team-26 cape +4367 150 555 Team-27 cape +4369 150 253 Team-28 cape +4371 150 131 Team-29 cape +4373 150 647 Team-30 cape +4375 150 435 Team-31 cape +4377 150 786 Team-32 cape +4379 150 261 Team-33 cape +4381 150 932 Team-34 cape +4383 150 325 Team-35 cape +4385 150 49 Team-36 cape +4387 150 231 Team-37 cape +4389 150 773 Team-38 cape +4391 150 153 Team-39 cape +4393 150 184 Team-40 cape +4395 150 550 Team-41 cape +4397 150 719 Team-42 cape +4399 150 582 Team-43 cape +4401 150 390 Team-44 cape +4403 150 600 Team-45 cape +4405 150 140 Team-46 cape +4407 150 270 Team-47 cape +4409 150 258 Team-48 cape +4411 150 561 Team-49 cape +4413 150 175 Team-50 cape +4417 2000 2819 Guthix rest(4) +4419 2000 2004 Guthix rest(3) +4421 2000 1354 Guthix rest(2) +4423 2000 718 Guthix rest(1) +4436 15 2089 Airtight pot +4438 100 5051 Unfired pot lid +4440 15 743 Pot lid +4456 13000 147 Bowl of hot water +4458 13000 42 Cup of water +4460 13000 427 Cup of hot water +4517 6000 6426 Giant frog legs +4522 40 970 Oil lamp +4525 10000 14 Empty oil lamp +4527 10000 9 Empty candle lantern +4529 40 236 Candle lantern (white) +4532 40 6869 Candle lantern (black) +4535 10000 4012 Empty oil lantern +4537 40 25 Oil lantern +4540 10000 5 Oil lantern frame +4542 10000 4 Lantern lens +4544 10000 508 Bullseye lantern (unf) +4546 10000 421 Bullseye lantern (empty) +4548 10000 1588 Bullseye lantern +4551 125 813 Spiny helmet +4580 8 3991 Black spear +4582 8 8515 Black spear(p) +4585 70 161060 Dragon plateskirt +4587 70 59318 Dragon scimitar +4591 15 232 Kharidian headpiece +4593 15 366 Fake beard +4595 15 1 Karidian disguise +4600 125 139 Willow blackjack +4608 15 1586 Super kebab +4627 2000 2313 Bandit's brew +4668 15 450 Garlic powder +4675 70 60242 Ancient staff +4684 15 1397 Linen (Icthlarin's Little Helper) +4687 15 233 Bucket of sap +4689 15 1440 Pile of salt +4694 18000 96 Steam rune +4695 18000 90 Mist rune +4696 18000 8 Dust rune +4697 18000 62 Smoke rune +4698 18000 87 Mud rune +4699 18000 25 Lava rune +4708 15 394098 Ahrim's hood +4710 15 142133 Ahrim's staff +4712 15 1429731 Ahrim's robetop +4714 15 1467735 Ahrim's robeskirt +4716 15 310344 Dharok's helm +4718 15 1852532 Dharok's greataxe +4720 15 857500 Dharok's platebody +4722 15 934831 Dharok's platelegs +4724 15 182261 Guthan's helm +4726 15 89589 Guthan's warspear +4728 15 218032 Guthan's platebody +4730 15 204991 Guthan's chainskirt +4732 15 49714 Karil's coif +4734 15 101500 Karil's crossbow +4736 15 818973 Karil's leathertop +4738 15 404973 Karil's leatherskirt +4740 11000 73 Bolt rack +4745 15 179873 Torag's helm +4747 15 122497 Torag's hammers +4749 15 297987 Torag's platebody +4751 15 329054 Torag's platelegs +4753 15 133179 Verac's helm +4755 15 100752 Verac's flail +4757 15 169000 Verac's brassard +4759 15 328954 Verac's plateskirt +4773 11000 116 Bronze brutal +4778 11000 136 Iron brutal +4783 11000 185 Steel brutal +4788 11000 1480 Black brutal +4793 11000 219 Mithril brutal +4798 11000 305 Adamant brutal +4803 11000 1033 Rune brutal +4812 3000 596 Zogre bones +4819 13000 2 Bronze nails +4820 13000 7 Iron nails +4821 13000 1634 Black nails +4822 13000 66 Mithril nails +4823 13000 120 Adamantite nails +4824 13000 817 Rune nails +4825 10000 699 Unstrung comp bow +4827 70 1247 Comp ogre bow +4830 7500 5036 Fayrg bones +4832 7500 7141 Raurg bones +4834 7500 42800 Ourg bones +4842 2000 939 Relicym's balm(4) +4844 2000 5433 Relicym's balm(3) +4846 2000 256 Relicym's balm(2) +4848 2000 59 Relicym's balm(1) +4850 3000 2038 Ogre coffin key +4860 15 326589 Ahrim's hood 0 +4866 15 83380 Ahrim's staff 0 +4872 15 1335889 Ahrim's robetop 0 +4878 15 1393633 Ahrim's robeskirt 0 +4884 15 266033 Dharok's helm 0 +4890 15 1820950 Dharok's greataxe 0 +4896 15 779120 Dharok's platebody 0 +4902 15 845694 Dharok's platelegs 0 +4908 15 115108 Guthan's helm 0 +4914 15 59192 Guthan's warspear 0 +4920 15 166663 Guthan's platebody 0 +4926 15 164012 Guthan's chainskirt 0 +4932 15 14182 Karil's coif 0 +4938 15 96575 Karil's crossbow 0 +4944 15 711666 Karil's leathertop 0 +4950 15 345269 Karil's leatherskirt 0 +4956 15 124029 Torag's helm 0 +4962 15 95679 Torag's hammers 0 +4968 15 245192 Torag's platebody 0 +4974 15 284756 Torag's platelegs 0 +4980 15 97500 Verac's helm 0 +4986 15 95052 Verac's flail 0 +4992 15 166908 Verac's brassard 0 +4998 15 291034 Verac's plateskirt 0 +5001 13000 85 Raw cave eel +5003 10000 31 Cave eel +5014 4 428 Mining helmet +5016 4 1147 Bone spear +5018 4 1446 Bone club +5024 150 841 Woven top (brown) +5026 150 500413 Woven top (yellow) +5028 150 93 Woven top (blue) +5030 150 391 Shirt (brown) +5032 150 4892 Shirt (yellow) +5034 150 27500 Shirt (lilac) +5036 150 503 Trousers (brown) +5038 150 275 Trousers (lilac) +5040 150 777 Trousers (blue) +5042 150 696 Shorts (brown) +5044 150 878 Shorts (yellow) +5046 150 2018 Shorts (blue) +5048 150 1150 Skirt (brown) +5050 150 200 Skirt (lilac) +5052 150 1497 Skirt (blue) +5075 11000 4137 Bird nest (empty) +5096 600 21 Marigold seed +5097 600 54 Rosemary seed +5098 200 9 Nasturtium seed +5099 600 10 Woad seed +5100 600 21 Limpwurt seed +5101 600 5 Redberry seed +5102 600 6 Cadavaberry seed +5103 600 11 Dwellberry seed +5104 200 4 Jangerberry seed +5105 200 7 Whiteberry seed +5106 200 36 Poison ivy seed +5280 200 48 Cactus seed +5281 200 137 Belladonna seed +5282 200 107 Mushroom spore +5283 200 5 Apple tree seed +5284 200 4 Banana tree seed +5285 200 21 Orange tree seed +5286 200 23 Curry tree seed +5287 200 135 Pineapple seed +5288 200 992 Papaya tree seed +5289 200 14799 Palm tree seed +5290 200 245 Calquat tree seed +5291 600 23 Guam seed +5292 600 15 Marrentill seed +5293 600 13 Tarromin seed +5294 600 53 Harralander seed +5295 200 29418 Ranarr seed +5296 600 971 Toadflax seed +5297 600 40 Irit seed +5298 200 250 Avantoe seed +5299 200 1746 Kwuarm seed +5300 200 45233 Snapdragon seed +5301 200 2907 Cadantine seed +5302 200 150 Lantadyme seed +5303 200 577 Dwarf weed seed +5304 200 12525 Torstol seed +5305 2000 10 Barley seed +5306 2000 8 Jute seed +5307 2000 5 Hammerstone seed +5308 2000 8 Asgarnian seed +5309 2000 10 Yanillian seed +5310 2000 14 Krandorian seed +5311 2000 32 Wildblood seed +5312 200 84 Acorn +5313 200 85 Willow seed +5314 200 3186 Maple seed +5315 200 20315 Yew seed +5316 200 68829 Magic seed +5318 600 3 Potato seed +5319 600 6 Onion seed +5320 600 7 Sweetcorn seed +5321 200 86 Watermelon seed +5322 600 9 Tomato seed +5323 600 13 Strawberry seed +5324 600 5 Cabbage seed +5325 40 128 Gardening trowel +5329 40 124 Secateurs +5331 40 155 Watering can +5341 40 172 Rake +5343 40 72 Seed dibber +5345 150 27500 Gardening boots +5350 10000 4 Empty plant pot +5352 10000 5053 Unfired plant pot +5354 2000 3 Filled plant pot +5370 200 396 Oak sapling +5371 200 364 Willow sapling +5372 200 4062 Maple sapling +5373 200 22509 Yew sapling +5374 200 71845 Magic sapling +5376 10000 3 Basket +5386 600 778 Apples(5) +5396 600 1805 Oranges(5) +5406 200 534 Strawberries(5) +5416 600 877 Bananas(5) +5418 10000 3 Empty sack +5438 600 1286 Potatoes(10) +5458 600 754 Onions(10) +5478 600 934 Cabbages(10) +5496 200 405 Apple sapling +5497 200 252 Banana sapling +5498 200 92 Orange sapling +5499 200 265 Curry sapling +5500 200 275 Pineapple sapling +5501 200 1760 Papaya sapling +5502 200 16698 Palm sapling +5503 200 1155 Calquat sapling +5504 11000 59 Strawberry +5516 40 1735 Elemental talisman +5521 10000 659 Binding necklace +5523 40 195 Tiara mould +5525 10000 105 Tiara +5527 40 27 Air tiara +5529 40 4 Mind tiara +5531 40 6 Water tiara +5533 40 11 Body tiara +5535 40 13 Earth tiara +5537 40 42 Fire tiara +5539 40 125 Cosmic tiara +5541 40 8 Nature tiara +5543 40 650 Chaos tiara +5547 40 1991 Death tiara +5549 0 120380 Blood tiara +5574 125 3020 Initiate sallet +5575 125 5349 Initiate hauberk +5576 125 4216 Initiate cuisse +5616 7000 34 Bronze arrow(p+) +5617 7000 27 Iron arrow(p+) +5618 7000 221 Steel arrow(p+) +5619 7000 215 Mithril arrow(p+) +5620 11000 380 Adamant arrow(p+) +5621 11000 425 Rune arrow(p+) +5622 7000 59 Bronze arrow(p++) +5623 7000 1096 Iron arrow(p++) +5624 7000 829 Steel arrow(p++) +5625 7000 265 Mithril arrow(p++) +5626 11000 23 Adamant arrow(p++) +5627 11000 387 Rune arrow(p++) +5628 7000 5249 Bronze dart(p+) +5629 7000 5050 Iron dart(p+) +5630 7000 47088 Steel dart(p+) +5631 7000 262 Black dart(p+) +5632 7000 36714 Mithril dart(p+) +5633 11000 179 Adamant dart(p+) +5634 11000 309 Rune dart(p+) +5635 7000 2166 Bronze dart(p++) +5636 7000 127 Iron dart(p++) +5637 7000 67 Steel dart(p++) +5638 7000 50000 Black dart(p++) +5639 7000 255 Mithril dart(p++) +5640 11000 575 Adamant dart(p++) +5641 11000 856 Rune dart(p++) +5642 7000 5006 Bronze javelin(p+) +5643 7000 60052 Iron javelin(p+) +5644 7000 50000 Steel javelin(p+) +5645 7000 124500 Mithril javelin(p+) +5646 11000 523 Adamant javelin(p+) +5647 11000 150001 Rune javelin(p+) +5648 7000 7000 Bronze javelin(p++) +5649 7000 9944 Iron javelin(p++) +5650 7000 34 Steel javelin(p++) +5651 7000 2 Mithril javelin(p++) +5652 11000 155 Adamant javelin(p++) +5653 11000 188 Rune javelin(p++) +5654 7000 57000 Bronze knife(p+) +5655 7000 50000 Iron knife(p+) +5656 7000 4999 Steel knife(p+) +5657 7000 34595 Mithril knife(p+) +5658 7000 50079 Black knife(p+) +5659 7000 750 Adamant knife(p+) +5660 7000 559 Rune knife(p+) +5661 7000 222 Bronze knife(p++) +5662 7000 1263 Iron knife(p++) +5663 7000 596 Steel knife(p++) +5664 7000 757 Mithril knife(p++) +5665 7000 4018 Black knife(p++) +5666 7000 1773 Adamant knife(p++) +5667 7000 129 Rune knife(p++) +5668 125 697 Iron dagger(p+) +5670 125 112 Bronze dagger(p+) +5672 125 266 Steel dagger(p+) +5674 125 650 Mithril dagger(p+) +5676 125 2239 Adamant dagger(p+) +5678 70 4152 Rune dagger(p+) +5680 70 16901 Dragon dagger(p+) +5682 125 2277 Black dagger(p+) +5686 125 5499 Iron dagger(p++) +5688 125 1030 Bronze dagger(p++) +5690 125 3916 Steel dagger(p++) +5692 125 24922 Mithril dagger(p++) +5694 125 3454 Adamant dagger(p++) +5696 70 11636 Rune dagger(p++) +5698 70 17287 Dragon dagger(p++) +5700 125 1979 Black dagger(p++) +5704 125 796 Bronze spear(p+) +5706 125 193259 Iron spear(p+) +5708 125 85 Steel spear(p+) +5710 125 3736 Mithril spear(p+) +5712 125 1390 Adamant spear(p+) +5714 70 14329 Rune spear(p+) +5716 70 37002 Dragon spear(p+) +5718 125 1039 Bronze spear(p++) +5720 125 5676 Iron spear(p++) +5722 125 252 Steel spear(p++) +5724 125 8628 Mithril spear(p++) +5726 125 3353 Adamant spear(p++) +5728 70 98776 Rune spear(p++) +5730 70 54952 Dragon spear(p++) +5734 8 12106 Black spear(p+) +5736 8 12953 Black spear(p++) +5739 2000 50500 Asgarnian ale(m) +5741 2000 59699 Mature wmb +5743 2000 31156 Greenman's ale(m) +5745 2000 89511 Dragon bitter(m) +5747 2000 17940 Dwarven stout(m) +5749 2000 52747 Moonlight mead(m) +5751 2000 340 Axeman's folly +5753 2000 45044 Axeman's folly(m) +5755 2000 136 Chef's delight +5757 2000 36080 Chef's delight(m) +5759 2000 101 Slayer's respite +5761 2000 30376 Slayer's respite(m) +5763 2000 122 Cider +5765 2000 135000 Mature cider +5767 11000 1304 Ale yeast +5769 10000 875 Calquat keg +5777 2000 13334 Dwarven stout(4) +5785 2000 34919 Asgarnian ale(4) +5793 2000 19500 Greenmans ale(4) +5801 2000 57710 Mind bomb(4) +5809 2000 21447 Dragon bitter(4) +5817 2000 28266 Moonlight mead(4) +5825 2000 9400 Axeman's folly(4) +5833 2000 12900 Chef's delight(4) +5841 2000 10135 Slayer's respite(4) +5849 2000 9249 Cider(4) +5857 2000 215728 Dwarven stout(m4) +5865 2000 100000 Asgarnian ale(m4) +5873 2000 150000 Greenmans ale(m4) +5881 2000 250000 Mind bomb(m4) +5889 2000 100000 Dragon bitter(m4) +5897 2000 133102 Moonlight mead(m4) +5905 2000 176508 Axeman's folly(m4) +5913 2000 182500 Chef's delight(m4) +5921 2000 146500 Slayer's respite(m4) +5929 2000 547500 Cider(m4) +5931 13000 652 Jute fibre +5933 11000 1591 Willow branch +5935 11000 259 Coconut milk +5937 2000 959 Weapon poison(+) +5940 2000 2165 Weapon poison(++) +5943 4000 1654 Antidote+(4) +5945 4000 1410 Antidote+(3) +5947 4000 717 Antidote+(2) +5949 4000 820 Antidote+(1) +5952 4000 9173 Antidote++(4) +5954 4000 6185 Antidote++(3) +5956 4000 4100 Antidote++(2) +5958 4000 2422 Antidote++(1) +5968 600 667 Tomatoes(5) +5970 11000 68 Curry leaf +5972 11000 1458 Papaya fruit +5974 11000 1352 Coconut +5980 11000 107 Calquat fruit +5982 11000 15 Watermelon +5984 11000 28 Watermelon slice +5986 11000 63 Sweetcorn +5988 11000 69 Cooked sweetcorn +5992 11000 5000 Apple mush +5994 11000 413 Hammerstone hops +5996 11000 360 Asgarnian hops +5998 11000 851 Yanillian hops +6000 11000 321 Krandorian hops +6002 11000 133 Wildblood hops +6004 11000 188 Mushroom +6006 11000 421 Barley +6008 11000 1205 Barley malt +6010 600 10 Marigolds +6012 600 253 Nasturtiums +6014 600 57 Rosemary +6016 11000 1530 Cactus spine +6018 11000 212 Poison ivy berries +6020 13000 15 Leaves +6022 0 44 Oak leaves +6024 0 131 Willow leaves +6026 7500 530 Yew leaves +6028 0 319 Maple leaves +6030 0 672 Magic leaves +6032 2000 11 Compost +6034 2000 14 Supercompost +6036 2000 140 Plant cure +6038 11000 1001 Magic string +6043 11000 157 Oak roots +6045 11000 21 Willow roots +6047 11000 20 Maple roots +6049 11000 67 Yew roots +6051 11000 8136 Magic roots +6055 600 137 Weeds +6061 11000 5000 Bronze bolts (p+) +6062 11000 505 Bronze bolts (p++) +6128 70 17836 Rock-shell helm +6129 70 39526 Rock-shell plate +6130 70 39157 Rock-shell legs +6131 70 36424 Spined helm +6133 70 4080 Spined body +6135 70 19500 Spined chaps +6137 70 139925 Skeletal helm +6139 70 26075 Skeletal top +6141 70 24576 Skeletal bottoms +6143 125 3628 Spined boots +6145 125 25502 Rock-shell boots +6147 125 77074 Skeletal boots +6149 125 1500 Spined gloves +6151 125 800 Rock-shell gloves +6153 125 260275 Skeletal gloves +6155 11000 220 Dagannoth hide +6157 11000 10145 Rock-shell chunk +6159 11000 16751 Rock-shell shard +6161 11000 21297 Rock-shell splinter +6163 11000 77344 Skull piece +6165 11000 6785 Ribcage piece +6167 11000 11824 Fibula piece +6169 11000 26755 Circular hide +6171 11000 1 Flattened hide +6173 11000 1966 Stretched hide +6211 11000 3212 Teak pyre logs +6213 11000 3696 Mahogany pyre logs +6215 125 3623 Broodoo shield (10) (poison) +6235 125 3000 Broodoo shield (poison) +6237 125 2780 Broodoo shield (10) (disease) +6257 125 3000 Broodoo shield (disease) +6259 125 1890 Broodoo shield (10) (combat) +6279 125 6100 Broodoo shield (combat) +6281 100 118 Thatch spar light +6283 100 1393 Thatch spar med +6285 100 557 Thatch spar dense +6287 13000 67 Snake hide +6289 13000 10 Snakeskin +6291 13000 52 Spider carcass +6297 6000 367 Spider on stick +6299 6000 12 Spider on shaft +6305 13000 64 Skewer stick +6306 18000 5 Trading sticks +6311 5 580009 Gout tuber +6313 15 1366 Opal machete +6315 15 11644 Jade machete +6317 15 16750 Red topaz machete +6319 100 299 Proboscis +6322 125 768 Snakeskin body +6324 125 532 Snakeskin chaps +6326 125 300 Snakeskin bandana +6328 125 276 Snakeskin boots +6330 125 25084 Snakeskin vambraces +6332 11000 105 Mahogany logs +6333 13000 120 Teak logs +6335 150 1317 Tribal mask (poison) +6337 150 1290 Tribal mask (disease) +6339 150 1037 Tribal mask (combat) +6341 150 3900 Tribal top (brown) +6343 150 3755 Villager robe (brown) +6345 150 4249 Villager hat (brown) +6347 150 12534 Villager armband (brown) +6349 150 2673 Villager sandals (brown) +6351 150 4608 Tribal top (blue) +6353 150 4427 Villager robe (blue) +6355 150 354 Villager hat (blue) +6357 150 4267 Villager sandals (blue) +6359 150 5500 Villager armband (blue) +6361 150 501397 Tribal top (yellow) +6363 150 1671 Villager robe (yellow) +6365 150 1902 Villager hat (yellow) +6367 150 598 Villager sandals (yellow) +6369 150 772 Villager armband (yellow) +6371 150 5530 Tribal top (pink) +6373 150 4886 Villager robe (pink) +6375 150 1242 Villager hat (pink) +6377 150 2943 Villager sandals (pink) +6379 150 5750 Villager armband (pink) +6382 150 1002 Fez +6384 150 430 Desert top +6386 150 2110 Desert robes +6388 150 11975 Desert top (overcoat) +6390 150 1715 Desert legs +6392 150 3115 Menaphite purple hat +6394 150 469 Menaphite purple top +6396 150 489 Menaphite purple robe +6398 150 10520 Menaphite purple kilt +6400 150 10454 Menaphite red hat +6402 150 734 Menaphite red top +6404 150 10325 Menaphite red robe +6406 150 10158 Menaphite red kilt +6408 125 3902 Oak blackjack(o) +6410 125 976 Oak blackjack(d) +6412 125 6177 Willow blackjack(o) +6414 125 1862 Willow blackjack(d) +6416 125 1529 Maple blackjack +6418 125 3574 Maple blackjack(o) +6420 125 19 Maple blackjack(d) +6470 50 12 Compost potion(4) +6472 50 1 Compost potion(3) +6474 50 5 Compost potion(2) +6476 50 1 Compost potion(1) +6522 11000 2356 Toktz-xil-ul +6523 70 451515 Toktz-xil-ak +6524 70 268216 Toktz-ket-xil +6525 70 417529 Toktz-xil-ek +6526 70 480065 Toktz-mej-tal +6527 70 208338 Tzhaar-ket-em +6528 70 170748 Tzhaar-ket-om +6562 8 24997 Mud battlestaff +6563 8 77174 Mystic mud staff +6568 70 445805 Obsidian cape +6571 11000 2782757 Uncut onyx +6573 11000 2889384 Onyx +6575 10000 1173996 Onyx ring +6577 10000 2712775 Onyx necklace +6579 10000 1633347 Onyx amulet (u) +6581 10000 2682664 Onyx amulet +6583 70 1017772 Ring of stone +6585 8 2794142 Amulet of fury +6587 125 629 White claws +6589 125 1036 White battleaxe +6591 125 1080 White dagger +6593 125 79 White dagger(p) +6595 125 140 White dagger(p+) +6597 125 12588 White dagger(p++) +6599 125 47565 White halberd +6601 125 571 White mace +6603 125 6411 White magic staff +6605 125 2610 White sword +6607 125 1357 White longsword +6609 125 1225 White 2h sword +6611 125 606 White scimitar +6613 125 1746 White warhammer +6615 125 682 White chainbody +6617 125 4137 White platebody +6619 125 2203 White boots +6621 125 100663 White med helm +6623 125 3465 White full helm +6625 125 2679 White platelegs +6627 125 1451 White plateskirt +6629 125 603 White gloves +6631 125 432 White sq shield +6633 125 70500 White kiteshield +6667 100 101 Empty fishbowl +6681 100 14 Ground guam +6685 2000 7037 Saradomin brew(4) +6687 2000 5304 Saradomin brew(3) +6689 2000 3464 Saradomin brew(2) +6691 2000 1639 Saradomin brew(1) +6693 11000 4334 Crushed nest +6697 11000 47 Pat of butter +6701 13000 99 Baked potato +6703 13000 261 Potato with butter +6705 13000 270 Potato with cheese +6724 8 47752 Seercull +6729 7500 12743 Dagannoth bones +6731 8 886614 Seers ring +6733 8 2501644 Archers ring +6735 8 52156 Warrior ring +6737 8 3765458 Berserker ring +6739 40 67390 Dragon axe +6750 15 273 Black desert shirt +6752 15 93 Black desert robe +6760 8 7573 Guthix mjolnir +6762 8 5109 Saradomin mjolnir +6764 8 6042 Zamorak mjolnir +6794 6000 100 Choc-ice +6809 70 42541 Granite legs +6812 7500 3552 Wyvern bones +6814 18000 303 Fur +6889 15 4492914 Mage's book +6891 15 211949 Arena book +6908 125 497646 Beginner wand +6910 125 1152650 Apprentice wand +6912 70 4200292 Teacher wand +6914 70 7035176 Master wand +6916 125 1976279 Infinity top +6918 125 2459795 Infinity hat +6920 125 938636 Infinity boots +6922 125 846856 Infinity gloves +6924 125 1961695 Infinity bottoms +6959 150 433 Pink cape +6962 6000 421 Triangle sandwich +6971 100 25001 Sandstone (1kg) +6973 100 146 Sandstone (2kg) +6975 100 262 Sandstone (5kg) +6977 100 3104 Sandstone (10kg) +6979 100 18 Granite (500g) +6981 100 39 Granite (2kg) +6983 100 245 Granite (5kg) +7051 40 348 Unlit bug lantern +7054 13000 305 Chilli potato +7056 13000 198 Egg potato +7058 13000 983 Mushroom potato +7060 13000 1398 Tuna potato +7062 13000 4371 Chilli con carne +7064 13000 175 Egg and tomato +7066 13000 1073 Mushroom & onion +7068 13000 1019 Tuna and corn +7070 13000 2643 Minced meat +7072 13000 5661 Spicy sauce +7074 13000 987 Chopped garlic +7076 13000 250 Uncooked egg +7078 13000 2474 Scrambled egg +7080 13000 118 Sliced mushrooms +7082 13000 49750 Fried mushrooms +7084 13000 90 Fried onions +7086 13000 153 Chopped tuna +7088 13000 255 Sweetcorn (bowl) +7110 150 1 Stripy pirate shirt (beige) +7112 150 198 Pirate bandana (white) +7114 150 860 Pirate boots +7116 150 22 Pirate leggings (beige) +7122 150 88 Stripy pirate shirt (red) +7124 150 134 Pirate bandana (red) +7126 150 126 Pirate leggings (red) +7128 150 210 Stripy pirate shirt (blue) +7130 150 1280 Pirate bandana (blue) +7132 150 116 Pirate leggings (blue) +7134 150 978 Stripy pirate shirt (brown) +7136 150 52 Pirate bandana (brown) +7138 150 151 Pirate leggings (brown) +7158 8 132242 Dragon 2h sword +7159 70 585 Insulated boots +7162 15 400 Pie recipe book +7168 13000 1588 Raw mud pie +7170 11000 1001 Mud pie +7176 13000 1427 Raw garden pie +7178 10000 98 Garden pie +7186 13000 1950 Raw fish pie +7188 10000 147 Fish pie +7196 13000 2175 Raw admiral pie +7198 10000 109 Admiral pie +7206 13000 4026 Raw wild pie +7208 10000 539 Wild pie +7216 13000 1813 Raw summer pie +7218 10000 667 Summer pie +7223 6000 2537 Roast rabbit +7225 11000 269 Iron spit +7228 100 85 Cooked chompy (roasted) +7319 4 4279 Red boater +7321 4 2550 Orange boater +7323 4 7500 Green boater +7325 4 1603 Blue boater +7327 4 5889 Black boater +7329 250 16 Red firelighter +7330 250 61 Green firelighter +7331 250 54 Blue firelighter +7332 70 5614 Black shield (h1) +7334 70 3704 Adamant shield (h1) +7336 70 68035 Rune shield (h1) +7338 70 2150 Black shield (h2) +7340 70 2445 Adamant shield (h2) +7342 70 35501 Rune shield (h2) +7344 70 1880 Black shield (h3) +7346 70 2771 Adamant shield (h3) +7348 70 40999 Rune shield (h3) +7350 70 1595 Black shield (h4) +7352 70 2646 Adamant shield (h4) +7354 70 34000 Rune shield (h4) +7356 70 1000 Black shield (h5) +7358 70 2461 Adamant shield (h5) +7360 70 35450 Rune shield (h5) +7362 8 6500 Studded body (g) +7364 8 16318 Studded body (t) +7366 8 16713 Studded chaps (g) +7368 8 3774 Studded chaps (t) +7370 8 4106 Green d'hide body (g) +7372 8 4067 Green d'hide body (t) +7374 8 4814 Blue d'hide body (g) +7376 8 4188 Blue d'hide body (t) +7378 8 51858 Green d'hide chaps (g) +7380 8 5720 Green d'hide chaps (t) +7382 8 1698 Blue d'hide chaps (g) +7384 8 1725 Blue d'hide chaps (t) +7386 4 20632 Blue skirt (g) +7388 4 9828 Blue skirt (t) +7390 4 168394 Blue wizard robe (g) +7392 4 26007 Blue wizard robe (t) +7394 4 122042 Blue wizard hat (g) +7396 4 34901 Blue wizard hat (t) +7398 8 47660 Enchanted robe +7399 8 71639 Enchanted top +7400 8 7593 Enchanted hat +7416 50 6363 Mole claw +7418 50 5182 Mole skin +7433 50 1579 Wooden spoon +7435 50 242 Egg whisk +7437 50 2308 Spork +7439 50 555 Spatula +7441 50 673 Frying pan +7443 50 1176 Skewer +7445 50 20074 Rolling pin +7447 50 7228 Kitchen knife +7449 50 24652 Meat tenderiser +7451 50 13369 Cleaver +7466 15 1 Cornflour +7468 15 1456 Pot of cornflour +7521 6000 296 Cooked giant crab meat +7566 11000 709 Raw jubbly +7568 10000 982 Cooked jubbly +7650 13000 1247 Silver dust +7660 2000 1570 Guthix balance(4) +7662 2000 628 Guthix balance(3) +7664 2000 6 Guthix balance(2) +7666 2000 1753 Guthix balance(1) +7668 8 1194 Gadderhammer +7759 150 3066 Toy soldier +7761 150 2642 Toy soldier (wound) +7763 150 4049 Toy doll +7765 150 4925 Toy doll (wound) +7767 150 4572 Toy mouse +7769 150 6550 Toy mouse (wound) +7771 150 3916 Toy cat +7801 11000 153 Snake hide (swamp) +7919 10000 1023 Bottle of wine +7936 30000 1 Pure essence +7939 7500 250 Tortoise shell +7944 13000 114 Raw monkfish +7946 13000 158 Monkfish +8007 15000 605 Varrock teleport (tablet) +8008 10000 575 Lumbridge teleport (tablet) +8009 15000 582 Falador teleport (tablet) +8010 15000 383 Camelot teleport (tablet) +8011 10000 420 Ardougne teleport (tablet) +8012 10000 249 Watchtower teleport (tablet) +8013 15000 570 Teleport to house (tablet) +8014 10000 384 Bones to bananas (tablet) +8015 10000 990 Bones to peaches (tablet) +8016 10000 104 Enchant sapphire or opal +8017 10000 172 Enchant emerald or jade +8018 10000 504 Enchant ruby or topaz +8019 10000 2692 Enchant diamond +8020 10000 131 Enchant dragonstone +8021 10000 170 Enchant onyx +8417 10000 1937 Bagged dead tree +8419 10000 3174 Bagged nice tree +8421 10000 8166 Bagged oak tree +8423 10000 12999 Bagged willow tree +8425 10000 21000 Bagged maple tree +8427 10000 32247 Bagged yew tree +8429 10000 54542 Bagged magic tree +8431 10000 1910 Bagged plant 1 +8433 10000 6188 Bagged plant 2 +8435 10000 13865 Bagged plant 3 +8437 10000 3827 Thorny hedge (bagged) +8439 10000 11297 Nice hedge (bagged) +8441 10000 20000 Small box hedge (bagged) +8443 10000 28629 Topiary hedge (bagged) +8445 10000 20415 Fancy hedge (bagged) +8447 10000 36806 Tall fancy hedge (bagged) +8449 10000 107053 Tall box hedge (bagged) +8451 10000 7864 Bagged flower +8453 10000 30000 Bagged daffodils +8455 10000 19265 Bagged bluebells +8457 10000 7280 Bagged sunflower +8459 10000 6609 Bagged marigolds +8461 10000 15708 Bagged roses +8496 500 2188 Crude chair (flatpack) +8498 500 911 Wooden chair (flatpack) +8500 500 25061 Rocking chair (flatpack) +8502 500 540 Oak chair (flatpack) +8504 500 49570 Oak armchair (flatpack) +8506 500 71 Teak armchair (flatpack) +8508 500 5154 Mahogany armchair (flatpack) +8510 500 16 Bookcase (flatpack) +8512 500 13 Oak bookcase (flatpack) +8514 500 512 Mahogany bookcase (flatpack) +8516 500 2750 Beer barrel (flatpack) +8518 500 5000 Cider barrel (flatpack) +8520 500 14523 Asgarnian ale (flatpack) +8522 500 13281 Greenman's ale (flatpack) +8524 500 172 Dragon bitter (flatpack) +8526 500 12752 Chef's delight (flatpack) +8528 500 9 Kitchen table (flatpack) +8530 500 8 Oak kitchen table (flatpack) +8532 500 29 Teak kitchen table (flatpack) +8548 500 2 Wood dining table (flatpack) +8550 500 1 Oak dining table (flatpack) +8552 500 14 Carved oak table (flatpack) +8554 500 5 Teak table (flatpack) +8556 500 197 Carved teak table (flatpack) +8558 500 10 Mahogany table (flatpack) +8560 500 1050000 Opulent table (flatpack) +8562 500 23 Wooden bench (flatpack) +8564 500 50000 Oak bench (flatpack) +8566 500 5 Carved oak bench (flatpack) +8568 500 51 Teak dining bench (flatpack) +8570 500 5 Carved teak bench (flatpack) +8572 500 53 Mahogany bench (flatpack) +8574 500 476246 Gilded bench (flatpack) +8576 500 1530 Wooden bed (flatpack) +8578 500 7042 Oak bed (flatpack) +8580 500 691 Large oak bed (flatpack) +8582 500 2110 Teak bed (flatpack) +8584 500 3824 Large teak bed (flatpack) +8586 500 5276 Four-poster bed (flatpack) +8588 500 109388 Gilded four-poster (flatpack) +8590 500 331 Oak clock (flatpack) +8592 500 1646 Teak clock (flatpack) +8594 500 327272 Gilded clock (flatpack) +8596 500 100000 Shaving stand (flatpack) +8598 500 289 Oak shaving stand (flatpack) +8600 500 120 Oak dresser (flatpack) +8602 500 198 Teak dresser (flatpack) +8604 500 2942 Fancy teak dresser (flatpack) +8606 500 26584 Mahogany dresser (flatpack) +8608 500 135000 Gilded dresser (flatpack) +8610 500 28 Shoe box (flatpack) +8612 500 33 Oak drawers (flatpack) +8614 500 37 Oak wardrobe (flatpack) +8616 500 96 Teak drawers (flatpack) +8618 500 2501 Teak wardrobe (flatpack) +8620 500 1188 Mahogany wardrobe (flatpack) +8622 500 278988 Gilded wardrobe (flatpack) +8624 500 10 Crystal ball (flatpack) +8626 500 10 Elemental sphere (flatpack) +8628 500 10 Crystal of power (flatpack) +8778 13000 531 Oak plank +8780 13000 839 Teak plank +8782 13000 2172 Mahogany plank +8784 11000 138271 Gold leaf +8786 11000 335749 Marble block +8788 11000 1003002 Magic stone +8790 13000 365 Bolt of cloth +8792 11000 869 Clockwork +8794 40 284 Saw +8837 13000 1081 Timber beam +8872 70 3800 Bone dagger +8874 70 4467 Bone dagger (p) +8876 70 2790 Bone dagger (p+) +8878 70 7150 Bone dagger (p++) +8880 70 3302 Dorgeshuun crossbow +8882 11000 6 Bone bolts +8901 70 841999 Black mask (10) +8921 70 837071 Black mask +8924 150 133000 Bandana eyepatch (white) +8925 150 331 Bandana eyepatch (red) +8926 150 164 Bandana eyepatch (blue) +8927 150 1923 Bandana eyepatch (brown) +8928 150 47411 Hat eyepatch +8940 0 42285 Rum (red) +8941 0 45285 Rum (blue) +9003 15 51 Security book +9004 15 309 Stronghold notes +9026 13000 906 Ivory comb +9028 13000 1006 Golden scarab +9030 13000 174 Stone scarab +9032 13000 120 Pottery scarab +9034 13000 1341 Golden statuette +9036 13000 87 Pottery statuette +9038 13000 189 Stone statuette +9040 13000 751 Gold seal +9042 13000 259 Stone seal +9052 6000 2300 Locust meat +9075 25000 110 Astral rune +9140 7000 19 Iron bolts +9141 7000 48 Steel bolts +9142 7000 21 Mithril bolts +9143 11000 144 Adamant bolts +9144 11000 85 Runite bolts +9145 11000 8 Silver bolts +9174 125 177 Bronze crossbow +9177 125 717 Iron crossbow +9179 125 118 Steel crossbow +9181 125 232 Mithril crossbow +9183 70 620 Adamant crossbow +9185 70 9449 Rune crossbow +9187 11000 6 Jade bolt tips +9188 11000 121 Topaz bolt tips +9189 11000 6 Sapphire bolt tips +9190 11000 44 Emerald bolt tips +9191 11000 86 Ruby bolt tips +9192 11000 173 Diamond bolt tips +9193 11000 421 Dragonstone bolt tips +9194 11000 8139 Onyx bolt tips +9236 11000 43 Opal bolts (e) +9238 11000 6 Pearl bolts (e) +9239 11000 2 Topaz bolts (e) +9240 11000 25 Sapphire bolts (e) +9241 11000 17 Emerald bolts (e) +9242 11000 175 Ruby bolts (e) +9243 11000 129 Diamond bolts (e) +9244 11000 345 Dragonstone bolts (e) +9245 11000 8553 Onyx bolts (e) +9287 11000 383 Iron bolts (p) +9288 11000 239 Steel bolts (p) +9289 11000 212 Mithril bolts (p) +9290 11000 375 Adamant bolts (p) +9291 11000 344 Runite bolts (p) +9292 11000 2646 Silver bolts (p) +9294 11000 322 Iron bolts (p+) +9295 11000 19655 Steel bolts (p+) +9296 11000 498 Mithril bolts (p+) +9297 11000 371 Adamant bolts (p+) +9298 11000 475 Runite bolts (p+) +9299 11000 105 Silver bolts (p+) +9301 11000 875 Iron bolts (p++) +9302 11000 498 Steel bolts (p++) +9303 11000 654 Mithril bolts (p++) +9304 11000 608 Adamant bolts (p++) +9305 11000 745 Runite bolts (p++) +9306 11000 204 Silver bolts (p++) +9336 11000 180 Topaz bolts +9337 11000 35 Sapphire bolts +9338 11000 75 Emerald bolts +9339 11000 246 Ruby bolts +9340 11000 362 Diamond bolts +9341 11000 524 Dragonstone bolts +9342 11000 8321 Onyx bolts +9375 13000 16 Bronze bolts (unf) +9377 13000 28 Iron bolts (unf) +9378 13000 63 Steel bolts (unf) +9379 13000 102 Mithril bolts (unf) +9380 13000 175 Adamant bolts(unf) +9381 13000 126 Runite bolts (unf) +9382 13000 12 Silver bolts (unf) +9416 11000 860 Mith grapple tip +9418 11000 1157 Mith grapple (unf) +9419 11000 1780 Mith grapple +9420 10000 37 Bronze limbs +9423 10000 100 Iron limbs +9425 10000 256 Steel limbs +9427 10000 334 Mithril limbs +9429 10000 1727 Adamantite limbs +9431 10000 9109 Runite limbs +9434 40 21 Bolt mould +9436 13000 282 Sinew +9438 11000 482 Crossbow string +9440 10000 10 Wooden stock +9442 10000 4 Oak stock +9444 10000 3 Willow stock +9446 10000 43 Teak stock +9448 10000 13 Maple stock +9450 10000 2 Mahogany stock +9452 10000 138 Yew stock +9454 10000 80 Bronze crossbow (u) +9457 10000 2805 Iron crossbow (u) +9459 10000 99 Steel crossbow (u) +9461 10000 325 Mithril crossbow (u) +9463 10000 603 Adamant crossbow (u) +9465 10000 9186 Runite crossbow (u) +9469 15 228898 Grand seed pod +9470 4 886114 Gnome scarf +9472 4 31049 Gnome goggles +9475 10000 64274 Mint cake +9629 150 1017 Tyras helm +9634 150 915 Vyrewatch top +9636 150 1078 Vyrewatch legs +9638 150 2556 Vyrewatch shoes +9640 150 25296 Citizen top +9642 150 50000 Citizen trousers +9644 150 50394 Citizen shoes +9666 70 165950 Proselyte harness m +9668 70 23179 Initiate harness m +9670 70 29877 Proselyte harness f +9672 70 4687 Proselyte sallet +9674 70 8565 Proselyte hauberk +9676 70 5794 Proselyte cuisse +9678 70 5949 Proselyte tasset +9729 70 367 Elemental helmet +9731 70 42806 Mind shield +9733 70 1410 Mind helmet +9735 13000 58 Goat horn +9736 13000 152 Goat horn dust +9739 2000 152 Combat potion(4) +9741 2000 103 Combat potion(3) +9743 2000 183 Combat potion(2) +9745 2000 67 Combat potion(1) +9843 500 30 Oak cape rack (flatpack) +9844 500 1 Teak cape rack (flatpack) +9845 500 1389 Mahogany cape rack (flatpack) +9846 500 350000 Gilded cape rack (flatpack) +9847 500 366734 Marble cape rack (flatpack) +9848 500 906107 Magic cape rack (flatpack) +9849 500 7507 Oak toy box (flatpack) +9850 500 47 Teak toy box (flatpack) +9851 500 25490 Mahogany toy box (flatpack) +9852 500 500 Oak magic wardrobe (flatpack) +9853 500 5000 Carved oak magic wardrobe (flatpack) +9854 500 1283 Teak magic wardrobe (flatpack) +9855 500 2500 Carved teak magic wardrobe (flatpack) +9856 500 1072 Mahogany magic wardrobe (flatpack) +9857 500 235000 Gilded magic wardrobe (flatpack) +9858 500 361398 Marble magic wardrobe (flatpack) +9859 500 25500 Oak armour case (flatpack) +9860 500 3499 Teak armour case (flatpack) +9861 500 4535 Mahogany armour case (flatpack) +9862 500 522 Oak treasure chest (flatpack) +9863 500 70000 Teak treasure chest (flatpack) +9864 500 6134 M. treasure chest (flatpack) +9865 500 22 Oak fancy dress box (flatpack) +9866 500 8000 Teak fancy dress box (flatpack) +9867 500 3699 Mahogany fancy dress box (flatpack) +9978 11000 53 Raw bird meat +9980 6000 18 Roast bird meat +9986 11000 10 Raw beast meat +9988 6000 1989 Roast beast meat +9994 8000 1145 Spicy tomato +9996 8000 1125 Spicy minced meat +9998 2000 645 Hunter potion(4) +10000 2000 246 Hunter potion(3) +10002 2000 504 Hunter potion(2) +10004 2000 1 Hunter potion(1) +10006 250 32 Bird snare +10008 250 50 Box trap +10010 125 40 Butterfly net +10012 40 30 Butterfly jar +10014 125 162 Black warlock (item) +10016 125 1575 Snowy knight (item) +10018 125 72 Sapphire glacialis (item) +10020 125 64 Ruby harvest (item) +10025 250 498 Magic box +10029 40 4 Teasing stick +10031 250 11 Rabbit snare +10033 7000 922 Chinchompa +10034 7000 1440 Red chinchompa +10035 150 594 Kyatt legs +10037 150 267 Kyatt top +10039 150 3335 Kyatt hat +10041 150 140 Larupia legs +10043 150 489 Larupia top +10045 150 4292 Larupia hat +10047 150 680 Graahk legs +10049 150 648 Graahk top +10051 150 1038 Graahk headdress +10053 0 5271 Wood camo top +10055 0 6565 Wood camo legs +10057 0 1146 Jungle camo top +10059 0 4513 Jungle camo legs +10061 0 3549 Desert camo top +10063 0 6410 Desert camo legs +10065 0 9077 Polar camo top +10067 0 11583 Polar camo legs +10069 125 2477 Spotted cape +10071 125 908 Spottier cape +10075 8000 1894 Gloves of silence +10077 125 50100 Spiky vambraces +10079 125 1052 Green spiky vambraces +10081 125 1904 Blue spiky vambraces +10083 125 1732 Red spiky vambraces +10085 125 2671 Black spiky vambraces +10087 8000 17 Stripy feather +10088 8000 12 Red feather +10089 8000 10 Blue feather +10090 8000 86 Yellow feather +10091 8000 9 Orange feather +10093 10000 61 Tatty larupia fur +10095 10000 337 Larupia fur +10097 10000 206 Tatty graahk fur +10099 10000 338 Graahk fur +10101 10000 20 Tatty kyatt fur +10103 10000 585 Kyatt fur +10105 10000 965 Kebbit spike +10107 10000 10 Long kebbit spike +10109 10000 674 Kebbit teeth +10111 10000 625 Kebbit teeth dust +10113 10000 99 Kebbit claws +10115 10000 110 Dark kebbit fur +10117 100 2937 Polar kebbit fur +10119 10000 142 Feldip weasel fur +10121 10000 1025 Common kebbit fur +10123 10000 769 Desert devil fur +10125 10000 78 Spotted kebbit fur +10127 10000 122 Dashing kebbit fur +10129 40 187 Barb-tail harpoon +10132 150 1879 Strung rabbit foot +10134 100 1474 Rabbit foot +10136 6000 120 Rainbow fish +10138 13000 233 Raw rainbow fish +10142 7000 4 Guam tar +10143 7000 2 Marrentill tar +10144 7000 2 Tarromin tar +10145 11000 4 Harralander tar +10146 125 165 Orange salamander +10147 125 6 Red salamander +10148 125 235 Black salamander +10149 125 28 Swamp lizard +10150 40 30 Noose wand +10156 70 1488 Hunters' crossbow +10158 11000 110 Kebbit bolts +10159 11000 13 Long kebbit bolts +10280 8 3852 Willow comp bow +10282 8 1309 Yew comp bow +10284 8 1055 Magic comp bow +10286 70 52491 Rune helm (h1) +10288 70 20504 Rune helm (h2) +10290 70 20965 Rune helm (h3) +10292 70 20524 Rune helm (h4) +10294 70 22198 Rune helm (h5) +10296 70 1983 Adamant helm (h1) +10298 70 2842 Adamant helm (h2) +10300 70 3554 Adamant helm (h3) +10302 70 2582 Adamant helm (h4) +10304 70 2360 Adamant helm (h5) +10306 70 46999 Black helm (h1) +10308 70 4793 Black helm (h2) +10310 70 2432 Black helm (h3) +10312 70 2323 Black helm (h4) +10314 70 2000 Black helm (h5) +10316 4 10354 Bob's red shirt +10318 4 19000 Bob's blue shirt +10320 4 11361 Bob's green shirt +10322 4 33420 Bob's black shirt +10324 4 12316 Bob's purple shirt +10326 250 60 Purple firelighter +10327 250 76 White firelighter +10330 8 30249995 3rd Age range top +10332 8 14763285 3rd Age range legs +10334 8 16825999 3rd Age range coif +10336 8 10888500 3rd Age vambraces +10338 8 122444923 3rd Age robe top +10340 8 32125374 3rd Age robe +10342 8 15927771 3rd Age mage hat +10344 8 42961421 3rd Age amulet +10346 8 82475263 3rd Age platelegs +10348 8 140216536 3rd Age platebody +10350 8 27798044 3rd Age full helmet +10352 8 66375725 3rd Age kiteshield +10354 5 45942 Amulet of glory (t4) +10362 5 51158 Amulet of glory (t) +10364 5 20193 Strength amulet (t) +10366 5 12375 Amulet of magic (t) +10368 8 64827 Zamorak bracers +10370 8 106776 Zamorak d'hide body +10372 8 163963 Zamorak chaps +10374 8 33825 Zamorak coif +10376 8 10332 Guthix bracers +10378 8 92584 Guthix d'hide body +10380 8 133806 Guthix chaps +10382 8 69990 Guthix coif +10384 8 16199 Saradomin bracers +10386 8 82005 Saradomin d'hide body +10388 8 154610 Saradomin chaps +10390 8 31772 Saradomin coif +10392 4 3474 A powdered wig +10394 4 2401389 Flared trousers +10396 4 1227 Pantaloons +10398 4 10760 Sleeping cap +10400 4 3734 Black elegant shirt +10402 4 1091 Black elegant legs +10404 4 9200 Red elegant shirt +10406 4 5574 Red elegant legs +10408 4 6541 Blue elegant shirt +10410 4 7772 Blue elegant legs +10412 4 9778 Green elegant shirt +10414 4 6330 Green elegant legs +10416 4 899 Purple elegant shirt +10418 4 7722 Purple elegant legs +10420 4 3280 White elegant blouse +10422 4 935 White elegant skirt +10424 4 19629 Red elegant blouse +10426 4 4691 Red elegant skirt +10428 4 33683 Blue elegant blouse +10430 4 7668 Blue elegant skirt +10432 4 26853 Green elegant blouse +10434 4 7732 Green elegant skirt +10436 4 1165 Purple elegant blouse +10438 4 494 Purple elegant skirt +10440 8 3075 Saradomin crozier +10442 8 3343 Guthix crozier +10444 8 2523 Zamorak crozier +10446 8 17887 Saradomin cloak +10448 8 10299 Guthix cloak +10450 8 18000 Zamorak cloak +10452 8 13132 Saradomin mitre +10454 8 8547 Guthix mitre +10456 8 17322 Zamorak mitre +10458 8 21199 Saradomin robe top +10460 8 58218 Zamorak robe top +10462 8 27391 Guthix robe top +10464 8 30785 Saradomin robe legs +10466 8 15498 Guthix robe legs +10468 8 39229 Zamorak robe legs +10470 8 3894 Saradomin stole +10472 8 11500 Guthix stole +10474 8 4553 Zamorak stole +10476 10000 4790 Purple sweets +10496 15 752 Polished buttons +10564 70 51954 Granite body +10589 70 47500 Granite helm +10808 11000 2051 Arctic pyre logs +10810 11000 664 Arctic pine logs +10812 11000 1011 Split log +10814 13000 103 Hair +10816 11000 305 Raw yak meat +10818 13000 340 Yak-hide +10820 13000 300 Cured yak-hide +10822 70 1392 Yak-hide armour (top) +10824 70 660 Yak-hide armour (legs) +10826 70 1988 Neitiznot shield +10828 70 48323 Helm of Neitiznot +10891 15 3400 Wooden cat +10925 2000 18500 Sanfew serum(4) +10927 2000 13292 Sanfew serum(3) +10929 2000 8421 Sanfew serum(2) +10931 2000 5130 Sanfew serum(1) +10937 11000 842 Nail beast nails +10952 40 99 Slayer bell +10954 70 513 Frog-leather body +10956 70 2794 Frog-leather chaps +10958 70 471 Frog-leather boots +10973 10000 239 Light orb +10978 13000 206 Swamp weed +10981 11000 1250 Cave goblin wire +10999 15 508 Goblin book +11037 8 399158 Brine sabre +11061 8 5699 Ancient mace +11065 40 286 Bracelet mould +11069 18000 221 Gold bracelet +11072 10000 468 Sapphire bracelet +11074 10000 447 Bracelet of clay +11076 10000 669 Emerald bracelet +11079 10000 618 Castle wars bracelet(3) +11085 10000 1118 Ruby bracelet +11088 10000 1085 Inoculation bracelet +11090 10000 2190 Phoenix necklace +11092 10000 1920 Diamond bracelet +11095 10000 2076 Abyssal bracelet(5) +11105 10000 11957 Skills necklace(4) +11113 10000 11566 Skills necklace +11115 10000 11427 Dragonstone bracelet +11118 10000 12100 Combat bracelet(4) +11126 10000 12122 Combat bracelet +11128 70 2873087 Berserker necklace +11130 10000 2611630 Onyx bracelet +11133 70 2843784 Regen bracelet +11200 50 35087 Dwarven helmet +11205 15 875 Shrunk ogleroot +11212 11000 1822 Dragon arrow +11227 11000 1800 Dragon arrow(p) +11228 11000 1749 Dragon arrow(p+) +11229 11000 1935 Dragon arrow(p++) +11230 11000 1053 Dragon dart +11231 11000 1081 Dragon dart(p) +11232 11000 1291 Dragon dart tip +11233 11000 958 Dragon dart(p+) +11234 11000 1686 Dragon dart(p++) +11235 8 520401 Dark bow +11237 10000 1890 Dragon arrowtips +11238 18000 2106 Baby impling jar +11240 18000 4526 Young impling jar +11242 18000 4495 Gourmet impling jar +11244 18000 1543 Earth impling jar +11246 18000 2228 Essence impling jar +11248 18000 4653 Eclectic impling jar +11250 18000 4370 Nature impling jar +11252 18000 23829 Magpie impling jar +11254 18000 40513 Ninja impling jar +11256 18000 462992 Dragon impling jar +11260 13000 320 Impling jar +11280 150 107137 Cavalier mask +11284 8 3179555 Dragonfire shield +11286 5 3299999 Draconic visage +11324 13000 37 Roe +11326 13000 65 Caviar +11328 13000 6 Leaping trout +11330 13000 5 Leaping salmon +11332 13000 9 Leaping sturgeon +11334 8000 14 Fish offcuts +11335 8 48061987 Dragon full helm +11367 125 2280 Bronze hasta +11369 125 5149 Iron hasta +11371 125 35 Steel hasta +11373 125 94 Mithril hasta +11375 125 937 Adamant hasta +11377 70 11947 Rune hasta +11379 125 27111 Bronze hasta(p) +11382 125 108000 Bronze hasta(p+) +11384 125 2449 Bronze hasta(p++) +11386 125 247750 Iron hasta(p) +11389 125 2506 Iron hasta(p+) +11391 125 1710 Iron hasta(p++) +11393 125 504 Steel hasta(p) +11396 125 2743 Steel hasta(p+) +11398 125 5913 Steel hasta(p++) +11400 125 405 Mithril hasta(p) +11403 125 3525 Mithril hasta(p+) +11405 125 3828 Mithril hasta(p++) +11407 125 1646 Adamant hasta(p) +11410 125 3907 Adamant hasta(p+) +11412 125 6415 Adamant hasta(p++) +11414 70 18420 Rune hasta(p) +11417 70 19750 Rune hasta(p+) +11419 70 12671 Rune hasta(p++) +11429 2000 25 Attack mix(2) +11431 2000 5086 Attack mix(1) +11433 2000 245 Antipoison mix(2) +11435 2000 296 Antipoison mix(1) +11437 2000 335 Relicym's mix(2) +11439 2000 37633 Relicym's mix(1) +11441 2000 145 Strength mix(1) +11443 2000 20 Strength mix(2) +11445 2000 100 Combat mix(2) +11447 2000 240 Combat mix(1) +11449 2000 55 Restore mix(2) +11451 2000 82 Restore mix(1) +11453 2000 54 Energy mix(2) +11455 2000 290 Energy mix(1) +11457 2000 28063 Defence mix(2) +11459 2000 247512 Defence mix(1) +11461 2000 260 Agility mix(2) +11463 2000 473 Agility mix(1) +11465 2000 4105 Prayer mix(2) +11467 2000 1756 Prayer mix(1) +11469 2000 165 Superattack mix(2) +11471 2000 16 Superattack mix(1) +11473 2000 539 Anti-poison supermix(2) +11475 2000 456 Anti-poison supermix(1) +11477 2000 81 Fishing mix(2) +11479 2000 77 Fishing mix(1) +11481 2000 125 Super energy mix(2) +11483 2000 998 Super energy mix(1) +11485 2000 744 Super str. mix(2) +11487 2000 251 Super str. mix(1) +11489 2000 193916 Magic essence mix(2) +11491 2000 10 Magic essence mix(1) +11493 2000 3837 Super restore mix(2) +11495 2000 1640 Super restore mix(1) +11497 2000 541 Super def. mix(2) +11499 2000 200 Super def. mix(1) +11501 2000 599 Antidote+ mix(2) +11503 2000 100981 Antidote+ mix(1) +11505 2000 82 Antifire mix(2) +11507 2000 126 Antifire mix(1) +11509 2000 666 Ranging mix(2) +11511 2000 448 Ranging mix(1) +11513 2000 34 Magic mix(2) +11515 2000 199 Magic mix(1) +11517 2000 182 Hunting mix(2) +11519 2000 171 Hunting mix(1) +11521 2000 300 Zamorak mix(2) +11523 2000 89 Zamorak mix(1) +11785 8 35207549 Armadyl crossbow +11787 8 51778 Steam battlestaff +11789 8 66179 Mystic steam staff +11791 8 5362366 Staff of the Dead +11798 10000 492489 Godsword blade +11802 8 7677363 Armadyl godsword +11804 8 17889467 Bandos godsword +11806 8 25408772 Saradomin godsword +11808 8 20060971 Zamorak godsword +11810 10000 6886985 Armadyl hilt +11812 10000 17305000 Bandos hilt +11814 10000 24227780 Saradomin hilt +11816 10000 18967500 Zamorak hilt +11818 11000 155227 Godsword shard 1 +11820 11000 155374 Godsword shard 2 +11822 11000 158473 Godsword shard 3 +11824 8 1254481 Zamorakian spear +11826 8 9090924 Armadyl helmet +11828 8 36215572 Armadyl chestplate +11830 8 26962102 Armadyl chainskirt +11832 8 23475000 Bandos chestplate +11834 8 15761617 Bandos tassets +11836 8 749223 Bandos boots +11838 8 121809 Saradomin sword +11840 70 207537 Dragon boots +11874 7000 56 Broad arrowheads +11875 7000 53 Broad bolts +11876 7000 64 Unfinished broad bolts +11889 8 1480520 Zamorakian hasta +11902 70 39733 Leaf-bladed sword +11905 8 897135 Trident of the Seas (full) +11908 8 44675 Uncharged trident +11920 40 608736 Dragon pickaxe +11924 8 1909188 Malediction ward +11926 8 2810859 Odium ward +11928 10000 1703000 Odium shard 1 +11929 10000 493239 Odium shard 2 +11930 10000 235511 Odium shard 3 +11931 10000 1113000 Malediction shard 1 +11932 10000 392500 Malediction shard 2 +11933 10000 166022 Malediction shard 3 +11934 15000 1028 Raw dark crab +11936 10000 1375 Dark crab +11940 8000 4 Dark fishing bait +11943 7500 5909 Lava dragon bones +11951 2000 281 Extended antifire(4) +11953 2000 185 Extended antifire(3) +11955 2000 200 Extended antifire(2) +11957 2000 169 Extended antifire(1) +11959 11000 3122 Black chinchompa +11960 2000 215 Extended antifire mix(2) +11962 2000 191 Extended antifire mix(1) +11964 5 60686 Amulet of glory (t6) +11968 10000 11781 Skills necklace(6) +11972 10000 12224 Combat bracelet(6) +11978 10000 10117 Amulet of glory(6) +11980 10000 13254 Ring of wealth (5) +11990 4 5481 Fedora +11992 11000 417 Lava scale +11994 11000 88 Lava scale shard +11998 8 1894986 Smoke battlestaff +12000 8 1966519 Mystic smoke staff +12002 8 416996 Occult necklace +12004 70 259087 Kraken tentacle +12007 4 40466 Jar of Dirt +12193 8 28711 Ancient robe top +12195 8 19950 Ancient robe legs +12197 8 13192 Ancient cloak +12199 8 7150 Ancient crozier +12201 8 2519 Ancient stole +12203 8 13417 Ancient mitre +12205 4 25978 Bronze platebody (g) +12207 4 5049 Bronze platelegs (g) +12209 4 1650 Bronze plateskirt (g) +12211 4 43385 Bronze full helm (g) +12213 4 28479 Bronze kiteshield (g) +12215 4 2665 Bronze platebody (t) +12217 4 1604 Bronze platelegs (t) +12219 4 2681 Bronze plateskirt (t) +12221 4 4205 Bronze full helm (t) +12223 4 26976 Bronze kiteshield (t) +12225 4 31388 Iron platebody (t) +12227 4 4837 Iron platelegs (t) +12229 4 1129 Iron plateskirt (t) +12231 4 30844 Iron full helm (t) +12233 4 11575 Iron kiteshield (t) +12235 4 111900 Iron platebody (g) +12237 4 45301 Iron platelegs (g) +12239 4 6040 Iron plateskirt (g) +12241 4 105200 Iron full helm (g) +12243 4 43250 Iron kiteshield (g) +12245 4 8144 Beanie +12247 4 4198 Red beret +12249 4 4576 Imp mask +12251 4 34894 Goblin mask +12253 8 28446 Armadyl robe top +12255 8 20993 Armadyl robe legs +12257 8 2538 Armadyl stole +12259 8 10002 Armadyl mitre +12261 8 9129 Armadyl cloak +12263 8 3184 Armadyl crozier +12265 8 34408 Bandos robe top +12267 8 15499 Bandos robe legs +12269 8 2187 Bandos stole +12271 8 12206 Bandos mitre +12273 8 43908 Bandos cloak +12275 8 3326 Bandos crozier +12277 8 4202 Mithril platebody (g) +12279 8 2565 Mithril platelegs (g) +12281 8 6289 Mithril kiteshield (g) +12283 8 4383 Mithril full helm (g) +12285 8 2028 Mithril plateskirt (g) +12287 8 6376 Mithril platebody (t) +12289 8 3508 Mithril platelegs (t) +12291 8 2237 Mithril kiteshield (t) +12293 8 4030 Mithril full helm (t) +12295 8 2586 Mithril plateskirt (t) +12297 4 1805 Black pickaxe +12299 4 545 White headband +12301 4 3650 Blue headband +12303 4 460 Gold headband +12305 4 5538 Pink headband +12307 4 583 Green headband +12309 4 4145 Pink boater +12311 4 1670 Purple boater +12313 4 1102 White boater +12315 4 10739 Pink elegant shirt +12317 4 1533 Pink elegant legs +12319 4 2216 Crier hat +12321 4 2278 White cavalier +12323 4 6027 Red cavalier +12325 4 1895 Navy cavalier +12327 8 5923 Red d'hide body (g) +12329 8 4592 Red d'hide chaps (g) +12331 8 5928 Red d'hide body (t) +12333 8 2770 Red d'hide chaps (t) +12335 4 120607 Briefcase +12337 4 371498 Sagacious spectacles +12339 4 1593 Pink elegant blouse +12341 4 2269 Pink elegant skirt +12343 4 2530 Gold elegant blouse +12345 4 1127 Gold elegant skirt +12347 4 1472 Gold elegant shirt +12349 4 429 Gold elegant legs +12351 4 40949 Musketeer hat +12353 4 2416 Monocle +12355 4 62935 Big pirate hat +12357 8 32740 Katana +12359 4 5184 Leprechaun hat +12361 4 14744 Cat mask +12363 4 5920 Bronze dragon mask +12365 4 5664 Iron dragon mask +12367 4 4610 Steel dragon mask +12369 4 4379 Mithril dragon mask +12371 4 814774 Lava dragon mask +12373 4 27685 Dragon cane +12375 4 2544 Black cane +12377 4 232 Adamant cane +12379 4 7671 Rune cane +12381 8 15647 Black d'hide body (g) +12383 8 65143 Black d'hide chaps (g) +12385 8 12226 Black d'hide body (t) +12387 8 39011 Black d'hide chaps (t) +12389 8 11911678 Gilded scimitar +12391 8 977612 Gilded boots +12393 4 208 Royal gown top +12395 4 4759 Royal gown bottom +12397 4 110838 Royal crown +12399 4 428761 Partyhat & specs +12402 10000 14279 Nardah teleport +12403 10000 4745 Digsite teleport +12404 10000 927 Feldip hills teleport +12405 10000 2634 Lunar isle teleport +12406 10000 13954 Mort'ton teleport +12407 10000 406 Pest control teleport +12408 10000 2988 Piscatoris teleport +12409 10000 3893 Tai bwo wannai teleport +12410 10000 6079 Iorwerth camp teleport +12411 10000 925 Mos le'harmless teleport +12412 150 107000 Pirate hat & patch +12422 8 255005000 3rd Age wand +12424 8 984000000 3rd Age bow +12426 8 940999999 3rd Age longsword +12428 4 34746 Penguin mask +12430 4 20398 Afro +12432 4 105408 Top hat +12434 4 103971 Top hat & monocle +12437 8 280000000 3rd Age cloak +12439 4 16604 Royal sceptre +12441 4 11092 Musketeer tabard +12443 4 5894 Musketeer pants +12445 4 25975 Black skirt (g) +12447 4 12750 Black skirt (t) +12449 4 170853 Black wizard robe (g) +12451 4 58367 Black wizard robe (t) +12453 4 192457 Black wizard hat (g) +12455 4 87170 Black wizard hat (t) +12460 8 40409 Ancient platebody +12462 8 39769 Ancient platelegs +12464 8 37118 Ancient plateskirt +12466 8 21207 Ancient full helm +12468 8 32375 Ancient kiteshield +12470 8 42409 Armadyl platebody +12472 8 37432 Armadyl platelegs +12474 8 37565 Armadyl plateskirt +12476 8 21548 Armadyl full helm +12478 8 32534 Armadyl kiteshield +12480 8 61256 Bandos platebody +12482 8 37681 Bandos platelegs +12484 8 37667 Bandos plateskirt +12486 8 23526 Bandos full helm +12488 8 32317 Bandos kiteshield +12490 8 16240 Ancient bracers +12492 8 90712 Ancient d'hide body +12494 8 108578 Ancient chaps +12496 8 49428 Ancient coif +12498 8 11702 Bandos bracers +12500 8 74938 Bandos d'hide body +12502 8 115500 Bandos chaps +12504 8 42242 Bandos coif +12506 8 37576 Armadyl bracers +12508 8 87006 Armadyl d'hide body +12510 8 148482 Armadyl chaps +12512 8 46796 Armadyl coif +12514 4 2361 Explorer backpack +12516 4 637 Pith helmet +12518 4 6519 Green dragon mask +12520 4 5066 Blue dragon mask +12522 4 2591 Red dragon mask +12524 4 45005 Black dragon mask +12526 4 237912 Fury ornament kit +12528 4 10398 Dark infinity colour kit +12530 4 4525 Light infinity colour kit +12532 4 7221 Dragon sq shield ornament kit +12534 4 45607 Dragon chainbody ornament kit +12536 4 2875 Dragon legs/skirt ornament kit +12538 4 15491 Dragon full helm ornament kit +12540 4 20890 Deerstalker +12596 4 2113895 Rangers' tunic +12598 4 850301 Holy sandals +12601 8 961578 Ring of the gods +12603 8 196520 Tyrannical ring +12605 8 91083 Treasonous ring +12613 5 901 Bandos page 1 +12614 5 841 Bandos page 2 +12615 5 900 Bandos page 3 +12616 5 987 Bandos page 4 +12617 5 3807 Armadyl page 1 +12618 5 3884 Armadyl page 2 +12619 5 2654 Armadyl page 3 +12620 5 2174 Armadyl page 4 +12621 5 1742 Ancient page 1 +12622 5 1524 Ancient page 2 +12623 5 1902 Ancient page 3 +12624 5 1319 Ancient page 4 +12625 2000 3982 Stamina potion(4) +12627 2000 3074 Stamina potion(3) +12629 2000 1986 Stamina potion(2) +12631 2000 999 Stamina potion(1) +12633 2000 1276 Stamina mix(2) +12635 2000 1405 Stamina mix(1) +12640 11000 521 Amylase crystal +12642 15000 4907 Lumberyard teleport +12695 2000 12157 Super combat potion(4) +12697 2000 9303 Super combat potion(3) +12699 2000 6265 Super combat potion(2) +12701 2000 3214 Super combat potion(1) +12746 0 50000 Archaic emblem (tier 1) +12751 0 50004 Archaic emblem (tier 5) +12757 4 260878 Blue dark bow paint +12759 4 229507 Green dark bow paint +12761 4 97451 Yellow dark bow paint +12763 4 879766 White dark bow paint +12769 4 1101000 Frozen whip mix +12771 4 1174620 Volcanic whip mix +12775 10000 3127 Annakarl teleport (tablet) +12776 10000 1692 Carrallanger teleport (tablet) +12777 10000 1604 Dareeyak teleport (tablet) +12778 10000 2335 Ghorrock teleport (tablet) +12779 10000 1942 Kharyrll teleport (tablet) +12780 10000 1771 Lassar teleport (tablet) +12781 10000 140 Paddewwa teleport (tablet) +12782 10000 1874 Senntisten teleport (tablet) +12783 10000 2947 Ring of wealth scroll +12786 10000 44216 Magic shortbow scroll +12789 50 189730 Clue box +12798 50 575167 Steam staff upgrade kit +12800 50 647927 Dragon pickaxe upgrade kit +12802 50 894160 Ward upgrade kit +12804 50 4892097 Saradomin's tear +12817 8 500790461 Elysian spirit shield +12819 5 495683499 Elysian sigil +12821 8 25205134 Spectral spirit shield +12823 5 23584578 Spectral sigil +12825 8 135440010 Arcane spirit shield +12827 5 136585987 Arcane sigil +12829 8 92962 Spirit shield +12831 8 990049 Blessed spirit shield +12833 5 880416 Holy elixir +12846 5 7175000 Target teleport scroll +12849 50 926766 Granite clamp +12851 8 33931 Amulet of the Damned (full) +12863 40 762310 Dwarf cannon set +12865 125 8088 Green dragonhide set +12867 125 10724 Blue dragonhide set +12869 70 17787 Red dragonhide set +12871 70 14808 Black dragonhide set +12873 8 716575 Guthan's armour set +12875 8 842132 Verac's armour set +12877 8 3996692 Dharok's armour set +12879 8 1092247 Torag's armour set +12881 8 3550526 Ahrim's armour set +12883 8 1490164 Karil's armour set +12885 5 673003 Jar of Sand +12900 8 3173307 Uncharged toxic trident +12902 8 8410918 Toxic staff (uncharged) +12905 2000 10328 Anti-venom(4) +12907 2000 7947 Anti-venom(3) +12909 2000 5625 Anti-venom(2) +12911 2000 2565 Anti-venom(1) +12913 2000 14417 Anti-venom+(4) +12915 2000 11393 Anti-venom+(3) +12917 2000 8728 Anti-venom+(2) +12919 2000 3146 Anti-venom+(1) +12922 5 9526055 Tanzanite fang +12924 8 9662270 Toxic blowpipe (empty) +12927 5 3070429 Serpentine visage +12929 8 3153989 Serpentine helm (uncharged) +12932 5 3160832 Magic fang +12934 30000 156 Zulrah's scales +12936 4 45337 Jar of Swamp +12938 10000 12568 Zul-andra teleport +12960 8 357 Bronze set (lg) +12962 8 472 Bronze set (sk) +12964 8 37363 Bronze trimmed set (lg) +12966 8 35000 Bronze trimmed set (sk) +12968 8 116516 Bronze gold-trimmed set (lg) +12970 8 147361 Bronze gold-trimmed set (sk) +12972 8 475 Iron set (lg) +12974 8 500 Iron set (sk) +12976 8 85132 Iron trimmed set (lg) +12978 8 97117 Iron trimmed set (sk) +12980 8 350542 Iron gold-trimmed set (lg) +12982 8 342937 Iron gold-trimmed set (sk) +12984 8 1671 Steel set (lg) +12986 8 2826 Steel set (sk) +12988 8 5608 Black set (lg) +12990 8 7233 Black set (sk) +12992 8 68833 Black trimmed set (lg) +12994 8 90683 Black trimmed set (sk) +12996 8 338790 Black gold-trimmed set (lg) +12998 8 283906 Black gold-trimmed set (sk) +13000 8 4650 Mithril set (lg) +13002 8 7569 Mithril set (sk) +13004 8 25193 Mithril trimmed set (lg) +13006 8 22929 Mithril trimmed set (sk) +13008 8 21588 Mithril gold-trimmed set (lg) +13010 8 31301 Mithril gold-trimmed set (sk) +13012 8 17225 Adamant set (lg) +13014 8 19333 Adamant set (sk) +13016 8 47541 Adamant trimmed set (lg) +13018 8 32500 Adamant trimmed set (sk) +13020 8 63996 Adamant gold-trimmed set (lg) +13022 8 47730 Adamant gold-trimmed set (sk) +13024 8 131948 Rune armour set (lg) +13026 8 142599 Rune armour set (sk) +13028 8 132600 Rune trimmed set (lg) +13030 8 149925 Rune trimmed set (sk) +13032 8 252647 Rune gold-trimmed set (lg) +13034 8 248395 Rune gold-trimmed set (sk) +13036 8 30282496 Gilded armour set (lg) +13038 8 25651237 Gilded armour set (sk) +13040 8 226688 Saradomin armour set (lg) +13042 8 254105 Saradomin armour set (sk) +13044 8 596263 Zamorak armour set (lg) +13046 8 580369 Zamorak armour set (sk) +13048 8 140356 Guthix armour set (lg) +13050 8 154694 Guthix armour set (sk) +13052 8 143000 Armadyl rune armour set (lg) +13054 8 131997 Armadyl rune armour set (sk) +13056 8 179381 Bandos rune armour set (lg) +13058 8 175000 Bandos rune armour set (sk) +13060 8 162537 Ancient rune armour set (lg) +13062 8 164403 Ancient rune armour set (sk) +13064 2000 355 Combat potion set +13066 2000 9216 Super potion set +13149 5 13876 Holy book page set +13151 5 27572 Unholy book page set +13153 5 13085 Book of Balance page set +13155 5 11933 Book of War page set +13157 5 22292 Book of Law page set +13159 5 16593 Book of Darkness page set +13161 8 420326 Zamorak dragonhide set +13163 8 304944 Saradomin dragonhide set +13165 8 306694 Guthix dragonhide set +13167 8 289060 Bandos dragonhide set +13169 8 314068 Armadyl dragonhide set +13171 8 307499 Ancient dragonhide set +13173 5 110756 Partyhat set +13175 5 30301 Halloween mask set +13190 100 11106256 Old school bond +13227 15 3067165 Eternal crystal +13229 15 105005 Pegasian crystal +13231 15 18200161 Primordial crystal +13233 5 3685036 Smouldering stone +13235 15 4101276 Eternal boots +13237 15 35360575 Pegasian boots +13239 15 18571694 Primordial boots +13245 4 49371 Jar of Souls +13249 0 25306 Key master teleport +13256 5 58000 Saradomin's light +13263 8 17301723 Abyssal bludgeon +13265 8 1002199 Abyssal dagger +13267 8 1084853 Abyssal dagger (p) +13269 8 1055802 Abyssal dagger (p+) +13271 8 1025254 Abyssal dagger (p++) +13277 4 28199 Jar of Miasma +13383 13000 1528 Xerician fabric +13385 125 5550 Xerician hat +13387 125 6919 Xerician top +13389 125 7317 Xerician robe +13391 13000 10 Lizardman fang +13421 13000 5 Saltpetre +13431 8000 101 Sandworms +13439 15000 1582 Raw anglerfish +13441 10000 1774 Anglerfish +13448 3000 230 Ensouled goblin head +13451 3000 563 Ensouled monkey head +13454 3000 1307 Ensouled imp head +13457 3000 643 Ensouled minotaur head +13460 3000 556 Ensouled scorpion head +13463 3000 544 Ensouled bear head +13466 3000 800 Ensouled unicorn head +13469 3000 2151 Ensouled dog head +13472 3000 318 Ensouled chaos druid head +13475 3000 483 Ensouled giant head +13478 3000 725 Ensouled ogre head +13481 7500 1041 Ensouled elf head +13484 7500 1665 Ensouled troll head +13487 7500 2859 Ensouled horror head +13490 7500 969 Ensouled kalphite head +13493 7500 1220 Ensouled dagannoth head +13496 3000 1800 Ensouled bloodveld head +13499 3000 2378 Ensouled tzhaar head +13502 7500 3902 Ensouled demon head +13505 7500 2198 Ensouled aviansie head +13508 3000 2760 Ensouled abyssal head +13511 7500 5857 Ensouled dragon head +13573 13000 175 Dynamite +13576 8 16017315 Dragon warhammer +13652 8 38753326 Dragon claws +13657 200 4822 Grape seed +13658 500 245 Teleport card +19478 8 619096 Light ballista +19481 8 956554 Heavy ballista +19484 11000 565 Dragon javelin +19486 11000 816 Dragon javelin(p) +19488 11000 870 Dragon javelin(p+) +19490 11000 1157 Dragon javelin(p++) +19493 11000 19499500 Zenyte +19496 10000 20148495 Uncut zenyte +19501 10000 15847465 Zenyte amulet (u) +19529 11000 16915833 Zenyte shard +19532 8 20066666 Zenyte bracelet +19535 8 20604993 Zenyte necklace +19538 8 20622221 Zenyte ring +19541 8 19498962 Zenyte amulet +19544 8 20230985 Tormented bracelet +19547 8 19973276 Necklace of anguish +19550 8 20041677 Ring of suffering +19553 8 19905527 Amulet of torture +19570 10000 24 Bronze javelin tips +19572 10000 102 Iron javelin tips +19574 10000 96 Steel javelin tips +19576 10000 182 Mithril javelin tips +19578 10000 393 Adamant javelin tips +19580 10000 371 Rune javelin tips +19582 10000 705 Dragon javelin tips +19584 7000 1 Javelin shaft +19586 8 28953 Light frame +19589 8 306147 Heavy frame +19592 8 30451 Ballista limbs +19595 8 53788 Incomplete light ballista +19598 8 400000 Incomplete heavy ballista +19601 8 28870 Ballista spring +19604 8 73410 Unstrung light ballista +19607 8 424998 Unstrung heavy ballista +19610 8 519000 Monkey tail +19613 10000 2176 Arceuus library teleport (tablet) +19615 10000 2350 Draynor manor teleport (tablet) +19617 10000 2372 Mind altar teleport (tablet) +19619 10000 2811 Salve graveyard teleport (tablet) +19621 15000 2657 Fenkenstrain's castle teleport (tablet) +19623 10000 3560 West ardougne teleport (tablet) +19625 10000 3042 Harmony island teleport (tablet) +19627 10000 2912 Cemetery teleport (tablet) +19629 10000 4117 Barrows teleport (tablet) +19631 10000 4625 Ape atoll teleport (tablet) +19653 100 769 Golovanova fruit top +19656 13000 1946 Uncooked botanical pie +19662 10000 711 Botanical pie +19665 13000 118 Damaged monkey tail +19669 12000 716 Redwood logs +19672 11000 4875 Redwood pyre logs +19701 4 186581 Jar of Darkness +19707 5 59365832 Amulet of eternal glory +19724 4 2638 Left eye patch +19727 4 9201 Double eye patch +19912 4 3948 Zombie head (Treasure Trails) +19915 4 2943 Cyclops head +19918 8 4159 Nunchaku +19921 8 161553 Ancient d'hide boots +19924 8 110547 Bandos d'hide boots +19927 8 108709 Guthix d'hide boots +19930 8 282514 Armadyl d'hide boots +19933 8 121410 Saradomin d'hide boots +19936 8 168822 Zamorak d'hide boots +19943 4 5131 Arceuus scarf +19946 4 6499 Hosidius scarf +19949 4 5303 Lovakengj scarf +19952 4 27492 Piscarilius scarf +19955 4 5263 Shayzien scarf +19958 4 1177851 Dark tuxedo jacket +19961 4 354819 Dark tuxedo cuffs +19964 4 768241 Dark trousers +19967 4 452762 Dark tuxedo shoes +19970 4 419983 Dark bow tie +19973 4 979444 Light tuxedo jacket +19976 4 320000 Light tuxedo cuffs +19979 4 357999 Light trousers +19982 4 519901 Light tuxedo shoes +19985 4 188203 Light bow tie +19988 125 22298 Blacksmith's helm +19991 4 15020 Bucket helm +19994 8 208217 Ranger gloves +19997 4 841668 Holy wraps +20002 4 121542 Dragon scimitar ornament kit +20005 4 6629798 Ring of nature +20008 4 4476 Fancy tiara +20011 40 2147483647 3rd Age axe +20014 40 2147483647 3rd Age pickaxe +20017 4 11200309 Ring of coins +20020 4 2727 Lesser demon mask +20023 4 2998 Greater demon mask +20026 4 31907 Black demon mask +20029 4 13363 Old demon mask +20032 4 12613 Jungle demon mask +20035 70 30500 Samurai kasa +20038 70 52308 Samurai shirt +20041 70 27955 Samurai gloves +20044 70 17056 Samurai greaves +20047 70 13259 Samurai boots +20050 70 370000 Obsidian cape (r) +20053 4 18389 Half moon spectacles +20056 4 169888 Ale of the gods +20059 4 2563865 Bucket helm (g) +20062 4 51050 Torture ornament kit +20065 4 2596531 Occult ornament kit +20068 4 85788 Armadyl godsword ornament kit +20071 4 2760903 Bandos godsword ornament kit +20074 4 5907278 Saradomin godsword ornament kit +20077 4 214353 Zamorak godsword ornament kit +20080 4 776132 Mummy's head +20083 4 1122000 Mummy's body +20086 4 341338 Mummy's hands +20089 4 779310 Mummy's legs +20092 4 405061 Mummy's feet +20095 4 8261997 Ankou mask +20098 4 13575534 Ankou top +20101 4 2218510 Ankou gloves +20104 4 6425739 Ankou's leggings +20107 4 2133096 Ankou socks +20110 4 33163 Bowl wig +20113 4 1921 Arceuus hood +20116 4 1341 Hosidius hood +20119 4 2199 Lovakengj hood +20122 4 3588 Piscarilius hood +20125 4 1000 Shayzien hood +20128 70 7051 Hood of darkness +20131 70 76731 Robe top of darkness +20134 70 5325 Gloves of darkness +20137 70 49000 Robe bottom of darkness +20140 70 5321 Boots of darkness +20143 4 19490 Dragon defender ornament kit +20146 70 1366531 Gilded med helm +20149 70 1981738 Gilded chainbody +20152 70 1091537 Gilded sq shield +20155 70 4050718 Gilded 2h sword +20158 70 775124 Gilded spear +20161 70 702063 Gilded hasta +20166 125 63312 Wooden shield (g) +20169 125 16910 Steel platebody (g) +20172 125 5389 Steel platelegs (g) +20175 125 2564 Steel plateskirt (g) +20178 125 10092 Steel full helm (g) +20181 125 3000 Steel kiteshield (g) +20184 125 11266 Steel platebody (t) +20187 125 39500 Steel platelegs (t) +20190 125 2546 Steel plateskirt (t) +20193 125 11500 Steel full helm (t) +20196 125 11930 Steel kiteshield (t) +20199 4 4093615 Monk's robe top (g) +20202 4 1069892 Monk's robe (g) +20205 4 378195 Golden chef's hat +20208 4 196741 Golden apron +20211 4 2780494 Team cape zero +20214 4 1203261 Team cape x +20217 4 1120683 Team cape i +20220 5 1108 Holy blessing +20223 5 9340 Unholy blessing +20226 5 2765 Peaceful blessing +20229 5 5874 Honourable blessing +20232 5 8755 War blessing +20235 5 2091 Ancient blessing +20238 10000 170 Charge dragonstone jewellery scroll +20240 4 2368 Crier coat +20243 4 3569 Crier bell +20246 4 3256 Black leprechaun hat +20251 4 1534 Arceuus banner +20254 4 977 Hosidius banner +20257 4 1519 Lovakengj banner +20260 4 9187 Piscarilius banner +20263 4 454 Shayzien banner +20266 4 12014 Black unicorn mask +20269 4 51956 White unicorn mask +20272 125 4059 Cabbage round shield +20275 40 2902 Gnomish firelighter +20376 8 46031 Steel trimmed set (lg) +20379 8 47442 Steel trimmed set (sk) +20382 8 52437 Steel gold-trimmed set (lg) +20385 8 70531 Steel gold-trimmed set (sk) +20433 4 171613 Evil chicken feet +20436 4 92441 Evil chicken wings +20439 4 147328 Evil chicken head +20442 4 224832 Evil chicken legs +20517 8 280827 Elder chaos top +20520 8 248588 Elder chaos robe +20590 5 615842 Stale baguette +20595 8 268067 Elder chaos hood +20716 15 1938447 Tome of Fire (empty) +20718 11000 8096 Burnt page +20724 8 73104534 Imbued heart +20727 70 70119 Leaf-bladed battleaxe +20730 8 35154 Mist battlestaff +20733 8 60808 Mystic mist staff +20736 8 8674 Dust battlestaff +20739 8 37550 Mystic dust staff +20749 11000 1772 Zamorak's grapes +20756 125 1109000 Hill giant club +20849 11000 322 Dragon thrownaxe +20997 8 1391050660 Twisted bow +21000 8 12017074 Twisted buckler +21003 8 93379620 Elder maul +21006 8 57027835 Kodai wand +21009 70 79771 Dragon sword +21012 8 29440053 Dragon hunter crossbow +21015 8 11942846 Dinh's bulwark +21018 8 47048222 Ancestral hat +21021 8 96337500 Ancestral robe top +21024 8 68230529 Ancestral robe bottom +21028 5 2149752 Dragon harpoon +21034 5 15727999 Dexterous prayer scroll +21043 8 47975012 Kodai insignia +21047 5 47750 Torn prayer scroll +21049 5 213625000 Ancestral robes set +21079 5 5176838 Arcane prayer scroll +21081 10000 804 Opal ring +21084 10000 682 Jade ring +21087 10000 1028 Topaz ring +21090 10000 1021 Opal necklace +21093 10000 819 Jade necklace +21096 10000 1126 Topaz necklace +21099 10000 385 Opal amulet (u) +21102 10000 1095 Jade amulet (u) +21105 10000 1885 Topaz amulet (u) +21108 10000 303 Opal amulet +21111 10000 1455 Jade amulet +21114 10000 2198 Topaz amulet +21117 10000 559 Opal bracelet +21120 10000 601 Jade bracelet +21123 10000 2300 Topaz bracelet +21126 100 1647 Ring of pursuit +21129 10000 1597 Ring of returning(5) +21140 15000 1790 Efaritay's aid +21143 10000 1496 Dodgy necklace +21146 10000 1052 Necklace of passage(5) +21157 10000 504 Necklace of faith +21160 10000 689 Amulet of bounty +21163 15000 1824 Amulet of chemistry +21166 10000 2344 Burning amulet(5) +21177 15000 1048 Expeditious bracelet +21180 10000 1071 Flamtaer bracelet +21183 15000 2504 Bracelet of slaughter +21202 50 470632 Lava staff upgrade kit +21257 500 1107 Slayer's enchantment +21270 8 9621153 Eternal gem +21279 5 2836186 Obsidian armour set +21298 70 709813 Obsidian helmet +21301 70 1139581 Obsidian platebody +21304 70 915096 Obsidian platelegs +21316 11000 205 Amethyst broad bolts +21318 11000 244 Amethyst javelin +21320 7000 375 Amethyst javelin(p) +21322 7000 1070 Amethyst javelin(p+) +21324 7000 723 Amethyst javelin(p++) +21326 11000 126 Amethyst arrow +21332 11000 287 Amethyst arrow(p) +21334 11000 399 Amethyst arrow(p+) +21336 11000 706 Amethyst arrow(p++) +21338 11000 156 Amethyst bolt tips +21347 13000 2866 Amethyst +21350 10000 187 Amethyst arrowtips +21352 10000 538 Amethyst javelin tips +21387 100 62315 Master scroll book (empty) +21477 200 293 Teak sapling +21480 200 403 Mahogany sapling +21483 2000 499 Ultracompost +21486 200 62 Teak seed +21488 200 277 Mahogany seed +21490 600 190 Seaweed spore +21504 11000 66 Giant seaweed +21512 250 302 Bird house +21515 250 304 Oak bird house +21518 250 1223 Willow bird house +21521 250 1072 Teak bird house +21543 13000 94 Calcite +21545 13000 152 Pyrophosphite +21555 30000 35 Numulite +21622 13000 143 Volcanic ash +21626 100 11128 Sulliuscep cap +21634 8 28247307 Ancient wyvern shield +21637 5 30275482 Wyvern visage +21643 70 304079 Granite boots +21646 70 20605 Granite longsword +21649 125 1137369 Merfolk trident +21652 600 1668 Drift net +21684 13000 17862 Uncooked mushroom pie +21690 10000 11801 Mushroom pie +21730 5 481823 Black tourmaline core +21733 8 1377709 Guardian boots +21736 8 29086 Granite gloves +21739 70 26274 Granite ring +21742 8 7883345 Granite hammer +21745 4 1038888 Jar of Stone +21754 11000 261 Rock thrownhammer +21802 15000 593 Revenant cave teleport +21804 250 150572 Ancient crystal +21807 250 498365 Ancient emblem +21810 250 997751 Ancient totem +21813 250 1998000 Ancient statuette +21817 10000 42712 Bracelet of ethereum (uncharged) +21820 30000 173 Revenant ether +21838 125 59085 Shaman mask +21880 25000 347 Wrath rune +21882 8 114500000 Dragon armour set (lg) +21885 8 119949997 Dragon armour set (sk) +21892 70 1960227 Dragon platebody +21895 70 64645504 Dragon kiteshield +21902 70 594504 Dragon crossbow +21905 11000 2640 Dragon bolts +21918 10000 572000 Dragon limbs +21921 10000 584152 Dragon crossbow (u) +21924 11000 2113 Dragon bolts (p) +21926 11000 2149 Dragon bolts (p+) +21928 11000 3394 Dragon bolts (p++) +21930 13000 2624 Dragon bolts (unf) +21932 11000 2971 Opal dragon bolts (e) +21934 11000 930 Jade dragon bolts (e) +21936 11000 1679 Pearl dragon bolts (e) +21938 11000 1654 Topaz dragon bolts (e) +21940 11000 2932 Sapphire dragon bolts (e) +21942 11000 1554 Emerald dragon bolts (e) +21944 11000 2992 Ruby dragon bolts (e) +21946 11000 2831 Diamond dragon bolts (e) +21948 11000 2983 Dragonstone dragon bolts (e) +21950 11000 9649 Onyx dragon bolts (e) +21952 10000 304 Magic stock +21955 11000 2814 Opal dragon bolts +21957 11000 2040 Jade dragon bolts +21959 11000 1296 Pearl dragon bolts +21961 11000 1410 Topaz dragon bolts +21963 11000 2700 Sapphire dragon bolts +21965 11000 2625 Emerald dragon bolts +21967 11000 2827 Ruby dragon bolts +21969 11000 2831 Diamond dragon bolts +21971 11000 2952 Dragonstone dragon bolts +21973 11000 10251 Onyx dragon bolts +21975 11000 20325 Crushed superior dragon bones +21978 2000 19545 Super antifire potion(4) +21981 2000 14990 Super antifire potion(3) +21984 2000 10580 Super antifire potion(2) +21987 2000 4812 Super antifire potion(1) +21994 2000 52859 Super antifire mix(2) +21997 2000 110399 Super antifire mix(1) +22003 8 11828268 Dragonfire ward +22006 5 11607096 Skeletal visage +22097 50 1625000 Dragon metal shard +22100 50 58250000 Dragon metal slice +22103 50 135134 Dragon metal lump +22106 4 85213 Jar of Decay +22111 8 141702 Dragonbone necklace +22118 11000 293 Wrath talisman +22121 40 251 Wrath tiara +22124 7500 20150 Superior dragon bones +22192 250 712 Maple bird house +22195 250 1162 Mahogany bird house +22198 250 1176 Yew bird house +22201 250 2106 Magic bird house +22204 250 2302 Redwood bird house +22209 2000 18226 Extended super antifire(4) +22212 2000 14705 Extended super antifire(3) +22215 2000 9825 Extended super antifire(2) +22218 2000 3683 Extended super antifire(1) +22221 2000 40000 Extended super antifire mix(2) +22224 2000 101475 Extended super antifire mix(1) +22231 4 4973 Dragon boots ornament kit +22236 4 5453799 Dragon platebody ornament kit +22239 4 3836605 Dragon kiteshield ornament kit +22246 4 207291 Anguish ornament kit +22251 18000 22 Oak shield +22254 18000 17 Willow shield +22257 18000 33 Maple shield +22260 18000 231 Yew shield +22263 18000 550 Magic shield +22266 18000 735 Redwood shield +22269 125 103 Hard leather shield +22272 125 230 Snakeskin shield +22275 125 2741 Green d'hide shield +22278 125 6164 Blue d'hide shield +22281 125 10312 Red d'hide shield +22284 125 17138 Black d'hide shield +22290 8 2137404 Uncharged trident (e) +22294 8 5562327 Uncharged toxic trident (e) +22296 8 5334785 Staff of Light +22299 250 3996500 Ancient medallion +22302 250 7991798 Ancient effigy +22305 250 15988291 Ancient relic +22324 8 23960641 Ghrazi rapier +22326 8 12586410 Justiciar faceguard +22327 8 11245882 Justiciar chestguard +22328 8 9769315 Justiciar legguards +22368 8 4353164 Bryophyta's staff (uncharged) +22372 100 4241666 Bryophyta's essence +22430 2000 1738 Bloody bracer +22438 5 33826441 Justiciar armour set +22443 10000 11118 Cadantine blood potion (unf) +22446 13000 8378 Vial of blood +22449 2000 15064 Battlemage potion(4) +22452 2000 10135 Battlemage potion(3) +22455 2000 5129 Battlemage potion(2) +22458 2000 2440 Battlemage potion(1) +22461 2000 14742 Bastion potion(4) +22464 2000 11275 Bastion potion(3) +22467 2000 6049 Bastion potion(2) +22470 2000 3323 Bastion potion(1) +22477 8 30045487 Avernic defender hilt +22481 8 17803357 Sanguinesti staff (uncharged) +22486 8 1231200000 Scythe of Vitur (uncharged) +22542 8 4034511 Viggora's chainmace (u) +22547 8 14214395 Craw's bow (u) +22552 8 2844428 Thammaron's sceptre (u) +22557 8 415395 Amulet of avarice +22593 10000 98 Te salt +22595 10000 97 Efh salt +22597 10000 110 Urt salt +22599 10000 1229 Icy basalt +22601 10000 1224 Stony basalt +22603 10000 1863 Basalt +22610 10 300000 Vesta's spear +22613 10 300000 Vesta's longsword +22616 10 500000 Vesta's chainbody +22619 10 500000 Vesta's plateskirt +22622 10 300000 Statius's warhammer +22625 10 250000 Statius's full helm +22628 10 500000 Statius's platebody +22631 10 500000 Statius's platelegs +22634 100 4000 Morrigan's throwing axe +22636 100 20001 Morrigan's javelin +22638 10 250000 Morrigan's coif +22641 10 500000 Morrigan's leather body +22644 10 500000 Morrigan's leather chaps +22647 10 300000 Zuriel's staff +22650 10 250000 Zuriel's hood +22653 10 500000 Zuriel's robe top +22656 10 500000 Zuriel's robe bottom +22731 70 35014 Dragon hasta +22734 70 40275 Dragon hasta(p) +22737 70 39000 Dragon hasta(p+) +22740 70 47375 Dragon hasta(p++) +22780 7500 1980 Wyrm bones +22783 7500 5385 Drake bones +22786 7500 7947 Hydra bones +22789 10000 1506 Uncooked dragonfruit pie +22795 10000 389 Dragonfruit pie +22804 11000 3409 Dragon knife +22806 7000 3614 Dragon knife(p) +22808 7000 3566 Dragon knife(p+) +22810 7000 4084 Dragon knife(p++) +22818 13000 50 Fish chunks +22826 13000 85 Bluegill +22829 13000 100 Common tench +22832 13000 180 Mottled eel +22835 13000 250 Greater siren +22856 100 18492 Celastrus sapling +22859 100 14869 Redwood sapling +22866 200 146425 Dragonfruit sapling +22869 200 17247 Celastrus seed +22871 200 16800 Redwood tree seed +22873 200 20 Potato cactus seed +22877 200 142079 Dragonfruit tree seed +22879 200 6993 Snape grass seed +22887 600 2372 White lily seed +22929 11000 877 Dragonfruit +22932 600 508 White lily +22935 12000 7719 Celastrus bark +22949 10000 2514 Battlefront teleport (tablet) +22951 70 234084 Boots of Brimstone +22954 70 1002070 Devout boots +22957 15 227800 Drake's claw +22960 70 67650 Drake's tooth +22963 70 37350 Broken dragon hasta +22966 15 46008322 Hydra's claw +22975 8 1880832 Brimstone ring +22978 8 49153390 Dragon hunter lance +22983 15 9101470 Hydra leather +22988 15 79952 Hydra tail +22994 5 499874 Bottomless compost bucket +22999 50 9793 Bottled dragonbreath (unpowered) +23002 50 11472 Bottled dragonbreath +23037 125 328 Boots of stone +23047 70 45103 Mystic hat (dusk) +23050 70 109049 Mystic robe top (dusk) +23053 70 61744 Mystic robe bottom (dusk) +23056 70 48162 Mystic gloves (dusk) +23059 70 92577 Mystic boots (dusk) +23064 4 68671 Jar of Chemicals +23110 70 151477 Mystic set (light) +23113 70 145818 Mystic set (blue) +23116 70 257635 Mystic set (dark) +23119 70 365398 Mystic set (dusk) +23124 8 11583353 Gilded dragonhide set +23185 4 1296794 Ring of 3rd Age +23188 8 125067 Guthix d'hide shield +23191 8 126942 Saradomin d'hide shield +23194 8 127833 Zamorak d'hide shield +23197 8 144288 Ancient d'hide shield +23200 8 156603 Armadyl d'hide shield +23203 8 135610 Bandos d'hide shield +23206 8 1630 Dual sai +23209 8 38427 Rune platebody (h1) +23212 8 39217 Rune platebody (h2) +23215 8 38303 Rune platebody (h3) +23218 8 38103 Rune platebody (h4) +23221 8 51287 Rune platebody (h5) +23224 8 9569 Thieving bag +23227 4 7152 Rune defender ornament kit +23232 4 5125 Tzhaar-ket-om ornament kit +23237 4 3452 Berserker necklace ornament kit +23242 8 30012784 3rd Age plateskirt +23246 8 1860962 Fremennik kilt +23249 8 145734 Rangers' tights +23252 4 27527 Giant boot +23255 4 21872 Uri's hat +23258 8 383758 Gilded coif +23261 8 3966842 Gilded d'hide vambraces +23264 8 1654457 Gilded d'hide body +23267 8 5236822 Gilded d'hide chaps +23270 4 5226 Adamant dragon mask +23273 4 23003 Rune dragon mask +23276 4 3390717 Gilded pickaxe +23279 4 4277458 Gilded axe +23282 4 2231812 Gilded spade +23285 4 163751 Mole slippers +23288 4 24986 Frog slippers +23291 4 16576 Bear feet +23294 4 79500 Demon feet +23297 4 17811 Jester cape +23300 4 74101 Shoulder parrot +23303 4 32825 Monk's robe top (t) +23306 4 7138 Monk's robe (t) +23309 4 3331 Amulet of defence (t) +23312 4 1999 Sandwich lady hat +23315 4 3421 Sandwich lady top +23318 4 999 Sandwich lady bottom +23321 4 2718 Rune scimitar ornament kit (Guthix) +23324 4 3190 Rune scimitar ornament kit (Saradomin) +23327 4 3075 Rune scimitar ornament kit (Zamorak) +23336 4 2147483647 3rd Age druidic robe top +23339 4 1673834710 3rd Age druidic robe bottoms +23342 8 460221437 3rd Age druidic staff +23345 8 813327327 3rd Age druidic cloak +23348 4 30480 Tormented ornament kit +23351 8 3599140 Cape of skulls +23354 5 296783 Amulet of power (t) +23357 18000 88022 Rain bow +23360 100 1224309 Ham joint +23363 8 87436 Staff of Bob the Cat +23366 8 1383 Black platebody (h1) +23369 8 1442 Black platebody (h2) +23372 8 1752 Black platebody (h3) +23375 8 3045 Black platebody (h4) +23378 8 1861 Black platebody (h5) +23381 8 55235 Leather body (g) +23384 8 13769 Leather chaps (g) +23387 10000 4328 Watson teleport +23389 8 381101 Spiked manacles +23392 8 10132 Adamant platebody (h1) +23395 8 9330 Adamant platebody (h2) +23398 8 9360 Adamant platebody (h3) +23401 8 9114 Adamant platebody (h4) +23404 8 9189 Adamant platebody (h5) +23407 4 23396 Wolf mask +23410 4 16044 Wolf cloak +23413 4 46750 Climbing boots (g) +23490 250 120270 Larran's key +23499 11000 31639 Grubby key +23517 100 17422 Giant egg sac(full) +23522 70 8899 Mask of Ranul +23525 4 33210 Jar of Eyes +23528 70 117777 Sarachnis cudgel +23667 0 2664921 Dragonstone armour set +23685 2000 18624 Divine super combat potion(4) +23688 2000 13943 Divine super combat potion(3) +23691 2000 8957 Divine super combat potion(2) +23694 2000 4621 Divine super combat potion(1) +23697 2000 5251 Divine super attack potion(4) +23700 2000 3057 Divine super attack potion(3) +23703 2000 1929 Divine super attack potion(2) +23706 2000 603 Divine super attack potion(1) +23709 2000 5677 Divine super strength potion(4) +23712 2000 4056 Divine super strength potion(3) +23715 2000 1847 Divine super strength potion(2) +23718 2000 951 Divine super strength potion(1) +23721 2000 7206 Divine super defence potion(4) +23724 2000 3914 Divine super defence potion(3) +23727 2000 909 Divine super defence potion(2) +23730 2000 1019 Divine super defence potion(1) +23733 2000 6237 Divine ranging potion(4) +23736 2000 4652 Divine ranging potion(3) +23739 2000 2952 Divine ranging potion(2) +23742 2000 1510 Divine ranging potion(1) +23745 2000 3563 Divine magic potion(4) +23748 2000 1864 Divine magic potion(3) +23751 2000 2093 Divine magic potion(2) +23754 2000 1720 Divine magic potion(1) +23908 50 93518 Zalcano shard +23943 5 1569044 Elven signet +23948 0 2000 Elven dawn +23953 100 23741000 Crystal tool seed +23956 100 5695000 Crystal armour seed +23959 100 3231800 Enhanced crystal teleport seed +23997 8 111272585 Blade of Saeldor (inactive) +24000 0 321640 Crystal grail +24003 125 11094 Elven boots +24006 0 10424 Elven gloves +24009 0 7115 Elven top (yellow) +24012 0 5250 Elven skirt (yellow) +24015 0 6455 Elven top (white) +24018 0 4250 Elven skirt (white) +24021 0 2138 Elven top (yellow vest) +24024 0 10249 Elven legwear +24027 0 4396 Elven top (white vest) +24034 0 811616 Dragonstone full helm +24037 0 729499 Dragonstone platebody +24040 0 620357 Dragonstone platelegs +24043 0 177966 Dragonstone boots +24046 0 215204 Dragonstone gauntlets +24144 0 24968756 Staff of Balance +24187 500 230375 Trouver parchment +24219 0 2991466 Swift blade +24229 50 638708 Ornate maul handle +24251 0 18037 Wilderness crabs teleport +24268 0 13592551 Basilisk jaw +24288 0 272534 Dagon'hai hat +24291 0 402636 Dagon'hai robe top +24294 0 395603 Dagon'hai robe bottom +24333 0 1154213 Dagon'hai robes set +24336 0 779 Target teleport +24387 5 493000 Twisted hat (t3) +24389 5 1071500 Twisted coat (t3) +24391 5 174356 Twisted trousers (t3) +24393 5 180500 Twisted boots (t3) +24395 10 1305999 Twisted cane +24397 5 159832 Twisted hat (t2) +24399 5 579405 Twisted coat (t2) +24401 5 81124 Twisted trousers (t2) +24403 5 23874 Twisted boots (t2) +24405 5 57502 Twisted hat (t1) +24407 5 60427 Twisted coat (t1) +24409 5 8216 Twisted trousers (t1) +24411 5 118626 Twisted boots (t1) +24413 5 57500 Twisted banner +24417 8 308782096 Inquisitor's mace +24419 8 69895404 Inquisitor's great helm +24420 8 121955718 Inquisitor's hauberk +24421 8 115006998 Inquisitor's plateskirt +24422 8 28818855 Nightmare staff +24460 5 160500 Twisted teleport scroll +24463 5 1446446 Twisted blueprints +24466 5 2001666 Twisted horns +24469 5 221500 Twisted relic hunter (t1) armour set +24472 5 405000 Twisted relic hunter (t2) armour set +24475 5 2247500 Twisted relic hunter (t3) armour set +24488 0 308500000 Inquisitor's armour set +24495 4 1180314 Jar of Dreams +24511 8 457151984 Harmonised orb +24514 8 32866435 Volatile orb +24517 8 111939270 Eldritch orb +24585 40 40028 Looting bag note +24587 40 3275015 Rune pouch note +24589 10000 91 Blighted manta ray +24592 10000 85 Blighted anglerfish +24595 10000 43 Blighted karambwan +24598 2000 3076 Blighted super restore(4) +24607 15000 439 Blighted ancient ice sack +24609 10000 27 Blighted bind sack +24611 10000 91 Blighted snare sack +24613 15000 44 Blighted entangle sack +24615 10000 88 Blighted teleport spell sack +24621 10000 78 Blighted vengeance sack +24623 2000 18715 Divine battlemage potion(4) +24626 2000 13755 Divine battlemage potion(3) +24629 2000 8827 Divine battlemage potion(2) +24632 2000 351908 Divine battlemage potion(1) +24635 2000 18957 Divine bastion potion(4) +24638 2000 15900 Divine bastion potion(3) +24641 2000 10726 Divine bastion potion(2) +24644 2000 4820 Divine bastion potion(1) +24740 50 20214 Strange old lockpick (full) +24774 2000 81 Blood pint +24777 8 7765257 Blood shard +24782 0 1170 Raw mystery meat +24785 0 959 Cooked mystery meat +24844 5 3332660 Ring of endurance (uncharged) +24949 10000 977 Moonclan teleport (tablet) +24951 10000 1128 Ourania teleport (tablet) +24953 10000 3072 Waterbirth teleport (tablet) +24955 10000 519 Barbarian teleport (tablet) +24957 10000 1817 Khazard teleport (tablet) +24959 10000 2409 Fishing guild teleport (tablet) +24961 10000 1267 Catherby teleport (tablet) +24963 10000 1568 Ice plateau teleport (tablet) +25001 5 111646 Trailblazer hood (t3) +25004 5 437473 Trailblazer top (t3) +25007 5 165000 Trailblazer trousers (t3) +25010 5 172175 Trailblazer boots (t3) +25013 5 1237879 Trailblazer cane +25016 5 75847 Trailblazer hood (t2) +25019 5 93726 Trailblazer top (t2) +25022 5 252848 Trailblazer trousers (t2) +25025 5 233931 Trailblazer boots (t2) +25028 5 17502 Trailblazer hood (t1) +25031 5 22333 Trailblazer top (t1) +25034 5 3102 Trailblazer trousers (t1) +25037 5 70000 Trailblazer boots (t1) +25056 5 83001 Trailblazer banner +25087 5 325367 Trailblazer teleport scroll +25090 5 562832 Trailblazer tool ornament kit +25093 5 768386 Trailblazer globe +25096 5 1385348 Trailblazer rug +25099 6 290000 Trailblazer graceful ornament kit +25139 500 637 Bone fragments +25380 0 79500 Trailblazer relic hunter (t1) armour set +25383 0 623397 Trailblazer relic hunter (t2) armour set +25386 0 1525000 Trailblazer relic hunter (t3) armour set +25389 0 82308 Swampbark body +25392 0 16773 Swampbark gauntlets +25395 0 36026 Swampbark boots +25398 0 52888 Swampbark helm +25401 0 81274 Swampbark legs +25404 70 209640 Bloodbark body +25407 70 41237 Bloodbark gauntlets +25410 0 48669 Bloodbark boots +25413 0 97325 Bloodbark helm +25416 70 193328 Bloodbark legs +25419 7500 8579 Urium remains +25442 8 280 Bronze locks +25445 8 481 Steel locks +25448 8 1559 Black locks +25451 8 10294 Silver locks +25454 8 18420 Gold locks +25478 50 266 Runescroll of swampbark +25481 50 246 Runescroll of bloodbark +25521 4 139500 Jar of Spirits +25524 4 80819 Jar of Smoke +25576 15 764500 Tome of Water (empty) +25578 11000 3148 Soaked page +25766 3000 56 Fiendish ashes +25769 3000 119 Vile ashes +25772 0 1117 Malicious ashes +25775 7500 2003 Abyssal ashes +25778 7500 3978 Infernal ashes +25826 0 4415 Lizardkicker +25833 0 579 Raw boar meat +25849 11000 185 Amethyst dart +25851 11000 346 Amethyst dart(p) +25853 11000 356 Amethyst dart tip +25855 11000 392 Amethyst dart(p+) +25857 11000 715 Amethyst dart(p++) +25859 70 113420755 Enhanced crystal weapon seed +25862 0 110193454 Bow of Faerdhinen (inactive) +25975 8 4081081 Lightbearer +25985 8 3706499 Elidinis' ward +25991 10 10000 Sigil of resilience +25994 10 10000 Sigil of consistency +25997 10 10000 Sigil of the formidable fighter +26000 10 10000 Sigil of the rigorous ranger +26003 10 10000 Sigil of the meticulous mage +26006 10 10000 Sigil of fortification +26009 10 10000 Sigil of barrows +26012 10 10000 Sigil of deft strikes +26015 10 10000 Sigil of freedom +26018 10 10000 Sigil of enhanced harvest +26021 10 10000 Sigil of storage +26024 10 10000 Sigil of the smith +26027 10 10000 Sigil of the alchemist +26030 10 10000 Sigil of the fletcher +26033 10 10000 Sigil of the chef +26036 10 10000 Sigil of the craftsman +26039 10 10000 Sigil of the abyss +26042 10 10000 Sigil of stamina +26045 10 10000 Sigil of the potion master +26048 10 10000 Sigil of the eternal jeweller +26051 10 10000 Sigil of the treasure hunter +26054 10 10000 Sigil of mobility +26057 10 10000 Sigil of exaggeration +26060 8 20000 Sigil of specialised strikes +26063 8 20000 Sigil of the porcupine +26066 8 20000 Sigil of binding +26069 8 20000 Sigil of escaping +26072 8 20000 Sigil of the ruthless ranger +26075 8 20000 Sigil of the feral fighter +26078 8 20000 Sigil of the menacing mage +26081 8 20000 Sigil of prosperity +26084 8 20000 Sigil of the dwarves +26087 8 20000 Sigil of the elves +26090 8 20000 Sigil of the barbarians +26093 8 20000 Sigil of the gnomes +26096 8 20000 Sigil of nature +26099 8 20000 Sigil of devotion +26102 8 20000 Sigil of the forager +26105 8 20000 Sigil of garments +26108 8 20000 Sigil of slaughter +26111 8 20000 Sigil of the fortune farmer +26114 8 20000 Sigil of versatility +26117 8 20000 Sigil of the serpent +26120 8 20000 Sigil of supreme stamina +26123 8 20000 Sigil of preservation +26126 4 30000 Sigil of finality +26129 4 30000 Sigil of pious protection +26132 4 30000 Sigil of aggression +26135 4 30000 Sigil of rampage +26138 4 30000 Sigil of the skiller +26141 4 30000 Sigil of remote storage +26144 4 30000 Sigil of last recall +26147 4 30000 Sigil of the guardian angel +26219 8 17469926 Osmumten's fang +26221 8 58949 Ancient ceremonial top +26223 8 47045 Ancient ceremonial legs +26225 8 64250 Ancient ceremonial mask +26227 8 22884 Ancient ceremonial gloves +26229 8 26890 Ancient ceremonial boots +26231 30000 3236 Nihil shard +26233 8 36073961 Ancient godsword +26235 7 72917500 Zaryte vambraces +26241 8 4660739 Virtus mask +26243 8 32648930 Virtus robe top +26245 8 22821961 Virtus robe bottom +26266 0 10225411 Condensed gold +26340 2000 2839 Ancient brew(4) +26342 2000 2076 Ancient brew(3) +26344 2000 1338 Ancient brew(2) +26346 2000 711 Ancient brew(1) +26350 0 101000 Ancient mix(2) +26353 0 470000 Ancient mix(1) +26368 11000 3390 Nihil dust +26370 0 34641000 Ancient hilt +26372 0 325745145 Nihil horn +26374 8 356254816 Zaryte crossbow +26376 8 199435726 Torva full helm (damaged) +26378 8 154316461 Torva platebody (damaged) +26380 8 92171417 Torva platelegs (damaged) +26382 8 210469110 Torva full helm +26384 8 171338719 Torva platebody +26386 8 109457033 Torva platelegs +26390 500 87349 Blood essence +26394 0 8200000 Bandosian components +26421 5 419687 Shattered relics variety ornament kit +26424 0 209895 Shattered banner +26427 0 53489 Shattered hood (t1) +26430 0 36723 Shattered top (t1) +26433 0 41813 Shattered trousers (t1) +26436 0 56952 Shattered boots (t1) +26439 0 404938 Shattered hood (t2) +26442 0 544388 Shattered top (t2) +26445 0 50000 Shattered trousers (t2) +26448 0 142925 Shattered boots (t2) +26451 0 1616889 Shattered hood (t3) +26454 0 601833 Shattered top (t3) +26457 0 150624 Shattered trousers (t3) +26460 0 210913 Shattered boots (t3) +26479 5 435034 Shattered relics void ornament kit +26500 0 250000 Shattered teleport scroll +26517 0 2239055 Shattered cane +26528 5 430000 Shattered cannon ornament kit +26541 5 42220 Shattered relics mystic ornament kit +26554 0 258813 Shattered relic hunter (t1) armour set +26557 0 1033276 Shattered relic hunter (t2) armour set +26560 0 4840372 Shattered relic hunter (t3) armour set +26602 0 1 Osman's report +26788 10000 45 Gold tiara +26798 40 1145 Catalytic talisman +26801 40 128 Catalytic tiara +26804 40 325 Elemental tiara +26815 0 791220 Ring of the elements +26945 0 9473486 Pharaoh's sceptre (uncharged) +26997 0 3633 Ensouled hellhound head +27014 0 84760 Kovac's grog +27202 2000 2056 Menaphite remedy(4) +27205 0 1688 Menaphite remedy(3) +27208 0 467 Menaphite remedy(2) +27211 0 612 Menaphite remedy(1) +27226 8 9004244 Masori mask +27229 8 44817221 Masori body +27232 8 29414165 Masori chaps +27235 8 17903500 Masori mask (f) +27238 8 80956070 Masori body (f) +27241 8 57580646 Masori chaps (f) +27269 0 9144494 Armadylean plate +27272 13000 1773 Lily of the Sands +27277 8 792792537 Tumeken's shadow (uncharged) +27355 5 157849099 Masori armour set (f) +27612 8 63126185 Venator bow (uncharged) +27614 8 12263433 Venator shard +27616 300000 18 Ancient essence +27629 2000 4249 Forgotten brew(4) +27632 0 3538 Forgotten brew(3) +27635 0 1925 Forgotten brew(2) +27638 0 1115 Forgotten brew(1) +27641 8 76851142 Saturated heart +27652 0 14549954 Webweaver bow (u) +27657 8 4250219 Ursine chainmace (u) +27662 8 2834621 Accursed sceptre (u) +27667 0 58329 Claws of Callisto +27670 8 49398 Fangs of Venenatis +27673 8 48583 Skull of Vet'ion +27676 8 2939109 Accursed sceptre (au) +27681 8 1323920 Voidwaker hilt +27684 8 939123 Voidwaker blade +27687 8 33350186 Voidwaker gem +27690 70 37130965 Voidwaker +27785 8 3357507 Thammaron's sceptre (au) +27897 0 517 Scaly blue dragonhide +28146 40 621056 Log brace +28149 0 745049 Sturdy harness +28152 0 975 Nature offerings +28154 0 26775 Ritual mulch +28157 6000 41 Forester's ration +28159 0 3998 Secateurs blade +28161 10000 102 Secateurs attachment +28163 0 2224555 Clothes pouch +28166 0 2123000 Clothes pouch blueprint +28177 0 1950127 Felling axe handle +28193 0 33 Unfired cup +28196 0 1199999 Bronze felling axe +28199 0 1002775 Iron felling axe +28202 0 1500000 Steel felling axe +28205 0 2060000 Black felling axe +28208 0 1575000 Mithril felling axe +28211 0 2100000 Adamant felling axe +28214 0 1883285 Rune felling axe +28217 0 2040618 Dragon felling axe +28220 0 520000 Crystal 2h axe +28223 0 520000 Crystal 2h axe (inactive) +28226 0 2118000000 3rd Age felling axe +28276 8 36669 Chromium ingot +28295 0 4010520 Berserker icon +28298 0 2486998 Archer icon +28301 0 65522 Warrior icon +28304 0 1116832 Seers icon +28307 0 110023166 Ultor ring +28310 0 48090777 Venator ring +28313 0 22625267 Magus ring +28316 0 19846940 Bellator ring +28334 50 279202 Awakener's orb +28338 5 409349500 Soulreaper axe +28478 0 10000 Sigil of sustenance +28481 0 10000 Sigil of hoarding +28484 0 10000 Sigil of the alchemaniac +28487 0 20000 Sigil of the hunter +28490 0 10000 Sigil of resistance +28493 0 10000 Sigil of agile fortune +28496 0 30000 Sigil of the food master +28499 0 10000 Sigil of the well fed +28502 0 10000 Sigil of the infernal chef +28505 0 10000 Sigil of the infernal smith +28508 0 10000 Sigil of the lightbearer +28511 0 10000 Sigil of the bloodhound +28514 0 10000 Sigil of precision +28517 0 10000 Sigil of the augmented thrall +28520 0 10000 Sigil of faith +28523 0 10000 Sigil of titanium +28526 0 10000 Sigil of the ninja +28529 0 10000 Sigil of woodcraft +28531 0 250000 Corrupted voidwaker +28534 0 250000 Corrupted dragon claws +28537 0 250000 Corrupted armadyl godsword +28540 0 250000 Corrupted twisted bow +28545 0 250000 Corrupted scythe of vitur (uncharged) +28549 0 250000 Corrupted tumeken's shadow (uncharged) +28561 0 10000 Trinket of vengeance +28564 0 10000 Trinket of fairies +28567 0 10000 Trinket of advanced weaponry +28570 0 10000 Trinket of undead +28583 0 89068 Warped sceptre (uncharged) +28628 10000 3300 Sawmill voucher +28684 0 3601684 Trailblazer reloaded bulwark ornament kit +28690 0 1977921 Trailblazer reloaded blowpipe ornament kit +28693 0 774220 Trailblazer reloaded alchemy scroll +28696 0 2468342 Trailblazer reloaded vengeance scroll +28699 5 1116547 Trailblazer reloaded death scroll +28702 0 76000 Trailblazer reloaded banner +28705 5 308205 Trailblazer reloaded home teleport scroll +28708 0 3159690 Trailblazer reloaded rejuvenation pool scroll +28712 0 77660 Trailblazer reloaded headband (t1) +28715 0 48322 Trailblazer reloaded top (t1) +28718 0 16986 Trailblazer reloaded trousers (t1) +28721 0 112804 Trailblazer reloaded boots (t1) +28724 0 232762 Trailblazer reloaded headband (t2) +28727 0 207488 Trailblazer reloaded top (t2) +28730 0 193833 Trailblazer reloaded trousers (t2) +28733 0 191275 Trailblazer reloaded boots (t2) +28736 0 986793 Trailblazer reloaded headband (t3) +28739 0 1229500 Trailblazer reloaded top (t3) +28742 0 1054999 Trailblazer reloaded trousers (t3) +28745 0 289715 Trailblazer reloaded boots (t3) +28748 0 819420 Trailblazer reloaded torch +28777 0 65000 Trailblazer reloaded relic hunter (t1) armour set +28780 0 2364083 Trailblazer reloaded relic hunter (t2) armour set +28783 0 3003750 Trailblazer reloaded relic hunter (t3) armour set +28790 0 833 Kourend castle teleport (tablet) +28810 0 1733629 Zombie axe +28813 0 1725000 Broken zombie axe +28824 0 668 Civitas illa fortis teleport +28831 0 154 Immature tecu salamander +28834 125 352977 Tecu salamander +28837 11000 34 Irit tar +28839 0 20 Wood camo top (equipped) +28842 0 20 Wood camo legs (equipped) +28845 0 20 Jungle camo top (equipped) +28848 0 20 Jungle camo legs (equipped) +28851 0 20 Desert camo top (equipped) +28854 0 20 Desert camo legs (equipped) +28857 0 20 Polar camo top (equipped) +28860 0 20 Polar camo legs (equipped) +28869 70 4739 Hunters' sunlight crossbow +28872 11000 211 Sunlight antler bolts +28878 11000 234 Moonlight antler bolts +28890 0 1052 Sunlight moth (item) +28893 125 1233 Moonlight moth (item) +28896 0 100 Rum +28899 3000 474 Wyrmling bones +28919 0 44865283 Tonalztics of Ralos (uncharged) +28924 30000 366 Sunfire splinters +28929 18000 105 Sunfire rune +28931 11000 19452 Searing page +28933 0 1350901 Sunfire fanatic helm +28936 0 2267149 Sunfire fanatic cuirass +28939 0 3797528 Sunfire fanatic chausses +28942 8 2548518 Echo crystal +28988 15 335333 Blue moon spear +28991 11000 112 Atlatl dart +28997 15 5158336 Dual macuahuitl +29000 15 689034 Eclipse atlatl +29004 15 1493550 Eclipse moon chestplate +29007 15 2529167 Eclipse moon tassets +29010 15 818806 Eclipse moon helm +29013 15 1471273 Blue moon chestplate +29016 15 4415624 Blue moon tassets +29019 15 698343 Blue moon helm +29022 15 3624716 Blood moon chestplate +29025 15 8870190 Blood moon tassets +29028 15 952873 Blood moon helm +29049 15 676474 Eclipse moon chestplate (broken) +29052 15 1677500 Eclipse moon tassets (broken) +29055 15 64252 Eclipse moon helm (broken) +29058 15 582029 Blue moon chestplate (broken) +29061 15 3038362 Blue moon tassets (broken) +29064 15 69898 Blue moon helm (broken) +29067 15 2824066 Blood moon chestplate (broken) +29070 15 7885000 Blood moon tassets (broken) +29073 15 72581 Blood moon helm (broken) +29084 15 114586 Sulphur blades +29090 100 4039 Calcified moth +29098 0 226 Not meat +29101 13000 1259 Raw barb-tailed kebbit +29104 13000 900 Raw wild kebbit +29107 13000 1600 Raw dashing kebbit +29110 13000 1182 Raw pyre fox +29113 13000 2270 Raw moonlight antelope +29116 13000 1765 Raw sunlight antelope +29119 13000 1181 Raw graahk +29122 13000 4247 Raw larupia +29125 13000 1129 Raw kyatt +29128 10000 832 Cooked wild kebbit +29131 10000 200 Cooked barb-tailed kebbit +29134 6000 1540 Cooked dashing kebbit +29137 10000 327 Cooked pyre fox +29140 10000 1128 Cooked sunlight antelope +29143 6000 2769 Cooked moonlight antelope +29146 10000 381 Cooked larupia +29149 10000 44 Cooked graahk +29152 10000 478 Cooked kyatt +29163 0 2197 Fox fur +29166 18000 253 Jerboa tail +29168 0 2250 Sunlight antelope antler +29171 18000 2695 Moonlight antelope antler +29174 18000 323 Moonlight antelope fur +29177 0 390 Sunlight antelope fur +29180 0 1080 Sapphire glacialis mix (2) +29183 0 4253 Snowy knight mix (2) +29186 0 745 Ruby harvest mix (2) +29189 0 939 Black warlock mix (2) +29192 125 4388 Sunlight moth mix (2) +29195 125 3571 Moonlight moth mix (2) +29198 0 350 Sapphire glacialis mix (1) +29201 0 1500 Snowy knight mix (1) +29204 0 846 Ruby harvest mix (1) +29207 0 128 Black warlock mix (1) +29210 0 50505 Sunlight moth mix (1) +29213 0 2667 Moonlight moth mix (1) +29218 10000 2205 Jaguar fur +29253 0 30741 Enhanced quetzal whistle blueprint +29256 50 31021 Perfected quetzal whistle blueprint +29277 0 152 Trapper's tipple +29280 100 15501 Mixed hide top +29283 100 22670 Mixed hide legs +29286 100 17453 Mixed hide boots +29289 100 21707 Mixed hide cape +29292 0 14511 Mixed hide base +29305 0 486 Hunter's spear +29307 100 3318 Quetzal feed +29311 10000 2 Hunter spear tips +29409 0 200 Sunbeam ale +29412 0 118 Steamforge brew +29415 2000 1343 Eclipse red +29418 0 107 Moon-lite +29421 0 12599 Sun-shine +29424 0 7700808 Sunfire fanatic armour set +29449 250 14911 Zombie pirate key +29455 5 39318 Teleport anchoring scroll +29458 18000 99 Adamant seeds +29486 125 18412 Cursed amulet of magic +29574 8 1770921 Burning claw +29577 8 3679164 Burning claws +29580 5 38047513 Tormented synapse +29599 0 250000 Corrupted dark bow +29602 0 250000 Corrupted Volatile Nightmare staff +29619 5 3806679 Armageddon rug +29622 5 11847590 Armageddon teleport scroll +29625 5 14684946 Armageddon weapon scroll +29628 5 8899401 Armageddon cape fabric +29631 50 50 Blighted overload (4) +29634 50 50 Blighted overload (3) +29637 50 50 Blighted overload (2) +29640 50 50 Blighted overload (1) +29643 0 2600 Chitin +29649 0 30000 Sigil of meticulousness +29652 0 30000 Sigil of revoked limitation +29655 0 10000 Sigil of the rampart +29658 0 10000 Sigil of deception +29661 0 10000 Sigil of litheness +29664 0 10000 Sigil of the adroit +29667 0 10000 Sigil of onslaught +29670 0 10000 Sigil of restoration +29673 0 10000 Sigil of the swashbuckler +29676 0 10000 Sigil of the gunslinger +29679 0 10000 Sigil of arcane swiftness +29684 0 11050 Guthixian temple teleport +29782 0 29089 Spider cave teleport +29784 11000 1046 Araxyte venom sac +29796 8 38577500 Noxious halberd +29801 8 49866806 Amulet of rancour +29806 8 673376 Aranea boots +29824 2000 19346 Extended anti-venom+(4) +29827 2000 13931 Extended anti-venom+(3) +29830 2000 8690 Extended anti-venom+(2) +29833 2000 4723 Extended anti-venom+(1) +29889 15 65040 Glacial temotli +29895 13000 251 Frozen tear +29900 0 12763 Varlamorian kebab +29944 0 906 Blackbird red +29947 0 535 Chilhuac red +29952 0 366 Ixcoztic white +29955 0 316 Metztonalli white +29958 0 920 Tonameyo white +29963 0 5500 Chichilihui rosé +29966 0 332 Imperial rosé +29993 13000 56261 Aldarium +30002 5 10255772 Chugging barrel (disassembled) +30040 0 22658 Colossal wyrm teleport scroll +30066 15 831833 Tome of Earth (empty) +30068 11000 3209 Soiled page +30070 8 9292401 Dragon hunter wand +30073 0 1027689 Hueycoatl hide coif +30076 0 1493175 Hueycoatl hide body +30079 0 974595 Hueycoatl hide chaps +30082 0 515348 Hueycoatl hide vambraces +30085 0 492100 Hueycoatl hide +30088 200 39395 Huasca seed +30094 0 6642 Grimy huasca +30097 0 6887 Huasca +30100 10000 7544 Huasca potion (unf) +30125 2000 76770 Prayer regeneration potion(4) +30128 2000 53655 Prayer regeneration potion(3) +30131 2000 37890 Prayer regeneration potion(2) +30134 2000 18183 Prayer regeneration potion(1) +30137 2000 66572 Goading potion(4) +30140 0 47482 Goading potion(3) +30143 0 33880 Goading potion(2) +30146 0 15600 Goading potion(1) +30321 50 269027 Zombie helmet +30324 70 116516 Broken zombie helmet +30328 250 1500 Dust +30331 0 180500 Raging echoes relic hunter (t1) armour set +30334 0 1192709 Raging echoes relic hunter (t2) armour set +30337 0 2912631 Raging echoes relic hunter (t3) armour set +30404 0 33991 Raging echoes hat (t1) +30406 0 9752 Raging echoes top (t1) +30408 0 19097 Raging echoes robeskirt (t1) +30410 0 69820 Raging echoes boots (t1) +30412 0 171837 Raging echoes hat (t2) +30414 0 164181 Raging echoes top (t2) +30416 0 123012 Raging echoes robeskirt (t2) +30418 0 288715 Raging echoes boots (t2) +30420 0 1882999 Raging echoes hat (t3) +30422 0 556349 Raging echoes top (t3) +30424 0 83109 Raging echoes robeskirt (t3) +30426 0 784644 Raging echoes boots (t3) +30428 0 1317343 Raging echoes cane +30430 0 46193 Raging echoes banner +30432 0 2069708 Echo venator bow ornament kit +30443 0 891507 Echo virtus ornament kit +30451 0 188890 Echo ahrim's ornament kit +30453 0 377375 Echo home teleport scroll +30455 0 794730 Raging echoes death scroll +30457 0 2035000 Raging echoes astral contact scroll +30459 0 1341828 Raging echoes nexus scroll +30461 0 513991 Raging echoes portal scroll +30554 0 127868 Raging echoes rug +30557 5 166351 Raging echoes curtains +30560 0 341582 Raging echoes scrying pool scroll +30563 0 2443729 Raging echoes spirit tree scroll +30576 0 10000 Bounty supply crate +30616 100 5250 Bounty supply crate (manta ray) +30619 100 6562 Bounty supply crate (anglerfish) +30628 8 4625325 Ice element staff crown +30631 8 4476388 Fire element staff crown +30634 8 9426435 Twinflame staff +30676 0 10000 Nimbleness charm +30679 0 10000 Stockpiling charm +30682 0 10000 Accumulation charm +30685 0 10000 Vulnerability charm +30744 0 250113635 Oathplate armour set +30750 0 81090000 Oathplate helm +30753 0 83125000 Oathplate chest +30756 0 83570959 Oathplate legs +30759 5 3801265 Soulflame horn +30765 0 171600 Oathplate shards +30771 25000 344 Aether catalyst +30773 8000 2341 Diabolic worms +30775 0 40288 Chasm teleport scroll +30795 0 17037 Barrel of demonic tallow (full) +30800 11000 184 Demonic tallow +30810 50 245744 Contract of Glyphic Attenuation +30813 50 198748 Contract of Sensory Clouding +30816 50 39646 Contract of Bloodied Blows +30819 50 36421 Contract of Divine Severance +30822 50 39383 Contract of Forfeit Breath +30825 50 80111319 Contract of Oathplate Acquisition +30828 50 5975000 Contract of Shard Acquisition +30831 50 95460 Contract of Catalyst Acquisition +30834 50 71342 Contract of Worm Acquisition +30837 50 2681620 Contract of Harmony Acquisition +30840 50 110887 Contract of Familiar Acquisition +30843 25000 785 Aether rune +30848 25000 1792 Crushed infernal shale +30864 0 9350000 Infernal plate +30895 0 139960 Steel ring +30900 10000 241 Shark lure +30957 0 94925 Earthbound tecpatl +30998 10000 31 Atlatl dart tips +31004 11000 34 Atlatl dart shaft +31010 11000 52 Headless atlatl dart +31024 0 54456 Greenman carving +31027 0 97741 Greenman statue +31032 0 3221 Ent branch +31034 4 22832 Greenman mask +31045 13000 124 Bale of flax +31049 0 797 Redwood hiking staff +31081 0 425098 Antler guard +31086 0 3637 Broken antler +31088 8 143375000 Avernic treads +31099 100 44069 Mokhaiotl waystone +31106 0 65376936 Confliction gauntlets +31111 13000 240 Demon tear +31115 8 38452642 Eye of Ayak (uncharged) +31136 2 19178785 Blood moon armour set +31139 2 6392874 Blue moon armour set +31142 2 5741867 Eclipse moon armour set +31145 2 487500000 Torva armour set +31148 2 60418205 Virtus armour set +31151 0 134385 Rock-shell armour set +31154 2 486400 Skeletal armour set +31157 0 132775 Spined armour set +31160 2 269000 Swampbark armour set +31163 2 639654 Bloodbark armour set +31166 0 86499 Mixed hide armour set +31169 0 3984617 Hueycoatl hide armour set +31181 0 62210 Grid master tabard +31184 0 18630 Grid master tabard (b) +31187 0 24129 Grid master tabard (p) +31190 0 26974 Grid master tabard (g) +31193 0 22780 Swords and emblem +31196 0 5750 Swords and emblem (b) +31199 0 38956 Swords and emblem (p) +31202 0 25077 Swords and emblem (g) +31205 0 143871 Grid master altar icon scroll +31208 0 101453 Grid master torch scroll +31235 11000 22 Gryphon feather +31243 15 481316 Horn of Plenty (empty) +31245 0 101956 Belle's folly (tarnished) +31248 15 97408 Belle's folly +31255 0 100 Barnacle blaster +31258 0 3987 Kraken colada +31261 0 225 Sailor's mirage +31383 0 4397 Camphor pyre logs +31386 0 4897 Ironwood pyre logs +31389 0 4417 Rosewood pyre logs +31406 13000 11981 Dragon nails +31432 13000 3956 Camphor plank +31435 13000 4187 Ironwood plank +31438 0 5425 Rosewood plank +31441 0 733 Summon boat +31443 0 615 Teleport to boat +31454 0 2 Ball of cotton +31457 13000 990 Hemp +31460 0 2057 Cotton boll +31463 13000 132 Linen yarn +31466 13000 850 Hemp yarn +31469 13000 1738 Cotton yarn +31472 13000 509 Bolt of linen +31475 13000 2081 Bolt of canvas +31478 13000 3281 Bolt of cotton +31481 13000 1088 Elkhorn coral +31484 13000 2198 Pillar coral +31487 11000 2614 Umbral coral +31502 0 5174 Camphor sapling +31505 0 39353 Ironwood sapling +31508 0 144483 Rosewood sapling +31511 600 150 Elkhorn frag +31513 600 261 Pillar frag +31515 200 1525 Umbral frag +31541 2000 11 Flax seed +31543 2000 1004 Hemp seed +31545 2000 2513 Cotton seed +31547 200 3774 Camphor seed +31549 200 37934 Ironwood seed +31551 200 145400 Rosewood seed +31553 0 850 Raw swordtip squid +31556 0 205 Swordtip squid +31561 0 2521 Raw jumbo squid +31564 0 345 Jumbo squid +31569 0 787 Squid paste +31572 0 1544303 Squid beak +31577 8 1100889 Camphor blowpipe (empty) +31581 8 1313919 Ironwood blowpipe (empty) +31585 8 1373518 Rosewood blowpipe (empty) +31587 2000 1663 Haemostatic poultice +31590 2000 4254 Haemostatic dressing (4) +31593 0 3152 Haemostatic dressing (3) +31596 0 1996 Haemostatic dressing (2) +31599 0 1049 Haemostatic dressing (1) +31602 0 3474 Super fishing potion(4) +31605 0 2296 Super fishing potion(3) +31608 0 2095 Super fishing potion(2) +31611 0 1085 Super fishing potion(1) +31614 0 2916 Extreme energy potion(4) +31617 0 1303 Extreme energy potion(3) +31620 0 1251 Extreme energy potion(2) +31623 0 535 Extreme energy potion(1) +31626 0 883 Super hunter potion(4) +31629 0 615 Super hunter potion(3) +31632 0 133 Super hunter potion(2) +31635 0 250 Super hunter potion(1) +31638 0 7868 Extended stamina potion(4) +31641 0 5742 Extended stamina potion(3) +31644 0 3577 Extended stamina potion(2) +31647 0 1747 Extended stamina potion(1) +31650 2000 1272 Armadyl brew(4) +31653 2000 867 Armadyl brew(3) +31656 2000 551 Armadyl brew(2) +31659 2000 373 Armadyl brew(1) +31662 10000 1222 Elkhorn potion (unf) +31665 0 2302 Pillar potion (unf) +31668 0 2873 Umbral potion (unf) +31671 0 73 Red crab +31674 0 440 Blue crab +31677 0 5599 Rainbow crab (1) +31680 0 5600 Rainbow crab (2) +31683 0 2250 Rainbow crab (3) +31686 0 101 Raw red crab meat +31689 0 460 Red crab meat +31692 0 443 Raw blue crab meat +31695 0 507 Blue crab meat +31700 0 1222 Raw rainbow crab meat +31703 0 950 Rainbow crab meat +31708 13000 77 Crab paste +31710 13000 1351 Rainbow crab paste +31712 0 184 Anti-odour salt +31716 0 466 Lead ore +31719 0 1962 Nickel ore +31722 30000 143 Rubium splinters +31726 0 2305 Strykewyrm bones +31729 7500 6752 Frost dragon bones +31906 11000 4 Bronze cannonball +31908 11000 10 Iron cannonball +31910 11000 146 Mithril cannonball +31912 11000 223 Adamant cannonball +31914 11000 692 Rune cannonball +31916 11000 1259 Dragon cannonball +31918 0 47 Bronze chainshot cannonball +31920 0 500 Iron chainshot cannonball +31922 0 311 Steel chainshot cannonball +31924 0 587 Mithril chainshot cannonball +31926 0 497 Adamant chainshot cannonball +31928 0 1918 Rune chainshot cannonball +31930 0 3600 Dragon chainshot cannonball +31932 0 115 Bronze incendiary cannonball +31934 0 617 Iron incendiary cannonball +31936 0 742 Steel incendiary cannonball +31938 0 759 Mithril incendiary cannonball +31940 0 1043 Adamant incendiary cannonball +31942 0 1498 Rune incendiary cannonball +31944 0 3600 Dragon incendiary cannonball +31946 0 41032 Echo pearl +31949 0 2431448 Bottled storm +31952 15 286776 Swift albatross feather +31954 15 17000 Narwhal horn +31959 15 125924 Ray barbs +31961 0 1228659 Broken dragon hook +31964 13000 70 Repair kit +31967 13000 107 Oak repair kit +31970 13000 305 Teak repair kit +31973 13000 1104 Mahogany repair kit +31976 13000 1682 Camphor repair kit +31979 13000 2049 Ironwood repair kit +31982 13000 14443 Rosewood repair kit +31989 100 16617 Boat bottle (empty) +31996 64 174923 Dragon metal sheet +31999 100 285 Bronze keel parts +32002 100 792 Iron keel parts +32005 100 1228 Steel keel parts +32008 100 2202 Mithril keel parts +32011 100 7850 Adamant keel parts +32014 100 59950 Rune keel parts +32017 100 277506 Dragon keel parts +32020 100 3935 Large bronze keel parts +32023 100 3275 Large iron keel parts +32026 100 16714 Large steel keel parts +32029 100 15322 Large mithril keel parts +32032 100 45894 Large adamant keel parts +32035 100 301515 Large rune keel parts +32038 100 583900 Large dragon keel parts +32041 100 35 Wooden hull parts +32044 100 406 Oak hull parts +32047 100 1774 Teak hull parts +32050 100 5596 Mahogany hull parts +32053 100 8739 Camphor hull parts +32056 100 11410 Ironwood hull parts +32059 100 10171 Rosewood hull parts +32062 100 100 Large wooden hull parts +32065 0 4067 Large oak hull parts +32068 100 10185 Large teak hull parts +32071 100 38247 Large mahogany hull parts +32074 100 48716 Large camphor hull parts +32077 100 56867 Large ironwood hull parts +32080 100 77013 Large rosewood hull parts +32087 0 3089953 Barracuda paint +32090 5 800 Shark paint +32093 5 892145 Inky paint +32096 0 12778975 Angler's paint +32099 0 313964 Salvor's paint +32110 0 10185802 Merchant's paint +32115 100 495660 Dragon cannon barrel +32307 8000 365 Fine fish offcuts +32309 15000 46 Raw giant krill +32312 0 249 Giant krill +32317 15000 209 Raw haddock +32320 0 485 Haddock +32325 15000 616 Raw yellowfin +32328 0 1081 Yellowfin +32333 15000 1797 Raw halibut +32336 10000 2653 Halibut +32341 15000 1419 Raw bluefin +32344 0 1840 Bluefin +32349 15000 3497 Raw marlin +32352 10000 3869 Marlin +32357 0 546 Haddock eye +32360 11000 156 Yellow fin +32362 11000 1250 Marlin scales +32364 250 1150 Camphor crate +32366 250 1282 Fish crate (empty) +32368 250 1032 Fish crate (giant krill) +32371 250 2420 Fish crate (haddock) +32374 250 6500 Fish crate (yellowfin) +32377 250 17025 Fish crate (halibut) +32380 250 13888 Fish crate (bluefin) +32383 250 33875 Fish crate (marlin) +32876 0 1200000 Aquanite tendon +32879 0 1313702 Aquanite hopper +32886 0 514 Chain +32889 10000 1343 Lead bar +32892 10000 2558 Cupronickel bar +32904 0 299 Camphor logs +32907 12000 106 Ironwood logs +32910 12000 420 Rosewood logs +33012 5 14190200 Annihilation weapon scroll +33015 5 19318462 Annihilation blueprints +33018 5 11730174 Annihilation teleport scroll +33038 0 1 The dogsword +33041 0 1 Thunder khopesh +33044 0 1 Trinket of avarice +33047 0 1 Trinket of fortuity (inactive) +33050 0 1 Trinket of fortuity (active) +33074 100 33435 Facility bottle (empty) +33093 4 2496 Cow slippers +33106 13000 182 Raw t-bone steak +33109 6000 77 Cooked t-bone steak +33260 4 43564 Demonic hood (t1) +33263 4 13000 Demonic robe top (t1) +33266 4 7595 Demonic robe bottom (t1) +33269 4 12135 Demonic boots (t1) +33272 4 380516 Demonic hood (t2) +33275 4 54342 Demonic robe top (t2) +33278 4 33004 Demonic robe bottom (t2) +33281 4 65452 Demonic boots (t2) +33284 4 4297991 Demonic hood (t3) +33287 4 515892 Demonic robe top (t3) +33290 4 295328 Demonic robe bottom (t3) +33293 4 347487 Demonic boots (t3) +33296 0 677944 Demonic sceptre +33299 0 105000 Demonic pacts banner +33302 0 881506 Demonic skin contract +33305 0 1960044 Demonic axe ornament kit +33308 0 192519 Demonic staff ornament kit +33311 0 839473 Demonic trident ornament kit +33342 0 123491 Demonic quill +33359 0 285752 Impish ritual scroll +33362 0 255145 Demonic pacts throne scroll +33365 0 2248965 Impish whistle +33368 0 1182084 Demonic pacts demon butler scroll +33428 0 1 Trinket of vengeance (2) +33431 0 1 Trinket of vengeance (1) +33451 0 89304 Demonic pacts relic hunter (t1) armour set +33454 0 468432 Demonic pacts relic hunter (t2) armour set +33457 0 5846910 Demonic pacts relic hunter (t3) armour set +33534 0 28619777 Etched araxyte fang +33553 0 1900 Seeking bronze arrow +33559 0 292 Seeking iron arrow +33565 0 130 Seeking steel arrow +33571 0 434 Seeking mithril arrow +33577 0 309 Seeking adamant arrow +33583 0 296 Seeking rune arrow +33589 0 589 Seeking amethyst arrow +33595 11000 2343 Seeking dragon arrow +33625 0 1542 Letvek +33631 5 51264722 Crimson kisten +33636 0 125041732 Etched elder venator fang +33639 8 146790000 Necklace of rupture +33644 0 1 Dummy stymphike feather +33651 0 22 Stymphike feather +33657 0 102465 Orikalkum gravel +33661 0 1819 Venator fang +33663 0 1200 Venator tooth +33821 0 2239 Raw impaler meat +33836 0 61748 Noble robe top (green) +33839 0 65801 Noble robe top (blue) +33842 0 150000 Noble robe top (red) +33845 0 36250 Noble robe top (yellow) +33848 0 68699 Noble robe top (white) +33851 0 54363 Noble robe top (black) +33854 0 65414 Noble robe top (pink) +33857 0 32875 Wyrmscraig noble skirt (green) +33860 0 29839 Wyrmscraig noble skirt (blue) +33863 0 242000 Wyrmscraig noble skirt (red) +33866 0 28752 Wyrmscraig noble skirt (yellow) +33869 0 56578 Wyrmscraig noble skirt (white) +33872 0 43881 Wyrmscraig noble skirt (black) +33875 0 100790 Wyrmscraig noble skirt (pink) +33878 0 4779 Wyrmscraig shoes +33881 0 15756 Wyrmscraig boots +33884 0 15024 Wyrmscraig villager robe top (A, green) +33887 0 1500 Wyrmscraig villager robe top (A, blue) +33890 0 323 Wyrmscraig villager robe top (A, red) +33893 0 267 Wyrmscraig villager robe top (A, yellow) +33896 0 2505 Wyrmscraig villager robe top (A, white) +33899 0 50000 Wyrmscraig villager robe top (A, black) +33902 0 19500 Wyrmscraig villager robe top (A, pink) +33905 0 1240 Wyrmscraig villager robe top (A, beige) +33908 0 973 Wyrmscraig villager robe bottom (A, green) +33911 0 1383 Wyrmscraig villager robe bottom (A, blue) +33914 0 500200 Wyrmscraig villager robe bottom (A, red) +33917 0 7700 Wyrmscraig villager robe bottom (A, yellow) +33920 0 2155 Wyrmscraig villager robe bottom (A, white) +33923 0 5000 Wyrmscraig villager robe bottom (A, black) +33926 0 2937 Wyrmscraig villager robe bottom (A, pink) +33929 0 50200 Wyrmscraig villager robe bottom (B, green) +33932 0 700 Wyrmscraig villager robe bottom (B, blue) +33935 0 2700 Wyrmscraig villager robe bottom (B, red) +33938 0 400 Wyrmscraig villager robe bottom (B, yellow) +33941 0 268 Wyrmscraig villager robe bottom (B, white) +33944 0 50176 Wyrmscraig villager robe bottom (B, black) +33947 0 398 Wyrmscraig villager robe bottom (B, pink) +33950 0 472 Wyrmscraig villager robe top (B, green) +33953 0 25203 Wyrmscraig villager robe top (B, blue) +33956 0 1916 Wyrmscraig villager robe top (B, red) +33959 0 1737 Wyrmscraig villager robe top (B, yellow) +33962 0 453000 Wyrmscraig villager robe top (B, white) +33965 0 1278 Wyrmscraig villager robe top (B, black) +33968 0 1500 Wyrmscraig villager robe top (B, pink) +33971 0 12663 Wyrmscraig villager skirt (green) +33974 0 371 Wyrmscraig villager skirt (blue) +33977 0 1107 Wyrmscraig villager skirt (red) +33980 0 445 Wyrmscraig villager skirt (yellow) +33983 0 246 Wyrmscraig villager skirt (white) +33986 0 2678 Wyrmscraig villager skirt (black) +33989 0 2675 Wyrmscraig villager skirt (pink) +33992 0 598 Wyrmscraig villager work skirt (green) +33995 0 647 Wyrmscraig villager work skirt (blue) +33998 0 2476 Wyrmscraig villager work skirt (red) +34001 0 5180 Wyrmscraig villager work skirt (yellow) +34004 0 50199 Wyrmscraig villager work skirt (white) +34007 0 6358 Wyrmscraig villager work skirt (black) +34010 0 557 Wyrmscraig villager work skirt (pink) +34013 0 2690 Wyrmscraig villager work skirt (beige) +34017 0 121 Wyrmscraig goat fur +34027 100 1682681 Hallowfell +34033 0 37451 Ardeaglais teleport +34035 0 1 Placeholder +34401 0 82051 Necklace of fangs +34404 0 265000 Air diamond +34407 0 301833 Amulet of air +34410 0 238898 Water sapphire +34413 0 277363 Amulet of water +34416 0 367036 Earth emerald +34419 0 441610 Amulet of earth +34422 0 85500 Fire ruby +34425 0 73092 Amulet of fire +34428 0 1260964 Elemental amulet diff --git a/content/other/grand-exchange/src/test/kotlin/org/rsmod/content/other/grandexchange/GeExchangeTest.kt b/content/other/grand-exchange/src/test/kotlin/org/rsmod/content/other/grandexchange/GeExchangeTest.kt new file mode 100644 index 000000000..6c7f79e83 --- /dev/null +++ b/content/other/grand-exchange/src/test/kotlin/org/rsmod/content/other/grandexchange/GeExchangeTest.kt @@ -0,0 +1,234 @@ +package org.rsmod.content.other.grandexchange + +import java.nio.file.Files +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class GeExchangeTest { + private val items = + GeItemData.fromLines( + listOf( + "# id\tlimit\tprice\tname", + "$WHIP\t70\t1500000\tAbyssal whip", + "$FEATHER\t13000\t3\tFeather", + "$LOBSTER\t6000\t150\tLobster", + "$COAL\t13000\t150\tCoal", + "$BOND\t100\t8000000\tOld school bond", + "$ENERGY\t2000\t90\tEnergy potion(4)", + "$RARE\t8\t2000000000\tThird age", + ) + ) + + private var now = 1_000_000L + private val exchange = GeExchange(items) { now } + private val finished = ArrayList>() + private val traded = ArrayList>() + + init { + exchange.listener = + object : GeExchange.Listener { + override fun onOfferTraded(offer: GeOffer, trade: GeTrade) { + traded += offer to trade + } + + override fun onOfferFinished(offer: GeOffer, aborted: Boolean) { + finished += offer to aborted + } + } + } + + @Test + fun `buy offer trades at the cheapest sell price and refunds the difference`() { + val sell = exchange.place(SELLER, "seller", 0, GeOfferType.Sell, FEATHER, 2, 100) + val buy = exchange.place(BUYER, "buyer", 0, GeOfferType.Buy, FEATHER, 5, 100) + + assertTrue(buy.finished) + assertTrue(sell.finished) + assertEquals(100, buy.collectItems) + assertEquals(300, buy.collectCoins, "buyer paid 5 each, trade happened at 2 each") + assertEquals(200, buy.gold) + assertEquals(200, sell.gold) + assertEquals(0, sell.taxPaid, "2% of 2 coins rounds down to nothing") + assertEquals(200, sell.collectCoins) + assertEquals(listOf(buy to false, sell to false), finished) + } + + @Test + fun `sell offer trades at the highest buy price so the seller earns more`() { + exchange.place(BUYER, "buyer", 0, GeOfferType.Buy, WHIP, 1_600_000, 1) + exchange.place(BUYER, "buyer", 1, GeOfferType.Buy, WHIP, 1_700_000, 1) + val sell = exchange.place(SELLER, "seller", 0, GeOfferType.Sell, WHIP, 1_500_000, 1) + + assertTrue(sell.finished) + assertEquals(1_700_000, sell.gold) + assertEquals(34_000, sell.taxPaid) + assertEquals(1_666_000, sell.collectCoins) + val cheaper = exchange.offer(BUYER, 0)!! + assertTrue(cheaper.isActive, "the lower buy offer is still waiting") + assertNull(exchange.offer(BUYER, 1)?.takeIf { it.isActive }) + } + + @Test + fun `older offers win ties`() { + val first = exchange.place(SELLER, "seller", 0, GeOfferType.Sell, LOBSTER, 150, 10) + now += 1 + val second = exchange.place(SELLER, "seller", 1, GeOfferType.Sell, LOBSTER, 150, 10) + exchange.place(BUYER, "buyer", 0, GeOfferType.Buy, LOBSTER, 150, 10) + + assertTrue(first.finished) + assertFalse(second.finished) + assertEquals(0, second.completed) + } + + @Test + fun `partial fills leave both offers live until quantities run out`() { + val sell = exchange.place(SELLER, "seller", 0, GeOfferType.Sell, COAL, 100, 30) + val buy = exchange.place(BUYER, "buyer", 0, GeOfferType.Buy, COAL, 100, 10) + + assertTrue(buy.finished) + assertFalse(sell.finished) + assertEquals(10, sell.completed) + assertEquals(20, sell.remaining) + assertEquals(1000, sell.gold) + assertEquals(20, sell.taxPaid) + assertEquals(980, sell.collectCoins) + } + + @Test + fun `tax is capped per item and waived on exempt items`() { + assertEquals(5_000_000, items.taxPerItem(RARE, 2_000_000_000)) + assertEquals(0, items.taxPerItem(ENERGY, 90)) + assertEquals(0, items.taxPerItem(BOND, 8_000_000)) + assertEquals(0, items.taxPerItem(LOBSTER, 150), "lobsters are exempt") + assertEquals(3, items.taxPerItem(COAL, 150)) + assertEquals(30_000, items.taxPerItem(WHIP, 1_500_000)) + assertEquals(0, items.taxPerItem(FEATHER, 49)) + assertEquals(1, items.taxPerItem(FEATHER, 50)) + } + + @Test + fun `aborting returns what was not traded to the collect box`() { + val buy = exchange.place(BUYER, "buyer", 0, GeOfferType.Buy, LOBSTER, 100, 10) + val sell = exchange.place(SELLER, "seller", 0, GeOfferType.Sell, LOBSTER, 100, 4) + assertTrue(sell.finished) + assertTrue(exchange.abort(buy)) + assertFalse(exchange.abort(buy)) + + assertTrue(buy.isCancelled) + assertEquals(4, buy.collectItems) + assertEquals(600, buy.collectCoins) + assertEquals(buy to true, finished.last()) + + val sell2 = exchange.place(SELLER, "seller", 1, GeOfferType.Sell, LOBSTER, 100, 5) + assertTrue(exchange.abort(sell2)) + assertEquals(5, sell2.collectItems) + assertEquals(0, sell2.collectCoins) + } + + @Test + fun `collecting everything frees the slot`() { + val sell = exchange.place(SELLER, "seller", 3, GeOfferType.Sell, COAL, 100, 1) + val buy = exchange.place(BUYER, "buyer", 2, GeOfferType.Buy, COAL, 120, 1) + + assertEquals(1, exchange.takeItems(buy)) + assertEquals(buy, exchange.offer(BUYER, 2), "coins are still waiting") + assertEquals(20, exchange.takeCoins(buy)) + assertNull(exchange.offer(BUYER, 2)) + assertEquals(0, exchange.firstEmptySlot(BUYER)) + + assertEquals(98, exchange.takeCoins(sell)) + assertNull(exchange.offer(SELLER, 3)) + assertEquals(0, exchange.offerCount) + } + + @Test + fun `buy limits cap trades and reset four hours after the first purchase`() { + exchange.place(SELLER, "seller", 0, GeOfferType.Sell, RARE, 1000, 20) + val buy = exchange.place(BUYER, "buyer", 0, GeOfferType.Buy, RARE, 1000, 20) + + assertEquals(8, buy.completed) + assertFalse(buy.finished) + assertEquals(0, exchange.remainingBuyLimit(BUYER, RARE)) + + now += GeConfig.BUY_LIMIT_WINDOW_MILLIS - 1 + assertEquals(0, exchange.remainingBuyLimit(BUYER, RARE)) + now += 1 + assertEquals(8, exchange.remainingBuyLimit(BUYER, RARE)) + + // A fresh sell offer matches the waiting buy offer again once the window has reset. + exchange.place(SELLER, "seller", 1, GeOfferType.Sell, RARE, 1000, 20) + assertEquals(16, buy.completed) + assertEquals(GeItemData.NO_LIMIT, exchange.remainingBuyLimit(BUYER, 999_999)) + } + + @Test + fun `history records both sides newest first`() { + exchange.place(SELLER, "seller", 0, GeOfferType.Sell, COAL, 100, 2) + exchange.place(BUYER, "buyer", 0, GeOfferType.Buy, COAL, 100, 1) + now += 10 + exchange.place(BUYER, "buyer", 1, GeOfferType.Buy, COAL, 100, 1) + + val buyer = exchange.history(BUYER) + assertEquals(2, buyer.size) + assertEquals(GeOfferType.Buy, buyer[0].type) + assertEquals(now, buyer[0].time) + val seller = exchange.history(SELLER) + assertEquals(2, seller.size) + assertEquals(GeOfferType.Sell, seller[0].type) + assertEquals(100, seller[0].total) + assertEquals(2, seller[0].tax) + } + + @Test + fun `offers survive a save and load round trip`() { + exchange.place(SELLER, "seller", 0, GeOfferType.Sell, LOBSTER, 100, 5) + val buy = exchange.place(BUYER, "buyer", 4, GeOfferType.Buy, LOBSTER, 100, 2) + val path = Files.createTempFile("ge-test", ".json") + try { + exchange.save(path) + assertFalse(exchange.dirty) + + val loaded = GeExchange(items) { now } + assertTrue(loaded.load(path)) + val sell = loaded.offer(SELLER, 0)!! + assertEquals(2, sell.completed) + assertEquals(3, sell.remaining) + assertTrue(sell.isActive) + val reloadedBuy = loaded.offer(BUYER, 4)!! + assertEquals(buy.id, reloadedBuy.id) + assertTrue(reloadedBuy.finished) + assertEquals(2, reloadedBuy.collectItems) + assertEquals(1, loaded.history(BUYER).size) + + // The active sell offer still matches new buyers after the reload. + loaded.place(BUYER, "buyer", 0, GeOfferType.Buy, LOBSTER, 100, 3) + assertTrue(loaded.offer(SELLER, 0)!!.finished) + } finally { + Files.deleteIfExists(path) + } + } + + @Test + fun `client status encodes type and completion`() { + val sell = exchange.place(SELLER, "seller", 0, GeOfferType.Sell, LOBSTER, 100, 1) + assertEquals(10, sell.clientStatus()) + val buy = exchange.place(BUYER, "buyer", 0, GeOfferType.Buy, LOBSTER, 100, 1) + assertEquals(5, buy.clientStatus()) + assertEquals(13, sell.clientStatus()) + } + + private companion object { + const val BUYER = 1 + const val SELLER = 2 + const val WHIP = 4151 + const val FEATHER = 314 + const val LOBSTER = 379 + const val COAL = 453 + const val BOND = 13190 + const val ENERGY = 3008 + const val RARE = 10344 + } +} diff --git a/content/other/level-up/build.gradle.kts b/content/other/level-up/build.gradle.kts new file mode 100644 index 000000000..b61c62dca --- /dev/null +++ b/content/other/level-up/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(libs.rsprot.api) + implementation(projects.api.attr) + implementation(projects.api.pluginCommons) + implementation(projects.api.scriptAdvanced) +} diff --git a/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/Jingles.kt b/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/Jingles.kt new file mode 100644 index 000000000..347f39b69 --- /dev/null +++ b/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/Jingles.kt @@ -0,0 +1,137 @@ +package org.rsmod.content.other.levelup + +import net.rsprot.protocol.game.outgoing.sound.MidiJingle +import org.rsmod.api.player.vars.intVarBit +import org.rsmod.game.entity.Player + +private var Player.musicClocks: Int by intVarBit("varbit.music_curr_clocks") + +/** + * Sends `MIDI_JINGLE` with the id the **client** understands, which is not the id `RSCMType.JINGLE` + * resolves to. + * + * [org.rsmod.api.player.midiJingle] passes the RSCM id straight into the packet, and for jingles + * that id is meaningless to the client. `jingle.advance_attack` resolves to 1177; the client looks + * that up as a group in js5 archive 11, which in this cache stops at 864. Nothing to play, so + * nothing played - which is why levelling up was silent while the fireworks and the box worked. + * + * #### Why the name cannot be turned into an id + * + * The cache's gameval table for sounds is one flat list of 1197 names covering both music and + * jingles, and its ids are their own space. 435 of the music names can be recovered from archive 6's + * name hashes (`String.hashCode` of the original name), and for **every single one** the gameval id + * differs from the group id - `claustrophobia` is gameval 2 but group 373, `sea_minor_shanty` is + * gameval 21 but group 683. It is not an offset or any orderly permutation. Archive 11 carries no + * name hashes at all, so nothing in the cache maps a jingle name to a group id. + * + * [IDS] therefore holds the numbers directly. See there for how they were found. + */ +internal object Jingles { + /** + * Archive 11 group ids for each level-up jingle, keyed by the RSCM name [LevelUpJingles] uses. + * + * Nothing in the cache maps a jingle name to a group id - archive 11's js5 index sets no name + * flag, the files embed no names, and no dbtable, dbcol or param references one. A published + * 317-era list was tried and is wrong for revision 240; the sound archives were renumbered + * somewhere between the two. + * + * #### The `advance_*` batch + * + * The `advance_*` jingles sit in one contiguous alphabetical batch starting at group 28, all + * 377-2550 bytes decompressed against much larger files either side. Group 28 was confirmed + * in-game as the Agility level up and 29 as Attack, and Agility and Attack are alphabetically + * first among those names, which fixes the run. + * + * The batch holds 43 names, not the 46 the cache lists: `advance_sailing`, + * `advance_sailing2` and `advance_sailing_charting` are absent. Sailing is far newer than the + * rest, so its jingles were appended to the end of the archive rather than inserted into a + * batch laid down years earlier. Sailing sorts after `advance_runecraft2`, so only groups 61 + * and up moved. The ordering is only alphabetical *within* the batch - the archive as a whole + * is not sorted. + * + * #### Everything outside the batch: match durations against the wiki + * + * Farming uses `farming_levelup` / `farming_levelup_2` rather than an `advance_` name, and + * Sailing's three were appended years later, so neither is in the batch. They were found by + * decoding every group in archive 11 - the files are Jagex's packed MIDI, which the client's + * `MusicTrack` constructor turns back into standard MIDI - and computing each one's playing + * time from its tempo events. The wiki's jingle recordings list their length to a tenth of a + * second, and the two agree exactly on every group already known (28, 29, 30, 69, 70), so a + * length is as good as a name: + * - Farming Level Up! is 3.9 s and the unlocks variant 6.9 s. Groups 10 and 11 are the only + * groups in the archive with those lengths, they are adjacent, and they share a tempo and + * resolution no other group nearby has: a pair authored together. + * - Sailing's three groups are 830-832, the only ones whose js5 version is a timestamp + * rather than a small edit count. 831 (6.4 s) matches Sailing Level Up!, 832 (9.9 s) the + * 99 jingle, and 830 (4.1 s) is the charting jingle by elimination. + * + * Only the level-up jingles are listed. Any other jingle can be found the same way, or by + * ear with `::jingle`. + */ + private val IDS: Map = + mapOf( + "jingle.farming_levelup" to 10, + "jingle.farming_levelup_2" to 11, + "jingle.advance_agility" to 28, + "jingle.advance_attack" to 29, + "jingle.advance_attack2" to 30, + "jingle.advance_carpentry" to 31, + "jingle.advance_carpentry2" to 32, + "jingle.advance_cooking" to 33, + "jingle.advance_cooking2" to 34, + "jingle.advance_crafting" to 35, + "jingle.advance_crafting2" to 36, + "jingle.advance_defense" to 37, + "jingle.advance_defense2" to 38, + "jingle.advance_firemarking" to 39, + "jingle.advance_firemarking2" to 40, + "jingle.advance_fishing" to 41, + "jingle.advance_fishing2" to 42, + "jingle.advance_fletching" to 43, + "jingle.advance_fletching2" to 44, + "jingle.advance_herblaw" to 45, + "jingle.advance_herblaw2" to 46, + "jingle.advance_hitpoints" to 47, + "jingle.advance_hitpoints2" to 48, + "jingle.advance_hunting" to 49, + "jingle.advance_hunting2" to 50, + "jingle.advance_magic" to 51, + "jingle.advance_magic2" to 52, + "jingle.advance_mining" to 53, + "jingle.advance_mining2" to 54, + "jingle.advance_prayer" to 55, + "jingle.advance_prayer2" to 56, + "jingle.advance_ranged" to 57, + "jingle.advance_ranged2" to 58, + "jingle.advance_runecraft" to 59, + "jingle.advance_runecraft2" to 60, + "jingle.advance_slayer" to 61, + "jingle.advance_slayer2" to 62, + "jingle.advance_smithing" to 63, + "jingle.advance_smithing2" to 64, + "jingle.advance_strength" to 65, + "jingle.advance_strength2" to 66, + "jingle.advance_thieving" to 67, + "jingle.advance_thieving2" to 68, + "jingle.advance_woodcutting" to 69, + "jingle.advance_woodcutting2" to 70, + "jingle.advance_sailing_charting" to 830, + "jingle.advance_sailing" to 831, + "jingle.advance_sailing2" to 832, + ) + + /** The archive 11 group id for [jingle], or `null` if it has not been identified. */ + fun idOf(jingle: String): Int? = IDS[jingle] + + /** Plays [jingle] on [player]; does nothing if that jingle's id is unknown. */ + fun play(player: Player, jingle: String) { + val id = idOf(jingle) ?: return + playRaw(player, id) + } + + /** Plays archive 11 group [id] directly, for identifying jingles by ear. */ + fun playRaw(player: Player, id: Int) { + player.musicClocks = 0 // The client restarts the current song once the jingle finishes. + player.client.write(MidiJingle(id)) + } +} diff --git a/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpDisplay.kt b/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpDisplay.kt new file mode 100644 index 000000000..1011cce58 --- /dev/null +++ b/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpDisplay.kt @@ -0,0 +1,102 @@ +package org.rsmod.content.other.levelup + +import dev.openrune.ServerCacheManager +import dev.openrune.definition.type.widget.IfEvent +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.aconverted.interf.IfSubType +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.config.constants +import org.rsmod.api.player.output.ClientScripts.topLevelChatboxResetBackground +import org.rsmod.api.player.ui.ifOpenSub +import org.rsmod.api.player.ui.ifSetEvents +import org.rsmod.api.player.ui.ifSetHide +import org.rsmod.api.player.ui.ifSetText +import org.rsmod.api.player.vars.intVarBit +import org.rsmod.events.EventBus +import org.rsmod.game.entity.Player + +private var Player.chatModalUnclamp: Int by intVarBit("varbit.chatmodal_unclamp") + +/** + * The level-up box in the chatbox - `interface.levelup_display`, the one with the skill's icon, + * "Congratulations, you just advanced a Cooking level." and "Click here to continue". + * + * The interface holds one hidden layer per skill (plus `combat`), each containing that skill's + * icon models, and shares `text1`, `text2` and `continue` across all of them. There is no + * clientscript driving it, so showing the right icon is the server's job: open the interface, then + * un-hide the one layer that matches the skill. + * + * The layer names line up with the stat names - `attack`, `cooking`, `woodcutting` - so they are + * derived rather than tabulated, with [LAYER_ALIASES] covering the one that does not match and a + * check against the interface itself so a skill with no layer falls back to plain chat lines + * instead of throwing. + * + * #### Dismissing it + * + * The box does not pause the player; it sits in the chatbox while they carry on. `continue` is sent + * as an [IfEvent.PauseButton], and `ResumePauseButtonHandler` closes the modal on click whether or + * not a coroutine is waiting on it, so clicking it dismisses the box. Anything that opens its own + * chat modal replaces it, which is also how Old School RuneScape behaves. + * + * #### Why it never lands on top of a dialogue + * + * Opening a chat modal closes whatever is in that slot, which would strand a suspended dialogue. + * It cannot happen here: `PlayerEngineQueueProcessor` only publishes an engine queue while the + * player is not access-protected, and a player waiting on a dialogue is. + */ +@Singleton +class LevelUpDisplay @Inject constructor(private val eventBus: EventBus) { + private val layerNames: Set by lazy { + val interf = ServerCacheManager.getInterface(INTERFACE.asRSCM(RSCMType.INTERFACE)) + interf?.components?.values?.mapNotNullTo(HashSet()) { it.internalName }.orEmpty() + } + + /** + * Returns the component of the layer holding [internalStatName]'s icon, or `null` if this + * interface has no layer for that skill. + */ + fun layerFor(internalStatName: String): String? { + val name = LAYER_ALIASES[internalStatName] ?: internalStatName.substringAfter('.') + return if (name in layerNames) "component.levelup_display:$name" else null + } + + /** Opens the box in the chatbox with [layer]'s icon showing and [line1] / [line2] set. */ + fun show(player: Player, layer: String, line1: String, line2: String) { + // Matches `ifOpenChat`, which is internal to api.player: the box is a fixed 479x96, so it + // must not inherit an "unclamped" size from whichever dialogue used the slot last. + player.chatModalUnclamp = constants.modal_fixedwidthandheight + topLevelChatboxResetBackground(player) + player.ifOpenSub(INTERFACE, CHAT_MODAL, IfSubType.Modal, eventBus) + + // Re-opening the interface resets its components to their cache defaults, which hides every + // skill layer again. Hiding the last one shown is belt and braces, and costs one packet. + val previous = player.attr[SHOWN_LAYER] + if (previous != null && previous != layer) { + player.ifSetHide(previous, true) + } + player.ifSetHide(layer, false) + player.attr[SHOWN_LAYER] = layer + + player.ifSetText(TEXT_LINE_1, line1) + player.ifSetText(TEXT_LINE_2, line2) + player.ifSetEvents(CONTINUE, -1..-1, IfEvent.PauseButton) + player.ifSetText(CONTINUE, "Click here to continue") + } + + private companion object { + private const val INTERFACE = "interface.levelup_display" + private const val CHAT_MODAL = "component.chatbox:chatmodal" + private const val TEXT_LINE_1 = "component.levelup_display:text1" + private const val TEXT_LINE_2 = "component.levelup_display:text2" + private const val CONTINUE = "component.levelup_display:continue" + + /** Layer names that do not match their stat name. */ + private val LAYER_ALIASES = mapOf("stat.runecrafting" to "runecraft") + + /** The layer last un-hidden for a player, so it can be hidden again on the next level up. */ + private val SHOWN_LAYER = AttributeKey() + } +} diff --git a/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpJingles.kt b/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpJingles.kt new file mode 100644 index 000000000..20b4defc9 --- /dev/null +++ b/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpJingles.kt @@ -0,0 +1,103 @@ +package org.rsmod.content.other.levelup + +/** + * Everything picking a jingle needs to know about one level up. + * + * @param level The base level the player has just reached. + * @param maxLevel The stat's own ceiling, from the cache. 99 for every released skill, but read + * rather than assumed so a server that raises a cap keeps the "mastery" jingle on the real last + * level. + * @param unlocksContent Whether this level makes new content available. See [LevelUpUnlocks]. + */ +internal data class LevelUp(val level: Int, val maxLevel: Int, val unlocksContent: Boolean) + +/** + * The one or two jingles a skill can play when it advances, and the rule that chooses between them. + * + * @param base The jingle played when [preferAlternate] does not apply. + * @param alternate The skill's second jingle, or `null` for the skills that only have one. + */ +internal class SkillJingle( + val base: String, + val alternate: String? = null, + private val preferAlternate: (LevelUp) -> Boolean = { false }, +) { + fun pick(levelUp: LevelUp): String = + if (alternate != null && preferAlternate(levelUp)) alternate else base +} + +/** + * Maps each skill to its level-up jingles, following + * [the wiki's jingle list](https://oldschool.runescape.wiki/w/Jingles). + * + * Most skills have two: a normal one and one for "level ups that unlock new content". The rest pick + * between their pair on a fixed rule instead - Hitpoints and Strength swap over at 50, Hunter + * alternates on odd/even levels, Construction has a separate every-tenth jingle, Sailing has one + * reserved for 99, and Smithing only ever plays its unlock jingle because in Old School RuneScape + * every Smithing level unlocks something. Agility is the only skill with a single jingle. + * + * The names are the cache's own, which is why some of them are spelled the way the original RuneScape + * developers spelled them: `advance_defense`, `advance_herblaw`, `advance_firemarking`, + * `advance_carpentry`, `advance_hunting`. Farming is the odd one out and uses `farming_levelup` + * rather than an `advance_` name. + */ +internal object LevelUpJingles { + /** Returns the jingles for [internalStatName] (an RSCM name such as `"stat.attack"`). */ + operator fun get(internalStatName: String): SkillJingle? = byStat[internalStatName] + + private val onUnlock: (LevelUp) -> Boolean = LevelUp::unlocksContent + private val always: (LevelUp) -> Boolean = { true } + private val fromLevelFifty: (LevelUp) -> Boolean = { it.level >= 50 } + private val onEvenLevel: (LevelUp) -> Boolean = { it.level % 2 == 0 } + private val everyTenthLevel: (LevelUp) -> Boolean = { it.level % 10 == 0 } + private val atMaxLevel: (LevelUp) -> Boolean = { it.level >= it.maxLevel } + + private val byStat: Map = + mapOf( + // Skills whose second jingle marks a level that unlocks new content. + "stat.attack" to SkillJingle("jingle.advance_attack", "jingle.advance_attack2", onUnlock), + "stat.cooking" to + SkillJingle("jingle.advance_cooking", "jingle.advance_cooking2", onUnlock), + "stat.crafting" to + SkillJingle("jingle.advance_crafting", "jingle.advance_crafting2", onUnlock), + "stat.defence" to + SkillJingle("jingle.advance_defense", "jingle.advance_defense2", onUnlock), + "stat.farming" to SkillJingle("jingle.farming_levelup", "jingle.farming_levelup_2", onUnlock), + "stat.firemaking" to + SkillJingle("jingle.advance_firemarking", "jingle.advance_firemarking2", onUnlock), + "stat.fishing" to + SkillJingle("jingle.advance_fishing", "jingle.advance_fishing2", onUnlock), + "stat.fletching" to + SkillJingle("jingle.advance_fletching", "jingle.advance_fletching2", onUnlock), + "stat.herblore" to + SkillJingle("jingle.advance_herblaw", "jingle.advance_herblaw2", onUnlock), + "stat.magic" to SkillJingle("jingle.advance_magic", "jingle.advance_magic2", onUnlock), + "stat.mining" to SkillJingle("jingle.advance_mining", "jingle.advance_mining2", onUnlock), + "stat.prayer" to SkillJingle("jingle.advance_prayer", "jingle.advance_prayer2", onUnlock), + "stat.ranged" to SkillJingle("jingle.advance_ranged", "jingle.advance_ranged2", onUnlock), + "stat.runecrafting" to + SkillJingle("jingle.advance_runecraft", "jingle.advance_runecraft2", onUnlock), + "stat.slayer" to SkillJingle("jingle.advance_slayer", "jingle.advance_slayer2", onUnlock), + "stat.thieving" to + SkillJingle("jingle.advance_thieving", "jingle.advance_thieving2", onUnlock), + "stat.woodcutting" to + SkillJingle("jingle.advance_woodcutting", "jingle.advance_woodcutting2", onUnlock), + + // Skills that pick between their pair on a fixed rule. + "stat.agility" to SkillJingle("jingle.advance_agility"), + "stat.construction" to + SkillJingle("jingle.advance_carpentry", "jingle.advance_carpentry2", everyTenthLevel), + "stat.hitpoints" to + SkillJingle("jingle.advance_hitpoints", "jingle.advance_hitpoints2", fromLevelFifty), + // The wiki names Hunter's pair "Even" and "Odd" without saying which cache name is + // which; the `2` variant is paired with the even levels here. + "stat.hunter" to + SkillJingle("jingle.advance_hunting", "jingle.advance_hunting2", onEvenLevel), + "stat.sailing" to + SkillJingle("jingle.advance_sailing", "jingle.advance_sailing2", atMaxLevel), + "stat.smithing" to + SkillJingle("jingle.advance_smithing", "jingle.advance_smithing2", always), + "stat.strength" to + SkillJingle("jingle.advance_strength", "jingle.advance_strength2", fromLevelFifty), + ) +} diff --git a/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpScript.kt b/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpScript.kt new file mode 100644 index 000000000..21788467f --- /dev/null +++ b/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpScript.kt @@ -0,0 +1,212 @@ +package org.rsmod.content.other.levelup + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.StatType +import dev.or2.central.account.Rights +import jakarta.inject.Inject +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.script.advanced.onAdvanceStat +import org.rsmod.api.script.onCommand +import org.rsmod.api.script.onPlayerLogin +import org.rsmod.game.cheat.Cheat +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The level-up celebration: the jingle, the fireworks over the player's head, and the box in the + * chatbox that says what went up. + * + * [org.rsmod.api.player.stat.PlayerSkillXP] already does the bookkeeping - it raises the base level, + * re-sends the stat and recalculates the combat level - and then pushes an `AdvanceStat` engine + * queue. Nothing was listening to it, so levelling up was silent. This script is that listener. + * + * Three fireworks play, matching Old School RuneScape: + * - `spotanim.levelup_anim` for an ordinary level. + * - `spotanim.levelup_99_anim` on reaching a skill's last level, in place of the ordinary set. + * - `spotanim.levelup_max` once every released skill is at its ceiling. + * + * Which jingle goes with which skill, and how the two-jingle skills choose, lives in + * [LevelUpJingles]. The chatbox box - `interface.levelup_display` - is in [LevelUpDisplay]. + * + * #### Combat levels + * + * A combat level up gets its chat lines, but neither the jingle nor the box. Both are single-slot: + * the client plays one jingle at a time and the chatbox holds one modal, and a combat level only + * ever moves on the back of a combat skill advancing. `jingle.combat_level_up` and the interface's + * own `combat` layer would therefore always land on top of - and replace - the skill that just + * earned them. The skill keeps the presentation. + * + * #### Only one script may bind `onAdvanceStat` + * + * `onAdvanceStat` is an engine-queue script, and the engine allows a single default handler per + * queue type. This plugin claims it. Anything else that wants to react to a level up should hook + * into here rather than binding its own. + */ +class LevelUpScript +@Inject +constructor(private val unlocks: LevelUpUnlocks, private val display: LevelUpDisplay) : + PluginScript() { + override fun ScriptContext.startup() { + onPlayerLogin { player.attr[LAST_COMBAT_LEVEL] = player.combatLevel } + + onAdvanceStat { player.celebrate(it.args) } + + onCommand("testlevelup") { + desc = "Replay the level-up jingle and fireworks for a skill, without granting xp" + requiredRights = Rights.ADMINISTRATOR + invalidArgs = "Usage: ::testlevelup [level]" + cheat(::testLevelUp) + } + + onCommand("jingle") { + desc = "Play a jingle by archive id; no id plays the next one along" + requiredRights = Rights.ADMINISTRATOR + invalidArgs = "Usage: ::jingle [id]" + cheat(::playJingle) + } + } + + /** + * Plays everything a player should see and hear for having just reached a new [stat] level. + * + * The chat line goes out as an ordinary game message. The client also has a chat type of its + * own for level ups (`ChatType.LevelUpMessage`, 118) which would let players filter them + * separately; swap [mes]'s type over if you want that and your client renders it. + */ + private fun Player.celebrate(stat: StatType, level: Int = statBase(stat.rscmName)) { + val name = stat.name() + val advanced = "Congratulations, you just advanced ${article(name)} $name level." + val nowAt = "Your $name level is now $level." + + // Old School RuneScape only puts the first line in the chat log; the second one lives in + // the level-up box. If this skill has no layer on that interface, both go to chat. + mes(advanced) + val layer = display.layerFor(stat.rscmName) + if (layer != null) { + display.show(this, layer, advanced, nowAt) + } else { + mes(nowAt) + } + + val jingle = LevelUpJingles[stat.rscmName] + if (jingle != null) { + val unlocksContent = unlocks.unlocksContent(stat.id, level) + Jingles.play(this, jingle.pick(LevelUp(level, stat.maxLevel, unlocksContent))) + } + + spotanim(fireworks(stat, level), height = FIREWORKS_HEIGHT) + + announceCombatLevel() + } + + /** + * Old School RuneScape swaps the usual fireworks for a bigger set on a skill's last level, and + * for a longer red and gold set once there is nothing left to level. + */ + private fun Player.fireworks(stat: StatType, level: Int): String = + when { + level < stat.maxLevel -> ORDINARY_FIREWORKS + hasMaxedEveryStat() -> MAXED_FIREWORKS + else -> MASTERY_FIREWORKS + } + + private fun Player.hasMaxedEveryStat(): Boolean = + releasedStats().all { statBase(it.rscmName) >= it.maxLevel } + + /** + * Sends the combat level lines if this level up pushed the combat level along with it. + * + * The combat level is recalculated inside `PlayerSkillXP` before the engine queue runs, so + * [Player.combatLevel] is already the new value by the time this is called; the previous one is + * tracked per session instead. A character that somehow reaches here without a stored value - + * only possible if the login handler did not run - records the current level and stays quiet + * rather than reporting a level up it cannot vouch for. + */ + private fun Player.announceCombatLevel() { + val previous = attr[LAST_COMBAT_LEVEL] + val current = combatLevel + attr[LAST_COMBAT_LEVEL] = current + if (previous == null || current <= previous) { + return + } + mes("Congratulations, you just advanced a Combat level.") + mes("Your Combat level is now $current.") + } + + /* Commands */ + + private fun testLevelUp(cheat: Cheat) { + val player = cheat.player + val name = cheat.args.getOrNull(0) + if (name == null) { + player.mes("Usage: ::testlevelup [level]") + return + } + + val stat = findStat(name) + if (stat == null) { + player.mes("No skill found named '$name'.") + return + } + + val level = cheat.args.getOrNull(1)?.toInt() ?: player.statBase(stat.rscmName) + player.celebrate(stat, level.coerceIn(1, stat.maxLevel)) + } + + /** + * Plays one jingle straight from archive 11, bypassing the name lookup, so the ids can be + * identified by ear - the cache has no jingle names left to derive them from. With no + * argument it plays the one after whatever was played last, so a run of ids can be walked + * through by repeating the command. The level-up family is most likely somewhere in 28-70. + */ + private fun playJingle(cheat: Cheat) { + val player = cheat.player + val id = cheat.args.getOrNull(0)?.toInt() ?: ((player.attr[LAST_JINGLE_ID] ?: -1) + 1) + player.attr[LAST_JINGLE_ID] = id + Jingles.playRaw(player, id) + player.mes("Playing jingle $id.") + } + + private fun findStat(name: String): StatType? = + releasedStats().firstOrNull { + it.displayName.equals(name, ignoreCase = true) || + it.rscmName.substringAfter('.').equals(name, ignoreCase = true) + } + + private companion object { + /** Matches the height Old School RuneScape draws over-the-head effects at. */ + private const val FIREWORKS_HEIGHT = 100 + + private const val ORDINARY_FIREWORKS = "spotanim.levelup_anim" + private const val MASTERY_FIREWORKS = "spotanim.levelup_99_anim" + private const val MAXED_FIREWORKS = "spotanim.levelup_max" + + /** + * The combat level as of the last time it was reported, so a level up can be spotted after + * the engine has already applied it. Per session; a fresh login re-seeds it. + */ + private val LAST_COMBAT_LEVEL = AttributeKey() + + /** The last archive id played with `::jingle`, so the command can step to the next. */ + private val LAST_JINGLE_ID = AttributeKey() + + private val StatType.rscmName: String + get() = RSCM.getReverseMapping(RSCMType.STAT, id) + + private fun releasedStats(): Collection = + ServerCacheManager.getStats().values.filterNot(StatType::unreleased) + + private fun StatType.name(): String = + displayName.ifBlank { rscmName.substringAfter('.').replaceFirstChar(Char::titlecase) } + + private fun article(name: String): String = + if ((name.firstOrNull() ?: ' ').lowercaseChar() in VOWELS) "an" else "a" + + private val VOWELS = setOf('a', 'e', 'i', 'o', 'u') + } +} diff --git a/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpUnlocks.kt b/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpUnlocks.kt new file mode 100644 index 000000000..f855ed4ce --- /dev/null +++ b/content/other/level-up/src/main/kotlin/org/rsmod/content/other/levelup/LevelUpUnlocks.kt @@ -0,0 +1,85 @@ +package org.rsmod.content.other.levelup + +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.config.refs.params + +/** + * Decides which levels count as "unlocking new content" for the seventeen skills whose second + * level-up jingle is reserved for exactly that. + * + * Old School RuneScape's own unlock lists are not something the server can read back: they are + * spread across quests, spellbooks, shop stock and every skilling action in the game, and any list + * copied out of the wiki would be wrong the moment this server's content differs from theirs. + * + * What the cache *does* hold, on every wieldable obj, is the level you need to equip it + * ([params.statreq1_level] / [params.statreq2_level]). Scanning those gives an unlock set that is + * exactly right for whatever gear this server actually ships - the Attack tiers, the armour tiers, + * the bows and staves, and the second requirement that gates things like a dragon axe on + * Woodcutting. Skills with no equipment gate simply never take their unlock jingle, which is the + * conservative outcome: they keep playing their normal one. + * + * [EXTRA_UNLOCK_LEVELS] is the hook for filling in the rest. Add the levels your own content + * unlocks at and they are merged into the scan. + */ +@Singleton +class LevelUpUnlocks @Inject constructor() { + private val levelsByStat: Map> by lazy(::scan) + + /** Returns `true` if reaching [level] in [statId] makes new content available. */ + fun unlocksContent(statId: Int, level: Int): Boolean = level in levelsByStat[statId].orEmpty() + + private fun scan(): Map> { + val levels = HashMap>() + + for (obj in ServerCacheManager.getItemTypes()) { + obj.recordRequirement(levels, first = true) + obj.recordRequirement(levels, first = false) + } + + for ((stat, extra) in EXTRA_UNLOCK_LEVELS) { + levels.getOrPut(stat.asRSCM(RSCMType.STAT), ::mutableSetOf) += extra + } + + logger.debug { + "Level-up unlock levels resolved for ${levels.size} stats " + + "(${levels.values.sumOf(Set::size)} levels in total)." + } + return levels + } + + private fun ItemServerType.recordRequirement( + levels: MutableMap>, + first: Boolean, + ) { + val stat = paramOrNull(if (first) params.statreq1_skill else params.statreq2_skill) ?: return + val level = paramOrNull(if (first) params.statreq1_level else params.statreq2_level) ?: return + if (level <= 1) { + return + } + levels.getOrPut(stat.id, ::mutableSetOf) += level + } + + private companion object { + private val logger = InlineLogger() + + /** + * Hand-maintained unlock levels, merged on top of the equipment scan. Keys are stat RSCM + * names; an unknown name fails loudly at first use rather than silently doing nothing. + * + * Nothing is listed by default - the equipment scan already covers the combat skills, and + * anything added here is a claim about *this* server's content, so it is left for whoever + * knows what that content is. For example: + * + * ``` + * "stat.cooking" to setOf(15, 25, 30, 40, 50, 62, 80), + * ``` + */ + private val EXTRA_UNLOCK_LEVELS: Map> = emptyMap() + } +} diff --git a/content/other/level-up/src/test/kotlin/org/rsmod/content/other/levelup/LevelUpJinglesTest.kt b/content/other/level-up/src/test/kotlin/org/rsmod/content/other/levelup/LevelUpJinglesTest.kt new file mode 100644 index 000000000..fb31051e7 --- /dev/null +++ b/content/other/level-up/src/test/kotlin/org/rsmod/content/other/levelup/LevelUpJinglesTest.kt @@ -0,0 +1,133 @@ +package org.rsmod.content.other.levelup + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test + +class LevelUpJinglesTest { + @Test + fun `every released skill has a jingle`() { + for (skill in RELEASED_SKILLS) { + assertNotNull(LevelUpJingles["stat.$skill"], "No jingle mapped for stat.$skill") + } + } + + /** + * A jingle name without an archive id plays nothing, silently - which is how Farming and + * Sailing level ups went quiet without any test noticing. + */ + @Test + fun `every jingle a skill can pick has a known archive id`() { + for (skill in RELEASED_SKILLS) { + val jingle = LevelUpJingles["stat.$skill"] ?: continue + assertNotNull(Jingles.idOf(jingle.base), "No archive id for ${jingle.base}") + val alternate = jingle.alternate ?: continue + assertNotNull(Jingles.idOf(alternate), "No archive id for $alternate") + } + } + + private companion object { + val RELEASED_SKILLS = + listOf( + "attack", + "defence", + "strength", + "hitpoints", + "ranged", + "prayer", + "magic", + "cooking", + "woodcutting", + "fletching", + "fishing", + "firemaking", + "crafting", + "smithing", + "mining", + "herblore", + "agility", + "thieving", + "slayer", + "farming", + "runecrafting", + "hunter", + "construction", + "sailing", + ) + } + + @Test + fun `skills with a single jingle ignore the level`() { + val agility = LevelUpJingles["stat.agility"]!! + + assertEquals("jingle.advance_agility", agility.pick(levelUp(2))) + assertEquals("jingle.advance_agility", agility.pick(levelUp(99, unlocksContent = true))) + } + + @Test + fun `unlock skills only take their second jingle when the level unlocks content`() { + val woodcutting = LevelUpJingles["stat.woodcutting"]!! + + assertEquals("jingle.advance_woodcutting", woodcutting.pick(levelUp(14))) + assertEquals( + "jingle.advance_woodcutting2", + woodcutting.pick(levelUp(15, unlocksContent = true)), + ) + } + + /** Smithing never plays its normal jingle, because every Smithing level unlocks something. */ + @Test + fun `smithing always takes its unlock jingle`() { + val smithing = LevelUpJingles["stat.smithing"]!! + + assertEquals("jingle.advance_smithing2", smithing.pick(levelUp(2))) + assertEquals("jingle.advance_smithing2", smithing.pick(levelUp(99))) + } + + @Test + fun `hitpoints and strength swap jingles at fifty`() { + val hitpoints = LevelUpJingles["stat.hitpoints"]!! + val strength = LevelUpJingles["stat.strength"]!! + + assertEquals("jingle.advance_hitpoints", hitpoints.pick(levelUp(49))) + assertEquals("jingle.advance_hitpoints2", hitpoints.pick(levelUp(50))) + assertEquals("jingle.advance_strength", strength.pick(levelUp(49))) + assertEquals("jingle.advance_strength2", strength.pick(levelUp(50))) + } + + @Test + fun `hunter alternates on odd and even levels`() { + val hunter = LevelUpJingles["stat.hunter"]!! + + assertEquals("jingle.advance_hunting2", hunter.pick(levelUp(2))) + assertEquals("jingle.advance_hunting", hunter.pick(levelUp(3))) + assertEquals("jingle.advance_hunting2", hunter.pick(levelUp(98))) + assertEquals("jingle.advance_hunting", hunter.pick(levelUp(99))) + } + + @Test + fun `construction has a separate jingle every tenth level`() { + val construction = LevelUpJingles["stat.construction"]!! + + assertEquals("jingle.advance_carpentry", construction.pick(levelUp(9))) + assertEquals("jingle.advance_carpentry2", construction.pick(levelUp(10))) + assertEquals("jingle.advance_carpentry", construction.pick(levelUp(11))) + assertEquals("jingle.advance_carpentry2", construction.pick(levelUp(90))) + } + + /** Sailing's second jingle is reserved for the last level, whatever the cache caps it at. */ + @Test + fun `sailing only takes its second jingle at max level`() { + val sailing = LevelUpJingles["stat.sailing"]!! + + assertEquals("jingle.advance_sailing", sailing.pick(levelUp(98))) + assertEquals("jingle.advance_sailing2", sailing.pick(levelUp(99))) + assertEquals( + "jingle.advance_sailing2", + sailing.pick(LevelUp(level = 120, maxLevel = 120, unlocksContent = false)), + ) + } + + private fun levelUp(level: Int, unlocksContent: Boolean = false): LevelUp = + LevelUp(level = level, maxLevel = 99, unlocksContent = unlocksContent) +} diff --git a/content/other/npc-aggression/build.gradle.kts b/content/other/npc-aggression/build.gradle.kts new file mode 100644 index 000000000..7ac63dc36 --- /dev/null +++ b/content/other/npc-aggression/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) + implementation(libs.jackson.databind) + implementation(libs.jackson.dataformat.toml) + implementation(libs.jackson.module.kotlin) +} diff --git a/content/other/npc-aggression/src/main/kotlin/org/rsmod/content/other/npcaggression/NpcAggressionFile.kt b/content/other/npc-aggression/src/main/kotlin/org/rsmod/content/other/npcaggression/NpcAggressionFile.kt new file mode 100644 index 000000000..3b62edbd4 --- /dev/null +++ b/content/other/npc-aggression/src/main/kotlin/org/rsmod/content/other/npcaggression/NpcAggressionFile.kt @@ -0,0 +1,42 @@ +package org.rsmod.content.other.npcaggression + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.toml.TomlFactory +import com.fasterxml.jackson.module.kotlin.readValue +import com.fasterxml.jackson.module.kotlin.registerKotlinModule + +/** + * One npc's aggression entry. [wiki] is the page the value came from and [note] any condition the + * wiki attaches (an item that pacifies it, a quest that removes it) which is recorded but not yet + * modelled. [aggressive] is only ever `false` in the overrides file, to silence a generated entry. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +data class NpcAggressionEntry( + val id: String, + val wiki: String? = null, + val note: String? = null, + val aggressive: Boolean = true, +) + +@JsonIgnoreProperties(ignoreUnknown = true) +data class NpcAggressionFile(val npc: List = emptyList()) { + companion object { + /** Generated from the OSRS wiki; see `docs/aggression.md`. */ + const val GENERATED_RESOURCE: String = "npc-aggression.toml" + + /** Hand-maintained corrections. Applied after the generated file and always win. */ + const val OVERRIDES_RESOURCE: String = "npc-aggression-overrides.toml" + + private val mapper: ObjectMapper = ObjectMapper(TomlFactory()).registerKotlinModule() + + fun loadResource(name: String): NpcAggressionFile { + val stream = + NpcAggressionFile::class.java.classLoader.getResourceAsStream(name) + ?: error("Missing resource: $name") + return stream.use { parse(it.readBytes().decodeToString()) } + } + + fun parse(toml: String): NpcAggressionFile = mapper.readValue(toml) + } +} diff --git a/content/other/npc-aggression/src/main/kotlin/org/rsmod/content/other/npcaggression/NpcAggressionScript.kt b/content/other/npc-aggression/src/main/kotlin/org/rsmod/content/other/npcaggression/NpcAggressionScript.kt new file mode 100644 index 000000000..0c00b0398 --- /dev/null +++ b/content/other/npc-aggression/src/main/kotlin/org/rsmod/content/other/npcaggression/NpcAggressionScript.kt @@ -0,0 +1,98 @@ +package org.rsmod.content.other.npcaggression + +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.NpcServerType +import org.rsmod.api.config.refs.params +import org.rsmod.api.script.onEvent +import org.rsmod.game.entity.npc.NpcStateEvents +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Makes the monsters the OSRS wiki lists as aggressive hunt players. + * + * The cache carries no aggression flag, so without server data every monster stands still until + * attacked. [NpcAggressionFile.GENERATED_RESOURCE] lists the npcs whose wiki infobox says + * `aggressive = Yes`; this script gives each of them a player-hunting mode at boot, before the + * map spawns its npcs, unless the npc's own config already declares one (bosses and God Wars + * creatures do). Melee monsters get `stalk.constant_melee`, which walks up to the target; + * ranged and magic monsters get `stalk.constant_ranged`, which attacks from range. Both modes + * only hunt players of at most twice the monster's combat level outside the Wilderness and + * respect the ten-minute tolerance, exactly as the real game does. + */ +class NpcAggressionScript : PluginScript() { + override fun ScriptContext.startup() { + val generated = NpcAggressionFile.loadResource(NpcAggressionFile.GENERATED_RESOURCE) + val overrides = NpcAggressionFile.loadResource(NpcAggressionFile.OVERRIDES_RESOURCE) + val applied = apply(generated.npc, force = false) + val forced = apply(overrides.npc, force = true) + logger.info { "Applied aggression to $applied npcs ($forced overrides)." } + + // Map npcs are constructed while the map loads, before any script runs, and copy their + // hunt mode from the type at that point. Give them the mode the type has now when they + // spawn; npcs created later already see the updated type. + onEvent { + val typeMode = npc.type.huntMode + if (npc.huntMode == null && typeMode != null) { + ServerCacheManager.getHunt(typeMode)?.let(npc::setHuntMode) + } + } + } + + private fun apply(entries: List, force: Boolean): Int { + var applied = 0 + for (entry in entries) { + val id = entry.id.asRSCM(RSCMType.NPC) + val type = ServerCacheManager.getNpc(id) + if (type == null) { + logger.warn { "Npc not in cache, skipping: ${entry.id}" } + continue + } + if (!entry.aggressive) { + type.huntMode = null + applied++ + continue + } + if (!force && type.huntMode != null) { + continue + } + if (!type.hasOp(ATTACK_OP)) { + continue + } + type.huntMode = if (type.attacksFromRange()) HUNT_CONSTANT_RANGED else HUNT_CONSTANT_MELEE + applied++ + } + return applied + } + + /** Ranged and magic attackers stop at range rather than walking up to their target. */ + private fun NpcServerType.attacksFromRange(): Boolean { + val attackType = paramOrNull(params.npc_attack_type) + if (attackType != null) { + return RANGED_ATTACK_TYPES.any { attackType.isType(it) } + } + return paramOrNull(params.proj_travel) != null + } + + private companion object { + private val logger = InlineLogger() + + /** The `Attack` op sits in slot 2 for every attackable npc. */ + private const val ATTACK_OP = 2 + + /** Hunt modes from `.data/gamevals/stalk.rscm`; there is no RSCM prefix for them. */ + private const val HUNT_CONSTANT_MELEE = 1 + private const val HUNT_CONSTANT_RANGED = 2 + + private val RANGED_ATTACK_TYPES = + listOf( + "category.attacktype_light", + "category.attacktype_standard", + "category.attacktype_heavy", + "category.attacktype_magic", + ) + } +} diff --git a/content/other/npc-aggression/src/main/resources/npc-aggression-overrides.toml b/content/other/npc-aggression/src/main/resources/npc-aggression-overrides.toml new file mode 100644 index 000000000..b6e39f5ac --- /dev/null +++ b/content/other/npc-aggression/src/main/resources/npc-aggression-overrides.toml @@ -0,0 +1,5 @@ +# Hand-maintained aggression corrections. Same format as npc-aggression.toml; applied after it. +# Set `aggressive = false` to stop a generated entry hunting, or list an npc the wiki misses. + +npc = [ +] diff --git a/content/other/npc-aggression/src/main/resources/npc-aggression.toml b/content/other/npc-aggression/src/main/resources/npc-aggression.toml new file mode 100644 index 000000000..1c467f4b0 --- /dev/null +++ b/content/other/npc-aggression/src/main/resources/npc-aggression.toml @@ -0,0 +1,2144 @@ +# Monsters the OSRS wiki lists as aggressive - generated from the Infobox Monster `aggressive` +# field of every attackable npc name, do not edit by hand. +# +# Regenerate with the fetch script described in docs/aggression.md. +# Hand corrections: npc-aggression-overrides.toml (same format; those always win). +# +# The plugin gives each npc listed here a player-hunting mode at boot unless its own config +# already declares one. `note` records a wiki condition the server does not model yet. + +npc = [ + { id = "npc.abyssal_leech", wiki = "Abyssal leech" }, + { id = "npc.abyssal_pyramid", wiki = "Abyssal guardian" }, + { id = "npc.abyssal_walker", wiki = "Abyssal walker" }, + { id = "npc.abyssalsire_scion", wiki = "Scion" }, + { id = "npc.abyssalsire_scion_dying", wiki = "Scion" }, + { id = "npc.abyssalsire_spawn", wiki = "Spawn" }, + { id = "npc.abyssalsire_spawn_dying", wiki = "Spawn" }, + { id = "npc.adamant_dragon", wiki = "Adamant dragon" }, + { id = "npc.aggressive_black_knight", wiki = "Black Knight" }, + { id = "npc.aggressive_black_knight_f", wiki = "Black Knight" }, + { id = "npc.agrith_naar", wiki = "Agrith Naar" }, + { id = "npc.akd_barbarian_warlord", wiki = "Barbarian Warlord" }, + { id = "npc.akd_egg_lizardman", wiki = "Lizardman brute" }, + { id = "npc.akd_xamphur_combat", wiki = "Xamphur" }, + { id = "npc.akd_xamphur_cutscene", wiki = "Xamphur" }, + { id = "npc.akd_xamphur_cutscene_transition_b", wiki = "Xamphur" }, + { id = "npc.akd_xamphur_hand_left", wiki = "Phantom Hand" }, + { id = "npc.akd_xamphur_hand_right", wiki = "Phantom Hand" }, + { id = "npc.akkha_enrage", wiki = "Akkha" }, + { id = "npc.akkha_enrage_dummy", wiki = "Akkha" }, + { id = "npc.akkha_enrage_initial", wiki = "Akkha" }, + { id = "npc.akkha_enrage_spawn", wiki = "Akkha" }, + { id = "npc.akkha_mage", wiki = "Akkha" }, + { id = "npc.akkha_melee", wiki = "Akkha" }, + { id = "npc.akkha_range", wiki = "Akkha" }, + { id = "npc.akkha_spawn", wiki = "Akkha" }, + { id = "npc.amoxliatl", wiki = "Amoxliatl" }, + { id = "npc.amoxliatl_quest", wiki = "Amoxliatl" }, + { id = "npc.ancient_wyvern", wiki = "Ancient Wyvern" }, + { id = "npc.araxxor_dead", wiki = "Araxxor" }, + { id = "npc.araxxor_minion_egg_explode", wiki = "Ruptura Araxyte" }, + { id = "npc.araxxor_minion_egg_mirrorback", wiki = "Mirrorback Araxyte" }, + { id = "npc.araxxor_minion_egg_venom", wiki = "Acidic Araxyte" }, + { id = "npc.araxyte_large", wiki = "Araxyte" }, + { id = "npc.araxyte_large_bigwander", wiki = "Araxyte" }, + { id = "npc.araxyte_normal", wiki = "Araxyte" }, + { id = "npc.araxyte_normal_bigwander", wiki = "Araxyte" }, + { id = "npc.arceuus_reanimated_abyssal", wiki = "Reanimated abyssal" }, + { id = "npc.arceuus_reanimated_aviansie", wiki = "Reanimated aviansie" }, + { id = "npc.arceuus_reanimated_bear", wiki = "Reanimated bear" }, + { id = "npc.arceuus_reanimated_bloodveld", wiki = "Reanimated bloodveld" }, + { id = "npc.arceuus_reanimated_chaosdruid", wiki = "Reanimated chaos druid" }, + { id = "npc.arceuus_reanimated_dagannoth", wiki = "Reanimated dagannoth" }, + { id = "npc.arceuus_reanimated_demon", wiki = "Reanimated demon" }, + { id = "npc.arceuus_reanimated_dog", wiki = "Reanimated dog" }, + { id = "npc.arceuus_reanimated_dragon", wiki = "Reanimated dragon" }, + { id = "npc.arceuus_reanimated_elf", wiki = "Reanimated elf" }, + { id = "npc.arceuus_reanimated_giant", wiki = "Reanimated giant" }, + { id = "npc.arceuus_reanimated_goblin", wiki = "Reanimated goblin" }, + { id = "npc.arceuus_reanimated_hellhound", wiki = "Reanimated hellhound" }, + { id = "npc.arceuus_reanimated_horror", wiki = "Reanimated horror" }, + { id = "npc.arceuus_reanimated_imp", wiki = "Reanimated imp" }, + { id = "npc.arceuus_reanimated_kalphite", wiki = "Reanimated kalphite" }, + { id = "npc.arceuus_reanimated_minotaur", wiki = "Reanimated minotaur" }, + { id = "npc.arceuus_reanimated_monkey", wiki = "Reanimated monkey" }, + { id = "npc.arceuus_reanimated_ogre", wiki = "Reanimated ogre" }, + { id = "npc.arceuus_reanimated_scorpion", wiki = "Reanimated scorpion" }, + { id = "npc.arceuus_reanimated_troll", wiki = "Reanimated troll" }, + { id = "npc.arceuus_reanimated_tzhaar", wiki = "Reanimated TzHaar" }, + { id = "npc.arceuus_reanimated_unicorn", wiki = "Reanimated unicorn" }, + { id = "npc.arcquest_ghost1", wiki = "Tormented Soul" }, + { id = "npc.arcquest_ghost2", wiki = "Tormented Soul" }, + { id = "npc.arcquest_soul", wiki = "Trapped Soul" }, + { id = "npc.arena_bouncer", wiki = "Bouncer" }, + { id = "npc.arena_ogre", wiki = "Khazard Ogre" }, + { id = "npc.arena_scorpion", wiki = "Khazard Scorpion" }, + { id = "npc.arenamage1", wiki = "Kolodion" }, + { id = "npc.arenamage2", wiki = "Kolodion" }, + { id = "npc.avium_giant1", wiki = "Hill Giant" }, + { id = "npc.avium_giant2", wiki = "Hill Giant" }, + { id = "npc.avium_giant3", wiki = "Hill Giant" }, + { id = "npc.baby_black_dragon_nohunt", wiki = "Baby black dragon", note = "Yes (except in Myths' Guild and Charred Dungeon)" }, + { id = "npc.baby_black_dragon_strongholdcave", wiki = "Baby black dragon", note = "Yes (except in Myths' Guild and Charred Dungeon)" }, + { id = "npc.baby_blue_dragon_tapoyauik_1", wiki = "Baby blue dragon" }, + { id = "npc.baby_blue_dragon_tapoyauik_2", wiki = "Baby blue dragon" }, + { id = "npc.babybluedragon", wiki = "Baby blue dragon" }, + { id = "npc.babybluedragon2", wiki = "Baby blue dragon" }, + { id = "npc.babybluedragon3", wiki = "Baby blue dragon" }, + { id = "npc.babygreendragon1", wiki = "Baby green dragon" }, + { id = "npc.babygreendragon2", wiki = "Baby green dragon" }, + { id = "npc.babygreendragon3", wiki = "Baby green dragon" }, + { id = "npc.babyreddragon", wiki = "Baby red dragon" }, + { id = "npc.babyreddragon2", wiki = "Baby red dragon" }, + { id = "npc.babyreddragon3", wiki = "Baby red dragon" }, + { id = "npc.barbassault_pen_fighter_lv1", wiki = "Penance Fighter" }, + { id = "npc.barbassault_pen_fighter_lv2", wiki = "Penance Fighter" }, + { id = "npc.barbassault_pen_fighter_lv3", wiki = "Penance Fighter" }, + { id = "npc.barbassault_pen_fighter_lv4", wiki = "Penance Fighter" }, + { id = "npc.barbassault_pen_fighter_lv5", wiki = "Penance Fighter" }, + { id = "npc.barbassault_pen_fighter_lv6", wiki = "Penance Fighter" }, + { id = "npc.barbassault_pen_fighter_lv7", wiki = "Penance Fighter" }, + { id = "npc.barbassault_pen_fighter_lv8", wiki = "Penance Fighter" }, + { id = "npc.barbassault_pen_fighter_lv9", wiki = "Penance Fighter" }, + { id = "npc.barbassault_pen_fighter_tutor", wiki = "Penance Fighter" }, + { id = "npc.barbassault_pen_queen_new", wiki = "Penance Queen" }, + { id = "npc.barbassault_pen_queen_spawn", wiki = "Queen spawn" }, + { id = "npc.barbassault_pen_ranger_lv1", wiki = "Penance Ranger" }, + { id = "npc.barbassault_pen_ranger_lv2", wiki = "Penance Ranger" }, + { id = "npc.barbassault_pen_ranger_lv3", wiki = "Penance Ranger" }, + { id = "npc.barbassault_pen_ranger_lv4", wiki = "Penance Ranger" }, + { id = "npc.barbassault_pen_ranger_lv5", wiki = "Penance Ranger" }, + { id = "npc.barbassault_pen_ranger_lv6", wiki = "Penance Ranger" }, + { id = "npc.barbassault_pen_ranger_lv7", wiki = "Penance Ranger" }, + { id = "npc.barbassault_pen_ranger_lv8", wiki = "Penance Ranger" }, + { id = "npc.barbassault_pen_ranger_lv9", wiki = "Penance Ranger" }, + { id = "npc.barbassault_pen_ranger_tutor", wiki = "Penance Ranger" }, + { id = "npc.barrows_bloodworm", wiki = "Bloodworm" }, + { id = "npc.barrows_giantrat", wiki = "Giant crypt rat" }, + { id = "npc.barrows_giantrat2", wiki = "Giant crypt rat" }, + { id = "npc.barrows_giantrat3", wiki = "Giant crypt rat" }, + { id = "npc.barrows_giantspider", wiki = "Giant crypt spider" }, + { id = "npc.barrows_rat", wiki = "Crypt rat" }, + { id = "npc.barrows_spider", wiki = "Crypt spider" }, + { id = "npc.basilisk_knight", wiki = "Basilisk Knight" }, + { id = "npc.bat", wiki = "Giant bat" }, + { id = "npc.bcs_champion_combat", wiki = "Champion of Scabaras" }, + { id = "npc.bcs_champion_cutscene", wiki = "Champion of Scabaras" }, + { id = "npc.bcs_menaphite_akh_combat", wiki = "Menaphite Akh" }, + { id = "npc.bcs_menaphite_akh_shadow", wiki = "Menaphite Shadow" }, + { id = "npc.bcs_necropolis_headguard_combat", wiki = "Head Menaphite Guard" }, + { id = "npc.bcs_scarab_mage", wiki = "Scarab Mage" }, + { id = "npc.bearded_dark_wizard", wiki = "Dark wizard" }, + { id = "npc.bim_golem_boss", wiki = "Ancient Guardian" }, + { id = "npc.bim_golem_boss_inactive_cutscene", wiki = "Ancient Guardian" }, + { id = "npc.bim_golem_guardian_cutscene", wiki = "Ancient Guardian" }, + { id = "npc.black_demon", wiki = "Black demon" }, + { id = "npc.black_demon2", wiki = "Black demon" }, + { id = "npc.black_demon3", wiki = "Black demon" }, + { id = "npc.black_demon4", wiki = "Black demon" }, + { id = "npc.black_demon5", wiki = "Black demon" }, + { id = "npc.black_demon_strongholdcave_1", wiki = "Black demon" }, + { id = "npc.black_demon_strongholdcave_2", wiki = "Black demon" }, + { id = "npc.black_demon_strongholdcave_3", wiki = "Black demon" }, + { id = "npc.black_demon_strongholdcave_4", wiki = "Black demon" }, + { id = "npc.black_demon_strongholdcave_5", wiki = "Black demon" }, + { id = "npc.black_dragon", wiki = "Black dragon" }, + { id = "npc.black_dragon2", wiki = "Black dragon" }, + { id = "npc.black_dragon3", wiki = "Black dragon" }, + { id = "npc.black_dragon4", wiki = "Black dragon" }, + { id = "npc.black_dragon5", wiki = "Black dragon" }, + { id = "npc.black_dragon_strongholdcave_1", wiki = "Black dragon" }, + { id = "npc.black_dragon_strongholdcave_2", wiki = "Black dragon" }, + { id = "npc.black_dragon_strongholdcave_3", wiki = "Black dragon" }, + { id = "npc.black_unicorn", wiki = "Black unicorn" }, + { id = "npc.blessed_spider", wiki = "Blessed spider" }, + { id = "npc.blessed_spider_kalrag", wiki = "Blessed spider" }, + { id = "npc.blooddiamond_vampirewarrior", wiki = "Dessous" }, + { id = "npc.blooddiamond_vampirewarrior_unkillable", wiki = "Dessous" }, + { id = "npc.blue_dragon", wiki = "Blue dragon" }, + { id = "npc.blue_dragon2", wiki = "Blue dragon" }, + { id = "npc.blue_dragon3", wiki = "Blue dragon" }, + { id = "npc.blue_dragon4", wiki = "Blue dragon" }, + { id = "npc.blue_dragon5", wiki = "Blue dragon" }, + { id = "npc.blue_dragon_strongholdcave_1", wiki = "Blue dragon" }, + { id = "npc.blue_dragon_strongholdcave_2", wiki = "Blue dragon" }, + { id = "npc.blue_dragon_strongholdcave_3", wiki = "Blue dragon" }, + { id = "npc.blue_dragon_strongholdcave_4", wiki = "Blue dragon" }, + { id = "npc.blue_dragon_strongholdcave_5", wiki = "Blue dragon" }, + { id = "npc.blue_dragon_tapoyauik_1", wiki = "Blue dragon" }, + { id = "npc.blue_dragon_tapoyauik_2", wiki = "Blue dragon" }, + { id = "npc.brain_barrel_chest", wiki = "Barrelchest" }, + { id = "npc.brawling_bandit", wiki = "Bandit" }, + { id = "npc.bronze_dragon", wiki = "Bronze dragon" }, + { id = "npc.bronze_dragon_strongholdcave", wiki = "Bronze dragon" }, + { id = "npc.brownbear", wiki = "Grizzly bear" }, + { id = "npc.brut_angry_spirit1", wiki = "Angry barbarian spirit" }, + { id = "npc.brut_angry_spirit2", wiki = "Enraged barbarian spirit" }, + { id = "npc.brut_angry_spirit3", wiki = "Berserk barbarian spirit" }, + { id = "npc.brut_angry_spirit_bad", wiki = "Ferocious barbarian spirit" }, + { id = "npc.brut_green_dragon", wiki = "Brutal green dragon" }, + { id = "npc.brut_mithril_dragon", wiki = "Mithril dragon" }, + { id = "npc.brut_skeleton1", wiki = "Skeleton hero" }, + { id = "npc.brut_skeleton2", wiki = "Skeleton brute" }, + { id = "npc.brut_skeleton3", wiki = "Skeleton warlord" }, + { id = "npc.brut_skeleton6", wiki = "Skeleton thug" }, + { id = "npc.brut_waterfiend", wiki = "Waterfiend" }, + { id = "npc.brutal_blue_dragon_tapoyauik", wiki = "Brutal blue dragon" }, + { id = "npc.burgh_ivan_temple_vampire_juve_1", wiki = "Vampyre Juvinate" }, + { id = "npc.burgh_ivan_temple_vampire_juve_2", wiki = "Vampyre Juvinate" }, + { id = "npc.burgh_vampire_juve_1", wiki = "Vampyre Juvinate" }, + { id = "npc.burgh_vampire_juve_1_attackable", wiki = "Vampyre Juvinate" }, + { id = "npc.burgh_vampire_juve_2", wiki = "Vampyre Juvinate" }, + { id = "npc.burgh_vampire_juve_2_attackable", wiki = "Vampyre Juvinate" }, + { id = "npc.burgh_vampire_juve_3", wiki = "Vampyre Juvinate" }, + { id = "npc.burgh_vampire_juve_4", wiki = "Vampyre Juvinate" }, + { id = "npc.burgh_vampire_juve_angry", wiki = "Feral Vampyre" }, + { id = "npc.burgh_vampire_juve_held", wiki = "Vampyre Juvinate" }, + { id = "npc.callisto", wiki = "Callisto" }, + { id = "npc.callisto_singles", wiki = "Artio" }, + { id = "npc.cata_boss", wiki = "Skotizo" }, + { id = "npc.cata_boss_ankou", wiki = "Dark Ankou" }, + { id = "npc.cata_boss_minion", wiki = "Reanimated demon spawn" }, + { id = "npc.cave_crawler_ice_1", wiki = "Cave crawler" }, + { id = "npc.cave_crawler_ice_2", wiki = "Cave crawler" }, + { id = "npc.cerberus_attacking", wiki = "Cerberus" }, + { id = "npc.cerberus_resetting", wiki = "Cerberus" }, + { id = "npc.cerberus_sitting", wiki = "Cerberus" }, + { id = "npc.champions_earthwarrior", wiki = "Earth Warrior Champion" }, + { id = "npc.champions_ghoul", wiki = "Ghoul Champion" }, + { id = "npc.champions_giant", wiki = "Giant Champion" }, + { id = "npc.champions_hobgoblin", wiki = "Hobgoblin Champion" }, + { id = "npc.champions_human", wiki = "Leon d'Cour" }, + { id = "npc.champions_imp", wiki = "Imp Champion" }, + { id = "npc.champions_jogre", wiki = "Jogre Champion" }, + { id = "npc.champions_lesserdemon", wiki = "Lesser Demon Champion" }, + { id = "npc.champions_skeleton", wiki = "Skeleton Champion" }, + { id = "npc.champions_zombie", wiki = "Zombies Champion" }, + { id = "npc.chaos_druid", wiki = "Chaos druid" }, + { id = "npc.chaos_druid_warrior", wiki = "Chaos druid warrior" }, + { id = "npc.chaos_fanatic", wiki = "Chaos Fanatic" }, + { id = "npc.chaoselemental", wiki = "Chaos Elemental" }, + { id = "npc.chaosmonk3", wiki = "Monk of Zamorak" }, + { id = "npc.chickenquest_baby_black_dragon", wiki = "Baby black dragon", note = "Yes (except in Myths' Guild and Charred Dungeon)" }, + { id = "npc.chronozon", wiki = "Chronozon" }, + { id = "npc.coa_arrav_combat", wiki = "Arrav" }, + { id = "npc.coa_mastaba_golem", wiki = "Golem guard" }, + { id = "npc.coa_scarab", wiki = "Small scarab" }, + { id = "npc.colosseum_boss_seated", wiki = "Sol Heredit" }, + { id = "npc.colosseum_jaguar_warrior", wiki = "Jaguar warrior" }, + { id = "npc.colosseum_javelin_colossus", wiki = "Javelin Colossus" }, + { id = "npc.colosseum_manticore", wiki = "Manticore" }, + { id = "npc.colosseum_shockwave_colossus", wiki = "Shockwave Colossus" }, + { id = "npc.colosseum_standard_mager", wiki = "Serpent shaman" }, + { id = "npc.colosseum_warbander_mage_male", wiki = "Fremennik warband seer" }, + { id = "npc.colosseum_warbander_melee_male", wiki = "Fremennik warband berserker" }, + { id = "npc.colosseum_warbander_ranged_female", wiki = "Fremennik warband archer" }, + { id = "npc.contact_dummy_boss", wiki = "Giant Scarab" }, + { id = "npc.contact_insectoid_mage", wiki = "Scarab Mage" }, + { id = "npc.contact_insectoid_mage_b", wiki = "Scarab Mage" }, + { id = "npc.contact_locust_bow", wiki = "Locust rider" }, + { id = "npc.contact_locust_bow_b", wiki = "Locust rider" }, + { id = "npc.contact_locust_lance", wiki = "Locust rider" }, + { id = "npc.contact_locust_lance_b", wiki = "Locust rider" }, + { id = "npc.contact_scarab_boss", wiki = "Giant Scarab" }, + { id = "npc.corp_beast", wiki = "Corporeal Beast" }, + { id = "npc.corscurs_navigator_combat", wiki = "Ithoi the Navigator" }, + { id = "npc.count_draynor", wiki = "Count Draynor" }, + { id = "npc.count_draynor_coffin", wiki = "Count Draynor" }, + { id = "npc.cowboss", wiki = "Brutus" }, + { id = "npc.cowboss_hardmode", wiki = "Demonic Brutus" }, + { id = "npc.cowboss_hardmode_ghost", wiki = "Demonic Brutus" }, + { id = "npc.cowboss_routefind", wiki = "Brutus" }, + { id = "npc.crazy_archaeologist", wiki = "Crazy archaeologist" }, + { id = "npc.crypt_of_tonali_grimy_lizard", wiki = "Grimy Lizard" }, + { id = "npc.crypt_of_tonali_mossgiant", wiki = "Moss giant" }, + { id = "npc.crypt_of_tonali_skeleton_1", wiki = "Skeleton" }, + { id = "npc.crypt_of_tonali_skeleton_2", wiki = "Skeleton" }, + { id = "npc.crypt_of_tonali_skeleton_3", wiki = "Skeleton" }, + { id = "npc.crystal_bat", wiki = "Crystalline Bat" }, + { id = "npc.crystal_bat_hm", wiki = "Corrupted Bat" }, + { id = "npc.crystal_bear", wiki = "Crystalline Bear" }, + { id = "npc.crystal_bear_hm", wiki = "Corrupted Bear" }, + { id = "npc.crystal_dark_beast", wiki = "Crystalline Dark Beast" }, + { id = "npc.crystal_dark_beast_hm", wiki = "Corrupted Dark Beast" }, + { id = "npc.crystal_dragon", wiki = "Crystalline Dragon" }, + { id = "npc.crystal_dragon_hm", wiki = "Corrupted Dragon" }, + { id = "npc.crystal_hunllef_death", wiki = "Crystalline Hunllef" }, + { id = "npc.crystal_hunllef_death_hm", wiki = "Corrupted Hunllef" }, + { id = "npc.crystal_hunllef_magic", wiki = "Crystalline Hunllef" }, + { id = "npc.crystal_hunllef_magic_hm", wiki = "Corrupted Hunllef" }, + { id = "npc.crystal_hunllef_melee", wiki = "Crystalline Hunllef" }, + { id = "npc.crystal_hunllef_melee_hm", wiki = "Corrupted Hunllef" }, + { id = "npc.crystal_hunllef_ranged", wiki = "Crystalline Hunllef" }, + { id = "npc.crystal_hunllef_ranged_hm", wiki = "Corrupted Hunllef" }, + { id = "npc.crystal_rat", wiki = "Crystalline Rat" }, + { id = "npc.crystal_rat_hm", wiki = "Corrupted Rat" }, + { id = "npc.crystal_scorpion", wiki = "Crystalline Scorpion" }, + { id = "npc.crystal_scorpion_hm", wiki = "Corrupted Scorpion" }, + { id = "npc.crystal_spider", wiki = "Crystalline Spider" }, + { id = "npc.crystal_spider_hm", wiki = "Corrupted Spider" }, + { id = "npc.crystal_unicorn", wiki = "Crystalline Unicorn" }, + { id = "npc.crystal_unicorn_hm", wiki = "Corrupted Unicorn" }, + { id = "npc.crystal_wolf", wiki = "Crystalline Wolf" }, + { id = "npc.crystal_wolf_hm", wiki = "Corrupted Wolf" }, + { id = "npc.culinaromancer_froze", wiki = "Culinaromancer" }, + { id = "npc.dagannoth_rock_lobster_2x2", wiki = "Rock lobster" }, + { id = "npc.dagannoth_rock_lobster_2x2_loc", wiki = "Rock lobster" }, + { id = "npc.dagannoth_water_creature_1x1", wiki = "Spinolyp" }, + { id = "npc.dagannoth_water_creature_1x12", wiki = "Spinolyp" }, + { id = "npc.dagannoth_water_creature_1x13", wiki = "Spinolyp" }, + { id = "npc.dagannoth_water_creature_1x1_invisible", wiki = "Spinolyp" }, + { id = "npc.dagannoth_water_creature_1x1_invisible2", wiki = "Spinolyp" }, + { id = "npc.dagannoth_water_creature_1x1_invisible3", wiki = "Spinolyp" }, + { id = "npc.dagcave_magic_boss", wiki = "Dagannoth Prime" }, + { id = "npc.dagcave_melee_boss", wiki = "Dagannoth Rex" }, + { id = "npc.dagcave_ranged_boss", wiki = "Dagannoth Supreme" }, + { id = "npc.dagganoth_critter", wiki = "Dagannoth spawn" }, + { id = "npc.dark_core", wiki = "Dark energy core" }, + { id = "npc.dark_seren", wiki = "Fragment of Seren" }, + { id = "npc.dark_seren_cutscene", wiki = "Fragment of Seren" }, + { id = "npc.dark_seren_cutscene_trapped", wiki = "Fragment of Seren" }, + { id = "npc.dark_seren_phantom", wiki = "Fragment of Seren" }, + { id = "npc.dark_squall_agile_minion", wiki = "Agile Warrior" }, + { id = "npc.dark_squall_combat", wiki = "Surok Magis" }, + { id = "npc.dark_squall_strong_minion", wiki = "Strong Warrior" }, + { id = "npc.dark_warrior", wiki = "Dark warrior" }, + { id = "npc.darkm_juvinate_01", wiki = "Vampyre Juvinate" }, + { id = "npc.darkm_juvinate_01_held", wiki = "Vampyre Juvinate" }, + { id = "npc.darkm_juvinate_02", wiki = "Vampyre Juvinate" }, + { id = "npc.darkm_juvinate_02_held", wiki = "Vampyre Juvinate" }, + { id = "npc.darkm_vyrewatch_female_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.darkm_vyrewatch_female_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.darkm_vyrewatch_female_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.darkm_vyrewatch_female_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.darkm_vyrewatch_male_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.darkm_vyrewatch_male_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.darkm_vyrewatch_male_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.darkm_vyrewatch_male_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.deadly_red_spider", wiki = "Deadly red spider" }, + { id = "npc.deadman_breach_dscim_npc", wiki = "I DSCIM YOU" }, + { id = "npc.deadman_breach_evil_chicken", wiki = "Big Evil Chicken" }, + { id = "npc.deadman_breach_giant_goblin", wiki = "Giant goblin" }, + { id = "npc.deadman_breach_jad_minion", wiki = "TzTok-Jad-Rek" }, + { id = "npc.deadman_breach_khazard_minion", wiki = "Zemouregal Summon" }, + { id = "npc.deadman_breach_magic_mark", wiki = "Magic Mark" }, + { id = "npc.deadman_breach_mummy_minion", wiki = "Zemouregal Summon" }, + { id = "npc.deadman_breach_pirate_minion", wiki = "Zemouregal Summon" }, + { id = "npc.deadman_breach_ranging_ro", wiki = "Ranging Ro" }, + { id = "npc.deadman_breach_shade_minion", wiki = "Zemouregal Summon" }, + { id = "npc.deadman_breach_ulfric_minion", wiki = "Zemouregal Summon" }, + { id = "npc.deadman_breach_undead_one_minion", wiki = "Zemouregal Summon" }, + { id = "npc.deal_evil_spirit", wiki = "Evil spirit" }, + { id = "npc.death_troll_guard1", wiki = "Pee Hat" }, + { id = "npc.death_troll_guard2", wiki = "Kraka" }, + { id = "npc.death_troll_leuitanant", wiki = "Stick" }, + { id = "npc.death_troll_melee1", wiki = "Mountain troll" }, + { id = "npc.death_troll_melee2", wiki = "Mountain troll" }, + { id = "npc.death_troll_melee3", wiki = "Mountain troll" }, + { id = "npc.death_troll_melee4", wiki = "Mountain troll" }, + { id = "npc.death_troll_melee5", wiki = "Mountain troll" }, + { id = "npc.death_troll_melee6", wiki = "Mountain troll" }, + { id = "npc.death_troll_melee7", wiki = "Mountain troll" }, + { id = "npc.death_troll_thrower1", wiki = "Thrower Troll" }, + { id = "npc.death_troll_thrower2", wiki = "Thrower Troll" }, + { id = "npc.death_troll_thrower3", wiki = "Thrower Troll" }, + { id = "npc.death_troll_thrower4", wiki = "Thrower Troll" }, + { id = "npc.death_troll_thrower5", wiki = "Thrower Troll" }, + { id = "npc.deathwing", wiki = "Death wing" }, + { id = "npc.desert_wolf", wiki = "Desert Wolf" }, + { id = "npc.desert_wolf2", wiki = "Desert Wolf" }, + { id = "npc.desert_wolf3", wiki = "Desert Wolf" }, + { id = "npc.direwolf", wiki = "Dire Wolf" }, + { id = "npc.dom_boss", wiki = "Doom of Mokhaiotl" }, + { id = "npc.dom_boss_burrowed", wiki = "Doom of Mokhaiotl" }, + { id = "npc.dom_boss_shielded", wiki = "Doom of Mokhaiotl" }, + { id = "npc.doomion_vis", wiki = "Doomion" }, + { id = "npc.doti_adala_boss", wiki = "Adala" }, + { id = "npc.double_agent_1", wiki = "Double agent" }, + { id = "npc.double_agent_2", wiki = "Double agent" }, + { id = "npc.double_agent_master", wiki = "Double agent" }, + { id = "npc.dragonslayer_elvarg_cutscene", wiki = "Elvarg" }, + { id = "npc.drake", wiki = "Drake" }, + { id = "npc.drake_death", wiki = "Drake" }, + { id = "npc.dream_everlasting", wiki = "The Everlasting" }, + { id = "npc.dream_illusive_noncombat", wiki = "The Illusive" }, + { id = "npc.dream_inadequacy", wiki = "The Inadequacy" }, + { id = "npc.dream_razorbeast", wiki = "A Doubt" }, + { id = "npc.dream_robert_combat", wiki = "Robert the Strong" }, + { id = "npc.ds2_adamant_dragon", wiki = "Adamant dragon" }, + { id = "npc.ds2_black_dragon", wiki = "Black dragon" }, + { id = "npc.ds2_black_dragon_cutscene", wiki = "Black dragon" }, + { id = "npc.ds2_brut_black_dragon", wiki = "Brutal black dragon" }, + { id = "npc.ds2_brut_black_dragon_cutscene", wiki = "Brutal black dragon" }, + { id = "npc.ds2_brut_red_dragon", wiki = "Brutal red dragon" }, + { id = "npc.ds2_golem_magic", wiki = "Stone Guardian" }, + { id = "npc.ds2_golem_melee", wiki = "Stone Guardian" }, + { id = "npc.ds2_golem_ranged", wiki = "Stone Guardian" }, + { id = "npc.ds2_green_dragon", wiki = "Green dragon" }, + { id = "npc.ds2_green_dragon_cutscene_flying", wiki = "Green dragon" }, + { id = "npc.ds2_green_dragon_flying", wiki = "Green dragon" }, + { id = "npc.ds2_iron_dragon", wiki = "Iron dragon" }, + { id = "npc.ds2_mithril_dragon", wiki = "Mithril dragon" }, + { id = "npc.ds2_mithril_dragon_cutscene", wiki = "Mithril dragon" }, + { id = "npc.ds2_red_dragon", wiki = "Red dragon" }, + { id = "npc.ds2_red_dragon_cutscene_flying", wiki = "Red dragon" }, + { id = "npc.ds2_red_dragon_flying", wiki = "Red dragon" }, + { id = "npc.ds2_rune_dragon", wiki = "Rune dragon" }, + { id = "npc.dt2_ahrim_combat", wiki = "Ahrim the Blighted" }, + { id = "npc.dt2_ahrim_cutscene", wiki = "Ahrim the Blighted" }, + { id = "npc.dt2_assassin_wight_combat", wiki = "The Forsaken Assassin" }, + { id = "npc.dt2_dharok_combat", wiki = "Dharok the Wretched" }, + { id = "npc.dt2_dharok_cutscene", wiki = "Dharok the Wretched" }, + { id = "npc.dt2_guthan_combat", wiki = "Guthan the Infested" }, + { id = "npc.dt2_guthan_cutscene", wiki = "Guthan the Infested" }, + { id = "npc.dt2_jhallan_chase", wiki = "Jhallan" }, + { id = "npc.dt2_jhallan_combat", wiki = "Jhallan" }, + { id = "npc.dt2_karil_combat", wiki = "Karil the Tainted" }, + { id = "npc.dt2_karil_cutscene", wiki = "Karil the Tainted" }, + { id = "npc.dt2_kasonde_injured", wiki = "Kasonde" }, + { id = "npc.dt2_kasonde_wight_combat", wiki = "Kasonde the Craven" }, + { id = "npc.dt2_kasonde_wight_combat_opbreak", wiki = "Kasonde the Craven" }, + { id = "npc.dt2_ketla_wight_combat", wiki = "Ketla the Unworthy" }, + { id = "npc.dt2_ketla_wight_combat_clone", wiki = "Ketla the Unworthy" }, + { id = "npc.dt2_persten_wight_combat", wiki = "Persten the Deceitful" }, + { id = "npc.dt2_pursuer_hideout_combat", wiki = "Mysterious Figure" }, + { id = "npc.dt2_pursuer_overworld_combat", wiki = "Mysterious Figure" }, + { id = "npc.dt2_scar_black_demon_2", wiki = "Scarred black demon" }, + { id = "npc.dt2_scar_greater_demon_2", wiki = "Scarred greater demon" }, + { id = "npc.dt2_scar_lesser_demon_2", wiki = "Scarred lesser demon" }, + { id = "npc.dt2_scar_maze_2_pillar_attacker", wiki = "Scarred greater demon" }, + { id = "npc.dt2_scar_maze_2_pillar_attacker_dog", wiki = "Scarred hellhound" }, + { id = "npc.dt2_scar_maze_3_link_attacker", wiki = "Scarred lesser demon" }, + { id = "npc.dt2_scar_maze_3_link_attacker_dog", wiki = "Scarred hellhound" }, + { id = "npc.dt2_scar_maze_mage_demon", wiki = "Scarred lesser demon" }, + { id = "npc.dt2_scar_maze_melee_demon", wiki = "Scarred lesser demon" }, + { id = "npc.dt2_scar_maze_ranged_demon", wiki = "Scarred lesser demon" }, + { id = "npc.dt2_shadow_keeper", wiki = "Shadow Keeper" }, + { id = "npc.dt2_strangled_base_t1_1", wiki = "Strangled" }, + { id = "npc.dt2_strangled_base_t1_2", wiki = "Strangled" }, + { id = "npc.dt2_strangled_base_t1_3", wiki = "Strangled" }, + { id = "npc.dt2_strangled_base_t1_4", wiki = "Strangled" }, + { id = "npc.dt2_strangled_base_t2_1", wiki = "Strangled" }, + { id = "npc.dt2_strangled_base_t2_2", wiki = "Strangled" }, + { id = "npc.dt2_strangled_base_t2_3", wiki = "Strangled" }, + { id = "npc.dt2_strangled_bear", wiki = "Strangled Bear" }, + { id = "npc.dt2_strangled_boar", wiki = "Strangled Boar" }, + { id = "npc.dt2_strangled_lynx", wiki = "Strangled Lynx" }, + { id = "npc.dt2_strangled_ruins_t1_1", wiki = "Strangled" }, + { id = "npc.dt2_strangled_ruins_t1_2", wiki = "Strangled" }, + { id = "npc.dt2_strangled_ruins_t1_3", wiki = "Strangled" }, + { id = "npc.dt2_strangled_ruins_t1_4", wiki = "Strangled" }, + { id = "npc.dt2_strangled_ruins_t2_1", wiki = "Strangled" }, + { id = "npc.dt2_strangled_ruins_t2_2", wiki = "Strangled" }, + { id = "npc.dt2_strangled_ruins_t2_3", wiki = "Strangled" }, + { id = "npc.dt2_strangled_survival_t1_1", wiki = "Strangled" }, + { id = "npc.dt2_strangled_survival_t1_2", wiki = "Strangled" }, + { id = "npc.dt2_strangled_survival_t1_3", wiki = "Strangled" }, + { id = "npc.dt2_strangled_survival_t2_1", wiki = "Strangled" }, + { id = "npc.dt2_strangled_t1_1_cutscene", wiki = "Strangled" }, + { id = "npc.dt2_strangled_t1_2_cutscene", wiki = "Strangled" }, + { id = "npc.dt2_strangled_t1_3_cutscene", wiki = "Strangled" }, + { id = "npc.dt2_strangled_t1_4_cutscene", wiki = "Strangled" }, + { id = "npc.dt2_strangled_t2_1_cutscene", wiki = "Strangled" }, + { id = "npc.dt2_strangled_t2_2_cutscene", wiki = "Strangled" }, + { id = "npc.dt2_strangled_t2_3_cutscene", wiki = "Strangled" }, + { id = "npc.dt2_strangled_temple_1", wiki = "Strangled" }, + { id = "npc.dt2_strangled_temple_2", wiki = "Strangled" }, + { id = "npc.dt2_torag_combat", wiki = "Torag the Corrupted" }, + { id = "npc.dt2_torag_cutscene", wiki = "Torag the Corrupted" }, + { id = "npc.dt2_verac_combat", wiki = "Verac the Defiled" }, + { id = "npc.dt2_verac_cutscene", wiki = "Verac the Defiled" }, + { id = "npc.dt2_wight_persten_npc", wiki = "Wighted Leech" }, + { id = "npc.dttd_cutscene_sigmund", wiki = "Sigmund" }, + { id = "npc.dttd_sigmund_magic", wiki = "Sigmund" }, + { id = "npc.dttd_sigmund_melee", wiki = "Sigmund" }, + { id = "npc.dttd_sigmund_mill", wiki = "Sigmund" }, + { id = "npc.dttd_sigmund_ranged", wiki = "Sigmund" }, + { id = "npc.dttd_sigmund_ranged_vs_zanik", wiki = "Sigmund" }, + { id = "npc.duke_sucellus_asleep", wiki = "Duke Sucellus" }, + { id = "npc.duke_sucellus_asleep_quest", wiki = "Duke Sucellus" }, + { id = "npc.duke_sucellus_awake", wiki = "Duke Sucellus" }, + { id = "npc.duke_sucellus_awake_quest", wiki = "Duke Sucellus" }, + { id = "npc.duke_sucellus_dead", wiki = "Duke Sucellus" }, + { id = "npc.duke_sucellus_dead_quest", wiki = "Duke Sucellus" }, + { id = "npc.duke_sucellus_inactive", wiki = "Duke Sucellus" }, + { id = "npc.duke_sucellus_inactive_quest", wiki = "Duke Sucellus" }, + { id = "npc.dungeon_dagganoth_magic_monster", wiki = "Wallasalki" }, + { id = "npc.dungeon_dagganoth_magic_monster_deeper", wiki = "Wallasalki" }, + { id = "npc.dungeonspider", wiki = "Poison spider" }, + { id = "npc.dwarf_chaos", wiki = "Chaos dwarf" }, + { id = "npc.dwarf_rock_avatar_archer", wiki = "Arzinian Avatar of Ranging" }, + { id = "npc.dwarf_rock_avatar_archer_green", wiki = "Arzinian Avatar of Ranging" }, + { id = "npc.dwarf_rock_avatar_archer_yellow", wiki = "Arzinian Avatar of Ranging" }, + { id = "npc.dwarf_rock_avatar_mage", wiki = "Arzinian Avatar of Magic" }, + { id = "npc.dwarf_rock_avatar_mage_green", wiki = "Arzinian Avatar of Magic" }, + { id = "npc.dwarf_rock_avatar_mage_yellow", wiki = "Arzinian Avatar of Magic" }, + { id = "npc.dwarf_rock_avatar_warrior", wiki = "Arzinian Avatar of Strength" }, + { id = "npc.dwarf_rock_avatar_warrior_green", wiki = "Arzinian Avatar of Strength" }, + { id = "npc.dwarf_rock_avatar_warrior_yellow", wiki = "Arzinian Avatar of Strength" }, + { id = "npc.earth_nagua", wiki = "Earthen Nagua" }, + { id = "npc.earth_nagua_reinforced", wiki = "Earthen Nagua" }, + { id = "npc.earthwarrior", wiki = "Earth warrior" }, + { id = "npc.echned_zekin", wiki = "Nezikchened" }, + { id = "npc.elder_custodian_stalker", wiki = "Elder custodian stalker" }, + { id = "npc.elid_golem_black", wiki = "Black golem" }, + { id = "npc.elid_golem_grey", wiki = "Grey golem" }, + { id = "npc.elid_golem_white", wiki = "White golem" }, + { id = "npc.elite_black_knight_1", wiki = "Elite Black Knight" }, + { id = "npc.elite_black_knight_1_cutscene", wiki = "Elite Black Knight" }, + { id = "npc.elite_black_knight_2", wiki = "Elite Black Knight" }, + { id = "npc.elite_black_knight_2_cutscene", wiki = "Elite Black Knight" }, + { id = "npc.elite_dark_mage_1", wiki = "Elite Dark Mage" }, + { id = "npc.elite_dark_mage_2", wiki = "Elite Dark Mage" }, + { id = "npc.elite_dark_ranger_1", wiki = "Elite Dark Ranger" }, + { id = "npc.elite_dark_ranger_2", wiki = "Elite Dark Ranger" }, + { id = "npc.elite_dark_warrior_1", wiki = "Elite Dark Warrior" }, + { id = "npc.elite_dark_warrior_2", wiki = "Elite Dark Warrior" }, + { id = "npc.elite_npc_1", wiki = "Armadylean guard" }, + { id = "npc.elite_npc_2", wiki = "Bandosian guard" }, + { id = "npc.ent_trunk", wiki = "Ent" }, + { id = "npc.fai_dark_wizard_old_1", wiki = "Dark wizard" }, + { id = "npc.fai_dark_wizard_old_2", wiki = "Dark wizard" }, + { id = "npc.fai_dark_wizard_young_1", wiki = "Dark wizard" }, + { id = "npc.fai_dark_wizard_young_2", wiki = "Dark wizard" }, + { id = "npc.fairy2_dryhadguardian_1", wiki = "Tree spirit" }, + { id = "npc.fairy2_dryhadguardian_2", wiki = "Tree spirit" }, + { id = "npc.fairy2_dryhadguardian_3", wiki = "Tree spirit" }, + { id = "npc.fairy2_dryhadguardian_4", wiki = "Tree spirit" }, + { id = "npc.fairy2_dryhadguardian_5", wiki = "Tree spirit" }, + { id = "npc.fairy2_dryhadguardian_6", wiki = "Tree spirit" }, + { id = "npc.fairy2_rivertrollguardian_1", wiki = "River troll" }, + { id = "npc.fairy2_rivertrollguardian_2", wiki = "River troll" }, + { id = "npc.fairy2_rivertrollguardian_3", wiki = "River troll" }, + { id = "npc.fairy2_rivertrollguardian_4", wiki = "River troll" }, + { id = "npc.fairy2_rivertrollguardian_5", wiki = "River troll" }, + { id = "npc.fairy2_rivertrollguardian_6", wiki = "River troll" }, + { id = "npc.fairytale2_gorak", wiki = "Gorak" }, + { id = "npc.fd_damis_normal", wiki = "Damis" }, + { id = "npc.fd_damis_tougher", wiki = "Damis" }, + { id = "npc.feud_bandit_toughguy", wiki = "Bandit champion" }, + { id = "npc.feud_menap_toughguy", wiki = "Tough Guy" }, + { id = "npc.fever_pirate_enemy_01", wiki = "Pirate" }, + { id = "npc.fever_pirate_enemy_02", wiki = "Pirate" }, + { id = "npc.fever_pirate_enemy_03", wiki = "Pirate" }, + { id = "npc.fever_pirate_enemy_04", wiki = "Pirate" }, + { id = "npc.fever_pirate_enemy_05", wiki = "Pirate" }, + { id = "npc.fever_pirate_enemy_06", wiki = "Pirate" }, + { id = "npc.fever_pirate_enemy_07", wiki = "Pirate" }, + { id = "npc.fever_pirate_enemy_08", wiki = "Pirate" }, + { id = "npc.fever_pirate_enemy_09", wiki = "Pirate" }, + { id = "npc.fever_pirate_enemy_10", wiki = "Pirate" }, + { id = "npc.ffg_troll_1", wiki = "Mountain troll" }, + { id = "npc.ffg_troll_2", wiki = "Mountain troll" }, + { id = "npc.ffg_troll_3", wiki = "Mountain troll" }, + { id = "npc.firediamond_firewarrior", wiki = "Fareed" }, + { id = "npc.firegiant", wiki = "Fire giant" }, + { id = "npc.firegiant2", wiki = "Fire giant" }, + { id = "npc.firegiant3", wiki = "Fire giant" }, + { id = "npc.firegiant_big", wiki = "Fire giant" }, + { id = "npc.firegiant_big2", wiki = "Fire giant" }, + { id = "npc.firegiant_big3", wiki = "Fire giant" }, + { id = "npc.firegiant_strongholdcave_1", wiki = "Fire giant" }, + { id = "npc.firegiant_strongholdcave_2", wiki = "Fire giant" }, + { id = "npc.firegiant_strongholdcave_3", wiki = "Fire giant" }, + { id = "npc.firegiant_strongholdcave_4", wiki = "Fire giant" }, + { id = "npc.fossil_ammonitecrab_inactive", wiki = "Ammonite Crab" }, + { id = "npc.fossil_crazy_archaeologist", wiki = "Deranged archaeologist" }, + { id = "npc.fossil_lobster", wiki = "Lobstrosity" }, + { id = "npc.fossil_mine_lavabeast", wiki = "Lava beast" }, + { id = "npc.fossil_tarmonster_bubbles", wiki = "Tar Monster" }, + { id = "npc.fourdiamonds_assasin", wiki = "Stranger" }, + { id = "npc.fris_baby_troll", wiki = "Ice troll runt" }, + { id = "npc.fris_baby_troll_lowxp", wiki = "Frenzied ice troll runt" }, + { id = "npc.fris_baby_troll_pc", wiki = "Ice troll runt" }, + { id = "npc.fris_troll_bodyguard", wiki = "Ice troll grunt" }, + { id = "npc.fris_troll_bodyguard_lowxp", wiki = "Frenzied ice troll grunt" }, + { id = "npc.fris_troll_bodyguard_pc", wiki = "Ice troll grunt" }, + { id = "npc.fris_troll_king_true", wiki = "Ice Troll King" }, + { id = "npc.fris_trollf", wiki = "Ice troll female", note = "Yes, except one troll north of Neitiznot" }, + { id = "npc.fris_trollf_lowxp", wiki = "Frenzied ice troll female" }, + { id = "npc.fris_trollf_pc", wiki = "Ice troll female", note = "Yes, except one troll north of Neitiznot" }, + { id = "npc.fris_trollm", wiki = "Ice troll male" }, + { id = "npc.fris_trollm_lowxp", wiki = "Frenzied ice troll male" }, + { id = "npc.fris_trollm_pc", wiki = "Ice troll male" }, + { id = "npc.frog_quest_cuthbert_combat", wiki = "Cuthbert" }, + { id = "npc.frost_dragon", wiki = "Frost dragon" }, + { id = "npc.frost_nagua", wiki = "Frost Nagua" }, + { id = "npc.frost_nagua_cutscene", wiki = "Frost Nagua" }, + { id = "npc.frostcrab", wiki = "Frost Crab" }, + { id = "npc.frostcrab_inactive", wiki = "Frost Crab" }, + { id = "npc.frostcrab_small", wiki = "Frost Crab" }, + { id = "npc.frostcrab_small_inactive", wiki = "Frost Crab" }, + { id = "npc.ga_beast", wiki = "Headless Beast" }, + { id = "npc.galvek_cutscene", wiki = "Galvek" }, + { id = "npc.galvek_earth", wiki = "Galvek" }, + { id = "npc.galvek_earth_cutscene", wiki = "Galvek" }, + { id = "npc.galvek_earth_dead", wiki = "Galvek" }, + { id = "npc.galvek_fire", wiki = "Galvek" }, + { id = "npc.galvek_water", wiki = "Galvek" }, + { id = "npc.galvek_waves", wiki = "Galvek" }, + { id = "npc.galvek_wind", wiki = "Galvek" }, + { id = "npc.gb_growthling", wiki = "Growthling" }, + { id = "npc.gb_mossgiant", wiki = "Bryophyta" }, + { id = "npc.gemstone_crab_remains", wiki = "Gemstone Crab" }, + { id = "npc.gh_giant1", wiki = "Hill Giant" }, + { id = "npc.gh_giant2", wiki = "Hill Giant" }, + { id = "npc.gh_giant3", wiki = "Hill Giant" }, + { id = "npc.ghast_invis", wiki = "Ghast" }, + { id = "npc.ghost", wiki = "Ghost" }, + { id = "npc.ghost2", wiki = "Ghost" }, + { id = "npc.ghost3", wiki = "Ghost" }, + { id = "npc.ghost3_unaggressive", wiki = "Ghost" }, + { id = "npc.ghost4", wiki = "Ghost" }, + { id = "npc.ghost5", wiki = "Ghost" }, + { id = "npc.ghost5_unaggressive", wiki = "Ghost" }, + { id = "npc.ghost6", wiki = "Ghost" }, + { id = "npc.ghost7", wiki = "Ghost" }, + { id = "npc.ghost7_unaggressive", wiki = "Ghost" }, + { id = "npc.ghost8", wiki = "Ghost" }, + { id = "npc.ghost8_unaggressive", wiki = "Ghost" }, + { id = "npc.ghost_unaggressive", wiki = "Ghost" }, + { id = "npc.ghoul", wiki = "Ghoul" }, + { id = "npc.giant", wiki = "Hill Giant" }, + { id = "npc.giant2", wiki = "Hill Giant" }, + { id = "npc.giant3", wiki = "Hill Giant" }, + { id = "npc.giant4", wiki = "Hill Giant" }, + { id = "npc.giant5", wiki = "Hill Giant" }, + { id = "npc.giant6", wiki = "Hill Giant" }, + { id = "npc.giant_leech", wiki = "Leech" }, + { id = "npc.giant_rockcrab_crypt_of_tonali_hidden", wiki = "Giant Rock Crab" }, + { id = "npc.giant_rockcrab_hidden", wiki = "Giant Rock Crab" }, + { id = "npc.giant_rockcrab_hidden_deeper", wiki = "Giant Rock Crab" }, + { id = "npc.giantrat", wiki = "Giant rat" }, + { id = "npc.giantrat1", wiki = "Giant rat" }, + { id = "npc.giantrat1_2", wiki = "Giant rat" }, + { id = "npc.giantrat1_3", wiki = "Giant rat" }, + { id = "npc.giantrat2", wiki = "Giant rat" }, + { id = "npc.giantrat3", wiki = "Giant rat" }, + { id = "npc.giantrat_grey", wiki = "Giant rat" }, + { id = "npc.giantrat_grey2", wiki = "Giant rat" }, + { id = "npc.giantrat_grey3", wiki = "Giant rat" }, + { id = "npc.giantskeleton", wiki = "Skeleton" }, + { id = "npc.giantskeleton2", wiki = "Skeleton" }, + { id = "npc.giantspider1", wiki = "Giant spider" }, + { id = "npc.giantspider2", wiki = "Giant spider" }, + { id = "npc.gim_boar", wiki = "Enraged Boar" }, + { id = "npc.gnome_archer", wiki = "Gnome Archer" }, + { id = "npc.gnome_driver", wiki = "Gnome Driver" }, + { id = "npc.gnome_mage", wiki = "Gnome Mage" }, + { id = "npc.godwars_ancient_black_demon", wiki = "Balfrug Kreeyath" }, + { id = "npc.godwars_ancient_greater_demon", wiki = "Tstanon Karlak" }, + { id = "npc.godwars_ancient_hellhound", wiki = "Hellhound", note = "Yes, unless wearing a Zamorak-affiliated item" }, + { id = "npc.godwars_ancient_hobgoblin", wiki = "Hobgoblin", note = "Yes, unless wearing a Bandos-affiliated item" }, + { id = "npc.godwars_ancient_jogre", wiki = "Jogre", note = "Yes, unless wearing a Bandos-affiliated item" }, + { id = "npc.godwars_ancient_lesser_demon", wiki = "Zakl'n Gritch" }, + { id = "npc.godwars_ancient_ogre", wiki = "Ogre", note = "Yes, unless wearing a Bandos-affiliated item" }, + { id = "npc.godwars_ancient_ork1", wiki = "Ork", note = "Yes, unless wearing a Bandos-affiliated item" }, + { id = "npc.godwars_ancient_ork2", wiki = "Ork", note = "Yes, unless wearing a Bandos-affiliated item" }, + { id = "npc.godwars_ancient_ork3", wiki = "Ork", note = "Yes, unless wearing a Bandos-affiliated item" }, + { id = "npc.godwars_ancient_ork4", wiki = "Ork", note = "Yes, unless wearing a Bandos-affiliated item" }, + { id = "npc.godwars_ancient_saradomin_wizard", wiki = "Saradomin priest", note = "Yes, unless wearing a Saradomin-affiliated item" }, + { id = "npc.godwars_ancient_vampire", wiki = "Feral Vampyre", note = "Yes, unless wearing a Zamorak-affiliated item" }, + { id = "npc.godwars_ancient_werewolf1", wiki = "Werewolf", note = "Yes, unless wearing a Zamorak-affiliated item" }, + { id = "npc.godwars_ancient_werewolf2", wiki = "Werewolf", note = "Yes, unless wearing a Zamorak-affiliated item" }, + { id = "npc.godwars_armadyl_avatar", wiki = "Kree'arra" }, + { id = "npc.godwars_armadyl_bodyguard_geerin", wiki = "Flockleader Geerin" }, + { id = "npc.godwars_armadyl_bodyguard_kilisa", wiki = "Flight Kilisa" }, + { id = "npc.godwars_armadyl_bodyguard_skree", wiki = "Wingman Skree" }, + { id = "npc.godwars_armadyl_female_armor01_blue", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_female_armor01_green", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_female_armor01_red", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_female_armor02_blue", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_female_armor02_green", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_female_armor02_red", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_female_armor03_blue", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_male_armor01_blue", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_male_armor01_green", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_male_armor01_red", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_male_armor02_blue", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_male_armor02_green", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_male_armor02_red", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_male_armor03_green", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_armadyl_male_armor03_red", wiki = "Aviansie", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_bandos_avatar", wiki = "General Graardor" }, + { id = "npc.godwars_gorak", wiki = "Gorak", note = "Yes, unless wearing a Zamorak-affiliated item" }, + { id = "npc.godwars_icefiend_1", wiki = "Icefiend", note = "Yes, unless wearing a Zamorak-affiliated item" }, + { id = "npc.godwars_pyrefiend_1", wiki = "Pyrefiend", note = "Yes, unless wearing a Zamorak-affiliated item" }, + { id = "npc.godwars_saradomin_avatar", wiki = "Commander Zilyana" }, + { id = "npc.godwars_saradomin_centaur", wiki = "Bree" }, + { id = "npc.godwars_saradomin_knight_1", wiki = "Knight of Saradomin", note = "Yes, unless wearing a Saradomin-affiliated item" }, + { id = "npc.godwars_saradomin_knight_2", wiki = "Knight of Saradomin", note = "Yes, unless wearing a Saradomin-affiliated item" }, + { id = "npc.godwars_saradomin_lion", wiki = "Growler" }, + { id = "npc.godwars_saradomin_unicorn", wiki = "Starlight" }, + { id = "npc.godwars_sergeant_goblin1", wiki = "Sergeant Strongstack" }, + { id = "npc.godwars_sergeant_goblin2", wiki = "Sergeant Steelwill" }, + { id = "npc.godwars_sergeant_goblin3", wiki = "Sergeant Grimspike" }, + { id = "npc.godwars_spiritual_armadyl_mage", wiki = "Spiritual mage", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_spiritual_armadyl_ranger", wiki = "Spiritual ranger", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_spiritual_armadyl_warrior", wiki = "Spiritual warrior", note = "Yes, unless wearing an Armadyl-affiliated item" }, + { id = "npc.godwars_spiritual_bandos_mage", wiki = "Spiritual mage", note = "Yes, unless wearing a Bandos-affiliated item" }, + { id = "npc.godwars_spiritual_bandos_ranger", wiki = "Spiritual ranger", note = "Yes, unless wearing a Bandos-affiliated item" }, + { id = "npc.godwars_spiritual_bandos_warrior", wiki = "Spiritual warrior", note = "Yes, unless wearing a Bandos-affiliated item" }, + { id = "npc.godwars_spiritual_saradomin_mage", wiki = "Spiritual mage", note = "Yes, unless wearing a Saradomin-affiliated item" }, + { id = "npc.godwars_spiritual_saradomin_ranger", wiki = "Spiritual ranger", note = "Yes, unless wearing a Saradomin-affiliated item" }, + { id = "npc.godwars_spiritual_saradomin_warrior", wiki = "Spiritual warrior", note = "Yes, unless wearing a Saradomin-affiliated item" }, + { id = "npc.godwars_spiritual_zamorak_mage", wiki = "Spiritual mage", note = "Yes, unless wearing a Zamorak-affiliated item" }, + { id = "npc.godwars_spiritual_zamorak_ranger", wiki = "Spiritual ranger", note = "Yes, unless wearing a Zamorak-affiliated item" }, + { id = "npc.godwars_spiritual_zamorak_warrior", wiki = "Spiritual warrior", note = "Yes, unless wearing a Zamorak-affiliated item" }, + { id = "npc.godwars_zamorak_avatar", wiki = "K'ril Tsutsaroth" }, + { id = "npc.grandtree_blackdemon", wiki = "Black demon (The Grand Tree)" }, + { id = "npc.greater_demon", wiki = "Greater demon" }, + { id = "npc.greater_demon2", wiki = "Greater demon" }, + { id = "npc.greater_demon3", wiki = "Greater demon" }, + { id = "npc.greater_demon4", wiki = "Greater demon" }, + { id = "npc.greater_demon5", wiki = "Greater demon" }, + { id = "npc.greater_demon_strongholdcave_1", wiki = "Greater demon" }, + { id = "npc.greater_demon_strongholdcave_2", wiki = "Greater demon" }, + { id = "npc.greater_demon_strongholdcave_3", wiki = "Greater demon" }, + { id = "npc.green_dragon", wiki = "Green dragon" }, + { id = "npc.green_dragon2", wiki = "Green dragon" }, + { id = "npc.green_dragon3", wiki = "Green dragon" }, + { id = "npc.green_dragon4", wiki = "Green dragon" }, + { id = "npc.green_dragon5", wiki = "Green dragon" }, + { id = "npc.grim_giant_mouse", wiki = "Mouse" }, + { id = "npc.grim_giant_mouse2", wiki = "Mouse" }, + { id = "npc.grim_glod", wiki = "Glod" }, + { id = "npc.grimy_lizard", wiki = "Grimy Lizard" }, + { id = "npc.gryphon_boss", wiki = "Shellbane gryphon" }, + { id = "npc.guard_bandit", wiki = "Guard Bandit" }, + { id = "npc.guild_towerarcher1", wiki = "Tower Archer" }, + { id = "npc.guild_towerarcher2", wiki = "Tower Archer" }, + { id = "npc.guild_towerarcher3", wiki = "Tower Archer" }, + { id = "npc.guild_towerarcher4", wiki = "Tower Archer" }, + { id = "npc.gunthor_the_brave", wiki = "Gunthor the brave" }, + { id = "npc.guthix_mage", wiki = "Battle mage", note = "Yes, unless wearing the same cape as the battle mage." }, + { id = "npc.gwenith_white_wolf", wiki = "Dire Wolf" }, + { id = "npc.harmless_island_albino_bat", wiki = "Albino bat" }, + { id = "npc.harmless_island_cave_horror_alpha", wiki = "Cave horror" }, + { id = "npc.harmless_island_cave_horror_guard", wiki = "Cave horror" }, + { id = "npc.harmless_island_cave_horror_small_guard", wiki = "Cave horror" }, + { id = "npc.harmless_island_cave_horror_worker", wiki = "Cave horror" }, + { id = "npc.harmless_island_cave_horror_young_worker", wiki = "Cave horror" }, + { id = "npc.harmless_island_jungle_horror_guard", wiki = "Jungle horror" }, + { id = "npc.harmless_island_jungle_horror_warrior", wiki = "Jungle horror" }, + { id = "npc.harmless_island_jungle_horror_worker", wiki = "Jungle horror" }, + { id = "npc.harmless_island_jungle_horror_young_warrior", wiki = "Jungle horror" }, + { id = "npc.harmless_island_jungle_horror_young_worker", wiki = "Jungle horror" }, + { id = "npc.hauntedmine_boss_ghost_faded", wiki = "Treus Dayth" }, + { id = "npc.hellhound", wiki = "Hellhound" }, + { id = "npc.hellhound_strongholdcave", wiki = "Hellhound" }, + { id = "npc.hespori", wiki = "Hespori" }, + { id = "npc.highwayman", wiki = "Highwayman" }, + { id = "npc.highwayman2", wiki = "Highwayman" }, + { id = "npc.hillgiant_boss", wiki = "Obor" }, + { id = "npc.hobgoblin_armed", wiki = "Hobgoblin" }, + { id = "npc.hobgoblin_unarmed", wiki = "Hobgoblin" }, + { id = "npc.holthion_vis", wiki = "Holthion" }, + { id = "npc.horror_dagannoth_medium", wiki = "Dagannoth" }, + { id = "npc.horror_dagannoth_medium_darker", wiki = "Dagannoth" }, + { id = "npc.horror_dagannoth_medium_lighter", wiki = "Dagannoth" }, + { id = "npc.horror_dagganoth_air", wiki = "Dagannoth mother" }, + { id = "npc.horror_dagganoth_aira", wiki = "Dagannoth mother" }, + { id = "npc.horror_dagganoth_airb", wiki = "Dagannoth mother" }, + { id = "npc.horror_dagganoth_airc", wiki = "Dagannoth mother" }, + { id = "npc.horror_dagganoth_earth", wiki = "Dagannoth mother" }, + { id = "npc.horror_dagganoth_fire", wiki = "Dagannoth mother" }, + { id = "npc.horror_dagganoth_jr", wiki = "Dagannoth" }, + { id = "npc.horror_dagganoth_jr_darker", wiki = "Dagannoth" }, + { id = "npc.horror_dagganoth_jr_lighter", wiki = "Dagannoth" }, + { id = "npc.horror_dagganoth_melee", wiki = "Dagannoth mother" }, + { id = "npc.horror_dagganoth_ranged", wiki = "Dagannoth mother" }, + { id = "npc.horror_dagganoth_water", wiki = "Dagannoth mother" }, + { id = "npc.horror_rockcrab", wiki = "Rock Crab" }, + { id = "npc.horror_rockcrab_inactive", wiki = "Rock Crab" }, + { id = "npc.horror_rockcrab_small", wiki = "Rock Crab" }, + { id = "npc.horror_rockcrab_small_inactive", wiki = "Rock Crab" }, + { id = "npc.hosdun_druid", wiki = "Undead Druid" }, + { id = "npc.hosdun_shaeded_beast", wiki = "Shaeded Beast" }, + { id = "npc.hosdun_spider", wiki = "Temple Spider" }, + { id = "npc.hosidiusquest_snake", wiki = "Sand Snake" }, + { id = "npc.house_ghost", wiki = "Ghost" }, + { id = "npc.house_ghost2", wiki = "Ghost" }, + { id = "npc.house_ghost3", wiki = "Ghost" }, + { id = "npc.house_ghost4", wiki = "Ghost" }, + { id = "npc.house_ghost5", wiki = "Ghost" }, + { id = "npc.huey_body_part", wiki = "The Hueycoatl" }, + { id = "npc.huey_head", wiki = "The Hueycoatl" }, + { id = "npc.huey_head_defeated", wiki = "The Hueycoatl" }, + { id = "npc.huey_head_enraged", wiki = "The Hueycoatl" }, + { id = "npc.huey_head_invulnerable", wiki = "The Hueycoatl" }, + { id = "npc.huey_head_respawn_placeholder", wiki = "The Hueycoatl" }, + { id = "npc.huey_tail", wiki = "The Hueycoatl" }, + { id = "npc.huey_tail_broken", wiki = "The Hueycoatl" }, + { id = "npc.hundred_culinaromancer_base", wiki = "Culinaromancer" }, + { id = "npc.hundred_culinaromancer_book1", wiki = "Culinaromancer" }, + { id = "npc.hundred_culinaromancer_book2", wiki = "Culinaromancer" }, + { id = "npc.hundred_culinaromancer_book3", wiki = "Culinaromancer" }, + { id = "npc.hundred_culinaromancer_book4", wiki = "Culinaromancer" }, + { id = "npc.hundred_culinaromancer_book5", wiki = "Culinaromancer" }, + { id = "npc.hundred_culinaromancer_dead", wiki = "Culinaromancer" }, + { id = "npc.hundred_culinaromancer_end", wiki = "Culinaromancer" }, + { id = "npc.hundred_culinaromancer_final", wiki = "Culinaromancer" }, + { id = "npc.hundred_ilm_snake", wiki = "Big Snake" }, + { id = "npc.hundred_minion1", wiki = "Agrith-Na-Na" }, + { id = "npc.hundred_minion2", wiki = "Flambeed" }, + { id = "npc.hundred_minion3", wiki = "Karamel" }, + { id = "npc.hundred_minion4", wiki = "Dessourt" }, + { id = "npc.hundred_minion5_air", wiki = "Gelatinnoth Mother" }, + { id = "npc.hundred_minion5_earth", wiki = "Gelatinnoth Mother" }, + { id = "npc.hundred_minion5_fire", wiki = "Gelatinnoth Mother" }, + { id = "npc.hundred_minion5_melee", wiki = "Gelatinnoth Mother" }, + { id = "npc.hundred_minion5_ranged", wiki = "Gelatinnoth Mother" }, + { id = "npc.hundred_minion5_water", wiki = "Gelatinnoth Mother" }, + { id = "npc.hydra", wiki = "Hydra" }, + { id = "npc.hydraboss", wiki = "Alchemical Hydra" }, + { id = "npc.hydraboss_2", wiki = "Alchemical Hydra" }, + { id = "npc.hydraboss_3", wiki = "Alchemical Hydra" }, + { id = "npc.hydraboss_4", wiki = "Alchemical Hydra" }, + { id = "npc.hydraboss_finaldeath", wiki = "Alchemical Hydra" }, + { id = "npc.hydraboss_p1_transition", wiki = "Alchemical Hydra" }, + { id = "npc.hydraboss_p2_transition", wiki = "Alchemical Hydra" }, + { id = "npc.hydraboss_p3_transition", wiki = "Alchemical Hydra" }, + { id = "npc.ice_giant_tapoyauik", wiki = "Ice giant" }, + { id = "npc.ice_giant_tapoyauik02", wiki = "Ice giant" }, + { id = "npc.ice_spider", wiki = "Ice spider" }, + { id = "npc.ice_spider_shortrange", wiki = "Ice spider" }, + { id = "npc.ice_warrior_tapoyauik", wiki = "Ice warrior" }, + { id = "npc.ice_wolf_1", wiki = "Ice wolf" }, + { id = "npc.ice_wolf_2", wiki = "Ice wolf" }, + { id = "npc.ice_wolf_3", wiki = "Ice wolf" }, + { id = "npc.ice_wolf_4", wiki = "Ice wolf" }, + { id = "npc.ice_wolf_5", wiki = "Ice wolf" }, + { id = "npc.ice_wolf_6", wiki = "Ice wolf" }, + { id = "npc.icediamond_icewarrior", wiki = "Kamil" }, + { id = "npc.icegiant", wiki = "Ice giant" }, + { id = "npc.icegiant2", wiki = "Ice giant" }, + { id = "npc.icegiant3", wiki = "Ice giant" }, + { id = "npc.icegiant_low_wanderrange", wiki = "Ice giant" }, + { id = "npc.icegiant_low_wanderrange2", wiki = "Ice giant" }, + { id = "npc.icewarrior", wiki = "Ice warrior" }, + { id = "npc.icewarrior_low_wanderrange", wiki = "Ice warrior" }, + { id = "npc.icewarrior_queen", wiki = "Ice warrior" }, + { id = "npc.ics_little_apmeken", wiki = "Apparition" }, + { id = "npc.ics_little_croc", wiki = "Crocodile" }, + { id = "npc.ics_little_croc_blocked", wiki = "Crocodile" }, + { id = "npc.ics_little_croc_normal", wiki = "Crocodile" }, + { id = "npc.ics_little_crondis", wiki = "Apparition" }, + { id = "npc.ics_little_het", wiki = "Apparition" }, + { id = "npc.ics_little_jackal", wiki = "Jackal" }, + { id = "npc.ics_little_jackal_alt", wiki = "Jackal" }, + { id = "npc.ics_little_locust_vis", wiki = "Locust" }, + { id = "npc.ics_little_plague_frog_vis", wiki = "Plague frog" }, + { id = "npc.ics_little_possessedpriest", wiki = "Possessed Priest" }, + { id = "npc.ics_little_scabaras", wiki = "Apparition" }, + { id = "npc.ics_scarab_swarm", wiki = "Scarab Swarm" }, + { id = "npc.ikov_firewarrior", wiki = "Fire Warrior of Lesarkus" }, + { id = "npc.inferno_creature_harpie", wiki = "Jal-MejRah" }, + { id = "npc.inferno_creature_mager", wiki = "Jal-Zek" }, + { id = "npc.inferno_creature_melee", wiki = "Jal-ImKot" }, + { id = "npc.inferno_creature_ranger", wiki = "Jal-Xil" }, + { id = "npc.inferno_creature_splitter", wiki = "Jal-Ak" }, + { id = "npc.inferno_creature_splitter_mage", wiki = "Jal-AkRek-Mej" }, + { id = "npc.inferno_creature_splitter_melee", wiki = "Jal-AkRek-Ket" }, + { id = "npc.inferno_creature_splitter_range", wiki = "Jal-AkRek-Xil" }, + { id = "npc.inferno_jad", wiki = "JalTok-Jad" }, + { id = "npc.inferno_jad_finalwave", wiki = "JalTok-Jad" }, + { id = "npc.inferno_mager_finalwave", wiki = "Jal-Zek" }, + { id = "npc.inferno_ranger_finalwave", wiki = "Jal-Xil" }, + { id = "npc.inferno_tzkalzuk_placeholder", wiki = "TzKal-Zuk" }, + { id = "npc.iron_dragon", wiki = "Iron dragon" }, + { id = "npc.iron_dragon_strongholdcave", wiki = "Iron dragon" }, + { id = "npc.jail_mugger", wiki = "Mugger" }, + { id = "npc.jailguard_four_patrol", wiki = "Jail guard" }, + { id = "npc.jailguard_three_patrol", wiki = "Jail guard" }, + { id = "npc.jailguard_two_patrol", wiki = "Jail guard" }, + { id = "npc.jogre", wiki = "Jogre" }, + { id = "npc.jormungand_dead", wiki = "The Jormungand" }, + { id = "npc.jungle_spider", wiki = "Jungle spider" }, + { id = "npc.jungle_wolf", wiki = "Jungle Wolf" }, + { id = "npc.juvenile_custodian_stalker", wiki = "Juvenile custodian stalker" }, + { id = "npc.kalphite_flyingqueen", wiki = "Kalphite Queen" }, + { id = "npc.kalphite_lord", wiki = "Kalphite Guardian" }, + { id = "npc.kalphite_lord_chamber", wiki = "Kalphite Guardian" }, + { id = "npc.kalphite_lord_strongholdcave", wiki = "Kalphite Guardian" }, + { id = "npc.kalphite_queen", wiki = "Kalphite Queen" }, + { id = "npc.kalphite_soldier", wiki = "Kalphite Soldier" }, + { id = "npc.kalphite_soldier_strongholdcave", wiki = "Kalphite Soldier" }, + { id = "npc.kalrag_vis", wiki = "Kalrag" }, + { id = "npc.khazard_warlord_chat", wiki = "Khazard warlord", note = "Yes, when talked to." }, + { id = "npc.khazard_warlord_combat", wiki = "Khazard warlord", note = "Yes, when talked to." }, + { id = "npc.king_dragon", wiki = "King Black Dragon" }, + { id = "npc.kingscorpion", wiki = "King Scorpion" }, + { id = "npc.koldemon", wiki = "Kolodion" }, + { id = "npc.kolethereal", wiki = "Kolodion" }, + { id = "npc.kolhuman", wiki = "Kolodion" }, + { id = "npc.kologre", wiki = "Kolodion" }, + { id = "npc.kolspider", wiki = "Kolodion" }, + { id = "npc.kourend_ankou", wiki = "Ankou" }, + { id = "npc.kourend_banshee", wiki = "Twisted Banshee" }, + { id = "npc.kourend_black_demon_1", wiki = "Black demon" }, + { id = "npc.kourend_black_demon_2", wiki = "Black demon" }, + { id = "npc.kourend_black_dragon", wiki = "Brutal black dragon" }, + { id = "npc.kourend_blue_dragon", wiki = "Brutal blue dragon" }, + { id = "npc.kourend_bronze_dragon", wiki = "Bronze dragon" }, + { id = "npc.kourend_dagannoth1", wiki = "Dagannoth" }, + { id = "npc.kourend_dagannoth2", wiki = "Dagannoth" }, + { id = "npc.kourend_dark_beast", wiki = "Dark beast" }, + { id = "npc.kourend_dark_warrior_1", wiki = "Dark warrior" }, + { id = "npc.kourend_dark_warrior_2", wiki = "Dark warrior" }, + { id = "npc.kourend_dark_warrior_3", wiki = "Dark warrior" }, + { id = "npc.kourend_firegiant1", wiki = "Fire giant" }, + { id = "npc.kourend_firegiant2", wiki = "Fire giant" }, + { id = "npc.kourend_ghost1", wiki = "Ghost" }, + { id = "npc.kourend_ghost2", wiki = "Ghost" }, + { id = "npc.kourend_greater_demon1", wiki = "Greater demon" }, + { id = "npc.kourend_greater_demon2", wiki = "Greater demon" }, + { id = "npc.kourend_greater_demon3", wiki = "Greater demon" }, + { id = "npc.kourend_hellhound", wiki = "Hellhound" }, + { id = "npc.kourend_hillgiant", wiki = "Hill Giant" }, + { id = "npc.kourend_iron_dragon", wiki = "Iron dragon" }, + { id = "npc.kourend_lesser_demon1", wiki = "Lesser demon" }, + { id = "npc.kourend_lesser_demon2", wiki = "Lesser demon" }, + { id = "npc.kourend_magicaxe", wiki = "Magic axe" }, + { id = "npc.kourend_mossgiant", wiki = "Moss giant" }, + { id = "npc.kourend_pickaxe", wiki = "Possessed pickaxe" }, + { id = "npc.kourend_red_dragon", wiki = "Brutal red dragon" }, + { id = "npc.kourend_rockcrab", wiki = "King Sand Crab" }, + { id = "npc.kourend_rockcrab_inactive", wiki = "King Sand Crab" }, + { id = "npc.kourend_spectre", wiki = "Deviant spectre" }, + { id = "npc.kourend_steel_dragon", wiki = "Steel dragon" }, + { id = "npc.kr_aggressive_black_knight", wiki = "Black Knight" }, + { id = "npc.kr_cam_bedivere", wiki = "Sir Bedivere" }, + { id = "npc.kr_cam_gawain", wiki = "Sir Gawain" }, + { id = "npc.kr_cam_kay", wiki = "Sir Kay" }, + { id = "npc.kr_cam_lancelot", wiki = "Sir Lancelot" }, + { id = "npc.kr_cam_lucan", wiki = "Sir Lucan" }, + { id = "npc.kr_cam_palomedes", wiki = "Sir Palomedes" }, + { id = "npc.kr_cam_pelleas", wiki = "Sir Pelleas" }, + { id = "npc.kr_cam_tristram", wiki = "Sir Tristram" }, + { id = "npc.kr_keep_knight", wiki = "Renegade knight" }, + { id = "npc.kr_knight1", wiki = "Sir Bedivere" }, + { id = "npc.kr_knight2", wiki = "Sir Pelleas" }, + { id = "npc.kr_knight3", wiki = "Sir Tristram" }, + { id = "npc.kr_knight4", wiki = "Sir Palomedes" }, + { id = "npc.kr_knight5", wiki = "Sir Lucan" }, + { id = "npc.kr_knight6", wiki = "Sir Gawain" }, + { id = "npc.kr_knight7", wiki = "Sir Kay" }, + { id = "npc.kr_knight8", wiki = "Sir Lancelot" }, + { id = "npc.lathastrainingogre", wiki = "Ogre" }, + { id = "npc.lava_dragon", wiki = "Lava dragon" }, + { id = "npc.league_superior_kurask", wiki = "Bloodthirsty kurask" }, + { id = "npc.lesser_demon", wiki = "Lesser demon" }, + { id = "npc.lesser_demon2", wiki = "Lesser demon" }, + { id = "npc.lesser_demon3", wiki = "Lesser demon" }, + { id = "npc.lesser_demon4", wiki = "Lesser demon" }, + { id = "npc.lesser_demon5", wiki = "Lesser demon" }, + { id = "npc.lesser_demon_slayercave_1", wiki = "Lesser demon" }, + { id = "npc.lesser_demon_slayercave_2", wiki = "Lesser demon" }, + { id = "npc.lesser_demon_slayercave_3", wiki = "Lesser demon" }, + { id = "npc.leviathan", wiki = "The Leviathan" }, + { id = "npc.leviathan_cutscene", wiki = "The Leviathan" }, + { id = "npc.leviathan_quest", wiki = "The Leviathan" }, + { id = "npc.lizardman_cave_shaman_1", wiki = "Lizardman shaman" }, + { id = "npc.lizardman_cave_shaman_2", wiki = "Lizardman shaman" }, + { id = "npc.lost_tribe_cutscene_sigmund", wiki = "Sigmund" }, + { id = "npc.lotg_goblin_skeleton", wiki = "Skoblin" }, + { id = "npc.lotg_goblin_skeleton_high_priest1", wiki = "Snothead" }, + { id = "npc.lotg_goblin_skeleton_high_priest1_defeated", wiki = "Snothead" }, + { id = "npc.lotg_goblin_skeleton_high_priest2", wiki = "Snailfeet" }, + { id = "npc.lotg_goblin_skeleton_high_priest2_defeated", wiki = "Snailfeet" }, + { id = "npc.lotg_goblin_skeleton_high_priest3", wiki = "Mosschin" }, + { id = "npc.lotg_goblin_skeleton_high_priest3_defeated", wiki = "Mosschin" }, + { id = "npc.lotg_goblin_skeleton_high_priest4", wiki = "Redeyes" }, + { id = "npc.lotg_goblin_skeleton_high_priest4_defeated", wiki = "Redeyes" }, + { id = "npc.lotg_goblin_skeleton_high_priest5", wiki = "Strongbones" }, + { id = "npc.lotg_goblin_skeleton_high_priest5_defeated", wiki = "Strongbones" }, + { id = "npc.lotr_possessed_pickaxe", wiki = "Possessed pickaxe" }, + { id = "npc.lotr_terror_dog_large", wiki = "Terror dog" }, + { id = "npc.lotr_terror_dog_small", wiki = "Terror dog" }, + { id = "npc.lotr_tran_razorlor_ghost", wiki = "Tarn" }, + { id = "npc.lotr_tran_razorlor_mutant", wiki = "Tarn" }, + { id = "npc.lunar_mossgiant", wiki = "Moss giant" }, + { id = "npc.lunar_mossgiant2", wiki = "Moss giant" }, + { id = "npc.lunar_suqka", wiki = "Suqah" }, + { id = "npc.lunar_suqka2", wiki = "Suqah" }, + { id = "npc.lunar_suqka3", wiki = "Suqah" }, + { id = "npc.lunar_suqka4", wiki = "Suqah" }, + { id = "npc.lunar_suqka5", wiki = "Suqah" }, + { id = "npc.lunar_suqka6", wiki = "Suqah" }, + { id = "npc.lunar_suqka7", wiki = "Suqah" }, + { id = "npc.ma2_boss_guthix", wiki = "Derwen" }, + { id = "npc.ma2_boss_guthix_spawning", wiki = "Derwen" }, + { id = "npc.mag_direwolf", wiki = "Dire Wolf Alpha" }, + { id = "npc.mag_direwolf_minion", wiki = "Dire Wolf" }, + { id = "npc.magicaxe", wiki = "Magic axe" }, + { id = "npc.mature_custodian_stalker", wiki = "Mature custodian stalker" }, + { id = "npc.mdaughter_bearman", wiki = "The Kendal" }, + { id = "npc.mdaughter_bearman_fighter", wiki = "The Kendal" }, + { id = "npc.melzar_the_mad", wiki = "Melzar the Mad" }, + { id = "npc.mm2_chieftan_keef_combat", wiki = "Keef" }, + { id = "npc.mm2_chieftan_keef_noncombat", wiki = "Keef" }, + { id = "npc.mm2_demon_glough", wiki = "Glough" }, + { id = "npc.mm2_demon_glough_cutscene", wiki = "Glough" }, + { id = "npc.mm2_demon_glough_nomove", wiki = "Glough" }, + { id = "npc.mm2_demon_gorilla_1_magic", wiki = "Demonic gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_demon_gorilla_1_melee", wiki = "Demonic gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_demon_gorilla_1_ranged", wiki = "Demonic gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_demon_gorilla_2_magic", wiki = "Demonic gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_demon_gorilla_2_melee", wiki = "Demonic gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_demon_gorilla_2_ranged", wiki = "Demonic gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_demon_gorilla_noncombat", wiki = "Demonic gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_kruk_combat", wiki = "Kruk" }, + { id = "npc.mm2_maniacal_monkey_maze", wiki = "Maniacal monkey" }, + { id = "npc.mm2_monkey_archer", wiki = "Monkey Archer", note = "Yes (unless the player is holding a monkey greegree)" }, + { id = "npc.mm2_monkey_archer_maze", wiki = "Maniacal Monkey Archer" }, + { id = "npc.mm2_tortured_gorilla_1", wiki = "Tortured gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_tortured_gorilla_2", wiki = "Tortured gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_tortured_gorilla_lab", wiki = "Tortured gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_tortured_gorilla_lab_defeated", wiki = "Tortured gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_tortured_gorilla_noncombat", wiki = "Tortured gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm2_tortured_gorilla_stronghold", wiki = "Tortured gorilla", note = "Yes, unless the player has completed Monkey Madness II" }, + { id = "npc.mm_demon", wiki = "Jungle Demon" }, + { id = "npc.mm_jungle_scorpion", wiki = "Scorpion" }, + { id = "npc.mm_monkey_archer", wiki = "Monkey Archer", note = "Yes (unless the player is holding a monkey greegree)" }, + { id = "npc.mm_oipuis", wiki = "Oipuis" }, + { id = "npc.mm_ouhai", wiki = "Ouhai" }, + { id = "npc.mm_padulah", wiki = "Padulah" }, + { id = "npc.mm_posted_archer", wiki = "Monkey Archer", note = "Yes (unless the player is holding a monkey greegree)" }, + { id = "npc.mm_ravine_archer", wiki = "Monkey Archer", note = "Yes (unless the player is holding a monkey greegree)" }, + { id = "npc.mm_religious_guard", wiki = "Monkey Guard", note = "Yes, unless you are wielding a Greegree of any kind or after completing Monkey Madness II" }, + { id = "npc.mm_religious_trapdoor_guard", wiki = "Monkey Guard", note = "Yes, unless you are wielding a Greegree of any kind or after completing Monkey Madness II" }, + { id = "npc.mm_uodai", wiki = "Uodai" }, + { id = "npc.mm_uyoro", wiki = "Uyoro" }, + { id = "npc.mm_zombie_monkey_large", wiki = "Monkey Zombie" }, + { id = "npc.mm_zombie_monkey_large_guard", wiki = "Monkey Zombie" }, + { id = "npc.mm_zombie_monkey_small", wiki = "Monkey Zombie" }, + { id = "npc.mmsnailpoint_blue", wiki = "Bruise Blamish Snail" }, + { id = "npc.mmsnailpoint_red+black", wiki = "Blood Blamish Snail" }, + { id = "npc.mmsnailpoint_swamp", wiki = "Myre Blamish Snail" }, + { id = "npc.mmsnailpoint_yellow", wiki = "Ochre Blamish Snail" }, + { id = "npc.mmsnailround_blue", wiki = "Bruise Blamish Snail" }, + { id = "npc.mmsnailround_orange", wiki = "Bark Blamish Snail" }, + { id = "npc.mmsnailround_red+black", wiki = "Blood Blamish Snail" }, + { id = "npc.mmsnailround_swamp", wiki = "Myre Blamish Snail" }, + { id = "npc.mmsnailround_yellow", wiki = "Ochre Blamish Snail" }, + { id = "npc.molanisk", wiki = "Molanisk" }, + { id = "npc.molch_lizardbrute_1", wiki = "Lizardman brute" }, + { id = "npc.molch_lizardman_1", wiki = "Lizardman" }, + { id = "npc.molch_lizardshaman_1", wiki = "Lizardman shaman" }, + { id = "npc.mort_afflicted_man", wiki = "Afflicted" }, + { id = "npc.mort_afflicted_man2", wiki = "Afflicted" }, + { id = "npc.mort_afflicted_woman", wiki = "Afflicted" }, + { id = "npc.mort_afflicted_woman2", wiki = "Afflicted" }, + { id = "npc.mossgiant", wiki = "Moss giant" }, + { id = "npc.mossgiant2", wiki = "Moss giant" }, + { id = "npc.mossgiant3", wiki = "Moss giant" }, + { id = "npc.mossgiant4", wiki = "Moss giant" }, + { id = "npc.mourner_hideout_guard_vis", wiki = "Mourner", note = "Yes unless wearing full mourner gear" }, + { id = "npc.mourner_hideout_head_mourner_vis", wiki = "Essyllt" }, + { id = "npc.mourning_dark_beast", wiki = "Dark beast" }, + { id = "npc.mudskipper_ogre", wiki = "Mogre" }, + { id = "npc.mugger", wiki = "Mugger" }, + { id = "npc.muspah", wiki = "Phantom Muspah" }, + { id = "npc.muspah_final", wiki = "Phantom Muspah" }, + { id = "npc.muspah_final_quest", wiki = "Strange Creature" }, + { id = "npc.muspah_melee", wiki = "Phantom Muspah" }, + { id = "npc.muspah_melee_quest", wiki = "Strange Creature" }, + { id = "npc.muspah_quest", wiki = "Strange Creature" }, + { id = "npc.muspah_soulsplit", wiki = "Phantom Muspah" }, + { id = "npc.muspah_soulsplit_quest", wiki = "Strange Creature" }, + { id = "npc.muspah_teleport", wiki = "Phantom Muspah" }, + { id = "npc.muspah_teleport_quest", wiki = "Strange Creature" }, + { id = "npc.my2arm_dontknowwhat", wiki = "Don't Know What" }, + { id = "npc.my2arm_dontknowwhat_stunned", wiki = "Don't Know What" }, + { id = "npc.my2arm_mother", wiki = "Mother" }, + { id = "npc.my2arm_mother_enthroned", wiki = "Mother" }, + { id = "npc.myarm_giant_roc", wiki = "Giant Roc" }, + { id = "npc.myq3_angry_juvenile", wiki = "Feral Vampyre" }, + { id = "npc.myq3_angry_juvinate", wiki = "Feral Vampyre" }, + { id = "npc.myq4_abomination", wiki = "Abomination" }, + { id = "npc.myq4_abomination_cutscene", wiki = "Abomination" }, + { id = "npc.myq4_abomination_cutscene_weakened", wiki = "Abomination" }, + { id = "npc.myq4_ranis_vampyre", wiki = "Ranis Drakan" }, + { id = "npc.myq4_ranis_vampyre_combat", wiki = "Ranis Drakan" }, + { id = "npc.myq4_ranis_vampyre_combat_enrage", wiki = "Ranis Drakan" }, + { id = "npc.myq4_ranis_vampyre_flying", wiki = "Ranis Drakan" }, + { id = "npc.myq4_ranis_vampyre_flying_combat", wiki = "Ranis Drakan" }, + { id = "npc.myq4_ranis_vampyre_flying_transition", wiki = "Ranis Drakan" }, + { id = "npc.myq4_ranis_vampyre_transition", wiki = "Ranis Drakan" }, + { id = "npc.myq4_vyrewatch_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myq4_vyrewatch_1_crowd", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myq4_vyrewatch_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myq4_vyrewatch_2_crowd", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myq4_vyrewatch_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myq4_vyrewatch_3_crowd", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myq4_vyrewatch_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myq4_vyrewatch_4_crowd", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myq5_damien_combat", wiki = "Damien Leucurte" }, + { id = "npc.myq5_damien_cutscene", wiki = "Damien Leucurte" }, + { id = "npc.myq5_damien_cutscene_strong", wiki = "Damien Leucurte" }, + { id = "npc.myq5_damien_cutscene_weak", wiki = "Damien Leucurte" }, + { id = "npc.myq5_kroy_combat", wiki = "Kroy" }, + { id = "npc.myq5_nail_beast_1", wiki = "Nail beast" }, + { id = "npc.myq5_nail_beast_2", wiki = "Nail beast" }, + { id = "npc.myq5_trek_juvinate_1", wiki = "Vampyre Juvinate" }, + { id = "npc.myq5_trek_juvinate_1_held", wiki = "Vampyre Juvinate" }, + { id = "npc.myq5_trek_juvinate_2", wiki = "Vampyre Juvinate" }, + { id = "npc.myq5_trek_juvinate_2_held", wiki = "Vampyre Juvinate" }, + { id = "npc.myq5_vanstrom_bloodveld", wiki = "Acidic Bloodveld" }, + { id = "npc.myq5_vanstrom_combat", wiki = "Vanstrom Klause" }, + { id = "npc.myq5_vanstrom_combat_enrage", wiki = "Vanstrom Klause" }, + { id = "npc.myq5_vanstrom_combat_noop", wiki = "Vanstrom Klause" }, + { id = "npc.myq5_vanstrom_human_cutscene", wiki = "Vanstrom Klause" }, + { id = "npc.myq5_vanstrom_vampyre_cutscene_vislevel", wiki = "Vanstrom Klause" }, + { id = "npc.myq5_vanstrom_vampyre_enrage_cutscene_vislevel", wiki = "Vanstrom Klause" }, + { id = "npc.myreque_pt3_takeoff_vyrewatch_female_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myreque_pt3_takeoff_vyrewatch_female_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myreque_pt3_takeoff_vyrewatch_female_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myreque_pt3_takeoff_vyrewatch_female_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myreque_pt3_takeoff_vyrewatch_male_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myreque_pt3_takeoff_vyrewatch_male_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myreque_pt3_takeoff_vyrewatch_male_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.myreque_pt3_takeoff_vyrewatch_male_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.necromancer", wiki = "Necromancer" }, + { id = "npc.nex", wiki = "Nex" }, + { id = "npc.nex_bloodmage", wiki = "Cruor", note = "Yes (after Nex makes him vulnerable)" }, + { id = "npc.nex_deflect", wiki = "Nex" }, + { id = "npc.nex_dying", wiki = "Nex" }, + { id = "npc.nex_icemage", wiki = "Glacies", note = "Yes (after Nex makes her vulnerable)" }, + { id = "npc.nex_prison_blood_reaver", wiki = "Blood Reaver", note = "Yes, unless wearing a Zaros-affiliated item" }, + { id = "npc.nex_prison_blood_reaver_boss", wiki = "Blood Reaver" }, + { id = "npc.nex_prison_mage", wiki = "Spiritual mage", note = "Yes, unless wearing a Zaros-affiliated item" }, + { id = "npc.nex_prison_ranger", wiki = "Spiritual ranger", note = "Yes, unless wearing a Zaros-affiliated item" }, + { id = "npc.nex_prison_warrior", wiki = "Spiritual warrior", note = "Yes, unless wearing a Zaros-affiliated item" }, + { id = "npc.nex_shadowmage", wiki = "Umbra", note = "Yes (after Nex makes her vulnerable)" }, + { id = "npc.nex_smokemage", wiki = "Fumus", note = "Yes (after Nex makes him vulnerable)" }, + { id = "npc.nex_soulsplit", wiki = "Nex" }, + { id = "npc.nex_spawning", wiki = "Nex" }, + { id = "npc.nightmare_challenge_dead", wiki = "Phosani's Nightmare" }, + { id = "npc.nightmare_challenge_initial", wiki = "Phosani's Nightmare" }, + { id = "npc.nightmare_husk_magic", wiki = "Husk" }, + { id = "npc.nightmare_husk_ranged", wiki = "Husk" }, + { id = "npc.nightmare_parasite", wiki = "Parasite" }, + { id = "npc.nightmare_parasite_weak", wiki = "Parasite" }, + { id = "npc.ntk_scarab_swarm", wiki = "Scarab Swarm" }, + { id = "npc.nylocas_boss_magic", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_boss_magic_hard", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_boss_magic_story", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_boss_melee", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_boss_melee_hard", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_boss_melee_story", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_boss_ranged", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_boss_ranged_hard", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_boss_ranged_story", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_boss_spawning", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_boss_spawning_hard", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_boss_spawning_story", wiki = "Nylocas Vasilias" }, + { id = "npc.nylocas_miniboss_magic_hard", wiki = "Nylocas Prinkipas" }, + { id = "npc.nylocas_miniboss_melee_hard", wiki = "Nylocas Prinkipas" }, + { id = "npc.nylocas_miniboss_ranged_hard", wiki = "Nylocas Prinkipas" }, + { id = "npc.nylocas_miniboss_spawning_hard", wiki = "Nylocas Prinkipas" }, + { id = "npc.nzone_agrith_naar_hard", wiki = "Agrith Naar (Nightmare Zone)" }, + { id = "npc.nzone_agrith_naar_normal", wiki = "Agrith Naar (Nightmare Zone)" }, + { id = "npc.nzone_arceuusquest_soul_hard", wiki = "Trapped Soul (Nightmare Zone)" }, + { id = "npc.nzone_arceuusquest_soul_normal", wiki = "Trapped Soul (Nightmare Zone)" }, + { id = "npc.nzone_arena_bouncer_hard", wiki = "Bouncer (Nightmare Zone)" }, + { id = "npc.nzone_arena_bouncer_normal", wiki = "Bouncer (Nightmare Zone)" }, + { id = "npc.nzone_black_knight_titan_hard", wiki = "Black Knight Titan (Nightmare Zone)" }, + { id = "npc.nzone_black_knight_titan_normal", wiki = "Black Knight Titan (Nightmare Zone)" }, + { id = "npc.nzone_blooddiamond_vampirewarrior_hard", wiki = "Dessous (Nightmare Zone)" }, + { id = "npc.nzone_blooddiamond_vampirewarrior_normal", wiki = "Dessous (Nightmare Zone)" }, + { id = "npc.nzone_brain_barrel_chest_hard", wiki = "Barrelchest (Nightmare Zone)" }, + { id = "npc.nzone_brain_barrel_chest_normal", wiki = "Barrelchest (Nightmare Zone)" }, + { id = "npc.nzone_chronozon_hard", wiki = "Chronozon (Nightmare Zone)" }, + { id = "npc.nzone_chronozon_normal", wiki = "Chronozon (Nightmare Zone)" }, + { id = "npc.nzone_contact_scarab_boss_hard", wiki = "Giant Scarab (Nightmare Zone)" }, + { id = "npc.nzone_contact_scarab_boss_normal", wiki = "Giant Scarab (Nightmare Zone)" }, + { id = "npc.nzone_corsaircurse_navigator_hard", wiki = "Corsair Traitor" }, + { id = "npc.nzone_corsaircurse_navigator_normal", wiki = "Corsair Traitor" }, + { id = "npc.nzone_count_draynor_hard", wiki = "Count Draynor (Nightmare Zone)" }, + { id = "npc.nzone_count_draynor_normal", wiki = "Count Draynor (Nightmare Zone)" }, + { id = "npc.nzone_cow_hard", wiki = "Cow (Nightmare Zone)" }, + { id = "npc.nzone_cow_normal", wiki = "Cow (Nightmare Zone)" }, + { id = "npc.nzone_dream_everlasting_hard", wiki = "The Everlasting (Nightmare Zone)" }, + { id = "npc.nzone_dream_everlasting_normal", wiki = "The Everlasting (Nightmare Zone)" }, + { id = "npc.nzone_dream_inadequacy_hard", wiki = "The Inadequacy (Nightmare Zone)" }, + { id = "npc.nzone_dream_inadequacy_normal", wiki = "The Inadequacy (Nightmare Zone)" }, + { id = "npc.nzone_elvarg_hard", wiki = "Elvarg (Nightmare Zone)" }, + { id = "npc.nzone_elvarg_normal", wiki = "Elvarg (Nightmare Zone)" }, + { id = "npc.nzone_fd_damis_normal_hard", wiki = "Damis (Nightmare Zone)" }, + { id = "npc.nzone_fd_damis_normal_normal", wiki = "Damis (Nightmare Zone)" }, + { id = "npc.nzone_fd_damis_tougher_hard", wiki = "Damis (Nightmare Zone)" }, + { id = "npc.nzone_fd_damis_tougher_normal", wiki = "Damis (Nightmare Zone)" }, + { id = "npc.nzone_firediamond_firewarrior_hard", wiki = "Fareed (Nightmare Zone)" }, + { id = "npc.nzone_firediamond_firewarrior_normal", wiki = "Fareed (Nightmare Zone)" }, + { id = "npc.nzone_fris_troll_king_true_hard", wiki = "Ice Troll King (Nightmare Zone)" }, + { id = "npc.nzone_fris_troll_king_true_normal", wiki = "Ice Troll King (Nightmare Zone)" }, + { id = "npc.nzone_ga_beast_hard", wiki = "Headless Beast (Nightmare Zone)" }, + { id = "npc.nzone_ga_beast_normal", wiki = "Headless Beast (Nightmare Zone)" }, + { id = "npc.nzone_grim_glod_hard", wiki = "Glod (Nightmare Zone)" }, + { id = "npc.nzone_grim_glod_normal", wiki = "Glod (Nightmare Zone)" }, + { id = "npc.nzone_hauntedmine_boss_ghost_hard", wiki = "Treus Dayth (Nightmare Zone)" }, + { id = "npc.nzone_hauntedmine_boss_ghost_normal", wiki = "Treus Dayth (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_air_hard", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_air_normal", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_earth_hard", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_earth_normal", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_fire_hard", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_fire_normal", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_melee_hard", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_melee_normal", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_ranged_hard", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_ranged_normal", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_water_hard", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_horror_dagganoth_water_normal", wiki = "Dagannoth mother (Nightmare Zone)" }, + { id = "npc.nzone_hosidiusquest_snake_hard", wiki = "Sand Snake (Nightmare Zone)" }, + { id = "npc.nzone_hosidiusquest_snake_normal", wiki = "Sand Snake (Nightmare Zone)" }, + { id = "npc.nzone_hundred_culinaromancer_final_hard", wiki = "Culinaromancer (Nightmare Zone)" }, + { id = "npc.nzone_hundred_culinaromancer_final_normal", wiki = "Culinaromancer (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion1_hard", wiki = "Agrith-Na-Na (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion1_normal", wiki = "Agrith-Na-Na (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion2_hard", wiki = "Flambeed (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion2_normal", wiki = "Flambeed (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion3_hard", wiki = "Karamel (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion3_normal", wiki = "Karamel (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion4_hard", wiki = "Dessourt (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion4_normal", wiki = "Dessourt (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_air_hard", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_air_normal", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_earth_hard", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_earth_normal", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_fire_hard", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_fire_normal", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_melee_hard", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_melee_normal", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_ranged_hard", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_ranged_normal", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_water_hard", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_hundred_minion5_water_normal", wiki = "Gelatinnoth Mother (Nightmare Zone)" }, + { id = "npc.nzone_icediamond_icewarrior_hard", wiki = "Kamil (Nightmare Zone)" }, + { id = "npc.nzone_icediamond_icewarrior_normal", wiki = "Kamil (Nightmare Zone)" }, + { id = "npc.nzone_khazard_warlord_hard", wiki = "Khazard warlord (Nightmare Zone)" }, + { id = "npc.nzone_khazard_warlord_normal", wiki = "Khazard warlord (Nightmare Zone)" }, + { id = "npc.nzone_mdaughter_bearman_fighter_hard", wiki = "The Kendal (Nightmare Zone)" }, + { id = "npc.nzone_mdaughter_bearman_fighter_normal", wiki = "The Kendal (Nightmare Zone)" }, + { id = "npc.nzone_mm_demon_hard", wiki = "Jungle Demon (Nightmare Zone)" }, + { id = "npc.nzone_mm_demon_normal", wiki = "Jungle Demon (Nightmare Zone)" }, + { id = "npc.nzone_myarm_giant_roc_hard", wiki = "Giant Roc (Nightmare Zone)" }, + { id = "npc.nzone_myarm_giant_roc_normal", wiki = "Giant Roc (Nightmare Zone)" }, + { id = "npc.nzone_nezikchened_hard", wiki = "Nezikchened (Nightmare Zone)" }, + { id = "npc.nzone_nezikchened_normal", wiki = "Nezikchened (Nightmare Zone)" }, + { id = "npc.nzone_quest_lunar_mirror_of_player_hard", wiki = "Me (Nightmare Zone)" }, + { id = "npc.nzone_quest_lunar_mirror_of_player_normal", wiki = "Me (Nightmare Zone)" }, + { id = "npc.nzone_roving_mossgiant_hard", wiki = "Moss Guardian (Nightmare Zone)" }, + { id = "npc.nzone_roving_mossgiant_normal", wiki = "Moss Guardian (Nightmare Zone)" }, + { id = "npc.nzone_shapeshifterbear_hard", wiki = "Witch's experiment (Nightmare Zone)" }, + { id = "npc.nzone_shapeshifterbear_normal", wiki = "Witch's experiment (Nightmare Zone)" }, + { id = "npc.nzone_shapeshifterglob_hard", wiki = "Witch's experiment (Nightmare Zone)" }, + { id = "npc.nzone_shapeshifterglob_normal", wiki = "Witch's experiment (Nightmare Zone)" }, + { id = "npc.nzone_shapeshifterspider_hard", wiki = "Witch's experiment (Nightmare Zone)" }, + { id = "npc.nzone_shapeshifterspider_normal", wiki = "Witch's experiment (Nightmare Zone)" }, + { id = "npc.nzone_shapeshifterwolf_hard", wiki = "Witch's experiment (Nightmare Zone)" }, + { id = "npc.nzone_shapeshifterwolf_normal", wiki = "Witch's experiment (Nightmare Zone)" }, + { id = "npc.nzone_shayzienquest_lizardman_hard", wiki = "Corrupt Lizardman (Nightmare Zone)" }, + { id = "npc.nzone_shayzienquest_lizardman_normal", wiki = "Corrupt Lizardman (Nightmare Zone)" }, + { id = "npc.nzone_skeleton_hellhound_hard", wiki = "Skeleton Hellhound (Nightmare Zone)" }, + { id = "npc.nzone_skeleton_hellhound_normal", wiki = "Skeleton Hellhound (Nightmare Zone)" }, + { id = "npc.nzone_slagilith_hard", wiki = "Slagilith (Nightmare Zone)" }, + { id = "npc.nzone_slagilith_normal", wiki = "Slagilith (Nightmare Zone)" }, + { id = "npc.nzone_sote_arianwyn_hard", wiki = "Arianwyn (Nightmare Zone)" }, + { id = "npc.nzone_sote_arianwyn_normal", wiki = "Arianwyn (Nightmare Zone)" }, + { id = "npc.nzone_sote_essyllt_hard", wiki = "Essyllt (Nightmare Zone)" }, + { id = "npc.nzone_sote_essyllt_normal", wiki = "Essyllt (Nightmare Zone)" }, + { id = "npc.nzone_surok_king_hard", wiki = "King Roald (Nightmare Zone)" }, + { id = "npc.nzone_surok_king_normal", wiki = "King Roald (Nightmare Zone)" }, + { id = "npc.nzone_tree_spirit_hard", wiki = "Tree spirit (Nightmare Zone)" }, + { id = "npc.nzone_tree_spirit_normal", wiki = "Tree spirit (Nightmare Zone)" }, + { id = "npc.nzone_trollromance_arrg_hard", wiki = "Arrg (Nightmare Zone)" }, + { id = "npc.nzone_trollromance_arrg_normal", wiki = "Arrg (Nightmare Zone)" }, + { id = "npc.nzone_zq_mainzombie1_hard", wiki = "Nazastarool (Nightmare Zone)" }, + { id = "npc.nzone_zq_mainzombie1_normal", wiki = "Nazastarool (Nightmare Zone)" }, + { id = "npc.nzone_zq_mainzombie2_hard", wiki = "Nazastarool (Nightmare Zone)" }, + { id = "npc.nzone_zq_mainzombie2_normal", wiki = "Nazastarool (Nightmare Zone)" }, + { id = "npc.nzone_zq_mainzombie3_hard", wiki = "Nazastarool (Nightmare Zone)" }, + { id = "npc.nzone_zq_mainzombie3_normal", wiki = "Nazastarool (Nightmare Zone)" }, + { id = "npc.ogre", wiki = "Ogre", note = "Yes (depends on location)" }, + { id = "npc.ogre2", wiki = "Ogre", note = "Yes (depends on location)" }, + { id = "npc.ogress_shaman1", wiki = "Ogress Shaman" }, + { id = "npc.ogress_shaman2", wiki = "Ogress Shaman" }, + { id = "npc.ogress_warrior1", wiki = "Ogress Warrior" }, + { id = "npc.ogress_warrior2", wiki = "Ogress Warrior" }, + { id = "npc.olaf2_ulfric_rise", wiki = "Ulfric" }, + { id = "npc.olm_head", wiki = "Great Olm" }, + { id = "npc.olm_head_spawning", wiki = "Great Olm" }, + { id = "npc.othainian_vis", wiki = "Othainian" }, + { id = "npc.pack_wolf", wiki = "Wolf" }, + { id = "npc.pack_wolf2", wiki = "Wolf" }, + { id = "npc.peng_icelord_warrior01", wiki = "Icelord" }, + { id = "npc.peng_icelord_warrior02", wiki = "Icelord" }, + { id = "npc.peng_icelord_warrior03", wiki = "Icelord" }, + { id = "npc.peng_icelord_warrior04", wiki = "Icelord" }, + { id = "npc.pest_brawler_1", wiki = "Brawler" }, + { id = "npc.pest_brawler_2", wiki = "Brawler" }, + { id = "npc.pest_brawler_3", wiki = "Brawler" }, + { id = "npc.pest_brawler_4", wiki = "Brawler" }, + { id = "npc.pest_brawler_5", wiki = "Brawler" }, + { id = "npc.pest_defiler_1a", wiki = "Defiler" }, + { id = "npc.pest_defiler_1b", wiki = "Defiler" }, + { id = "npc.pest_defiler_2a", wiki = "Defiler" }, + { id = "npc.pest_defiler_2b", wiki = "Defiler" }, + { id = "npc.pest_defiler_3a", wiki = "Defiler" }, + { id = "npc.pest_defiler_3b", wiki = "Defiler" }, + { id = "npc.pest_defiler_4a", wiki = "Defiler" }, + { id = "npc.pest_defiler_4b", wiki = "Defiler" }, + { id = "npc.pest_defiler_5a", wiki = "Defiler" }, + { id = "npc.pest_defiler_5b", wiki = "Defiler" }, + { id = "npc.pest_ravager_1", wiki = "Ravager" }, + { id = "npc.pest_ravager_2", wiki = "Ravager" }, + { id = "npc.pest_ravager_3", wiki = "Ravager" }, + { id = "npc.pest_ravager_4", wiki = "Ravager" }, + { id = "npc.pest_ravager_5", wiki = "Ravager" }, + { id = "npc.pest_shifter_1a", wiki = "Shifter" }, + { id = "npc.pest_shifter_1b", wiki = "Shifter" }, + { id = "npc.pest_shifter_2a", wiki = "Shifter" }, + { id = "npc.pest_shifter_2b", wiki = "Shifter" }, + { id = "npc.pest_shifter_3a", wiki = "Shifter" }, + { id = "npc.pest_shifter_3b", wiki = "Shifter" }, + { id = "npc.pest_shifter_4a", wiki = "Shifter" }, + { id = "npc.pest_shifter_4b", wiki = "Shifter" }, + { id = "npc.pest_shifter_5a", wiki = "Shifter" }, + { id = "npc.pest_shifter_5b", wiki = "Shifter" }, + { id = "npc.pest_splatter_1", wiki = "Splatter" }, + { id = "npc.pest_splatter_2", wiki = "Splatter" }, + { id = "npc.pest_splatter_3", wiki = "Splatter" }, + { id = "npc.pest_splatter_4", wiki = "Splatter" }, + { id = "npc.pest_splatter_5", wiki = "Splatter" }, + { id = "npc.pest_torcher_1a", wiki = "Torcher" }, + { id = "npc.pest_torcher_1b", wiki = "Torcher" }, + { id = "npc.pest_torcher_2a", wiki = "Torcher" }, + { id = "npc.pest_torcher_2b", wiki = "Torcher" }, + { id = "npc.pest_torcher_3a", wiki = "Torcher" }, + { id = "npc.pest_torcher_3b", wiki = "Torcher" }, + { id = "npc.pest_torcher_4a", wiki = "Torcher" }, + { id = "npc.pest_torcher_4b", wiki = "Torcher" }, + { id = "npc.pest_torcher_5a", wiki = "Torcher" }, + { id = "npc.pest_torcher_5b", wiki = "Torcher" }, + { id = "npc.pirate_aggressive", wiki = "Pirate" }, + { id = "npc.pmoon_boss_blood_moon_vis", wiki = "Blood Moon" }, + { id = "npc.pmoon_boss_blue_moon_vis", wiki = "Blue Moon" }, + { id = "npc.pmoon_boss_eclipse_moon_vis", wiki = "Eclipse Moon" }, + { id = "npc.pmoon_boss_jaguar", wiki = "Blood jaguar" }, + { id = "npc.pmoon_sulphur_nagua", wiki = "Sulphur Nagua" }, + { id = "npc.pog_mutated_terrorbird_1", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_2", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_3", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_4", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_5", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_6", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_7", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_8", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_boss_1", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_boss_1_background_vis", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_boss_2", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_boss_2_background_vis", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_boss_3", wiki = "Warped Terrorbird" }, + { id = "npc.pog_mutated_terrorbird_boss_3_background_vis", wiki = "Warped Terrorbird" }, + { id = "npc.poh_giantspider", wiki = "Huge spider" }, + { id = "npc.poh_ogre", wiki = "Ogre", note = "Yes (depends on location)" }, + { id = "npc.poh_oub_monster1", wiki = "Rocnar" }, + { id = "npc.poison_scorpion", wiki = "Poison Scorpion" }, + { id = "npc.poisonspider", wiki = "Poison spider" }, + { id = "npc.possessed_pickaxe", wiki = "Possessed pickaxe" }, + { id = "npc.possessed_pickaxe_stationary", wiki = "Possessed pickaxe" }, + { id = "npc.qip_ds_young_dark_wizard1", wiki = "Dark wizard" }, + { id = "npc.qip_ds_young_dark_wizard2", wiki = "Dark wizard" }, + { id = "npc.qip_ds_young_dark_wizard3", wiki = "Dark wizard" }, + { id = "npc.qip_ds_young_dark_wizard4", wiki = "Dark wizard" }, + { id = "npc.quest_lunar_mirror_of_player", wiki = "Me" }, + { id = "npc.quest_lunar_mirror_of_player_female", wiki = "Me" }, + { id = "npc.raids_dogodile", wiki = "Muttadile" }, + { id = "npc.raids_dogodile_junior", wiki = "Muttadile" }, + { id = "npc.raids_dogodile_submerged", wiki = "Muttadile" }, + { id = "npc.raids_scavenger_beast_a", wiki = "Scavenger beast" }, + { id = "npc.raids_scavenger_beast_b", wiki = "Scavenger beast" }, + { id = "npc.raids_tekton_fighting_enraged", wiki = "Tekton" }, + { id = "npc.raids_tekton_fighting_standard", wiki = "Tekton" }, + { id = "npc.raids_tekton_hammering", wiki = "Tekton" }, + { id = "npc.raids_tekton_waiting", wiki = "Tekton" }, + { id = "npc.raids_tekton_walking_enraged", wiki = "Tekton" }, + { id = "npc.raids_tekton_walking_standard", wiki = "Tekton" }, + { id = "npc.raids_vanguard_magic", wiki = "Vanguard" }, + { id = "npc.raids_vanguard_melee", wiki = "Vanguard" }, + { id = "npc.raids_vanguard_ranged", wiki = "Vanguard" }, + { id = "npc.raids_vanguard_walking", wiki = "Vanguard" }, + { id = "npc.raids_vasanistirio_healing", wiki = "Vasa Nistirio" }, + { id = "npc.raids_vasanistirio_walking", wiki = "Vasa Nistirio" }, + { id = "npc.raids_vespula_vespine_flying", wiki = "Vespine soldier" }, + { id = "npc.raids_vespula_vespine_walking", wiki = "Vespine soldier" }, + { id = "npc.ranalph_devere", wiki = "Ranalph Devere", note = "Yes, when not holding the respective heart piece" }, + { id = "npc.rat_boss_instance", wiki = "Scurrius" }, + { id = "npc.rat_boss_normal", wiki = "Scurrius" }, + { id = "npc.rc_zmi_lizard", wiki = "Cave lizard" }, + { id = "npc.rc_zmi_mage", wiki = "Zamorak mage" }, + { id = "npc.rc_zmi_mage2", wiki = "Zamorak mage" }, + { id = "npc.rc_zmi_melee", wiki = "Zamorak warrior" }, + { id = "npc.rc_zmi_melee2", wiki = "Zamorak warrior" }, + { id = "npc.rc_zmi_ranger", wiki = "Zamorak ranger" }, + { id = "npc.rc_zmi_ranger2", wiki = "Zamorak ranger" }, + { id = "npc.rc_zmi_runerunner", wiki = "Zamorak crafter" }, + { id = "npc.rc_zmi_runerunner2", wiki = "Zamorak crafter" }, + { id = "npc.rd_combat_npc_room_3", wiki = "Sir Leye" }, + { id = "npc.red_dragon", wiki = "Red dragon" }, + { id = "npc.red_dragon2", wiki = "Red dragon" }, + { id = "npc.red_dragon3", wiki = "Red dragon" }, + { id = "npc.red_dragon4", wiki = "Red dragon" }, + { id = "npc.red_dragon5", wiki = "Red dragon" }, + { id = "npc.regicide_darkbear", wiki = "Grizzly bear" }, + { id = "npc.rimmington_hobgoblin_armed_1", wiki = "Hobgoblin" }, + { id = "npc.rimmington_hobgoblin_unarmed_1", wiki = "Hobgoblin" }, + { id = "npc.rimmington_hobgoblin_unarmed_2", wiki = "Hobgoblin" }, + { id = "npc.rimmington_hobgoblin_unarmed_3", wiki = "Hobgoblin" }, + { id = "npc.route_vanstrom_vampire", wiki = "Vanstrom Klause" }, + { id = "npc.roving_mossgiant", wiki = "Moss Guardian" }, + { id = "npc.royal_sea_snake_mother_smaller", wiki = "Giant Sea Snake" }, + { id = "npc.rt_fire_queen_inactive", wiki = "Branda the Fire Queen" }, + { id = "npc.rt_ice_king_inactive", wiki = "Eldric the Ice King" }, + { id = "npc.rt_summon_elemental_ice", wiki = "Ice elemental (Royal Titans)" }, + { id = "npc.rune_dragon", wiki = "Rune dragon" }, + { id = "npc.sailing_armoured_kraken_dead", wiki = "Armoured kraken" }, + { id = "npc.sailing_charting_drink_crate_blue_lagoon_effect", wiki = "Blue dagannoth" }, + { id = "npc.sailing_charting_drink_crate_corpse_reviver_effect", wiki = "A corpse" }, + { id = "npc.sailing_charting_drink_crate_fish_stoutier_effect", wiki = "Dinky the drink troll" }, + { id = "npc.sailing_charting_drink_crate_monkfish_stout_effect_dead", wiki = "Drink troll queen" }, + { id = "npc.sailing_charting_drink_crate_possible_albumen_effect", wiki = "Angry tortugan" }, + { id = "npc.sailing_charting_drink_crate_prying_times_effect_troll", wiki = "Drink troll" }, + { id = "npc.sailing_charting_drink_troll_dad", wiki = "Daddy's special water" }, + { id = "npc.sailing_charting_drink_troll_ice", wiki = "The rocks" }, + { id = "npc.sailing_charting_drink_troll_thrower", wiki = "Chuck up" }, + { id = "npc.sailing_great_white_shark_dead", wiki = "Great white shark" }, + { id = "npc.sailing_hammerhead_shark_dead", wiki = "Hammerhead shark" }, + { id = "npc.sailing_npc_boat_combat_crew_captain_1", wiki = "Captain Ruban Acer" }, + { id = "npc.sailing_npc_boat_combat_crew_captain_2", wiki = "Captain Jack" }, + { id = "npc.sailing_npc_boat_combat_crew_trr_1", wiki = "Oaky Doak" }, + { id = "npc.sailing_npc_boat_combat_crew_trr_2", wiki = "Boatswain Bill Teak" }, + { id = "npc.sailing_npc_boat_combat_crew_trr_4", wiki = "Old Jack" }, + { id = "npc.sailing_npc_boat_combat_crew_trr_5", wiki = "Mute Jack" }, + { id = "npc.sailing_pygmy_kraken_dead", wiki = "Pygmy kraken" }, + { id = "npc.sailing_spined_kraken_dead", wiki = "Spined kraken" }, + { id = "npc.sailing_tiger_shark_dead", wiki = "Tiger shark" }, + { id = "npc.sailing_vampyre_kraken_dead", wiki = "Vampyre kraken" }, + { id = "npc.sailing_veiled_kraken_dead", wiki = "Veiled kraken" }, + { id = "npc.salarin_the_twisted", wiki = "Salarin the twisted" }, + { id = "npc.sang_myq3_female_flying_ns_vyrewatch_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_flying_ns_vyrewatch_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_flying_ns_vyrewatch_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_flying_ns_vyrewatch_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_flying_vyrewatch_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_flying_vyrewatch_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_flying_vyrewatch_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_flying_vyrewatch_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_juvinate", wiki = "Vampyre Juvinate" }, + { id = "npc.sang_myq3_female_juvinate_held", wiki = "Vampyre Juvinate" }, + { id = "npc.sang_myq3_female_walk_ns_vyrewatch_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_walk_ns_vyrewatch_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_walk_ns_vyrewatch_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_walk_ns_vyrewatch_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_walk_vyrewatch_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_walk_vyrewatch_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_walk_vyrewatch_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_female_walk_vyrewatch_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_flying_ns_vyrewatch_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_flying_ns_vyrewatch_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_flying_ns_vyrewatch_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_flying_ns_vyrewatch_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_flying_vyrewatch_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_flying_vyrewatch_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_flying_vyrewatch_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_flying_vyrewatch_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_juvinate", wiki = "Vampyre Juvinate" }, + { id = "npc.sang_myq3_male_juvinate_held", wiki = "Vampyre Juvinate" }, + { id = "npc.sang_myq3_male_walk_ns_vyrewatch_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_walk_ns_vyrewatch_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_walk_ns_vyrewatch_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_walk_ns_vyrewatch_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_walk_vyrewatch_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_walk_vyrewatch_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_walk_vyrewatch_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sang_myq3_male_walk_vyrewatch_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.sarachnis", wiki = "Sarachnis" }, + { id = "npc.sarachnis_mage_spawn", wiki = "Spawn of Sarachnis" }, + { id = "npc.sarachnis_melee_spawn", wiki = "Spawn of Sarachnis" }, + { id = "npc.saradomin_mage", wiki = "Battle mage", note = "Yes, unless wearing the same cape as the battle mage." }, + { id = "npc.scarab_swarm", wiki = "Scarabs" }, + { id = "npc.scorpia", wiki = "Scorpia" }, + { id = "npc.scorpion", wiki = "Scorpion" }, + { id = "npc.scrambled_chicken", wiki = "Large chicken" }, + { id = "npc.shade_level1", wiki = "Loar Shade" }, + { id = "npc.shade_level2", wiki = "Phrin Shade" }, + { id = "npc.shade_level3", wiki = "Riyl Shade" }, + { id = "npc.shade_level4", wiki = "Asyn Shade" }, + { id = "npc.shade_level5", wiki = "Fiyr Shade" }, + { id = "npc.shade_level6", wiki = "Urium Shade" }, + { id = "npc.shades_undead_zealot_1", wiki = "Undead Zealot" }, + { id = "npc.shades_undead_zealot_2", wiki = "Undead Zealot" }, + { id = "npc.shadeshadow_level1", wiki = "Loar Shade" }, + { id = "npc.shadeshadow_level2", wiki = "Phrin Shade" }, + { id = "npc.shadeshadow_level3", wiki = "Riyl Shade" }, + { id = "npc.shadeshadow_level4", wiki = "Asyn Shade" }, + { id = "npc.shadeshadow_level5", wiki = "Fiyr Shade" }, + { id = "npc.shadeshadow_level6", wiki = "Urium Shade" }, + { id = "npc.shadow_dog_wild", wiki = "Shadow Hound" }, + { id = "npc.shadow_maj_khazard", wiki = "General Khazard", note = "Yes, after killing Bouncer and talked to." }, + { id = "npc.shadow_spider", wiki = "Shadow spider" }, + { id = "npc.shadow_warrior", wiki = "Shadow warrior" }, + { id = "npc.shantay_guard", wiki = "Shantay Guard" }, + { id = "npc.shantay_guard_still", wiki = "Shantay Guard" }, + { id = "npc.shapeshifterbear", wiki = "Witch's experiment" }, + { id = "npc.shapeshifterglob", wiki = "Witch's experiment" }, + { id = "npc.shapeshifterspider", wiki = "Witch's experiment" }, + { id = "npc.shapeshifterwolf", wiki = "Witch's experiment" }, + { id = "npc.shayzienquest_lizardman_boss", wiki = "Corrupt Lizardman" }, + { id = "npc.skeletal_miner", wiki = "Skeletal miner" }, + { id = "npc.skeletal_wyvern1", wiki = "Skeletal Wyvern" }, + { id = "npc.skeletal_wyvern2", wiki = "Skeletal Wyvern" }, + { id = "npc.skeletal_wyvern3", wiki = "Skeletal Wyvern" }, + { id = "npc.skeletal_wyvern4", wiki = "Skeletal Wyvern" }, + { id = "npc.skeleton_armed", wiki = "Skeleton" }, + { id = "npc.skeleton_armed2", wiki = "Skeleton" }, + { id = "npc.skeleton_armed3", wiki = "Skeleton" }, + { id = "npc.skeleton_armed4", wiki = "Skeleton" }, + { id = "npc.skeleton_armed5", wiki = "Skeleton" }, + { id = "npc.skeleton_hellhound", wiki = "Skeleton Hellhound" }, + { id = "npc.skeleton_unagressive", wiki = "Skeleton" }, + { id = "npc.skeleton_unagressive2", wiki = "Skeleton" }, + { id = "npc.skeleton_unagressive3", wiki = "Skeleton" }, + { id = "npc.skeleton_unarmed", wiki = "Skeleton" }, + { id = "npc.skeleton_unarmed2", wiki = "Skeleton" }, + { id = "npc.skeleton_unarmed3", wiki = "Skeleton" }, + { id = "npc.skeleton_unarmed4", wiki = "Skeleton" }, + { id = "npc.skeletonmage", wiki = "Skeleton Mage" }, + { id = "npc.slagilith_hybernate", wiki = "Slagilith" }, + { id = "npc.slave_rowdy", wiki = "Rowdy slave" }, + { id = "npc.slayer_abberant_spectre_1", wiki = "Aberrant spectre" }, + { id = "npc.slayer_abberant_spectre_2", wiki = "Aberrant spectre" }, + { id = "npc.slayer_abberant_spectre_3", wiki = "Aberrant spectre" }, + { id = "npc.slayer_abberant_spectre_4", wiki = "Aberrant spectre" }, + { id = "npc.slayer_aberrantspectre_1_strongholdcave", wiki = "Aberrant spectre" }, + { id = "npc.slayer_aberrantspectre_2_strongholdcave", wiki = "Aberrant spectre" }, + { id = "npc.slayer_banshee_1", wiki = "Banshee" }, + { id = "npc.slayer_cave_crawler_1", wiki = "Cave crawler" }, + { id = "npc.slayer_cave_crawler_2", wiki = "Cave crawler" }, + { id = "npc.slayer_cave_crawler_3", wiki = "Cave crawler" }, + { id = "npc.slayer_cave_crawler_4", wiki = "Cave crawler" }, + { id = "npc.slayer_cave_gargoyle", wiki = "Gargoyle" }, + { id = "npc.slayer_gargoyle_1", wiki = "Gargoyle" }, + { id = "npc.slayer_gargoyle_dead", wiki = "Gargoyle" }, + { id = "npc.slayer_harpiebugswarm", wiki = "Harpie Bug Swarm" }, + { id = "npc.slayer_kursk_1", wiki = "Kurask" }, + { id = "npc.slayer_kursk_2", wiki = "Kurask" }, + { id = "npc.slayer_lava_strykewyrm", wiki = "Lava Strykewyrm" }, + { id = "npc.slayer_lizard_large1_green", wiki = "Desert Lizard" }, + { id = "npc.slayer_lizard_large1_green_lowrange", wiki = "Desert Lizard" }, + { id = "npc.slayer_lizard_large2_sandy", wiki = "Desert Lizard" }, + { id = "npc.slayer_lizard_large3_sandy", wiki = "Desert Lizard" }, + { id = "npc.slayer_lizard_massive", wiki = "Lizard" }, + { id = "npc.slayer_nechryael_spawn", wiki = "Death spawn" }, + { id = "npc.slepe_vyrewatch_female_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.slepe_vyrewatch_female_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.slepe_vyrewatch_female_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.slepe_vyrewatch_female_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.slepe_vyrewatch_male_1", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.slepe_vyrewatch_male_2", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.slepe_vyrewatch_male_3", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.slepe_vyrewatch_male_4", wiki = "Vyrewatch", note = "Yes, unless wearing Vyre noble clothing" }, + { id = "npc.slice_ham_archer", wiki = "H.A.M. Archer" }, + { id = "npc.slice_ham_mage", wiki = "H.A.M. Mage" }, + { id = "npc.slice_sigmund_magic", wiki = "Sigmund" }, + { id = "npc.slice_sigmund_melee", wiki = "Sigmund" }, + { id = "npc.slice_sigmund_noprayer", wiki = "Sigmund" }, + { id = "npc.slice_sigmund_ranged", wiki = "Sigmund" }, + { id = "npc.slice_sigmund_showdown", wiki = "Sigmund" }, + { id = "npc.slice_sigmund_village", wiki = "Sigmund" }, + { id = "npc.slug2_giant_lobster_peaceful", wiki = "Giant lobster" }, + { id = "npc.slug2_prince_noncombat", wiki = "Slug Prince" }, + { id = "npc.smallscorpion", wiki = "Pit Scorpion" }, + { id = "npc.snake", wiki = "Snake" }, + { id = "npc.snakeboss_boss_magic", wiki = "Zulrah" }, + { id = "npc.snakeboss_boss_melee", wiki = "Zulrah" }, + { id = "npc.snakeboss_boss_ranged", wiki = "Zulrah" }, + { id = "npc.snakeboss_minion_magic", wiki = "Snakeling" }, + { id = "npc.snakeboss_minion_melee", wiki = "Snakeling" }, + { id = "npc.sos_death_ankou", wiki = "Ankou" }, + { id = "npc.sos_death_ankou2", wiki = "Ankou" }, + { id = "npc.sos_death_ankou3", wiki = "Ankou" }, + { id = "npc.sos_death_ankou_strongholdcave", wiki = "Ankou" }, + { id = "npc.sos_death_ankou_strongholdcave_2", wiki = "Ankou" }, + { id = "npc.sos_death_ankou_strongholdcave_3", wiki = "Ankou" }, + { id = "npc.sos_death_ghost", wiki = "Ghost" }, + { id = "npc.sos_death_ghost2", wiki = "Ghost" }, + { id = "npc.sos_death_ghost2_b", wiki = "Ghost" }, + { id = "npc.sos_death_ghost2_c", wiki = "Ghost" }, + { id = "npc.sos_death_ghost2_d", wiki = "Ghost" }, + { id = "npc.sos_death_ghost_b", wiki = "Ghost" }, + { id = "npc.sos_death_ghost_c", wiki = "Ghost" }, + { id = "npc.sos_death_ghost_d", wiki = "Ghost" }, + { id = "npc.sos_fam_fleshcrawler", wiki = "Flesh Crawler" }, + { id = "npc.sos_fam_fleshcrawler2", wiki = "Flesh Crawler" }, + { id = "npc.sos_fam_fleshcrawler3", wiki = "Flesh Crawler" }, + { id = "npc.sos_fam_giantrat", wiki = "Giant rat" }, + { id = "npc.sos_fam_giantrat2", wiki = "Giant rat" }, + { id = "npc.sos_fam_giantrat3", wiki = "Giant rat" }, + { id = "npc.sos_pest_catablepon", wiki = "Catablepon" }, + { id = "npc.sos_pest_catablepon2", wiki = "Catablepon" }, + { id = "npc.sos_pest_catablepon3", wiki = "Catablepon" }, + { id = "npc.sos_pest_giantspider1", wiki = "Giant spider" }, + { id = "npc.sos_pest_scorpion", wiki = "Scorpion" }, + { id = "npc.sos_pest_scorpion2", wiki = "Scorpion" }, + { id = "npc.sos_pest_spider", wiki = "Spider" }, + { id = "npc.sote_arianwyn_combat", wiki = "Arianwyn" }, + { id = "npc.sote_essyllt_combat", wiki = "Essyllt" }, + { id = "npc.sote_essyllt_cutscene", wiki = "Essyllt" }, + { id = "npc.sote_essyllt_cutscene_armed", wiki = "Essyllt" }, + { id = "npc.sote_head_mourner", wiki = "Essyllt" }, + { id = "npc.sotn_assassin_combat", wiki = "Assassin" }, + { id = "npc.sotn_evelot_combat", wiki = "Evelot" }, + { id = "npc.sotn_muspah_cutscene", wiki = "Strange Creature" }, + { id = "npc.soul_wars_tut_blue_boss", wiki = "Avatar of Creation", note = "Yes (To Red team only)" }, + { id = "npc.soul_wars_tut_red_boss", wiki = "Avatar of Destruction", note = "Yes (to Blue team only)" }, + { id = "npc.soulbane_anger_bear", wiki = "Angry bear" }, + { id = "npc.soulbane_anger_goblin", wiki = "Angry goblin" }, + { id = "npc.soulbane_anger_rat", wiki = "Angry giant rat" }, + { id = "npc.soulbane_anger_unicorn", wiki = "Angry unicorn" }, + { id = "npc.soulbane_conf_beast", wiki = "Confusion beast" }, + { id = "npc.soulbane_confu_creeper", wiki = "Confusion beast" }, + { id = "npc.soulbane_confu_creeper_fake1", wiki = "Confusion beast" }, + { id = "npc.soulbane_confu_creeper_fake2", wiki = "Confusion beast" }, + { id = "npc.soulbane_confu_creeper_fake3", wiki = "Confusion beast" }, + { id = "npc.soulbane_confu_creeper_fake4", wiki = "Confusion beast" }, + { id = "npc.soulbane_fear_beast", wiki = "Fear reaper" }, + { id = "npc.soulbane_fear_reaper", wiki = "Fear reaper" }, + { id = "npc.soulbane_final_tolna1", wiki = "Tolna" }, + { id = "npc.soulbane_final_tolna2", wiki = "Tolna" }, + { id = "npc.soulbane_final_tolna3", wiki = "Tolna" }, + { id = "npc.soulbane_hope_monst1", wiki = "Hopeless creature" }, + { id = "npc.soulbane_hope_monst2", wiki = "Hopeless creature" }, + { id = "npc.soulbane_hope_monst3", wiki = "Hopeless creature" }, + { id = "npc.soulbane_less_beast3", wiki = "Hopeless creature" }, + { id = "npc.souless_vis", wiki = "Soulless" }, + { id = "npc.sourhog", wiki = "Sourhog" }, + { id = "npc.spindel_spiderling", wiki = "Spindel's Spiderling" }, + { id = "npc.steel_dragon", wiki = "Steel dragon" }, + { id = "npc.steel_dragon_strongholdcave", wiki = "Steel dragon" }, + { id = "npc.suit_of_armour", wiki = "Suit of armour", note = "Yes to whoever causes them to animate" }, + { id = "npc.sulphur_lizard", wiki = "Sulphur Lizard" }, + { id = "npc.summonedzombie", wiki = "Summoned Zombie" }, + { id = "npc.superior_araxyte", wiki = "Dreadborn Araxyte" }, + { id = "npc.superior_basilisk_knight", wiki = "Basilisk Sentinel" }, + { id = "npc.superior_custodian", wiki = "Ancient Custodian" }, + { id = "npc.superior_kurask", wiki = "King kurask" }, + { id = "npc.surok_king", wiki = "King Roald" }, + { id = "npc.swamp_cave_bug", wiki = "Cave bug" }, + { id = "npc.swamp_wallbeast", wiki = "Wall beast" }, + { id = "npc.swamp_wallbeast_combat", wiki = "Wall beast" }, + { id = "npc.swampcrab", wiki = "Swamp Crab" }, + { id = "npc.swampcrab_bloom", wiki = "Swamp Crab" }, + { id = "npc.swampcrab_inactive", wiki = "Swamp Crab" }, + { id = "npc.swan_kalphite_1", wiki = "Kalphite Queen" }, + { id = "npc.swan_kalphite_2", wiki = "Kalphite Queen" }, + { id = "npc.swan_seatroll_queen", wiki = "Sea Troll Queen" }, + { id = "npc.swan_troll_ambush", wiki = "Sea troll" }, + { id = "npc.swan_troll_fishing_1", wiki = "Sea troll" }, + { id = "npc.swan_troll_fishing_2", wiki = "Sea troll" }, + { id = "npc.swan_troll_fishing_3", wiki = "Sea troll" }, + { id = "npc.tapo_ice_spider_shortrange", wiki = "Ice spider" }, + { id = "npc.tbwcu_broodoo_combat", wiki = "Broodoo victim" }, + { id = "npc.tbwcu_broodoo_combat_instant", wiki = "Broodoo victim" }, + { id = "npc.tbwcu_broodoo_disease", wiki = "Broodoo victim" }, + { id = "npc.tbwcu_broodoo_disease_instant", wiki = "Broodoo victim" }, + { id = "npc.tbwcu_broodoo_poison", wiki = "Broodoo victim" }, + { id = "npc.tbwcu_broodoo_poison_instant", wiki = "Broodoo victim" }, + { id = "npc.tbwcu_jungle_spider", wiki = "Jungle spider" }, + { id = "npc.tbwcu_mosquito_single", wiki = "Large mosquito" }, + { id = "npc.tbwcu_mosquito_swarm3", wiki = "Mosquito swarm" }, + { id = "npc.tbwcu_mosquito_swarm5", wiki = "Mosquito swarm" }, + { id = "npc.tbwcu_snakejungle_appear", wiki = "Bush snake" }, + { id = "npc.tbwcu_spider_appear", wiki = "Jungle spider" }, + { id = "npc.tbwcu_tribesman", wiki = "Tribesman" }, + { id = "npc.tbwcu_tribesman_instant", wiki = "Tribesman" }, + { id = "npc.tbwcu_village_snake", wiki = "Bush snake" }, + { id = "npc.tbwt_beast_cutscene", wiki = "The Shaikahan" }, + { id = "npc.templetrek_ghast_invis_1", wiki = "Ghast" }, + { id = "npc.templetrek_ghast_invis_2", wiki = "Ghast" }, + { id = "npc.templetrek_ghast_invis_3", wiki = "Ghast" }, + { id = "npc.templetrek_ghast_vis_1", wiki = "Ghast" }, + { id = "npc.templetrek_ghast_vis_2", wiki = "Ghast" }, + { id = "npc.templetrek_giantsnail_1", wiki = "Giant snail", note = "Yes, after 5 seconds of inactivity." }, + { id = "npc.templetrek_giantsnail_2", wiki = "Giant snail", note = "Yes, after 5 seconds of inactivity." }, + { id = "npc.templetrek_giantsnail_3", wiki = "Giant snail", note = "Yes, after 5 seconds of inactivity." }, + { id = "npc.templetrek_snake_1", wiki = "Swamp snake", note = "Yes, after 5 seconds of inactivity" }, + { id = "npc.templetrek_snake_2", wiki = "Swamp snake", note = "Yes, after 5 seconds of inactivity" }, + { id = "npc.templetrek_snake_3", wiki = "Swamp snake", note = "Yes, after 5 seconds of inactivity" }, + { id = "npc.templetrek_vampire_1", wiki = "Vampyre Juvinate" }, + { id = "npc.templetrek_vampire_2", wiki = "Vampyre Juvinate" }, + { id = "npc.templetrek_vampire_3", wiki = "Vampyre Juvinate" }, + { id = "npc.thug", wiki = "Thug" }, + { id = "npc.tlati_mossgiant01", wiki = "Moss giant" }, + { id = "npc.tlati_mossgiant02", wiki = "Moss giant" }, + { id = "npc.tlati_mossgiant03", wiki = "Moss giant" }, + { id = "npc.tlati_mossgiant04", wiki = "Moss giant" }, + { id = "npc.toa_baba", wiki = "Ba-Ba" }, + { id = "npc.toa_baba_baboon", wiki = "Baboon" }, + { id = "npc.toa_baba_coffin", wiki = "Ba-Ba" }, + { id = "npc.toa_baba_digging", wiki = "Ba-Ba" }, + { id = "npc.toa_kephri_boss_dead", wiki = "Kephri" }, + { id = "npc.toa_kephri_boss_enrage", wiki = "Kephri" }, + { id = "npc.toa_kephri_guardian_mage", wiki = "Arcane Scarab" }, + { id = "npc.toa_kephri_guardian_melee", wiki = "Soldier Scarab" }, + { id = "npc.toa_kephri_guardian_ranged", wiki = "Spitting Scarab" }, + { id = "npc.toa_kephri_scarab_rangekite", wiki = "Agile Scarab" }, + { id = "npc.toa_path_apmeken_baboon_magic_1", wiki = "Baboon Mage" }, + { id = "npc.toa_path_apmeken_baboon_magic_2", wiki = "Baboon Mage" }, + { id = "npc.toa_path_apmeken_baboon_melee_1", wiki = "Baboon Brawler" }, + { id = "npc.toa_path_apmeken_baboon_melee_2", wiki = "Baboon Brawler" }, + { id = "npc.toa_path_apmeken_baboon_ranged_1", wiki = "Baboon Thrower" }, + { id = "npc.toa_path_apmeken_baboon_ranged_2", wiki = "Baboon Thrower" }, + { id = "npc.toa_path_apmeken_baboon_shaman", wiki = "Baboon Shaman" }, + { id = "npc.toa_path_apmeken_baboon_thrall", wiki = "Baboon Thrall" }, + { id = "npc.toa_path_apmeken_baboon_zombie", wiki = "Volatile Baboon" }, + { id = "npc.toa_warden_elidinis_phase2_exposed", wiki = "Elidinis' Warden" }, + { id = "npc.toa_warden_elidinis_phase2_mage", wiki = "Elidinis' Warden" }, + { id = "npc.toa_warden_elidinis_phase2_range", wiki = "Elidinis' Warden" }, + { id = "npc.toa_warden_elidinis_phase3", wiki = "Elidinis' Warden" }, + { id = "npc.toa_warden_elidinis_phase3_charging", wiki = "Elidinis' Warden" }, + { id = "npc.toa_warden_tumeken_phase2_exposed", wiki = "Tumeken's Warden" }, + { id = "npc.toa_warden_tumeken_phase2_mage", wiki = "Tumeken's Warden" }, + { id = "npc.toa_warden_tumeken_phase2_range", wiki = "Tumeken's Warden" }, + { id = "npc.toa_warden_tumeken_phase3", wiki = "Tumeken's Warden" }, + { id = "npc.toa_warden_tumeken_phase3_charging", wiki = "Tumeken's Warden" }, + { id = "npc.toa_zebak", wiki = "Zebak" }, + { id = "npc.toa_zebak_dead", wiki = "Zebak" }, + { id = "npc.toa_zebak_enraged", wiki = "Zebak" }, + { id = "npc.tob_bloat", wiki = "Pestilent Bloat" }, + { id = "npc.tob_bloat_hard", wiki = "Pestilent Bloat" }, + { id = "npc.tob_bloat_story", wiki = "Pestilent Bloat" }, + { id = "npc.tob_maiden_100", wiki = "The Maiden of Sugadinti" }, + { id = "npc.tob_maiden_100_hard", wiki = "The Maiden of Sugadinti" }, + { id = "npc.tob_maiden_100_story", wiki = "The Maiden of Sugadinti" }, + { id = "npc.tob_maiden_30", wiki = "The Maiden of Sugadinti" }, + { id = "npc.tob_maiden_50", wiki = "The Maiden of Sugadinti" }, + { id = "npc.tob_maiden_70", wiki = "The Maiden of Sugadinti" }, + { id = "npc.tob_xarpus_feeding", wiki = "Xarpus" }, + { id = "npc.tob_xarpus_feeding_hard", wiki = "Xarpus" }, + { id = "npc.tob_xarpus_static", wiki = "Xarpus" }, + { id = "npc.tob_xarpus_static_hard", wiki = "Xarpus" }, + { id = "npc.tol_frogeel", wiki = "Frogeel" }, + { id = "npc.tol_jubster", wiki = "Jubster" }, + { id = "npc.tol_newtroost", wiki = "Newtroost" }, + { id = "npc.tol_spidine", wiki = "Spidine" }, + { id = "npc.tol_swordchick", wiki = "Swordchick" }, + { id = "npc.tol_unicow", wiki = "Unicow" }, + { id = "npc.tourtrap_qip_desert_mining_merc_1", wiki = "Mercenary", note = "Yes, if wielding a weapon." }, + { id = "npc.tourtrap_qip_desert_mining_merc_2", wiki = "Mercenary", note = "Yes, if wielding a weapon." }, + { id = "npc.tourtrap_qip_desert_mining_merc_3", wiki = "Mercenary", note = "Yes, if wielding a weapon." }, + { id = "npc.tourtrap_qip_desert_mining_merc_4", wiki = "Mercenary", note = "Yes, if wielding a weapon." }, + { id = "npc.trail_cabbage_mage", wiki = "Brassican Mage" }, + { id = "npc.trail_hard", wiki = "Zamorak wizard" }, + { id = "npc.trail_hard2", wiki = "Saradomin wizard" }, + { id = "npc.trail_mimic_spawn_mage", wiki = "Third Age Mage" }, + { id = "npc.trail_mimic_spawn_melee", wiki = "Third Age Warrior" }, + { id = "npc.trail_mimic_spawn_ranger", wiki = "Third Age Ranger" }, + { id = "npc.trail_zaros_melee", wiki = "Ancient Wizard" }, + { id = "npc.trail_zaros_range", wiki = "Ancient Wizard" }, + { id = "npc.trail_zaros_wizard", wiki = "Ancient Wizard" }, + { id = "npc.trek_vampire_juve_angry_1", wiki = "Feral Vampyre" }, + { id = "npc.trek_vampire_juve_angry_2", wiki = "Feral Vampyre" }, + { id = "npc.trek_vampire_juve_angry_3", wiki = "Feral Vampyre" }, + { id = "npc.trek_vampire_juve_held_1", wiki = "Vampyre Juvinate" }, + { id = "npc.trek_vampire_juve_held_2", wiki = "Vampyre Juvinate" }, + { id = "npc.trek_vampire_juve_held_3", wiki = "Vampyre Juvinate" }, + { id = "npc.tribesman", wiki = "Tribesman" }, + { id = "npc.tribesman_variant01", wiki = "Tribesman" }, + { id = "npc.tribeswoman", wiki = "Tribeswoman" }, + { id = "npc.tribeswoman_variant01", wiki = "Tribeswoman" }, + { id = "npc.troll_general", wiki = "Troll general" }, + { id = "npc.troll_general2", wiki = "Troll general" }, + { id = "npc.troll_general3", wiki = "Troll general" }, + { id = "npc.troll_melee1", wiki = "Mountain troll" }, + { id = "npc.troll_prison_guard2", wiki = "Berry" }, + { id = "npc.troll_prison_guard2_awake", wiki = "Berry" }, + { id = "npc.trollrescue_icetroll_melee1", wiki = "Ice troll" }, + { id = "npc.trollrescue_icetroll_melee2", wiki = "Ice troll" }, + { id = "npc.trollrescue_icetroll_melee3", wiki = "Ice troll" }, + { id = "npc.trollrescue_icetroll_melee4", wiki = "Ice troll" }, + { id = "npc.trollrescue_icetroll_melee5", wiki = "Ice troll" }, + { id = "npc.trollrescue_icetroll_melee6", wiki = "Ice troll" }, + { id = "npc.trollrescue_icetroll_melee7", wiki = "Ice troll" }, + { id = "npc.trollromance_arrg", wiki = "Arrg" }, + { id = "npc.trollromance_arrg_attackable", wiki = "Arrg" }, + { id = "npc.trollromance_ice_wolf_1", wiki = "Ice wolf" }, + { id = "npc.trollromance_ice_wolf_1b", wiki = "Ice wolf" }, + { id = "npc.trollromance_ice_wolf_2", wiki = "Ice wolf" }, + { id = "npc.trollromance_icetroll_melee1", wiki = "Ice troll" }, + { id = "npc.trollromance_icetroll_melee2", wiki = "Ice troll" }, + { id = "npc.trollromance_icetroll_melee3", wiki = "Ice troll" }, + { id = "npc.trollromance_icetroll_melee4", wiki = "Ice troll" }, + { id = "npc.trollromance_icetroll_melee5", wiki = "Ice troll" }, + { id = "npc.trollromance_icetroll_melee6", wiki = "Ice troll" }, + { id = "npc.trollromance_icetroll_melee7", wiki = "Ice troll" }, + { id = "npc.trr_pirate_captain", wiki = "Black Eye Bethel" }, + { id = "npc.ttrek2_nail_beast_1", wiki = "Nail beast" }, + { id = "npc.ttrek2_nail_beast_2", wiki = "Nail beast" }, + { id = "npc.ttrek2_nail_beast_3", wiki = "Nail beast" }, + { id = "npc.ttrek2_zombie_diff_1_ver_1_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_1_ver_1_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_1_ver_1_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_1_ver_1_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_1_ver_2_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_1_ver_2_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_1_ver_2_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_1_ver_2_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_2_ver_1_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_2_ver_1_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_2_ver_1_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_2_ver_1_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_2_ver_2_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_2_ver_2_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_2_ver_2_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_2_ver_2_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_3_ver_1_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_3_ver_1_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_3_ver_1_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_3_ver_1_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_3_ver_2_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_3_ver_2_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_3_ver_2_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_3_ver_2_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_4_ver_1_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_4_ver_1_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_4_ver_1_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_4_ver_1_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_4_ver_2_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_4_ver_2_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_4_ver_2_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_4_ver_2_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_5_ver_1_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_5_ver_1_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_5_ver_1_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_5_ver_1_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_5_ver_2_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_5_ver_2_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_5_ver_2_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_5_ver_2_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_6_ver_1_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_6_ver_1_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_6_ver_1_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_6_ver_1_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_6_ver_2_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_6_ver_2_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_6_ver_2_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_6_ver_2_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_7_ver_1_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_7_ver_1_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_7_ver_1_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_7_ver_1_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_7_ver_2_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_7_ver_2_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_7_ver_2_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_7_ver_2_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_8_ver_1_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_8_ver_1_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_8_ver_1_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_8_ver_1_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_8_ver_2_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_8_ver_2_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_8_ver_2_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_8_ver_2_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_9_ver_1_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_9_ver_1_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_9_ver_1_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_9_ver_1_4", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_9_ver_2_1", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_9_ver_2_2", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_9_ver_2_3", wiki = "Undead Lumberjack" }, + { id = "npc.ttrek2_zombie_diff_9_ver_2_4", wiki = "Undead Lumberjack" }, + { id = "npc.twilight_cultist_acolyte_f_1", wiki = "Emissary Acolyte" }, + { id = "npc.twilight_cultist_acolyte_f_2", wiki = "Emissary Acolyte" }, + { id = "npc.twilight_cultist_acolyte_f_3", wiki = "Emissary Acolyte" }, + { id = "npc.twilight_cultist_acolyte_m_1", wiki = "Emissary Acolyte" }, + { id = "npc.twilight_cultist_acolyte_m_2", wiki = "Emissary Acolyte" }, + { id = "npc.twilight_cultist_acolyte_m_3", wiki = "Emissary Acolyte" }, + { id = "npc.twilight_cultist_ascended_f_1", wiki = "Emissary Ascended" }, + { id = "npc.twilight_cultist_ascended_f_2", wiki = "Emissary Ascended" }, + { id = "npc.twilight_cultist_ascended_f_3", wiki = "Emissary Ascended" }, + { id = "npc.twilight_cultist_ascended_f_4", wiki = "Emissary Ascended" }, + { id = "npc.twilight_cultist_ascended_m_1", wiki = "Emissary Ascended" }, + { id = "npc.twilight_cultist_ascended_m_2", wiki = "Emissary Ascended" }, + { id = "npc.twilight_cultist_ascended_m_3", wiki = "Emissary Ascended" }, + { id = "npc.twilight_cultist_ascended_m_4", wiki = "Emissary Ascended" }, + { id = "npc.twilight_cultist_chosen_f_1", wiki = "Emissary Chosen" }, + { id = "npc.twilight_cultist_chosen_f_2", wiki = "Emissary Chosen" }, + { id = "npc.twilight_cultist_chosen_f_3", wiki = "Emissary Chosen" }, + { id = "npc.twilight_cultist_chosen_m_1", wiki = "Emissary Chosen" }, + { id = "npc.twilight_cultist_chosen_m_2", wiki = "Emissary Chosen" }, + { id = "npc.twilight_cultist_chosen_m_3", wiki = "Emissary Chosen" }, + { id = "npc.twocats_kbd_cutscene", wiki = "King Black Dragon" }, + { id = "npc.typhor", wiki = "Typhor" }, + { id = "npc.typhor_cutscene", wiki = "Typhor" }, + { id = "npc.tzhaar_fightcave_swarm_1a", wiki = "Tz-Kih" }, + { id = "npc.tzhaar_fightcave_swarm_1b", wiki = "Tz-Kih" }, + { id = "npc.tzhaar_fightcave_swarm_2a", wiki = "Tz-Kek" }, + { id = "npc.tzhaar_fightcave_swarm_2b", wiki = "Tz-Kek" }, + { id = "npc.tzhaar_fightcave_swarm_2spawn", wiki = "Tz-Kek" }, + { id = "npc.tzhaar_fightcave_swarm_3a", wiki = "Tok-Xil" }, + { id = "npc.tzhaar_fightcave_swarm_3b", wiki = "Tok-Xil" }, + { id = "npc.tzhaar_fightcave_swarm_4a", wiki = "Yt-MejKot" }, + { id = "npc.tzhaar_fightcave_swarm_4b", wiki = "Yt-MejKot" }, + { id = "npc.tzhaar_fightcave_swarm_5a", wiki = "Ket-Zek" }, + { id = "npc.tzhaar_fightcave_swarm_5b", wiki = "Ket-Zek" }, + { id = "npc.tzhaar_fightcave_swarm_boss", wiki = "TzTok-Jad" }, + { id = "npc.tzhaar_fightpit_swarm_1a", wiki = "Tz-Kih" }, + { id = "npc.tzhaar_fightpit_swarm_1b", wiki = "Tz-Kih" }, + { id = "npc.tzhaar_fightpit_swarm_2a", wiki = "Tz-Kek" }, + { id = "npc.tzhaar_fightpit_swarm_2b", wiki = "Tz-Kek" }, + { id = "npc.tzhaar_fightpit_swarm_3a", wiki = "Tok-Xil" }, + { id = "npc.tzhaar_fightpit_swarm_3b", wiki = "Tok-Xil" }, + { id = "npc.ugthanki", wiki = "Ugthanki" }, + { id = "npc.ullek_locust", wiki = "Locust" }, + { id = "npc.vampire_flyer", wiki = "Feral Vampyre" }, + { id = "npc.vampire_juve", wiki = "Feral Vampyre" }, + { id = "npc.vampire_leech", wiki = "Leech" }, + { id = "npc.varlamore_dark_jaguar", wiki = "Black jaguar" }, + { id = "npc.varlamore_jaguar", wiki = "Jaguar" }, + { id = "npc.varlamore_jaguar_cub", wiki = "Jaguar cub" }, + { id = "npc.varlamore_mugger", wiki = "Mugger" }, + { id = "npc.varlamore_ocelot_1", wiki = "Ocelot" }, + { id = "npc.varlamore_ocelot_2", wiki = "Ocelot" }, + { id = "npc.varlamore_ocelot_3", wiki = "Ocelot" }, + { id = "npc.varlamore_scorpion_savannah", wiki = "Scorpion" }, + { id = "npc.varlamore_snake_rainforest", wiki = "Snake" }, + { id = "npc.varlamore_snake_savannah", wiki = "Snake" }, + { id = "npc.varlamore_white_jaguar", wiki = "White jaguar" }, + { id = "npc.venenatis", wiki = "Venenatis" }, + { id = "npc.venenatis_singles", wiki = "Spindel" }, + { id = "npc.venenatis_spiderling", wiki = "Venenatis' Spiderling (monster)" }, + { id = "npc.verzik_death_bat", wiki = "Verzik Vitur" }, + { id = "npc.verzik_death_bat_hard", wiki = "Verzik Vitur" }, + { id = "npc.verzik_death_bat_story", wiki = "Verzik Vitur" }, + { id = "npc.verzik_phase2", wiki = "Verzik Vitur" }, + { id = "npc.verzik_phase2_hard", wiki = "Verzik Vitur" }, + { id = "npc.verzik_phase2_story", wiki = "Verzik Vitur" }, + { id = "npc.verzik_phase2_to3_transition", wiki = "Verzik Vitur" }, + { id = "npc.verzik_phase2_to3_transition_hard", wiki = "Verzik Vitur" }, + { id = "npc.verzik_phase2_to3_transition_story", wiki = "Verzik Vitur" }, + { id = "npc.verzik_phase3", wiki = "Verzik Vitur" }, + { id = "npc.verzik_phase3_hard", wiki = "Verzik Vitur" }, + { id = "npc.verzik_phase3_story", wiki = "Verzik Vitur" }, + { id = "npc.vetion", wiki = "Vet'ion" }, + { id = "npc.vetion_2", wiki = "Vet'ion" }, + { id = "npc.vetion_2_single", wiki = "Calvar'ion" }, + { id = "npc.vetion_single", wiki = "Calvar'ion" }, + { id = "npc.viking_draugen", wiki = "The Draugen" }, + { id = "npc.viking_enemy1", wiki = "Koschei the deathless" }, + { id = "npc.viking_enemy2", wiki = "Koschei the deathless" }, + { id = "npc.viking_enemy3", wiki = "Koschei the deathless" }, + { id = "npc.viking_wolf", wiki = "Wolf" }, + { id = "npc.vikingexile_basilisk_superior", wiki = "Monstrous Basilisk (The Fremennik Exiles)" }, + { id = "npc.vikingexile_basilisk_superior_huntplayer", wiki = "Monstrous Basilisk (The Fremennik Exiles)" }, + { id = "npc.vikingexile_koschei_battle", wiki = "Koschei the deathless" }, + { id = "npc.vikingexile_youngling", wiki = "Basilisk Youngling" }, + { id = "npc.vmq2_cultist_f_1", wiki = "Cultist" }, + { id = "npc.vmq2_cultist_f_2", wiki = "Cultist" }, + { id = "npc.vmq2_cultist_f_3", wiki = "Cultist" }, + { id = "npc.vmq2_cultist_m_1", wiki = "Cultist" }, + { id = "npc.vmq2_cultist_m_2", wiki = "Cultist" }, + { id = "npc.vmq2_cultist_m_3", wiki = "Cultist" }, + { id = "npc.vmq3_ruins_frost_nagua", wiki = "Frost Nagua" }, + { id = "npc.vmq3_tower_twilight_magic_variant_1", wiki = "Emissary Conjurer" }, + { id = "npc.vmq3_tower_twilight_magic_variant_2", wiki = "Emissary Conjurer" }, + { id = "npc.vmq3_tower_twilight_melee_variant_1a", wiki = "Emissary Brawler" }, + { id = "npc.vmq3_tower_twilight_melee_variant_1b", wiki = "Emissary Brawler" }, + { id = "npc.vmq3_tower_twilight_melee_variant_2a", wiki = "Emissary Brawler" }, + { id = "npc.vmq3_tower_twilight_melee_variant_2b", wiki = "Emissary Brawler" }, + { id = "npc.vmq4_crypt_attacker_ranged_variant_1", wiki = "Emissary Archer" }, + { id = "npc.vmq4_crypt_attacker_ranged_variant_2", wiki = "Emissary Archer" }, + { id = "npc.vmq4_crypt_ennius_boss", wiki = "Ennius Tullus" }, + { id = "npc.vmq4_crypt_ennius_boss_noops", wiki = "Ennius Tullus" }, + { id = "npc.vmq4_crypt_metzli_noops", wiki = "Metzli, Teokan of Ranul" }, + { id = "npc.vmq4_itzla_prisoner_cs", wiki = "Prince Itzla Arkan" }, + { id = "npc.vmq4_keystone_chamber_boss_magic_cs", wiki = "Lucius" }, + { id = "npc.vmq4_keystone_chamber_boss_ranged_cs", wiki = "Chimalli" }, + { id = "npc.vmq4_metzli_boss", wiki = "Metzli, Teokan of Ranul" }, + { id = "npc.vmq4_temple_guard_boss_fight", wiki = "Emissary Enforcer" }, + { id = "npc.vorkath_spawn", wiki = "Zombified Spawn" }, + { id = "npc.vorkath_spawn_quest", wiki = "Zombified Spawn" }, + { id = "npc.vyrewatch_elite_1", wiki = "Vyrewatch Sentinel", note = "Yes (without [[Vyre_noble_clothing" }, + { id = "npc.vyrewatch_elite_2", wiki = "Vyrewatch Sentinel", note = "Yes (without [[Vyre_noble_clothing" }, + { id = "npc.vyrewatch_elite_3", wiki = "Vyrewatch Sentinel", note = "Yes (without [[Vyre_noble_clothing" }, + { id = "npc.vyrewatch_elite_4", wiki = "Vyrewatch Sentinel", note = "Yes (without [[Vyre_noble_clothing" }, + { id = "npc.vyrewatch_elite_5", wiki = "Vyrewatch Sentinel", note = "Yes (without [[Vyre_noble_clothing" }, + { id = "npc.vyrewatch_elite_6", wiki = "Vyrewatch Sentinel", note = "Yes (without [[Vyre_noble_clothing" }, + { id = "npc.vyrewatch_elite_7", wiki = "Vyrewatch Sentinel", note = "Yes (without [[Vyre_noble_clothing" }, + { id = "npc.vyrewatch_elite_8", wiki = "Vyrewatch Sentinel", note = "Yes (without [[Vyre_noble_clothing" }, + { id = "npc.wanted_solus", wiki = "Solus Dellagar" }, + { id = "npc.wanted_solus_attackable", wiki = "Solus Dellagar" }, + { id = "npc.wanted_solus_for_cutscene", wiki = "Solus Dellagar" }, + { id = "npc.warguild_adamant_armour", wiki = "Animated Adamant Armour" }, + { id = "npc.warguild_black_armour", wiki = "Animated Black Armour" }, + { id = "npc.warguild_bronze_armour", wiki = "Animated Bronze Armour" }, + { id = "npc.warguild_cyclops1", wiki = "Cyclops" }, + { id = "npc.warguild_cyclops1_high", wiki = "Cyclops" }, + { id = "npc.warguild_cyclops2", wiki = "Cyclops" }, + { id = "npc.warguild_cyclops2_high", wiki = "Cyclops" }, + { id = "npc.warguild_cyclops3", wiki = "Cyclops" }, + { id = "npc.warguild_cyclops3_high", wiki = "Cyclops" }, + { id = "npc.warguild_cyclops4", wiki = "Cyclops" }, + { id = "npc.warguild_cyclops4_high", wiki = "Cyclops" }, + { id = "npc.warguild_cyclops5", wiki = "Cyclops" }, + { id = "npc.warguild_cyclops5_high", wiki = "Cyclops" }, + { id = "npc.warguild_cyclops6", wiki = "Cyclops" }, + { id = "npc.warguild_cyclops6_high", wiki = "Cyclops" }, + { id = "npc.warguild_iron_armour", wiki = "Animated Iron Armour" }, + { id = "npc.warguild_mithril_armour", wiki = "Animated Mithril Armour" }, + { id = "npc.warguild_rune_armour", wiki = "Animated Rune Armour" }, + { id = "npc.warguild_steel_armour", wiki = "Animated Steel Armour" }, + { id = "npc.waterfiend_strongholdcave", wiki = "Waterfiend" }, + { id = "npc.wbr_poisonspider", wiki = "Poison spider" }, + { id = "npc.wcguild_ent", wiki = "Ent" }, + { id = "npc.wgs_balance_elemental", wiki = "Balance Elemental" }, + { id = "npc.wgs_balance_elemental_cutscene", wiki = "Balance Elemental" }, + { id = "npc.wgs_balance_elemental_dying", wiki = "Balance Elemental" }, + { id = "npc.wgs_khazard_guard_1", wiki = "Elite Khazard Guard" }, + { id = "npc.wgs_khazard_guard_2", wiki = "Elite Khazard Guard" }, + { id = "npc.wgs_khazard_guard_3", wiki = "Elite Khazard Guard" }, + { id = "npc.wgs_khazard_guard_4", wiki = "Elite Khazard Guard" }, + { id = "npc.wgs_lucien_henchman_axe", wiki = "Mercenary axeman" }, + { id = "npc.wgs_lucien_henchman_axe_2", wiki = "Mercenary axeman" }, + { id = "npc.whisperer", wiki = "The Whisperer" }, + { id = "npc.whisperer_melee", wiki = "The Whisperer" }, + { id = "npc.whisperer_melee_quest", wiki = "The Whisperer" }, + { id = "npc.whisperer_quest", wiki = "The Whisperer" }, + { id = "npc.whitewolf", wiki = "White wolf" }, + { id = "npc.whitewolf_sentry", wiki = "White wolf" }, + { id = "npc.wild_cave_ankou", wiki = "Ankou" }, + { id = "npc.wild_cave_black_demon", wiki = "Black demon" }, + { id = "npc.wild_cave_black_demon2", wiki = "Black demon" }, + { id = "npc.wild_cave_black_demon3", wiki = "Black demon" }, + { id = "npc.wild_cave_black_dragon", wiki = "Black dragon" }, + { id = "npc.wild_cave_black_dragon2", wiki = "Black dragon" }, + { id = "npc.wild_cave_black_dragon3", wiki = "Black dragon" }, + { id = "npc.wild_cave_cyclops", wiki = "Revenant cyclops" }, + { id = "npc.wild_cave_darkbeast", wiki = "Revenant dark beast" }, + { id = "npc.wild_cave_demon", wiki = "Revenant demon" }, + { id = "npc.wild_cave_dragon", wiki = "Revenant dragon" }, + { id = "npc.wild_cave_goblin", wiki = "Revenant goblin" }, + { id = "npc.wild_cave_greater_demon", wiki = "Greater demon" }, + { id = "npc.wild_cave_greater_demon2", wiki = "Greater demon" }, + { id = "npc.wild_cave_greater_demon3", wiki = "Greater demon" }, + { id = "npc.wild_cave_green_dragon", wiki = "Green dragon" }, + { id = "npc.wild_cave_green_dragon2", wiki = "Green dragon" }, + { id = "npc.wild_cave_green_dragon3", wiki = "Green dragon" }, + { id = "npc.wild_cave_hell_hound", wiki = "Hellhound" }, + { id = "npc.wild_cave_hellhound", wiki = "Revenant hellhound" }, + { id = "npc.wild_cave_hobgoblin", wiki = "Revenant hobgoblin" }, + { id = "npc.wild_cave_icegiant", wiki = "Ice giant" }, + { id = "npc.wild_cave_icegiant2", wiki = "Ice giant" }, + { id = "npc.wild_cave_icegiant3", wiki = "Ice giant" }, + { id = "npc.wild_cave_imp", wiki = "Revenant imp" }, + { id = "npc.wild_cave_knight", wiki = "Revenant knight" }, + { id = "npc.wild_cave_lesser_demon", wiki = "Lesser demon" }, + { id = "npc.wild_cave_lesser_demon2", wiki = "Lesser demon" }, + { id = "npc.wild_cave_lesser_demon3", wiki = "Lesser demon" }, + { id = "npc.wild_cave_ork", wiki = "Revenant ork" }, + { id = "npc.wild_cave_pyrefiend", wiki = "Revenant pyrefiend" }, + { id = "npc.wild_cave_superior", wiki = "Revenant maledictus" }, + { id = "npc.wilderness_ankou", wiki = "Ankou" }, + { id = "npc.wilderness_bandit", wiki = "Bandit" }, + { id = "npc.wilderness_chaos_druid", wiki = "Elder Chaos druid" }, + { id = "npc.wilderness_dark_warrior", wiki = "Dark warrior" }, + { id = "npc.wilderness_hill_giant", wiki = "Hill Giant" }, + { id = "npc.wilderness_hill_giant2", wiki = "Hill Giant" }, + { id = "npc.wilderness_hill_giant3", wiki = "Hill Giant" }, + { id = "npc.wilderness_mammoth", wiki = "Mammoth" }, + { id = "npc.wildy_zombie_pirate_1", wiki = "Zombie pirate" }, + { id = "npc.wildy_zombie_pirate_2", wiki = "Zombie pirate" }, + { id = "npc.wildy_zombie_pirate_3", wiki = "Zombie pirate" }, + { id = "npc.wildy_zombie_pirate_4", wiki = "Zombie pirate" }, + { id = "npc.wildy_zombie_pirate_5", wiki = "Zombie pirate" }, + { id = "npc.wolf", wiki = "Wolf" }, + { id = "npc.wolfpack_leader", wiki = "Big Wolf" }, + { id = "npc.wolfpack_leader_whiter", wiki = "Big Wolf" }, + { id = "npc.wyvern_long_tailed", wiki = "Long-tailed Wyvern" }, + { id = "npc.wyvern_spitting", wiki = "Spitting Wyvern" }, + { id = "npc.wyvern_taloned", wiki = "Taloned Wyvern" }, + { id = "npc.xarpus_death_hard", wiki = "Xarpus" }, + { id = "npc.yama", wiki = "Yama" }, + { id = "npc.yama_judge_of_yama", wiki = "Judge of Yama" }, + { id = "npc.yama_throne_occupied", wiki = "Yama" }, + { id = "npc.yama_throne_unoccupied", wiki = "Yama" }, + { id = "npc.young_dark_wizard", wiki = "Dark wizard" }, + { id = "npc.zalcano", wiki = "Zalcano" }, + { id = "npc.zalcano_weak", wiki = "Zalcano" }, + { id = "npc.zamorak_mage", wiki = "Battle mage", note = "Yes, unless wearing the same cape as the battle mage." }, + { id = "npc.zeah_lizardman_1_vn", wiki = "Lizardman" }, + { id = "npc.zeah_lizardman_1_vp", wiki = "Lizardman" }, + { id = "npc.zeah_lizardman_2_vn", wiki = "Lizardman" }, + { id = "npc.zeah_lizardman_2_vp", wiki = "Lizardman" }, + { id = "npc.zeah_lizardman_3_vn", wiki = "Lizardman brute" }, + { id = "npc.zeah_lizardman_3_vp", wiki = "Lizardman brute" }, + { id = "npc.zeah_lizardshaman_1", wiki = "Lizardman shaman" }, + { id = "npc.zeah_lizardshaman_2", wiki = "Lizardman shaman" }, + { id = "npc.zeah_mugger", wiki = "Mugger" }, + { id = "npc.zeah_sandcrab_inactive", wiki = "Sand Crab" }, + { id = "npc.zeah_sandcrab_small_inactive", wiki = "Sand Crab" }, + { id = "npc.zogre_1", wiki = "Zogre" }, + { id = "npc.zogre_2", wiki = "Zogre" }, + { id = "npc.zogre_3", wiki = "Zogre" }, + { id = "npc.zogre_4", wiki = "Zogre" }, + { id = "npc.zogre_5", wiki = "Zogre" }, + { id = "npc.zogre_6", wiki = "Zogre" }, + { id = "npc.zogre_dance_1", wiki = "Zogre" }, + { id = "npc.zogre_dance_2", wiki = "Zogre" }, + { id = "npc.zogre_dance_3", wiki = "Zogre" }, + { id = "npc.zogre_dance_4", wiki = "Zogre" }, + { id = "npc.zogre_drummer1", wiki = "Zogre" }, + { id = "npc.zogre_drummer2", wiki = "Skogre" }, + { id = "npc.zogre_skele", wiki = "Skogre" }, + { id = "npc.zogre_skele_dance", wiki = "Skogre" }, + { id = "npc.zogre_slash_bash", wiki = "Slash Bash" }, + { id = "npc.zombie2", wiki = "Zombie" }, + { id = "npc.zombie2_b", wiki = "Zombie" }, + { id = "npc.zombie2_c", wiki = "Zombie" }, + { id = "npc.zombie_armed", wiki = "Zombie" }, + { id = "npc.zombie_armed2", wiki = "Zombie" }, + { id = "npc.zombie_armed3", wiki = "Zombie" }, + { id = "npc.zombie_armed_city1", wiki = "Zombie" }, + { id = "npc.zombie_armed_city3", wiki = "Zombie" }, + { id = "npc.zombie_armed_sewer1", wiki = "Zombie" }, + { id = "npc.zombie_armed_sewer2", wiki = "Zombie" }, + { id = "npc.zombie_armed_sewer3", wiki = "Zombie" }, + { id = "npc.zombie_armed_sewer4", wiki = "Zombie" }, + { id = "npc.zombie_unarmed", wiki = "Zombie" }, + { id = "npc.zombie_unarmed2", wiki = "Zombie" }, + { id = "npc.zombie_unarmed3", wiki = "Zombie" }, + { id = "npc.zombie_unarmed4", wiki = "Zombie" }, + { id = "npc.zombie_unarmed5", wiki = "Zombie" }, + { id = "npc.zombie_unarmed6", wiki = "Zombie" }, + { id = "npc.zombie_unarmed_city1", wiki = "Zombie" }, + { id = "npc.zombie_unarmed_city3", wiki = "Zombie" }, + { id = "npc.zombie_unarmed_city6", wiki = "Zombie" }, + { id = "npc.zombie_unarmed_sewer1", wiki = "Zombie" }, + { id = "npc.zombie_unarmed_sewer2", wiki = "Zombie" }, + { id = "npc.zombie_unarmed_sewer3", wiki = "Zombie" }, + { id = "npc.zombie_unarmed_sewer4", wiki = "Zombie" }, + { id = "npc.zq_mainzombie1", wiki = "Nazastarool" }, + { id = "npc.zq_mainzombie2", wiki = "Nazastarool" }, + { id = "npc.zq_mainzombie3", wiki = "Nazastarool" }, + { id = "npc.zqskeleton_armed", wiki = "Undead one" }, + { id = "npc.zqskeleton_armed2", wiki = "Undead one" }, + { id = "npc.zqskeleton_unarmed", wiki = "Undead one" }, + { id = "npc.zqskeleton_unarmed2", wiki = "Undead one" }, + { id = "npc.zqzombie_armed", wiki = "Undead one" }, + { id = "npc.zqzombie_armed2", wiki = "Undead one" }, + { id = "npc.zqzombie_armed3", wiki = "Undead one" }, +] diff --git a/content/other/npc-aggression/src/test/kotlin/org/rsmod/content/other/npcaggression/NpcAggressionFileTest.kt b/content/other/npc-aggression/src/test/kotlin/org/rsmod/content/other/npcaggression/NpcAggressionFileTest.kt new file mode 100644 index 000000000..f6945d9dd --- /dev/null +++ b/content/other/npc-aggression/src/test/kotlin/org/rsmod/content/other/npcaggression/NpcAggressionFileTest.kt @@ -0,0 +1,37 @@ +package org.rsmod.content.other.npcaggression + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class NpcAggressionFileTest { + @Test + fun `parses entries with and without notes`() { + val file = + NpcAggressionFile.parse( + """ + npc = [ + { id = "npc.hill_giant", wiki = "Hill Giant" }, + { id = "npc.vyrewatch", wiki = "Vyrewatch", note = "Yes, unless wearing vyre noble clothing" }, + { id = "npc.guard", aggressive = false }, + ] + """ + .trimIndent() + ) + assertEquals(3, file.npc.size) + assertEquals("npc.hill_giant", file.npc[0].id) + assertTrue(file.npc[0].aggressive) + assertEquals("Vyrewatch", file.npc[1].wiki) + assertFalse(file.npc[2].aggressive) + } + + @Test + fun `generated and override resources load and name npcs`() { + val generated = NpcAggressionFile.loadResource(NpcAggressionFile.GENERATED_RESOURCE) + assertTrue(generated.npc.size > 1000, "expected the wiki list, got ${generated.npc.size}") + assertTrue(generated.npc.all { it.id.startsWith("npc.") }) + assertEquals(generated.npc.size, generated.npc.map { it.id }.toSet().size, "duplicate ids") + NpcAggressionFile.loadResource(NpcAggressionFile.OVERRIDES_RESOURCE) + } +} diff --git a/content/other/npc-combat-anims/build.gradle.kts b/content/other/npc-combat-anims/build.gradle.kts new file mode 100644 index 000000000..7ac63dc36 --- /dev/null +++ b/content/other/npc-combat-anims/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) + implementation(libs.jackson.databind) + implementation(libs.jackson.dataformat.toml) + implementation(libs.jackson.module.kotlin) +} diff --git a/content/other/npc-combat-anims/src/main/kotlin/org/rsmod/content/other/npccombatanims/NpcCombatAnimsFile.kt b/content/other/npc-combat-anims/src/main/kotlin/org/rsmod/content/other/npccombatanims/NpcCombatAnimsFile.kt new file mode 100644 index 000000000..800f9a278 --- /dev/null +++ b/content/other/npc-combat-anims/src/main/kotlin/org/rsmod/content/other/npccombatanims/NpcCombatAnimsFile.kt @@ -0,0 +1,50 @@ +package org.rsmod.content.other.npccombatanims + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.toml.TomlFactory +import com.fasterxml.jackson.module.kotlin.readValue +import com.fasterxml.jackson.module.kotlin.registerKotlinModule + +/** + * One npc's combat animation set as written by `tools/combat-anims`. Every field but [id] is + * optional: only the fields present are applied. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +data class NpcCombatAnimEntry( + val id: String, + val source: String? = null, + @JsonProperty("attack_anim") val attackAnim: String? = null, + @JsonProperty("attack_type") val attackType: String? = null, + @JsonProperty("attack_sound") val attackSound: Int? = null, + @JsonProperty("defend_anim") val defendAnim: String? = null, + @JsonProperty("defend_sound") val defendSound: Int? = null, + @JsonProperty("death_anim") val deathAnim: String? = null, + @JsonProperty("death_sound") val deathSound: Int? = null, + @JsonProperty("proj_travel") val projTravel: String? = null, + @JsonProperty("proj_type") val projType: String? = null, + @JsonProperty("attack_range") val attackRange: Int? = null, +) + +@JsonIgnoreProperties(ignoreUnknown = true) +data class NpcCombatAnimsFile(val npc: List = emptyList()) { + companion object { + /** Generated by `./gradlew :tools:combat-anims:dumpNpcCombatAnims`. */ + const val GENERATED_RESOURCE: String = "npc-combat-anims.toml" + + /** Hand-maintained corrections. Applied after the generated file and always win. */ + const val OVERRIDES_RESOURCE: String = "npc-combat-anims-overrides.toml" + + private val mapper: ObjectMapper = ObjectMapper(TomlFactory()).registerKotlinModule() + + fun loadResource(name: String): NpcCombatAnimsFile { + val stream = + NpcCombatAnimsFile::class.java.classLoader.getResourceAsStream(name) + ?: error("Missing resource: $name") + return stream.use { parse(it.readBytes().decodeToString()) } + } + + fun parse(toml: String): NpcCombatAnimsFile = mapper.readValue(toml) + } +} diff --git a/content/other/npc-combat-anims/src/main/kotlin/org/rsmod/content/other/npccombatanims/NpcCombatAnimsScript.kt b/content/other/npc-combat-anims/src/main/kotlin/org/rsmod/content/other/npccombatanims/NpcCombatAnimsScript.kt new file mode 100644 index 000000000..8650602e1 --- /dev/null +++ b/content/other/npc-combat-anims/src/main/kotlin/org/rsmod/content/other/npccombatanims/NpcCombatAnimsScript.kt @@ -0,0 +1,103 @@ +package org.rsmod.content.other.npccombatanims + +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.ParamMap +import dev.openrune.ServerCacheManager +import dev.openrune.TypedParamType +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.NpcServerType +import org.rsmod.api.config.refs.params +import org.rsmod.api.script.onGameStartup +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Gives npcs the combat animations the cache leaves them without. + * + * Npc-versus-player combat plays one `param.attack_anim` per npc, defaulting to the bare-handed + * punch, and only a few hundred npcs declare one. A guard with a sword punches, a hill giant + * punches with a human animation, and a wizard punches too. The same goes for the block and + * death animations, and for the attack type: with no `param.npc_attack_type`, an archer walks up + * and melees. + * + * `tools/combat-anims` works out what each npc should do - from the weapon and shield it is drawn + * holding, or from the animation family its ready animation belongs to - and writes the answer + * to [NpcCombatAnimsFile.GENERATED_RESOURCE]. This script applies that file, then + * [NpcCombatAnimsFile.OVERRIDES_RESOURCE] on top, to the cached npc types once at game startup. + * The generated entries never replace a value the npc's own data declares; overrides always do. + * Ranged and magic entries also raise the npc's attack range so it fights from a distance. Entries + * may carry the attack, block and death sounds too (`param.attack_sound`, `param.defend_sound`, + * `param.death_sound`) for families such as ghosts and spiders whose npcs the cache leaves mute. + */ +class NpcCombatAnimsScript : PluginScript() { + override fun ScriptContext.startup() { + onGameStartup { applyAll() } + } + + private fun applyAll() { + val generated = NpcCombatAnimsFile.loadResource(NpcCombatAnimsFile.GENERATED_RESOURCE) + val overrides = NpcCombatAnimsFile.loadResource(NpcCombatAnimsFile.OVERRIDES_RESOURCE) + val applied = apply(generated.npc, force = false) + val forced = apply(overrides.npc, force = true) + logger.info { "Applied combat animations to $applied npcs ($forced overrides)." } + } + + private fun apply(entries: List, force: Boolean): Int { + var applied = 0 + for (entry in entries) { + val id = entry.id.asRSCM(RSCMType.NPC) + val type = ServerCacheManager.getNpc(id) + if (type == null) { + logger.warn { "Npc not in cache, skipping: ${entry.id}" } + continue + } + if (type.apply(entry, force)) { + applied++ + } + } + return applied + } + + /** Returns `true` if anything changed. */ + private fun NpcServerType.apply(entry: NpcCombatAnimEntry, force: Boolean): Boolean { + val values = mutableMapOf() + fun put(param: TypedParamType<*>, value: Int?) { + if (value == null) { + return + } + if (!force && paramMap?.contains(param) == true) { + return + } + values[param.id] = value + } + put(params.attack_anim, entry.attackAnim?.asRSCM(RSCMType.SEQ)) + put(params.defend_anim, entry.defendAnim?.asRSCM(RSCMType.SEQ)) + put(params.death_anim, entry.deathAnim?.asRSCM(RSCMType.SEQ)) + put(params.npc_attack_type, entry.attackType?.asRSCM(RSCMType.CATEGORY)) + put(params.attack_sound, entry.attackSound) + put(params.defend_sound, entry.defendSound) + put(params.death_sound, entry.deathSound) + put(params.proj_travel, entry.projTravel?.asRSCM(RSCMType.SPOTANIM)) + put(params.proj_type, entry.projType?.asRSCM(RSCMType.PROJANIM)) + + var changed = false + if (values.isNotEmpty()) { + paramMap = ParamMap(primitiveMap = paramMap?.primitiveMap.orEmpty() + values) + changed = true + } + val range = entry.attackRange + if (range != null && (force || attackRange < range)) { + attackRange = range + if (maxRange < range) { + maxRange = range + } + changed = true + } + return changed + } + + private companion object { + private val logger = InlineLogger() + } +} diff --git a/content/other/npc-combat-anims/src/main/resources/npc-combat-anims-overrides.toml b/content/other/npc-combat-anims/src/main/resources/npc-combat-anims-overrides.toml new file mode 100644 index 000000000..329cace76 --- /dev/null +++ b/content/other/npc-combat-anims/src/main/resources/npc-combat-anims-overrides.toml @@ -0,0 +1,38 @@ +# Hand corrections to npc-combat-anims.toml. Same format; these are applied after the generated +# file and replace whatever the npc has, so use them for the npcs the generator gets wrong. +# +# Fields (all optional but id): attack_anim, defend_anim, death_anim (seq.*), attack_type +# (category.attacktype_stab|slash|crush|standard|magic), attack_sound, defend_sound, death_sound +# (synth ids), proj_travel (spotanim.*), proj_type (projanim.*), attack_range (tiles). +# +# Example: +# { id = "npc.wizard", attack_anim = "seq.human_caststrike", proj_travel = "spotanim.firestrike_travel" }, + +npc = [ + # Dark wizards cast their own spells (content/generic/generic-npcs DarkWizardScript); these keep + # the generic caster data on the right element for anything that reads the params directly. + { id = "npc.young_dark_wizard", proj_travel = "spotanim.waterstrike_travel" }, + { id = "npc.fai_dark_wizard_young_1", proj_travel = "spotanim.waterstrike_travel" }, + { id = "npc.fai_dark_wizard_young_2", proj_travel = "spotanim.waterstrike_travel" }, + { id = "npc.qip_ds_young_dark_wizard1", proj_travel = "spotanim.waterstrike_travel" }, + { id = "npc.qip_ds_young_dark_wizard2", proj_travel = "spotanim.waterstrike_travel" }, + { id = "npc.bearded_dark_wizard", proj_travel = "spotanim.earthstrike_travel" }, + { id = "npc.fai_dark_wizard_old_1", proj_travel = "spotanim.earthstrike_travel" }, + { id = "npc.fai_dark_wizard_old_2", proj_travel = "spotanim.earthstrike_travel" }, + { id = "npc.qip_ds_young_dark_wizard3", proj_travel = "spotanim.earthstrike_travel" }, + { id = "npc.qip_ds_young_dark_wizard4", proj_travel = "spotanim.earthstrike_travel" }, + # The Restless Ghost's skeleton in the Wizards' Tower basement: its def carries human idle + # anims, so the generator leaves it silent; it swings a sword with the updated skeleton set + # every other skeleton uses. + { id = "npc.skull_skeleton", attack_anim = "seq.skeleton_update_attack_weapon", attack_type = "category.attacktype_slash", attack_sound = 776, defend_anim = "seq.skeleton_update_defend", defend_sound = 779, death_anim = "seq.skeleton_update_death", death_sound = 777 }, + # Barrows brothers whose animation sets do not follow a family the generator understands. + { id = "npc.barrows_guthan", attack_anim = "seq.barrow_guthan_crush", attack_type = "category.attacktype_crush", attack_sound = 2562, defend_anim = "seq.barrow_guthan_defend" }, + { id = "npc.barrows_torag", attack_anim = "seq.barrow_torag_crush", attack_type = "category.attacktype_crush", attack_sound = 2567, defend_anim = "seq.human_blunt_block" }, + # The Dragon Slayer II ghosts share the ghost animation family but have their own voice + # (dragonslayer_ghost_attack/hit/death on the OSRS wiki sound id list). + { id = "npc.dragonslayer_ghost_1_key", attack_sound = 3755, defend_sound = 3757, death_sound = 3756 }, + { id = "npc.dragonslayer_ghost_2", attack_sound = 3755, defend_sound = 3757, death_sound = 3756 }, + { id = "npc.dragonslayer_ghost_3", attack_sound = 3755, defend_sound = 3757, death_sound = 3756 }, + { id = "npc.dragonslayer_ghost_4", attack_sound = 3755, defend_sound = 3757, death_sound = 3756 }, + { id = "npc.dragonslayer_ghost_5", attack_sound = 3755, defend_sound = 3757, death_sound = 3756 }, +] diff --git a/content/other/npc-combat-anims/src/main/resources/npc-combat-anims.toml b/content/other/npc-combat-anims/src/main/resources/npc-combat-anims.toml new file mode 100644 index 000000000..9d7ca2893 --- /dev/null +++ b/content/other/npc-combat-anims/src/main/resources/npc-combat-anims.toml @@ -0,0 +1,3267 @@ +# Combat animations for npcs the cache leaves silent - generated, do not edit by hand. +# +# Regenerate with: ./gradlew :tools:combat-anims:dumpNpcCombatAnims +# Hand corrections: npc-combat-anims-overrides.toml (same format; those always win). +# +# Each entry names the npc, where its animations came from (a held weapon, a spell caster, or the +# animation family of its ready animation) and the values the plugin sets at boot. Only fields +# present are applied, and only where the npc's own data does not already declare them. + +npc = [ + { id = "npc.abyssal_leech", source = "family:abyssal_leech_ready", attack_anim = "seq.abyssal_leech_attack", defend_anim = "seq.abyssal_leech_defend", death_anim = "seq.abyssal_leech_death" }, + { id = "npc.abyssal_pyramid", source = "family:abyssal_pyramid_ready", attack_anim = "seq.abyssal_pyramid_attack", defend_anim = "seq.abyssal_pyramid_defend", death_anim = "seq.abyssal_pyramid_death" }, + { id = "npc.abyssal_walker", source = "family:abyssal_walker_ready", attack_anim = "seq.abyssal_walker_attack", defend_anim = "seq.abyssal_walker_defend", death_anim = "seq.abyssal_walker_death" }, + { id = "npc.abyssalsire_scion", source = "family:abyssal_scion_idle", attack_anim = "seq.abyssal_scion_attack_melee", defend_anim = "seq.abyssal_scion_defend", death_anim = "seq.abyssal_scion_death" }, + { id = "npc.abyssalsire_sire_apocalypse", source = "family:sire_idle_four", death_anim = "seq.sire_death" }, + { id = "npc.abyssalsire_sire_panicking", source = "family:sire_idle_three", death_anim = "seq.sire_death" }, + { id = "npc.abyssalsire_sire_puppet", source = "family:sire_idle_two", death_anim = "seq.sire_death" }, + { id = "npc.abyssalsire_sire_stasis_awake", source = "family:sire_idle_one", death_anim = "seq.sire_death" }, + { id = "npc.abyssalsire_sire_wandering", source = "family:sire_idle_two", death_anim = "seq.sire_death" }, + { id = "npc.abyssalsire_spawn", source = "family:abyssal_spawn_idle", attack_anim = "seq.abyssal_spawn_attack", defend_anim = "seq.abyssal_spawn_defend", death_anim = "seq.abyssal_spawn_death" }, + { id = "npc.adamant_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.aggressive_black_knight", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.agrith_naar", source = "family:demon_update_ready", attack_anim = "seq.demon_update_attack", defend_anim = "seq.demon_update_defend", death_anim = "seq.demon_update_death" }, + { id = "npc.ahoy_undead_chicken", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.ahoy_undead_cow", source = "family:cow_just_ready_update", attack_anim = "seq.cow_attack", defend_anim = "seq.cow_block", death_anim = "seq.cow_death" }, + { id = "npc.air_wizard", source = "caster:obj.plainstaff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_stafforb_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.akd_barbarian_1", source = "weapon:model:11793", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.akd_barbarian_2", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.akd_barbarian_3", source = "weapon:model:11790", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.akd_barbarian_4", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.akd_barbarian_5", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.akd_barbarian_warlord", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.akd_egg_lizardman", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.akd_judge_of_yama_combat", source = "family:demon_update_ready", attack_anim = "seq.demon_update_attack", defend_anim = "seq.demon_update_defend", death_anim = "seq.demon_update_death" }, + { id = "npc.akd_settlement_ruins_assassin", source = "weapon:obj.dragon_dagger", attack_anim = "seq.human_ddagger_lunge", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_ddagger_block" }, + { id = "npc.akd_xamphur_combat", source = "family:npc_xamphur_combat_idle", attack_anim = "seq.npc_xamphur_combat_attack_grasp" }, + { id = "npc.akd_xamphur_combat_nohands", source = "family:npc_xamphur_combat_idle", attack_anim = "seq.npc_xamphur_combat_attack_grasp" }, + { id = "npc.akd_xamphur_hand_left", source = "family:npc_hand_left_idle", attack_anim = "seq.npc_hand_left_attack", death_anim = "seq.npc_hand_left_death" }, + { id = "npc.akd_xamphur_hand_right", source = "family:npc_hand_right_idle", attack_anim = "seq.npc_hand_right_attack", death_anim = "seq.npc_hand_right_death" }, + { id = "npc.akkha_enrage", source = "family:npc_akkha_idle_spear", death_anim = "seq.npc_akkha_death_spear" }, + { id = "npc.akkha_enrage_dummy", source = "family:npc_akkha_idle_spear", death_anim = "seq.npc_akkha_death_spear" }, + { id = "npc.akkha_enrage_initial", source = "family:npc_akkha_idle_spear", death_anim = "seq.npc_akkha_death_spear" }, + { id = "npc.akkha_enrage_spawn", source = "family:npc_akkha_idle_spear", death_anim = "seq.npc_akkha_death_spear" }, + { id = "npc.akkha_mage", source = "family:npc_akkha_idle_spear", death_anim = "seq.npc_akkha_death_spear" }, + { id = "npc.akkha_melee", source = "family:npc_akkha_idle_spear", death_anim = "seq.npc_akkha_death_spear" }, + { id = "npc.akkha_range", source = "family:npc_akkha_idle_spear", death_anim = "seq.npc_akkha_death_spear" }, + { id = "npc.akkha_shadow", source = "family:npc_akkha_idle_spear", death_anim = "seq.npc_akkha_death_spear" }, + { id = "npc.akkha_spawn", source = "family:npc_akkha_idle_spear", death_anim = "seq.npc_akkha_death_spear" }, + { id = "npc.al_kharid_warrior", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.aldarin_guard_f_1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.aldarin_guard_f_2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.aldarin_guard_m_2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.aldarin_guard_m_5", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.alomone_hazeel_cultist_2op", source = "weapon:obj.iron_dagger", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_sword_def" }, + { id = "npc.aluinus_combat", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.amoxliatl", source = "family:amoxliatl_idle", attack_anim = "seq.amoxliatl_attack", death_anim = "seq.amoxliatl_death" }, + { id = "npc.amoxliatl_quest", source = "family:amoxliatl_idle", attack_anim = "seq.amoxliatl_attack", death_anim = "seq.amoxliatl_death" }, + { id = "npc.ancient_wyvern", source = "family:fossil_npc_wyvern_flight_idle", defend_anim = "seq.fossil_npc_wyvern_flight_defend", death_anim = "seq.fossil_npc_wyvern_flight_death" }, + { id = "npc.araxxor", source = "family:npc_araxxor_01_idle_01", death_anim = "seq.npc_araxxor_01_death_01" }, + { id = "npc.araxxor_minion_egg_explode", source = "family:egg_araxyte_idle_01", death_anim = "seq.egg_araxyte_death_01" }, + { id = "npc.araxxor_minion_egg_mirrorback", source = "family:egg_araxyte_idle_01", death_anim = "seq.egg_araxyte_death_01" }, + { id = "npc.araxxor_minion_egg_venom", source = "family:egg_araxyte_idle_01", death_anim = "seq.egg_araxyte_death_01" }, + { id = "npc.araxxor_minion_explode", source = "family:npc_araxyte01_idle", attack_anim = "seq.npc_araxyte01_attack", death_anim = "seq.npc_araxyte01_death" }, + { id = "npc.araxxor_minion_mirrorback", source = "family:npc_araxyte01_idle", attack_anim = "seq.npc_araxyte01_attack", death_anim = "seq.npc_araxyte01_death" }, + { id = "npc.araxxor_minion_venom", source = "family:npc_araxyte01_idle", attack_anim = "seq.npc_araxyte01_attack", death_anim = "seq.npc_araxyte01_death" }, + { id = "npc.araxyte_large", source = "family:npc_araxyte01_idle", attack_anim = "seq.npc_araxyte01_attack", death_anim = "seq.npc_araxyte01_death" }, + { id = "npc.araxyte_large_bigwander", source = "family:npc_araxyte01_idle", attack_anim = "seq.npc_araxyte01_attack", death_anim = "seq.npc_araxyte01_death" }, + { id = "npc.araxyte_normal", source = "family:npc_araxyte01_idle", attack_anim = "seq.npc_araxyte01_attack", death_anim = "seq.npc_araxyte01_death" }, + { id = "npc.araxyte_normal_bigwander", source = "family:npc_araxyte01_idle", attack_anim = "seq.npc_araxyte01_attack", death_anim = "seq.npc_araxyte01_death" }, + { id = "npc.arceuus_reanimated_abyssal", source = "family:abyssal_ready", attack_anim = "seq.abyssal_attack", death_anim = "seq.abyssal_death" }, + { id = "npc.arceuus_reanimated_aviansie", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.arceuus_reanimated_bear", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.arceuus_reanimated_bloodveld", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.arceuus_reanimated_chaosdruid", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.arceuus_reanimated_dagannoth", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.arceuus_reanimated_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.arceuus_reanimated_dog", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.arceuus_reanimated_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.arceuus_reanimated_giant", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.arceuus_reanimated_goblin", source = "family:goblin_ready", attack_anim = "seq.goblin_attack_unarmed", defend_anim = "seq.goblin_block", death_anim = "seq.goblin_death" }, + { id = "npc.arceuus_reanimated_hellhound", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.arceuus_reanimated_horror", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.arceuus_reanimated_imp", source = "family:imp_ready", attack_anim = "seq.imp_attack", defend_anim = "seq.imp_block", death_anim = "seq.imp_death" }, + { id = "npc.arceuus_reanimated_kalphite", source = "family:kalphite_update_lord_ready", defend_anim = "seq.kalphite_update_defend", death_anim = "seq.kalphite_update_lord_death" }, + { id = "npc.arceuus_reanimated_minotaur", source = "family:mino_ready", attack_anim = "seq.mino_attack", defend_anim = "seq.mino_parry", death_anim = "seq.mino_death" }, + { id = "npc.arceuus_reanimated_monkey", source = "family:monkey_ready", attack_anim = "seq.monkey_attack", defend_anim = "seq.monkey_block", death_anim = "seq.monkey_death" }, + { id = "npc.arceuus_reanimated_ogre", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.arceuus_reanimated_scorpion", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.arceuus_reanimated_troll", source = "family:troll_ready_sword+shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.arceuus_reanimated_tzhaar", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.arceuus_reanimated_unicorn", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.arcquest_ghost1", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.arcquest_ghost2", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.arcquest_soul", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.ardougne_archer", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.ardougne_guard", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.ardougne_guard_variant01", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.ardougne_paladin2", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.arena_bouncer", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.arena_guard1", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.arena_guard4", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.arena_guard7", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.arena_ogre", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.arena_scorpion", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.auburnvale_guard_f_1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.auburnvale_guard_f_2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.auburnvale_guard_m_1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.auburnvale_guard_m_2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.avium_giant1", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.avium_giant2", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.avium_giant3", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.baby_black_dragon_nohunt", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.baby_black_dragon_strongholdcave", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.baby_blue_dragon_tapoyauik_1", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.baby_blue_dragon_tapoyauik_2", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.babybluedragon", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.babybluedragon2", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.babybluedragon3", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.babygreendragon1", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.babygreendragon2", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.babygreendragon3", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.babyreddragon", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.babyreddragon2", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.babyreddragon3", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.barbarian", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.barbarian_guard", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.barbassault_pen_fighter_lv1", source = "family:barbassault_penance_fighter_ready", attack_anim = "seq.barbassault_penance_fighter_attack", defend_anim = "seq.barbassault_penance_fighter_defend", death_anim = "seq.barbassault_penance_fighter_death" }, + { id = "npc.barbassault_pen_fighter_lv2", source = "family:barbassault_penance_fighter_ready", attack_anim = "seq.barbassault_penance_fighter_attack", defend_anim = "seq.barbassault_penance_fighter_defend", death_anim = "seq.barbassault_penance_fighter_death" }, + { id = "npc.barbassault_pen_fighter_lv3", source = "family:barbassault_penance_fighter_ready", attack_anim = "seq.barbassault_penance_fighter_attack", defend_anim = "seq.barbassault_penance_fighter_defend", death_anim = "seq.barbassault_penance_fighter_death" }, + { id = "npc.barbassault_pen_fighter_lv4", source = "family:barbassault_penance_fighter_ready", attack_anim = "seq.barbassault_penance_fighter_attack", defend_anim = "seq.barbassault_penance_fighter_defend", death_anim = "seq.barbassault_penance_fighter_death" }, + { id = "npc.barbassault_pen_fighter_lv5", source = "family:barbassault_penance_fighter_ready", attack_anim = "seq.barbassault_penance_fighter_attack", defend_anim = "seq.barbassault_penance_fighter_defend", death_anim = "seq.barbassault_penance_fighter_death" }, + { id = "npc.barbassault_pen_fighter_lv6", source = "family:barbassault_penance_fighter_ready", attack_anim = "seq.barbassault_penance_fighter_attack", defend_anim = "seq.barbassault_penance_fighter_defend", death_anim = "seq.barbassault_penance_fighter_death" }, + { id = "npc.barbassault_pen_fighter_lv7", source = "family:barbassault_penance_fighter_ready", attack_anim = "seq.barbassault_penance_fighter_attack", defend_anim = "seq.barbassault_penance_fighter_defend", death_anim = "seq.barbassault_penance_fighter_death" }, + { id = "npc.barbassault_pen_fighter_lv8", source = "family:barbassault_penance_fighter_ready", attack_anim = "seq.barbassault_penance_fighter_attack", defend_anim = "seq.barbassault_penance_fighter_defend", death_anim = "seq.barbassault_penance_fighter_death" }, + { id = "npc.barbassault_pen_fighter_lv9", source = "family:barbassault_penance_fighter_ready", attack_anim = "seq.barbassault_penance_fighter_attack", defend_anim = "seq.barbassault_penance_fighter_defend", death_anim = "seq.barbassault_penance_fighter_death" }, + { id = "npc.barbassault_pen_queen_new", source = "family:barbassault_penacne_queen_new_ready", attack_anim = "seq.barbassault_penacne_queen_new_attack", death_anim = "seq.barbassault_penacne_queen_new_death" }, + { id = "npc.barbassault_pen_queen_spawn", source = "family:barbassault_penance_queen_spawn_ready", attack_anim = "seq.barbassault_penance_queen_spawn_attack", defend_anim = "seq.barbassault_penance_queen_spawn_defend", death_anim = "seq.barbassault_penance_queen_spawn_death" }, + { id = "npc.barbassault_pen_ranger_lv1", source = "family:barbassault_penance_ranger_ready", attack_anim = "seq.barbassault_penance_ranger_attack", defend_anim = "seq.barbassault_penance_ranger_defend", death_anim = "seq.barbassault_penance_ranger_death" }, + { id = "npc.barbassault_pen_ranger_lv2", source = "family:barbassault_penance_ranger_ready", attack_anim = "seq.barbassault_penance_ranger_attack", defend_anim = "seq.barbassault_penance_ranger_defend", death_anim = "seq.barbassault_penance_ranger_death" }, + { id = "npc.barbassault_pen_ranger_lv3", source = "family:barbassault_penance_ranger_ready", attack_anim = "seq.barbassault_penance_ranger_attack", defend_anim = "seq.barbassault_penance_ranger_defend", death_anim = "seq.barbassault_penance_ranger_death" }, + { id = "npc.barbassault_pen_ranger_lv4", source = "family:barbassault_penance_ranger_ready", attack_anim = "seq.barbassault_penance_ranger_attack", defend_anim = "seq.barbassault_penance_ranger_defend", death_anim = "seq.barbassault_penance_ranger_death" }, + { id = "npc.barbassault_pen_ranger_lv5", source = "family:barbassault_penance_ranger_ready", attack_anim = "seq.barbassault_penance_ranger_attack", defend_anim = "seq.barbassault_penance_ranger_defend", death_anim = "seq.barbassault_penance_ranger_death" }, + { id = "npc.barbassault_pen_ranger_lv6", source = "family:barbassault_penance_ranger_ready", attack_anim = "seq.barbassault_penance_ranger_attack", defend_anim = "seq.barbassault_penance_ranger_defend", death_anim = "seq.barbassault_penance_ranger_death" }, + { id = "npc.barbassault_pen_ranger_lv7", source = "family:barbassault_penance_ranger_ready", attack_anim = "seq.barbassault_penance_ranger_attack", defend_anim = "seq.barbassault_penance_ranger_defend", death_anim = "seq.barbassault_penance_ranger_death" }, + { id = "npc.barbassault_pen_ranger_lv8", source = "family:barbassault_penance_ranger_ready", attack_anim = "seq.barbassault_penance_ranger_attack", defend_anim = "seq.barbassault_penance_ranger_defend", death_anim = "seq.barbassault_penance_ranger_death" }, + { id = "npc.barbassault_pen_ranger_lv9", source = "family:barbassault_penance_ranger_ready", attack_anim = "seq.barbassault_penance_ranger_attack", defend_anim = "seq.barbassault_penance_ranger_defend", death_anim = "seq.barbassault_penance_ranger_death" }, + { id = "npc.barrows_ahrim", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.barrows_bloodworm", source = "family:bloodworm_ready", attack_anim = "seq.bloodworm_attack", defend_anim = "seq.bloodworm_block", death_anim = "seq.bloodworm_death" }, + { id = "npc.barrows_dharok", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.barrows_giantrat", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.barrows_giantrat2", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.barrows_giantrat3", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.barrows_giantspider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.barrows_guthan", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.barrows_rat", source = "family:rat_ready", attack_anim = "seq.rat_attack", defend_anim = "seq.rat_block", death_anim = "seq.rat_death" }, + { id = "npc.barrows_skeleton_armed", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.barrows_skeleton_armed2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.barrows_skeleton_unarmed", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.barrows_skeleton_unarmed2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.barrows_spider", source = "family:small_spider_update_ready", attack_anim = "seq.small_spider_update_attack", attack_sound = 3604, defend_anim = "seq.small_spider_update_defend", defend_sound = 3609, death_anim = "seq.small_spider_update_death", death_sound = 3608 }, + { id = "npc.barrows_verac", source = "family:barrow_guthan_ready", attack_anim = "seq.barrow_guthan_crush", defend_anim = "seq.barrow_guthan_defend" }, + { id = "npc.basilisk_knight", source = "family:basilisk_knight_ready", attack_anim = "seq.basilisk_knight_melee", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_knight_death" }, + { id = "npc.bat", source = "family:bat_rework_ready", attack_anim = "seq.bat_rework_attack", defend_anim = "seq.bat_rework_defend", death_anim = "seq.bat_rework_death" }, + { id = "npc.bat_unaggressive", source = "family:bat_rework_ready", attack_anim = "seq.bat_rework_attack", defend_anim = "seq.bat_rework_defend", death_anim = "seq.bat_rework_death" }, + { id = "npc.battle_tortoise", source = "family:battle_tortoise_ready", attack_anim = "seq.battle_tortoise_attack", defend_anim = "seq.battle_tortoise_defend", death_anim = "seq.battle_tortoise_death" }, + { id = "npc.battle_tortoise_war2", source = "family:battle_tortoise_ready", attack_anim = "seq.battle_tortoise_attack", defend_anim = "seq.battle_tortoise_defend", death_anim = "seq.battle_tortoise_death" }, + { id = "npc.battlefield_d_skingnomeknight", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.battlefield_darkskinned_gnome_archer", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.battlefield_gnome_archer", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.battlefield_gnome_troop", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.battlefield_gnomeknight", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.battlefield_khazard_troop", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.battlefield_khazard_troop_spearmen", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.battlefield_terrorbird_mountedspear", source = "family:mounted_terrorbird_ready", attack_anim = "seq.mounted_terrorbird_attack", defend_anim = "seq.mounted_terrorbird_defend", death_anim = "seq.mounted_terrorbird_death" }, + { id = "npc.battlefield_terrorbird_mountedspear2", source = "family:mounted_terrorbird_ready", attack_anim = "seq.mounted_terrorbird_attack", defend_anim = "seq.mounted_terrorbird_defend", death_anim = "seq.mounted_terrorbird_death" }, + { id = "npc.battlefield_terrorbird_mountedspear3", source = "family:mounted_terrorbird_ready", attack_anim = "seq.mounted_terrorbird_attack", defend_anim = "seq.mounted_terrorbird_defend", death_anim = "seq.mounted_terrorbird_death" }, + { id = "npc.bcs_champion_rift", source = "family:bcs_rift_idle", attack_anim = "seq.bcs_scabarite_champion_attack_slash" }, + { id = "npc.bcs_champion_scarab_swarm", source = "family:scarab_ready", attack_anim = "seq.scarab_attack", defend_anim = "seq.scarab_parry" }, + { id = "npc.bcs_necropolis_headguard_combat", source = "weapon:obj.brut_bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.bcs_scarab_mage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.bcs_scarab_swarm", source = "family:scarab_ready", attack_anim = "seq.scarab_attack", defend_anim = "seq.scarab_parry" }, + { id = "npc.bearded_dark_wizard", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.bedabin_fighter", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.bim_golem_boss", source = "family:ancient_guardian_idle", attack_anim = "seq.ancient_guardian_attack_melee", death_anim = "seq.ancient_guardian_death" }, + { id = "npc.black_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.black_demon2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.black_demon3", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.black_demon4", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.black_demon5", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.black_demon_strongholdcave_1", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.black_demon_strongholdcave_2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.black_demon_strongholdcave_3", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.black_demon_strongholdcave_4", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.black_demon_strongholdcave_5", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.black_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.black_dragon2", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.black_dragon3", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.black_dragon4", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.black_dragon5", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.black_dragon_echokbd", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.black_dragon_strongholdcave_1", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.black_dragon_strongholdcave_2", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.black_dragon_strongholdcave_3", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.black_heather", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.black_knight", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.black_knight_titan", source = "family:giant_ready", attack_anim = "seq.giant_attack", defend_anim = "seq.giant_block", death_anim = "seq.giant_death" }, + { id = "npc.black_unicorn", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.black_unicorn_foal", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.blessed_giantrat", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.blessed_giantrat2", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.blessed_spider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.blessed_spider_kalrag", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.blue_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.blue_dragon2", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.blue_dragon3", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.blue_dragon4", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.blue_dragon5", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.blue_dragon_strongholdcave_1", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.blue_dragon_strongholdcave_2", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.blue_dragon_strongholdcave_3", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.blue_dragon_strongholdcave_4", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.blue_dragon_strongholdcave_5", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.blue_dragon_tapoyauik_1", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.blue_dragon_tapoyauik_2", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.boar", source = "family:npc_boar_combat_idle", attack_anim = "seq.npc_boar_combat_melee", death_anim = "seq.npc_boar_combat_death" }, + { id = "npc.boar02", source = "family:npc_boar_combat_idle", attack_anim = "seq.npc_boar_combat_melee", death_anim = "seq.npc_boar_combat_death" }, + { id = "npc.brain_barrel_chest", source = "family:brain_barrelchest_ready", defend_anim = "seq.brain_barrelchest_defend", death_anim = "seq.brain_barrelchest_death" }, + { id = "npc.brain_sawbones_1", source = "family:brain_sawbones_ready", attack_anim = "seq.brain_sawbones_attack", defend_anim = "seq.brain_sawbones_defend" }, + { id = "npc.brain_sawbones_2", source = "family:brain_sawbones_ready", attack_anim = "seq.brain_sawbones_attack", defend_anim = "seq.brain_sawbones_defend" }, + { id = "npc.brain_zombie_pirate_1", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.brain_zombie_pirate_10", source = "family:brain_zombie_pirate_drag_feet_ready", attack_anim = "seq.brain_zombie_pirate_drag_feet_attack", defend_anim = "seq.brain_zombie_pirate_drag_feet_defend", death_anim = "seq.brain_zombie_pirate_drag_feet_death" }, + { id = "npc.brain_zombie_pirate_11", source = "family:brain_zombie_pirate_limpers_ready", attack_anim = "seq.brain_zombie_pirate_limpers_attack", defend_anim = "seq.brain_zombie_pirate_limpers_defend" }, + { id = "npc.brain_zombie_pirate_12", source = "family:brain_zombie_pirate_limpers_ready", attack_anim = "seq.brain_zombie_pirate_limpers_attack", defend_anim = "seq.brain_zombie_pirate_limpers_defend" }, + { id = "npc.brain_zombie_pirate_13", source = "family:brain_zombie_pirate_limpers_ready", attack_anim = "seq.brain_zombie_pirate_limpers_attack", defend_anim = "seq.brain_zombie_pirate_limpers_defend" }, + { id = "npc.brain_zombie_pirate_14", source = "family:brain_zombie_pirate_limpers_ready", attack_anim = "seq.brain_zombie_pirate_limpers_attack", defend_anim = "seq.brain_zombie_pirate_limpers_defend" }, + { id = "npc.brain_zombie_pirate_15", source = "family:brain_zombie_pirate_limpers_ready", attack_anim = "seq.brain_zombie_pirate_limpers_attack", defend_anim = "seq.brain_zombie_pirate_limpers_defend" }, + { id = "npc.brain_zombie_pirate_16", source = "family:brain_zombie_pirate_boney_ready", attack_anim = "seq.brain_zombie_pirate_boney_attack", defend_anim = "seq.brain_zombie_pirate_boney_defend", death_anim = "seq.brain_zombie_pirate_boney_death" }, + { id = "npc.brain_zombie_pirate_17", source = "family:brain_zombie_pirate_boney_ready", attack_anim = "seq.brain_zombie_pirate_boney_attack", defend_anim = "seq.brain_zombie_pirate_boney_defend", death_anim = "seq.brain_zombie_pirate_boney_death" }, + { id = "npc.brain_zombie_pirate_18", source = "family:brain_zombie_pirate_boney_ready", attack_anim = "seq.brain_zombie_pirate_boney_attack", defend_anim = "seq.brain_zombie_pirate_boney_defend", death_anim = "seq.brain_zombie_pirate_boney_death" }, + { id = "npc.brain_zombie_pirate_19", source = "family:brain_zombie_pirate_boney_ready", attack_anim = "seq.brain_zombie_pirate_boney_attack", defend_anim = "seq.brain_zombie_pirate_boney_defend", death_anim = "seq.brain_zombie_pirate_boney_death" }, + { id = "npc.brain_zombie_pirate_2", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.brain_zombie_pirate_20", source = "family:brain_zombie_pirate_boney_ready", attack_anim = "seq.brain_zombie_pirate_boney_attack", defend_anim = "seq.brain_zombie_pirate_boney_defend", death_anim = "seq.brain_zombie_pirate_boney_death" }, + { id = "npc.brain_zombie_pirate_21", source = "family:brain_zombie_pirate_cutters_ready", attack_anim = "seq.brain_zombie_pirate_cutters_attack", defend_anim = "seq.brain_zombie_pirate_cutters_defend", death_anim = "seq.brain_zombie_pirate_cutters_death" }, + { id = "npc.brain_zombie_pirate_22", source = "family:brain_zombie_pirate_cutters_ready", attack_anim = "seq.brain_zombie_pirate_cutters_attack", defend_anim = "seq.brain_zombie_pirate_cutters_defend", death_anim = "seq.brain_zombie_pirate_cutters_death" }, + { id = "npc.brain_zombie_pirate_23", source = "family:brain_zombie_pirate_cutters_ready", attack_anim = "seq.brain_zombie_pirate_cutters_attack", defend_anim = "seq.brain_zombie_pirate_cutters_defend", death_anim = "seq.brain_zombie_pirate_cutters_death" }, + { id = "npc.brain_zombie_pirate_24", source = "family:brain_zombie_pirate_cutters_ready", attack_anim = "seq.brain_zombie_pirate_cutters_attack", defend_anim = "seq.brain_zombie_pirate_cutters_defend", death_anim = "seq.brain_zombie_pirate_cutters_death" }, + { id = "npc.brain_zombie_pirate_25", source = "family:brain_zombie_pirate_cutters_ready", attack_anim = "seq.brain_zombie_pirate_cutters_attack", defend_anim = "seq.brain_zombie_pirate_cutters_defend", death_anim = "seq.brain_zombie_pirate_cutters_death" }, + { id = "npc.brain_zombie_pirate_26", source = "family:brain_zombie_pirate_cutters_ready", attack_anim = "seq.brain_zombie_pirate_cutters_attack", defend_anim = "seq.brain_zombie_pirate_cutters_defend", death_anim = "seq.brain_zombie_pirate_cutters_death" }, + { id = "npc.brain_zombie_pirate_3", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.brain_zombie_pirate_4", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.brain_zombie_pirate_5", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.brain_zombie_pirate_6", source = "family:brain_zombie_pirate_drag_feet_ready", attack_anim = "seq.brain_zombie_pirate_drag_feet_attack", defend_anim = "seq.brain_zombie_pirate_drag_feet_defend", death_anim = "seq.brain_zombie_pirate_drag_feet_death" }, + { id = "npc.brain_zombie_pirate_7", source = "family:brain_zombie_pirate_drag_feet_ready", attack_anim = "seq.brain_zombie_pirate_drag_feet_attack", defend_anim = "seq.brain_zombie_pirate_drag_feet_defend", death_anim = "seq.brain_zombie_pirate_drag_feet_death" }, + { id = "npc.brain_zombie_pirate_8", source = "family:brain_zombie_pirate_drag_feet_ready", attack_anim = "seq.brain_zombie_pirate_drag_feet_attack", defend_anim = "seq.brain_zombie_pirate_drag_feet_defend", death_anim = "seq.brain_zombie_pirate_drag_feet_death" }, + { id = "npc.brain_zombie_pirate_9", source = "family:brain_zombie_pirate_drag_feet_ready", attack_anim = "seq.brain_zombie_pirate_drag_feet_attack", defend_anim = "seq.brain_zombie_pirate_drag_feet_defend", death_anim = "seq.brain_zombie_pirate_drag_feet_death" }, + { id = "npc.brain_zombie_pirate_underwater_1", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.brain_zombie_pirate_underwater_17", source = "family:brain_zombie_pirate_boney_ready", attack_anim = "seq.brain_zombie_pirate_boney_attack", defend_anim = "seq.brain_zombie_pirate_boney_defend", death_anim = "seq.brain_zombie_pirate_boney_death" }, + { id = "npc.brain_zombie_pirate_underwater_18", source = "family:brain_zombie_pirate_boney_ready", attack_anim = "seq.brain_zombie_pirate_boney_attack", defend_anim = "seq.brain_zombie_pirate_boney_defend", death_anim = "seq.brain_zombie_pirate_boney_death" }, + { id = "npc.brain_zombie_pirate_underwater_19", source = "family:brain_zombie_pirate_boney_ready", attack_anim = "seq.brain_zombie_pirate_boney_attack", defend_anim = "seq.brain_zombie_pirate_boney_defend", death_anim = "seq.brain_zombie_pirate_boney_death" }, + { id = "npc.brain_zombie_pirate_underwater_2", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.brain_zombie_pirate_underwater_20", source = "family:brain_zombie_pirate_boney_ready", attack_anim = "seq.brain_zombie_pirate_boney_attack", defend_anim = "seq.brain_zombie_pirate_boney_defend", death_anim = "seq.brain_zombie_pirate_boney_death" }, + { id = "npc.brain_zombie_pirate_underwater_21", source = "family:brain_zombie_pirate_cutters_ready", attack_anim = "seq.brain_zombie_pirate_cutters_attack", defend_anim = "seq.brain_zombie_pirate_cutters_defend", death_anim = "seq.brain_zombie_pirate_cutters_death" }, + { id = "npc.brain_zombie_pirate_underwater_23", source = "family:brain_zombie_pirate_cutters_ready", attack_anim = "seq.brain_zombie_pirate_cutters_attack", defend_anim = "seq.brain_zombie_pirate_cutters_defend", death_anim = "seq.brain_zombie_pirate_cutters_death" }, + { id = "npc.brain_zombie_pirate_underwater_24", source = "family:brain_zombie_pirate_cutters_ready", attack_anim = "seq.brain_zombie_pirate_cutters_attack", defend_anim = "seq.brain_zombie_pirate_cutters_defend", death_anim = "seq.brain_zombie_pirate_cutters_death" }, + { id = "npc.brain_zombie_pirate_underwater_25", source = "family:brain_zombie_pirate_cutters_ready", attack_anim = "seq.brain_zombie_pirate_cutters_attack", defend_anim = "seq.brain_zombie_pirate_cutters_defend", death_anim = "seq.brain_zombie_pirate_cutters_death" }, + { id = "npc.brain_zombie_pirate_underwater_5", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.brawling_bandit", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.bronze_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.bronze_dragon_strongholdcave", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.browclothedgnome", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.brownbear", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.brownbear_cub_1", source = "family:bear_rework_cub_ready", attack_anim = "seq.bear_rework_cub_attack", defend_anim = "seq.bear_rework_cub_defend", death_anim = "seq.bear_rework_cub_death" }, + { id = "npc.brownbear_cub_2", source = "family:bear_rework_cub_ready", attack_anim = "seq.bear_rework_cub_attack", defend_anim = "seq.bear_rework_cub_defend", death_anim = "seq.bear_rework_cub_death" }, + { id = "npc.brut_angry_spirit1", source = "family:brut_angry_spirit_ready", attack_anim = "seq.brut_angry_spirit_attack", defend_anim = "seq.brut_angry_spirit_defend", death_anim = "seq.brut_angry_spirit_death" }, + { id = "npc.brut_angry_spirit2", source = "family:brut_angry_spirit_ready", attack_anim = "seq.brut_angry_spirit_attack", defend_anim = "seq.brut_angry_spirit_defend", death_anim = "seq.brut_angry_spirit_death" }, + { id = "npc.brut_angry_spirit3", source = "family:brut_angry_spirit_ready", attack_anim = "seq.brut_angry_spirit_attack", defend_anim = "seq.brut_angry_spirit_defend", death_anim = "seq.brut_angry_spirit_death" }, + { id = "npc.brut_angry_spirit_bad", source = "family:brut_angry_spirit_ready", attack_anim = "seq.brut_angry_spirit_attack", defend_anim = "seq.brut_angry_spirit_defend", death_anim = "seq.brut_angry_spirit_death" }, + { id = "npc.brut_barbarian1", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.brut_green_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.brut_mithril_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.brut_skeleton1", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.brut_skeleton3", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.brut_skeleton5", source = "weapon:model:11793", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.brut_waterfiend", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.brutal_blue_dragon_tapoyauik", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.burgh_gadderanks_attackable", source = "weapon:obj.gadderanks_warhammer", attack_anim = "seq.slayer_granite_maul_attack", attack_type = "category.attacktype_crush", attack_sound = 2567, defend_anim = "seq.slayer_granite_maul_defend" }, + { id = "npc.caerbannog", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.calf", source = "family:cow_just_ready_update", attack_anim = "seq.cow_attack", defend_anim = "seq.cow_block", death_anim = "seq.cow_death" }, + { id = "npc.callisto", source = "family:npc_callisto_idle01", attack_anim = "seq.npc_callisto_attack_melee01", death_anim = "seq.npc_callisto_death01" }, + { id = "npc.callisto_singles", source = "family:npc_callisto_idle01", attack_anim = "seq.npc_callisto_attack_melee01", death_anim = "seq.npc_callisto_death01" }, + { id = "npc.camdozaal_golem_body", source = "family:ancient_guardian_idle", attack_anim = "seq.ancient_guardian_attack_melee", death_anim = "seq.ancient_guardian_death" }, + { id = "npc.camdozaal_golem_chaos", source = "family:ancient_guardian_idle", attack_anim = "seq.ancient_guardian_attack_melee", death_anim = "seq.ancient_guardian_death" }, + { id = "npc.camdozaal_golem_flawed", source = "family:ancient_guardian_idle", attack_anim = "seq.ancient_guardian_attack_melee", death_anim = "seq.ancient_guardian_death" }, + { id = "npc.camdozaal_golem_mind", source = "family:ancient_guardian_idle", attack_anim = "seq.ancient_guardian_attack_melee", death_anim = "seq.ancient_guardian_death" }, + { id = "npc.canafis_werewolf_man1", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_man2", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_man3", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_man4", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_man5", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_man6", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_man7", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_man8", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman1", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman10", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman11", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman12", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman2", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman3", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman4", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman5", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman6", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman7", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman8", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.canafis_werewolf_woman9", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.castle_drakan_crow", source = "family:myq6_crow_idle", attack_anim = "seq.myq6_crow_attack", death_anim = "seq.myq6_crow_death" }, + { id = "npc.castle_drakan_sanguidae", source = "family:myq6_sanguidae_idle_01", attack_anim = "seq.myq6_sanguidae_attack_01", death_anim = "seq.myq6_sanguidae_death" }, + { id = "npc.castle_drakan_sanguidae_aranei_bully", source = "family:myq6_sanguidae_idle_01", attack_anim = "seq.myq6_sanguidae_attack_01", death_anim = "seq.myq6_sanguidae_death" }, + { id = "npc.castle_drakan_sanguidae_full", source = "family:myq6_sanguidae_idle_01", attack_anim = "seq.myq6_sanguidae_attack_01", death_anim = "seq.myq6_sanguidae_death" }, + { id = "npc.castle_drakan_sanguidae_part_full", source = "family:myq6_sanguidae_idle_01", attack_anim = "seq.myq6_sanguidae_attack_01", death_anim = "seq.myq6_sanguidae_death" }, + { id = "npc.castle_drakan_venator", source = "family:npc_venator_idle01", death_anim = "seq.npc_venator_death01" }, + { id = "npc.castle_drakan_venator_2", source = "family:npc_venator_idle01", death_anim = "seq.npc_venator_death01" }, + { id = "npc.castle_drakan_venator_3", source = "family:npc_venator_idle01", death_anim = "seq.npc_venator_death01" }, + { id = "npc.castle_drakan_venator_4", source = "family:npc_venator_idle01", death_anim = "seq.npc_venator_death01" }, + { id = "npc.castle_drakan_venator_5", source = "family:npc_venator_idle01", death_anim = "seq.npc_venator_death01" }, + { id = "npc.cata_boss", source = "family:demon_update_ready", attack_anim = "seq.demon_update_attack", defend_anim = "seq.demon_update_defend", death_anim = "seq.demon_update_death" }, + { id = "npc.cata_boss_minion", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.cave_crawler_ice_1", source = "family:crawler_ready", attack_anim = "seq.crawler_attack", defend_anim = "seq.crawler_parry", death_anim = "seq.crawler_death" }, + { id = "npc.cave_crawler_ice_2", source = "family:crawler_ready", attack_anim = "seq.crawler_attack", defend_anim = "seq.crawler_parry", death_anim = "seq.crawler_death" }, + { id = "npc.cave_goblin", source = "family:dorgesh_goblin_ready", attack_anim = "seq.dorgesh_goblin_unarmed_attack", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin2", source = "family:dorgesh_goblin_ready", attack_anim = "seq.dorgesh_goblin_unarmed_attack", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin3", source = "family:dorgesh_goblin_ready", attack_anim = "seq.dorgesh_goblin_unarmed_attack", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin4", source = "family:dorgesh_goblin_ready", attack_anim = "seq.dorgesh_goblin_unarmed_attack", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin_guard1", source = "family:dorgesh_goblin_ready_spear", attack_anim = "seq.dorgesh_goblin_unarmed_attack", defend_anim = "seq.dorgesh_goblin_spear_block", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin_guard2", source = "family:dorgesh_goblin_ready_spear", attack_anim = "seq.dorgesh_goblin_unarmed_attack", defend_anim = "seq.dorgesh_goblin_spear_block", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin_miner", source = "family:dorgesh_goblin_ready_spear", attack_anim = "seq.dorgesh_goblin_unarmed_attack", defend_anim = "seq.dorgesh_goblin_spear_block", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin_miner2", source = "family:dorgesh_goblin_ready_spear", attack_anim = "seq.dorgesh_goblin_unarmed_attack", defend_anim = "seq.dorgesh_goblin_spear_block", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin_miner2_mining", source = "family:dorgesh_goblin_ready_mining", attack_anim = "seq.dorgesh_goblin_unarmed_attack", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin_miner3", source = "family:dorgesh_goblin_ready_spear", attack_anim = "seq.dorgesh_goblin_unarmed_attack", defend_anim = "seq.dorgesh_goblin_spear_block", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin_miner3_mining", source = "family:dorgesh_goblin_ready_mining", attack_anim = "seq.dorgesh_goblin_unarmed_attack", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin_miner4", source = "family:dorgesh_goblin_ready_spear", attack_anim = "seq.dorgesh_goblin_unarmed_attack", defend_anim = "seq.dorgesh_goblin_spear_block", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin_miner4_mining", source = "family:dorgesh_goblin_ready_mining", attack_anim = "seq.dorgesh_goblin_unarmed_attack", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cave_goblin_miner_mining", source = "family:dorgesh_goblin_ready_mining", attack_anim = "seq.dorgesh_goblin_unarmed_attack", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.cerberus_attacking", source = "family:cerberus_idle", attack_anim = "seq.cerberus_attack_range", defend_anim = "seq.cerberus_defend", death_anim = "seq.cerberus_death" }, + { id = "npc.champions_earthwarrior", source = "family:earthwarrior_ready", defend_anim = "seq.earthwarrior_def", death_anim = "seq.earthwarrior_death" }, + { id = "npc.champions_giant", source = "family:giant_champion_ready", attack_anim = "seq.giant_champion_attack", defend_anim = "seq.giant_champion_defend", death_anim = "seq.giant_champion_death" }, + { id = "npc.champions_human", source = "weapon:stance:human_dh_weapon_ready", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.champions_imp", source = "family:imp_ready", attack_anim = "seq.imp_attack", defend_anim = "seq.imp_block", death_anim = "seq.imp_death" }, + { id = "npc.champions_jogre", source = "family:champions_jogre_ready", attack_anim = "seq.champions_jogre_attack" }, + { id = "npc.champions_lesserdemon", source = "family:demon_update_ready", attack_anim = "seq.demon_update_attack", defend_anim = "seq.demon_update_defend", death_anim = "seq.demon_update_death" }, + { id = "npc.champions_skeleton", source = "family:skeleton_update_champion_ready", attack_anim = "seq.skeleton_update_champion_attack", defend_anim = "seq.skeleton_update_champion_defend", death_anim = "seq.skeleton_update_champion_death" }, + { id = "npc.champions_zombie", source = "family:zombie_update_ready_arm_champion", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.chaos_druid", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.chaos_druid_warrior", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.chaos_fanatic", source = "caster:obj.staff_of_zaros", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_staff_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.chaos_temple_monk1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.chaos_temple_monk2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.chaos_temple_monk_wilderness", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.chaoselemental", source = "family:chaoselemental_ready", attack_anim = "seq.chaoselemental_attack", death_anim = "seq.chaoselemental_death" }, + { id = "npc.chaosmonk1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.chaosmonk2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.chaosmonk3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.chickenquest_baby_black_dragon", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.chronozon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.city_guard", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.clancup_callisto", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.clancup_chaoselemental", source = "family:chaoselemental_ready", attack_anim = "seq.chaoselemental_attack", death_anim = "seq.chaoselemental_death" }, + { id = "npc.clancup_dagcave_magic_boss", source = "family:dagannoth_meganoth_ready", attack_anim = "seq.dagannoth_meganoth_attack_melee", defend_anim = "seq.dagannoth_meganoth_defend", death_anim = "seq.dagannoth_meganoth_death" }, + { id = "npc.clancup_dagcave_melee_boss", source = "family:dagannoth_meganoth_ready", attack_anim = "seq.dagannoth_meganoth_attack_melee", defend_anim = "seq.dagannoth_meganoth_defend", death_anim = "seq.dagannoth_meganoth_death" }, + { id = "npc.clancup_dagcave_ranged_boss", source = "family:dagannoth_meganoth_ready", attack_anim = "seq.dagannoth_meganoth_attack_melee", defend_anim = "seq.dagannoth_meganoth_defend", death_anim = "seq.dagannoth_meganoth_death" }, + { id = "npc.clancup_godwars_armadyl_avatar", source = "family:godwars_armadyl_avatar_ready", defend_anim = "seq.godwars_armadyl_avatar_defend", death_anim = "seq.godwars_armadyl_avatar_death" }, + { id = "npc.clancup_godwars_bandos_avatar", source = "family:godwars_bandos_ready", attack_anim = "seq.godwars_bandos_attack", defend_anim = "seq.godwars_bandos_defend", death_anim = "seq.godwars_bandos_death" }, + { id = "npc.clancup_godwars_saradomin_avatar", source = "family:godwars_saradomin_ready", attack_anim = "seq.godwars_saradomin_attack", defend_anim = "seq.godwars_saradomin_defend", death_anim = "seq.godwars_saradomin_death" }, + { id = "npc.clancup_godwars_zamorak_avatar", source = "family:godwars_zamorak_ready", attack_anim = "seq.godwars_zamorak_attack", defend_anim = "seq.godwars_zamorak_defend", death_anim = "seq.godwars_zamorak_death" }, + { id = "npc.clancup_kalphite_flyingqueen", source = "family:kalphite_update_flying_queen_ready_walk", attack_anim = "seq.kalphite_update_flying_queen_stinger_attack", defend_anim = "seq.kalphite_update_flying_queen_defend", death_anim = "seq.kalphite_update_flying_queen_death" }, + { id = "npc.clancup_kalphite_queen", source = "family:kalphite_update_queen_ready", attack_anim = "seq.kalphite_update_queen_jaws_attack", defend_anim = "seq.kalphite_update_queen_defend", death_anim = "seq.kalphite_update_queen_death" }, + { id = "npc.clancup_king_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.clancup_mole_giant", source = "family:mole_ready", attack_anim = "seq.mole_attack", defend_anim = "seq.mole_defend", death_anim = "seq.mole_death" }, + { id = "npc.clancup_tzhaar_fightcave_swarm_boss", source = "family:lordmagmus_ready", attack_anim = "seq.lordmagmus_attack", defend_anim = "seq.lordmagmus_defend", death_anim = "seq.lordmagmus_death" }, + { id = "npc.clancup_venenatis", source = "family:spider_update_ready_large", attack_anim = "seq.spider_update_attack_large", attack_sound = 537, defend_anim = "seq.spider_update_defend_large", defend_sound = 539, death_anim = "seq.spider_update_death_large", death_sound = 538 }, + { id = "npc.clanwars_teleport_power", source = "family:elemental_air_ready", attack_anim = "seq.elemental_air_attack", defend_anim = "seq.elemental_air_defend", death_anim = "seq.elemental_air_death" }, + { id = "npc.clocktower_rat", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.clocktower_rat2", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.clocktower_rat3", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.coa_armoured_zombie_arrav_1", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.coa_armoured_zombie_arrav_2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.coa_armoured_zombie_melee_1", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.coa_armoured_zombie_melee_2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.coa_armoured_zombie_melee_3", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.coa_armoured_zombie_melee_4", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.coa_armoured_zombie_melee_5", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.coa_armoured_zombie_ranged_1", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.coa_armoured_zombie_ranged_2", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.coa_armoured_zombie_ranged_3", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.coa_armoured_zombie_ranged_4", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.coa_armoured_zombie_ranged_5", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.coa_mastaba_golem", source = "family:coa_stone_golem_ready", attack_anim = "seq.coa_stone_golem_attack", defend_anim = "seq.coa_stone_golem_defend", death_anim = "seq.coa_stone_golem_death" }, + { id = "npc.coa_scarab", source = "family:mom2_scarab_ready", attack_anim = "seq.mom2_scarab_attack", defend_anim = "seq.mom2_scarab_defend", death_anim = "seq.mom2_scarab_death" }, + { id = "npc.colonel_radick", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.colosseum_healing_totem", source = "family:npc_colosseum_totem_idle_01", attack_anim = "seq.npc_colosseum_totem_attack_01" }, + { id = "npc.colosseum_javelin_colossus", source = "family:npc_colossi_javelin_01_idle", death_anim = "seq.npc_colossi_javelin_01_death" }, + { id = "npc.colosseum_manticore", source = "family:npc_manticore_01_idle", death_anim = "seq.npc_manticore_01_death" }, + { id = "npc.colosseum_minotaur", source = "family:npc_minotaur_boss_idle", attack_anim = "seq.npc_minotaur_boss_attack_melee", defend_anim = "seq.npc_minotaur_boss_defend", death_anim = "seq.npc_minotaur_boss_death" }, + { id = "npc.colosseum_minotaur_routefind", source = "family:npc_minotaur_boss_idle", attack_anim = "seq.npc_minotaur_boss_attack_melee", defend_anim = "seq.npc_minotaur_boss_defend", death_anim = "seq.npc_minotaur_boss_death" }, + { id = "npc.colosseum_modifier_bees", source = "family:npc_colosseum_bees_idle_01", attack_anim = "seq.npc_colosseum_bees_attack_01" }, + { id = "npc.colosseum_shockwave_colossus", source = "family:npc_colossi_shockwave_01_idle", death_anim = "seq.npc_colossi_shockwave_01_death" }, + { id = "npc.colosseum_sol_p1", source = "family:npc_colossi_finalboss_01_idle", attack_anim = "seq.npc_colossi_finalboss_01_melee_attack", death_anim = "seq.npc_colossi_finalboss_01_death" }, + { id = "npc.colosseum_standard_mager", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.colosseum_warbander_mage_male", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.contact_insectoid_mage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.contact_insectoid_mage_b", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.contact_locust_bow", source = "family:contact_locust_ready", defend_anim = "seq.contact_locust_defend", death_anim = "seq.contact_locust_death" }, + { id = "npc.contact_locust_bow_b", source = "family:contact_locust_ready", defend_anim = "seq.contact_locust_defend", death_anim = "seq.contact_locust_death" }, + { id = "npc.contact_locust_lance", source = "family:contact_locust_ready", defend_anim = "seq.contact_locust_defend", death_anim = "seq.contact_locust_death" }, + { id = "npc.contact_locust_lance_b", source = "family:contact_locust_ready", defend_anim = "seq.contact_locust_defend", death_anim = "seq.contact_locust_death" }, + { id = "npc.contact_scarab_boss", source = "family:contact_boss_ready", attack_anim = "seq.contact_boss_attack", defend_anim = "seq.contact_boss_defend", death_anim = "seq.contact_boss_death" }, + { id = "npc.corp_beast", source = "family:corpbeast_idle", defend_anim = "seq.corpbeast_defend", death_anim = "seq.corpbeast_death" }, + { id = "npc.corscurs_navigator_combat", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.cow_beef", source = "family:cow_just_ready_update", attack_anim = "seq.cow_attack", defend_anim = "seq.cow_block", death_anim = "seq.cow_death" }, + { id = "npc.cowboss", source = "family:cow_boss_idle", attack_anim = "seq.cow_boss_attack", defend_anim = "seq.cow_boss_defend", death_anim = "seq.cow_boss_death" }, + { id = "npc.cowboss_hardmode", source = "family:cow_boss_idle", attack_anim = "seq.cow_boss_attack", defend_anim = "seq.cow_boss_defend", death_anim = "seq.cow_boss_death" }, + { id = "npc.cowboss_routefind", source = "family:cow_boss_idle", attack_anim = "seq.cow_boss_attack", defend_anim = "seq.cow_boss_defend", death_anim = "seq.cow_boss_death" }, + { id = "npc.crazy_archaeologist", source = "shield:obj.unfinished_saradominbook", defend_anim = "seq.human_shield_defence" }, + { id = "npc.crypt_of_tonali_grimy_lizard", source = "family:grimy_lizard_ready", attack_anim = "seq.grimy_lizard_melee", death_anim = "seq.grimy_lizard_death" }, + { id = "npc.crypt_of_tonali_mossgiant", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.crypt_of_tonali_skeleton_1", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.crypt_of_tonali_skeleton_2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.crypt_of_tonali_skeleton_3", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.crystal_bat", source = "family:bat_rework_ready", attack_anim = "seq.bat_rework_attack", defend_anim = "seq.bat_rework_defend", death_anim = "seq.bat_rework_death" }, + { id = "npc.crystal_bat_hm", source = "family:bat_rework_ready", attack_anim = "seq.bat_rework_attack", defend_anim = "seq.bat_rework_defend", death_anim = "seq.bat_rework_death" }, + { id = "npc.crystal_bear", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.crystal_bear_hm", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.crystal_dark_beast", source = "family:dark_beast_update_ready", attack_anim = "seq.dark_beast_update_attack", defend_anim = "seq.dark_beast_update_defend", death_anim = "seq.dark_beast_update_death" }, + { id = "npc.crystal_dark_beast_hm", source = "family:dark_beast_update_ready", attack_anim = "seq.dark_beast_update_attack", defend_anim = "seq.dark_beast_update_defend", death_anim = "seq.dark_beast_update_death" }, + { id = "npc.crystal_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.crystal_dragon_hm", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.crystal_hunllef_magic", source = "family:hunllef_ready", attack_anim = "seq.hunllef_attack_melee" }, + { id = "npc.crystal_hunllef_magic_hm", source = "family:hunllef_ready", attack_anim = "seq.hunllef_attack_melee" }, + { id = "npc.crystal_hunllef_melee", source = "family:hunllef_ready", attack_anim = "seq.hunllef_attack_melee" }, + { id = "npc.crystal_hunllef_melee_hm", source = "family:hunllef_ready", attack_anim = "seq.hunllef_attack_melee" }, + { id = "npc.crystal_hunllef_ranged", source = "family:hunllef_ready", attack_anim = "seq.hunllef_attack_melee" }, + { id = "npc.crystal_hunllef_ranged_hm", source = "family:hunllef_ready", attack_anim = "seq.hunllef_attack_melee" }, + { id = "npc.crystal_rat", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.crystal_rat_hm", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.crystal_scorpion", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.crystal_scorpion_hm", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.crystal_spider", source = "family:sote_gauntlet_spider_idle", attack_anim = "seq.sote_gauntlet_spider_attack_melee", death_anim = "seq.sote_gauntlet_spider_death" }, + { id = "npc.crystal_spider_hm", source = "family:sote_gauntlet_spider_idle", attack_anim = "seq.sote_gauntlet_spider_attack_melee", death_anim = "seq.sote_gauntlet_spider_death" }, + { id = "npc.crystal_unicorn", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.crystal_unicorn_hm", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.crystal_wolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.crystal_wolf_hm", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.cyclops", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.d_skingnomeknight", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.dagannoth_rock_lobster_2x2", source = "family:dagannoth_rock_lobster_ready", attack_anim = "seq.dagannoth_rock_lobster_attack", defend_anim = "seq.dagannoth_rock_lobster_defend", death_anim = "seq.dagannoth_rock_lobster_death" }, + { id = "npc.dagannoth_water_creature_1x1", source = "family:dagannoth_water_creature_ready", attack_anim = "seq.dagannoth_water_creature_attack", defend_anim = "seq.dagannoth_water_creature_defend", death_anim = "seq.dagannoth_water_creature_death" }, + { id = "npc.dagannoth_water_creature_1x12", source = "family:dagannoth_water_creature_ready", attack_anim = "seq.dagannoth_water_creature_attack", defend_anim = "seq.dagannoth_water_creature_defend", death_anim = "seq.dagannoth_water_creature_death" }, + { id = "npc.dagannoth_water_creature_1x13", source = "family:dagannoth_water_creature_ready", attack_anim = "seq.dagannoth_water_creature_attack", defend_anim = "seq.dagannoth_water_creature_defend", death_anim = "seq.dagannoth_water_creature_death" }, + { id = "npc.dagcave_magic_boss", source = "family:dagannoth_meganoth_ready", attack_anim = "seq.dagannoth_meganoth_attack_melee", defend_anim = "seq.dagannoth_meganoth_defend", death_anim = "seq.dagannoth_meganoth_death" }, + { id = "npc.dagcave_melee_boss", source = "family:dagannoth_meganoth_ready", attack_anim = "seq.dagannoth_meganoth_attack_melee", defend_anim = "seq.dagannoth_meganoth_defend", death_anim = "seq.dagannoth_meganoth_death" }, + { id = "npc.dagcave_ranged_boss", source = "family:dagannoth_meganoth_ready", attack_anim = "seq.dagannoth_meganoth_attack_melee", defend_anim = "seq.dagannoth_meganoth_defend", death_anim = "seq.dagannoth_meganoth_death" }, + { id = "npc.dagganoth_critter", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.dark_seren", source = "family:dark_lord_ready", attack_anim = "seq.dark_lord_attack_melee", death_anim = "seq.dark_lord_death" }, + { id = "npc.dark_seren_phantom", source = "family:dark_lord_ready", attack_anim = "seq.dark_lord_attack_melee", death_anim = "seq.dark_lord_death" }, + { id = "npc.dark_squall_combat", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.dark_warrior", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.darkbear", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.darkm_juvenile_01", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkm_juvenile_02", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkm_juvinate_01", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkm_juvinate_02", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkm_vyrewatch_female_1", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkm_vyrewatch_female_2", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkm_vyrewatch_female_3", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkm_vyrewatch_female_4", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkm_vyrewatch_male_1", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkm_vyrewatch_male_2", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkm_vyrewatch_male_3", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkm_vyrewatch_male_4", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.darkskinned_gnome", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.deadly_red_spider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.deadman_all_stars_mission_godwars_armadyl", source = "family:godwars_armadyl_avatar_ready", defend_anim = "seq.godwars_armadyl_avatar_defend", death_anim = "seq.godwars_armadyl_avatar_death" }, + { id = "npc.deadman_all_stars_mission_jad", source = "family:lordmagmus_ready", attack_anim = "seq.lordmagmus_attack", defend_anim = "seq.lordmagmus_defend", death_anim = "seq.lordmagmus_death" }, + { id = "npc.deadman_all_stars_mission_scurrius", source = "family:npc_rat_boss_idle_01", death_anim = "seq.npc_rat_boss_death_01" }, + { id = "npc.deadman_all_stars_mission_sol_heredit", source = "family:npc_colossi_finalboss_01_idle", attack_anim = "seq.npc_colossi_finalboss_01_melee_attack", death_anim = "seq.npc_colossi_finalboss_01_death" }, + { id = "npc.deadman_all_stars_mission_warden_tumeken_mage", source = "family:npc_wardens_idle01", attack_anim = "seq.npc_wardens_melee01", death_anim = "seq.npc_wardens_death01" }, + { id = "npc.deadman_all_stars_mission_yama", source = "family:npc_yama01_idle01", attack_anim = "seq.npc_yama01_melee01" }, + { id = "npc.deadman_breach_barrows_dharok", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.deadman_breach_bees", source = "family:npc_colosseum_bees_idle_01", attack_anim = "seq.npc_colosseum_bees_attack_01" }, + { id = "npc.deadman_breach_bloat", source = "family:tob_bloat_ready", death_anim = "seq.tob_bloat_death" }, + { id = "npc.deadman_breach_cerberus", source = "family:cerberus_idle", attack_anim = "seq.cerberus_attack_range", defend_anim = "seq.cerberus_defend", death_anim = "seq.cerberus_death" }, + { id = "npc.deadman_breach_dks_magic", source = "family:dagannoth_meganoth_ready", attack_anim = "seq.dagannoth_meganoth_attack_melee", defend_anim = "seq.dagannoth_meganoth_defend", death_anim = "seq.dagannoth_meganoth_death" }, + { id = "npc.deadman_breach_dks_melee", source = "family:dagannoth_meganoth_ready", attack_anim = "seq.dagannoth_meganoth_attack_melee", defend_anim = "seq.dagannoth_meganoth_defend", death_anim = "seq.dagannoth_meganoth_death" }, + { id = "npc.deadman_breach_dks_range", source = "family:dagannoth_meganoth_ready", attack_anim = "seq.dagannoth_meganoth_attack_melee", defend_anim = "seq.dagannoth_meganoth_defend", death_anim = "seq.dagannoth_meganoth_death" }, + { id = "npc.deadman_breach_dscim_npc", source = "weapon:obj.dragon_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.deadman_breach_durial321", source = "weapon:obj.abyssal_whip", attack_anim = "seq.slayer_abyssal_whip_attack", attack_type = "category.attacktype_slash", attack_sound = 2720, defend_anim = "seq.human_shield_defence" }, + { id = "npc.deadman_breach_giant_goblin", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.deadman_breach_godwars_armadyl", source = "family:godwars_armadyl_avatar_ready", defend_anim = "seq.godwars_armadyl_avatar_defend", death_anim = "seq.godwars_armadyl_avatar_death" }, + { id = "npc.deadman_breach_godwars_bandos", source = "family:godwars_bandos_ready", attack_anim = "seq.godwars_bandos_attack", defend_anim = "seq.godwars_bandos_defend", death_anim = "seq.godwars_bandos_death" }, + { id = "npc.deadman_breach_godwars_saradomin", source = "family:godwars_saradomin_ready", attack_anim = "seq.godwars_saradomin_attack", defend_anim = "seq.godwars_saradomin_defend", death_anim = "seq.godwars_saradomin_death" }, + { id = "npc.deadman_breach_godwars_zamorak", source = "family:godwars_zamorak_ready", attack_anim = "seq.godwars_zamorak_attack", defend_anim = "seq.godwars_zamorak_defend", death_anim = "seq.godwars_zamorak_death" }, + { id = "npc.deadman_breach_inferno_melee", source = "family:jalimkot_ready", attack_anim = "seq.jalimkot_attack", defend_anim = "seq.jalimkot_defend", death_anim = "seq.jalimkot_death" }, + { id = "npc.deadman_breach_jad", source = "family:lordmagmus_ready", attack_anim = "seq.lordmagmus_attack", defend_anim = "seq.lordmagmus_defend", death_anim = "seq.lordmagmus_death" }, + { id = "npc.deadman_breach_jad_minion", source = "family:lordmagmus_ready", attack_anim = "seq.lordmagmus_attack", defend_anim = "seq.lordmagmus_defend", death_anim = "seq.lordmagmus_death" }, + { id = "npc.deadman_breach_king_black_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.deadman_breach_lucien", source = "family:mahjarrat_idle_05", defend_anim = "seq.mahjarrat_defend_05" }, + { id = "npc.deadman_breach_ma2_guthix", source = "family:ent_boss_idle", attack_anim = "seq.ent_boss_attack_melee", defend_anim = "seq.ent_boss_defend" }, + { id = "npc.deadman_breach_ma2_saradomin", source = "family:wild_zealot_idle", attack_anim = "seq.wild_zealot_slash", defend_anim = "seq.wild_zealot_defend", death_anim = "seq.wild_zealot_death" }, + { id = "npc.deadman_breach_ma2_zamorak", source = "family:zamorak_demon_boss_idle", attack_anim = "seq.zamorak_demon_boss_attack_melee", defend_anim = "seq.zamorak_demon_boss_defend", death_anim = "seq.zamorak_demon_boss_death" }, + { id = "npc.deadman_breach_magic_mark", source = "caster:obj.staff_of_zaros", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_shield_defence", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.deadman_breach_mummy_minion", source = "family:mummy_update_civillian_ready", attack_anim = "seq.mummy_update_civillian_attack", defend_anim = "seq.mummy_update_civillian_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.deadman_breach_muspah", source = "family:npc_muspah_idle_01", death_anim = "seq.npc_muspah_death_01" }, + { id = "npc.deadman_breach_pirate_minion", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.deadman_breach_ranging_ro", source = "weapon:obj.chinchompa_captured", attack_anim = "seq.human_chinchompa_attack_pvn", attack_type = "category.attacktype_standard", defend_anim = "seq.human_shield_defence", proj_travel = "spotanim.chinchompa_grenade", proj_type = "projanim.chinchompa", attack_range = 9 }, + { id = "npc.deadman_breach_scurrius", source = "family:npc_rat_boss_idle_01", death_anim = "seq.npc_rat_boss_death_01" }, + { id = "npc.deadman_breach_shade_minion", source = "family:shade_ready", attack_anim = "seq.shade_attack", defend_anim = "seq.shade_block" }, + { id = "npc.deadman_breach_smoke_devil", source = "family:lore_dust_devil_ready", attack_anim = "seq.lore_dust_devil_attack", defend_anim = "seq.lore_dust_devil_defend", death_anim = "seq.lore_dust_devil_death" }, + { id = "npc.deadman_breach_sol_heredit", source = "family:npc_colossi_finalboss_01_idle", attack_anim = "seq.npc_colossi_finalboss_01_melee_attack", death_anim = "seq.npc_colossi_finalboss_01_death" }, + { id = "npc.deadman_breach_splatter", source = "family:splatter_ready", attack_anim = "seq.splatter_attack", defend_anim = "seq.splatter_defend", death_anim = "seq.splatter_death" }, + { id = "npc.deadman_breach_sulphur_lizard", source = "family:sulphur_lizard_ready", attack_anim = "seq.sulphur_lizard_melee", death_anim = "seq.sulphur_lizard_death" }, + { id = "npc.deadman_breach_superior_abyssal_demon", source = "family:abyssal_ready", attack_anim = "seq.abyssal_attack", death_anim = "seq.abyssal_death" }, + { id = "npc.deadman_breach_superior_cave_horror", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.deadman_breach_superior_dark_beast", source = "family:dark_beast_update_ready", attack_anim = "seq.dark_beast_update_attack", defend_anim = "seq.dark_beast_update_defend", death_anim = "seq.dark_beast_update_death" }, + { id = "npc.deadman_breach_superior_infernal_mage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.deadman_breach_superior_kourend_jelly", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.deadman_breach_superior_pyrefiend", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.deadman_breach_ulfric_minion", source = "family:olaf2_undead_ready", attack_anim = "seq.olaf2_undead_sword_lunge", defend_anim = "seq.olaf2_undead_sword_def" }, + { id = "npc.deadman_breach_undead_one_minion", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.deadman_breach_vardorvis", source = "family:npc_vardorvis_01_idle_01", attack_anim = "seq.npc_vardorvis_01_melee_01", death_anim = "seq.npc_vardorvis_01_death_01" }, + { id = "npc.deadman_breach_warden_tumeken_mage", source = "family:npc_wardens_idle01", attack_anim = "seq.npc_wardens_melee01", death_anim = "seq.npc_wardens_death01" }, + { id = "npc.deadman_breach_warden_tumeken_range", source = "family:npc_wardens_idle01", attack_anim = "seq.npc_wardens_melee01", death_anim = "seq.npc_wardens_death01" }, + { id = "npc.deadman_breach_yama", source = "family:npc_yama01_idle01", attack_anim = "seq.npc_yama01_melee01" }, + { id = "npc.deadman_chest_zemouregal", source = "family:mahjarrat_idle_05", defend_anim = "seq.mahjarrat_defend_05" }, + { id = "npc.deadman_guard_ardougne_range_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_ardougne_vis", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.deadman_guard_catherby_vis", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.deadman_guard_falador_range_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_falador_vis", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_shield_defence" }, + { id = "npc.deadman_guard_ja_range_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_ja_vis", source = "weapon:obj.viking_sword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.deadman_guard_lumbridge_range_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_lumbridge_vis", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.deadman_guard_portphas_range_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_rellekka_range_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_rellekka_vis", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_shield_defence" }, + { id = "npc.deadman_guard_seers_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_sophanem_range_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_varrock_range_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_varrock_vis", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.deadman_guard_voidknight_range_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_voidknight_vis", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.deadman_guard_warguild_range_vis", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.deadman_guard_warguild_vis", source = "weapon:obj.iron_warhammer", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2567, defend_anim = "seq.human_shield_defence" }, + { id = "npc.deadman_guard_wintertodt_vis", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.deadman_guard_yanille_range_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_yanille_vis", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.deadman_guard_zeah_range_vis", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.deadman_guard_zeah_vis", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.deal_evil_spirit", source = "family:spirit_ready", attack_anim = "seq.spirit_attack", defend_anim = "seq.spirit_parry", death_anim = "seq.spirit_death" }, + { id = "npc.deal_fever_spiders1", source = "family:spider_update_ready_large", attack_anim = "seq.spider_update_attack_large", attack_sound = 537, defend_anim = "seq.spider_update_defend_large", defend_sound = 539, death_anim = "seq.spider_update_death_large", death_sound = 538 }, + { id = "npc.deal_pirate_a", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.deal_pirate_b", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.deal_pirate_c", source = "family:brain_zombie_pirate_drag_feet_ready", attack_anim = "seq.brain_zombie_pirate_drag_feet_attack", defend_anim = "seq.brain_zombie_pirate_drag_feet_defend", death_anim = "seq.brain_zombie_pirate_drag_feet_death" }, + { id = "npc.deal_pirate_d", source = "family:brain_zombie_pirate_drag_feet_ready", attack_anim = "seq.brain_zombie_pirate_drag_feet_attack", defend_anim = "seq.brain_zombie_pirate_drag_feet_defend", death_anim = "seq.brain_zombie_pirate_drag_feet_death" }, + { id = "npc.deal_pirate_e", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.deal_pirate_f", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.deal_zombie_pirates_1", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.deal_zombie_pirates_2", source = "family:brain_zombie_pirate_drag_feet_ready", attack_anim = "seq.brain_zombie_pirate_drag_feet_attack", defend_anim = "seq.brain_zombie_pirate_drag_feet_defend", death_anim = "seq.brain_zombie_pirate_drag_feet_death" }, + { id = "npc.deal_zombie_pirates_3", source = "family:brain_zombie_pirate_drag_feet_ready", attack_anim = "seq.brain_zombie_pirate_drag_feet_attack", defend_anim = "seq.brain_zombie_pirate_drag_feet_defend", death_anim = "seq.brain_zombie_pirate_drag_feet_death" }, + { id = "npc.deal_zombie_pirates_4", source = "family:brain_zombie_pirate_drag_feet_ready", attack_anim = "seq.brain_zombie_pirate_drag_feet_attack", defend_anim = "seq.brain_zombie_pirate_drag_feet_defend", death_anim = "seq.brain_zombie_pirate_drag_feet_death" }, + { id = "npc.deal_zombie_pirates_5", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.deal_zombie_pirates_6", source = "family:brain_zombie_pirate_wanderers_ready", attack_anim = "seq.brain_zombie_pirate_wanderers_attack", defend_anim = "seq.brain_zombie_pirate_wanderers_defend", death_anim = "seq.brain_zombie_pirate_wanderers_death" }, + { id = "npc.death_archer1", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.death_archer2", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.death_archer_trapped", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.death_guard1", source = "weapon:obj.iron_warhammer", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2567, defend_anim = "seq.human_shield_defence" }, + { id = "npc.death_guard2", source = "weapon:obj.iron_warhammer", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2567, defend_anim = "seq.human_shield_defence" }, + { id = "npc.death_ig_solider_wander", source = "weapon:obj.bronze_claws", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2548, defend_anim = "seq.human_axe_block" }, + { id = "npc.death_man_indoors1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.death_troll_commander", source = "family:troll_ready_sword+shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_guard1", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_guard2", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_leuitanant", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_melee1", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_melee2", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_melee3", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_melee4", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_melee5", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_melee6", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_melee7", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_thrower1", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_thrower2", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_thrower3", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_thrower4", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_troll_thrower5", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.death_white_knight", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.deathwing", source = "family:bat_rework_ready", attack_anim = "seq.bat_rework_attack", defend_anim = "seq.bat_rework_defend", death_anim = "seq.bat_rework_death" }, + { id = "npc.delrith", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.desert_goat_black1", source = "family:sheep_ready", attack_anim = "seq.sheep_attack", defend_anim = "seq.sheep_block", death_anim = "seq.sheep_death" }, + { id = "npc.desert_goat_black2", source = "family:sheep_ready", attack_anim = "seq.sheep_attack", defend_anim = "seq.sheep_block", death_anim = "seq.sheep_death" }, + { id = "npc.desert_goat_black3", source = "family:sheep_ready", attack_anim = "seq.sheep_attack", defend_anim = "seq.sheep_block", death_anim = "seq.sheep_death" }, + { id = "npc.desert_goat_white1", source = "family:sheep_ready", attack_anim = "seq.sheep_attack", defend_anim = "seq.sheep_block", death_anim = "seq.sheep_death" }, + { id = "npc.desert_goat_white2", source = "family:sheep_ready", attack_anim = "seq.sheep_attack", defend_anim = "seq.sheep_block", death_anim = "seq.sheep_death" }, + { id = "npc.desert_goat_white3", source = "family:sheep_ready", attack_anim = "seq.sheep_attack", defend_anim = "seq.sheep_block", death_anim = "seq.sheep_death" }, + { id = "npc.desert_wolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.desert_wolf2", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.desert_wolf3", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.desertminingcaptain", source = "shield:obj.warguild_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.deserttreasure_mummy_1", source = "family:mummy_update_civillian_ready", attack_anim = "seq.mummy_update_civillian_attack", defend_anim = "seq.mummy_update_civillian_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.deserttreasure_mummy_1_on_fire", source = "family:mummy_update_civillian_ready", attack_anim = "seq.mummy_update_civillian_attack", defend_anim = "seq.mummy_update_civillian_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.deserttreasure_mummy_2", source = "family:mummy_update_civillian_ready", attack_anim = "seq.mummy_update_civillian_attack", defend_anim = "seq.mummy_update_civillian_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.deserttreasure_mummy_2_on_fire", source = "family:mummy_update_civillian_ready", attack_anim = "seq.mummy_update_civillian_attack", defend_anim = "seq.mummy_update_civillian_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.deserttreasure_mummy_3", source = "family:mummy_update_civillian_ready", attack_anim = "seq.mummy_update_civillian_attack", defend_anim = "seq.mummy_update_civillian_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.deserttreasure_mummy_3_on_fire", source = "family:mummy_update_civillian_ready", attack_anim = "seq.mummy_update_civillian_attack", defend_anim = "seq.mummy_update_civillian_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.deserttreasure_mummy_4", source = "family:mummy_update_civillian_ready", attack_anim = "seq.mummy_update_civillian_attack", defend_anim = "seq.mummy_update_civillian_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.deserttreasure_mummy_4_on_fire", source = "family:mummy_update_civillian_ready", attack_anim = "seq.mummy_update_civillian_attack", defend_anim = "seq.mummy_update_civillian_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.deserttreasure_mummy_5", source = "family:mummy_update_civillian_ready", attack_anim = "seq.mummy_update_civillian_attack", defend_anim = "seq.mummy_update_civillian_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.deserttreasure_mummy_5_on_fire", source = "family:mummy_update_civillian_ready", attack_anim = "seq.mummy_update_civillian_attack", defend_anim = "seq.mummy_update_civillian_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.direwolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.dog_wild", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.dog_wild_2", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.dom_shockwave_path_node", source = "family:area_node_idle", death_anim = "seq.area_node_death" }, + { id = "npc.donny_the_lad", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.doomion_vis", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dorgesh_guard1", source = "family:dorgesh_goblin_ready", attack_anim = "seq.dorgesh_goblin_unarmed_attack", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.dorgesh_guard2", source = "family:dorgesh_goblin_ready", attack_anim = "seq.dorgesh_goblin_unarmed_attack", death_anim = "seq.dorgesh_goblin_death" }, + { id = "npc.double_agent_1", source = "shield:obj.trail_briefcase", defend_anim = "seq.human_shield_defence" }, + { id = "npc.double_agent_2", source = "shield:obj.trail_briefcase", defend_anim = "seq.human_shield_defence" }, + { id = "npc.double_agent_master", source = "shield:obj.trail_briefcase", defend_anim = "seq.human_shield_defence" }, + { id = "npc.dov_armoured_zombie_melee_1", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_melee_2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dov_armoured_zombie_melee_3", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_melee_4", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_melee_5", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_ranged_1", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_ranged_2", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_ranged_3", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_ranged_4", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_ranged_5", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_varrock_melee_1", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_varrock_melee_1_huntplayer", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_varrock_melee_2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dov_armoured_zombie_varrock_melee_2_huntplayer", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dov_armoured_zombie_varrock_melee_3", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_varrock_melee_3_huntplayer", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_varrock_melee_4", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_varrock_melee_4_huntplayer", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_varrock_melee_5", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_varrock_melee_5_huntplayer", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dov_armoured_zombie_varrock_ranged_1", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_varrock_ranged_1_huntplayer", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_varrock_ranged_2", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_varrock_ranged_2_huntplayer", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_varrock_ranged_3", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_varrock_ranged_3_huntplayer", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_varrock_ranged_4", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_varrock_ranged_4_huntplayer", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_varrock_ranged_5", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dov_armoured_zombie_varrock_ranged_5_huntplayer", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dps_calc_npc_recoil", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.dragonslayer_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dragonslayer_ghost_1_key", source = "family:ghost_update_tendrill_ready", attack_anim = "seq.ghost_update_tendrill_attack", attack_sound = 436, defend_anim = "seq.ghost_update_tendrill_defend", defend_sound = 439, death_anim = "seq.ghost_update_tendrill_death", death_sound = 438 }, + { id = "npc.dragonslayer_ghost_2", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.dragonslayer_ghost_3", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.dragonslayer_ghost_4", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.dragonslayer_ghost_5", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.dragonslayer_giantrat_1_key", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.dragonslayer_giantrat_2", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.dragonslayer_giantrat_3", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.dragonslayer_skeleton_1_key", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.dragonslayer_skeleton_2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.dragonslayer_skeleton_3", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.dragonslayer_zombie_1_key", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dragonslayer_zombie_2", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.drake", source = "family:drake_ready", attack_anim = "seq.drake_melee" }, + { id = "npc.draynor_skeleton", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.dream_everlasting", source = "family:dream_everlasting_ready", attack_anim = "seq.dream_everlasting_attack", defend_anim = "seq.dream_everlasting_defend", death_anim = "seq.dream_everlasting_death" }, + { id = "npc.dream_illusive", source = "family:dream_illusive_ready", attack_anim = "seq.dream_illusive_attack", death_anim = "seq.dream_illusive_death" }, + { id = "npc.dream_inadequacy", source = "family:dream_inadequacy_ready", defend_anim = "seq.dream_inadequacy_defend", death_anim = "seq.dream_inadequacy_death" }, + { id = "npc.dream_razorbeast", source = "family:dream_razorbeast_ready", attack_anim = "seq.dream_razorbeast_attack", defend_anim = "seq.dream_razorbeast_defend", death_anim = "seq.dream_razorbeast_death" }, + { id = "npc.dream_untouchable", source = "family:dream_untouchable_ready_walk", attack_anim = "seq.dream_untouchable_attack", defend_anim = "seq.dream_untouchable_defend", death_anim = "seq.dream_untouchable_death" }, + { id = "npc.druid", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.ds2_adamant_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.ds2_black_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.ds2_blue_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.ds2_brut_green_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.ds2_brut_red_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.ds2_golem_magic", source = "family:ds2_stone_guardian_ready", attack_anim = "seq.ds2_stone_guardian_attack", defend_anim = "seq.ds2_stone_guardian_defend", death_anim = "seq.ds2_stone_guardian_death" }, + { id = "npc.ds2_golem_melee", source = "family:ds2_stone_guardian_ready", attack_anim = "seq.ds2_stone_guardian_attack", defend_anim = "seq.ds2_stone_guardian_defend", death_anim = "seq.ds2_stone_guardian_death" }, + { id = "npc.ds2_golem_ranged", source = "family:ds2_stone_guardian_ready", attack_anim = "seq.ds2_stone_guardian_attack", defend_anim = "seq.ds2_stone_guardian_defend", death_anim = "seq.ds2_stone_guardian_death" }, + { id = "npc.ds2_green_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.ds2_iron_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.ds2_mithril_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.ds2_red_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.ds2_rune_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.ds2_skeleton_magic", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.ds2_skeleton_melee", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.ds2_skeleton_ranged", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.ds2_steel_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.ds2_zombie_magic", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.ds2_zombie_melee", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.ds2_zombie_ranged", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dt2_ancient_guardian", source = "family:npc_ancient_guardian01_zaros01_idle01", death_anim = "seq.npc_ancient_guardian01_zaros01_death01" }, + { id = "npc.dt2_ancient_guardian_shield", source = "family:npc_ancient_guardian01_zaros01_idle01", death_anim = "seq.npc_ancient_guardian01_zaros01_death01" }, + { id = "npc.dt2_assassin_wight_combat", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.dt2_ketla_wight_combat", source = "family:dwarf_crossbow_idle", attack_anim = "seq.dwarf_crossbow_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dt2_ketla_wight_combat_clone", source = "family:dwarf_crossbow_idle", attack_anim = "seq.dwarf_crossbow_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dt2_lassar_maze_npc", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.dt2_persten_wight_combat", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.dt2_pursuer_hideout_combat", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.dt2_pursuer_overworld_combat", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.dt2_scar_abyssal_leech", source = "family:abyssal_leech_ready", attack_anim = "seq.abyssal_leech_attack", defend_anim = "seq.abyssal_leech_defend", death_anim = "seq.abyssal_leech_death" }, + { id = "npc.dt2_scar_abyssal_walker", source = "family:abyssal_walker_ready", attack_anim = "seq.abyssal_walker_attack", defend_anim = "seq.abyssal_walker_defend", death_anim = "seq.abyssal_walker_death" }, + { id = "npc.dt2_scar_black_demon_1", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_black_demon_2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_greater_demon_1", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_greater_demon_2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_leech", source = "family:abyssal_leech_ready", attack_anim = "seq.abyssal_leech_attack", defend_anim = "seq.abyssal_leech_defend", death_anim = "seq.abyssal_leech_death" }, + { id = "npc.dt2_scar_lesser_demon_1", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_lesser_demon_2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_maze_2_blood_cell_red", source = "family:cave_slime_idle", attack_anim = "seq.cave_slime_attack", defend_anim = "seq.cave_slime_defend", death_anim = "seq.cave_slime_death" }, + { id = "npc.dt2_scar_maze_2_blood_cell_white", source = "family:cave_slime_idle", attack_anim = "seq.cave_slime_attack", defend_anim = "seq.cave_slime_defend", death_anim = "seq.cave_slime_death" }, + { id = "npc.dt2_scar_maze_2_pillar_attacker", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_maze_2_pillar_attacker_dog", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.dt2_scar_maze_2_pillar_repairer", source = "family:abyssal_pyramid_ready", attack_anim = "seq.abyssal_pyramid_attack", defend_anim = "seq.abyssal_pyramid_defend", death_anim = "seq.abyssal_pyramid_death" }, + { id = "npc.dt2_scar_maze_3_link_attacker", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_maze_3_link_attacker_dog", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.dt2_scar_maze_3_link_chanter", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_maze_3_link_npc", source = "family:imp_ready", attack_anim = "seq.imp_attack", defend_anim = "seq.imp_block", death_anim = "seq.imp_death" }, + { id = "npc.dt2_scar_maze_leech", source = "family:abyssal_leech_ready", attack_anim = "seq.abyssal_leech_attack", defend_anim = "seq.abyssal_leech_defend", death_anim = "seq.abyssal_leech_death" }, + { id = "npc.dt2_scar_maze_mage_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_maze_mage_demon_normal", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_maze_melee_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_maze_melee_demon_normal", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_maze_ranged_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_maze_ranged_demon_normal", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_scar_walker", source = "family:abyssal_walker_ready", attack_anim = "seq.abyssal_walker_attack", defend_anim = "seq.abyssal_walker_defend", death_anim = "seq.abyssal_walker_death" }, + { id = "npc.dt2_shadow_keeper", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.dt2_strangled_base_t1_1", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_base_t1_2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_base_t1_3", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_base_t1_4", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_base_t2_1", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dt2_strangled_base_t2_2", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dt2_strangled_base_t2_3", source = "family:zombie_update_ready_arm_champion", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dt2_strangled_boar", source = "family:npc_boar_combat_idle_silent", attack_anim = "seq.npc_boar_combat_melee", death_anim = "seq.npc_boar_combat_death" }, + { id = "npc.dt2_strangled_lynx", source = "family:npc_lynx_combat_idle", attack_anim = "seq.npc_lynx_combat_melee", death_anim = "seq.npc_lynx_combat_death" }, + { id = "npc.dt2_strangled_ruins_t1_1", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_ruins_t1_2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_ruins_t1_3", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_ruins_t1_4", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_ruins_t2_1", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dt2_strangled_ruins_t2_2", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dt2_strangled_ruins_t2_3", source = "family:zombie_update_ready_arm_champion", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.dt2_strangled_survival_t1_1", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_survival_t1_2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_survival_t1_3", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_survival_t2_1", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dt2_strangled_temple_1", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.dt2_strangled_temple_2", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.dt2_wight_persten_npc", source = "family:abyssal_leech_ready", attack_anim = "seq.abyssal_leech_attack", defend_anim = "seq.abyssal_leech_defend", death_anim = "seq.abyssal_leech_death" }, + { id = "npc.dttd_ham_guard_1_postquest", source = "shield:obj.wooden_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.dttd_ham_guard_2_postquest", source = "shield:obj.wooden_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.dttd_ham_guard_3_postquest", source = "shield:obj.wooden_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.dttd_ham_guard_4_postquest", source = "shield:obj.wooden_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.dttd_ham_guard_5_postquest", source = "shield:obj.wooden_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.dttd_ham_guard_mill", source = "shield:obj.wooden_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.dttd_sigmund_magic", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.dttd_sigmund_melee", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.dttd_sigmund_ranged", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.dttd_sigmund_ranged_vs_zanik", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.dungeon_dagganoth_lighter", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.dungeon_dagganoth_lighter_deeper", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.dungeon_dagganoth_magic_monster", source = "family:wallasalki_ready", attack_anim = "seq.wallasalki_attack", defend_anim = "seq.wallasalki_defend", death_anim = "seq.wallasalki_death" }, + { id = "npc.dungeon_dagganoth_magic_monster_deeper", source = "family:wallasalki_ready", attack_anim = "seq.wallasalki_attack", defend_anim = "seq.wallasalki_defend", death_anim = "seq.wallasalki_death" }, + { id = "npc.dungeon_dagganoth_mainroom", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.dungeon_dagganoth_mainroom_deeper", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.dungeon_rat", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.dungeon_rat2", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.dungeon_rat3", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.dungeonspider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.dwarf_chaos", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dwarf_city_berserker1", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dwarf_city_berserker2", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dwarf_city_berserker3", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dwarf_city_black_guard1", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dwarf_city_black_guard2", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dwarf_city_black_guard3", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dwarf_city_black_guard4", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dwarf_mountain", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dwarf_normal", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.dwarf_rock_avatar_archer", source = "family:rock_avatar_ready", attack_anim = "seq.rock_avatar_attack", defend_anim = "seq.rock_avatar_block", death_anim = "seq.rock_avatar_death" }, + { id = "npc.dwarf_rock_avatar_archer_green", source = "family:rock_avatar_ready", attack_anim = "seq.rock_avatar_attack", defend_anim = "seq.rock_avatar_block", death_anim = "seq.rock_avatar_death" }, + { id = "npc.dwarf_rock_avatar_archer_yellow", source = "family:rock_avatar_ready", attack_anim = "seq.rock_avatar_attack", defend_anim = "seq.rock_avatar_block", death_anim = "seq.rock_avatar_death" }, + { id = "npc.dwarf_rock_avatar_mage", source = "family:rock_avatar_ready", attack_anim = "seq.rock_avatar_attack", defend_anim = "seq.rock_avatar_block", death_anim = "seq.rock_avatar_death" }, + { id = "npc.dwarf_rock_avatar_mage_green", source = "family:rock_avatar_ready", attack_anim = "seq.rock_avatar_attack", defend_anim = "seq.rock_avatar_block", death_anim = "seq.rock_avatar_death" }, + { id = "npc.dwarf_rock_avatar_mage_yellow", source = "family:rock_avatar_ready", attack_anim = "seq.rock_avatar_attack", defend_anim = "seq.rock_avatar_block", death_anim = "seq.rock_avatar_death" }, + { id = "npc.dwarf_rock_avatar_warrior", source = "family:rock_avatar_ready", attack_anim = "seq.rock_avatar_attack", defend_anim = "seq.rock_avatar_block", death_anim = "seq.rock_avatar_death" }, + { id = "npc.dwarf_rock_avatar_warrior_green", source = "family:rock_avatar_ready", attack_anim = "seq.rock_avatar_attack", defend_anim = "seq.rock_avatar_block", death_anim = "seq.rock_avatar_death" }, + { id = "npc.dwarf_rock_avatar_warrior_yellow", source = "family:rock_avatar_ready", attack_anim = "seq.rock_avatar_attack", defend_anim = "seq.rock_avatar_block", death_anim = "seq.rock_avatar_death" }, + { id = "npc.eaglepeak_uber_kebbit", source = "family:uber_kebbit_ready", attack_anim = "seq.uber_kebbit_attack", defend_anim = "seq.uber_kebbit_defend", death_anim = "seq.uber_kebbit_death" }, + { id = "npc.earth_nagua", source = "family:npc_djinn_earth01_idle01", attack_anim = "seq.npc_djinn_earth01_attack01", death_anim = "seq.npc_djinn_earth01_death01" }, + { id = "npc.earth_nagua_reinforced", source = "family:npc_djinn_earth01_idle02", attack_anim = "seq.npc_djinn_earth01_attack02" }, + { id = "npc.earth_wizard", source = "caster:obj.plainstaff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_stafforb_block", proj_travel = "spotanim.earthstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.elder_custodian_stalker", source = "family:npc_custodian_stalker04_ready01", attack_anim = "seq.npc_custodian_stalker04_attack01", death_anim = "seq.npc_custodian_stalker04_death01" }, + { id = "npc.elem1_qip_earth_elemental_rock_version", source = "family:earth_elemental_rework_ready", attack_anim = "seq.earth_elemental_rework_attack", defend_anim = "seq.earth_elemental_rework_defend", death_anim = "seq.earth_elemental_rework_death" }, + { id = "npc.elemental_air", source = "family:elemental_air_ready", attack_anim = "seq.elemental_air_attack", defend_anim = "seq.elemental_air_defend", death_anim = "seq.elemental_air_death" }, + { id = "npc.elemental_earth", source = "family:earth_elemental_rework_ready", attack_anim = "seq.earth_elemental_rework_attack", defend_anim = "seq.earth_elemental_rework_defend", death_anim = "seq.earth_elemental_rework_death" }, + { id = "npc.elemental_fire", source = "family:elemental_fire_ready", attack_anim = "seq.elemental_fire_attack", defend_anim = "seq.elemental_fire_defend", death_anim = "seq.elemental_fire_death" }, + { id = "npc.elemental_water", source = "family:elemental_water_ready", attack_anim = "seq.elemental_water_attack", defend_anim = "seq.elemental_water_defend", death_anim = "seq.elemental_water_death" }, + { id = "npc.elid_golem_black", source = "family:clay_golem_ready", attack_anim = "seq.clay_golem_attack", defend_anim = "seq.clay_golem_defend", death_anim = "seq.clay_golem_death" }, + { id = "npc.elid_golem_grey", source = "family:clay_golem_ready", attack_anim = "seq.clay_golem_attack", defend_anim = "seq.clay_golem_defend", death_anim = "seq.clay_golem_death" }, + { id = "npc.elid_golem_white", source = "family:clay_golem_ready", attack_anim = "seq.clay_golem_attack", defend_anim = "seq.clay_golem_defend", death_anim = "seq.clay_golem_death" }, + { id = "npc.elid_mayor_guard_1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.elid_mayor_guard_2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.elid_mayor_guard_3", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.elite_black_knight_1", source = "family:dh_sword_update_ready", attack_anim = "seq.dh_sword_update_slash", defend_anim = "seq.dh_sword_update_defend" }, + { id = "npc.elite_black_knight_2", source = "family:dh_sword_update_ready", attack_anim = "seq.dh_sword_update_slash", defend_anim = "seq.dh_sword_update_defend" }, + { id = "npc.elite_dark_mage_1", source = "caster:obj.plainstaff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_stafforb_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.elite_dark_mage_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.elite_dark_ranger_1", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.elite_dark_ranger_2", source = "ranger", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.elite_npc_1", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.elite_npc_2", source = "family:ork_update_weapon_ready", attack_anim = "seq.ork_update_double_grip_attack", defend_anim = "seq.ork_update_defend", death_anim = "seq.ork_update_death" }, + { id = "npc.enclave_guard", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.etc_guard1", source = "weapon:obj.viking_sword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.etc_guard2", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_shield_defence" }, + { id = "npc.fai_barbarian_1", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.fai_barbarian_10", source = "weapon:model:11792", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.fai_barbarian_11", source = "weapon:model:11793", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.fai_barbarian_12", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.fai_barbarian_13", source = "weapon:model:11789", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.fai_barbarian_14", source = "weapon:model:11790", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.fai_barbarian_2", source = "weapon:model:11793", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.fai_barbarian_3", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.fai_barbarian_4", source = "weapon:model:11792", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.fai_barbarian_5", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.fai_barbarian_6", source = "weapon:model:11793", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.fai_barbarian_7", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.fai_barbarian_8", source = "weapon:model:11789", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.fai_barbarian_female_1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.fai_barbarian_female_2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.fai_barbarian_female_3", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.fai_barbarian_female_4", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.fai_barbarian_goblin_armed_2", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.fai_barbarian_goblin_armed_3", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.fai_barbarian_goblin_armed_4", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.fai_black_guard_01", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_black_guard_02", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_black_guard_03", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_black_guard_04", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_dark_wizard_old_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.fai_dark_wizard_old_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.fai_dark_wizard_young_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.fai_dark_wizard_young_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.fai_dwarf_worker_01", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_dwarf_worker_02", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_dwarf_worker_03", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_dwarf_worker_04", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_dwarf_worker_05", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_dwarf_worker_06", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_dwarf_worker_07", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_dwarf_worker_08", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_dwarf_worker_09", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_falador_dwarf_normal1", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_falador_dwarf_normal2", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_falador_dwarf_normal3", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.fai_falador_guard1", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_shield_defence" }, + { id = "npc.fai_falador_guard1_variant01", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_shield_defence" }, + { id = "npc.fai_falador_guard1_variant02", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_shield_defence" }, + { id = "npc.fai_falador_guard2", source = "weapon:obj.phoenix_crossbow", attack_anim = "seq.xbows_human_fire_and_reload_pvn", attack_type = "category.attacktype_standard", attack_sound = 2695, proj_travel = "spotanim.crossbowbolt_travel", proj_type = "projanim.bolt", attack_range = 5 }, + { id = "npc.fai_falador_guard2_f", source = "weapon:obj.phoenix_crossbow", attack_anim = "seq.xbows_human_fire_and_reload_pvn", attack_type = "category.attacktype_standard", attack_sound = 2695, proj_travel = "spotanim.crossbowbolt_travel", proj_type = "projanim.bolt", attack_range = 5 }, + { id = "npc.fai_falador_guard3", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_shield_defence" }, + { id = "npc.fai_falador_guard4", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.fai_falador_guard5", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.fai_falador_guard6", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.fai_varrock_guard", source = "family:fai_varrock_guard_ready", attack_anim = "seq.fai_varrock_guard_attack", defend_anim = "seq.fai_varrock_guard_defend", death_anim = "seq.fai_varrock_guard_death" }, + { id = "npc.fai_varrock_guard02", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.fai_varrock_guard02_variant01", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.fai_varrock_guard02_variant02", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.fai_varrock_guard_captain", source = "family:fai_varrock_guard_ready", attack_anim = "seq.fai_varrock_guard_attack", defend_anim = "seq.fai_varrock_guard_defend", death_anim = "seq.fai_varrock_guard_death" }, + { id = "npc.fai_varrock_guard_captain02", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.fairy2_dryhadguardian_1", source = "family:dryhad_ready", attack_anim = "seq.dryhad_attack", defend_anim = "seq.dryhad_block", death_anim = "seq.dryhad_death" }, + { id = "npc.fairy2_dryhadguardian_2", source = "family:dryhad_ready", attack_anim = "seq.dryhad_attack", defend_anim = "seq.dryhad_block", death_anim = "seq.dryhad_death" }, + { id = "npc.fairy2_dryhadguardian_3", source = "family:dryhad_ready", attack_anim = "seq.dryhad_attack", defend_anim = "seq.dryhad_block", death_anim = "seq.dryhad_death" }, + { id = "npc.fairy2_dryhadguardian_4", source = "family:dryhad_ready", attack_anim = "seq.dryhad_attack", defend_anim = "seq.dryhad_block", death_anim = "seq.dryhad_death" }, + { id = "npc.fairy2_dryhadguardian_5", source = "family:dryhad_ready", attack_anim = "seq.dryhad_attack", defend_anim = "seq.dryhad_block", death_anim = "seq.dryhad_death" }, + { id = "npc.fairy2_dryhadguardian_6", source = "family:dryhad_ready", attack_anim = "seq.dryhad_attack", defend_anim = "seq.dryhad_block", death_anim = "seq.dryhad_death" }, + { id = "npc.fairy2_golemguardian_1", source = "family:golem_ready", attack_anim = "seq.golem_attack", defend_anim = "seq.golem_block", death_anim = "seq.golem_death" }, + { id = "npc.fairy2_golemguardian_2", source = "family:golem_ready", attack_anim = "seq.golem_attack", defend_anim = "seq.golem_block", death_anim = "seq.golem_death" }, + { id = "npc.fairy2_golemguardian_3", source = "family:golem_ready", attack_anim = "seq.golem_attack", defend_anim = "seq.golem_block", death_anim = "seq.golem_death" }, + { id = "npc.fairy2_golemguardian_4", source = "family:golem_ready", attack_anim = "seq.golem_attack", defend_anim = "seq.golem_block", death_anim = "seq.golem_death" }, + { id = "npc.fairy2_golemguardian_5", source = "family:golem_ready", attack_anim = "seq.golem_attack", defend_anim = "seq.golem_block", death_anim = "seq.golem_death" }, + { id = "npc.fairy2_golemguardian_6", source = "family:golem_ready", attack_anim = "seq.golem_attack", defend_anim = "seq.golem_block", death_anim = "seq.golem_death" }, + { id = "npc.fairy2_rabbit1", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.fairy2_rabbit2", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.fairy2_rivertrollguardian_1", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fairy2_rivertrollguardian_2", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fairy2_rivertrollguardian_3", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fairy2_rivertrollguardian_4", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fairy2_rivertrollguardian_5", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fairy2_rivertrollguardian_6", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fairy2_stag", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.fairy_cow", source = "family:cow_just_ready_update", attack_anim = "seq.cow_attack", defend_anim = "seq.cow_block", death_anim = "seq.cow_death" }, + { id = "npc.fairy_tanglefoot", source = "family:tanglefoot_ready", attack_anim = "seq.tanglefoot_attack", defend_anim = "seq.tanglefoot_block", death_anim = "seq.tanglefoot_death" }, + { id = "npc.fairy_tanglefoot_sml1", source = "family:tanglefoot_ready", attack_anim = "seq.tanglefoot_attack", defend_anim = "seq.tanglefoot_block", death_anim = "seq.tanglefoot_death" }, + { id = "npc.fairy_tanglefoot_sml2", source = "family:tanglefoot_ready", attack_anim = "seq.tanglefoot_attack", defend_anim = "seq.tanglefoot_block", death_anim = "seq.tanglefoot_death" }, + { id = "npc.fairytale2_gorak", source = "family:gorak_ready", attack_anim = "seq.gorak_attack", defend_anim = "seq.gorak_defend", death_anim = "seq.gorak_death" }, + { id = "npc.falador_doric_area_guard", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_shield_defence" }, + { id = "npc.farm_chicken_brown", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.farm_chicken_brown_indoors", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.farm_chicken_brown_outdoors", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.farm_chicken_dark_brown", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.farm_chicken_dark_brown_indoors", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.farm_chicken_dark_brown_outdoors", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.farm_chicken_tan", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.farm_chicken_tan_indoors", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.farm_chicken_tan_outdoors", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.farming_market_guard", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.favour_gangster_dwarf", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.favour_gangster_dwarf_2", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.favour_gangster_dwarf_3", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.favour_guard_long_beard", source = "shield:obj.wooden_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.favour_guard_male_bearded", source = "shield:obj.wooden_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.favour_guard_male_no_beard", source = "shield:obj.wooden_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.fd_damis_normal", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.fd_damis_tougher", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.fenk_experiment_1", source = "family:homolupus_ready", attack_anim = "seq.homolupus_attack", defend_anim = "seq.homolupus_defend", death_anim = "seq.homolupus_death" }, + { id = "npc.fenk_experiment_2", source = "family:waa_homo_arachnus_ready", attack_anim = "seq.waa_homo_arachnus_attack", defend_anim = "seq.waa_homo_arachnus_defend", death_anim = "seq.waa_homo_arachnus_death" }, + { id = "npc.fenk_experiment_3", source = "family:waa_ovinocanus_ready", attack_anim = "seq.waa_ovinocanus_attack", defend_anim = "seq.waa_ovinocanus_defend", death_anim = "seq.waa_ovinocanus_death" }, + { id = "npc.feud_arabian_guard1_1", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.feud_arabian_guard1_2", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.feud_arabian_guard2_1", source = "weapon:obj.blackjack_oak", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2512, defend_anim = "seq.human_blunt_block" }, + { id = "npc.feud_arabian_guard2_2", source = "weapon:obj.blackjack_oak", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2512, defend_anim = "seq.human_blunt_block" }, + { id = "npc.feud_bandit_toughguy", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.feud_desert_snake", source = "family:snake_ready", attack_anim = "seq.snake_attack", defend_anim = "seq.snake_block", death_anim = "seq.snake_death" }, + { id = "npc.feud_desert_snake_outside", source = "family:snake_ready", attack_anim = "seq.snake_attack", defend_anim = "seq.snake_block", death_anim = "seq.snake_death" }, + { id = "npc.feud_egyptian_doorman_1", source = "weapon:obj.blackjack_oak", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2512, defend_anim = "seq.human_blunt_block" }, + { id = "npc.feud_egyptian_doorman_2", source = "weapon:obj.blackjack_oak", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2512, defend_anim = "seq.human_blunt_block" }, + { id = "npc.feud_menap_toughguy", source = "weapon:obj.blackjack_oak", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2512, defend_anim = "seq.human_blunt_block" }, + { id = "npc.fever_pirate_enemy_01", source = "weapon:obj.bronze_halberd", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.fever_pirate_enemy_02", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.fever_pirate_enemy_04", source = "weapon:obj.bronze_halberd", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.fever_pirate_enemy_06", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.fever_pirate_enemy_08", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.fever_pirate_enemy_09", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.ffg_troll_1", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.ffg_troll_2", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.ffg_troll_3", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fire_bird", source = "family:firebird_update_ready", attack_anim = "seq.firebird_update_attack", defend_anim = "seq.firebird_update_defend", death_anim = "seq.firebird_update_death" }, + { id = "npc.fire_wizard", source = "caster:obj.plainstaff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_stafforb_block", proj_travel = "spotanim.firestrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.firegiant", source = "family:giant_update_fire_ready", attack_anim = "seq.giant_update_fire_attack", defend_anim = "seq.giant_update_fire_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.firegiant2", source = "family:giant_update_fire_sword_ready", attack_anim = "seq.giant_update_fire_sword_attack", defend_anim = "seq.giant_update_fire_sword_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.firegiant3", source = "family:giant_update_fire_sword_ready", attack_anim = "seq.giant_update_fire_sword_attack", defend_anim = "seq.giant_update_fire_sword_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.firegiant_big", source = "family:giant_update_fire_ready", attack_anim = "seq.giant_update_fire_attack", defend_anim = "seq.giant_update_fire_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.firegiant_big2", source = "family:giant_update_fire_sword_ready", attack_anim = "seq.giant_update_fire_sword_attack", defend_anim = "seq.giant_update_fire_sword_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.firegiant_big3", source = "family:giant_update_fire_sword_ready", attack_anim = "seq.giant_update_fire_sword_attack", defend_anim = "seq.giant_update_fire_sword_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.firegiant_strongholdcave_1", source = "family:giant_update_fire_sword_ready", attack_anim = "seq.giant_update_fire_sword_attack", defend_anim = "seq.giant_update_fire_sword_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.firegiant_strongholdcave_2", source = "family:giant_update_fire_sword_ready", attack_anim = "seq.giant_update_fire_sword_attack", defend_anim = "seq.giant_update_fire_sword_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.firegiant_strongholdcave_3", source = "family:giant_update_fire_ready", attack_anim = "seq.giant_update_fire_attack", defend_anim = "seq.giant_update_fire_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.firegiant_strongholdcave_4", source = "family:giant_update_fire_sword_ready", attack_anim = "seq.giant_update_fire_sword_attack", defend_anim = "seq.giant_update_fire_sword_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.forester", source = "weapon:obj.iron_axe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.fortressguard", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.fortressguard_01", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.fortressguard_02", source = "weapon:obj.bronze_halberd", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_shield_defence" }, + { id = "npc.fortressguard_03", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_shield_defence" }, + { id = "npc.fortressguard_04", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.fossil_ammonitecrab", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.fossil_crazy_archaeologist", source = "shield:obj.unfinished_saradominbook", defend_anim = "seq.human_shield_defence" }, + { id = "npc.fossil_hoopsnake", source = "family:fossil_npc_hoopsnake_idle", attack_anim = "seq.fossil_npc_hoopsnake_attack", defend_anim = "seq.fossil_npc_hoopsnake_defend" }, + { id = "npc.fossil_lobster", source = "family:lobster_update_ready", attack_anim = "seq.lobster_update_attack", defend_anim = "seq.lobster_update_defend", death_anim = "seq.lobster_update_death" }, + { id = "npc.fossil_mine_lavabeast", source = "family:fossil_npc_lavabeast_idle", attack_anim = "seq.fossil_npc_lavabeast_attack", defend_anim = "seq.fossil_npc_lavabeast_defend" }, + { id = "npc.fossil_tarmonster", source = "family:fossil_npc_lavabeast_idle", attack_anim = "seq.fossil_npc_lavabeast_attack", defend_anim = "seq.fossil_npc_lavabeast_defend" }, + { id = "npc.fossil_zygomite", source = "family:zygomite_ready", attack_anim = "seq.zygomite_attack", defend_anim = "seq.zygomite_defend", death_anim = "seq.zygomite_death" }, + { id = "npc.fourdiamonds_assasin", source = "weapon:obj.dragon_dagger", attack_anim = "seq.human_ddagger_lunge", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_ddagger_block" }, + { id = "npc.fourdiamonds_sword_bandit_1", source = "weapon:obj.iron_dagger", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_sword_def" }, + { id = "npc.fourdiamonds_sword_bandit_free", source = "weapon:obj.iron_dagger", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_sword_def" }, + { id = "npc.fris_baby_troll", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_baby_troll_lowxp", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_baby_troll_pc", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_troll_bodyguard", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_troll_bodyguard_lowxp", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_troll_bodyguard_pc", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_troll_king_true", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_trollf", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_trollf_lowxp", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_trollf_pc", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_trollm", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_trollm_lowxp", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.fris_trollm_pc", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.frog_quest_cuthbert_combat", source = "family:giant_frog_ready", attack_anim = "seq.giant_frog_attack", defend_anim = "seq.giant_frog_block", death_anim = "seq.giant_frog_death" }, + { id = "npc.frost_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.frost_nagua", source = "family:npc_djinn_01_idle", attack_anim = "seq.npc_djinn_01_melee" }, + { id = "npc.frostcrab", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.frostcrab_small", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.ga_beast", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.galvek_earth", source = "family:galvek_grounded_ready", attack_anim = "seq.galvek_grounded_slash", death_anim = "seq.galvek_death" }, + { id = "npc.galvek_fire", source = "family:galvek_grounded_ready", attack_anim = "seq.galvek_grounded_slash", death_anim = "seq.galvek_death" }, + { id = "npc.galvek_water", source = "family:galvek_flight_ready", attack_anim = "seq.galvek_grounded_slash", death_anim = "seq.galvek_death" }, + { id = "npc.galvek_wind", source = "family:galvek_flight_ready", attack_anim = "seq.galvek_grounded_slash", death_anim = "seq.galvek_death" }, + { id = "npc.gargboss_dusk_phase1_defensive", source = "family:gg_dusk_idle_defensive", defend_anim = "seq.gg_dusk_defend", death_anim = "seq.gg_dusk_death" }, + { id = "npc.gargboss_dusk_phase2_attacking", source = "family:gg_dusk_idle", defend_anim = "seq.gg_dusk_defend", death_anim = "seq.gg_dusk_death" }, + { id = "npc.gargboss_dusk_phase3_defensive", source = "family:gg_dusk_idle_defensive", defend_anim = "seq.gg_dusk_defend", death_anim = "seq.gg_dusk_death" }, + { id = "npc.gargboss_dusk_phase4", source = "family:gg_dusk_enrage_idle", attack_anim = "seq.gg_dusk_enrage_attack_slash", defend_anim = "seq.gg_dusk_defend", death_anim = "seq.gg_dusk_death" }, + { id = "npc.gb_growthling", source = "family:tanglefoot_ready", attack_anim = "seq.tanglefoot_attack", defend_anim = "seq.tanglefoot_block", death_anim = "seq.tanglefoot_death" }, + { id = "npc.gb_mossgiant", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.gemstone_crab", source = "family:crab_boss_idle", attack_anim = "seq.crab_boss_attack", defend_anim = "seq.crab_block", death_anim = "seq.crab_boss_death" }, + { id = "npc.gh_giant1", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.gh_giant2", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.gh_giant3", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.gh_warrior_1", source = "weapon:obj.brut_bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_shield_defence" }, + { id = "npc.gh_warrior_2", source = "shield:obj.warguild_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.gh_warrior_3", source = "weapon:model:11792", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.ghast_vis", source = "family:ghast_ready", attack_anim = "seq.ghast_attack", defend_anim = "seq.ghast_block", death_anim = "seq.ghast_death" }, + { id = "npc.ghost", source = "family:ghost_update_tendrill_ready", attack_anim = "seq.ghost_update_tendrill_attack", attack_sound = 436, defend_anim = "seq.ghost_update_tendrill_defend", defend_sound = 439, death_anim = "seq.ghost_update_tendrill_death", death_sound = 438 }, + { id = "npc.ghost2", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost2_unaggressive", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost3", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost3_unaggressive", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost4", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost4_unaggressive", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost5", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost5_unaggressive", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost6", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost6_unaggressive", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost7", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost7_unaggressive", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost8", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost8_unaggressive", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ghost_unaggressive", source = "family:ghost_update_tendrill_ready", attack_anim = "seq.ghost_update_tendrill_attack", attack_sound = 436, defend_anim = "seq.ghost_update_tendrill_defend", defend_sound = 439, death_anim = "seq.ghost_update_tendrill_death", death_sound = 438 }, + { id = "npc.giant", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.giant2", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.giant3", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.giant4", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.giant5", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.giant6", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.giant_frog", source = "family:giant_frog_ready", attack_anim = "seq.giant_frog_attack", defend_anim = "seq.giant_frog_block", death_anim = "seq.giant_frog_death" }, + { id = "npc.giant_lobster", source = "family:lobster_update_ready", attack_anim = "seq.lobster_update_attack", defend_anim = "seq.lobster_update_defend", death_anim = "seq.lobster_update_death" }, + { id = "npc.giant_mosquito", source = "family:tbw_mosquito_ready", attack_anim = "seq.tbw_mosquito_attack", defend_anim = "seq.tbw_mosquito_defend", death_anim = "seq.tbw_mosquito_death" }, + { id = "npc.giant_rockcrab", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.giant_rockcrab_crypt_of_tonali", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.giant_rockcrab_deeper", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.giant_wasp", source = "family:ti_giant_wasp_ready_walk", attack_anim = "seq.ti_giant_wasp_attack", defend_anim = "seq.ti_giant_wasp_defend", death_anim = "seq.ti_giant_wasp_death" }, + { id = "npc.giant_wasp_red", source = "family:ti_giant_wasp_ready_walk", attack_anim = "seq.ti_giant_wasp_attack", defend_anim = "seq.ti_giant_wasp_defend", death_anim = "seq.ti_giant_wasp_death" }, + { id = "npc.giantrat1", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.giantrat2", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.giantrat3", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.giantskeleton", source = "family:skeleton_update_giant_ready", attack_anim = "seq.skeleton_update_giant_attack", defend_anim = "seq.skeleton_update_giant_defend", death_anim = "seq.skeleton_update_giant_death" }, + { id = "npc.giantskeleton2", source = "family:skeleton_update_giant_vary2_ready", attack_anim = "seq.skeleton_update_giant_attack", defend_anim = "seq.skeleton_update_giant_defend", death_anim = "seq.skeleton_update_giant_death" }, + { id = "npc.giantspider2", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.gim_boar", source = "family:npc_boar_combat_idle", attack_anim = "seq.npc_boar_combat_melee", death_anim = "seq.npc_boar_combat_death" }, + { id = "npc.gnome", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gnome_archer", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gnome_driver", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gnome_mage", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gnomechild", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gnomechildblue", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gnomechildgreen", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gnomefemale", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gnomefemale_dskinned", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gnomekingsguard", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gnomekingsguard2", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gnomeknight", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.goblin", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_armed_mcannon_1", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.goblin_armed_mcannon_2", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_armed_mcannon_3", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_armed_mcannon_4", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_armed_mcannon_5", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_armed_melee_1", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_armed_melee_2", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_armed_melee_3", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_armed_melee_4", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_green_soldier_1", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.goblin_green_soldier_2", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_green_soldier_3", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.goblin_green_soldier_4", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_green_soldier_6", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_green_soldier_7", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.goblin_green_soldier_8", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.goblin_greenarmour", source = "family:goblin_ready_spear", attack_anim = "seq.goblin_attack_unarmed", defend_anim = "seq.goblin_block", death_anim = "seq.goblin_death" }, + { id = "npc.goblin_helmet", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_red_soldier_1", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_red_soldier_2", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_red_soldier_3", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.goblin_red_soldier_4", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_red_soldier_6", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_red_soldier_7", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.goblin_red_soldier_8", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.goblin_red_soldier_underground", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_redarmour", source = "family:goblin_ready_spear", attack_anim = "seq.goblin_attack_unarmed", defend_anim = "seq.goblin_block", death_anim = "seq.goblin_death" }, + { id = "npc.goblin_unarmed_mcannon_1", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_mcannon_2", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_mcannon_3", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_mcannon_4", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_mcannon_5", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_mcannon_6", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_mcannon_7", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_mcannon_8", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_mcannon_9", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_melee_in_1", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_melee_in_2", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_melee_in_3", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_melee_in_4", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_melee_in_5", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_melee_in_6", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_melee_in_7", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.goblin_unarmed_melee_in_8", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.godwars_ancient_cyclops", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.godwars_ancient_cyclops2", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.godwars_ancient_hellhound", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.godwars_ancient_hobgoblin", source = "family:hobgoblin_ready", attack_anim = "seq.hobgoblin_attack", defend_anim = "seq.hobgoblin_block", death_anim = "seq.hobgoblin_death" }, + { id = "npc.godwars_ancient_imp", source = "family:imp_ready", attack_anim = "seq.imp_attack", defend_anim = "seq.imp_block", death_anim = "seq.imp_death" }, + { id = "npc.godwars_ancient_jogre", source = "family:champions_jogre_ready", attack_anim = "seq.champions_jogre_attack" }, + { id = "npc.godwars_ancient_ogre", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.godwars_ancient_ork1", source = "family:ork_update_weapon_ready", attack_anim = "seq.ork_update_double_grip_attack", defend_anim = "seq.ork_update_defend", death_anim = "seq.ork_update_death" }, + { id = "npc.godwars_ancient_ork2", source = "family:ork_update_weapon_ready", attack_anim = "seq.ork_update_double_grip_attack", defend_anim = "seq.ork_update_defend", death_anim = "seq.ork_update_death" }, + { id = "npc.godwars_ancient_ork3", source = "family:ork_update_weapon_ready", attack_anim = "seq.ork_update_double_grip_attack", defend_anim = "seq.ork_update_defend", death_anim = "seq.ork_update_death" }, + { id = "npc.godwars_ancient_ork4", source = "family:ork_update_weapon_ready", attack_anim = "seq.ork_update_double_grip_attack", defend_anim = "seq.ork_update_defend", death_anim = "seq.ork_update_death" }, + { id = "npc.godwars_ancient_saradomin_wizard", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.godwars_ancient_werewolf1", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.godwars_ancient_werewolf2", source = "family:werewolf_update_ready", attack_anim = "seq.werewolf_update_attack", defend_anim = "seq.werewolf_update_defend", death_anim = "seq.werewolf_update_death" }, + { id = "npc.godwars_armadyl_avatar", source = "family:godwars_armadyl_avatar_ready", defend_anim = "seq.godwars_armadyl_avatar_defend", death_anim = "seq.godwars_armadyl_avatar_death" }, + { id = "npc.godwars_armadyl_female_armor01_blue", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_female_armor01_green", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_female_armor01_red", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_female_armor02_blue", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_female_armor02_green", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_female_armor02_red", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_female_armor03_blue", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_male_armor01_blue", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_male_armor01_green", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_male_armor01_red", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_male_armor02_blue", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_male_armor02_green", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_male_armor02_red", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_male_armor03_green", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_armadyl_male_armor03_red", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_bloodveld", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.godwars_goblin1", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.godwars_goblin2", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.godwars_goblin4", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.godwars_goblin5", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.godwars_gorak", source = "family:gorak_ready", attack_anim = "seq.gorak_attack", defend_anim = "seq.gorak_defend", death_anim = "seq.gorak_death" }, + { id = "npc.godwars_icefiend_1", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.godwars_pyrefiend_1", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.godwars_saradomin_avatar", source = "family:godwars_saradomin_ready", attack_anim = "seq.godwars_saradomin_attack", defend_anim = "seq.godwars_saradomin_defend", death_anim = "seq.godwars_saradomin_death" }, + { id = "npc.godwars_saradomin_knight_1", source = "weapon:stance:human_dh_weapon_ready", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.godwars_spiritual_armadyl_mage", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_spiritual_armadyl_ranger", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_spiritual_armadyl_warrior", source = "family:godwars_armadyl_ready", defend_anim = "seq.godwars_armadyl_defend", death_anim = "seq.godwars_armadyl_death" }, + { id = "npc.godwars_spiritual_bandos_mage", source = "family:ork_update_weapon_ready", attack_anim = "seq.ork_update_double_grip_attack", defend_anim = "seq.ork_update_defend", death_anim = "seq.ork_update_death" }, + { id = "npc.godwars_spiritual_bandos_ranger", source = "family:ork_update_weapon_ready", attack_anim = "seq.ork_update_double_grip_attack", defend_anim = "seq.ork_update_defend", death_anim = "seq.ork_update_death" }, + { id = "npc.godwars_spiritual_bandos_warrior", source = "family:ork_update_weapon_ready", attack_anim = "seq.ork_update_double_grip_attack", defend_anim = "seq.ork_update_defend", death_anim = "seq.ork_update_death" }, + { id = "npc.godwars_spiritual_saradomin_mage", source = "caster:obj.saradomin_staff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_blunt_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.godwars_spiritual_saradomin_ranger", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.godwars_spiritual_saradomin_warrior", source = "weapon:model:11793", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_shield_defence" }, + { id = "npc.godwars_spiritual_zamorak_mage", source = "caster:obj.zamorak_staff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_blunt_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.godwars_spiritual_zamorak_ranger", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.godwars_spiritual_zamorak_warrior", source = "shield:obj.bronze_kiteshield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.godwars_zamorak_avatar", source = "family:godwars_zamorak_ready", attack_anim = "seq.godwars_zamorak_attack", defend_anim = "seq.godwars_zamorak_defend", death_anim = "seq.godwars_zamorak_death" }, + { id = "npc.golem", source = "family:golem_ready", attack_anim = "seq.golem_attack", defend_anim = "seq.golem_block", death_anim = "seq.golem_death" }, + { id = "npc.gorad", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.grandtree_blackdemon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.grave_scorpion", source = "family:small_scorpion_update_ready", attack_anim = "seq.small_scorpion_update_attack", defend_anim = "seq.small_scorpion_update_defend", death_anim = "seq.small_scorpion_update_death" }, + { id = "npc.great_conch_hermit_crab", source = "family:hermit_crab_ready", attack_anim = "seq.hermit_crab_attack", death_anim = "seq.hermit_crab_death" }, + { id = "npc.greater_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.greater_demon2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.greater_demon3", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.greater_demon4", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.greater_demon5", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.greater_demon_strongholdcave_1", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.greater_demon_strongholdcave_2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.greater_demon_strongholdcave_3", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.green_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.green_dragon2", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.green_dragon3", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.green_dragon4", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.green_dragon5", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.green_dragon_myth_guild", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.green_dragon_myth_guild_2", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.grim_experiment", source = "family:grim_experiment_no2_ready", attack_anim = "seq.grim_experiment_no2_attack", defend_anim = "seq.grim_experiment_no2_defend", death_anim = "seq.grim_experiment_no2_death" }, + { id = "npc.grim_giant_mouse", source = "family:grim_giant_mouse_ready", attack_anim = "seq.grim_giant_mouse_attack", defend_anim = "seq.grim_giant_mouse_defend", death_anim = "seq.grim_giant_mouse_death" }, + { id = "npc.grim_giant_mouse2", source = "family:grim_giant_mouse_ready", attack_anim = "seq.grim_giant_mouse_attack", defend_anim = "seq.grim_giant_mouse_defend", death_anim = "seq.grim_giant_mouse_death" }, + { id = "npc.grim_glod", source = "family:grim_giant_ready", attack_anim = "seq.grim_giant_attack", defend_anim = "seq.grim_giant_defend", death_anim = "seq.grim_giant_death" }, + { id = "npc.grimy_lizard", source = "family:grimy_lizard_ready", attack_anim = "seq.grimy_lizard_melee", death_anim = "seq.grimy_lizard_death" }, + { id = "npc.grip", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.gryphon_boss", source = "family:gryphon_boss_idle01", death_anim = "seq.gryphon_boss_death01" }, + { id = "npc.guard1", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_shield_defence" }, + { id = "npc.guard1_variant01", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_shield_defence" }, + { id = "npc.guard_bandit", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.guarddog", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.guild_towerarcher1", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.guild_towerarcher2", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.guild_towerarcher3", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.guild_towerarcher4", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.gunthor_the_brave", source = "weapon:model:11793", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.guthix_mage", source = "family:gnome_ready", attack_anim = "seq.gnome_unarmed_attack", defend_anim = "seq.gnome_block", death_anim = "seq.gnome_death" }, + { id = "npc.gwenith_white_wolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.harmless_island_albino_bat", source = "family:bat_rework_ready", attack_anim = "seq.bat_rework_attack", defend_anim = "seq.bat_rework_defend", death_anim = "seq.bat_rework_death" }, + { id = "npc.harmless_island_cave_horror_alpha", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.harmless_island_cave_horror_guard", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.harmless_island_cave_horror_small_guard", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.harmless_island_cave_horror_worker", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.harmless_island_cave_horror_young_worker", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.harmless_island_jungle_horror_guard", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.harmless_island_jungle_horror_warrior", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.harmless_island_jungle_horror_worker", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.harmless_island_jungle_horror_young_warrior", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.harmless_island_jungle_horror_young_worker", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.harmless_island_monkey", source = "family:monkey_ready", attack_anim = "seq.monkey_attack", defend_anim = "seq.monkey_block", death_anim = "seq.monkey_death" }, + { id = "npc.harmless_island_mosquito", source = "family:tbw_mosquito_ready", attack_anim = "seq.tbw_mosquito_attack", defend_anim = "seq.tbw_mosquito_defend", death_anim = "seq.tbw_mosquito_death" }, + { id = "npc.harmless_island_snake", source = "family:snake_ready", attack_anim = "seq.snake_attack", defend_anim = "seq.snake_block", death_anim = "seq.snake_death" }, + { id = "npc.hauntedmine_boss_ghost", source = "family:ghost_update_tendrill_ready", attack_anim = "seq.ghost_update_tendrill_attack", attack_sound = 436, defend_anim = "seq.ghost_update_tendrill_defend", defend_sound = 439, death_anim = "seq.ghost_update_tendrill_death", death_sound = 438 }, + { id = "npc.hauntedmine_suprise_ghost", source = "family:ghost_update_tendrill_ready", attack_anim = "seq.ghost_update_tendrill_attack", attack_sound = 436, defend_anim = "seq.ghost_update_tendrill_defend", defend_sound = 439, death_anim = "seq.ghost_update_tendrill_death", death_sound = 438 }, + { id = "npc.headthief_blanket", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.hellhound", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.hellhound_strongholdcave", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.hero", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.hero_variant01", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.hespori", source = "family:hespori_ready", death_anim = "seq.hespori_death" }, + { id = "npc.hespori_healer_active", source = "family:hespori_healer_ready", death_anim = "seq.hespori_healer_dead" }, + { id = "npc.highwayman", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_sword_def" }, + { id = "npc.highwayman2", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_sword_def" }, + { id = "npc.hillgiant_boss", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.hobgoblin_armed", source = "family:hobgoblin_ready", attack_anim = "seq.hobgoblin_attack", defend_anim = "seq.hobgoblin_block", death_anim = "seq.hobgoblin_death" }, + { id = "npc.hobgoblin_unarmed", source = "family:hobgoblin_ready", attack_anim = "seq.hobgoblin_attack", defend_anim = "seq.hobgoblin_block", death_anim = "seq.hobgoblin_death" }, + { id = "npc.holthion_vis", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.horror_dagannoth_jr4", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagannoth_medium", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagannoth_medium_darker", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagannoth_medium_lighter", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagganoth_air", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagganoth_earth", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagganoth_fire", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagganoth_jr", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagganoth_jr_darker", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagganoth_jr_lighter", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagganoth_melee", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagganoth_ranged", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_dagganoth_water", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.horror_rockcrab", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.horror_rockcrab_small", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.hos_town_guard_01", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.hos_town_guard_02", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.hos_town_guard_03", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.hos_town_guard_04", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.hosdun_druid", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.hosdun_shaeded_beast", source = "family:lotr_tarn_razorlor_mutant_ready", attack_anim = "seq.lotr_tarn_razorlor_mutant_attack", defend_anim = "seq.lotr_tarn_razorlor_mutant_defend", death_anim = "seq.lotr_tarn_razorlor_mutant_death" }, + { id = "npc.hosdun_spider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.hosidius_guarddog", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.hosidiusquest_snake", source = "family:royal_sea_snake_ready", attack_anim = "seq.royal_sea_snake_attack", defend_anim = "seq.royal_sea_snake_defend", death_anim = "seq.royal_sea_snake_death" }, + { id = "npc.house_ghost", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.house_ghost2", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.house_ghost3", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.house_ghost4", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.house_ghost5", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.huey_head", source = "family:huey_idle", attack_anim = "seq.huey_attack_melee" }, + { id = "npc.huey_head_invulnerable", source = "family:huey_shield_idle", attack_anim = "seq.huey_shield_attack_melee" }, + { id = "npc.hundred_culinaromancer_final", source = "weapon:obj.hundred_cleaver", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.hundred_ilm_snake", source = "family:royal_sea_snake_ready", attack_anim = "seq.royal_sea_snake_attack", defend_anim = "seq.royal_sea_snake_defend", death_anim = "seq.royal_sea_snake_death" }, + { id = "npc.hundred_minion1", source = "family:agrith_nana_ready", attack_anim = "seq.agrith_nana_attack", defend_anim = "seq.agrith_nana_block", death_anim = "seq.agrith_nana_death" }, + { id = "npc.hundred_minion2", source = "family:rock_ready", attack_anim = "seq.rock_attack", defend_anim = "seq.rock_parry", death_anim = "seq.rock_death" }, + { id = "npc.hundred_minion4", source = "family:dessourt_ready", defend_anim = "seq.dessourt_block", death_anim = "seq.dessourt_death" }, + { id = "npc.hundred_minion5_air", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.hundred_minion5_earth", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.hundred_minion5_fire", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.hundred_minion5_melee", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.hundred_minion5_ranged", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.hundred_minion5_water", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.hundred_pirate_giant_crab", source = "family:crab_ready", attack_anim = "seq.crab_attack", defend_anim = "seq.crab_block", death_anim = "seq.crab_death" }, + { id = "npc.hundred_pirate_giant_crab_2", source = "family:crab_ready", attack_anim = "seq.crab_attack", defend_anim = "seq.crab_block", death_anim = "seq.crab_death" }, + { id = "npc.hundred_pirate_giant_mudskipper", source = "family:mudskipper_ready", attack_anim = "seq.mudskipper_attack", defend_anim = "seq.mudskipper_block", death_anim = "seq.mudskipper_death" }, + { id = "npc.hundred_pirate_giant_mudskipper_2", source = "family:mudskipper_ready", attack_anim = "seq.mudskipper_attack", defend_anim = "seq.mudskipper_block", death_anim = "seq.mudskipper_death" }, + { id = "npc.hunting_chinchompa", source = "family:hunting_chinchompa_ready", death_anim = "seq.hunting_chinchompa_death" }, + { id = "npc.hunting_chinchompa_big", source = "family:hunting_chinchompa_ready", death_anim = "seq.hunting_chinchompa_death" }, + { id = "npc.hunting_chinchompa_black", source = "family:hunting_chinchompa_ready", death_anim = "seq.hunting_chinchompa_death" }, + { id = "npc.hydra", source = "family:hydra_lesser_ready", death_anim = "seq.hydra_lesser_death" }, + { id = "npc.hydraboss", source = "family:hydra_stage_1_ready", attack_anim = "seq.hydra_stage_1_switch_attack", death_anim = "seq.hydra_stage_1_death" }, + { id = "npc.hydraboss_2", source = "family:hydra_stage_4_ready", attack_anim = "seq.hydra_stage_4_switch_attack", death_anim = "seq.hydra_stage_4_death" }, + { id = "npc.hydraboss_3", source = "family:hydra_stage_3_ready", attack_anim = "seq.hydra_stage_3_switch_attack", death_anim = "seq.hydra_stage_3_death" }, + { id = "npc.hydraboss_4", source = "family:hydra_stage_2_ready", attack_anim = "seq.hydra_stage_2_switch_attack", death_anim = "seq.hydra_stage_2_death" }, + { id = "npc.hydraboss_p2_transition", source = "family:hydra_stage_2_ready", attack_anim = "seq.hydra_stage_2_switch_attack", death_anim = "seq.hydra_stage_2_death" }, + { id = "npc.hydraboss_p3_transition", source = "family:hydra_stage_3_ready", attack_anim = "seq.hydra_stage_3_switch_attack", death_anim = "seq.hydra_stage_3_death" }, + { id = "npc.ic_basement_imp", source = "family:imp_ready", attack_anim = "seq.imp_attack", defend_anim = "seq.imp_block", death_anim = "seq.imp_death" }, + { id = "npc.ice_giant_tapoyauik", source = "family:giant_update_ice_ready", attack_anim = "seq.giant_update_ice_attack", defend_anim = "seq.giant_update_ice_defend", death_anim = "seq.giant_update_ice_death" }, + { id = "npc.ice_giant_tapoyauik02", source = "family:giant_update_ice_ready", attack_anim = "seq.giant_update_ice_attack", defend_anim = "seq.giant_update_ice_defend", death_anim = "seq.giant_update_ice_death" }, + { id = "npc.ice_spider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.ice_spider_shortrange", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.ice_wolf_1", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.ice_wolf_2", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.ice_wolf_3", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.ice_wolf_4", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.ice_wolf_5", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.ice_wolf_6", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.icediamond_icewarrior", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.icegiant", source = "family:giant_update_ice_ready", attack_anim = "seq.giant_update_ice_attack", defend_anim = "seq.giant_update_ice_defend", death_anim = "seq.giant_update_ice_death" }, + { id = "npc.icegiant2", source = "family:giant_update_ice_ready", attack_anim = "seq.giant_update_ice_attack", defend_anim = "seq.giant_update_ice_defend", death_anim = "seq.giant_update_ice_death" }, + { id = "npc.icegiant3", source = "family:giant_update_ice_ready", attack_anim = "seq.giant_update_ice_attack", defend_anim = "seq.giant_update_ice_defend", death_anim = "seq.giant_update_ice_death" }, + { id = "npc.icegiant_low_wanderrange", source = "family:giant_update_ice_ready", attack_anim = "seq.giant_update_ice_attack", defend_anim = "seq.giant_update_ice_defend", death_anim = "seq.giant_update_ice_death" }, + { id = "npc.icegiant_low_wanderrange2", source = "family:giant_update_ice_ready", attack_anim = "seq.giant_update_ice_attack", defend_anim = "seq.giant_update_ice_defend", death_anim = "seq.giant_update_ice_death" }, + { id = "npc.icewarrior", source = "weapon:stance:human_transready", attack_anim = "seq.human_trans_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_trans_axe_def" }, + { id = "npc.icewarrior_low_wanderrange", source = "weapon:stance:human_transready", attack_anim = "seq.human_trans_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_trans_axe_def" }, + { id = "npc.icewarrior_queen", source = "weapon:stance:human_transready", attack_anim = "seq.human_trans_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_trans_axe_def" }, + { id = "npc.ics_little_apmeken", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.ics_little_croc", source = "family:croc_ready", attack_anim = "seq.croc_attack", defend_anim = "seq.croc_parry", death_anim = "seq.croc_death" }, + { id = "npc.ics_little_croc_blocked", source = "family:croc_ready", attack_anim = "seq.croc_attack", defend_anim = "seq.croc_parry", death_anim = "seq.croc_death" }, + { id = "npc.ics_little_croc_normal", source = "family:croc_ready", attack_anim = "seq.croc_attack", defend_anim = "seq.croc_parry", death_anim = "seq.croc_death" }, + { id = "npc.ics_little_crondis", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.ics_little_het", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.ics_little_jackal", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.ics_little_jackal_alt", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.ics_little_locust", source = "family:locust_ready", attack_anim = "seq.locust_attack", defend_anim = "seq.locust_parry", death_anim = "seq.locust_death" }, + { id = "npc.ics_little_locust_vis", source = "family:locust_ready", attack_anim = "seq.locust_attack", defend_anim = "seq.locust_parry", death_anim = "seq.locust_death" }, + { id = "npc.ics_little_mummy_1", source = "family:mummy_update_soldier_ready", defend_anim = "seq.mummy_update_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.ics_little_mummy_2", source = "family:mummy_update_soldier_ready", defend_anim = "seq.mummy_update_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.ics_little_mummy_3", source = "family:mummy_update_soldier_ready", defend_anim = "seq.mummy_update_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.ics_little_mummy_4", source = "family:mummy_update_soldier_ready", defend_anim = "seq.mummy_update_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.ics_little_mummy_5", source = "family:mummy_update_soldier_ready", defend_anim = "seq.mummy_update_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.ics_little_plague_frog", source = "family:chompy_toad_ready", attack_anim = "seq.chompy_attack", death_anim = "seq.chompy_toad_death" }, + { id = "npc.ics_little_plague_frog_vis", source = "family:chompy_toad_ready", attack_anim = "seq.chompy_attack", death_anim = "seq.chompy_toad_death" }, + { id = "npc.ics_little_possessedpriest", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.ics_little_scabaras", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.ics_scarab_swarm", source = "family:scarab_ready", attack_anim = "seq.scarab_attack", defend_anim = "seq.scarab_parry" }, + { id = "npc.imp_notele", source = "family:imp_ready", attack_anim = "seq.imp_attack", defend_anim = "seq.imp_block", death_anim = "seq.imp_death" }, + { id = "npc.inferno_creature_harpie", source = "family:jalmejrah_ready", attack_anim = "seq.jalmejrah_attack", defend_anim = "seq.jalmejrah_defend", death_anim = "seq.jalmejrah_death" }, + { id = "npc.inferno_creature_mager", source = "family:jalakxil_ready", attack_anim = "seq.jalakxil_attack_melee", death_anim = "seq.jalakxil_death" }, + { id = "npc.inferno_creature_melee", source = "family:jalimkot_ready", attack_anim = "seq.jalimkot_attack", defend_anim = "seq.jalimkot_defend", death_anim = "seq.jalimkot_death" }, + { id = "npc.inferno_creature_melee_small", source = "family:jalimkot_ready", attack_anim = "seq.jalimkot_attack", defend_anim = "seq.jalimkot_defend", death_anim = "seq.jalimkot_death" }, + { id = "npc.inferno_creature_ranger", source = "family:jalxil_ready", attack_anim = "seq.jalxil_attack_melee", defend_anim = "seq.jalxil_defend", death_anim = "seq.jalxil_death" }, + { id = "npc.inferno_creature_splitter", source = "family:jalak_ready", attack_anim = "seq.jalak_attack_melee", defend_anim = "seq.jalak_defend", death_anim = "seq.jalak_death" }, + { id = "npc.inferno_creature_splitter_mage", source = "family:jalak_ready", attack_anim = "seq.jalak_attack_melee", defend_anim = "seq.jalak_defend", death_anim = "seq.jalak_death" }, + { id = "npc.inferno_creature_splitter_melee", source = "family:jalak_ready", attack_anim = "seq.jalak_attack_melee", defend_anim = "seq.jalak_defend", death_anim = "seq.jalak_death" }, + { id = "npc.inferno_creature_splitter_range", source = "family:jalak_ready", attack_anim = "seq.jalak_attack_melee", defend_anim = "seq.jalak_defend", death_anim = "seq.jalak_death" }, + { id = "npc.inferno_jad", source = "family:jaltokjad_ready", attack_anim = "seq.jaltokjad_attack_melee", defend_anim = "seq.jaltokjad_defend", death_anim = "seq.jaltokjad_death" }, + { id = "npc.inferno_jad_finalwave", source = "family:jaltokjad_ready", attack_anim = "seq.jaltokjad_attack_melee", defend_anim = "seq.jaltokjad_defend", death_anim = "seq.jaltokjad_death" }, + { id = "npc.inferno_jad_healer", source = "family:lizard_cleric_ready", attack_anim = "seq.lizard_cleric_attack", defend_anim = "seq.lizard_cleric_defend", death_anim = "seq.lizard_cleric_death" }, + { id = "npc.inferno_jad_healer_finalwave", source = "family:lizard_cleric_ready", attack_anim = "seq.lizard_cleric_attack", defend_anim = "seq.lizard_cleric_defend", death_anim = "seq.lizard_cleric_death" }, + { id = "npc.inferno_mager_finalwave", source = "family:jalakxil_ready", attack_anim = "seq.jalakxil_attack_melee", death_anim = "seq.jalakxil_death" }, + { id = "npc.inferno_nibbler", source = "family:jalnib_ready", attack_anim = "seq.jalnib_attack", defend_anim = "seq.jalnib_defend", death_anim = "seq.jalnib_death" }, + { id = "npc.inferno_ranger_finalwave", source = "family:jalxil_ready", attack_anim = "seq.jalxil_attack_melee", defend_anim = "seq.jalxil_defend", death_anim = "seq.jalxil_death" }, + { id = "npc.inferno_tzkalzuk_placeholder", source = "family:zuk_idle", attack_anim = "seq.zuk_attack", defend_anim = "seq.zuk_defend", death_anim = "seq.zuk_death" }, + { id = "npc.inferno_zuk_healer", source = "family:dagannoth_water_creature_ready", attack_anim = "seq.dagannoth_water_creature_attack", defend_anim = "seq.dagannoth_water_creature_defend", death_anim = "seq.dagannoth_water_creature_death" }, + { id = "npc.invisible_tormented_demon_1", source = "family:luc2_undead_demon_ready", attack_anim = "seq.luc2_undead_demon_melee", defend_anim = "seq.luc2_undead_demon_defend", death_anim = "seq.luc2_undead_demon_death" }, + { id = "npc.invisible_tormented_demon_2", source = "family:luc2_undead_demon_ready", attack_anim = "seq.luc2_undead_demon_melee", defend_anim = "seq.luc2_undead_demon_defend", death_anim = "seq.luc2_undead_demon_death" }, + { id = "npc.invisible_tormented_demon_quest_1", source = "family:luc2_undead_demon_ready", attack_anim = "seq.luc2_undead_demon_melee", defend_anim = "seq.luc2_undead_demon_defend", death_anim = "seq.luc2_undead_demon_death" }, + { id = "npc.invisible_tormented_demon_quest_2", source = "family:luc2_undead_demon_ready", attack_anim = "seq.luc2_undead_demon_melee", defend_anim = "seq.luc2_undead_demon_defend", death_anim = "seq.luc2_undead_demon_death" }, + { id = "npc.invrigar_the_necromancer", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.iron_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.iron_dragon_strongholdcave", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.irvig_senay", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.jad_challenge_healer", source = "family:lizard_cleric_ready", attack_anim = "seq.lizard_cleric_attack", defend_anim = "seq.lizard_cleric_defend", death_anim = "seq.lizard_cleric_death" }, + { id = "npc.jad_challenge_jad", source = "family:jaltokjad_ready", attack_anim = "seq.jaltokjad_attack_melee", defend_anim = "seq.jaltokjad_defend", death_anim = "seq.jaltokjad_death" }, + { id = "npc.jailguard", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.jailguard_four_patrol", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.jailguard_three_patrol", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.jailguard_two_patrol", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.jb_baby_dagganoth", source = "family:slayer_lizard_ready", attack_anim = "seq.slayer_lizard_attack", defend_anim = "seq.slayer_lizard_defend", death_anim = "seq.slayer_lizard_death" }, + { id = "npc.jelly_tapoyauik", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.jogre", source = "family:giant_ready", attack_anim = "seq.giant_attack", defend_anim = "seq.giant_block", death_anim = "seq.giant_death" }, + { id = "npc.jormungand", source = "family:jormungand_ready", attack_anim = "seq.jormungand_melee", death_anim = "seq.jormungand_dead" }, + { id = "npc.jungle_savage", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.jungle_spider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.jungle_wolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.juvenile_custodian_stalker", source = "family:npc_custodian_stalker02_ready01", attack_anim = "seq.npc_custodian_stalker02_attack01", death_anim = "seq.npc_custodian_stalker02_death01" }, + { id = "npc.kalphite_flyingqueen", source = "family:kalphite_update_flying_queen_ready_walk", attack_anim = "seq.kalphite_update_flying_queen_stinger_attack", defend_anim = "seq.kalphite_update_flying_queen_defend", death_anim = "seq.kalphite_update_flying_queen_death" }, + { id = "npc.kalphite_lord", source = "family:kalphite_update_lord_ready", defend_anim = "seq.kalphite_update_defend", death_anim = "seq.kalphite_update_lord_death" }, + { id = "npc.kalphite_lord_chamber", source = "family:kalphite_update_lord_ready", defend_anim = "seq.kalphite_update_defend", death_anim = "seq.kalphite_update_lord_death" }, + { id = "npc.kalphite_lord_strongholdcave", source = "family:kalphite_update_lord_ready", defend_anim = "seq.kalphite_update_defend", death_anim = "seq.kalphite_update_lord_death" }, + { id = "npc.kalphite_queen", source = "family:kalphite_update_queen_ready", attack_anim = "seq.kalphite_update_queen_jaws_attack", defend_anim = "seq.kalphite_update_queen_defend", death_anim = "seq.kalphite_update_queen_death" }, + { id = "npc.kalphite_soldier", source = "family:kalphite_update_ready", defend_anim = "seq.kalphite_update_defend", death_anim = "seq.kalphite_update_death" }, + { id = "npc.kalphite_soldier_strongholdcave", source = "family:kalphite_update_ready", defend_anim = "seq.kalphite_update_defend", death_anim = "seq.kalphite_update_death" }, + { id = "npc.kalphite_worker", source = "family:kalphite_update_ready", defend_anim = "seq.kalphite_update_defend", death_anim = "seq.kalphite_update_death" }, + { id = "npc.kalphite_worker_chamber", source = "family:kalphite_update_ready", defend_anim = "seq.kalphite_update_defend", death_anim = "seq.kalphite_update_death" }, + { id = "npc.kalphite_worker_strongholdcave", source = "family:kalphite_update_ready", defend_anim = "seq.kalphite_update_defend", death_anim = "seq.kalphite_update_death" }, + { id = "npc.kalrag", source = "family:spider_update_ready_large", attack_anim = "seq.spider_update_attack_large", attack_sound = 537, defend_anim = "seq.spider_update_defend_large", defend_sound = 539, death_anim = "seq.spider_update_death_large", death_sound = 538 }, + { id = "npc.kalrag_vis", source = "family:spider_update_ready_large", attack_anim = "seq.spider_update_attack_large", attack_sound = 537, defend_anim = "seq.spider_update_defend_large", defend_sound = 539, death_anim = "seq.spider_update_death_large", death_sound = 538 }, + { id = "npc.khazard_commander", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.khazard_troop", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.khazard_troop_spearmen", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.khazard_warlord_combat", source = "weapon:obj.iron_warhammer", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2567, defend_anim = "seq.human_blunt_block" }, + { id = "npc.king_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.kingscorpion", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.knight_of_ardougne", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.knight_of_ardougne2", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.knight_of_ardougne_west_vis", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.koldemon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.kolethereal", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.kologre", source = "family:giant_ready", attack_anim = "seq.giant_attack", defend_anim = "seq.giant_block", death_anim = "seq.giant_death" }, + { id = "npc.kolspider", source = "family:spider_update_ready_large", attack_anim = "seq.spider_update_attack_large", attack_sound = 537, defend_anim = "seq.spider_update_defend_large", defend_sound = 539, death_anim = "seq.spider_update_death_large", death_sound = 538 }, + { id = "npc.kourend_abyssal", source = "family:abyssal_ready", attack_anim = "seq.abyssal_attack", death_anim = "seq.abyssal_death" }, + { id = "npc.kourend_banshee", source = "family:banshee_ready", attack_anim = "seq.banshee_attack", defend_anim = "seq.banshee_parry", death_anim = "seq.banshee_death" }, + { id = "npc.kourend_black_demon_1", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.kourend_black_demon_2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.kourend_black_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.kourend_bloodveld", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.kourend_blue_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.kourend_bronze_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.kourend_cyclops1", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.kourend_cyclops2", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.kourend_dagannoth1", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.kourend_dagannoth2", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.kourend_dark_beast", source = "family:dark_beast_update_ready", attack_anim = "seq.dark_beast_update_attack", defend_anim = "seq.dark_beast_update_defend", death_anim = "seq.dark_beast_update_death" }, + { id = "npc.kourend_dark_warrior_1", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.kourend_dark_warrior_2", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.kourend_dark_warrior_3", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.kourend_dustdevil", source = "family:dustdevil_ready", attack_anim = "seq.dustdevil_attack", defend_anim = "seq.dustdevil_defend", death_anim = "seq.dustdevil_death" }, + { id = "npc.kourend_firegiant1", source = "family:giant_update_fire_sword_ready", attack_anim = "seq.giant_update_fire_sword_attack", defend_anim = "seq.giant_update_fire_sword_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.kourend_firegiant2", source = "family:giant_update_fire_sword_ready", attack_anim = "seq.giant_update_fire_sword_attack", defend_anim = "seq.giant_update_fire_sword_defend", death_anim = "seq.giant_update_fire_death" }, + { id = "npc.kourend_ghost1", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.kourend_ghost2", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.kourend_greater_demon1", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.kourend_greater_demon2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.kourend_greater_demon3", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.kourend_guard_m1", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.kourend_guard_m1_big", source = "weapon:obj.dragon_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.kourend_guard_m2", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.kourend_guard_m2_big", source = "weapon:obj.dragon_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.kourend_guard_m3", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.kourend_guard_m3_big", source = "weapon:obj.dragon_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.kourend_guard_m4", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.kourend_guard_m4_big", source = "weapon:obj.dragon_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.kourend_hellhound", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.kourend_hillgiant", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.kourend_iron_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.kourend_jelly", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.kourend_lesser_demon1", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.kourend_lesser_demon2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.kourend_magicaxe", source = "family:magicaxe_ready", attack_anim = "seq.magicaxe_attack", defend_anim = "seq.magicaxe_block", death_anim = "seq.magicaxe_death" }, + { id = "npc.kourend_mossgiant", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.kourend_nechryael", source = "family:demons_ready", attack_anim = "seq.demons_attack", defend_anim = "seq.demons_parry", death_anim = "seq.demons_death" }, + { id = "npc.kourend_pickaxe", source = "family:magicaxe_ready", attack_anim = "seq.magicaxe_attack", defend_anim = "seq.magicaxe_block", death_anim = "seq.magicaxe_death" }, + { id = "npc.kourend_red_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.kourend_rockcrab", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.kourend_shade", source = "family:shade_ready", attack_anim = "seq.shade_attack", defend_anim = "seq.shade_block" }, + { id = "npc.kourend_skeleton", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.kourend_spectre", source = "family:specter_ready", attack_anim = "seq.specter_attack", defend_anim = "seq.specter_parry", death_anim = "seq.specter_death" }, + { id = "npc.kourend_steel_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.kourend_zombie1", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.kourend_zombie2", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.kourend_zombie3", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.kourend_zombie4", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.kr_aggressive_black_knight", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.laboratory_spider", source = "family:small_spider_update_ready", attack_anim = "seq.small_spider_update_attack", attack_sound = 3604, defend_anim = "seq.small_spider_update_defend", defend_sound = 3609, death_anim = "seq.small_spider_update_death", death_sound = 3608 }, + { id = "npc.laboratory_spider_aggressive", source = "family:small_spider_update_ready", attack_anim = "seq.small_spider_update_attack", attack_sound = 3604, defend_anim = "seq.small_spider_update_defend", defend_sound = 3609, death_anim = "seq.small_spider_update_death", death_sound = 3608 }, + { id = "npc.lady_pirate", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.lathastrainingogre", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.lava_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.lava_dragon2", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.lava_dragon3", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.league_6_gg_dusk_alive", source = "family:gg_dusk_idle_defensive", defend_anim = "seq.gg_dusk_defend", death_anim = "seq.gg_dusk_death" }, + { id = "npc.league_6_kbd", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.league_superior_abberant_spectre", source = "family:giant_champion_ready", attack_anim = "seq.giant_champion_attack", defend_anim = "seq.giant_champion_defend", death_anim = "seq.giant_champion_death" }, + { id = "npc.league_superior_abyssal_demon", source = "family:abyssal_ready", attack_anim = "seq.abyssal_attack", death_anim = "seq.abyssal_death" }, + { id = "npc.league_superior_banshee", source = "family:screaming_banshee_ready", attack_anim = "seq.screaming_banshee_attack", defend_anim = "seq.screaming_banshee_defend", death_anim = "seq.screaming_banshee_death" }, + { id = "npc.league_superior_basilisk", source = "family:basilisk_ready", attack_anim = "seq.basilisk_attack", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_death" }, + { id = "npc.league_superior_basilisk_knight", source = "family:basilisk_knight_ready", attack_anim = "seq.basilisk_knight_melee", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_knight_death" }, + { id = "npc.league_superior_bloodveld", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.league_superior_cave_crawler", source = "family:crawler_ready", attack_anim = "seq.crawler_attack", defend_anim = "seq.crawler_parry", death_anim = "seq.crawler_death" }, + { id = "npc.league_superior_cave_horror", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.league_superior_cockatrice", source = "family:cockatrice_ready", attack_anim = "seq.cockatrice_attack", defend_anim = "seq.cockatrice_defend", death_anim = "seq.cockatrice_death" }, + { id = "npc.league_superior_crawling_hand", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.league_superior_dark_beast", source = "family:dark_beast_update_ready", attack_anim = "seq.dark_beast_update_attack", defend_anim = "seq.dark_beast_update_defend", death_anim = "seq.dark_beast_update_death" }, + { id = "npc.league_superior_drake", source = "family:drake_ready", attack_anim = "seq.drake_melee" }, + { id = "npc.league_superior_dustdevil", source = "family:dustdevil_ready", attack_anim = "seq.dustdevil_attack", defend_anim = "seq.dustdevil_defend", death_anim = "seq.dustdevil_death" }, + { id = "npc.league_superior_hydra", source = "family:superior_hydra_idle", death_anim = "seq.superior_hydra_death" }, + { id = "npc.league_superior_infernal_mage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.league_superior_jelly", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.league_superior_kourend_banshee", source = "family:screaming_banshee_ready", attack_anim = "seq.screaming_banshee_attack", defend_anim = "seq.screaming_banshee_defend", death_anim = "seq.screaming_banshee_death" }, + { id = "npc.league_superior_kourend_bloodveld", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.league_superior_kourend_jelly", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.league_superior_kourend_spectre", source = "family:giant_champion_ready", attack_anim = "seq.giant_champion_attack", defend_anim = "seq.giant_champion_defend", death_anim = "seq.giant_champion_death" }, + { id = "npc.league_superior_kurask", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.league_superior_nechryael", source = "family:giant_champion_ready", attack_anim = "seq.giant_champion_attack", defend_anim = "seq.giant_champion_defend", death_anim = "seq.giant_champion_death" }, + { id = "npc.league_superior_pyrefiend", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.league_superior_pyrelord", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.league_superior_rockslug", source = "family:rockslug_ready", attack_anim = "seq.rockslug_attack", defend_anim = "seq.rockslug_defend", death_anim = "seq.rockslug_death" }, + { id = "npc.league_superior_smoke_devil", source = "family:lore_dust_devil_ready", attack_anim = "seq.lore_dust_devil_attack", defend_anim = "seq.lore_dust_devil_defend", death_anim = "seq.lore_dust_devil_death" }, + { id = "npc.league_superior_turoth", source = "family:turoth_ready", attack_anim = "seq.turoth_attack", defend_anim = "seq.turoth_defend", death_anim = "seq.turoth_death" }, + { id = "npc.league_superior_wyrm_light", source = "family:wyrm_ready", attack_anim = "seq.wyrm_attack_melee", death_anim = "seq.wyrm_death" }, + { id = "npc.lesser_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.lesser_demon2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.lesser_demon3", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.lesser_demon4", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.lesser_demon5", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.lesser_demon_slayercave_1", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.lesser_demon_slayercave_2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.lesser_demon_slayercave_3", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.leviathan", source = "family:npc_leviathan_01_idle_01", attack_anim = "seq.npc_leviathan_01_melee_01", death_anim = "seq.npc_leviathan_01_death" }, + { id = "npc.leviathan_quest", source = "family:npc_leviathan_01_idle_01", attack_anim = "seq.npc_leviathan_01_melee_01", death_anim = "seq.npc_leviathan_01_death" }, + { id = "npc.lizardman_cave_shaman_1", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.lizardman_cave_shaman_2", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.lotg_goblin_skeleton", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.lotg_goblin_skeleton_high_priest1", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.lotg_goblin_skeleton_high_priest2", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.lotg_goblin_skeleton_high_priest3", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.lotg_goblin_skeleton_high_priest4", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.lotg_goblin_skeleton_high_priest5", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.lotr_giant_skeleton", source = "family:skeleton_update_giant_ready", attack_anim = "seq.skeleton_update_giant_attack", defend_anim = "seq.skeleton_update_giant_defend", death_anim = "seq.skeleton_update_giant_death" }, + { id = "npc.lotr_mage_skeleton", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.lotr_possessed_pickaxe", source = "family:lotr_pickaxe_ready", attack_anim = "seq.lotr_pickaxe_attack", defend_anim = "seq.lotr_pickaxe_defend", death_anim = "seq.lotr_pickaxe_death" }, + { id = "npc.lotr_possessed_spade", source = "family:lotr_spade_ready", attack_anim = "seq.lotr_spade_attack", death_anim = "seq.lotr_spade_death" }, + { id = "npc.lotr_skeleton_lvl_13", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.lotr_skeleton_lvl_21", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.lotr_skeleton_lvl_22", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.lotr_skeleton_lvl_25", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.lotr_skeleton_lvl_45", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.lotr_skeleton_lvl_77", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.lotr_skeleton_miner1", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.lotr_skeleton_possesed_1", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.lotr_skeleton_possesed_2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.lotr_steel_armour", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.lotr_terror_dog_large", source = "family:lotr_terror_dog_look_ready", attack_anim = "seq.lotr_terror_dog_attack", defend_anim = "seq.lotr_terror_dog_defend", death_anim = "seq.lotr_terror_dog_death" }, + { id = "npc.lotr_terror_dog_small", source = "family:lotr_terror_dog_look_ready", attack_anim = "seq.lotr_terror_dog_attack", defend_anim = "seq.lotr_terror_dog_defend", death_anim = "seq.lotr_terror_dog_death" }, + { id = "npc.lotr_tran_razorlor_mutant", source = "family:lotr_tarn_razorlor_mutant_ready", attack_anim = "seq.lotr_tarn_razorlor_mutant_attack", defend_anim = "seq.lotr_tarn_razorlor_mutant_defend", death_anim = "seq.lotr_tarn_razorlor_mutant_death" }, + { id = "npc.lotr_zombie1_lvl_18", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie1_lvl_24", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie1_lvl_25", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie1_lvl_30", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie1_lvl_44", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.lotr_zombie1_lvl_53", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.lotr_zombie2_lvl_18", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie2_lvl_24", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie2_lvl_25", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie2_lvl_30", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie2_lvl_44", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.lotr_zombie2_lvl_53", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.lotr_zombie3_lvl_18", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie3_lvl_24", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie3_lvl_25", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie3_lvl_30", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.lotr_zombie3_lvl_44", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.lotr_zombie3_lvl_53", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.lunar_mossgiant", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.lunar_mossgiant2", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.lunar_pirate_generic_pirate_5_attackable", source = "weapon:obj.blackjack_oak", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2512, defend_anim = "seq.human_blunt_block" }, + { id = "npc.lunar_suqka", source = "family:suqka_ready", attack_anim = "seq.suqka_attack", defend_anim = "seq.suqka_defend", death_anim = "seq.suqka_death" }, + { id = "npc.lunar_suqka2", source = "family:suqka_ready", attack_anim = "seq.suqka_attack", defend_anim = "seq.suqka_defend", death_anim = "seq.suqka_death" }, + { id = "npc.lunar_suqka3", source = "family:suqka_ready", attack_anim = "seq.suqka_attack", defend_anim = "seq.suqka_defend", death_anim = "seq.suqka_death" }, + { id = "npc.lunar_suqka4", source = "family:suqka_ready", attack_anim = "seq.suqka_attack", defend_anim = "seq.suqka_defend", death_anim = "seq.suqka_death" }, + { id = "npc.lunar_suqka5", source = "family:suqka_ready", attack_anim = "seq.suqka_attack", defend_anim = "seq.suqka_defend", death_anim = "seq.suqka_death" }, + { id = "npc.lunar_suqka6", source = "family:suqka_ready", attack_anim = "seq.suqka_attack", defend_anim = "seq.suqka_defend", death_anim = "seq.suqka_death" }, + { id = "npc.lunar_suqka7", source = "family:suqka_ready", attack_anim = "seq.suqka_attack", defend_anim = "seq.suqka_defend", death_anim = "seq.suqka_death" }, + { id = "npc.lynx", source = "family:npc_lynx_combat_idle", attack_anim = "seq.npc_lynx_combat_melee", death_anim = "seq.npc_lynx_combat_death" }, + { id = "npc.lynx02", source = "family:npc_lynx_combat_idle", attack_anim = "seq.npc_lynx_combat_melee", death_anim = "seq.npc_lynx_combat_death" }, + { id = "npc.ma2_boss_guthix", source = "family:ent_boss_idle", attack_anim = "seq.ent_boss_attack_melee", defend_anim = "seq.ent_boss_defend" }, + { id = "npc.ma2_boss_saradomin", source = "family:wild_zealot_idle", attack_anim = "seq.wild_zealot_slash", defend_anim = "seq.wild_zealot_defend", death_anim = "seq.wild_zealot_death" }, + { id = "npc.ma2_boss_zamorak", source = "family:zamorak_demon_boss_idle", attack_anim = "seq.zamorak_demon_boss_attack_melee", defend_anim = "seq.zamorak_demon_boss_defend", death_anim = "seq.zamorak_demon_boss_death" }, + { id = "npc.macro_zombie", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.mad_angel", source = "family:npc_mad_angel_idle", attack_anim = "seq.npc_mad_angel_attack_melee" }, + { id = "npc.mad_angel_quest", source = "family:npc_mad_angel_idle", attack_anim = "seq.npc_mad_angel_attack_melee" }, + { id = "npc.mag_direwolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.mag_direwolf_minion", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.mag_minotaur", source = "family:mag_minotaur_idle", attack_anim = "seq.mag_minotaur_melee", death_anim = "seq.mag_minotaur_death" }, + { id = "npc.maggot_king", source = "family:npc_maggot_king_idle_01", death_anim = "seq.npc_maggot_king_death_01" }, + { id = "npc.magicaxe", source = "family:magicaxe_ready", attack_anim = "seq.magicaxe_attack", defend_anim = "seq.magicaxe_block", death_anim = "seq.magicaxe_death" }, + { id = "npc.maiden_blood_slug", source = "family:tob_blood_spawn_idle", death_anim = "seq.tob_blood_spawn_death" }, + { id = "npc.maiden_blood_slug_hard", source = "family:tob_blood_spawn_idle", death_anim = "seq.tob_blood_spawn_death" }, + { id = "npc.maiden_blood_slug_story", source = "family:tob_blood_spawn_idle", death_anim = "seq.tob_blood_spawn_death" }, + { id = "npc.maiden_elemental", source = "family:elemental_idle", death_anim = "seq.elemental_death" }, + { id = "npc.maiden_elemental_hard", source = "family:elemental_idle", death_anim = "seq.elemental_death" }, + { id = "npc.maiden_elemental_story", source = "family:elemental_idle", death_anim = "seq.elemental_death" }, + { id = "npc.man4", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.man4_for_musa_point", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.mature_custodian_stalker", source = "family:npc_custodian_stalker03_ready02", attack_anim = "seq.npc_custodian_stalker03_attack01" }, + { id = "npc.mcannon_goblin1", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.mcannon_goblin_guard", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.mcannonguard", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.mcannonguard1", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.mcannonguard2", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.mcannonguard3", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.mcannonguard4", source = "family:dwarf_ready", attack_anim = "seq.dwarf_attack", defend_anim = "seq.dwarf_block", death_anim = "seq.dwarf_death" }, + { id = "npc.mdaughter_bearman", source = "family:bearsuit_ready", attack_anim = "seq.bearsuit_attack", defend_anim = "seq.bearsuit_defend", death_anim = "seq.bearsuit_death" }, + { id = "npc.mdaughter_bearman_fighter", source = "family:bearsuit_ready", attack_anim = "seq.bearsuit_attack", defend_anim = "seq.bearsuit_defend", death_anim = "seq.bearsuit_death" }, + { id = "npc.mdaughter_multi_bear", source = "family:bearsuit_ready", attack_anim = "seq.bearsuit_attack", defend_anim = "seq.bearsuit_defend", death_anim = "seq.bearsuit_death" }, + { id = "npc.medium_frog", source = "family:giant_frog_ready", attack_anim = "seq.giant_frog_attack", defend_anim = "seq.giant_frog_block", death_anim = "seq.giant_frog_death" }, + { id = "npc.melzar_the_mad", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.misc_chicken", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.misc_chicken_brown", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.misc_man_4", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_shield_defence" }, + { id = "npc.misc_man_6", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_shield_defence" }, + { id = "npc.misc_man_7", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.misc_rabbit", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.misc_rabbit2", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.misc_rooster", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.mm2_chieftan_keef_combat", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.mm2_demon_glough", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.mm2_demon_glough_nomove", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.mm2_demon_gorilla_1_magic", source = "family:demonic_gorilla_idle", attack_anim = "seq.demonic_gorilla_punch", defend_anim = "seq.demonic_gorilla_defend", death_anim = "seq.demonic_gorilla_death" }, + { id = "npc.mm2_demon_gorilla_1_melee", source = "family:demonic_gorilla_idle", attack_anim = "seq.demonic_gorilla_punch", defend_anim = "seq.demonic_gorilla_defend", death_anim = "seq.demonic_gorilla_death" }, + { id = "npc.mm2_demon_gorilla_1_ranged", source = "family:demonic_gorilla_idle", attack_anim = "seq.demonic_gorilla_punch", defend_anim = "seq.demonic_gorilla_defend", death_anim = "seq.demonic_gorilla_death" }, + { id = "npc.mm2_demon_gorilla_2_magic", source = "family:demonic_gorilla_idle", attack_anim = "seq.demonic_gorilla_punch", defend_anim = "seq.demonic_gorilla_defend", death_anim = "seq.demonic_gorilla_death" }, + { id = "npc.mm2_demon_gorilla_2_melee", source = "family:demonic_gorilla_idle", attack_anim = "seq.demonic_gorilla_punch", defend_anim = "seq.demonic_gorilla_defend", death_anim = "seq.demonic_gorilla_death" }, + { id = "npc.mm2_demon_gorilla_2_ranged", source = "family:demonic_gorilla_idle", attack_anim = "seq.demonic_gorilla_punch", defend_anim = "seq.demonic_gorilla_defend", death_anim = "seq.demonic_gorilla_death" }, + { id = "npc.mm2_general_kob_combat", source = "family:troll_ready_sword+shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.mm2_kruk_combat", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm2_maniacal_monkey", source = "family:monkey_ready", attack_anim = "seq.monkey_attack", defend_anim = "seq.monkey_block", death_anim = "seq.monkey_death" }, + { id = "npc.mm2_maniacal_monkey_maze", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm2_monkey_archer_maze", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm2_tortured_gorilla_1", source = "family:demonic_gorilla_idle", attack_anim = "seq.demonic_gorilla_punch", defend_anim = "seq.demonic_gorilla_defend", death_anim = "seq.demonic_gorilla_death" }, + { id = "npc.mm2_tortured_gorilla_2", source = "family:demonic_gorilla_idle", attack_anim = "seq.demonic_gorilla_punch", defend_anim = "seq.demonic_gorilla_defend", death_anim = "seq.demonic_gorilla_death" }, + { id = "npc.mm2_tortured_gorilla_lab", source = "family:demonic_gorilla_idle", attack_anim = "seq.demonic_gorilla_punch", defend_anim = "seq.demonic_gorilla_defend", death_anim = "seq.demonic_gorilla_death" }, + { id = "npc.mm2_tortured_gorilla_stronghold", source = "family:demonic_gorilla_idle", attack_anim = "seq.demonic_gorilla_punch", defend_anim = "seq.demonic_gorilla_defend", death_anim = "seq.demonic_gorilla_death" }, + { id = "npc.mm_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.mm_duke", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_jungle_bird_blue", source = "family:bird_flying_update_ready_and_extra", attack_anim = "seq.bird_flying_update_attack", defend_anim = "seq.bird_flying_update_defend", death_anim = "seq.bird_flying_update_death" }, + { id = "npc.mm_jungle_bird_green", source = "family:bird_flying_update_ready_and_extra_toucan_and_cormarant", attack_anim = "seq.bird_flying_update_attack", defend_anim = "seq.bird_flying_update_defend", death_anim = "seq.bird_flying_update_death" }, + { id = "npc.mm_jungle_scorpion", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.mm_jungle_spider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.mm_monkey_archer", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_monkey_guard", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_oipuis", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_ouhai", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_padulah", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_posted_archer", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_ravine_archer", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_religious_guard", source = "family:m_gorilla_ready", attack_anim = "seq.m_gorilla_attack", defend_anim = "seq.m_gorilla_block", death_anim = "seq.m_gorilla_death" }, + { id = "npc.mm_religious_trapdoor_guard", source = "family:m_gorilla_ready", attack_anim = "seq.m_gorilla_attack", defend_anim = "seq.m_gorilla_block", death_anim = "seq.m_gorilla_death" }, + { id = "npc.mm_skeleton", source = "family:skeleton_update_gorilla_ready", attack_anim = "seq.skeleton_update_gorilla_attack", defend_anim = "seq.skeleton_update_gorilla_defend", death_anim = "seq.skeleton_update_gorilla_death" }, + { id = "npc.mm_uodai", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_uyoro", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_warehouse_spider", source = "family:small_spider_update_ready", attack_anim = "seq.small_spider_update_attack", attack_sound = 3604, defend_anim = "seq.small_spider_update_defend", defend_sound = 3609, death_anim = "seq.small_spider_update_death", death_sound = 3608 }, + { id = "npc.mm_zombie_monkey_large", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_zombie_monkey_large_guard", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mm_zombie_monkey_small", source = "family:m_monkey_ready", death_anim = "seq.m_monkey_death" }, + { id = "npc.mmsnailpoint_blue", source = "family:snail_ready", attack_anim = "seq.snail_attack", defend_anim = "seq.snail_defend", death_anim = "seq.snail_death" }, + { id = "npc.mmsnailpoint_red+black", source = "family:snail_ready", attack_anim = "seq.snail_attack", defend_anim = "seq.snail_defend", death_anim = "seq.snail_death" }, + { id = "npc.mmsnailpoint_swamp", source = "family:snail_ready", attack_anim = "seq.snail_attack", defend_anim = "seq.snail_defend", death_anim = "seq.snail_death" }, + { id = "npc.mmsnailpoint_yellow", source = "family:snail_ready", attack_anim = "seq.snail_attack", defend_anim = "seq.snail_defend", death_anim = "seq.snail_death" }, + { id = "npc.mmsnailround_blue", source = "family:snail_ready", attack_anim = "seq.snail_attack", defend_anim = "seq.snail_defend", death_anim = "seq.snail_death" }, + { id = "npc.mmsnailround_orange", source = "family:snail_ready", attack_anim = "seq.snail_attack", defend_anim = "seq.snail_defend", death_anim = "seq.snail_death" }, + { id = "npc.mmsnailround_red+black", source = "family:snail_ready", attack_anim = "seq.snail_attack", defend_anim = "seq.snail_defend", death_anim = "seq.snail_death" }, + { id = "npc.mmsnailround_swamp", source = "family:snail_ready", attack_anim = "seq.snail_attack", defend_anim = "seq.snail_defend", death_anim = "seq.snail_death" }, + { id = "npc.mmsnailround_yellow", source = "family:snail_ready", attack_anim = "seq.snail_attack", defend_anim = "seq.snail_defend", death_anim = "seq.snail_death" }, + { id = "npc.molanisk", source = "family:monalisk_creature_ready", attack_anim = "seq.monalisk_creature_attack", defend_anim = "seq.monalisk_creature_defend", death_anim = "seq.monalisk_creature_death" }, + { id = "npc.molanisk_giant_cave_bug", source = "family:monalisk_cave_bug_ready", attack_anim = "seq.monalisk_cave_bug_attack", defend_anim = "seq.monalisk_cave_bug_defend" }, + { id = "npc.molch_lizardbrute_1", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.molch_lizardman_1", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.molch_lizardshaman_1", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.molch_tree_frog", source = "family:giant_frog_ready", attack_anim = "seq.giant_frog_attack", defend_anim = "seq.giant_frog_block", death_anim = "seq.giant_frog_death" }, + { id = "npc.mole_giant", source = "family:mole_ready", attack_anim = "seq.mole_attack", defend_anim = "seq.mole_defend", death_anim = "seq.mole_death" }, + { id = "npc.monkey", source = "family:monkey_ready", attack_anim = "seq.monkey_attack", defend_anim = "seq.monkey_block", death_anim = "seq.monkey_death" }, + { id = "npc.mossgiant", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.mossgiant2", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.mossgiant3", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.mossgiant4", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.mourner_hideout_guard_vis", source = "weapon:obj.nzone_crystal_halberd_new", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.mourning_dark_beast", source = "family:dark_beast_update_ready", attack_anim = "seq.dark_beast_update_attack", defend_anim = "seq.dark_beast_update_defend", death_anim = "seq.dark_beast_update_death" }, + { id = "npc.mourning_guard_melee", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.mourning_guard_melee2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.mourning_guard_range", source = "ranger", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.mourning_guard_range2", source = "ranger", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.mourning_overpass_mourner_vis", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.mourning_shadow_beast", source = "family:shadow_ready", attack_anim = "seq.shadow_attack", defend_anim = "seq.shadow_defend", death_anim = "seq.shadow_death" }, + { id = "npc.mudskipper_ogre", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.muspah", source = "family:npc_muspah_idle_01", death_anim = "seq.npc_muspah_death_01" }, + { id = "npc.muspah_final", source = "family:npc_muspah_idle_01", death_anim = "seq.npc_muspah_death_01" }, + { id = "npc.muspah_final_quest", source = "family:npc_muspah_idle_01", death_anim = "seq.npc_muspah_death_01" }, + { id = "npc.muspah_melee", source = "family:npc_muspah_idle_01", death_anim = "seq.npc_muspah_death_01" }, + { id = "npc.muspah_melee_quest", source = "family:npc_muspah_idle_01", death_anim = "seq.npc_muspah_death_01" }, + { id = "npc.muspah_quest", source = "family:npc_muspah_idle_01", death_anim = "seq.npc_muspah_death_01" }, + { id = "npc.muspah_soulsplit", source = "family:npc_muspah_idle_01", death_anim = "seq.npc_muspah_death_01" }, + { id = "npc.muspah_soulsplit_quest", source = "family:npc_muspah_idle_01", death_anim = "seq.npc_muspah_death_01" }, + { id = "npc.my2arm_dontknowwhat_battle", source = "family:my2arm_troll_idle_knuckledrag", attack_anim = "seq.my2arm_troll_attack_melee", death_anim = "seq.my2arm_troll_death" }, + { id = "npc.my2arm_mother_battle", source = "family:my2arm_troll_idle", attack_anim = "seq.my2arm_troll_attack_melee", death_anim = "seq.my2arm_troll_death" }, + { id = "npc.my2arm_mother_battle_magic", source = "family:my2arm_troll_idle", attack_anim = "seq.my2arm_troll_attack_melee", death_anim = "seq.my2arm_troll_death" }, + { id = "npc.my2arm_mother_battle_melee", source = "family:my2arm_troll_idle", attack_anim = "seq.my2arm_troll_attack_melee", death_anim = "seq.my2arm_troll_death" }, + { id = "npc.my2arm_mother_battle_ranged", source = "family:my2arm_troll_idle", attack_anim = "seq.my2arm_troll_attack_melee", death_anim = "seq.my2arm_troll_death" }, + { id = "npc.myarm_baby_roc", source = "family:myarm_roc_baby_ready", attack_anim = "seq.myarm_roc_baby_attack", defend_anim = "seq.myarm_roc_defend", death_anim = "seq.myarm_roc_baby_death" }, + { id = "npc.myarm_giant_roc", source = "family:myarm_roc_ready", defend_anim = "seq.myarm_roc_defend", death_anim = "seq.myarm_roc_death" }, + { id = "npc.myq3_vanstrom_klause_vampyre_attack", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq4_abomination", source = "family:myq4_abomination_idle", attack_anim = "seq.myq4_abomination_melee", defend_anim = "seq.myq4_abomination_defend", death_anim = "seq.myq4_abomination_death" }, + { id = "npc.myq4_ranis_vampyre_combat", source = "family:myq4_vampire_ready", attack_anim = "seq.myq4_vampire_grounded_attack" }, + { id = "npc.myq4_ranis_vampyre_combat_enrage", source = "family:myq4_vampire_ready", attack_anim = "seq.myq4_vampire_grounded_attack" }, + { id = "npc.myq4_vyrewatch_1", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq4_vyrewatch_2", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq4_vyrewatch_3", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq4_vyrewatch_4", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq5_bloodveld", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.myq5_nail_beast_1", source = "family:trek_nailbeast_ready", attack_anim = "seq.trek_nailbeast_attack", defend_anim = "seq.trek_nailbeast_defend", death_anim = "seq.trek_nailbeast_death" }, + { id = "npc.myq5_nail_beast_2", source = "family:trek_nailbeast_ready", attack_anim = "seq.trek_nailbeast_attack", defend_anim = "seq.trek_nailbeast_defend", death_anim = "seq.trek_nailbeast_death" }, + { id = "npc.myq5_vanstrom_bloodveld", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.myq5_vanstrom_combat", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.myq5_vanstrom_combat_enrage", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.myq6_defending_ivan_bloodveld", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.myq6_defending_ivan_vyrewatch_1", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq6_defending_ivan_vyrewatch_2", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq6_defending_ivan_vyrewatch_3", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq6_defending_ivan_vyrewatch_4", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq6_defending_ivan_vyrewatch_5", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq6_defending_ivan_vyrewatch_elite_1", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.myq6_defending_ivan_vyrewatch_elite_2", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.myq6_defending_ivan_vyrewatch_elite_3", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.myq6_defending_ivan_vyrewatch_elite_4", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.myq6_lowerniel_final_combat", source = "family:npc_lowerniel_drakan_idle01", death_anim = "seq.npc_lowerniel_drakan_death01" }, + { id = "npc.myq6_maiden_100", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.myq6_maiden_30", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.myq6_maiden_50", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.myq6_maiden_70", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.myq6_maiden_blood_slug", source = "family:tob_blood_spawn_idle", death_anim = "seq.tob_blood_spawn_death" }, + { id = "npc.myq6_maiden_dying_a", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.myq6_maiden_dying_b", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.myq6_maiden_elemental", source = "family:elemental_idle", death_anim = "seq.elemental_death" }, + { id = "npc.myq6_nylocas_boss_magic", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.myq6_nylocas_boss_melee", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.myq6_nylocas_boss_ranged", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.myq6_theatre_nylocas_big_magic", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.myq6_theatre_nylocas_big_melee", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.myq6_theatre_nylocas_big_ranged", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.myq6_theatre_nylocas_magic", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.myq6_theatre_nylocas_melee", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.myq6_theatre_nylocas_ranged", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.myq6_vyrewatch_1", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq6_vyrewatch_2", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq6_vyrewatch_3", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq6_vyrewatch_4", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq6_vyrewatch_5", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.myq6_vyrewatch_elite", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.myq6_vyrewatch_elite_2", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.myq6_zamorak_monk_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.myq6_zamorak_monk_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.myq6_zamorak_monk_3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.necromancer", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.newbiechicken", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.newbiegiantrat", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.newbiegiantrat2", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.newbiegiantrat3", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.nex", source = "family:nex_ready", attack_anim = "seq.nex_attack", defend_anim = "seq.nex_defend", death_anim = "seq.nex_death" }, + { id = "npc.nex_bloodmage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.nex_deflect", source = "family:nex_ready", attack_anim = "seq.nex_attack", defend_anim = "seq.nex_defend", death_anim = "seq.nex_death" }, + { id = "npc.nex_dying", source = "family:nex_ready", attack_anim = "seq.nex_attack", defend_anim = "seq.nex_defend", death_anim = "seq.nex_death" }, + { id = "npc.nex_icemage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.nex_prison_blood_reaver", source = "family:blood_reaver_ready", attack_anim = "seq.blood_reaver_attack", defend_anim = "seq.blood_reaver_defend", death_anim = "seq.blood_reaver_death" }, + { id = "npc.nex_prison_blood_reaver_boss", source = "family:blood_reaver_ready", attack_anim = "seq.blood_reaver_attack", defend_anim = "seq.blood_reaver_defend", death_anim = "seq.blood_reaver_death" }, + { id = "npc.nex_prison_mage", source = "caster:obj.staff_of_zaros", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_shield_defence", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.nex_prison_ranger", source = "ranger", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.nex_prison_warrior", source = "family:dh_sword_update_ready", attack_anim = "seq.dh_sword_update_slash", defend_anim = "seq.dh_sword_update_defend" }, + { id = "npc.nex_shadowmage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.nex_smokemage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.nex_soulsplit", source = "family:nex_ready", attack_anim = "seq.nex_attack", defend_anim = "seq.nex_defend", death_anim = "seq.nex_death" }, + { id = "npc.nezikchened", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.nightmare_challenge_husk_magic", source = "family:husk_idle", death_anim = "seq.husk_death" }, + { id = "npc.nightmare_challenge_husk_ranged", source = "family:husk_idle", death_anim = "seq.husk_death" }, + { id = "npc.nightmare_challenge_parasite", source = "family:nightmare_parasite_idle", attack_anim = "seq.nightmare_attack_parasite", death_anim = "seq.nightmare_parasite_death" }, + { id = "npc.nightmare_challenge_parasite_weak", source = "family:nightmare_parasite_idle", attack_anim = "seq.nightmare_attack_parasite", death_anim = "seq.nightmare_parasite_death" }, + { id = "npc.nightmare_challenge_phase_01", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_challenge_phase_02", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_challenge_phase_03", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_challenge_phase_04", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_challenge_phase_05", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_challenge_sleepwalker", source = "family:sleepwalker_ready", death_anim = "seq.sleepwalker_death" }, + { id = "npc.nightmare_challenge_weak_phase_01", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_challenge_weak_phase_02", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_challenge_weak_phase_03", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_challenge_weak_phase_04", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_husk_magic", source = "family:husk_idle", death_anim = "seq.husk_death" }, + { id = "npc.nightmare_husk_ranged", source = "family:husk_idle", death_anim = "seq.husk_death" }, + { id = "npc.nightmare_parasite", source = "family:nightmare_parasite_idle", attack_anim = "seq.nightmare_attack_parasite", death_anim = "seq.nightmare_parasite_death" }, + { id = "npc.nightmare_parasite_weak", source = "family:nightmare_parasite_idle", attack_anim = "seq.nightmare_attack_parasite", death_anim = "seq.nightmare_parasite_death" }, + { id = "npc.nightmare_phase_01", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_phase_02", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_phase_03", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_sleepwalker_1", source = "family:sleepwalker_ready", death_anim = "seq.sleepwalker_death" }, + { id = "npc.nightmare_sleepwalker_2", source = "family:sleepwalker_ready", death_anim = "seq.sleepwalker_death" }, + { id = "npc.nightmare_sleepwalker_3", source = "family:sleepwalker_ready", death_anim = "seq.sleepwalker_death" }, + { id = "npc.nightmare_sleepwalker_4", source = "family:sleepwalker_ready", death_anim = "seq.sleepwalker_death" }, + { id = "npc.nightmare_sleepwalker_5", source = "family:sleepwalker_ready", death_anim = "seq.sleepwalker_death" }, + { id = "npc.nightmare_sleepwalker_6", source = "family:sleepwalker_ready", death_anim = "seq.sleepwalker_death" }, + { id = "npc.nightmare_weak_phase_01", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_weak_phase_02", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.nightmare_weak_phase_03", source = "family:nightmare_idle", attack_anim = "seq.nightmare_attack_melee", death_anim = "seq.nightmare_death" }, + { id = "npc.ntk_mummy_1", source = "family:mummy_update_soldier_ready", defend_anim = "seq.mummy_update_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.ntk_mummy_2", source = "family:mummy_update_soldier_ready", defend_anim = "seq.mummy_update_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.ntk_mummy_3", source = "family:mummy_update_soldier_ready", defend_anim = "seq.mummy_update_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.ntk_mummy_4", source = "family:mummy_update_soldier_ready", defend_anim = "seq.mummy_update_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.ntk_mummy_5", source = "family:mummy_update_soldier_ready", defend_anim = "seq.mummy_update_defend", death_anim = "seq.mummy_update_death" }, + { id = "npc.ntk_scarab_swarm", source = "family:scarab_ready", attack_anim = "seq.scarab_attack", defend_anim = "seq.scarab_parry" }, + { id = "npc.nylocas_boss_magic", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.nylocas_boss_magic_hard", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.nylocas_boss_magic_story", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.nylocas_boss_melee", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.nylocas_boss_melee_hard", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.nylocas_boss_melee_story", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.nylocas_boss_ranged", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.nylocas_boss_ranged_hard", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.nylocas_boss_ranged_story", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.nylocas_miniboss_magic_hard", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.nylocas_miniboss_melee_hard", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.nylocas_miniboss_ranged_hard", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.nzone_agrith_naar_hard", source = "family:demon_update_ready", attack_anim = "seq.demon_update_attack", defend_anim = "seq.demon_update_defend", death_anim = "seq.demon_update_death" }, + { id = "npc.nzone_agrith_naar_normal", source = "family:demon_update_ready", attack_anim = "seq.demon_update_attack", defend_anim = "seq.demon_update_defend", death_anim = "seq.demon_update_death" }, + { id = "npc.nzone_arceuusquest_soul_hard", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.nzone_arceuusquest_soul_normal", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.nzone_arena_bouncer_hard", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.nzone_arena_bouncer_normal", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.nzone_black_knight_titan_hard", source = "family:giant_ready", attack_anim = "seq.giant_attack", defend_anim = "seq.giant_block", death_anim = "seq.giant_death" }, + { id = "npc.nzone_black_knight_titan_normal", source = "family:giant_ready", attack_anim = "seq.giant_attack", defend_anim = "seq.giant_block", death_anim = "seq.giant_death" }, + { id = "npc.nzone_brain_barrel_chest_hard", source = "family:brain_barrelchest_ready", defend_anim = "seq.brain_barrelchest_defend", death_anim = "seq.brain_barrelchest_death" }, + { id = "npc.nzone_brain_barrel_chest_normal", source = "family:brain_barrelchest_ready", defend_anim = "seq.brain_barrelchest_defend", death_anim = "seq.brain_barrelchest_death" }, + { id = "npc.nzone_chronozon_hard", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.nzone_chronozon_normal", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.nzone_contact_scarab_boss_hard", source = "family:contact_boss_ready", attack_anim = "seq.contact_boss_attack", defend_anim = "seq.contact_boss_defend", death_anim = "seq.contact_boss_death" }, + { id = "npc.nzone_contact_scarab_boss_normal", source = "family:contact_boss_ready", attack_anim = "seq.contact_boss_attack", defend_anim = "seq.contact_boss_defend", death_anim = "seq.contact_boss_death" }, + { id = "npc.nzone_corsaircurse_navigator_hard", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.nzone_corsaircurse_navigator_normal", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.nzone_cow_hard", source = "family:cow_just_ready_update", attack_anim = "seq.cow_attack", defend_anim = "seq.cow_block", death_anim = "seq.cow_death" }, + { id = "npc.nzone_cow_normal", source = "family:cow_just_ready_update", attack_anim = "seq.cow_attack", defend_anim = "seq.cow_block", death_anim = "seq.cow_death" }, + { id = "npc.nzone_dream_everlasting_hard", source = "family:dream_everlasting_ready", attack_anim = "seq.dream_everlasting_attack", defend_anim = "seq.dream_everlasting_defend", death_anim = "seq.dream_everlasting_death" }, + { id = "npc.nzone_dream_everlasting_normal", source = "family:dream_everlasting_ready", attack_anim = "seq.dream_everlasting_attack", defend_anim = "seq.dream_everlasting_defend", death_anim = "seq.dream_everlasting_death" }, + { id = "npc.nzone_dream_inadequacy_hard", source = "family:dream_inadequacy_ready", defend_anim = "seq.dream_inadequacy_defend", death_anim = "seq.dream_inadequacy_death" }, + { id = "npc.nzone_dream_inadequacy_normal", source = "family:dream_inadequacy_ready", defend_anim = "seq.dream_inadequacy_defend", death_anim = "seq.dream_inadequacy_death" }, + { id = "npc.nzone_dream_untouchable_hard", source = "family:dream_untouchable_ready_walk", attack_anim = "seq.dream_untouchable_attack", defend_anim = "seq.dream_untouchable_defend", death_anim = "seq.dream_untouchable_death" }, + { id = "npc.nzone_dream_untouchable_normal", source = "family:dream_untouchable_ready_walk", attack_anim = "seq.dream_untouchable_attack", defend_anim = "seq.dream_untouchable_defend", death_anim = "seq.dream_untouchable_death" }, + { id = "npc.nzone_elvarg_hard", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.nzone_elvarg_normal", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.nzone_fairy_tanglefoot_hard", source = "family:tanglefoot_ready", attack_anim = "seq.tanglefoot_attack", defend_anim = "seq.tanglefoot_block", death_anim = "seq.tanglefoot_death" }, + { id = "npc.nzone_fairy_tanglefoot_normal", source = "family:tanglefoot_ready", attack_anim = "seq.tanglefoot_attack", defend_anim = "seq.tanglefoot_block", death_anim = "seq.tanglefoot_death" }, + { id = "npc.nzone_fd_damis_normal_hard", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.nzone_fd_damis_normal_normal", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.nzone_fd_damis_tougher_hard", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.nzone_fd_damis_tougher_normal", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.nzone_fris_troll_king_true_hard", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.nzone_fris_troll_king_true_normal", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.nzone_ga_beast_hard", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.nzone_ga_beast_normal", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.nzone_grandtree_blackdemon_hard", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.nzone_grandtree_blackdemon_normal", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.nzone_grim_glod_hard", source = "family:grim_giant_ready", attack_anim = "seq.grim_giant_attack", defend_anim = "seq.grim_giant_defend", death_anim = "seq.grim_giant_death" }, + { id = "npc.nzone_grim_glod_normal", source = "family:grim_giant_ready", attack_anim = "seq.grim_giant_attack", defend_anim = "seq.grim_giant_defend", death_anim = "seq.grim_giant_death" }, + { id = "npc.nzone_hauntedmine_boss_ghost_hard", source = "family:ghost_update_tendrill_ready", attack_anim = "seq.ghost_update_tendrill_attack", attack_sound = 436, defend_anim = "seq.ghost_update_tendrill_defend", defend_sound = 439, death_anim = "seq.ghost_update_tendrill_death", death_sound = 438 }, + { id = "npc.nzone_hauntedmine_boss_ghost_normal", source = "family:ghost_update_tendrill_ready", attack_anim = "seq.ghost_update_tendrill_attack", attack_sound = 436, defend_anim = "seq.ghost_update_tendrill_defend", defend_sound = 439, death_anim = "seq.ghost_update_tendrill_death", death_sound = 438 }, + { id = "npc.nzone_horror_dagganoth_air_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_horror_dagganoth_air_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_horror_dagganoth_earth_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_horror_dagganoth_earth_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_horror_dagganoth_fire_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_horror_dagganoth_fire_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_horror_dagganoth_melee_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_horror_dagganoth_melee_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_horror_dagganoth_ranged_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_horror_dagganoth_ranged_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_horror_dagganoth_water_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_horror_dagganoth_water_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hosidiusquest_snake_hard", source = "family:royal_sea_snake_ready", attack_anim = "seq.royal_sea_snake_attack", defend_anim = "seq.royal_sea_snake_defend", death_anim = "seq.royal_sea_snake_death" }, + { id = "npc.nzone_hosidiusquest_snake_normal", source = "family:royal_sea_snake_ready", attack_anim = "seq.royal_sea_snake_attack", defend_anim = "seq.royal_sea_snake_defend", death_anim = "seq.royal_sea_snake_death" }, + { id = "npc.nzone_hundred_culinaromancer_final_hard", source = "weapon:obj.hundred_cleaver", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.nzone_hundred_culinaromancer_final_normal", source = "weapon:obj.hundred_cleaver", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.nzone_hundred_minion1_hard", source = "family:agrith_nana_ready", attack_anim = "seq.agrith_nana_attack", defend_anim = "seq.agrith_nana_block", death_anim = "seq.agrith_nana_death" }, + { id = "npc.nzone_hundred_minion1_normal", source = "family:agrith_nana_ready", attack_anim = "seq.agrith_nana_attack", defend_anim = "seq.agrith_nana_block", death_anim = "seq.agrith_nana_death" }, + { id = "npc.nzone_hundred_minion2_hard", source = "family:rock_ready", attack_anim = "seq.rock_attack", defend_anim = "seq.rock_parry", death_anim = "seq.rock_death" }, + { id = "npc.nzone_hundred_minion2_normal", source = "family:rock_ready", attack_anim = "seq.rock_attack", defend_anim = "seq.rock_parry", death_anim = "seq.rock_death" }, + { id = "npc.nzone_hundred_minion4_hard", source = "family:dessourt_ready", defend_anim = "seq.dessourt_block", death_anim = "seq.dessourt_death" }, + { id = "npc.nzone_hundred_minion4_normal", source = "family:dessourt_ready", defend_anim = "seq.dessourt_block", death_anim = "seq.dessourt_death" }, + { id = "npc.nzone_hundred_minion5_air_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hundred_minion5_air_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hundred_minion5_earth_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hundred_minion5_earth_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hundred_minion5_fire_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hundred_minion5_fire_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hundred_minion5_melee_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hundred_minion5_melee_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hundred_minion5_ranged_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hundred_minion5_ranged_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hundred_minion5_water_hard", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_hundred_minion5_water_normal", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.nzone_icediamond_icewarrior_hard", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.nzone_icediamond_icewarrior_normal", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.nzone_khazard_warlord_hard", source = "weapon:obj.iron_warhammer", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2567, defend_anim = "seq.human_blunt_block" }, + { id = "npc.nzone_khazard_warlord_normal", source = "weapon:obj.iron_warhammer", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2567, defend_anim = "seq.human_blunt_block" }, + { id = "npc.nzone_mdaughter_bearman_fighter_hard", source = "family:bearsuit_ready", attack_anim = "seq.bearsuit_attack", defend_anim = "seq.bearsuit_defend", death_anim = "seq.bearsuit_death" }, + { id = "npc.nzone_mdaughter_bearman_fighter_normal", source = "family:bearsuit_ready", attack_anim = "seq.bearsuit_attack", defend_anim = "seq.bearsuit_defend", death_anim = "seq.bearsuit_death" }, + { id = "npc.nzone_mm_demon_hard", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.nzone_mm_demon_normal", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.nzone_myarm_giant_roc_hard", source = "family:myarm_roc_ready", defend_anim = "seq.myarm_roc_defend", death_anim = "seq.myarm_roc_death" }, + { id = "npc.nzone_myarm_giant_roc_normal", source = "family:myarm_roc_ready", defend_anim = "seq.myarm_roc_defend", death_anim = "seq.myarm_roc_death" }, + { id = "npc.nzone_nezikchened_hard", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.nzone_nezikchened_normal", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.nzone_quest_lunar_mirror_of_player_hard", source = "weapon:obj.lunar_moonclan_liminal_staff", attack_anim = "seq.human_staff_pummel", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.nzone_quest_lunar_mirror_of_player_normal", source = "weapon:obj.lunar_moonclan_liminal_staff", attack_anim = "seq.human_staff_pummel", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.nzone_roving_mossgiant_hard", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.nzone_roving_mossgiant_normal", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.nzone_shapeshifterbear_hard", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.nzone_shapeshifterbear_normal", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.nzone_shapeshifterglob_hard", source = "family:skavid_ready", attack_anim = "seq.skavid_attack", defend_anim = "seq.skavid_block", death_anim = "seq.skavid_death" }, + { id = "npc.nzone_shapeshifterglob_normal", source = "family:skavid_ready", attack_anim = "seq.skavid_attack", defend_anim = "seq.skavid_block", death_anim = "seq.skavid_death" }, + { id = "npc.nzone_shapeshifterspider_hard", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.nzone_shapeshifterspider_normal", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.nzone_shapeshifterwolf_hard", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.nzone_shapeshifterwolf_normal", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.nzone_shayzienquest_lizardman_hard", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.nzone_shayzienquest_lizardman_normal", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.nzone_skeleton_hellhound_hard", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.nzone_skeleton_hellhound_normal", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.nzone_slagilith_hard", source = "family:rock_ready", attack_anim = "seq.rock_attack", defend_anim = "seq.rock_parry", death_anim = "seq.rock_death" }, + { id = "npc.nzone_slagilith_normal", source = "family:rock_ready", attack_anim = "seq.rock_attack", defend_anim = "seq.rock_parry", death_anim = "seq.rock_death" }, + { id = "npc.nzone_sote_essyllt_hard", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.nzone_sote_essyllt_normal", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.nzone_tree_spirit_hard", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.nzone_tree_spirit_normal", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.nzone_troll_champion_hard", source = "family:troll_ready_sword", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.nzone_troll_champion_normal", source = "family:troll_ready_sword", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.nzone_trollromance_arrg_hard", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.nzone_trollromance_arrg_normal", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.nzone_zq_mainzombie1_hard", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.nzone_zq_mainzombie1_normal", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.nzone_zq_mainzombie2_hard", source = "family:skeleton_update_giant_vary3_ready", attack_anim = "seq.skeleton_update_giant_vary3_attack", defend_anim = "seq.skeleton_update_giant_vary3_defend", death_anim = "seq.skeleton_update_giant_vary3_death" }, + { id = "npc.nzone_zq_mainzombie2_normal", source = "family:skeleton_update_giant_vary3_ready", attack_anim = "seq.skeleton_update_giant_vary3_attack", defend_anim = "seq.skeleton_update_giant_vary3_defend", death_anim = "seq.skeleton_update_giant_vary3_death" }, + { id = "npc.nzone_zq_mainzombie3_hard", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.nzone_zq_mainzombie3_normal", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.ogre", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.ogre2", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.ogre_chieftan", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.ogre_red_axe_shaman", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.ogre_shaman", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.ogress_shaman1", source = "family:ogre_ready_new", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.ogress_shaman2", source = "family:ogre_ready_new", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.ogress_warrior1", source = "family:ogre_ready_new", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.ogress_warrior2", source = "family:ogre_ready_new", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.olaf2_brine_rats", source = "family:olaf2_brine_rat_ready", attack_anim = "seq.olaf2_brine_rat_attack", defend_anim = "seq.olaf2_brine_rat_defend", death_anim = "seq.olaf2_brine_rat_death" }, + { id = "npc.olaf2_giant_bat", source = "family:firebat_ready", attack_anim = "seq.firebat_attack", defend_anim = "seq.firebat_defend", death_anim = "seq.firebat_death" }, + { id = "npc.olaf2_ulfric", source = "family:olaf2_undead_ready", attack_anim = "seq.olaf2_undead_sword_lunge", defend_anim = "seq.olaf2_undead_sword_def" }, + { id = "npc.olaf2_undead_viking_lvl40", source = "family:olaf2_undead_ready", attack_anim = "seq.olaf2_undead_sword_lunge", defend_anim = "seq.olaf2_undead_sword_def" }, + { id = "npc.olaf2_undead_viking_lvl40_b", source = "family:olaf2_undead_ready", attack_anim = "seq.olaf2_undead_sword_lunge", defend_anim = "seq.olaf2_undead_sword_def" }, + { id = "npc.olaf2_undead_viking_lvl40_c", source = "family:olaf2_undead_ready", attack_anim = "seq.olaf2_undead_sword_lunge", defend_anim = "seq.olaf2_undead_sword_def" }, + { id = "npc.olaf2_undead_viking_lvl50", source = "family:olaf2_undead_ready", attack_anim = "seq.olaf2_undead_sword_lunge", defend_anim = "seq.olaf2_undead_sword_def" }, + { id = "npc.olaf2_undead_viking_lvl50_b", source = "family:olaf2_undead_ready", attack_anim = "seq.olaf2_undead_sword_lunge", defend_anim = "seq.olaf2_undead_sword_def" }, + { id = "npc.olaf2_undead_viking_lvl50_c", source = "family:olaf2_undead_ready", attack_anim = "seq.olaf2_undead_sword_lunge", defend_anim = "seq.olaf2_undead_sword_def" }, + { id = "npc.olaf2_undead_viking_lvl60", source = "family:olaf2_undead_ready", attack_anim = "seq.olaf2_undead_sword_lunge", defend_anim = "seq.olaf2_undead_sword_def" }, + { id = "npc.olaf2_undead_viking_lvl60_b", source = "family:olaf2_undead_ready", attack_anim = "seq.olaf2_undead_sword_lunge", defend_anim = "seq.olaf2_undead_sword_def" }, + { id = "npc.olaf2_undead_viking_lvl60_c", source = "family:olaf2_undead_ready", attack_anim = "seq.olaf2_undead_sword_lunge", defend_anim = "seq.olaf2_undead_sword_def" }, + { id = "npc.oomlie_bird", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.osb10_toa_akkha_cutscene", source = "family:npc_akkha_idle_spear", death_anim = "seq.npc_akkha_death_spear" }, + { id = "npc.osb10_toa_baba_cutscene", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.osb10_toa_kephri_cutscene", source = "family:npc_kephri_idle", death_anim = "seq.npc_kephri_death" }, + { id = "npc.osb10_toa_zebak_cutscene", source = "family:npc_zebak01_idle", attack_anim = "seq.npc_zebak01_attack_melee", death_anim = "seq.npc_zebak01_death" }, + { id = "npc.othainian_vis", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.pack_wolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.pack_wolf2", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.pack_wolf3", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.pack_wolf_whiter", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.paladin", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.paladin2", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.paladin_variant01", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.paladin_variant02", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.paladin_west_vis", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.peng_icelord_warrior01", source = "family:peng_icelord_ready", attack_anim = "seq.peng_icelord_attack", defend_anim = "seq.peng_icelord_defend", death_anim = "seq.peng_icelord_death" }, + { id = "npc.peng_icelord_warrior02", source = "family:peng_icelord_ready", attack_anim = "seq.peng_icelord_attack", defend_anim = "seq.peng_icelord_defend", death_anim = "seq.peng_icelord_death" }, + { id = "npc.peng_icelord_warrior03", source = "family:peng_icelord_ready", attack_anim = "seq.peng_icelord_attack", defend_anim = "seq.peng_icelord_defend", death_anim = "seq.peng_icelord_death" }, + { id = "npc.peng_icelord_warrior04", source = "family:peng_icelord_ready", attack_anim = "seq.peng_icelord_attack", defend_anim = "seq.peng_icelord_defend", death_anim = "seq.peng_icelord_death" }, + { id = "npc.penguin", source = "family:peng_gentoo_ready", attack_anim = "seq.peng_gentoo_attack", defend_anim = "seq.peng_gentoo_defend", death_anim = "seq.peng_gentoo_death" }, + { id = "npc.pest_brawler_1", source = "family:brawler_ready", attack_anim = "seq.brawler_attack", defend_anim = "seq.brawler_defend", death_anim = "seq.brawler_death" }, + { id = "npc.pest_brawler_2", source = "family:brawler_ready", attack_anim = "seq.brawler_attack", defend_anim = "seq.brawler_defend", death_anim = "seq.brawler_death" }, + { id = "npc.pest_brawler_3", source = "family:brawler_ready", attack_anim = "seq.brawler_attack", defend_anim = "seq.brawler_defend", death_anim = "seq.brawler_death" }, + { id = "npc.pest_brawler_4", source = "family:brawler_ready", attack_anim = "seq.brawler_attack", defend_anim = "seq.brawler_defend", death_anim = "seq.brawler_death" }, + { id = "npc.pest_brawler_5", source = "family:brawler_ready", attack_anim = "seq.brawler_attack", defend_anim = "seq.brawler_defend", death_anim = "seq.brawler_death" }, + { id = "npc.pest_defiler_1a", source = "family:defiler_ready", attack_anim = "seq.defiler_attack", defend_anim = "seq.defiler_defend", death_anim = "seq.defiler_death" }, + { id = "npc.pest_defiler_1b", source = "family:defiler_ready", attack_anim = "seq.defiler_attack", defend_anim = "seq.defiler_defend", death_anim = "seq.defiler_death" }, + { id = "npc.pest_defiler_2a", source = "family:defiler_ready", attack_anim = "seq.defiler_attack", defend_anim = "seq.defiler_defend", death_anim = "seq.defiler_death" }, + { id = "npc.pest_defiler_2b", source = "family:defiler_ready", attack_anim = "seq.defiler_attack", defend_anim = "seq.defiler_defend", death_anim = "seq.defiler_death" }, + { id = "npc.pest_defiler_3a", source = "family:defiler_ready", attack_anim = "seq.defiler_attack", defend_anim = "seq.defiler_defend", death_anim = "seq.defiler_death" }, + { id = "npc.pest_defiler_3b", source = "family:defiler_ready", attack_anim = "seq.defiler_attack", defend_anim = "seq.defiler_defend", death_anim = "seq.defiler_death" }, + { id = "npc.pest_defiler_4a", source = "family:defiler_ready", attack_anim = "seq.defiler_attack", defend_anim = "seq.defiler_defend", death_anim = "seq.defiler_death" }, + { id = "npc.pest_defiler_4b", source = "family:defiler_ready", attack_anim = "seq.defiler_attack", defend_anim = "seq.defiler_defend", death_anim = "seq.defiler_death" }, + { id = "npc.pest_defiler_5a", source = "family:defiler_ready", attack_anim = "seq.defiler_attack", defend_anim = "seq.defiler_defend", death_anim = "seq.defiler_death" }, + { id = "npc.pest_defiler_5b", source = "family:defiler_ready", attack_anim = "seq.defiler_attack", defend_anim = "seq.defiler_defend", death_anim = "seq.defiler_death" }, + { id = "npc.pest_ravager_1", source = "family:ravager_ready", attack_anim = "seq.ravager_attack", defend_anim = "seq.ravager_defend", death_anim = "seq.ravager_death" }, + { id = "npc.pest_ravager_2", source = "family:ravager_ready", attack_anim = "seq.ravager_attack", defend_anim = "seq.ravager_defend", death_anim = "seq.ravager_death" }, + { id = "npc.pest_ravager_3", source = "family:ravager_ready", attack_anim = "seq.ravager_attack", defend_anim = "seq.ravager_defend", death_anim = "seq.ravager_death" }, + { id = "npc.pest_ravager_4", source = "family:ravager_ready", attack_anim = "seq.ravager_attack", defend_anim = "seq.ravager_defend", death_anim = "seq.ravager_death" }, + { id = "npc.pest_ravager_5", source = "family:ravager_ready", attack_anim = "seq.ravager_attack", defend_anim = "seq.ravager_defend", death_anim = "seq.ravager_death" }, + { id = "npc.pest_shifter_1a", source = "family:shifter_ready", attack_anim = "seq.shifter_attack", defend_anim = "seq.shifter_defend", death_anim = "seq.shifter_death" }, + { id = "npc.pest_shifter_1b", source = "family:shifter_ready", attack_anim = "seq.shifter_attack", defend_anim = "seq.shifter_defend", death_anim = "seq.shifter_death" }, + { id = "npc.pest_shifter_2a", source = "family:shifter_ready", attack_anim = "seq.shifter_attack", defend_anim = "seq.shifter_defend", death_anim = "seq.shifter_death" }, + { id = "npc.pest_shifter_2b", source = "family:shifter_ready", attack_anim = "seq.shifter_attack", defend_anim = "seq.shifter_defend", death_anim = "seq.shifter_death" }, + { id = "npc.pest_shifter_3a", source = "family:shifter_ready", attack_anim = "seq.shifter_attack", defend_anim = "seq.shifter_defend", death_anim = "seq.shifter_death" }, + { id = "npc.pest_shifter_3b", source = "family:shifter_ready", attack_anim = "seq.shifter_attack", defend_anim = "seq.shifter_defend", death_anim = "seq.shifter_death" }, + { id = "npc.pest_shifter_4a", source = "family:shifter_ready", attack_anim = "seq.shifter_attack", defend_anim = "seq.shifter_defend", death_anim = "seq.shifter_death" }, + { id = "npc.pest_shifter_4b", source = "family:shifter_ready", attack_anim = "seq.shifter_attack", defend_anim = "seq.shifter_defend", death_anim = "seq.shifter_death" }, + { id = "npc.pest_shifter_5a", source = "family:shifter_ready", attack_anim = "seq.shifter_attack", defend_anim = "seq.shifter_defend", death_anim = "seq.shifter_death" }, + { id = "npc.pest_shifter_5b", source = "family:shifter_ready", attack_anim = "seq.shifter_attack", defend_anim = "seq.shifter_defend", death_anim = "seq.shifter_death" }, + { id = "npc.pest_spinner_1", source = "family:spinner_ready", attack_anim = "seq.spinner_attack", defend_anim = "seq.spinner_defend", death_anim = "seq.spinner_death" }, + { id = "npc.pest_spinner_2", source = "family:spinner_ready", attack_anim = "seq.spinner_attack", defend_anim = "seq.spinner_defend", death_anim = "seq.spinner_death" }, + { id = "npc.pest_spinner_3", source = "family:spinner_ready", attack_anim = "seq.spinner_attack", defend_anim = "seq.spinner_defend", death_anim = "seq.spinner_death" }, + { id = "npc.pest_spinner_4", source = "family:spinner_ready", attack_anim = "seq.spinner_attack", defend_anim = "seq.spinner_defend", death_anim = "seq.spinner_death" }, + { id = "npc.pest_spinner_5", source = "family:spinner_ready", attack_anim = "seq.spinner_attack", defend_anim = "seq.spinner_defend", death_anim = "seq.spinner_death" }, + { id = "npc.pest_splatter_1", source = "family:splatter_ready", attack_anim = "seq.splatter_attack", defend_anim = "seq.splatter_defend", death_anim = "seq.splatter_death" }, + { id = "npc.pest_splatter_2", source = "family:splatter_ready", attack_anim = "seq.splatter_attack", defend_anim = "seq.splatter_defend", death_anim = "seq.splatter_death" }, + { id = "npc.pest_splatter_3", source = "family:splatter_ready", attack_anim = "seq.splatter_attack", defend_anim = "seq.splatter_defend", death_anim = "seq.splatter_death" }, + { id = "npc.pest_splatter_4", source = "family:splatter_ready", attack_anim = "seq.splatter_attack", defend_anim = "seq.splatter_defend", death_anim = "seq.splatter_death" }, + { id = "npc.pest_splatter_5", source = "family:splatter_ready", attack_anim = "seq.splatter_attack", defend_anim = "seq.splatter_defend", death_anim = "seq.splatter_death" }, + { id = "npc.pest_torcher_1a", source = "family:torcher_ready", attack_anim = "seq.torcher_attack", defend_anim = "seq.torcher_defend", death_anim = "seq.torcher_death" }, + { id = "npc.pest_torcher_1b", source = "family:torcher_ready", attack_anim = "seq.torcher_attack", defend_anim = "seq.torcher_defend", death_anim = "seq.torcher_death" }, + { id = "npc.pest_torcher_2a", source = "family:torcher_ready", attack_anim = "seq.torcher_attack", defend_anim = "seq.torcher_defend", death_anim = "seq.torcher_death" }, + { id = "npc.pest_torcher_2b", source = "family:torcher_ready", attack_anim = "seq.torcher_attack", defend_anim = "seq.torcher_defend", death_anim = "seq.torcher_death" }, + { id = "npc.pest_torcher_3a", source = "family:torcher_ready", attack_anim = "seq.torcher_attack", defend_anim = "seq.torcher_defend", death_anim = "seq.torcher_death" }, + { id = "npc.pest_torcher_3b", source = "family:torcher_ready", attack_anim = "seq.torcher_attack", defend_anim = "seq.torcher_defend", death_anim = "seq.torcher_death" }, + { id = "npc.pest_torcher_4a", source = "family:torcher_ready", attack_anim = "seq.torcher_attack", defend_anim = "seq.torcher_defend", death_anim = "seq.torcher_death" }, + { id = "npc.pest_torcher_4b", source = "family:torcher_ready", attack_anim = "seq.torcher_attack", defend_anim = "seq.torcher_defend", death_anim = "seq.torcher_death" }, + { id = "npc.pest_torcher_5a", source = "family:torcher_ready", attack_anim = "seq.torcher_attack", defend_anim = "seq.torcher_defend", death_anim = "seq.torcher_death" }, + { id = "npc.pest_torcher_5b", source = "family:torcher_ready", attack_anim = "seq.torcher_attack", defend_anim = "seq.torcher_defend", death_anim = "seq.torcher_death" }, + { id = "npc.pirate1", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.pirate2", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.pirate2_aggressive", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.pirate3_aggressive", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.pirate_aggressive", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.pirate_guard", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.pirate_pickpocketable_1", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.pirate_pickpocketable_2", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.pirate_pickpocketable_3", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.pmoon_boss_blood_moon", source = "family:npc_djinn_01_idle", attack_anim = "seq.npc_djinn_01_melee" }, + { id = "npc.pmoon_boss_blue_moon", source = "family:npc_djinn_01_idle", attack_anim = "seq.npc_djinn_01_melee" }, + { id = "npc.pmoon_boss_eclipse_moon", source = "family:npc_djinn_02_idle", attack_anim = "seq.npc_djinn_02_melee" }, + { id = "npc.pmoon_boss_jaguar", source = "family:npc_lynx_combat_idle", attack_anim = "seq.npc_lynx_combat_melee", death_anim = "seq.npc_lynx_combat_death" }, + { id = "npc.pmoon_quest_nagua", source = "family:npc_djinn_01_idle", attack_anim = "seq.npc_djinn_01_melee" }, + { id = "npc.pmoon_quest_nagua_vis", source = "family:npc_djinn_01_idle", attack_anim = "seq.npc_djinn_01_melee" }, + { id = "npc.pmoon_sulphur_nagua", source = "family:npc_djinn_01_idle", attack_anim = "seq.npc_djinn_01_melee" }, + { id = "npc.pog_mutated_terrorbird_1", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.pog_mutated_terrorbird_2", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.pog_mutated_terrorbird_3", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.pog_mutated_terrorbird_4", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.pog_mutated_terrorbird_5", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.pog_mutated_terrorbird_6", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.pog_mutated_terrorbird_7", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.pog_mutated_terrorbird_8", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.pog_mutated_terrorbird_boss_1", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.pog_mutated_terrorbird_boss_2", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.pog_mutated_terrorbird_boss_3", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.pog_mutated_tortoise", source = "family:pog_mutated_battle_tortoise_ready", attack_anim = "seq.pog_mutated_battle_tortoise_attack", defend_anim = "seq.pog_mutated_battle_tortoise_defend", death_anim = "seq.pog_mutated_battle_tortoise_death" }, + { id = "npc.poh_babyreddragon", source = "family:bdrag_ready", attack_anim = "seq.bdrag_attack", defend_anim = "seq.bdrag_block", death_anim = "seq.bdrag_death" }, + { id = "npc.poh_dagganoth", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.poh_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.poh_giantspider", source = "family:spider_update_ready_large", attack_anim = "seq.spider_update_attack_large", attack_sound = 537, defend_anim = "seq.spider_update_defend_large", defend_sound = 539, death_anim = "seq.spider_update_death_large", death_sound = 538 }, + { id = "npc.poh_guarddog", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.poh_hellhound", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.poh_hobgoblin", source = "family:hobgoblin_ready", attack_anim = "seq.hobgoblin_attack", defend_anim = "seq.hobgoblin_block", death_anim = "seq.hobgoblin_death" }, + { id = "npc.poh_kalphite_soldier", source = "family:kalphite_update_ready", defend_anim = "seq.kalphite_update_defend", death_anim = "seq.kalphite_update_death" }, + { id = "npc.poh_ogre", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.poh_oub_monster1", source = "family:oub_monster1_ready", attack_anim = "seq.oub_monster1_attack", defend_anim = "seq.oub_monster1_block", death_anim = "seq.oub_monster1_death" }, + { id = "npc.poh_rune_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.poh_skeleton", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.poh_tok_xil", source = "family:magmaquris_ready", attack_anim = "seq.magmaquris_punch", defend_anim = "seq.magmaquris_defend", death_anim = "seq.magmaquris_death" }, + { id = "npc.poh_troll", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.poison_scorpion", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.poisonspider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.porcine_sourhog_second", source = "family:sourhog_idle", attack_anim = "seq.sourhog_attack_melee", death_anim = "seq.sourhog_death" }, + { id = "npc.port_roberts_guard_m1", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.port_roberts_guard_m2", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.possessed_pickaxe", source = "family:magicaxe_ready", attack_anim = "seq.magicaxe_attack", defend_anim = "seq.magicaxe_block", death_anim = "seq.magicaxe_death" }, + { id = "npc.praying_mantis", source = "family:ti_mantis_ready", defend_anim = "seq.ti_mantis_defend", death_anim = "seq.ti_mantis_death" }, + { id = "npc.praying_mantis_brown", source = "family:ti_mantis_ready", defend_anim = "seq.ti_mantis_defend", death_anim = "seq.ti_mantis_death" }, + { id = "npc.praying_mantis_orchid", source = "family:ti_mantis_ready", defend_anim = "seq.ti_mantis_defend", death_anim = "seq.ti_mantis_death" }, + { id = "npc.priestperil_guardian_model", source = "family:dm_ready", attack_anim = "seq.dm_attack", defend_anim = "seq.dm_block", death_anim = "seq.dm_death" }, + { id = "npc.priestperilevilmonk1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.priestperilevilmonk1_vis", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.priestperilevilmonk2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.priestperilevilmonk2_vis", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.priestperilevilmonk3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.priestperilevilmonk3_vis", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.priestperilguarddog", source = "family:dm_ready", attack_anim = "seq.dm_attack", defend_anim = "seq.dm_block", death_anim = "seq.dm_death" }, + { id = "npc.prif_darkelf2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.prif_darkelf3", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.prif_guard0", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.prif_guard1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.prif_guard2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.prif_guard3", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.prif_guard4", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.prif_guard5", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.prif_guard6", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.prif_guard7", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.prif_mossgiant", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.qip_ds_young_dark_wizard1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.qip_ds_young_dark_wizard2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.qip_ds_young_dark_wizard3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.qip_ds_young_dark_wizard4", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.qip_watchtower_ogre_shaman_01_normal", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.qip_watchtower_ogre_shaman_02_normal", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.qip_watchtower_ogre_shaman_03_normal", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.qip_watchtower_ogre_shaman_04_normal", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.qip_watchtower_ogre_shaman_05_normal", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.qip_watchtower_ogre_shaman_06_normal", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.quest_lunar_mirror_of_player", source = "weapon:obj.lunar_moonclan_liminal_staff", attack_anim = "seq.human_staff_pummel", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.quest_lunar_mirror_of_player_female", source = "weapon:obj.lunar_moonclan_liminal_staff", attack_anim = "seq.human_staff_pummel", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.rabbit_1", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.rabbit_2", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.rabbit_3", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.rag_vulture", source = "family:vulture_ground_ready", attack_anim = "seq.vulture_ground_attack", defend_anim = "seq.vulture_ground_block", death_anim = "seq.vulture_ground_death" }, + { id = "npc.raids_dogodile", source = "family:dohgadyle_idle", defend_anim = "seq.dohgadyle_defend", death_anim = "seq.dohgadyle_death" }, + { id = "npc.raids_dogodile_junior", source = "family:dohgadyle_idle", defend_anim = "seq.dohgadyle_defend", death_anim = "seq.dohgadyle_death" }, + { id = "npc.raids_icedemon_combat", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.raids_lasercrabs_crab_blue", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.raids_lasercrabs_crab_green", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.raids_lasercrabs_crab_grey", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.raids_lasercrabs_crab_red", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.raids_lizardshaman_a", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.raids_lizardshaman_b", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.raids_scavenger_beast_a", source = "family:skavid_ready", attack_anim = "seq.skavid_attack", defend_anim = "seq.skavid_block", death_anim = "seq.skavid_death" }, + { id = "npc.raids_scavenger_beast_b", source = "family:skavid_ready", attack_anim = "seq.skavid_attack", defend_anim = "seq.skavid_block", death_anim = "seq.skavid_death" }, + { id = "npc.raids_skeletonmystic_a", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.raids_skeletonmystic_b", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.raids_skeletonmystic_c", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.raids_tekton_fighting_enraged", source = "family:tekton_ready_enraged", attack_anim = "seq.tekton_slash_enraged", death_anim = "seq.tekton_death" }, + { id = "npc.raids_tekton_fighting_standard", source = "family:tekton_ready", attack_anim = "seq.tekton_slash", death_anim = "seq.tekton_death" }, + { id = "npc.raids_tekton_walking_enraged", source = "family:tekton_idle_enraged", attack_anim = "seq.tekton_slash_enraged", death_anim = "seq.tekton_death" }, + { id = "npc.raids_tekton_walking_standard", source = "family:tekton_idle", attack_anim = "seq.tekton_slash", death_anim = "seq.tekton_death" }, + { id = "npc.raids_tightrope_mage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.raids_tightrope_ranger", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.raids_vanguard_magic", source = "family:vanguard_idle_magic", attack_anim = "seq.vanguard_attack_magic", defend_anim = "seq.vanguard_defend_magic", death_anim = "seq.vanguard_death" }, + { id = "npc.raids_vanguard_melee", source = "family:vanguard_idle_melee", attack_anim = "seq.vanguard_attack_melee", defend_anim = "seq.vanguard_defend_melee", death_anim = "seq.vanguard_death" }, + { id = "npc.raids_vanguard_ranged", source = "family:vanguard_idle_ranged", attack_anim = "seq.vanguard_attack_ranged", defend_anim = "seq.vanguard_defend_ranged", death_anim = "seq.vanguard_death" }, + { id = "npc.raids_vasanistirio_healing", source = "family:vasa_heal_idle", attack_anim = "seq.vasa_attack", death_anim = "seq.vasa_death" }, + { id = "npc.raids_vasanistirio_walking", source = "family:vasa_idle", attack_anim = "seq.vasa_attack", death_anim = "seq.vasa_death" }, + { id = "npc.raids_vespula_vespine_walking", source = "family:vespula_idle", attack_anim = "seq.vespula_attack_melee_flying", defend_anim = "seq.vespula_defend" }, + { id = "npc.raids_vespula_walking", source = "family:vespula_idle", attack_anim = "seq.vespula_attack_melee_flying", defend_anim = "seq.vespula_defend" }, + { id = "npc.ramsheered", source = "family:goat_update_ready", attack_anim = "seq.goat_update_attack", defend_anim = "seq.goat_update_defend", death_anim = "seq.goat_update_death" }, + { id = "npc.ramunsheeredshaggy", source = "family:sheep_update_shaggy_ready", attack_anim = "seq.sheep_update_attack", defend_anim = "seq.sheep_update_shaggy_defend", death_anim = "seq.sheep_update_death" }, + { id = "npc.ranalph_devere", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.ranging_guild_guard", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.rat_boss_instance", source = "family:npc_rat_boss_idle_01", death_anim = "seq.npc_rat_boss_death_01" }, + { id = "npc.rat_boss_normal", source = "family:npc_rat_boss_idle_01", death_anim = "seq.npc_rat_boss_death_01" }, + { id = "npc.rat_indoors", source = "family:mouse_ready", attack_anim = "seq.mouse_attack", defend_anim = "seq.mouse_defend", death_anim = "seq.mouse_death" }, + { id = "npc.rat_lowwander", source = "family:mouse_ready", attack_anim = "seq.mouse_attack", defend_anim = "seq.mouse_defend", death_anim = "seq.mouse_death" }, + { id = "npc.rc_zmi_lizard", source = "family:rc_zmi_lizard_ready", attack_anim = "seq.rc_zmi_lizard_attack", defend_anim = "seq.rc_zmi_lizard_defend" }, + { id = "npc.rc_zmi_mage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.rc_zmi_mage2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.rc_zmi_melee", source = "shield:obj.poh_shield_zamorak_rune", defend_anim = "seq.human_shield_defence" }, + { id = "npc.rc_zmi_melee2", source = "weapon:obj.dragon_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.rc_zmi_ranger", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.rc_zmi_ranger2", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.rc_zmi_runerunner", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.rc_zmi_runerunner2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.rd_combat_npc_room_3", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.red_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.red_dragon2", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.red_dragon3", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.red_dragon4", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.red_dragon5", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.regicide_bunny1", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.regicide_bunny2", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.regicide_darkbear", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.regicide_darkbear_cub1", source = "family:bear_rework_cub_ready", attack_anim = "seq.bear_rework_cub_attack", defend_anim = "seq.bear_rework_cub_defend", death_anim = "seq.bear_rework_cub_death" }, + { id = "npc.regicide_darkbear_cub2", source = "family:bear_rework_cub_ready", attack_anim = "seq.bear_rework_cub_attack", defend_anim = "seq.bear_rework_cub_defend", death_anim = "seq.bear_rework_cub_death" }, + { id = "npc.regicide_darkelf", source = "ranger", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.regicide_darkelf2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.regicide_darkelf3", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.regicide_darkelf4", source = "ranger", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.regicide_old_camp_guard", source = "weapon:obj.bronze_halberd", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.regicide_rabbit", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.regicide_tyras_camp_guard", source = "weapon:obj.bronze_halberd", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.regicide_tyras_guard", source = "weapon:obj.bronze_halberd", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.renegade_knight", source = "family:kr_keep_le_faye_guard_ready", defend_anim = "seq.kr_keep_le_faye_guard_defend" }, + { id = "npc.rimmington_hobgoblin_armed_1", source = "family:hobgoblin_ready", attack_anim = "seq.hobgoblin_attack", defend_anim = "seq.hobgoblin_block", death_anim = "seq.hobgoblin_death" }, + { id = "npc.rimmington_hobgoblin_unarmed_1", source = "family:hobgoblin_ready", attack_anim = "seq.hobgoblin_attack", defend_anim = "seq.hobgoblin_block", death_anim = "seq.hobgoblin_death" }, + { id = "npc.rimmington_hobgoblin_unarmed_2", source = "family:hobgoblin_ready", attack_anim = "seq.hobgoblin_attack", defend_anim = "seq.hobgoblin_block", death_anim = "seq.hobgoblin_death" }, + { id = "npc.rimmington_hobgoblin_unarmed_3", source = "family:hobgoblin_ready", attack_anim = "seq.hobgoblin_attack", defend_anim = "seq.hobgoblin_block", death_anim = "seq.hobgoblin_death" }, + { id = "npc.rogue", source = "weapon:obj.iron_dagger", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_sword_def" }, + { id = "npc.rooster", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.roving_mossgiant", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.rowdymineguard", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.royal_sea_snake_baby", source = "family:royal_sea_snake_ready", attack_anim = "seq.royal_sea_snake_attack", defend_anim = "seq.royal_sea_snake_defend", death_anim = "seq.royal_sea_snake_death" }, + { id = "npc.royal_sea_snake_medium", source = "family:royal_sea_snake_ready", attack_anim = "seq.royal_sea_snake_attack", defend_anim = "seq.royal_sea_snake_defend", death_anim = "seq.royal_sea_snake_death" }, + { id = "npc.royal_sea_snake_mother_smaller", source = "family:royal_sea_snake_mother_ready", attack_anim = "seq.royal_sea_snake_mother_attack", defend_anim = "seq.royal_sea_snake_mother_defend", death_anim = "seq.royal_sea_snake_mother_death" }, + { id = "npc.rt_fire_queen", source = "family:fire_giant_branda_queen_idle01", death_anim = "seq.fire_giant_branda_queen_death01" }, + { id = "npc.rt_ice_king", source = "family:ice_giant_eldric_king_idle01", death_anim = "seq.ice_giant_eldric_king_death01" }, + { id = "npc.rt_summon_elemental_fire", source = "family:fire_minion_idle01", attack_anim = "seq.fire_minion_attack01", death_anim = "seq.fire_minion_death01" }, + { id = "npc.rt_summon_elemental_ice", source = "family:ice_minion_idle01", attack_anim = "seq.ice_minion_attack01", death_anim = "seq.ice_minion_death01" }, + { id = "npc.rune_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.sailing_armoured_kraken", source = "family:npc_kraken01_idle01", attack_anim = "seq.npc_kraken01_melee01", death_anim = "seq.npc_kraken01_death01" }, + { id = "npc.sailing_bull_shark", source = "family:sailing_shark_idle01", attack_anim = "seq.sailing_shark_attack01", death_anim = "seq.sailing_shark_death01" }, + { id = "npc.sailing_butterfly_ray", source = "family:npc_butterflyray01_idle01", attack_anim = "seq.npc_butterflyray01_attack01", death_anim = "seq.npc_butterflyray01_death01" }, + { id = "npc.sailing_charting_drink_crate_blue_lagoon_effect", source = "family:horror_dagannoth_ready", attack_anim = "seq.horror_dagannoth_attack", defend_anim = "seq.horror_dagannoth_defend", death_anim = "seq.horror_dagannoth_death" }, + { id = "npc.sailing_charting_drink_crate_corpse_reviver_effect", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.sailing_charting_drink_crate_fish_stoutier_effect", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.sailing_charting_drink_crate_monkfish_stout_effect", source = "family:swan_queen_ready", attack_anim = "seq.swan_queen_attack", defend_anim = "seq.swan_queen_defend", death_anim = "seq.swan_queen_death" }, + { id = "npc.sailing_charting_drink_crate_possible_albumen_effect", source = "family:tortugan_ready_staff", attack_anim = "seq.tortugan_staff_stab", death_anim = "seq.tortugan_death" }, + { id = "npc.sailing_charting_drink_crate_prying_times_effect_troll", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.sailing_charting_drink_crate_snake_gravy_effect_snakeling", source = "family:snakeboss_pet_idle", attack_anim = "seq.snakeboss_pet_attack", defend_anim = "seq.snakeboss_pet_defend", death_anim = "seq.snakeboss_pet_death" }, + { id = "npc.sailing_charting_drink_troll_dad", source = "family:troll_ready_sword", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.sailing_charting_drink_troll_ice", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.sailing_charting_drink_troll_thrower", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.sailing_dolphin", source = "family:sailing_dolphin_idle01", attack_anim = "seq.sailing_dolphin_attack01", death_anim = "seq.sailing_dolphin_death01" }, + { id = "npc.sailing_eagle_ray", source = "family:npc_eagleray01_idle01", attack_anim = "seq.npc_eagleray01_attack01", death_anim = "seq.npc_eagleray01_death01" }, + { id = "npc.sailing_frigatebird", source = "family:npc_frigatebird_flying_idle_01", attack_anim = "seq.npc_frigatebird_flying_attack_01", death_anim = "seq.npc_frigatebird_flying_death_01" }, + { id = "npc.sailing_great_white_shark", source = "family:sailing_shark_idle01", attack_anim = "seq.sailing_shark_attack01", death_anim = "seq.sailing_shark_death01" }, + { id = "npc.sailing_hammerhead_shark", source = "family:sailing_shark_idle01", attack_anim = "seq.sailing_shark_attack01", death_anim = "seq.sailing_shark_death01" }, + { id = "npc.sailing_manta_ray", source = "family:npc_mantaray01_idle01", attack_anim = "seq.npc_mantaray01_attack01", death_anim = "seq.npc_mantaray01_death01" }, + { id = "npc.sailing_narwhal", source = "family:sailing_monodon_idle01", attack_anim = "seq.sailing_monodon_attack01", death_anim = "seq.sailing_monodon_death01" }, + { id = "npc.sailing_npc_boat_combat_crew_captain_1", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.sailing_npc_boat_combat_crew_captain_2", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.sailing_npc_boat_combat_crew_test_mage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.sailing_npc_boat_combat_crew_trr_1", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.sailing_npc_boat_combat_crew_trr_2", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.sailing_npc_boat_combat_crew_trr_3", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.sailing_npc_boat_combat_crew_trr_4", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.sailing_npc_boat_combat_crew_trr_5", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.sailing_npc_boat_combat_crew_trr_6", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.sailing_orca", source = "family:sailing_orca_idle01", attack_anim = "seq.sailing_orca_attack01", death_anim = "seq.sailing_orca_death01" }, + { id = "npc.sailing_osprey", source = "family:npc_osprey_flying_idle_01", attack_anim = "seq.npc_osprey_flying_attack_01", death_anim = "seq.npc_osprey_flying_death_01" }, + { id = "npc.sailing_pygmy_kraken", source = "family:npc_kraken01_idle01", attack_anim = "seq.npc_kraken01_melee01", death_anim = "seq.npc_kraken01_death01" }, + { id = "npc.sailing_sea_mogre", source = "family:npc_sea_mogre_idle01", attack_anim = "seq.npc_sea_mogre_attack_melee01" }, + { id = "npc.sailing_sea_mogre_f", source = "family:npc_sea_mogre_idle01", attack_anim = "seq.npc_sea_mogre_attack_melee01" }, + { id = "npc.sailing_spined_kraken", source = "family:npc_kraken01_idle01", attack_anim = "seq.npc_kraken01_melee01", death_anim = "seq.npc_kraken01_death01" }, + { id = "npc.sailing_stingray", source = "family:npc_stingray01_idle01", attack_anim = "seq.npc_stingray01_attack01", death_anim = "seq.npc_stingray01_death01" }, + { id = "npc.sailing_tern", source = "family:npc_tern_flying_idle_01", attack_anim = "seq.npc_tern_flying_attack_01", death_anim = "seq.npc_tern_flying_death_01" }, + { id = "npc.sailing_tiger_shark", source = "family:sailing_shark_idle01", attack_anim = "seq.sailing_shark_attack01", death_anim = "seq.sailing_shark_death01" }, + { id = "npc.sailing_vampyre_kraken", source = "family:npc_kraken01_idle01", attack_anim = "seq.npc_kraken01_melee01", death_anim = "seq.npc_kraken01_death01" }, + { id = "npc.sailing_veiled_kraken", source = "family:npc_kraken01_idle01", attack_anim = "seq.npc_kraken01_melee01", death_anim = "seq.npc_kraken01_death01" }, + { id = "npc.san_tojalon", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.sang_myq3_female_flying_ns_vyrewatch_1", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_female_flying_ns_vyrewatch_2", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_female_flying_ns_vyrewatch_3", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_female_flying_ns_vyrewatch_4", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_female_flying_vyrewatch_1", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_female_flying_vyrewatch_2", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_female_flying_vyrewatch_3", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_female_flying_vyrewatch_4", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_female_juvenile", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.sang_myq3_female_juvinate", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.sang_myq3_male_flying_ns_vyrewatch_1", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_male_flying_ns_vyrewatch_2", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_male_flying_ns_vyrewatch_3", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_male_flying_ns_vyrewatch_4", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_male_flying_vyrewatch_1", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_male_flying_vyrewatch_2", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_male_flying_vyrewatch_3", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_male_flying_vyrewatch_4", source = "family:myq3_vampire_flying_ready", defend_anim = "seq.myq3_vampire_flying_defend", death_anim = "seq.myq3_vampire_flying_death" }, + { id = "npc.sang_myq3_male_juvenile", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.sang_myq3_male_juvinate", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.sarachnis", source = "family:hosdun_spider_boss_idle", attack_anim = "seq.hosdun_spider_boss_attack_melee", death_anim = "seq.hosdun_spider_boss_death" }, + { id = "npc.sarachnis_mage_spawn", source = "family:hosdun_spider_boss_idle", attack_anim = "seq.hosdun_spider_boss_attack_melee", death_anim = "seq.hosdun_spider_boss_death" }, + { id = "npc.sarachnis_melee_spawn", source = "family:hosdun_spider_boss_idle", attack_anim = "seq.hosdun_spider_boss_attack_melee", death_anim = "seq.hosdun_spider_boss_death" }, + { id = "npc.saradomin_mage", source = "caster:obj.saradomin_staff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_blunt_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.scarab_swarm", source = "family:scarab_ready", attack_anim = "seq.scarab_attack", defend_anim = "seq.scarab_parry" }, + { id = "npc.scorpia", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.scorpia_guardian", source = "family:small_scorpion_update_ready", attack_anim = "seq.small_scorpion_update_attack", defend_anim = "seq.small_scorpion_update_defend", death_anim = "seq.small_scorpion_update_death" }, + { id = "npc.scorpia_minion", source = "family:small_scorpion_update_ready", attack_anim = "seq.small_scorpion_update_attack", defend_anim = "seq.small_scorpion_update_defend", death_anim = "seq.small_scorpion_update_death" }, + { id = "npc.scorpion", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.scrambled_chicken", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.scrambled_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.scrambled_jaguar", source = "family:npc_lynx_combat_idle", attack_anim = "seq.npc_lynx_combat_melee", death_anim = "seq.npc_lynx_combat_death" }, + { id = "npc.shade_level1", source = "family:shade_ready", attack_anim = "seq.shade_attack", defend_anim = "seq.shade_block" }, + { id = "npc.shade_level2", source = "family:shade_ready", attack_anim = "seq.shade_attack", defend_anim = "seq.shade_block" }, + { id = "npc.shade_level3", source = "family:shade_ready", attack_anim = "seq.shade_attack", defend_anim = "seq.shade_block" }, + { id = "npc.shade_level4", source = "family:shade_ready", attack_anim = "seq.shade_attack", defend_anim = "seq.shade_block" }, + { id = "npc.shade_level5", source = "family:shade_ready", attack_anim = "seq.shade_attack", defend_anim = "seq.shade_block" }, + { id = "npc.shade_level6", source = "family:shade_ready", attack_anim = "seq.shade_attack", defend_anim = "seq.shade_block" }, + { id = "npc.shades_undead_zealot_1", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.shades_undead_zealot_2", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.shadow_dog_wild", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.shadow_maj_bouncer", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.shadow_maj_bouncer_cutscene", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.shadow_spider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.shadow_warrior", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.shantay_guard", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_sword_def" }, + { id = "npc.shapeshifterbear", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.shapeshifterglob", source = "family:skavid_ready", attack_anim = "seq.skavid_attack", defend_anim = "seq.skavid_block", death_anim = "seq.skavid_death" }, + { id = "npc.shapeshifterspider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.shapeshifterwolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.shayzien_armour_1_combat", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.shayzien_armour_2_combat", source = "weapon:obj.iron_warhammer", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2567, defend_anim = "seq.human_blunt_block" }, + { id = "npc.shayzien_armour_3_combat", source = "weapon:obj.iron_mace", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2508, defend_anim = "seq.human_blunt_block" }, + { id = "npc.shayzien_armour_4_combat", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.shayzien_bandit_1", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.shayzien_bandit_2", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.shayzien_bandit_3", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.shayzien_criminal_1", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.shayzien_criminal_2", source = "weapon:obj.iron_warhammer", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2567, defend_anim = "seq.human_blunt_block" }, + { id = "npc.shayzien_criminal_3", source = "weapon:obj.bronze_halberd", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.shayzien_criminal_boss_2_chatting", source = "weapon:obj.iron_knife", attack_anim = "seq.human_stake2_pvn", attack_type = "category.attacktype_standard", attack_sound = 2707, proj_travel = "spotanim.iron_tknife_travel", proj_type = "projanim.thrown", attack_range = 4 }, + { id = "npc.shayzien_criminal_boss_2_fighting", source = "weapon:obj.iron_knife", attack_anim = "seq.human_stake2_pvn", attack_type = "category.attacktype_standard", attack_sound = 2707, proj_travel = "spotanim.iron_tknife_travel", proj_type = "projanim.thrown", attack_range = 4 }, + { id = "npc.shayzien_necromancer", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.shayzien_soldier_m1", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.shayzien_soldier_m1_big", source = "weapon:obj.dragon_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.shayzien_soldier_m2", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.shayzien_soldier_m2_big", source = "weapon:obj.dragon_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.shayzien_soldier_m3", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.shayzien_soldier_m3_big", source = "weapon:obj.dragon_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.shayzien_soldier_m4", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.shayzien_soldier_m4_big", source = "weapon:obj.dragon_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.shayzienquest_lizardman_boss", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.sir_mordred", source = "family:fai_varrock_guard_ready", attack_anim = "seq.fai_varrock_guard_attack", defend_anim = "seq.fai_varrock_guard_defend", death_anim = "seq.fai_varrock_guard_death" }, + { id = "npc.skavid", source = "family:skavid_ready", attack_anim = "seq.skavid_attack", defend_anim = "seq.skavid_block", death_anim = "seq.skavid_death" }, + { id = "npc.skeletal_miner", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeletal_wyvern1", source = "family:wyvern_skeleton_ready", defend_anim = "seq.wyvern_skeleton_block", death_anim = "seq.wyvern_skeleton_death" }, + { id = "npc.skeletal_wyvern2", source = "family:wyvern_skeleton_ready", defend_anim = "seq.wyvern_skeleton_block", death_anim = "seq.wyvern_skeleton_death" }, + { id = "npc.skeletal_wyvern3", source = "family:wyvern_skeleton_ready", defend_anim = "seq.wyvern_skeleton_block", death_anim = "seq.wyvern_skeleton_death" }, + { id = "npc.skeletal_wyvern4", source = "family:wyvern_skeleton_ready", defend_anim = "seq.wyvern_skeleton_block", death_anim = "seq.wyvern_skeleton_death" }, + { id = "npc.skeleton_armed", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_armed2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_armed2_shortrange", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_armed3", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_armed3_shortrange", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_armed4", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_armed4_shortrange", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_armed5", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_armed5_shortrange", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_armed_shortrange", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_hellhound", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.skeleton_unagressive", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_unagressive2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_unagressive3", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_unarmed", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_unarmed2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_unarmed3", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeleton_unarmed4", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.skeletonmage", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.slagilith", source = "family:rock_ready", attack_anim = "seq.rock_attack", defend_anim = "seq.rock_parry", death_anim = "seq.rock_death" }, + { id = "npc.slave_rowdy", source = "weapon:obj.bronze_pickaxe", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.slayer_abberant_spectre_1", source = "family:specter_ready", attack_anim = "seq.specter_attack", defend_anim = "seq.specter_parry", death_anim = "seq.specter_death" }, + { id = "npc.slayer_abberant_spectre_2", source = "family:specter_ready", attack_anim = "seq.specter_attack", defend_anim = "seq.specter_parry", death_anim = "seq.specter_death" }, + { id = "npc.slayer_abberant_spectre_3", source = "family:specter_ready", attack_anim = "seq.specter_attack", defend_anim = "seq.specter_parry", death_anim = "seq.specter_death" }, + { id = "npc.slayer_abberant_spectre_4", source = "family:specter_ready", attack_anim = "seq.specter_attack", defend_anim = "seq.specter_parry", death_anim = "seq.specter_death" }, + { id = "npc.slayer_aberrantspectre_1_strongholdcave", source = "family:specter_ready", attack_anim = "seq.specter_attack", defend_anim = "seq.specter_parry", death_anim = "seq.specter_death" }, + { id = "npc.slayer_aberrantspectre_2_strongholdcave", source = "family:specter_ready", attack_anim = "seq.specter_attack", defend_anim = "seq.specter_parry", death_anim = "seq.specter_death" }, + { id = "npc.slayer_abyssal", source = "family:abyssal_ready", attack_anim = "seq.abyssal_attack", death_anim = "seq.abyssal_death" }, + { id = "npc.slayer_abyssal_strongholdcave", source = "family:abyssal_ready", attack_anim = "seq.abyssal_attack", death_anim = "seq.abyssal_death" }, + { id = "npc.slayer_aquanite", source = "family:slayer_aquanite_ready", attack_anim = "seq.slayer_aquanite_attack", defend_anim = "seq.slayer_aquanite_defend", death_anim = "seq.slayer_aquanite_death" }, + { id = "npc.slayer_aquanite_nolure", source = "family:slayer_aquanite_ready", attack_anim = "seq.slayer_aquanite_attack", defend_anim = "seq.slayer_aquanite_defend", death_anim = "seq.slayer_aquanite_death" }, + { id = "npc.slayer_banshee_1", source = "family:banshee_ready", attack_anim = "seq.banshee_attack", defend_anim = "seq.banshee_parry", death_anim = "seq.banshee_death" }, + { id = "npc.slayer_basilisk", source = "family:basilisk_ready", attack_anim = "seq.basilisk_attack", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_death" }, + { id = "npc.slayer_basilisk_baby", source = "family:basilisk_ready", attack_anim = "seq.basilisk_attack", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_death" }, + { id = "npc.slayer_bloodveld", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.slayer_bloodveld_baby", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.slayer_bloodveld_baby_strongholdcave", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.slayer_bloodveld_strongholdcave", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.slayer_cave_crawler_1", source = "family:crawler_ready", attack_anim = "seq.crawler_attack", defend_anim = "seq.crawler_parry", death_anim = "seq.crawler_death" }, + { id = "npc.slayer_cave_crawler_2", source = "family:crawler_ready", attack_anim = "seq.crawler_attack", defend_anim = "seq.crawler_parry", death_anim = "seq.crawler_death" }, + { id = "npc.slayer_cave_crawler_3", source = "family:crawler_ready", attack_anim = "seq.crawler_attack", defend_anim = "seq.crawler_parry", death_anim = "seq.crawler_death" }, + { id = "npc.slayer_cave_crawler_4", source = "family:crawler_ready", attack_anim = "seq.crawler_attack", defend_anim = "seq.crawler_parry", death_anim = "seq.crawler_death" }, + { id = "npc.slayer_cave_gargoyle", source = "family:gargoyle_ready", attack_anim = "seq.gargoyle_attack", defend_anim = "seq.gargoyle_parry", death_anim = "seq.gargoyle_death" }, + { id = "npc.slayer_cockatrice", source = "family:cockatrice_ready", attack_anim = "seq.cockatrice_attack", defend_anim = "seq.cockatrice_defend", death_anim = "seq.cockatrice_death" }, + { id = "npc.slayer_cockatrice_daddy", source = "family:cockatrice_ready", attack_anim = "seq.cockatrice_attack", defend_anim = "seq.cockatrice_defend", death_anim = "seq.cockatrice_death" }, + { id = "npc.slayer_crawling_hand_1", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.slayer_crawling_hand_2", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.slayer_crawling_hand_3", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.slayer_crawling_hand_4", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.slayer_crawling_hand_5", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.slayer_crawling_hand_big_1", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.slayer_crawling_hand_big_2", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.slayer_crawling_hand_big_3", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.slayer_crawling_hand_big_4", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.slayer_crawling_hand_big_5", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.slayer_dustdevil", source = "family:dustdevil_ready", attack_anim = "seq.dustdevil_attack", defend_anim = "seq.dustdevil_defend", death_anim = "seq.dustdevil_death" }, + { id = "npc.slayer_gargoyle_1", source = "family:gargoyle_ready", attack_anim = "seq.gargoyle_attack", defend_anim = "seq.gargoyle_parry", death_anim = "seq.gargoyle_death" }, + { id = "npc.slayer_gryphon_1", source = "family:gryphon_idle01", death_anim = "seq.gryphon_death01" }, + { id = "npc.slayer_gryphon_2", source = "family:gryphon_idle01", death_anim = "seq.gryphon_death01" }, + { id = "npc.slayer_icefiend_1", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.slayer_infernal_mage_1", source = "caster:obj.slayer_staff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_staff_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.slayer_infernal_mage_2", source = "caster:obj.slayer_staff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_staff_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.slayer_infernal_mage_3", source = "caster:obj.slayer_staff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_staff_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.slayer_infernal_mage_4", source = "caster:obj.slayer_staff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_staff_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.slayer_infernal_mage_5", source = "caster:obj.slayer_staff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_staff_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.slayer_jelly_1", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.slayer_jelly_2", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.slayer_jelly_3", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.slayer_jelly_4", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.slayer_jelly_5", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.slayer_jelly_6", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.slayer_killerwatt", source = "family:killerwatt_biped_ready", attack_anim = "seq.killerwatt_biped_attack", defend_anim = "seq.killerwatt_biped_block", death_anim = "seq.killerwatt_biped_death" }, + { id = "npc.slayer_killerwatt_ball", source = "family:killerwatt_ball_ready_update", attack_anim = "seq.killerwatt_biped_attack" }, + { id = "npc.slayer_kraken", source = "family:swan_queen_ready", attack_anim = "seq.swan_queen_attack", defend_anim = "seq.swan_queen_defend", death_anim = "seq.swan_queen_death" }, + { id = "npc.slayer_kraken_boss", source = "family:swan_queen_ready", attack_anim = "seq.swan_queen_attack", defend_anim = "seq.swan_queen_defend", death_anim = "seq.swan_queen_death" }, + { id = "npc.slayer_kraken_boss_tentacle", source = "family:tentacle_monster_ready", attack_anim = "seq.tentacle_monster_attack", defend_anim = "seq.tentacle_monster_block", death_anim = "seq.tentacle_monster_death" }, + { id = "npc.slayer_kursk_1", source = "family:kursk_ready", attack_anim = "seq.kursk_attack", defend_anim = "seq.kursk_parry", death_anim = "seq.kursk_death" }, + { id = "npc.slayer_kursk_2", source = "family:kursk_ready", attack_anim = "seq.kursk_attack", defend_anim = "seq.kursk_parry", death_anim = "seq.kursk_death" }, + { id = "npc.slayer_lava_strykewyrm", source = "family:npc_strykewyrm_lava01_idle01", attack_anim = "seq.npc_strykewyrm_lava01_melee01", death_anim = "seq.npc_strykewyrm_lava01_death01" }, + { id = "npc.slayer_lizard_large1_green", source = "family:slayer_lizard_ready", attack_anim = "seq.slayer_lizard_attack", defend_anim = "seq.slayer_lizard_defend", death_anim = "seq.slayer_lizard_death" }, + { id = "npc.slayer_lizard_large1_green_lowrange", source = "family:slayer_lizard_ready", attack_anim = "seq.slayer_lizard_attack", defend_anim = "seq.slayer_lizard_defend", death_anim = "seq.slayer_lizard_death" }, + { id = "npc.slayer_lizard_large2_sandy", source = "family:slayer_lizard_ready", attack_anim = "seq.slayer_lizard_attack", defend_anim = "seq.slayer_lizard_defend", death_anim = "seq.slayer_lizard_death" }, + { id = "npc.slayer_lizard_large3_sandy", source = "family:slayer_lizard_ready", attack_anim = "seq.slayer_lizard_attack", defend_anim = "seq.slayer_lizard_defend", death_anim = "seq.slayer_lizard_death" }, + { id = "npc.slayer_lizard_massive", source = "family:slayer_lizard_ready", attack_anim = "seq.slayer_lizard_attack", defend_anim = "seq.slayer_lizard_defend", death_anim = "seq.slayer_lizard_death" }, + { id = "npc.slayer_lizard_small1_green", source = "family:slayer_lizard_ready", attack_anim = "seq.slayer_lizard_attack", defend_anim = "seq.slayer_lizard_defend", death_anim = "seq.slayer_lizard_death" }, + { id = "npc.slayer_lizard_small1_green_lowrange", source = "family:slayer_lizard_ready", attack_anim = "seq.slayer_lizard_attack", defend_anim = "seq.slayer_lizard_defend", death_anim = "seq.slayer_lizard_death" }, + { id = "npc.slayer_lizard_small2_sandy", source = "family:slayer_lizard_ready", attack_anim = "seq.slayer_lizard_attack", defend_anim = "seq.slayer_lizard_defend", death_anim = "seq.slayer_lizard_death" }, + { id = "npc.slayer_mutated_zygomite_adolescent", source = "family:zygomite_ready", attack_anim = "seq.zygomite_attack", defend_anim = "seq.zygomite_defend", death_anim = "seq.zygomite_death" }, + { id = "npc.slayer_mutated_zygomite_adult", source = "family:zygomite_ready", attack_anim = "seq.zygomite_attack", defend_anim = "seq.zygomite_defend", death_anim = "seq.zygomite_death" }, + { id = "npc.slayer_nechryael", source = "family:demons_ready", attack_anim = "seq.demons_attack", defend_anim = "seq.demons_parry", death_anim = "seq.demons_death" }, + { id = "npc.slayer_nechryael_strongholdcave", source = "family:demons_ready", attack_anim = "seq.demons_attack", defend_anim = "seq.demons_parry", death_anim = "seq.demons_death" }, + { id = "npc.slayer_pyrebeast_1", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.slayer_pyrebeast_2", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.slayer_pyrefiend_1", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.slayer_pyrefiend_2", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.slayer_pyrefiend_3", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.slayer_pyrefiend_4", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.slayer_reef_snake", source = "family:royal_sea_snake_mother_ready", attack_anim = "seq.royal_sea_snake_mother_attack", defend_anim = "seq.royal_sea_snake_mother_defend", death_anim = "seq.royal_sea_snake_mother_death" }, + { id = "npc.slayer_rockslug", source = "family:rockslug_ready", attack_anim = "seq.rockslug_attack", defend_anim = "seq.rockslug_defend", death_anim = "seq.rockslug_death" }, + { id = "npc.slayer_rockslug_baby", source = "family:rockslug_ready", attack_anim = "seq.rockslug_attack", defend_anim = "seq.rockslug_defend", death_anim = "seq.rockslug_death" }, + { id = "npc.slayer_rockslug_crypt_of_tonali", source = "family:rockslug_ready", attack_anim = "seq.rockslug_attack", defend_anim = "seq.rockslug_defend", death_anim = "seq.rockslug_death" }, + { id = "npc.slayer_turoth", source = "family:turoth_ready", attack_anim = "seq.turoth_attack", defend_anim = "seq.turoth_defend", death_anim = "seq.turoth_death" }, + { id = "npc.slayer_turoth_baby", source = "family:turoth_ready", attack_anim = "seq.turoth_attack", defend_anim = "seq.turoth_defend", death_anim = "seq.turoth_death" }, + { id = "npc.slayer_turoth_child", source = "family:turoth_ready", attack_anim = "seq.turoth_attack", defend_anim = "seq.turoth_defend", death_anim = "seq.turoth_death" }, + { id = "npc.slayer_turoth_dad", source = "family:turoth_ready", attack_anim = "seq.turoth_attack", defend_anim = "seq.turoth_defend", death_anim = "seq.turoth_death" }, + { id = "npc.slayer_turoth_dad2", source = "family:turoth_ready", attack_anim = "seq.turoth_attack", defend_anim = "seq.turoth_defend", death_anim = "seq.turoth_death" }, + { id = "npc.slayer_turoth_dad3", source = "family:turoth_ready", attack_anim = "seq.turoth_attack", defend_anim = "seq.turoth_defend", death_anim = "seq.turoth_death" }, + { id = "npc.slayer_turoth_mum", source = "family:turoth_ready", attack_anim = "seq.turoth_attack", defend_anim = "seq.turoth_defend", death_anim = "seq.turoth_death" }, + { id = "npc.slepe_vyrewatch_female_1", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.slepe_vyrewatch_female_2", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.slepe_vyrewatch_female_3", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.slepe_vyrewatch_female_4", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.slepe_vyrewatch_male_1", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.slepe_vyrewatch_male_2", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.slepe_vyrewatch_male_3", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.slepe_vyrewatch_male_4", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.slice_ham_archer", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.slice_ham_mage", source = "caster:obj.plainstaff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_stafforb_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.slice_sigmund_magic", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.slice_sigmund_melee", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.slice_sigmund_noprayer", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.slice_sigmund_ranged", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.slice_sigmund_showdown", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.slug2_giant_lobster", source = "family:lobster_update_ready", attack_anim = "seq.lobster_update_attack", defend_anim = "seq.lobster_update_defend", death_anim = "seq.lobster_update_death" }, + { id = "npc.slug2_hobgoblin_armed_1", source = "family:hobgoblin_ready", attack_anim = "seq.hobgoblin_attack", defend_anim = "seq.hobgoblin_block", death_anim = "seq.hobgoblin_death" }, + { id = "npc.slug2_the_slug_prince", source = "family:slug2_prince_ready", attack_anim = "seq.slug2_prince_attack", defend_anim = "seq.slug2_prince_defend", death_anim = "seq.slug2_prince_death" }, + { id = "npc.small_bat", source = "family:bat_rework_ready", attack_anim = "seq.bat_rework_attack", defend_anim = "seq.bat_rework_defend", death_anim = "seq.bat_rework_death" }, + { id = "npc.small_bat_outdoors", source = "family:bat_rework_ready", attack_anim = "seq.bat_rework_attack", defend_anim = "seq.bat_rework_defend", death_anim = "seq.bat_rework_death" }, + { id = "npc.small_bat_outdoors_lowwander", source = "family:bat_rework_ready", attack_anim = "seq.bat_rework_attack", defend_anim = "seq.bat_rework_defend", death_anim = "seq.bat_rework_death" }, + { id = "npc.smallscorpion", source = "family:small_scorpion_update_ready", attack_anim = "seq.small_scorpion_update_attack", defend_anim = "seq.small_scorpion_update_defend", death_anim = "seq.small_scorpion_update_death" }, + { id = "npc.smoke_devil", source = "family:lore_dust_devil_ready", attack_anim = "seq.lore_dust_devil_attack", defend_anim = "seq.lore_dust_devil_defend", death_anim = "seq.lore_dust_devil_death" }, + { id = "npc.smoke_devil_boss", source = "family:lore_dust_devil_ready", attack_anim = "seq.lore_dust_devil_attack", defend_anim = "seq.lore_dust_devil_defend", death_anim = "seq.lore_dust_devil_death" }, + { id = "npc.snake", source = "family:snake_ready", attack_anim = "seq.snake_attack", defend_anim = "seq.snake_block", death_anim = "seq.snake_death" }, + { id = "npc.snakeboss_boss_magic", source = "family:snakeboss_idle", defend_anim = "seq.snakeboss_defend", death_anim = "seq.snakeboss_death" }, + { id = "npc.snakeboss_boss_melee", source = "family:snakeboss_idle", defend_anim = "seq.snakeboss_defend", death_anim = "seq.snakeboss_death" }, + { id = "npc.snakeboss_boss_ranged", source = "family:snakeboss_idle", defend_anim = "seq.snakeboss_defend", death_anim = "seq.snakeboss_death" }, + { id = "npc.snakeboss_minion_magic", source = "family:snakeboss_pet_idle", attack_anim = "seq.snakeboss_pet_attack", defend_anim = "seq.snakeboss_pet_defend", death_anim = "seq.snakeboss_pet_death" }, + { id = "npc.snakeboss_minion_melee", source = "family:snakeboss_pet_idle", attack_anim = "seq.snakeboss_pet_attack", defend_anim = "seq.snakeboss_pet_defend", death_anim = "seq.snakeboss_pet_death" }, + { id = "npc.soc_quest_juvenile", source = "family:npc_custodian_stalker02_ready01", attack_anim = "seq.npc_custodian_stalker02_attack01", death_anim = "seq.npc_custodian_stalker02_death01" }, + { id = "npc.sos_death_ghost", source = "family:ghost_update_tendrill_ready", attack_anim = "seq.ghost_update_tendrill_attack", attack_sound = 436, defend_anim = "seq.ghost_update_tendrill_defend", defend_sound = 439, death_anim = "seq.ghost_update_tendrill_death", death_sound = 438 }, + { id = "npc.sos_death_ghost2", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.sos_death_ghost2_b", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.sos_death_ghost2_c", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.sos_death_ghost2_d", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.sos_death_ghost_b", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.sos_death_ghost_c", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.sos_death_ghost_d", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.sos_death_skeleton_armed", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.sos_death_skeleton_armed2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.sos_death_skeleton_armed3", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.sos_death_skeleton_unarmed", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.sos_death_skeleton_unarmed2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.sos_death_skeleton_unarmed3", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.sos_death_skeleton_unarmed4", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.sos_fam_fleshcrawler", source = "family:kalphite_ready", defend_anim = "seq.kalphite_block", death_anim = "seq.kalphite_death" }, + { id = "npc.sos_fam_fleshcrawler2", source = "family:kalphite_ready", defend_anim = "seq.kalphite_block", death_anim = "seq.kalphite_death" }, + { id = "npc.sos_fam_fleshcrawler3", source = "family:kalphite_ready", defend_anim = "seq.kalphite_block", death_anim = "seq.kalphite_death" }, + { id = "npc.sos_fam_giantrat", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.sos_fam_giantrat2", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.sos_fam_giantrat3", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.sos_fam_rat", source = "family:mouse_ready", attack_anim = "seq.mouse_attack", defend_anim = "seq.mouse_defend", death_anim = "seq.mouse_death" }, + { id = "npc.sos_fam_zombie2", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.sos_fam_zombie2_b", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.sos_fam_zombie2_c", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.sos_fam_zombie_armed", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.sos_fam_zombie_armed2", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.sos_fam_zombie_armed3", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.sos_fam_zombie_unarmed", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.sos_fam_zombie_unarmed2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.sos_fam_zombie_unarmed3", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.sos_pest_catablepon", source = "family:catpon_ready", attack_anim = "seq.catpon_attack", defend_anim = "seq.catpon_parry", death_anim = "seq.catpon_death" }, + { id = "npc.sos_pest_catablepon2", source = "family:catpon_ready", attack_anim = "seq.catpon_attack", defend_anim = "seq.catpon_parry", death_anim = "seq.catpon_death" }, + { id = "npc.sos_pest_catablepon3", source = "family:catpon_ready", attack_anim = "seq.catpon_attack", defend_anim = "seq.catpon_parry", death_anim = "seq.catpon_death" }, + { id = "npc.sos_pest_giantspider1", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.sos_pest_scorpion", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.sos_pest_scorpion2", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.sos_pest_spider", source = "family:small_spider_update_ready", attack_anim = "seq.small_spider_update_attack", attack_sound = 3604, defend_anim = "seq.small_spider_update_defend", defend_sound = 3609, death_anim = "seq.small_spider_update_death", death_sound = 3608 }, + { id = "npc.sos_war_goblin_armed", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.sos_war_goblin_armed2", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.sos_war_goblin_green_soldier_1", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.sos_war_goblin_green_soldier_2", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.sos_war_goblin_helmet", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.sos_war_goblin_helmet2", source = "family:slice_surface_goblin_squat_ready_spear", attack_anim = "seq.slice_surface_goblin_squat_attack_spear" }, + { id = "npc.sos_war_minotaur", source = "family:mino_ready", attack_anim = "seq.mino_attack", defend_anim = "seq.mino_parry", death_anim = "seq.mino_death" }, + { id = "npc.sos_war_minotaur2", source = "family:mino_ready", attack_anim = "seq.mino_attack", defend_anim = "seq.mino_parry", death_anim = "seq.mino_death" }, + { id = "npc.sos_war_minotaur3", source = "family:mino_ready", attack_anim = "seq.mino_attack", defend_anim = "seq.mino_parry", death_anim = "seq.mino_death" }, + { id = "npc.sos_war_rat", source = "family:mouse_ready", attack_anim = "seq.mouse_attack", defend_anim = "seq.mouse_defend", death_anim = "seq.mouse_death" }, + { id = "npc.sos_war_wolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.sos_war_wolf2", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.sote_cave_warrior_1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.sote_cave_warrior_2", source = "ranger", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.sote_essyllt_combat", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.sote_knight", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.sote_knight2", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.sote_mourner_1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.sote_mourner_2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.sote_mourner_3", source = "weapon:obj.nzone_crystal_halberd_new", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.sote_paladin", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.sote_paladin2", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.sote_upass_iorwerth_warrior_1", source = "ranger", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.sote_upass_iorwerth_warrior_2", source = "ranger", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.sote_upass_iorwerth_warrior_3", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.sote_upass_iorwerth_warrior_4", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.sotfa_forest_ancient_feral_vyre_1", source = "family:feyal_vyre_ready", attack_anim = "seq.feyal_vyre_attack", defend_anim = "seq.feyal_vyre_defend", death_anim = "seq.feyal_vyre_death" }, + { id = "npc.sotfa_forest_ancient_feral_vyre_2", source = "family:feyal_vyre_ready", attack_anim = "seq.feyal_vyre_attack", defend_anim = "seq.feyal_vyre_defend", death_anim = "seq.feyal_vyre_death" }, + { id = "npc.sotfa_forest_ancient_feral_vyre_3", source = "family:feyal_vyre_ready", attack_anim = "seq.feyal_vyre_attack", defend_anim = "seq.feyal_vyre_defend", death_anim = "seq.feyal_vyre_death" }, + { id = "npc.sotfa_forest_ancient_feral_vyre_4", source = "family:feyal_vyre_ready", attack_anim = "seq.feyal_vyre_attack", defend_anim = "seq.feyal_vyre_defend", death_anim = "seq.feyal_vyre_death" }, + { id = "npc.sotfa_forest_nylocas_fighting_melee", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.sotfa_forest_nylocas_fighting_ranged", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.sotfa_forest_nylocas_incoming_melee", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.sotfa_forest_nylocas_incoming_ranged", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.sotn_assassin_combat", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.soul_wars_avatar_blue", source = "family:snp_nature_boss_ready", attack_anim = "seq.snp_nature_boss_attack", death_anim = "seq.snp_nature_boss_death" }, + { id = "npc.soul_wars_avatar_red", source = "family:snp_fire_boss_ready", attack_anim = "seq.snp_fire_boss_attack", death_anim = "seq.snp_fire_boss_death" }, + { id = "npc.soul_wars_soul_strong_1", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.soul_wars_soul_strong_2", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.soul_wars_soul_weak_1", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.soul_wars_soul_weak_2", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.soul_wars_tut_imp_obelisk", source = "family:imp_ready", attack_anim = "seq.imp_attack", defend_anim = "seq.imp_block", death_anim = "seq.imp_death" }, + { id = "npc.soul_wars_tut_imp_ruins", source = "family:imp_ready", attack_anim = "seq.imp_attack", defend_anim = "seq.imp_block", death_anim = "seq.imp_death" }, + { id = "npc.soul_wars_wolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.soulbane_anger_bear", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.soulbane_anger_goblin", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.soulbane_anger_rat", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.soulbane_anger_unicorn", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.soulbane_bear", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.soulbane_fear_beast", source = "family:fear_ready", attack_anim = "seq.fear_attack", defend_anim = "seq.fear_parry", death_anim = "seq.fear_death" }, + { id = "npc.soulbane_fear_reaper", source = "family:fear_ready", attack_anim = "seq.fear_attack", defend_anim = "seq.fear_parry", death_anim = "seq.fear_death" }, + { id = "npc.soulbane_goblin", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.soulbane_rat", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.soulbane_rat2", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.soulbane_unicorn", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.souless_vis", source = "family:souless_ready", attack_anim = "seq.souless_attack", defend_anim = "seq.souless_block", death_anim = "seq.souless_death" }, + { id = "npc.sourhog", source = "family:sourhog_idle", attack_anim = "seq.sourhog_attack_melee", death_anim = "seq.sourhog_death" }, + { id = "npc.speedy_keith", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.spindel_spiderling", source = "family:small_spider_update_ready", attack_anim = "seq.small_spider_update_attack", attack_sound = 3604, defend_anim = "seq.small_spider_update_defend", defend_sound = 3609, death_anim = "seq.small_spider_update_death", death_sound = 3608 }, + { id = "npc.stag", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.steel_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.steel_dragon_strongholdcave", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.strange_spawn", source = "family:ds2_spawn_ready", attack_anim = "seq.ds2_spawn_attack", defend_anim = "seq.ds2_spawn_defend", death_anim = "seq.ds2_spawn_death" }, + { id = "npc.suit_of_armour", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.sulphur_lizard", source = "family:sulphur_lizard_ready", attack_anim = "seq.sulphur_lizard_melee", death_anim = "seq.sulphur_lizard_death" }, + { id = "npc.summonedzombie", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.superior_abberant_spectre", source = "family:giant_champion_ready", attack_anim = "seq.giant_champion_attack", defend_anim = "seq.giant_champion_defend", death_anim = "seq.giant_champion_death" }, + { id = "npc.superior_abyssal_demon", source = "family:abyssal_ready", attack_anim = "seq.abyssal_attack", death_anim = "seq.abyssal_death" }, + { id = "npc.superior_aquanite", source = "family:slayer_sup_aquanite_ready", attack_anim = "seq.slayer_sup_aquanite_attack", defend_anim = "seq.slayer_sup_aquanite_defend", death_anim = "seq.slayer_sup_aquanite_death" }, + { id = "npc.superior_aquanite_nolure", source = "family:slayer_sup_aquanite_ready", attack_anim = "seq.slayer_sup_aquanite_attack", defend_anim = "seq.slayer_sup_aquanite_defend", death_anim = "seq.slayer_sup_aquanite_death" }, + { id = "npc.superior_araxyte", source = "family:npc_araxyte01_idle", attack_anim = "seq.npc_araxyte01_attack", death_anim = "seq.npc_araxyte01_death" }, + { id = "npc.superior_banshee", source = "family:screaming_banshee_ready", attack_anim = "seq.screaming_banshee_attack", defend_anim = "seq.screaming_banshee_defend", death_anim = "seq.screaming_banshee_death" }, + { id = "npc.superior_basilisk", source = "family:basilisk_ready", attack_anim = "seq.basilisk_attack", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_death" }, + { id = "npc.superior_basilisk_knight", source = "family:basilisk_knight_ready", attack_anim = "seq.basilisk_knight_melee", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_knight_death" }, + { id = "npc.superior_bloodveld", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.superior_cave_crawler", source = "family:crawler_ready", attack_anim = "seq.crawler_attack", defend_anim = "seq.crawler_parry", death_anim = "seq.crawler_death" }, + { id = "npc.superior_cave_crawler_ice", source = "family:crawler_ready", attack_anim = "seq.crawler_attack", defend_anim = "seq.crawler_parry", death_anim = "seq.crawler_death" }, + { id = "npc.superior_cave_horror", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.superior_chilled_jelly", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.superior_cockatrice", source = "family:cockatrice_ready", attack_anim = "seq.cockatrice_attack", defend_anim = "seq.cockatrice_defend", death_anim = "seq.cockatrice_death" }, + { id = "npc.superior_crawling_hand", source = "family:crawlinghand_ready", attack_anim = "seq.crawlinghand_attack", defend_anim = "seq.crawlinghand_defend", death_anim = "seq.crawlinghand_death" }, + { id = "npc.superior_custodian", source = "family:npc_custodian_stalker05_ready03", attack_anim = "seq.npc_custodian_stalker05_attack01" }, + { id = "npc.superior_dark_beast", source = "family:dark_beast_update_ready", attack_anim = "seq.dark_beast_update_attack", defend_anim = "seq.dark_beast_update_defend", death_anim = "seq.dark_beast_update_death" }, + { id = "npc.superior_drake", source = "family:drake_ready", attack_anim = "seq.drake_melee" }, + { id = "npc.superior_dustdevil", source = "family:dustdevil_ready", attack_anim = "seq.dustdevil_attack", defend_anim = "seq.dustdevil_defend", death_anim = "seq.dustdevil_death" }, + { id = "npc.superior_gryphon", source = "family:gryphon_idle01", death_anim = "seq.gryphon_death01" }, + { id = "npc.superior_hydra", source = "family:superior_hydra_idle", death_anim = "seq.superior_hydra_death" }, + { id = "npc.superior_infernal_mage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.superior_jelly", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.superior_kourend_banshee", source = "family:screaming_banshee_ready", attack_anim = "seq.screaming_banshee_attack", defend_anim = "seq.screaming_banshee_defend", death_anim = "seq.screaming_banshee_death" }, + { id = "npc.superior_kourend_bloodveld", source = "family:bloodveld_ready", attack_anim = "seq.bloodveld_attack", defend_anim = "seq.bloodveld_defend", death_anim = "seq.bloodveld_death" }, + { id = "npc.superior_kourend_jelly", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.superior_kourend_spectre", source = "family:giant_champion_ready", attack_anim = "seq.giant_champion_attack", defend_anim = "seq.giant_champion_defend", death_anim = "seq.giant_champion_death" }, + { id = "npc.superior_kurask", source = "family:harmless_island_jungle_horror_ready", attack_anim = "seq.harmless_island_jungle_horror_attack", defend_anim = "seq.harmless_island_jungle_horror_block", death_anim = "seq.harmless_island_jungle_horror_death" }, + { id = "npc.superior_lava_strykewyrm", source = "family:npc_strykewyrm_lava01_idle01", attack_anim = "seq.npc_strykewyrm_lava01_melee01", death_anim = "seq.npc_strykewyrm_lava01_death01" }, + { id = "npc.superior_nechryael", source = "family:giant_champion_ready", attack_anim = "seq.giant_champion_attack", defend_anim = "seq.giant_champion_defend", death_anim = "seq.giant_champion_death" }, + { id = "npc.superior_pyrefiend", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.superior_pyrelord", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.superior_rockslug", source = "family:rockslug_ready", attack_anim = "seq.rockslug_attack", defend_anim = "seq.rockslug_defend", death_anim = "seq.rockslug_death" }, + { id = "npc.superior_smoke_devil", source = "family:lore_dust_devil_ready", attack_anim = "seq.lore_dust_devil_attack", defend_anim = "seq.lore_dust_devil_defend", death_anim = "seq.lore_dust_devil_death" }, + { id = "npc.superior_turoth", source = "family:turoth_ready", attack_anim = "seq.turoth_attack", defend_anim = "seq.turoth_defend", death_anim = "seq.turoth_death" }, + { id = "npc.superior_venator", source = "family:npc_venator_idle01", death_anim = "seq.npc_venator_death01" }, + { id = "npc.superior_warped_terrorbird", source = "family:pog_mutant_terror_bird_ready", attack_anim = "seq.pog_mutant_terror_bird_attack", defend_anim = "seq.pog_mutant_terror_bird_defend", death_anim = "seq.pog_mutant_terror_bird_death" }, + { id = "npc.superior_warped_tortoise", source = "family:pog_mutated_battle_tortoise_ready", attack_anim = "seq.pog_mutated_battle_tortoise_attack", defend_anim = "seq.pog_mutated_battle_tortoise_defend", death_anim = "seq.pog_mutated_battle_tortoise_death" }, + { id = "npc.superior_wyrm_light", source = "family:wyrm_ready", attack_anim = "seq.wyrm_attack_melee", death_anim = "seq.wyrm_death" }, + { id = "npc.sw_bird_blue", source = "family:bird_flying_update_ready_and_extra", attack_anim = "seq.bird_flying_update_attack", defend_anim = "seq.bird_flying_update_defend", death_anim = "seq.bird_flying_update_death" }, + { id = "npc.sw_tower_ghost1", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.sw_tower_ghost2", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.swamp_cave_bug", source = "family:monalisk_cave_bug_ready", attack_anim = "seq.monalisk_cave_bug_attack", defend_anim = "seq.monalisk_cave_bug_defend" }, + { id = "npc.swamp_cave_slime", source = "family:cave_slime_idle", attack_anim = "seq.cave_slime_attack", defend_anim = "seq.cave_slime_defend", death_anim = "seq.cave_slime_death" }, + { id = "npc.swampcrab", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.swampcrab_bloom", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.swan_kalphite_1", source = "family:kalphite_update_queen_ready", attack_anim = "seq.kalphite_update_queen_jaws_attack", defend_anim = "seq.kalphite_update_queen_defend", death_anim = "seq.kalphite_update_queen_death" }, + { id = "npc.swan_kalphite_2", source = "family:kalphite_update_flying_queen_ready_walk", attack_anim = "seq.kalphite_update_flying_queen_stinger_attack", defend_anim = "seq.kalphite_update_flying_queen_defend", death_anim = "seq.kalphite_update_flying_queen_death" }, + { id = "npc.swan_seatroll_queen", source = "family:swan_queen_ready", attack_anim = "seq.swan_queen_attack", defend_anim = "seq.swan_queen_defend", death_anim = "seq.swan_queen_death" }, + { id = "npc.swan_skeleton_training", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.swan_troll_ambush", source = "family:swan_seatroll_ready", attack_anim = "seq.swan_seatroll_attack", defend_anim = "seq.swan_seatroll_block", death_anim = "seq.swan_seatroll_death" }, + { id = "npc.swan_troll_fishing_1", source = "family:swan_seatroll_ready", attack_anim = "seq.swan_seatroll_attack", defend_anim = "seq.swan_seatroll_block", death_anim = "seq.swan_seatroll_death" }, + { id = "npc.swan_troll_fishing_2", source = "family:swan_seatroll_ready", attack_anim = "seq.swan_seatroll_attack", defend_anim = "seq.swan_seatroll_block", death_anim = "seq.swan_seatroll_death" }, + { id = "npc.swan_troll_fishing_3", source = "family:swan_seatroll_ready", attack_anim = "seq.swan_seatroll_attack", defend_anim = "seq.swan_seatroll_block", death_anim = "seq.swan_seatroll_death" }, + { id = "npc.sword_skeleton_3", source = "family:skeleton_update_giant_ready", attack_anim = "seq.skeleton_update_giant_attack", defend_anim = "seq.skeleton_update_giant_defend", death_anim = "seq.skeleton_update_giant_death" }, + { id = "npc.sword_skeleton_3b", source = "family:skeleton_update_giant_ready", attack_anim = "seq.skeleton_update_giant_attack", defend_anim = "seq.skeleton_update_giant_defend", death_anim = "seq.skeleton_update_giant_death" }, + { id = "npc.tapo_ice_spider_shortrange", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.tbwcu_jungle_spider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.tbwcu_mosquito_single", source = "family:tbw_mosquito_ready", attack_anim = "seq.tbw_mosquito_attack", defend_anim = "seq.tbw_mosquito_defend", death_anim = "seq.tbw_mosquito_death" }, + { id = "npc.tbwcu_mosquito_swarm3", source = "family:tbw_mosquito_ready", attack_anim = "seq.tbw_mosquito_attack", defend_anim = "seq.tbw_mosquito_defend", death_anim = "seq.tbw_mosquito_death" }, + { id = "npc.tbwcu_mosquito_swarm5", source = "family:tbw_mosquito_ready", attack_anim = "seq.tbw_mosquito_attack", defend_anim = "seq.tbw_mosquito_defend", death_anim = "seq.tbw_mosquito_death" }, + { id = "npc.tbwcu_snakejungle_appear", source = "family:snake_ready", attack_anim = "seq.snake_attack", defend_anim = "seq.snake_block", death_anim = "seq.snake_death" }, + { id = "npc.tbwcu_spider_appear", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.tbwcu_tribesman", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.tbwcu_village_snake", source = "family:snake_ready", attack_anim = "seq.snake_attack", defend_anim = "seq.snake_block", death_anim = "seq.snake_death" }, + { id = "npc.tbwt_beast", source = "family:lion_ready", attack_anim = "seq.lion_attack", defend_anim = "seq.lion_block", death_anim = "seq.lion_death" }, + { id = "npc.templetrek_ghast_vis_1", source = "family:ghast_ready", attack_anim = "seq.ghast_attack", defend_anim = "seq.ghast_block", death_anim = "seq.ghast_death" }, + { id = "npc.templetrek_ghast_vis_2", source = "family:ghast_ready", attack_anim = "seq.ghast_attack", defend_anim = "seq.ghast_block", death_anim = "seq.ghast_death" }, + { id = "npc.templetrek_ghast_vis_3", source = "family:ghast_ready", attack_anim = "seq.ghast_attack", defend_anim = "seq.ghast_block", death_anim = "seq.ghast_death" }, + { id = "npc.templetrek_giantsnail_1", source = "family:templetrek_giant_snail_ready", attack_anim = "seq.templetrek_giant_snail_attack", defend_anim = "seq.templetrek_giant_snail_defend", death_anim = "seq.templetrek_giant_snail_death" }, + { id = "npc.templetrek_giantsnail_2", source = "family:templetrek_giant_snail_ready", attack_anim = "seq.templetrek_giant_snail_attack", defend_anim = "seq.templetrek_giant_snail_defend", death_anim = "seq.templetrek_giant_snail_death" }, + { id = "npc.templetrek_giantsnail_3", source = "family:templetrek_giant_snail_ready", attack_anim = "seq.templetrek_giant_snail_attack", defend_anim = "seq.templetrek_giant_snail_defend", death_anim = "seq.templetrek_giant_snail_death" }, + { id = "npc.templetrek_shade_1", source = "family:shade_ready", attack_anim = "seq.shade_attack", defend_anim = "seq.shade_block" }, + { id = "npc.templetrek_shade_2", source = "family:shade_ready", attack_anim = "seq.shade_attack", defend_anim = "seq.shade_block" }, + { id = "npc.templetrek_shade_3", source = "family:shade_ready", attack_anim = "seq.shade_attack", defend_anim = "seq.shade_block" }, + { id = "npc.templetrek_snake_1", source = "family:royal_sea_snake_ready", attack_anim = "seq.royal_sea_snake_attack", defend_anim = "seq.royal_sea_snake_defend", death_anim = "seq.royal_sea_snake_death" }, + { id = "npc.templetrek_snake_2", source = "family:royal_sea_snake_ready", attack_anim = "seq.royal_sea_snake_attack", defend_anim = "seq.royal_sea_snake_defend", death_anim = "seq.royal_sea_snake_death" }, + { id = "npc.templetrek_snake_3", source = "family:royal_sea_snake_ready", attack_anim = "seq.royal_sea_snake_attack", defend_anim = "seq.royal_sea_snake_defend", death_anim = "seq.royal_sea_snake_death" }, + { id = "npc.templetrek_tentacle_arm", source = "family:tentacle_monster_ready", attack_anim = "seq.tentacle_monster_attack", defend_anim = "seq.tentacle_monster_block", death_anim = "seq.tentacle_monster_death" }, + { id = "npc.templetrek_tentacle_head", source = "family:templetrek_tentacle_head_ready", attack_anim = "seq.templetrek_tentacle_head_attack", defend_anim = "seq.templetrek_tentacle_head_defend", death_anim = "seq.templetrek_tentacle_head_death" }, + { id = "npc.templetrek_zombie", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.terrorbird", source = "family:pog_terror_bird_ready", attack_anim = "seq.pog_terror_bird_attack", defend_anim = "seq.pog_terror_bird_defend", death_anim = "seq.pog_terror_bird_death" }, + { id = "npc.terrorbird2", source = "family:pog_terror_bird_ready", attack_anim = "seq.pog_terror_bird_attack", defend_anim = "seq.pog_terror_bird_defend", death_anim = "seq.pog_terror_bird_death" }, + { id = "npc.terrorbird3", source = "family:pog_terror_bird_ready", attack_anim = "seq.pog_terror_bird_attack", defend_anim = "seq.pog_terror_bird_defend", death_anim = "seq.pog_terror_bird_death" }, + { id = "npc.terrorbird_mounted", source = "family:mounted_terrorbird_ready", attack_anim = "seq.mounted_terrorbird_attack", defend_anim = "seq.mounted_terrorbird_defend", death_anim = "seq.mounted_terrorbird_death" }, + { id = "npc.terrorbird_mountedspear", source = "family:mounted_terrorbird_ready", attack_anim = "seq.mounted_terrorbird_attack", defend_anim = "seq.mounted_terrorbird_defend", death_anim = "seq.mounted_terrorbird_death" }, + { id = "npc.test_combat", source = "family:sailing_shark_idle01", attack_anim = "seq.sailing_shark_attack01", death_anim = "seq.sailing_shark_death01" }, + { id = "npc.test_combat_maxhit", source = "family:sailing_shark_idle01", attack_anim = "seq.sailing_shark_attack01", death_anim = "seq.sailing_shark_death01" }, + { id = "npc.thief_blanket", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_sword_def" }, + { id = "npc.thrantax", source = "family:ghost_update_tendrill_ready", attack_anim = "seq.ghost_update_tendrill_attack", attack_sound = 436, defend_anim = "seq.ghost_update_tendrill_defend", defend_sound = 439, death_anim = "seq.ghost_update_tendrill_death", death_sound = 438 }, + { id = "npc.thug", source = "weapon:obj.iron_dagger", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_sword_def" }, + { id = "npc.tinyscorpion", source = "family:small_scorpion_update_ready", attack_anim = "seq.small_scorpion_update_attack", defend_anim = "seq.small_scorpion_update_defend", death_anim = "seq.small_scorpion_update_death" }, + { id = "npc.tlati_guard_f_1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.tlati_guard_f_2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.tlati_guard_m_1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.tlati_guard_m_2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.tlati_mossgiant01", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.tlati_mossgiant02", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.tlati_mossgiant03", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.tlati_mossgiant04", source = "family:giant_update_moss_ready", attack_anim = "seq.giant_update_moss_attack", defend_anim = "seq.giant_update_moss_defend", death_anim = "seq.giant_update_moss_death" }, + { id = "npc.toa_baba", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_baba_baboon", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_baba_coffin", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_crondis_crocodile", source = "family:croc_ready", attack_anim = "seq.croc_attack", defend_anim = "seq.croc_parry", death_anim = "seq.croc_death" }, + { id = "npc.toa_kephri_boss_enrage", source = "family:npc_kephri_idle", death_anim = "seq.npc_kephri_death" }, + { id = "npc.toa_kephri_boss_shielded", source = "family:npc_kephri_idle", death_anim = "seq.npc_kephri_death" }, + { id = "npc.toa_kephri_guardian_melee", source = "family:npc_scarab_idle", attack_anim = "seq.npc_scarab_attack", death_anim = "seq.npc_scarab_death" }, + { id = "npc.toa_kephri_guardian_ranged", source = "family:npc_scarab_idle", attack_anim = "seq.npc_scarab_attack", death_anim = "seq.npc_scarab_death" }, + { id = "npc.toa_kephri_scarab_rangekite", source = "family:npc_scarab_flying_idle", attack_anim = "seq.npc_scarab_flying_attack", death_anim = "seq.npc_scarab_flying_death" }, + { id = "npc.toa_kephri_shield_scarab", source = "family:npc_swarm01_scarab01_idle01", death_anim = "seq.npc_swarm01_scarab01_death01" }, + { id = "npc.toa_path_apmeken_baboon_cursed", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_path_apmeken_baboon_magic_1", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_path_apmeken_baboon_magic_2", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_path_apmeken_baboon_melee_1", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_path_apmeken_baboon_melee_2", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_path_apmeken_baboon_ranged_1", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_path_apmeken_baboon_ranged_2", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_path_apmeken_baboon_shaman", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_path_apmeken_baboon_thrall", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_path_apmeken_baboon_zombie", source = "family:npc_mandrill_idle", attack_anim = "seq.npc_mandrill_attack_melee" }, + { id = "npc.toa_scabaras_scarab", source = "family:npc_scarab_idle", attack_anim = "seq.npc_scarab_attack", death_anim = "seq.npc_scarab_death" }, + { id = "npc.toa_warden_elidinis_phase2_mage", source = "family:npc_wardens_idle01", attack_anim = "seq.npc_wardens_melee01", death_anim = "seq.npc_wardens_death01" }, + { id = "npc.toa_warden_elidinis_phase2_range", source = "family:npc_wardens_idle01", attack_anim = "seq.npc_wardens_melee01", death_anim = "seq.npc_wardens_death01" }, + { id = "npc.toa_warden_tumeken_phase2_mage", source = "family:npc_wardens_idle01", attack_anim = "seq.npc_wardens_melee01", death_anim = "seq.npc_wardens_death01" }, + { id = "npc.toa_warden_tumeken_phase2_range", source = "family:npc_wardens_idle01", attack_anim = "seq.npc_wardens_melee01", death_anim = "seq.npc_wardens_death01" }, + { id = "npc.toa_zebak", source = "family:npc_zebak01_idle", attack_anim = "seq.npc_zebak01_attack_melee", death_anim = "seq.npc_zebak01_death" }, + { id = "npc.toa_zebak_enraged", source = "family:npc_zebak01_idle", attack_anim = "seq.npc_zebak01_attack_melee", death_anim = "seq.npc_zebak01_death" }, + { id = "npc.tob_bloat", source = "family:tob_bloat_ready", death_anim = "seq.tob_bloat_death" }, + { id = "npc.tob_bloat_hard", source = "family:tob_bloat_ready", death_anim = "seq.tob_bloat_death" }, + { id = "npc.tob_bloat_story", source = "family:tob_bloat_ready", death_anim = "seq.tob_bloat_death" }, + { id = "npc.tob_maiden_100", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_100_hard", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_100_story", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_30", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_30_hard", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_30_story", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_50", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_50_hard", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_50_story", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_70", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_70_hard", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_70_story", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_dying_a", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_dying_a_hard", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_dying_a_story", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_dying_b", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_dying_b_hard", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_maiden_dying_b_story", source = "family:maiden_idle", attack_anim = "seq.maiden_attack_blood" }, + { id = "npc.tob_nylocas_big_fighting_magic", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_big_fighting_magic_hard", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_big_fighting_magic_story", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_big_fighting_melee", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_big_fighting_melee_hard", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_big_fighting_melee_story", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_big_fighting_ranged", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_nylocas_big_fighting_ranged_hard", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_nylocas_big_fighting_ranged_story", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_nylocas_big_incoming_magic", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_big_incoming_magic_hard", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_big_incoming_magic_story", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_big_incoming_melee", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_big_incoming_melee_hard", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_big_incoming_melee_story", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_big_incoming_ranged", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_nylocas_big_incoming_ranged_hard", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_nylocas_big_incoming_ranged_story", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_nylocas_fighting_magic", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_fighting_magic_hard", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_fighting_magic_story", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_fighting_melee", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_fighting_melee_hard", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_fighting_melee_story", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_fighting_ranged", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_nylocas_fighting_ranged_hard", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_nylocas_fighting_ranged_story", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_nylocas_incoming_magic", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_incoming_magic_hard", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_incoming_magic_story", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.tob_nylocas_incoming_melee", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_incoming_melee_hard", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_incoming_melee_story", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.tob_nylocas_incoming_ranged", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_nylocas_incoming_ranged_hard", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_nylocas_incoming_ranged_story", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.tob_sotetseg_combat", source = "family:tob_sotetseg_idle", attack_anim = "seq.tob_sotetseg_attack_melee", death_anim = "seq.tob_sotetseg_death" }, + { id = "npc.tob_sotetseg_combat_hard", source = "family:tob_sotetseg_idle", attack_anim = "seq.tob_sotetseg_attack_melee", death_anim = "seq.tob_sotetseg_death" }, + { id = "npc.tob_sotetseg_combat_story", source = "family:tob_sotetseg_idle", attack_anim = "seq.tob_sotetseg_attack_melee", death_anim = "seq.tob_sotetseg_death" }, + { id = "npc.tob_verzik_phase2_armourednylocas", source = "family:tob_spider_tank_idle", death_anim = "seq.tob_spider_tank_death" }, + { id = "npc.tob_verzik_phase2_armourednylocas_hard", source = "family:tob_spider_tank_idle", death_anim = "seq.tob_spider_tank_death" }, + { id = "npc.tob_verzik_phase2_armourednylocas_story", source = "family:tob_spider_tank_idle", death_anim = "seq.tob_spider_tank_death" }, + { id = "npc.tob_verzik_phase2_bloodnylocas", source = "family:elemental_idle", death_anim = "seq.elemental_death" }, + { id = "npc.tob_verzik_phase2_bloodnylocas_hard", source = "family:elemental_idle", death_anim = "seq.elemental_death" }, + { id = "npc.tob_verzik_phase2_bloodnylocas_story", source = "family:elemental_idle", death_anim = "seq.elemental_death" }, + { id = "npc.tobquest_hespori", source = "family:hespori_ready", death_anim = "seq.hespori_death" }, + { id = "npc.tobquest_hespori_healer_active", source = "family:hespori_healer_ready", death_anim = "seq.hespori_healer_dead" }, + { id = "npc.tobquest_vyrewatch_female_1", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.tobquest_vyrewatch_female_2", source = "family:myq3_vampire_grounded_ready_female", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_female", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.tobquest_vyrewatch_male_1", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.tobquest_vyrewatch_male_2", source = "family:myq3_vampire_grounded_ready_male", attack_anim = "seq.myq3_vampire_grounded_attack", defend_anim = "seq.myq3_vampire_grounded_defend_male", death_anim = "seq.myq3_vampire_grounded_death" }, + { id = "npc.tol_frogeel", source = "family:tol_frogeel_ready", attack_anim = "seq.tol_frogeel_attack", defend_anim = "seq.tol_frogeel_defend", death_anim = "seq.tol_frogeel_death" }, + { id = "npc.tol_jubster", source = "family:jubbly_update_ready_and_extra", attack_anim = "seq.jubbly_update_attack", defend_anim = "seq.jubbly_update_defend", death_anim = "seq.jubbly_update_death" }, + { id = "npc.tol_spidine", source = "family:giantspider_ready", attack_anim = "seq.giantspider_attack", defend_anim = "seq.giantspider_block", death_anim = "seq.giantspider_death" }, + { id = "npc.tol_swordchick", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.tol_unicow", source = "family:cow_just_ready_update", attack_anim = "seq.cow_attack", defend_anim = "seq.cow_block", death_anim = "seq.cow_death" }, + { id = "npc.tormented_demon_1", source = "family:luc2_undead_demon_ready", attack_anim = "seq.luc2_undead_demon_melee", defend_anim = "seq.luc2_undead_demon_defend", death_anim = "seq.luc2_undead_demon_death" }, + { id = "npc.tormented_demon_2", source = "family:luc2_undead_demon_ready", attack_anim = "seq.luc2_undead_demon_melee", defend_anim = "seq.luc2_undead_demon_defend", death_anim = "seq.luc2_undead_demon_death" }, + { id = "npc.tormented_demon_quest_1", source = "family:luc2_undead_demon_ready", attack_anim = "seq.luc2_undead_demon_melee", defend_anim = "seq.luc2_undead_demon_defend", death_anim = "seq.luc2_undead_demon_death" }, + { id = "npc.tormented_demon_quest_2", source = "family:luc2_undead_demon_ready", attack_anim = "seq.luc2_undead_demon_melee", defend_anim = "seq.luc2_undead_demon_defend", death_anim = "seq.luc2_undead_demon_death" }, + { id = "npc.tourtrap_qip_desert_mining_guard_1", source = "weapon:obj.machette", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.tourtrap_qip_desert_mining_guard_3", source = "weapon:obj.machette", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.tourtrap_qip_desert_mining_guard_6", source = "weapon:obj.machette", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.tourtrap_qip_desert_mining_guard_still_1", source = "weapon:obj.machette", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.tourtrap_qip_desert_mining_guard_still_3", source = "weapon:obj.machette", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.tourtrap_qip_desert_mining_merc_1", source = "shield:obj.fremmenik_round_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.tourtrap_qip_desert_mining_merc_2", source = "weapon:obj.machette", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.tourtrap_qip_desert_mining_merc_3", source = "shield:obj.wooden_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.tourtrap_qip_desert_mining_merc_4", source = "weapon:obj.machette", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.tourtrap_qip_rowdy_desert_mining_guard_1", source = "weapon:obj.machette", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.tower_guard", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_shield_defence" }, + { id = "npc.trail_cabbage_mage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", defend_anim = "seq.human_shield_defence", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.trail_hard", source = "caster:obj.zamorak_staff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_blunt_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.trail_hard2", source = "weapon:obj.dragon_dagger", attack_anim = "seq.human_ddagger_lunge", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_ddagger_block" }, + { id = "npc.trail_mimic_combat", source = "family:mimic_ready", attack_anim = "seq.mimic_melee", death_anim = "seq.mimic_death" }, + { id = "npc.trail_mimic_spawn_mage", source = "caster:obj.trail_mage_wand", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_staff_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.trail_mimic_spawn_melee", source = "shield:obj.trail_fighter_shield", defend_anim = "seq.human_shield_defence" }, + { id = "npc.trail_mimic_spawn_ranger", source = "weapon:obj.trail_ranger_bow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 9 }, + { id = "npc.trail_zaros_melee", source = "weapon:obj.dragon_dagger", attack_anim = "seq.human_ddagger_lunge", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_ddagger_block" }, + { id = "npc.trail_zaros_range", source = "weapon:obj.phoenix_crossbow", attack_anim = "seq.xbows_human_fire_and_reload_pvn", attack_type = "category.attacktype_standard", attack_sound = 2695, proj_travel = "spotanim.crossbowbolt_travel", proj_type = "projanim.bolt", attack_range = 5 }, + { id = "npc.trail_zaros_wizard", source = "caster:obj.staff_of_zaros", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_staff_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.tree_spirit", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.tribesman", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.tribesman_variant01", source = "weapon:obj.bronze_spear", attack_anim = "seq.human_spear_lunge", attack_type = "category.attacktype_stab", attack_sound = 2555, defend_anim = "seq.human_spear_block" }, + { id = "npc.tribeswoman", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.tribeswoman_variant01", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.troll_champion", source = "family:troll_ready_sword", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_general", source = "family:troll_ready_sword+shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_general2", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_general3", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_melee1", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_prison_guard1_awake", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_prison_guard2_awake", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_spectator1", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_spectator2", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_spectator3", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_spectator4", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_spectator5", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_spectator6", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_spectator7", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_thrower1", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_thrower2", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_thrower3", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_thrower4", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.troll_thrower5", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollrescue_icetroll_melee1", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollrescue_icetroll_melee2", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollrescue_icetroll_melee3", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollrescue_icetroll_melee4", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollrescue_icetroll_melee5", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollrescue_icetroll_melee6", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollrescue_icetroll_melee7", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollromance_arrg_attackable", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollromance_ice_wolf_1", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.trollromance_ice_wolf_1b", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.trollromance_ice_wolf_2", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.trollromance_icetroll_melee1", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollromance_icetroll_melee2", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollromance_icetroll_melee3", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollromance_icetroll_melee4", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollromance_icetroll_melee5", source = "family:troll_ready_shield", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollromance_icetroll_melee6", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trollromance_icetroll_melee7", source = "family:troll_ready", attack_anim = "seq.troll_attack", defend_anim = "seq.troll_block", death_anim = "seq.troll_death" }, + { id = "npc.trr_giant_lobster", source = "family:lobster_update_ready", attack_anim = "seq.lobster_update_attack", defend_anim = "seq.lobster_update_defend", death_anim = "seq.lobster_update_death" }, + { id = "npc.trr_pirate_1", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.trr_pirate_2", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.trr_pirate_3", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.tt_conch_gryphon", source = "family:gryphon_idle01", death_anim = "seq.gryphon_death01" }, + { id = "npc.tt_pearl_gryphon", source = "family:gryphon_boss_idle01", death_anim = "seq.gryphon_boss_death01" }, + { id = "npc.ttrek2_nail_beast_1", source = "family:trek_nailbeast_ready", attack_anim = "seq.trek_nailbeast_attack", defend_anim = "seq.trek_nailbeast_defend", death_anim = "seq.trek_nailbeast_death" }, + { id = "npc.ttrek2_nail_beast_2", source = "family:trek_nailbeast_ready", attack_anim = "seq.trek_nailbeast_attack", defend_anim = "seq.trek_nailbeast_defend", death_anim = "seq.trek_nailbeast_death" }, + { id = "npc.ttrek2_nail_beast_3", source = "family:trek_nailbeast_ready", attack_anim = "seq.trek_nailbeast_attack", defend_anim = "seq.trek_nailbeast_defend", death_anim = "seq.trek_nailbeast_death" }, + { id = "npc.ttrek2_zombie_diff_1_ver_1_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_1_ver_2_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_2_ver_1_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_2_ver_2_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_3_ver_1_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_3_ver_2_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_4_ver_1_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_4_ver_2_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_5_ver_1_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_5_ver_2_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_6_ver_1_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_6_ver_2_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_7_ver_1_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_7_ver_2_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_8_ver_1_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_8_ver_2_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_9_ver_1_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.ttrek2_zombie_diff_9_ver_2_4", source = "family:ramble_zombie_ready", attack_anim = "seq.ramble_zombie_attack", defend_anim = "seq.ramble_zombie_defend", death_anim = "seq.ramble_zombie_death" }, + { id = "npc.tut2_chicken", source = "family:lore_chicken_ready", attack_anim = "seq.lore_chicken_attack", defend_anim = "seq.lore_chicken_defend", death_anim = "seq.lore_chicken_death" }, + { id = "npc.tut2_giantrat", source = "family:giant_rat_update_ready", attack_anim = "seq.giant_rat_update_attack", defend_anim = "seq.giant_rat_update_defend", death_anim = "seq.giant_rat_update_death" }, + { id = "npc.twilight_cultist_acolyte_f_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_acolyte_f_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_acolyte_f_3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_acolyte_m_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_acolyte_m_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_acolyte_m_3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_ascended_f_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_ascended_f_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_ascended_f_3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_ascended_f_4", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_ascended_m_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_ascended_m_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_ascended_m_3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_ascended_m_4", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_chosen_f_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_chosen_f_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_chosen_f_3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_chosen_m_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_chosen_m_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_chosen_m_3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_forebearer_f_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_forebearer_f_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_forebearer_f_3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_forebearer_m_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_forebearer_m_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.twilight_cultist_forebearer_m_3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.typhor", source = "family:basilisk_knight_ready", attack_anim = "seq.basilisk_knight_melee", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_knight_death" }, + { id = "npc.tzhaar_fightcave_swarm_1a", source = "family:firebat_ready", attack_anim = "seq.firebat_attack", defend_anim = "seq.firebat_defend", death_anim = "seq.firebat_death" }, + { id = "npc.tzhaar_fightcave_swarm_1b", source = "family:firebat_ready", attack_anim = "seq.firebat_attack", defend_anim = "seq.firebat_defend", death_anim = "seq.firebat_death" }, + { id = "npc.tzhaar_fightcave_swarm_2a", source = "family:lavabeast_ready", attack_anim = "seq.lavabeast_attack", defend_anim = "seq.lavabeast_defend", death_anim = "seq.lavabeast_death" }, + { id = "npc.tzhaar_fightcave_swarm_2b", source = "family:lavabeast_ready", attack_anim = "seq.lavabeast_attack", defend_anim = "seq.lavabeast_defend", death_anim = "seq.lavabeast_death" }, + { id = "npc.tzhaar_fightcave_swarm_2spawn", source = "family:lavabeast_ready", attack_anim = "seq.lavabeast_attack", defend_anim = "seq.lavabeast_defend", death_anim = "seq.lavabeast_death" }, + { id = "npc.tzhaar_fightcave_swarm_3a", source = "family:magmaquris_ready", attack_anim = "seq.magmaquris_punch", defend_anim = "seq.magmaquris_defend", death_anim = "seq.magmaquris_death" }, + { id = "npc.tzhaar_fightcave_swarm_3b", source = "family:magmaquris_ready", attack_anim = "seq.magmaquris_punch", defend_anim = "seq.magmaquris_defend", death_anim = "seq.magmaquris_death" }, + { id = "npc.tzhaar_fightcave_swarm_4a", source = "family:lizard_cleric_ready", attack_anim = "seq.lizard_cleric_attack", defend_anim = "seq.lizard_cleric_defend", death_anim = "seq.lizard_cleric_death" }, + { id = "npc.tzhaar_fightcave_swarm_4b", source = "family:lizard_cleric_ready", attack_anim = "seq.lizard_cleric_attack", defend_anim = "seq.lizard_cleric_defend", death_anim = "seq.lizard_cleric_death" }, + { id = "npc.tzhaar_fightcave_swarm_5a", source = "family:igniferum_ready", attack_anim = "seq.igniferum_attack", defend_anim = "seq.igniferum_defend", death_anim = "seq.igniferum_death" }, + { id = "npc.tzhaar_fightcave_swarm_5b", source = "family:igniferum_ready", attack_anim = "seq.igniferum_attack", defend_anim = "seq.igniferum_defend", death_anim = "seq.igniferum_death" }, + { id = "npc.tzhaar_fightcave_swarm_boss", source = "family:lordmagmus_ready", attack_anim = "seq.lordmagmus_attack", defend_anim = "seq.lordmagmus_defend", death_anim = "seq.lordmagmus_death" }, + { id = "npc.tzhaar_fightcave_swarm_boss_cleric", source = "family:lizard_cleric_ready", attack_anim = "seq.lizard_cleric_attack", defend_anim = "seq.lizard_cleric_defend", death_anim = "seq.lizard_cleric_death" }, + { id = "npc.tzhaar_fightpit_swarm_1a", source = "family:firebat_ready", attack_anim = "seq.firebat_attack", defend_anim = "seq.firebat_defend", death_anim = "seq.firebat_death" }, + { id = "npc.tzhaar_fightpit_swarm_1b", source = "family:firebat_ready", attack_anim = "seq.firebat_attack", defend_anim = "seq.firebat_defend", death_anim = "seq.firebat_death" }, + { id = "npc.tzhaar_fightpit_swarm_2a", source = "family:lavabeast_ready", attack_anim = "seq.lavabeast_attack", defend_anim = "seq.lavabeast_defend", death_anim = "seq.lavabeast_death" }, + { id = "npc.tzhaar_fightpit_swarm_2b", source = "family:lavabeast_ready", attack_anim = "seq.lavabeast_attack", defend_anim = "seq.lavabeast_defend", death_anim = "seq.lavabeast_death" }, + { id = "npc.tzhaar_fightpit_swarm_3a", source = "family:magmaquris_ready", attack_anim = "seq.magmaquris_punch", defend_anim = "seq.magmaquris_defend", death_anim = "seq.magmaquris_death" }, + { id = "npc.tzhaar_fightpit_swarm_3b", source = "family:magmaquris_ready", attack_anim = "seq.magmaquris_punch", defend_anim = "seq.magmaquris_defend", death_anim = "seq.magmaquris_death" }, + { id = "npc.tzhaar_fightpit_swarm_4a", source = "family:igniferum_ready", attack_anim = "seq.igniferum_attack", defend_anim = "seq.igniferum_defend", death_anim = "seq.igniferum_death" }, + { id = "npc.tzhaar_fightpit_swarm_4b", source = "family:igniferum_ready", attack_anim = "seq.igniferum_attack", defend_anim = "seq.igniferum_defend", death_anim = "seq.igniferum_death" }, + { id = "npc.tzhaar_fightpit_swarm_boss", source = "family:lordmagmus_ready", attack_anim = "seq.lordmagmus_attack", defend_anim = "seq.lordmagmus_defend", death_anim = "seq.lordmagmus_death" }, + { id = "npc.tzhaar_hur1", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_hur2", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_hur3", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_hur4", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_hur5", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_hur6", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_hur_city1", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_hur_city2", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_hur_city3", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_hur_city4", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_hur_city5", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_hur_city6", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_ket1", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_ket2", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_ket3", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_ket4", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_ket5", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_ket6", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_ket7", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_ket_strong", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_mej1", source = "family:thzaar_staff_ready", attack_anim = "seq.thzaar_staff_attack", defend_anim = "seq.thzaar_staff_parry", death_anim = "seq.thzaar_staff_death" }, + { id = "npc.tzhaar_mej2", source = "family:thzaar_staff_ready", attack_anim = "seq.thzaar_staff_attack", defend_anim = "seq.thzaar_staff_parry", death_anim = "seq.thzaar_staff_death" }, + { id = "npc.tzhaar_mej3", source = "family:thzaar_staff_ready", attack_anim = "seq.thzaar_staff_attack", defend_anim = "seq.thzaar_staff_parry", death_anim = "seq.thzaar_staff_death" }, + { id = "npc.tzhaar_mej4", source = "family:thzaar_staff_ready", attack_anim = "seq.thzaar_staff_attack", defend_anim = "seq.thzaar_staff_parry", death_anim = "seq.thzaar_staff_death" }, + { id = "npc.tzhaar_mej5", source = "family:thzaar_staff_ready", attack_anim = "seq.thzaar_staff_attack", defend_anim = "seq.thzaar_staff_parry", death_anim = "seq.thzaar_staff_death" }, + { id = "npc.tzhaar_mej6", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_mej7", source = "family:thzaar_staff_ready", attack_anim = "seq.thzaar_staff_attack", defend_anim = "seq.thzaar_staff_parry", death_anim = "seq.thzaar_staff_death" }, + { id = "npc.tzhaar_xil1", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_xil2", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_xil3", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_xil4", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_xil5", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.tzhaar_xil6", source = "family:thzaar_ready", attack_anim = "seq.thzaar_unarmed_attack", defend_anim = "seq.thzaar_parry", death_anim = "seq.thzaar_death" }, + { id = "npc.ugthanki", source = "family:camel_ready", attack_anim = "seq.camel_attack", defend_anim = "seq.camel_block", death_anim = "seq.camel_death" }, + { id = "npc.ullek_locust", source = "family:locust_ready", attack_anim = "seq.locust_attack", defend_anim = "seq.locust_parry", death_anim = "seq.locust_death" }, + { id = "npc.ungadulu_bad", source = "weapon:obj.plainstaff", attack_anim = "seq.human_stafforb_pummel", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_stafforb_block" }, + { id = "npc.ungadulu_good", source = "caster:obj.plainstaff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_stafforb_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.unicorn_foal_1", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.unicorn_lowwander", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.upass_little_spider", source = "family:small_spider_update_ready", attack_anim = "seq.small_spider_update_attack", attack_sound = 3604, defend_anim = "seq.small_spider_update_defend", defend_sound = 3609, death_anim = "seq.small_spider_update_death", death_sound = 3608 }, + { id = "npc.upass_paladin1_vis", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.upass_paladin2_vis", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.upass_paladin3_vis", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.vampire_count", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.vampyrium_ancient_feral_vyre", source = "family:feyal_vyre_ready", attack_anim = "seq.feyal_vyre_attack", defend_anim = "seq.feyal_vyre_defend", death_anim = "seq.feyal_vyre_death" }, + { id = "npc.vampyrium_dire_bat", source = "family:npc_bat_dire_idle_01", attack_anim = "seq.npc_bat_dire_attack_melee_01", death_anim = "seq.npc_bat_dire_death_01" }, + { id = "npc.vampyrium_impaler_deer", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.vardorvis", source = "family:npc_vardorvis_01_idle_01", attack_anim = "seq.npc_vardorvis_01_melee_01", death_anim = "seq.npc_vardorvis_01_death_01" }, + { id = "npc.vardorvis_quest", source = "family:npc_vardorvis_01_idle_01", attack_anim = "seq.npc_vardorvis_01_melee_01", death_anim = "seq.npc_vardorvis_01_death_01" }, + { id = "npc.varlamore_buffalo", source = "family:cow_just_ready_update", attack_anim = "seq.cow_attack", defend_anim = "seq.cow_block", death_anim = "seq.cow_death" }, + { id = "npc.varlamore_cockatrice_moon01", source = "family:cockatrice_ready", attack_anim = "seq.cockatrice_attack", defend_anim = "seq.cockatrice_defend", death_anim = "seq.cockatrice_death" }, + { id = "npc.varlamore_dark_jaguar", source = "family:npc_lynx_combat_idle", attack_anim = "seq.npc_lynx_combat_melee", death_anim = "seq.npc_lynx_combat_death" }, + { id = "npc.varlamore_guard_f_1", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.varlamore_guard_f_2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.varlamore_guard_m_2", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.varlamore_guard_m_5", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.varlamore_hunterjerboa01", source = "family:rabbit_update_ready", attack_anim = "seq.rabbit_update_attack", defend_anim = "seq.rabbit_update_defend", death_anim = "seq.rabbit_update_death" }, + { id = "npc.varlamore_jaguar", source = "family:npc_lynx_combat_idle", attack_anim = "seq.npc_lynx_combat_melee", death_anim = "seq.npc_lynx_combat_death" }, + { id = "npc.varlamore_jaguar_cub", source = "family:npc_lynx_combat_idle", attack_anim = "seq.npc_lynx_combat_melee", death_anim = "seq.npc_lynx_combat_death" }, + { id = "npc.varlamore_ocelot_1", source = "family:npc_ocelot_combat_idle", attack_anim = "seq.npc_ocelot_combat_melee", death_anim = "seq.npc_ocelot_combat_death" }, + { id = "npc.varlamore_ocelot_2", source = "family:npc_ocelot_combat_idle", attack_anim = "seq.npc_ocelot_combat_melee", death_anim = "seq.npc_ocelot_combat_death" }, + { id = "npc.varlamore_ocelot_3", source = "family:npc_ocelot_combat_idle", attack_anim = "seq.npc_ocelot_combat_melee", death_anim = "seq.npc_ocelot_combat_death" }, + { id = "npc.varlamore_oryx", source = "family:unicorn_rework_ready", attack_anim = "seq.unicorn_rework_attack", defend_anim = "seq.unicorn_rework_defend", death_anim = "seq.unicorn_rework_death" }, + { id = "npc.varlamore_porcupine", source = "family:npc_boar_combat_idle_silent", attack_anim = "seq.npc_boar_combat_melee", death_anim = "seq.npc_boar_combat_death" }, + { id = "npc.varlamore_scorpion_savannah", source = "family:scorpion_update_ready", attack_anim = "seq.scorpion_update_attack_tail", defend_anim = "seq.scorpion_update_defend", death_anim = "seq.scorpion_update_death" }, + { id = "npc.varlamore_snake_rainforest", source = "family:snake_ready", attack_anim = "seq.snake_attack", defend_anim = "seq.snake_block", death_anim = "seq.snake_death" }, + { id = "npc.varlamore_snake_savannah", source = "family:snake_ready", attack_anim = "seq.snake_attack", defend_anim = "seq.snake_block", death_anim = "seq.snake_death" }, + { id = "npc.varlamore_white_jaguar", source = "family:npc_lynx_combat_idle", attack_anim = "seq.npc_lynx_combat_melee", death_anim = "seq.npc_lynx_combat_death" }, + { id = "npc.varlamore_wyrm_light01", source = "family:wyrm_ready", attack_anim = "seq.wyrm_attack_melee", death_anim = "seq.wyrm_death" }, + { id = "npc.vc_rat", source = "family:mouse_ready", attack_anim = "seq.mouse_attack", defend_anim = "seq.mouse_defend", death_anim = "seq.mouse_death" }, + { id = "npc.venator", source = "family:npc_venator_idle01", death_anim = "seq.npc_venator_death01" }, + { id = "npc.venator_2", source = "family:npc_venator_idle01", death_anim = "seq.npc_venator_death01" }, + { id = "npc.venator_3", source = "family:npc_venator_idle01", death_anim = "seq.npc_venator_death01" }, + { id = "npc.venator_4", source = "family:npc_venator_idle01", death_anim = "seq.npc_venator_death01" }, + { id = "npc.venator_5", source = "family:npc_venator_idle01", death_anim = "seq.npc_venator_death01" }, + { id = "npc.venenatis", source = "family:npc_venenatis_idle_01", attack_anim = "seq.npc_venenatis_melee_01", death_anim = "seq.npc_venenatis_death_01" }, + { id = "npc.venenatis_singles", source = "family:npc_venenatis_idle_01", attack_anim = "seq.npc_venenatis_melee_01", death_anim = "seq.npc_venenatis_death_01" }, + { id = "npc.venenatis_spiderling", source = "family:small_spider_update_ready", attack_anim = "seq.small_spider_update_attack", attack_sound = 3604, defend_anim = "seq.small_spider_update_defend", defend_sound = 3609, death_anim = "seq.small_spider_update_death", death_sound = 3608 }, + { id = "npc.verzik_nylocas_magic", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.verzik_nylocas_magic_hard", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.verzik_nylocas_magic_story", source = "family:top_spider_magic_idle", attack_anim = "seq.top_spider_magic_attack", death_anim = "seq.top_spider_magic_death" }, + { id = "npc.verzik_nylocas_melee", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.verzik_nylocas_melee_hard", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.verzik_nylocas_melee_story", source = "family:top_spider_melee_idle", attack_anim = "seq.top_spider_melee_attack", death_anim = "seq.top_spider_melee_death" }, + { id = "npc.verzik_nylocas_ranged", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.verzik_nylocas_ranged_hard", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.verzik_nylocas_ranged_story", source = "family:top_spider_ranged_idle", attack_anim = "seq.top_spider_ranged_attack", death_anim = "seq.top_spider_ranged_death" }, + { id = "npc.verzik_phase1", source = "family:verzik_phase1_idle", defend_anim = "seq.verzik_phase1_defend", death_anim = "seq.verzik_phase1_death" }, + { id = "npc.verzik_phase1_hard", source = "family:verzik_phase1_idle", defend_anim = "seq.verzik_phase1_defend", death_anim = "seq.verzik_phase1_death" }, + { id = "npc.verzik_phase1_story", source = "family:verzik_phase1_idle", defend_anim = "seq.verzik_phase1_defend", death_anim = "seq.verzik_phase1_death" }, + { id = "npc.verzik_phase2", source = "family:verzik_phase2_idle", attack_anim = "seq.verzik_phase2_attack_melee", death_anim = "seq.verzik_phase2_death" }, + { id = "npc.verzik_phase2_hard", source = "family:verzik_phase2_idle", attack_anim = "seq.verzik_phase2_attack_melee", death_anim = "seq.verzik_phase2_death" }, + { id = "npc.verzik_phase2_story", source = "family:verzik_phase2_idle", attack_anim = "seq.verzik_phase2_attack_melee", death_anim = "seq.verzik_phase2_death" }, + { id = "npc.verzik_phase3", source = "family:verzik_phase3_idle", attack_anim = "seq.verzik_phase3_attack_melee" }, + { id = "npc.verzik_phase3_hard", source = "family:verzik_phase3_idle", attack_anim = "seq.verzik_phase3_attack_melee" }, + { id = "npc.verzik_phase3_story", source = "family:verzik_phase3_idle", attack_anim = "seq.verzik_phase3_attack_melee" }, + { id = "npc.vetion", source = "family:npc_vetion_idle_01", death_anim = "seq.npc_vetion_death_01" }, + { id = "npc.vetion_2", source = "family:npc_vetion_idle_02", death_anim = "seq.npc_vetion_death_02" }, + { id = "npc.vetion_2_single", source = "family:npc_vetion_idle_02", death_anim = "seq.npc_vetion_death_02" }, + { id = "npc.vetion_hellhound_jnr", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.vetion_hellhound_jnr_singles", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.vetion_hellhound_snr", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.vetion_hellhound_snr_singles", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.vetion_single", source = "family:npc_vetion_idle_01", death_anim = "seq.npc_vetion_death_01" }, + { id = "npc.viking_bunny_1", source = "family:rabbit_ready", attack_anim = "seq.rabbit_attack", defend_anim = "seq.rabbit_block", death_anim = "seq.rabbit_death" }, + { id = "npc.viking_bunny_2", source = "family:rabbit_ready", attack_anim = "seq.rabbit_attack", defend_anim = "seq.rabbit_block", death_anim = "seq.rabbit_death" }, + { id = "npc.viking_enemy1", source = "weapon:obj.viking_sword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.viking_enemy2", source = "weapon:obj.viking_sword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.viking_enemy3", source = "weapon:obj.viking_sword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.viking_enemy4", source = "weapon:obj.viking_sword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.viking_fox", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.viking_guard", source = "weapon:obj.viking_sword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_shield_defence" }, + { id = "npc.viking_guard2", source = "weapon:obj.iron_warhammer", attack_anim = "seq.human_blunt_pound", attack_type = "category.attacktype_crush", attack_sound = 2567, defend_anim = "seq.human_shield_defence" }, + { id = "npc.viking_man3", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.viking_man4", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.viking_wolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.vikingexile_basilisk", source = "family:basilisk_ready", attack_anim = "seq.basilisk_attack", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_death" }, + { id = "npc.vikingexile_basilisk_baby", source = "family:basilisk_ready", attack_anim = "seq.basilisk_attack", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_death" }, + { id = "npc.vikingexile_basilisk_baby_huntplayer", source = "family:basilisk_ready", attack_anim = "seq.basilisk_attack", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_death" }, + { id = "npc.vikingexile_basilisk_huntplayer", source = "family:basilisk_ready", attack_anim = "seq.basilisk_attack", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_death" }, + { id = "npc.vikingexile_basilisk_superior", source = "family:basilisk_ready", attack_anim = "seq.basilisk_attack", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_death" }, + { id = "npc.vikingexile_basilisk_superior_huntplayer", source = "family:basilisk_ready", attack_anim = "seq.basilisk_attack", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_death" }, + { id = "npc.vikingexile_youngling", source = "family:basilisk_ready", attack_anim = "seq.basilisk_attack", defend_anim = "seq.basilisk_defend", death_anim = "seq.basilisk_death" }, + { id = "npc.viyeldi", source = "weapon:obj.plainstaff", attack_anim = "seq.human_stafforb_pummel", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_stafforb_block" }, + { id = "npc.vmq2_cultist_f_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.vmq2_cultist_f_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.vmq2_cultist_f_3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.vmq2_cultist_m_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.vmq2_cultist_m_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.vmq2_cultist_m_3", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.vmq3_ruins_frost_nagua", source = "family:npc_djinn_01_idle", attack_anim = "seq.npc_djinn_01_melee" }, + { id = "npc.vmq3_tower_twilight_magic_variant_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.vmq3_tower_twilight_magic_variant_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.vmq4_crypt_attacker_magic_variant_1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.vmq4_crypt_attacker_magic_variant_2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.vmq4_crypt_attacker_ranged_variant_1", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.vmq4_crypt_attacker_ranged_variant_2", source = "ranger", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.vmq4_keystone_chamber_boss_magic", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.vmq4_keystone_chamber_boss_ranged", source = "weapon:obj.shortbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2693, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + { id = "npc.vorkath", source = "family:ds2_vorkath_idle", attack_anim = "seq.ds2_vorkath_attack_melee", death_anim = "seq.ds2_vorkath_death" }, + { id = "npc.vorkath_quest", source = "family:ds2_vorkath_idle", attack_anim = "seq.ds2_vorkath_attack_melee", death_anim = "seq.ds2_vorkath_death" }, + { id = "npc.vorkath_spawn", source = "family:ds2_spawn_ready", attack_anim = "seq.ds2_spawn_attack", defend_anim = "seq.ds2_spawn_defend", death_anim = "seq.ds2_spawn_death" }, + { id = "npc.vorkath_spawn_quest", source = "family:ds2_spawn_ready", attack_anim = "seq.ds2_spawn_attack", defend_anim = "seq.ds2_spawn_defend", death_anim = "seq.ds2_spawn_death" }, + { id = "npc.vyrewatch_elite_1", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.vyrewatch_elite_2", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.vyrewatch_elite_3", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.vyrewatch_elite_4", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.vyrewatch_elite_5", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.vyrewatch_elite_6", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.vyrewatch_elite_7", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.vyrewatch_elite_8", source = "family:vyrelord_ready", attack_anim = "seq.vyrelord_new_ground_attack" }, + { id = "npc.wanted_solus_attackable", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_shield_defence" }, + { id = "npc.wanted_summoned_black_knight", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.wanted_white_ranger", source = "weapon:obj.longbow", attack_anim = "seq.human_bow", attack_type = "category.attacktype_standard", attack_sound = 2700, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 10 }, + { id = "npc.warguild_adamant_armour", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.warguild_black_armour", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.warguild_bronze_armour", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.warguild_cyclops1", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_cyclops1_high", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_cyclops2", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_cyclops2_high", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_cyclops3", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_cyclops3_high", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_cyclops4", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_cyclops4_high", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_cyclops5", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_cyclops5_high", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_cyclops6", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_cyclops6_high", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.warguild_iron_armour", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.warguild_mithril_armour", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.warguild_rune_armour", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.warguild_steel_armour", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.warrens_thief_stall", source = "weapon:obj.iron_dagger", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_sword_def" }, + { id = "npc.warrior_man", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_shield_defence" }, + { id = "npc.warrior_man_variant01", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_shield_defence" }, + { id = "npc.warrior_man_variant02", source = "weapon:obj.bronze_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_shield_defence" }, + { id = "npc.water_wizard", source = "caster:obj.plainstaff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_stafforb_block", proj_travel = "spotanim.waterstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.waterfiend_strongholdcave", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.wbr_bear", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.wbr_cave_poisonspider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.wbr_poisonspider", source = "family:spider_update_ready", attack_anim = "seq.spider_update_attack", attack_sound = 537, defend_anim = "seq.spider_update_defend", defend_sound = 539, death_anim = "seq.spider_update_death", death_sound = 538 }, + { id = "npc.wcguild_ent", source = "family:npc_ent_ready_01", attack_anim = "seq.npc_ent_attack_01", defend_anim = "seq.npc_ent_defend_01", death_anim = "seq.npc_ent_death_01" }, + { id = "npc.wcguild_forester", source = "weapon:obj.iron_axe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.weaponsmaster_vis", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.wgs_balance_elemental", source = "family:npc_balance_elemental_fire_idle01", attack_anim = "seq.npc_balance_elemental_fire_attack01" }, + { id = "npc.wgs_balance_elemental_dying", source = "family:npc_balance_elemental_fire_idle01", attack_anim = "seq.npc_balance_elemental_fire_attack01" }, + { id = "npc.wgs_khazard_guard_1", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.wgs_khazard_guard_3", source = "weapon:obj.iron_battleaxe", attack_anim = "seq.human_axe_chop", attack_type = "category.attacktype_slash", attack_sound = 2498, defend_anim = "seq.human_axe_block" }, + { id = "npc.wgs_lucien_henchman_axe", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.wgs_lucien_henchman_axe_2", source = "weapon:stance:barrow_dharok_ready", attack_anim = "seq.barrow_dharok_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_axe_block" }, + { id = "npc.wgs_lucien_henchman_mage", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.wgs_skeleton_1", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.wgs_skeleton_2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.wgs_skeleton_3", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.wgs_skeleton_4", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.whisperer", source = "family:npc_whisperer_01_idle_01", death_anim = "seq.npc_whisperer_01_death_01" }, + { id = "npc.whisperer_melee", source = "family:npc_whisperer_01_idle_01", death_anim = "seq.npc_whisperer_01_death_01" }, + { id = "npc.whisperer_melee_quest", source = "family:npc_whisperer_01_idle_01", death_anim = "seq.npc_whisperer_01_death_01" }, + { id = "npc.whisperer_quest", source = "family:npc_whisperer_01_idle_01", death_anim = "seq.npc_whisperer_01_death_01" }, + { id = "npc.whisperer_soul_vulnerable", source = "family:ghost_update_tendrill_ready", attack_anim = "seq.ghost_update_tendrill_attack", attack_sound = 436, defend_anim = "seq.ghost_update_tendrill_defend", defend_sound = 439, death_anim = "seq.ghost_update_tendrill_death", death_sound = 438 }, + { id = "npc.white_knight", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.white_knight_blue_plumes", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.white_knight_blue_plumes_f", source = "weapon:stance:human_dh_weapon_ready", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.white_knight_f", source = "weapon:stance:human_dh_weapon_ready", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.white_knight_green_plumes", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.white_knight_green_plumes_f", source = "weapon:stance:human_dh_weapon_ready", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.white_knight_yellow_plumes", source = "weapon:obj.bronze_2h_sword", attack_anim = "seq.human_dhsword_slash", attack_type = "category.attacktype_slash", attack_sound = 2503, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.white_knight_yellow_plumes_f", source = "weapon:stance:human_dh_weapon_ready", attack_anim = "seq.human_dhsword_chop", attack_type = "category.attacktype_slash", attack_sound = 2502, defend_anim = "seq.human_dhsword_block" }, + { id = "npc.whitewolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.whitewolf_sentry", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.wild_cave_abyssal", source = "family:abyssal_ready", attack_anim = "seq.abyssal_attack", death_anim = "seq.abyssal_death" }, + { id = "npc.wild_cave_black_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.wild_cave_black_demon2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.wild_cave_black_demon3", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.wild_cave_black_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.wild_cave_black_dragon2", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.wild_cave_black_dragon3", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.wild_cave_cyclops", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.wild_cave_cyclops_deadman", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.wild_cave_darkbeast", source = "family:dark_beast_update_ready", attack_anim = "seq.dark_beast_update_attack", defend_anim = "seq.dark_beast_update_defend", death_anim = "seq.dark_beast_update_death" }, + { id = "npc.wild_cave_darkbeast_deadman", source = "family:dark_beast_update_ready", attack_anim = "seq.dark_beast_update_attack", defend_anim = "seq.dark_beast_update_defend", death_anim = "seq.dark_beast_update_death" }, + { id = "npc.wild_cave_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.wild_cave_demon_deadman", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.wild_cave_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.wild_cave_dragon_deadman", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.wild_cave_dustdevil", source = "family:dustdevil_ready", attack_anim = "seq.dustdevil_attack", defend_anim = "seq.dustdevil_defend", death_anim = "seq.dustdevil_death" }, + { id = "npc.wild_cave_goblin", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.wild_cave_goblin_deadman", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.wild_cave_greater_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.wild_cave_greater_demon2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.wild_cave_greater_demon3", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.wild_cave_green_dragon", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.wild_cave_green_dragon2", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.wild_cave_green_dragon3", source = "family:dragon_ready", attack_anim = "seq.dragon_attack", defend_anim = "seq.dragon_block", death_anim = "seq.dragon_death" }, + { id = "npc.wild_cave_hell_hound", source = "family:dog_update_small_dog_ready", attack_anim = "seq.dog_update_small_dog_attack" }, + { id = "npc.wild_cave_hellhound", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.wild_cave_hellhound_deadman", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.wild_cave_hobgoblin", source = "family:hobgoblin_ready", attack_anim = "seq.hobgoblin_attack", defend_anim = "seq.hobgoblin_block", death_anim = "seq.hobgoblin_death" }, + { id = "npc.wild_cave_hobgoblin_deadman", source = "family:hobgoblin_ready", attack_anim = "seq.hobgoblin_attack", defend_anim = "seq.hobgoblin_block", death_anim = "seq.hobgoblin_death" }, + { id = "npc.wild_cave_icegiant", source = "family:giant_update_ice_ready", attack_anim = "seq.giant_update_ice_attack", defend_anim = "seq.giant_update_ice_defend", death_anim = "seq.giant_update_ice_death" }, + { id = "npc.wild_cave_icegiant2", source = "family:giant_update_ice_ready", attack_anim = "seq.giant_update_ice_attack", defend_anim = "seq.giant_update_ice_defend", death_anim = "seq.giant_update_ice_death" }, + { id = "npc.wild_cave_icegiant3", source = "family:giant_update_ice_ready", attack_anim = "seq.giant_update_ice_attack", defend_anim = "seq.giant_update_ice_defend", death_anim = "seq.giant_update_ice_death" }, + { id = "npc.wild_cave_imp", source = "family:imp_ready", attack_anim = "seq.imp_attack", defend_anim = "seq.imp_block", death_anim = "seq.imp_death" }, + { id = "npc.wild_cave_imp_deadman", source = "family:imp_ready", attack_anim = "seq.imp_attack", defend_anim = "seq.imp_block", death_anim = "seq.imp_death" }, + { id = "npc.wild_cave_jelly_1", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.wild_cave_jelly_2", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.wild_cave_jelly_3", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.wild_cave_jelly_4", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.wild_cave_jelly_5", source = "family:jelly_ready", attack_anim = "seq.jelly_attack", defend_anim = "seq.jelly_defend", death_anim = "seq.jelly_death" }, + { id = "npc.wild_cave_knight", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.wild_cave_knight_deadman", source = "weapon:stance:human_staffready", attack_anim = "seq.human_staff_pound", attack_type = "category.attacktype_crush", attack_sound = 2555, defend_anim = "seq.human_staff_block" }, + { id = "npc.wild_cave_lesser_demon", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.wild_cave_lesser_demon2", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.wild_cave_lesser_demon3", source = "family:demon_ready", attack_anim = "seq.demon_attack", defend_anim = "seq.demon_block", death_anim = "seq.demon_death" }, + { id = "npc.wild_cave_nechryael", source = "family:demons_ready", attack_anim = "seq.demons_attack", defend_anim = "seq.demons_parry", death_anim = "seq.demons_death" }, + { id = "npc.wild_cave_ork", source = "family:ork_update_weapon_ready", attack_anim = "seq.ork_update_double_grip_attack", defend_anim = "seq.ork_update_defend", death_anim = "seq.ork_update_death" }, + { id = "npc.wild_cave_ork_deadman", source = "family:ork_update_weapon_ready", attack_anim = "seq.ork_update_double_grip_attack", defend_anim = "seq.ork_update_defend", death_anim = "seq.ork_update_death" }, + { id = "npc.wild_cave_pyrefiend", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.wild_cave_pyrefiend_deadman", source = "family:pyrefiend_ready", attack_anim = "seq.pyrefiend_attack", defend_anim = "seq.pyrefiend_defend", death_anim = "seq.pyrefiend_death" }, + { id = "npc.wild_cave_superior", source = "family:npc_revenant_superior_idle", attack_anim = "seq.npc_revenant_superior_attack_melee", death_anim = "seq.npc_revenant_superior_death" }, + { id = "npc.wilderness_bandit", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.wilderness_chaos_druid", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.wilderness_dark_warrior", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.wilderness_ent", source = "family:npc_ent_ready_01", attack_anim = "seq.npc_ent_attack_01", defend_anim = "seq.npc_ent_defend_01", death_anim = "seq.npc_ent_death_01" }, + { id = "npc.wilderness_hill_giant", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.wilderness_hill_giant2", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.wilderness_hill_giant3", source = "family:giant_update_basic_ready", attack_anim = "seq.giant_update_basic_attack", defend_anim = "seq.giant_update_basic_defend", death_anim = "seq.giant_update_basic_death" }, + { id = "npc.wilderness_mammoth", source = "family:mammoth_ready", attack_anim = "seq.mammoth_attack", defend_anim = "seq.mammoth_block", death_anim = "seq.mammoth_death" }, + { id = "npc.wilderness_rogue", source = "weapon:obj.deathdagger", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_sword_def" }, + { id = "npc.wilderness_rune_golem", source = "family:golem_ready", attack_anim = "seq.golem_attack", defend_anim = "seq.golem_block", death_anim = "seq.golem_death" }, + { id = "npc.wilderness_zombie", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.wilderness_zombie2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.wilderness_zombie3", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.wildwars_boss_armour", source = "family:bear_rework_ready", attack_anim = "seq.bear_rework_attack", defend_anim = "seq.bear_rework_defend", death_anim = "seq.bear_rework_death" }, + { id = "npc.wildwars_boss_weapons", source = "family:skeleton_update_giant_vary3_ready", attack_anim = "seq.skeleton_update_giant_vary3_attack", defend_anim = "seq.skeleton_update_giant_vary3_defend", death_anim = "seq.skeleton_update_giant_vary3_death" }, + { id = "npc.wildy_agility_skeleton_1", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.wildy_agility_skeleton_1_longhunt", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.wildy_agility_skeleton_2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.wildy_agility_skeleton_2_longhunt", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.wildy_agility_skeleton_3", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.wildy_agility_skeleton_4", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.wildy_agility_skeleton_5", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.wildy_zombie_pirate_1", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.wildy_zombie_pirate_2", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.wildy_zombie_pirate_3", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.wildy_zombie_pirate_4", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.wildy_zombie_pirate_5", source = "family:zombie_update_ready_normal_alt", attack_anim = "seq.zombie_update_attack_normal" }, + { id = "npc.witch1", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.witch2", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.wizard", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.wolf", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.wolfpack_leader", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.wolfpack_leader_whiter", source = "family:dog_update_wolf_ready", attack_anim = "seq.dog_update_wolf_attack", defend_anim = "seq.dog_update_wolf_defend", death_anim = "seq.dog_update_wolf_death" }, + { id = "npc.wom_bed_active", source = "family:wom_bed_ready", attack_anim = "seq.wom_bed_attack", defend_anim = "seq.wom_bed_block", death_anim = "seq.wom_bed_death" }, + { id = "npc.wom_marketguard", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.wormbrain", source = "family:slice_surface_goblin_squat_ready", attack_anim = "seq.slice_surface_goblin_squat_unarmed_attack", defend_anim = "seq.slice_surface_goblin_defend", death_anim = "seq.slice_surface_goblin_death" }, + { id = "npc.wyrm_light", source = "family:wyrm_ready", attack_anim = "seq.wyrm_attack_melee", death_anim = "seq.wyrm_death" }, + { id = "npc.wyrmscraig_wyrm01", source = "family:wyrm_ready", attack_anim = "seq.wyrm_attack_melee", death_anim = "seq.wyrm_death" }, + { id = "npc.wyvern_long_tailed", source = "family:fossil_npc_wyvern_flight_idle", defend_anim = "seq.fossil_npc_wyvern_flight_defend", death_anim = "seq.fossil_npc_wyvern_flight_death" }, + { id = "npc.wyvern_spitting", source = "family:fossil_npc_wyvern_flight_idle", defend_anim = "seq.fossil_npc_wyvern_flight_defend", death_anim = "seq.fossil_npc_wyvern_flight_death" }, + { id = "npc.wyvern_taloned", source = "family:fossil_npc_wyvern_flight_idle", defend_anim = "seq.fossil_npc_wyvern_flight_defend", death_anim = "seq.fossil_npc_wyvern_flight_death" }, + { id = "npc.yak", source = "family:yak_just_ready_update", attack_anim = "seq.yak_update_attack" }, + { id = "npc.yama", source = "family:npc_yama01_idle01", attack_anim = "seq.npc_yama01_melee01" }, + { id = "npc.yama_judge_of_yama", source = "family:demon_update_ready", attack_anim = "seq.demon_update_attack", defend_anim = "seq.demon_update_defend", death_anim = "seq.demon_update_death" }, + { id = "npc.yama_voidflare", source = "family:npc_voidflare_idle", death_anim = "seq.npc_voidflare_death" }, + { id = "npc.yanille_soldier", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.yeti", source = "family:yeti_ready", attack_anim = "seq.yeti_attack", defend_anim = "seq.yeti_block", death_anim = "seq.yeti_death" }, + { id = "npc.young_dark_wizard", source = "caster", attack_anim = "seq.human_caststrike", attack_type = "category.attacktype_magic", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.zamorak_mage", source = "caster:obj.zamorak_staff", attack_anim = "seq.human_caststrike_staff", attack_type = "category.attacktype_magic", defend_anim = "seq.human_blunt_block", proj_travel = "spotanim.windstrike_travel", proj_type = "projanim.magic_spell", attack_range = 7 }, + { id = "npc.zamorakghost", source = "family:ghost_update_tendrill_ready", attack_anim = "seq.ghost_update_tendrill_attack", attack_sound = 436, defend_anim = "seq.ghost_update_tendrill_defend", defend_sound = 439, death_anim = "seq.ghost_update_tendrill_death", death_sound = 438 }, + { id = "npc.zeah_lizardman_1_vn", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.zeah_lizardman_1_vp", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.zeah_lizardman_2_vn", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.zeah_lizardman_2_vp", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.zeah_lizardman_3_vn", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.zeah_lizardman_3_vp", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.zeah_lizardshaman_1", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.zeah_lizardshaman_2", source = "family:shay_lizard_warrior_ready", attack_anim = "seq.shay_lizard_warrior_attack_melee", defend_anim = "seq.shay_lizard_warrior_defend", death_anim = "seq.shay_lizard_warrior_death" }, + { id = "npc.zeah_pirate1", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.zeah_pirate2", source = "weapon:obj.bronze_longsword", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.zeah_pirate3", source = "weapon:obj.bronze_scimitar", attack_anim = "seq.human_sword_slash", attack_type = "category.attacktype_slash", attack_sound = 2500, defend_anim = "seq.human_sword_def" }, + { id = "npc.zeah_sandcrab", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.zeah_sandcrab_small", source = "family:horror_crab_ready", attack_anim = "seq.horror_crab_attack", defend_anim = "seq.horror_crab_defend" }, + { id = "npc.zogre_1", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.zogre_2", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.zogre_3", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.zogre_4", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.zogre_5", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.zogre_6", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.zogre_human_brentle_vahn", source = "family:zombie_ready", attack_anim = "seq.zombie_attack", defend_anim = "seq.zombie_block", death_anim = "seq.zombie_death" }, + { id = "npc.zogre_skele", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.zogre_slash_bash", source = "family:ogre_ready", attack_anim = "seq.ogre_attack", defend_anim = "seq.ogre_block", death_anim = "seq.ogre_death" }, + { id = "npc.zombie2", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.zombie2_b", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie2_c", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie2_rural1", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie2_rural2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie2_rural3", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie2_rural4", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_armed", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed2", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed3", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.zombie_armed_city1", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed_city2", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed_city3", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed_rural1", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed_rural2", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed_rural3", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed_rural4", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed_rural5", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed_sewer1", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed_sewer2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_armed_sewer3", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_armed_sewer4", source = "family:zombie_update_ready_dragging", attack_anim = "seq.zombie_update_attack_dragging", defend_anim = "seq.zombie_update_defend_dragging", death_anim = "seq.zombie_update_death_dragging" }, + { id = "npc.zombie_entranan", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_entranan2", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_entranan3", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_entranan4", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_entranan5", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zombie_unarmed", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed3", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed4", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed5", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed6", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed_city1", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed_city2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed_city3", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed_city4", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed_city5", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed_city6", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed_sewer1", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed_sewer2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed_sewer3", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zombie_unarmed_sewer4", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zq_mainzombie1", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zq_mainzombie2", source = "family:skeleton_update_giant_vary3_ready", attack_anim = "seq.skeleton_update_giant_vary3_attack", defend_anim = "seq.skeleton_update_giant_vary3_defend", death_anim = "seq.skeleton_update_giant_vary3_death" }, + { id = "npc.zq_mainzombie3", source = "family:ghost_update_normal_ready", attack_anim = "seq.ghost_update_normal_attack", attack_sound = 436, defend_anim = "seq.ghost_update_normal_defend", defend_sound = 439, death_anim = "seq.ghost_update_normal_death", death_sound = 438 }, + { id = "npc.zqskeleton_armed", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.zqskeleton_armed2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.zqskeleton_unarmed", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.zqskeleton_unarmed2", source = "family:skeleton_update_ready", attack_anim = "seq.skeleton_update_attack_weapon", defend_anim = "seq.skeleton_update_defend", death_anim = "seq.skeleton_update_death" }, + { id = "npc.zqzombie_armed", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zqzombie_armed2", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zqzombie_armed3", source = "family:zombie_update_ready_weapon", attack_anim = "seq.zombie_update_attack_weapon", defend_anim = "seq.zombie_update_defend_weapon", death_anim = "seq.zombie_update_death_weapon" }, + { id = "npc.zqzombie_unarmed", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zqzombie_unarmed2", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, + { id = "npc.zqzombie_unarmed3", source = "family:zombie_update_ready_normal", attack_anim = "seq.zombie_update_attack_normal", defend_anim = "seq.zombie_update_defend_normal", death_anim = "seq.zombie_update_death_normal" }, +] diff --git a/content/other/npc-combat-anims/src/test/kotlin/org/rsmod/content/other/npccombatanims/NpcCombatAnimsFileTest.kt b/content/other/npc-combat-anims/src/test/kotlin/org/rsmod/content/other/npccombatanims/NpcCombatAnimsFileTest.kt new file mode 100644 index 000000000..460d54bd6 --- /dev/null +++ b/content/other/npc-combat-anims/src/test/kotlin/org/rsmod/content/other/npccombatanims/NpcCombatAnimsFileTest.kt @@ -0,0 +1,96 @@ +package org.rsmod.content.other.npccombatanims + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class NpcCombatAnimsFileTest { + @Test + fun `parses an inline entry with every field`() { + val file = + NpcCombatAnimsFile.parse( + """ + npc = [ + { id = "npc.guard", source = "weapon:obj.iron_sword", attack_anim = "seq.human_sword_stab", attack_type = "category.attacktype_stab", attack_sound = 2549, defend_anim = "seq.human_shield_defence", defend_sound = 518, death_anim = "seq.human_death", death_sound = 512, proj_travel = "spotanim.iron_arrow_travel", proj_type = "projanim.arrow", attack_range = 7 }, + ] + """ + .trimIndent() + ) + val entry = file.npc.single() + assertEquals("npc.guard", entry.id) + assertEquals("seq.human_sword_stab", entry.attackAnim) + assertEquals("category.attacktype_stab", entry.attackType) + assertEquals(2549, entry.attackSound) + assertEquals("seq.human_shield_defence", entry.defendAnim) + assertEquals(518, entry.defendSound) + assertEquals("seq.human_death", entry.deathAnim) + assertEquals(512, entry.deathSound) + assertEquals("spotanim.iron_arrow_travel", entry.projTravel) + assertEquals("projanim.arrow", entry.projType) + assertEquals(7, entry.attackRange) + } + + @Test + fun `missing fields are null and an empty array parses`() { + val entry = NpcCombatAnimsFile.parse("""npc = [ { id = "npc.man" } ]""").npc.single() + assertNull(entry.attackAnim) + assertNull(entry.attackRange) + assertTrue(NpcCombatAnimsFile.parse("npc = [\n]\n").npc.isEmpty()) + } + + @Test + fun `shipped resources parse, reference the right namespaces and have no duplicates`() { + for (name in + listOf(NpcCombatAnimsFile.GENERATED_RESOURCE, NpcCombatAnimsFile.OVERRIDES_RESOURCE)) { + val file = NpcCombatAnimsFile.loadResource(name) + val ids = file.npc.map { it.id } + assertEquals(ids.size, ids.toSet().size, "duplicate npc in $name") + for (entry in file.npc) { + assertTrue(entry.id.startsWith("npc."), "${entry.id} in $name") + entry.attackAnim?.let { assertTrue(it.startsWith("seq."), "${entry.id}: $it") } + entry.defendAnim?.let { assertTrue(it.startsWith("seq."), "${entry.id}: $it") } + entry.deathAnim?.let { assertTrue(it.startsWith("seq."), "${entry.id}: $it") } + entry.attackType?.let { + assertTrue(it.startsWith("category.attacktype_"), "${entry.id}: $it") + } + entry.projTravel?.let { assertTrue(it.startsWith("spotanim."), "${entry.id}: $it") } + entry.projType?.let { assertTrue(it.startsWith("projanim."), "${entry.id}: $it") } + } + } + } + + @Test + fun `the generated file covers the npcs that were reported silent`() { + val ids = NpcCombatAnimsFile.loadResource(NpcCombatAnimsFile.GENERATED_RESOURCE).npc.associateBy { it.id } + // A sword, a battleaxe, a mace, a bow, a spell caster and a monster family. + assertEquals("seq.human_sword_stab", ids.getValue("npc.highwayman").attackAnim) + assertEquals("seq.human_sword_slash", ids.getValue("npc.black_knight").attackAnim) + assertEquals("seq.human_axe_chop", ids.getValue("npc.barbarian").attackAnim) + assertEquals("seq.human_blunt_pound", ids.getValue("npc.ardougne_guard").attackAnim) + assertEquals("seq.human_bow", ids.getValue("npc.ardougne_archer").attackAnim) + assertEquals("seq.human_caststrike", ids.getValue("npc.wizard").attackAnim) + assertEquals("seq.giant_update_basic_attack", ids.getValue("npc.giant").attackAnim) + // Villagers with every stat equal are not casters. + assertNull(ids["npc.al_kharid_man"]) + } + + @Test + fun `ghosts and spiders get their family sounds`() { + val ids = NpcCombatAnimsFile.loadResource(NpcCombatAnimsFile.GENERATED_RESOURCE).npc.associateBy { it.id } + val ghost = ids.getValue("npc.ghost") + assertEquals(436, ghost.attackSound) + assertEquals(439, ghost.defendSound) + assertEquals(438, ghost.deathSound) + val giantSpider = ids.getValue("npc.giantspider2") + assertEquals(537, giantSpider.attackSound) + assertEquals(539, giantSpider.defendSound) + assertEquals(538, giantSpider.deathSound) + val cryptSpider = ids.getValue("npc.barrows_spider") + assertEquals(3604, cryptSpider.attackSound) + assertEquals(3609, cryptSpider.defendSound) + assertEquals(3608, cryptSpider.deathSound) + // A family without a known voice stays silent rather than borrowing one. + assertNull(ids.getValue("npc.giant").attackSound) + } +} diff --git a/content/other/pets/build.gradle.kts b/content/other/pets/build.gradle.kts new file mode 100644 index 000000000..40c9b3455 --- /dev/null +++ b/content/other/pets/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) + implementation(projects.api.attr) +} diff --git a/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/PetsModule.kt b/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/PetsModule.kt new file mode 100644 index 000000000..65ebf8785 --- /dev/null +++ b/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/PetsModule.kt @@ -0,0 +1,11 @@ +package org.rsmod.content.other.pets + +import org.rsmod.api.death.PlayerDeathCleanupHook +import org.rsmod.content.other.pets.cats.CatPetDeathHook +import org.rsmod.plugin.module.PluginModule + +class PetsModule : PluginModule() { + override fun bind() { + addSetBinding(CatPetDeathHook::class.java) + } +} diff --git a/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPet.kt b/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPet.kt new file mode 100644 index 000000000..f4a501f64 --- /dev/null +++ b/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPet.kt @@ -0,0 +1,173 @@ +package org.rsmod.content.other.pets.cats + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType + +/** The four cat life stages that exist as follower npcs in the cache. */ +enum class CatStage( + /** Lower-case name used in game messages, e.g. "Your kitten has run away." */ + val label: String, + /** Held obj per [CatColour] (ordinal-indexed). */ + val objs: List, + /** Follower npc per [CatColour] (ordinal-indexed). */ + val npcs: List, + /** Cycles of following before the cat grows into [next]; `null` for a terminal stage. */ + val growthCycles: Int?, + /** Stage this cat grows into, or `null` if it never grows further. */ + val next: CatStage?, + /** Whether the cat is still young enough to catch rats. */ + val canChase: Boolean, +) { + LAZY( + label = "lazy cat", + objs = + listOf( + "obj.lazycatobject", + "obj.lazycatobject_light", + "obj.lazycatobject_brown", + "obj.lazycatobject_black", + "obj.lazycatobject_browngrey", + "obj.lazycatobject_bluegrey", + "obj.lazycatobject_hell", + ), + npcs = + listOf( + "npc.lazycat", + "npc.lazycat_light", + "npc.lazycat_brown", + "npc.lazycat_black", + "npc.lazycat_browngrey", + "npc.lazycat_bluegrey", + "npc.lazycat_hell", + ), + growthCycles = null, + next = null, + canChase = false, + ), + OVERGROWN( + label = "cat", + objs = + listOf( + "obj.overgrowncatobject", + "obj.overgrowncatobject_light", + "obj.overgrowncatobject_brown", + "obj.overgrowncatobject_black", + "obj.overgrowncatobject_browngrey", + "obj.overgrowncatobject_bluegrey", + "obj.overgrowncatobject_hell", + ), + npcs = + listOf( + "npc.overgrowncat", + "npc.overgrowncat_light", + "npc.overgrowncat_brown", + "npc.overgrowncat_black", + "npc.overgrowncat_browngrey", + "npc.overgrowncat_bluegrey", + "npc.overgrowncat_hell", + ), + growthCycles = null, + next = null, + canChase = false, + ), + CAT( + label = "cat", + objs = + listOf( + "obj.growncatobject", + "obj.growncatobject_light", + "obj.growncatobject_brown", + "obj.growncatobject_black", + "obj.growncatobject_browngrey", + "obj.growncatobject_bluegrey", + "obj.growncatobject_hell", + ), + npcs = + listOf( + "npc.growncat", + "npc.growncat_light", + "npc.growncat_brown", + "npc.growncat_black", + "npc.growncat_browngrey", + "npc.growncat_bluegrey", + "npc.growncat_hell", + ), + // Five and a half hours of following, the middle of the wiki's 5-6 hour range. + growthCycles = 33_000, + next = OVERGROWN, + canChase = true, + ), + KITTEN( + label = "kitten", + objs = + listOf( + "obj.kittenobject", + "obj.kittenobject_light", + "obj.kittenobject_brown", + "obj.kittenobject_black", + "obj.kittenobject_browngrey", + "obj.kittenobject_bluegrey", + "obj.kittenobject_hell", + ), + npcs = + listOf( + "npc.kittenpet1", + "npc.kittenpet_light", + "npc.kittenpet_brown", + "npc.kittenpet_black", + "npc.kittenpet_browngrey", + "npc.kittenpet_bluegrey", + "npc.kittenpet_hell", + ), + // Three hours of following. + growthCycles = 18_000, + next = CAT, + canChase = true, + ), +} + +enum class CatColour { + DEFAULT, + LIGHT, + BROWN, + BLACK, + BROWNGREY, + BLUEGREY, + HELL, +} + +/** One concrete cat: a life stage in a colour. Resolves to a held obj and a follower npc. */ +data class CatPet(val stage: CatStage, val colour: CatColour) { + val obj: String + get() = stage.objs[colour.ordinal] + + val npc: String + get() = stage.npcs[colour.ordinal] + + val objId: Int + get() = obj.asRSCM(RSCMType.OBJ) + + val npcId: Int + get() = npc.asRSCM(RSCMType.NPC) + + val isKitten: Boolean + get() = stage == CatStage.KITTEN + + val isHellcat: Boolean + get() = colour == CatColour.HELL + + /** The same cat one stage older, or `null` if it is fully grown. */ + fun grown(): CatPet? = stage.next?.let { CatPet(it, colour) } + + companion object { + val all: List = + CatStage.entries.flatMap { stage -> CatColour.entries.map { CatPet(stage, it) } } + + private val byObj: Map by lazy { all.associateBy { it.objId } } + private val byNpc: Map by lazy { all.associateBy { it.npcId } } + + fun fromObj(objId: Int): CatPet? = byObj[objId] + + fun fromNpc(npcId: Int): CatPet? = byNpc[npcId] + } +} diff --git a/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPetDeathHook.kt b/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPetDeathHook.kt new file mode 100644 index 000000000..23fcfea21 --- /dev/null +++ b/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPetDeathHook.kt @@ -0,0 +1,16 @@ +package org.rsmod.content.other.pets.cats + +import jakarta.inject.Inject +import org.rsmod.api.death.PlayerDeathCleanupHook +import org.rsmod.api.player.output.mes +import org.rsmod.game.entity.Player + +/** A cat that is following its owner when they die runs off; cats cannot be insured. */ +class CatPetDeathHook @Inject constructor(private val cats: CatPetManager) : + PlayerDeathCleanupHook { + override fun cleanup(player: Player) { + val pet = cats.followingPet(player) ?: return + cats.release(player) + player.mes("Your ${pet.stage.label} has run away.") + } +} diff --git a/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPetManager.kt b/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPetManager.kt new file mode 100644 index 000000000..93c2b7f1d --- /dev/null +++ b/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPetManager.kt @@ -0,0 +1,386 @@ +package org.rsmod.content.other.pets.cats + +import dev.openrune.types.NpcMode +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.npc.owner.assignSpawnOwner +import org.rsmod.api.npc.owner.isSpawnOwnedBy +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.output.soundSynth +import org.rsmod.api.player.vars.VarPlayerIntMapSetter +import org.rsmod.api.player.vars.resyncVar +import org.rsmod.api.random.GameRandom +import org.rsmod.api.repo.npc.NpcRepository +import org.rsmod.game.MapClock +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player +import org.rsmod.game.map.Direction +import org.rsmod.game.map.collision.isWalkBlocked +import org.rsmod.game.map.collision.isZoneValid +import org.rsmod.map.CoordGrid +import org.rsmod.routefinder.collision.CollisionFlagMap + +/** + * Owns the per-player state of a following cat: which cat is out, the npc that represents it, + * and the growth / hunger / attention counters that tick while it follows. + * + * Counters are stored as cycle counts in persistent attributes so they survive logout. Only one + * cat can follow a player at a time. + */ +@Singleton +class CatPetManager +@Inject +constructor( + private val npcRepo: NpcRepository, + private val mapClock: MapClock, + private val random: GameRandom, + private val collision: CollisionFlagMap, +) { + + fun following(player: Player): Npc? = player.attr[PET_NPC] + + fun followingPet(player: Player): CatPet? = + player.attr[FOLLOWING_OBJ]?.let(CatPet::fromObj) + + fun hasFollower(player: Player): Boolean = following(player) != null + + fun isOwnedBy(npc: Npc, player: Player): Boolean = + npc.isSpawnOwnedBy(player) && following(player) === npc + + /** Spawns [pet] beside [player] and starts the per-cycle upkeep timer. */ + fun spawn(player: Player, pet: CatPet): Npc { + following(player)?.let { remove(it) } + val npc = Npc(pet.npc, spawnTile(player)) + npc.mode = NpcMode.PlayerFollow + npcRepo.add(npc, Int.MAX_VALUE) + // `add` with a permanent duration flags the npc as respawning; a pet never does. + npc.respawns = false + npc.assignSpawnOwner(player, mapClock.cycle) + npc.facePlayer(player) + setFollowerVarp(player, npc) + + player.attr[PET_NPC] = npc + player.attr[FOLLOWING_OBJ] = pet.objId + player.softTimer(TICK_TIMER, 1) + return npc + } + + /** + * A free tile beside the player so the cat is not hidden under them when it appears. Cardinal + * neighbours are tried before diagonals; the player's own tile is the last resort. + */ + private fun spawnTile(player: Player): CoordGrid { + val origin = player.coords + for (direction in SPAWN_DIRECTIONS) { + val tile = origin.translate(direction.xOff, direction.zOff) + if (collision.isZoneValid(tile) && !collision.isWalkBlocked(tile)) { + return tile + } + } + return origin + } + + /** Removes the follower npc but keeps the cat "out", so it respawns on the next login. */ + fun despawn(player: Player) { + val npc = following(player) ?: return + remove(npc) + clearFollowerVarp(player) + player.attr.remove(PET_NPC) + player.clearSoftTimer(TICK_TIMER) + } + + /** + * The client only shows right-click options on a follower-flagged npc when `varp.follower_npc` + * names it: the npc type id in the high 16 bits and its slot index in the low 16 bits. + */ + private fun setFollowerVarp(player: Player, npc: Npc) { + val packed = (npc.visType.id shl 16) or (npc.slotId and 0xFFFF) + VarPlayerIntMapSetter.set(player, FOLLOWER_VARP, packed) + // The setter does not transmit before the player's first processed cycle (login spawns), + // so the first tick pushes the value again. + player.attr[RESYNC_FOLLOWER_VARP] = true + } + + private fun clearFollowerVarp(player: Player) { + VarPlayerIntMapSetter.set(player, FOLLOWER_VARP, NO_FOLLOWER) + } + + /** The cat is gone for good: picked up, shooed, run away or lost on death. */ + fun release(player: Player) { + despawn(player) + player.attr.remove(FOLLOWING_OBJ) + resetCounters(player) + } + + /** + * Like [release], but the npc trots off for a few cycles before disappearing instead of + * vanishing on the spot. + */ + fun runAway(player: Player) { + val npc = following(player) + if (npc == null) { + release(player) + return + } + clearFollowerVarp(player) + player.attr.remove(PET_NPC) + player.attr.remove(FOLLOWING_OBJ) + player.clearSoftTimer(TICK_TIMER) + resetCounters(player) + + npc.mode = NpcMode.None + npc.resetFaceEntity() + val away = Direction.CARDINAL[mapClock.cycle % Direction.CARDINAL.size] + npc.walk(npc.coords.translate(away.xOff * RUN_AWAY_DISTANCE, away.zOff * RUN_AWAY_DISTANCE)) + npc.lifecycleDelCycle = mapClock.cycle + RUN_AWAY_CYCLES + } + + /** Called once per cycle while a cat is out. */ + fun tick(player: Player) { + val pet = followingPet(player) + val npc = following(player) + if (pet == null || npc == null) { + despawn(player) + return + } + if (!npc.isSlotAssigned) { + // The engine dropped the npc (e.g. an exception in its cycle); put the cat back. + spawn(player, pet) + return + } + if (player.attr[RESYNC_FOLLOWER_VARP] == true) { + player.attr.remove(RESYNC_FOLLOWER_VARP) + player.resyncVar(FOLLOWER_VARP) + } + if (player.attr[CHASING] != true) { + ensureFollowing(npc, player) + } + if (tickGrowth(player, pet)) { + // The cat was replaced by its grown form; `npc` and `pet` are stale from here on. + return + } + if (pet.isKitten) { + tickHunger(player, npc) + tickAttention(player, npc) + } + tickAmbientNoise(player, npc, pet) + } + + /** Every few minutes on average the cat mews or purrs of its own accord. */ + private fun tickAmbientNoise(player: Player, npc: Npc, pet: CatPet) { + if (random.of(maxExclusive = AMBIENT_NOISE_CHANCE) != 0) { + return + } + if (random.of(maxExclusive = 3) == 0) { + npc.say("Purr...purr...") + player.soundSynth("synth.purr") + } else { + npc.say(if (pet.isKitten) "Meow!" else "Miaow!") + player.soundSynth(if (pet.isKitten) "synth.kittens_mew" else "synth.meeoow") + } + } + + private fun ensureFollowing(npc: Npc, player: Player) { + if (!npc.isFacingPlayer) { + npc.facePlayer(player) + } + if (npc.mode != NpcMode.PlayerFollow) { + npc.mode = NpcMode.PlayerFollow + } + } + + /** Returns `true` if the cat grew into its next stage this cycle. */ + private fun tickGrowth(player: Player, pet: CatPet): Boolean { + val threshold = pet.stage.growthCycles ?: return false + val grown = pet.grown() ?: return false + val growth = player.attr.getOrDefault(GROWTH, 0) + 1 + if (growth < threshold) { + player.attr[GROWTH] = growth + return false + } + val grownNpc = spawn(player, grown) + resetCounters(player) + val message = + when (grown.stage) { + CatStage.CAT -> "Your kitten has grown into a healthy cat that can hunt for itself." + CatStage.OVERGROWN -> + "Your cat has grown into a mighty feline, but it will no longer be able to " + + "chase vermin." + else -> "Your cat has grown." + } + player.mes(message) + // The grown cat announces itself: a meow now and a purr shortly after. + grownNpc.say("Meeoow!") + player.soundSynth("synth.meeoow") + player.soundSynth("synth.purr", delay = GROWTH_PURR_DELAY) + return true + } + + /** Debug helper: the cat grows on its next cycle. Returns `false` if it cannot grow. */ + fun forceGrowth(player: Player): Boolean { + val pet = followingPet(player) ?: return false + val threshold = pet.stage.growthCycles ?: return false + player.attr[GROWTH] = threshold - 1 + return true + } + + /** Debug helper: one-line summary of the follower's counters. */ + fun status(player: Player): String { + val pet = followingPet(player) ?: return "No cat is following you." + val growth = player.attr.getOrDefault(GROWTH, 0) + val threshold = pet.stage.growthCycles + val growthText = + if (threshold == null) "fully grown" else "growth $growth/$threshold cycles" + val kittenText = + if (pet.isKitten) { + ", hunger ${player.attr.getOrDefault(HUNGER, 0)}/$HUNGER_RUN" + + ", attention ${player.attr.getOrDefault(ATTENTION, 0)}/$ATTENTION_RUN" + } else { + "" + } + return "${pet.stage.name.lowercase()} (${pet.colour.name.lowercase()}): $growthText$kittenText" + } + + private fun tickHunger(player: Player, npc: Npc) { + val hunger = player.attr.getOrDefault(HUNGER, 0) + 1 + player.attr[HUNGER] = hunger + when (hunger) { + HUNGER_WARN -> player.mes("Your kitten is hungry.") + HUNGER_CRY -> { + npc.say("Meeeooowww!") + player.soundSynth("synth.sad_meeoow") + player.mes("Your kitten is very hungry. Feed it some fish or milk before it runs away.") + } + HUNGER_RUN -> { + npc.say("Meeeooowww!") + player.soundSynth("synth.sad_meeoow") + player.mes("Your kitten has run away because you didn't feed it.") + runAway(player) + } + } + } + + private fun tickAttention(player: Player, npc: Npc) { + val attention = player.attr.getOrDefault(ATTENTION, 0) + 1 + player.attr[ATTENTION] = attention + when (attention) { + ATTENTION_WARN -> player.mes("Your kitten wants some attention.") + ATTENTION_CRY -> { + npc.say("Meeeooowww...") + player.soundSynth("synth.sad_meeoow") + player.mes("Your kitten is lonely. Stroke it or give it a ball of wool.") + } + ATTENTION_RUN -> { + npc.say("Meeeooowww...") + player.soundSynth("synth.sad_meeoow") + player.mes("Your kitten has run away because it felt neglected.") + runAway(player) + } + } + } + + fun isHungry(player: Player): Boolean = player.attr.getOrDefault(HUNGER, 0) >= HUNGER_WARN + + fun isLonely(player: Player): Boolean = + player.attr.getOrDefault(ATTENTION, 0) >= ATTENTION_WARN + + /** Fraction of the way (0-100) to the next growth stage, for the talk-to lines. */ + fun growthPercent(player: Player, pet: CatPet): Int { + val threshold = pet.stage.growthCycles ?: return 100 + return (player.attr.getOrDefault(GROWTH, 0) * 100 / threshold).coerceIn(0, 100) + } + + fun feed(player: Player) { + player.attr[HUNGER] = 0 + } + + /** + * A stroke keeps a kitten content for 18 minutes; stroking it again while it is already content + * tops that up to the full 25. + */ + fun stroke(player: Player) { + val attention = player.attr.getOrDefault(ATTENTION, 0) + player.attr[ATTENTION] = if (attention <= STROKE_ATTENTION) 0 else STROKE_ATTENTION + } + + /** Playing with a ball of wool keeps a kitten content for 51 minutes. */ + fun play(player: Player) { + player.attr[ATTENTION] = WOOL_ATTENTION + } + + fun setChasing(player: Player, chasing: Boolean) { + if (chasing) { + player.attr[CHASING] = true + } else { + player.attr.remove(CHASING) + } + } + + private fun resetCounters(player: Player) { + player.attr.remove(GROWTH) + player.attr.remove(HUNGER) + player.attr.remove(ATTENTION) + } + + private fun remove(npc: Npc) { + if (npc.isSlotAssigned) { + npcRepo.del(npc, Int.MAX_VALUE) + } + } + + companion object { + const val TICK_TIMER: String = "timer.pet_cat_tick" + + private const val FOLLOWER_VARP: String = "varp.follower_npc" + private const val NO_FOLLOWER: Int = -1 + + /** Minutes to cycles: one cycle is 0.6 seconds. */ + private const val MINUTE: Int = 100 + + const val HUNGER_WARN: Int = 24 * MINUTE + const val HUNGER_CRY: Int = 27 * MINUTE + const val HUNGER_RUN: Int = 30 * MINUTE + + const val ATTENTION_WARN: Int = 25 * MINUTE + const val ATTENTION_CRY: Int = 32 * MINUTE + const val ATTENTION_RUN: Int = 39 * MINUTE + + /** 25 minutes to the warning minus the 18 minutes a stroke buys. */ + private const val STROKE_ATTENTION: Int = ATTENTION_WARN - 18 * MINUTE + + /** 51 minutes of contentment: 25 to the warning plus 26 in credit. */ + private const val WOOL_ATTENTION: Int = ATTENTION_WARN - 51 * MINUTE + + /** Client cycles (20ms) between the growth meow and the purr that follows it. */ + private const val GROWTH_PURR_DELAY: Int = 50 + + /** One-in-N chance per cycle: on average one noise every five minutes. */ + private const val AMBIENT_NOISE_CHANCE: Int = 500 + + private val SPAWN_DIRECTIONS: List = + listOf( + Direction.South, + Direction.West, + Direction.East, + Direction.North, + Direction.SouthWest, + Direction.SouthEast, + Direction.NorthWest, + Direction.NorthEast, + ) + + private const val RUN_AWAY_DISTANCE: Int = 6 + private const val RUN_AWAY_CYCLES: Int = 6 + + /** Obj id of the cat currently out following (persists so it respawns on login). */ + private val FOLLOWING_OBJ = AttributeKey(persistenceKey = "pets.cat.following_obj") + private val GROWTH = AttributeKey(persistenceKey = "pets.cat.growth") + private val HUNGER = AttributeKey(persistenceKey = "pets.cat.hunger") + private val ATTENTION = AttributeKey(persistenceKey = "pets.cat.attention") + + private val PET_NPC = AttributeKey(temp = true) + private val CHASING = AttributeKey(temp = true) + private val RESYNC_FOLLOWER_VARP = AttributeKey(temp = true) + } +} diff --git a/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPetScript.kt b/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPetScript.kt new file mode 100644 index 000000000..708c6a4ff --- /dev/null +++ b/content/other/pets/src/main/kotlin/org/rsmod/content/other/pets/cats/CatPetScript.kt @@ -0,0 +1,438 @@ +package org.rsmod.content.other.pets.cats + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import dev.openrune.types.NpcMode +import dev.openrune.util.Wearpos +import dev.or2.central.account.Rights +import jakarta.inject.Inject +import org.rsmod.api.invtx.invAdd +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.repo.npc.NpcRepository +import org.rsmod.api.script.onCommand +import org.rsmod.api.script.onOpHeld5 +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpc3 +import org.rsmod.api.script.onOpNpc4 +import org.rsmod.api.script.onOpNpc5 +import org.rsmod.api.script.onOpNpcU +import org.rsmod.api.script.onPlayerLogin +import org.rsmod.api.script.onPlayerLogout +import org.rsmod.api.script.onPlayerSoftTimer +import org.rsmod.game.entity.Npc +import org.rsmod.map.zone.ZoneKey +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Pet cats: kittens, cats, overgrown cats and lazy cats that follow their owner. + * + * Dropping the held cat releases it as a follower; `Pick-up` returns it to the inventory. A + * kitten must be fed (fish or milk) and given attention (stroking or a ball of wool) or it runs + * away; after three hours of following it grows into a cat, which after a further five and a + * half hours becomes an overgrown cat. Cats and kittens can chase rats; overgrown and lazy cats + * cannot. Wily cats do not exist in this cache revision and are not handled. + */ +class CatPetScript +@Inject +constructor(private val cats: CatPetManager, private val npcRepo: NpcRepository) : + PluginScript() { + + override fun ScriptContext.startup() { + onPlayerLogin { cats.followingPet(player)?.let { cats.spawn(player, it) } } + onPlayerLogout { cats.despawn(player) } + onPlayerSoftTimer(CatPetManager.TICK_TIMER) { cats.tick(player) } + + for (pet in CatPet.all) { + onOpHeld5(pet.obj) { release(pet, it.slot) } + onOpNpc1(pet.npc) { pickUp(it.npc, pet) } + onOpNpc3(pet.npc) { talkTo(it.npc, pet) } + onOpNpc4(pet.npc) { chase(it.npc, pet) } + onOpNpc5(pet.npc) { interact(it.npc, pet) } + onOpNpcU(pet.npc) { useItem(it.npc, pet, it.objType, it.invSlot) } + } + + onCommand("cat") { + desc = "Spawn a pet cat obj: ::cat [colour 0-6]" + requiredRights = Rights.ADMINISTRATOR + cheat { + val stage = + when (args.getOrNull(0)?.lowercase()) { + "kitten" -> CatStage.KITTEN + "cat" -> CatStage.CAT + "overgrown" -> CatStage.OVERGROWN + "lazy" -> CatStage.LAZY + else -> { + player.mes("Usage: ::cat [colour 0-6]") + return@cheat + } + } + val colour = CatColour.entries[args.getOrNull(1)?.toInt() ?: 0] + val pet = CatPet(stage, colour) + if (player.inv.isFull()) { + player.mes("You don't have enough inventory space.") + return@cheat + } + player.invAdd(player.inv, pet.obj, count = 1) + player.mes("Spawned ${pet.obj}. Drop it to have it follow you.") + } + } + + onCommand("catgrow") { + desc = "Make your following cat grow into its next stage on the next cycle" + requiredRights = Rights.ADMINISTRATOR + cheat { + if (cats.forceGrowth(player)) { + player.mes("Your cat will grow on the next cycle.") + } else { + player.mes("You have no following cat that can grow.") + } + } + } + + onCommand("catstatus") { + desc = "Show your following cat's growth, hunger and attention counters" + requiredRights = Rights.ADMINISTRATOR + cheat { player.mes(cats.status(player)) } + } + } + + /* Release / pick-up */ + + private suspend fun ProtectedAccess.release(pet: CatPet, slot: Int) { + if (cats.hasFollower(player)) { + mes("You already have a follower.") + return + } + if (invDel(inv, pet.obj, count = 1, slot = slot).failure) { + return + } + val npc = cats.spawn(player, pet) + anim("seq.human_pickupfloor") + npc.say("Miaooow!") + soundSynth(pet.mewSynth) + mes("You put down your ${pet.stage.label} and it starts to follow you.") + } + + private suspend fun ProtectedAccess.pickUp(npc: Npc, pet: CatPet) { + if (!ownsCat(npc)) { + return + } + arriveDelay() + if (inv.isFull()) { + mes("You don't have enough inventory space to pick up your ${pet.stage.label}.") + return + } + faceEntitySquare(npc) + anim("seq.human_pickupfloor") + if (invAdd(inv, pet.obj, count = 1).failure) { + return + } + cats.release(player) + soundSynth(pet.mewSynth) + mes("You pick up your ${pet.stage.label}.") + } + + /* Talk-to */ + + private suspend fun ProtectedAccess.talkTo(npc: Npc, pet: CatPet) { + if (!ownsCat(npc)) { + return + } + arriveDelay() + val speaksCat = wearsCatspeakAmulet() + startDialogue(npc) { + chatPlayer(quiz, "Hey kitty. What's new?") + if (!speaksCat) { + npc.say(if (pet.isKitten) "Meow!" else "Miaow!") + access.soundSynth(pet.mewSynth) + access.mes("You don't understand a word your ${pet.stage.label} says.") + return@startDialogue + } + catReply(npc, pet) + } + } + + private suspend fun Dialogue.catReply(npc: Npc, pet: CatPet) { + val hungry = pet.isKitten && cats.isHungry(player) + val lonely = pet.isKitten && cats.isLonely(player) + val nearlyGrown = cats.growthPercent(player, pet) >= 80 + val line = + when { + hungry -> "I'm really hungry, do you have any fish, meeaow?" + lonely -> + "You don't think I'm cute and cuddly any more, do you? Is there another cat " + + "you like?" + pet.stage == CatStage.KITTEN -> "Meeow I'm happy." + pet.stage == CatStage.CAT && nearlyGrown -> + "I'm not as young as I used be, I think I'm beginning to get a bit fat too." + pet.stage == CatStage.CAT -> + "I'm good. But could we go adventuring soon, I'm tired of talking, meeoow?" + pet.stage == CatStage.OVERGROWN -> + "I'm a bit too big to be chasing rats these days, meeoow. Let's just take " + + "things easy." + else -> "Zzz... I'm far too comfortable to go anywhere, meeoow." + } + access.soundSynth(if (hungry || lonely) "synth.sad_meeoow" else pet.mewSynth) + chatNpc(if (hungry || lonely) sad else happy, line) + npc.say(if (hungry || lonely) "Meeeooow..." else "Purr") + } + + /* Chase */ + + private suspend fun ProtectedAccess.chase(npc: Npc, pet: CatPet) { + if (!ownsCat(npc)) { + return + } + arriveDelay() + if (!pet.stage.canChase) { + mes("Your ${pet.stage.label} is far too big and lazy to go chasing rats.") + return + } + val rat = nearestRat(npc) + if (rat == null) { + npc.say("Meow?") + soundSynth(pet.mewSynth) + mes("Your ${pet.stage.label} looks around but can't see anything to chase.") + return + } + cats.setChasing(player, true) + try { + npc.mode = NpcMode.None + npc.resetFaceEntity() + npc.walk(rat.coords) + val steps = npc.coords.chebyshevDistance(rat.coords).coerceIn(1, 4) + delay(steps) + npc.faceSquare(rat.coords) + npc.anim("seq.cat_pounce") + soundSynth("synth.cat_hiss") + delay(1) + val chance = if (pet.isKitten) KITTEN_CATCH_CHANCE else CAT_CATCH_CHANCE + if (rat.isSlotAssigned && random.of(maxExclusive = 100) < chance) { + npcRepo.despawn(rat, rat.type.respawnRate) + npc.say("Purr!") + soundSynth("synth.purr") + mes("Your ${pet.stage.label} pounces on the rat and catches it!") + } else { + mes("The rat is too quick and scurries away from your ${pet.stage.label}.") + } + } finally { + cats.setChasing(player, false) + } + } + + private fun nearestRat(cat: Npc): Npc? = + npcRepo + .findAll(ZoneKey.from(cat.coords), zoneRadius = 1) + .filter { it.isSlotAssigned && it.isVisible && it.type.name == "Rat" } + .filter { it.coords.level == cat.coords.level } + .filter { it.coords.chebyshevDistance(cat.coords) <= CHASE_RANGE } + .minByOrNull { it.coords.chebyshevDistance(cat.coords) } + + /* Interact: stroke / shoo away */ + + private suspend fun ProtectedAccess.interact(npc: Npc, pet: CatPet) { + if (!ownsCat(npc)) { + return + } + arriveDelay() + startDialogue(npc) { + val choice = choice2("Stroke", 1, "Shoo away", 2) + when (choice) { + 1 -> stroke(npc, pet) + 2 -> shooAway(npc, pet) + } + } + } + + private suspend fun Dialogue.stroke(npc: Npc, pet: CatPet) { + access.faceEntitySquare(npc) + access.anim("seq.human_pickupfloor") + npc.anim("seq.cat_rollingover") + npc.say("Purr...purr...") + access.soundSynth("synth.purr") + cats.stroke(player) + access.mes("You stroke your ${pet.stage.label}. It purrs contentedly.") + } + + private suspend fun Dialogue.shooAway(npc: Npc, pet: CatPet) { + mesbox("Are you sure you want to shoo your ${pet.stage.label} away? It won't come back.") + val confirm = choice2("Yes, shoo it away.", true, "No, I want to keep it.", false) + if (!confirm) { + return + } + access.faceEntitySquare(npc) + npc.anim("seq.cat_archback") + npc.say("Meeeooow!") + access.soundSynth("synth.cat_hiss") + cats.runAway(player) + access.mes("You shoo your ${pet.stage.label} away. It runs off into the distance.") + } + + /* Use item on cat */ + + private suspend fun ProtectedAccess.useItem( + npc: Npc, + pet: CatPet, + objType: ItemServerType, + slot: Int, + ) { + if (!ownsCat(npc)) { + return + } + arriveDelay() + when { + objType.id == BALL_OF_WOOL -> playWithWool(npc, pet, slot) + objType.id == BUCKET_OF_MILK -> feedMilk(npc, pet, slot) + objType.id in fishIds -> feedFish(npc, pet, objType, slot) + else -> mes("Your ${pet.stage.label} isn't interested in that.") + } + } + + private fun ProtectedAccess.feedFish(npc: Npc, pet: CatPet, fish: ItemServerType, slot: Int) { + if (invDel(inv, fish.internalName, count = 1, slot = slot).failure) { + return + } + faceEntitySquare(npc) + anim("seq.human_pickupfloor") + npc.anim("seq.cat_paw") + npc.say("Purr!") + soundSynth("synth.purr") + cats.feed(player) + mes("The ${pet.stage.label} gobbles up the fish.") + } + + private fun ProtectedAccess.feedMilk(npc: Npc, pet: CatPet, slot: Int) { + val replace = invReplace(inv, "obj.bucket_milk", count = 1, replacement = "obj.bucket_empty") + if (replace.failure) { + return + } + faceEntitySquare(npc) + anim("seq.human_pickupfloor") + npc.anim("seq.cat_paw") + npc.say("Purpurr") + soundSynth("synth.purr") + cats.feed(player) + mes("The ${pet.stage.label} laps up the milk.") + if (pet.isHellcat) { + // Milk turns a hellcat back into a normal cat of the default colour. + val normal = CatPet(pet.stage, CatColour.DEFAULT) + cats.spawn(player, normal) + mes("The milk washes the hellfire out of your ${pet.stage.label}'s fur.") + } + } + + private fun ProtectedAccess.playWithWool(npc: Npc, pet: CatPet, slot: Int) { + if (invDel(inv, "obj.ball_of_wool", count = 1, slot = slot).failure) { + return + } + faceEntitySquare(npc) + anim("seq.human_pickupfloor") + npc.anim("seq.cat_pounce") + npc.say("Purr!") + soundSynth("synth.purr") + cats.play(player) + mes( + "That ${pet.stage.label} loves to play with that ball of wool. I think it is its " + + "favourite." + ) + } + + /* Helpers */ + + private fun ProtectedAccess.ownsCat(npc: Npc): Boolean { + if (cats.isOwnedBy(npc, player)) { + return true + } + mes("That's not your cat.") + return false + } + + private fun ProtectedAccess.wearsCatspeakAmulet(): Boolean { + val worn = player.worn[Wearpos.Front.slot] ?: return false + return worn.id in catspeakAmulets + } + + private val CatPet.mewSynth: String + get() = if (isKitten) "synth.kittens_mew" else "synth.meeoow" + + private companion object { + const val CHASE_RANGE: Int = 6 + const val KITTEN_CATCH_CHANCE: Int = 30 + const val CAT_CATCH_CHANCE: Int = 50 + + val BALL_OF_WOOL: Int = "obj.ball_of_wool".asRSCM(RSCMType.OBJ) + val BUCKET_OF_MILK: Int = "obj.bucket_milk".asRSCM(RSCMType.OBJ) + + val catspeakAmulets: Set = + setOf( + "obj.twocats_amuletofcatspeak".asRSCM(RSCMType.OBJ), + "obj.ics_little_amulet_of_catspeak".asRSCM(RSCMType.OBJ), + ) + + /** Raw and cooked fish a cat will eat, plus roe, caviar and frog spawn. */ + val fishIds: Set = + listOf( + "obj.raw_shrimp", + "obj.shrimp", + "obj.raw_anchovies", + "obj.anchovies", + "obj.raw_sardine", + "obj.sardine", + "obj.seasoned_sardine", + "obj.raw_herring", + "obj.herring", + "obj.raw_mackerel", + "obj.mackerel", + "obj.raw_trout", + "obj.trout", + "obj.raw_cod", + "obj.cod", + "obj.raw_pike", + "obj.pike", + "obj.raw_salmon", + "obj.salmon", + "obj.raw_tuna", + "obj.tuna", + "obj.raw_lobster", + "obj.lobster", + "obj.raw_bass", + "obj.bass", + "obj.raw_swordfish", + "obj.swordfish", + "obj.raw_monkfish", + "obj.monkfish", + "obj.raw_shark", + "obj.shark", + "obj.raw_seaturtle", + "obj.seaturtle", + "obj.raw_mantaray", + "obj.mantaray", + "obj.raw_anglerfish", + "obj.anglerfish", + "obj.raw_dark_crab", + "obj.dark_crab", + "obj.raw_cave_eel", + "obj.cave_eel", + "obj.raw_lava_eel", + "obj.lava_eel", + "obj.mort_slimey_eel", + "obj.mort_slimey_eel_cooked", + "obj.raw_giant_carp", + "obj.hunting_raw_fish_special", + "obj.hunting_fish_special", + "obj.tbwt_raw_karambwan", + "obj.tbwt_cooked_karambwan", + "obj.tbwt_poorly_cooked_karambwan", + "obj.tbwt_raw_karambwanji", + "obj.brut_roe", + "obj.brut_caviar", + "obj.giant_frogspawn", + ) + .map { it.asRSCM(RSCMType.OBJ) } + .toSet() + } +} diff --git a/content/other/pets/src/main/resources/gamevals.toml b/content/other/pets/src/main/resources/gamevals.toml new file mode 100644 index 000000000..05d1874e0 --- /dev/null +++ b/content/other/pets/src/main/resources/gamevals.toml @@ -0,0 +1,12 @@ +# Cat sound effects. The vanilla gamevals dump carries no synth names, so the raw cache ids are +# declared here (ids taken from the OSRS wiki "List of sound IDs"). +[gamevals.synth] +kittens_mew = 339 +purr = 340 +cat_hiss = 335 +meeoow = 1169 +sad_meeoow = 1176 + +[gamevals.timer] +# Per-cycle soft timer that drives a following cat: follow upkeep, growth, hunger and attention. +pet_cat_tick = 120 diff --git a/content/other/playtime/build.gradle.kts b/content/other/playtime/build.gradle.kts new file mode 100644 index 000000000..a7e5cb889 --- /dev/null +++ b/content/other/playtime/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) +} diff --git a/content/other/playtime/src/main/kotlin/org/rsmod/content/other/playtime/PlaytimeScript.kt b/content/other/playtime/src/main/kotlin/org/rsmod/content/other/playtime/PlaytimeScript.kt new file mode 100644 index 000000000..97a2348eb --- /dev/null +++ b/content/other/playtime/src/main/kotlin/org/rsmod/content/other/playtime/PlaytimeScript.kt @@ -0,0 +1,71 @@ +package org.rsmod.content.other.playtime + +import jakarta.inject.Inject +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.vars.intVarp +import org.rsmod.api.script.onCommand +import org.rsmod.api.script.onPlayerLogin +import org.rsmod.game.entity.Player +import org.rsmod.game.entity.PlayerList +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Surfaces the `varp.playtime` counter that [org.rsmod.content.other.mapclock] increments once per + * cycle: a greeting on login and a `::playtime [name]` lookup for any online player. + */ +class PlaytimeScript @Inject constructor(private val playerList: PlayerList) : PluginScript() { + private var Player.playtime by intVarp("varp.playtime") + + override fun ScriptContext.startup() { + onPlayerLogin { player.greet() } + + onCommand("playtime") { + desc = "Show how long you (or another online player) have played for" + cheat { + val target = args.firstOrNull()?.let(::findOnline) ?: player + if (args.isNotEmpty() && target === player && !player.matches(args[0])) { + player.mes("No online player found named '${args[0]}'.") + return@cheat + } + if (target === player) { + player.mes("You have played for ${format(player.playtime)}.") + } else { + player.mes("${target.displayName} has played for ${format(target.playtime)}.") + } + } + } + } + + private fun Player.greet() { + val cycles = playtime + if (cycles < CYCLES_PER_HOUR) { + mes("Welcome. Type ::playtime to see how long you have played for.") + return + } + mes("Welcome back. You have played for ${format(cycles)}.") + } + + private fun findOnline(name: String): Player? = playerList.firstOrNull { it.matches(name) } + + private fun Player.matches(name: String): Boolean = + displayName.equals(name.replace('_', ' '), ignoreCase = true) + + private fun format(cycles: Int): String { + val totalMinutes = (cycles.toLong() * MILLIS_PER_CYCLE) / 60_000 + val hours = totalMinutes / 60 + val minutes = totalMinutes % 60 + return when { + hours > 0 -> "$hours hour${plural(hours)} and $minutes minute${plural(minutes)}" + minutes > 0 -> "$minutes minute${plural(minutes)}" + else -> "less than a minute" + } + } + + private fun plural(value: Long): String = if (value == 1L) "" else "s" + + private companion object { + const val MILLIS_PER_CYCLE = 600L + const val CYCLES_PER_HOUR = 6_000 + } +} diff --git a/content/other/poison/build.gradle.kts b/content/other/poison/build.gradle.kts new file mode 100644 index 000000000..7ac63dc36 --- /dev/null +++ b/content/other/poison/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) + implementation(libs.jackson.databind) + implementation(libs.jackson.dataformat.toml) + implementation(libs.jackson.module.kotlin) +} diff --git a/content/other/poison/src/main/kotlin/org/rsmod/content/other/poison/NpcPoison.kt b/content/other/poison/src/main/kotlin/org/rsmod/content/other/poison/NpcPoison.kt new file mode 100644 index 000000000..0579d6d38 --- /dev/null +++ b/content/other/poison/src/main/kotlin/org/rsmod/content/other/poison/NpcPoison.kt @@ -0,0 +1,79 @@ +package org.rsmod.content.other.poison + +import kotlin.math.min +import org.rsmod.api.config.refs.done.hitmark_groups +import org.rsmod.api.mechanics.toxins.impl.PlayerPoison +import org.rsmod.api.npc.hit.modifier.NpcHitModifier +import org.rsmod.api.npc.hit.queueHit +import org.rsmod.game.entity.Npc +import org.rsmod.game.hit.HitType + +/** + * Poison on npcs, mirroring the player mechanic in `api/mechanics/toxins`: a severity that falls + * by one every [TICK_INTERVAL] cycles, dealing [PlayerPoison.damageForSeverity] each time until it + * runs out. The severity lives in `varn.npc_poison_severity`; the ticking is `timer.npc_poison`. + */ +internal object NpcPoison { + const val TIMER: String = "timer.npc_poison" + const val TICK_INTERVAL: Int = PlayerPoison.TICK_INTERVAL + + private const val SEVERITY_VARN = "varn.npc_poison_severity" + + /** Poison hits carry no attacker and must not be modified by combat hooks. */ + private val noModifier = NpcHitModifier {} + + fun isPoisoned(npc: Npc): Boolean = npc.vars[SEVERITY_VARN] > 0 + + /** + * Poisons [npc] with a poison whose first hit deals [initialDamage]. A stronger poison already + * running is left alone, as it is for players; a weaker one is replaced. + */ + fun tryPoison(npc: Npc, initialDamage: Int): Boolean { + if (initialDamage <= 0 || npc.hitpoints <= 0) { + return false + } + val severity = PlayerPoison.severityForInitialDamage(initialDamage) + if (npc.vars[SEVERITY_VARN] >= severity) { + return false + } + queuePoisonHit(npc, initialDamage) + npc.vars[SEVERITY_VARN] = severity - 1 + npc.timer(TIMER, TICK_INTERVAL) + return true + } + + fun onTimerTick(npc: Npc) { + var severity = npc.vars[SEVERITY_VARN] + if (severity <= 0 || npc.hitpoints <= 0) { + clear(npc) + return + } + queuePoisonHit(npc, PlayerPoison.damageForSeverity(severity)) + severity-- + if (severity <= 0) { + clear(npc) + return + } + npc.vars[SEVERITY_VARN] = severity + npc.timer(TIMER, TICK_INTERVAL) + } + + fun clear(npc: Npc) { + npc.vars[SEVERITY_VARN] = 0 + npc.clearTimer(TIMER) + } + + private fun queuePoisonHit(npc: Npc, damage: Int) { + val capped = min(damage, npc.hitpoints) + if (capped <= 0) { + return + } + npc.queueHit( + delay = 1, + type = HitType.Typeless, + damage = capped, + modifier = noModifier, + hitmark = hitmark_groups.poison_damage, + ) + } +} diff --git a/content/other/poison/src/main/kotlin/org/rsmod/content/other/poison/PoisonScript.kt b/content/other/poison/src/main/kotlin/org/rsmod/content/other/poison/PoisonScript.kt new file mode 100644 index 000000000..40a23af5a --- /dev/null +++ b/content/other/poison/src/main/kotlin/org/rsmod/content/other/poison/PoisonScript.kt @@ -0,0 +1,181 @@ +package org.rsmod.content.other.poison + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.toml.TomlFactory +import com.fasterxml.jackson.module.kotlin.readValue +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.ParamMap +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import jakarta.inject.Inject +import org.rsmod.api.config.refs.params +import org.rsmod.api.mechanics.toxins.impl.PlayerPoison +import org.rsmod.api.npc.events.NpcHitEvents +import org.rsmod.api.player.events.PlayerHitEvents +import org.rsmod.api.random.GameRandom +import org.rsmod.api.script.onEvent +import org.rsmod.api.script.onGameStartup +import org.rsmod.api.script.onNpcTimer +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.NpcList +import org.rsmod.game.entity.Player +import org.rsmod.game.entity.npc.NpcStateEvents +import org.rsmod.game.hit.Hit +import org.rsmod.game.hit.HitType +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Poison in combat: poisonous npcs poison the players they hit, poisoned weapons and ammunition + * poison whoever they hit, and poisoned npcs take poison damage over time. + * + * Player poison itself (the varp, the status orb, the timer and the damage) already exists in + * `api/mechanics/toxins`; nothing in combat called it. Npcs had no poison at all, so [NpcPoison] + * mirrors the player mechanic with an npc var and timer. + * + * Which npcs are poisonous, and how badly, is data: [POISONOUS_NPCS_RESOURCE] is applied to the + * npc types at startup as `param.npc_poison_severity`, the param the player poison api already + * reads. Poisoned weapons are recognised by their gameval name (`_p`, `_p+`, `_p++`). + */ +class PoisonScript +@Inject +constructor(private val npcList: NpcList, private val random: GameRandom) : PluginScript() { + override fun ScriptContext.startup() { + onGameStartup { applyPoisonousNpcs() } + onEvent { onPlayerHit(player, hit) } + onEvent { onNpcHit(npc, hit) } + onNpcTimer(NpcPoison.TIMER) { NpcPoison.onTimerTick(npc) } + onEvent { NpcPoison.clear(npc) } + } + + private fun applyPoisonousNpcs() { + val file = PoisonousNpcsFile.loadResource() + var applied = 0 + for (entry in file.npc) { + val type = ServerCacheManager.getNpc(entry.id.asRSCM(RSCMType.NPC)) + if (type == null) { + logger.warn { "Npc not in cache, skipping: ${entry.id}" } + continue + } + val severity = PlayerPoison.severityForInitialDamage(entry.damage) + val values = mapOf(params.npc_poison_severity.id to severity) + type.paramMap = ParamMap(primitiveMap = type.paramMap?.primitiveMap.orEmpty() + values) + applied++ + } + logger.info { "Marked $applied npcs as poisonous." } + } + + /** A player was hit: a poisonous npc, or another player's poisoned weapon, may poison them. */ + private fun onPlayerHit(player: Player, hit: Hit) { + if (!hit.canCarryPoison()) { + return + } + if (hit.isFromNpc) { + val npc = hit.resolveNpcSource(npcList) ?: return + val severity = npc.visType.paramOrNull(params.npc_poison_severity) ?: 0 + if (severity > 0 && random.of(NPC_POISON_ONE_IN) == 0) { + PlayerPoison.tryPoison(player, source = npc) + } + return + } + if (hit.isFromPlayer) { + val poison = WeaponPoison.of(hit) ?: return + if (random.of(poison.oneIn) == 0) { + PlayerPoison.tryPoison(player, initialDamage = poison.damage) + } + } + } + + /** An npc was hit by a player: a poisoned weapon or ammunition may poison it. */ + private fun onNpcHit(npc: Npc, hit: Hit) { + if (!hit.isFromPlayer || !hit.canCarryPoison()) { + return + } + if ((npc.visType.paramOrNull(params.poison_immunity) ?: 0) > 0) { + return + } + val poison = WeaponPoison.of(hit) ?: return + if (random.of(poison.oneIn) == 0) { + NpcPoison.tryPoison(npc, poison.damage) + } + } + + /** Only melee and ranged hits that dealt damage can poison; magic and typeless hits cannot. */ + private fun Hit.canCarryPoison(): Boolean = + damage > 0 && (type == HitType.Melee || type == HitType.Ranged) + + private data class WeaponPoison(val damage: Int, val oneIn: Int) { + companion object { + /** The poison on the weapon (melee) or ammunition (ranged) that dealt [hit], if any. */ + fun of(hit: Hit): WeaponPoison? { + val ranged = hit.type == HitType.Ranged + // Ranged hits carry the ammunition as the secondary obj; thrown weapons have none + // and are the righthand obj themselves. + val source = + if (ranged) hit.secondaryType() ?: hit.righthandType() else hit.righthandType() + val tier = source?.poisonTier() ?: return null + return if (ranged) { + WeaponPoison(damage = RANGED_DAMAGE[tier - 1], oneIn = RANGED_POISON_ONE_IN) + } else { + WeaponPoison(damage = MELEE_DAMAGE[tier - 1], oneIn = MELEE_POISON_ONE_IN) + } + } + + /** `1` for `(p)`, `2` for `(p+)`, `3` for `(p++)`, `null` for an unpoisoned obj. */ + private fun ItemServerType.poisonTier(): Int? { + val name = + runCatching { RSCM.getReverseMapping(RSCMType.OBJ, id) }.getOrNull() + ?: return null + return when { + name.endsWith("_p++") -> 3 + name.endsWith("_p+") -> 2 + name.endsWith("_p") -> 1 + else -> null + } + } + + private val MELEE_DAMAGE = intArrayOf(4, 5, 6) + private val RANGED_DAMAGE = intArrayOf(2, 3, 4) + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + data class PoisonousNpc(val id: String, val damage: Int) + + @JsonIgnoreProperties(ignoreUnknown = true) + data class PoisonousNpcsFile(val npc: List = emptyList()) { + companion object { + private val mapper: ObjectMapper = ObjectMapper(TomlFactory()).registerKotlinModule() + + fun loadResource(): PoisonousNpcsFile { + val stream = + PoisonousNpcsFile::class.java.classLoader.getResourceAsStream( + POISONOUS_NPCS_RESOURCE + ) ?: error("Missing resource: $POISONOUS_NPCS_RESOURCE") + return stream.use { parse(it.readBytes().decodeToString()) } + } + + fun parse(toml: String): PoisonousNpcsFile = mapper.readValue(toml) + } + } + + private companion object { + private val logger = InlineLogger() + + const val POISONOUS_NPCS_RESOURCE = "poisonous-npcs.toml" + + /** A poisonous npc's landed hit poisons one time in this many. */ + private const val NPC_POISON_ONE_IN = 4 + + /** A landed hit with a poisoned melee weapon poisons one time in this many. */ + private const val MELEE_POISON_ONE_IN = 4 + + /** A landed hit with poisoned ammunition or a thrown weapon poisons one time in this many. */ + private const val RANGED_POISON_ONE_IN = 8 + } +} diff --git a/content/other/poison/src/main/resources/poisonous-npcs.toml b/content/other/poison/src/main/resources/poisonous-npcs.toml new file mode 100644 index 000000000..2f021c4b3 --- /dev/null +++ b/content/other/poison/src/main/resources/poisonous-npcs.toml @@ -0,0 +1,46 @@ +# Npcs whose melee and ranged hits can poison the player, and the poison's starting damage. +# Applied to the npc types at game startup as `param.npc_poison_severity`; a hit that lands has +# a one-in-four chance to poison. Values follow the wiki where it lists them. + +npc = [ + { id = "npc.poisonspider", damage = 4 }, + { id = "npc.dungeonspider", damage = 4 }, + { id = "npc.wbr_poisonspider", damage = 4 }, + { id = "npc.wbr_cave_poisonspider", damage = 4 }, + { id = "npc.deadly_red_spider", damage = 4 }, + { id = "npc.jungle_spider", damage = 2 }, + { id = "npc.mm_jungle_spider", damage = 2 }, + { id = "npc.tbwcu_jungle_spider", damage = 2 }, + { id = "npc.tbwcu_spider_appear", damage = 2 }, + { id = "npc.poison_scorpion", damage = 2 }, + { id = "npc.snake", damage = 1 }, + { id = "npc.mm_jungle_snake", damage = 1 }, + { id = "npc.varlamore_snake_rainforest", damage = 1 }, + { id = "npc.varlamore_snake_savannah", damage = 1 }, + { id = "npc.tbwcu_snakejungle_appear", damage = 1 }, + { id = "npc.tbwcu_village_snake", damage = 1 }, + { id = "npc.royal_sea_snake_baby", damage = 2 }, + { id = "npc.royal_sea_snake_medium", damage = 6 }, + { id = "npc.royal_sea_snake_mother_smaller", damage = 8 }, + { id = "npc.kalphite_worker", damage = 4 }, + { id = "npc.kalphite_worker_chamber", damage = 4 }, + { id = "npc.kalphite_worker_strongholdcave", damage = 4 }, + { id = "npc.kalphite_soldier", damage = 4 }, + { id = "npc.kalphite_soldier_strongholdcave", damage = 4 }, + { id = "npc.poh_kalphite_soldier", damage = 4 }, + { id = "npc.kalphite_lord", damage = 8 }, + { id = "npc.kalphite_lord_chamber", damage = 8 }, + { id = "npc.kalphite_lord_strongholdcave", damage = 8 }, + { id = "npc.kalphite_queen", damage = 8 }, + { id = "npc.slayer_cave_crawler_1", damage = 4 }, + { id = "npc.slayer_cave_crawler_2", damage = 4 }, + { id = "npc.slayer_cave_crawler_3", damage = 4 }, + { id = "npc.slayer_cave_crawler_4", damage = 4 }, + { id = "npc.cave_crawler_ice_1", damage = 4 }, + { id = "npc.cave_crawler_ice_2", damage = 4 }, + { id = "npc.tribesman", damage = 4 }, + { id = "npc.tribesman_variant01", damage = 4 }, + { id = "npc.tbwcu_tribesman", damage = 4 }, + { id = "npc.tbwcu_tribesman_instant", damage = 4 }, + { id = "npc.tol_spidine", damage = 2 }, +] diff --git a/content/other/pouches/build.gradle.kts b/content/other/pouches/build.gradle.kts new file mode 100644 index 000000000..164e89607 --- /dev/null +++ b/content/other/pouches/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.attr) + implementation(projects.api.pluginCommons) + implementation(projects.api.scriptAdvanced) + implementation(projects.content.interfaces.bank) +} diff --git a/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/PouchDeathHook.kt b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/PouchDeathHook.kt new file mode 100644 index 000000000..719c77360 --- /dev/null +++ b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/PouchDeathHook.kt @@ -0,0 +1,135 @@ +package org.rsmod.content.other.pouches + +import dev.openrune.types.ItemServerType +import dev.openrune.types.util.UncheckedType +import jakarta.inject.Inject +import org.rsmod.api.death.PlayerDeathContext +import org.rsmod.api.death.PlayerDeathHandling +import org.rsmod.api.death.PlayerDeathItemHook +import org.rsmod.api.invtx.invClear +import org.rsmod.api.player.SupplyItems +import org.rsmod.api.player.hook.GroundItemDropContext +import org.rsmod.api.player.hook.GroundItemDropResolver +import org.rsmod.api.player.hook.GroundItemDropSource +import org.rsmod.api.repo.obj.ObjRepository +import org.rsmod.content.other.pouches.lootingbag.LootingBags +import org.rsmod.content.other.pouches.lootingbag.hasLootingBag +import org.rsmod.content.other.pouches.lootingbag.lootingBag +import org.rsmod.content.other.pouches.lootingbag.saveLootingBag +import org.rsmod.content.other.pouches.runepouch.RunePouchRunes +import org.rsmod.content.other.pouches.runepouch.readRunePouch +import org.rsmod.content.other.pouches.runepouch.runePouchSlots +import org.rsmod.content.other.pouches.runepouch.writeRunePouch +import org.rsmod.game.MapClock +import org.rsmod.game.entity.Player +import org.rsmod.game.inv.InvObj +import org.rsmod.game.obj.Obj +import org.rsmod.game.type.getInvObj + +/** + * What happens to a looting bag and a rune pouch when the player dies. + * + * The looting bag's contents always go to the floor (to the killer on a PvP death, minus food and + * potions, otherwise to the player) and never count towards the items kept; the bag itself is + * destroyed on any PvP or Wilderness death. A rune pouch drops its runes to the killer on a PvP + * death; outside PvP the runes stay in the pouch. + */ +@OptIn(UncheckedType::class) +class PouchDeathHook +@Inject +constructor( + private val objRepo: ObjRepository, + private val mapClock: MapClock, + private val dropResolver: GroundItemDropResolver, +) : PlayerDeathItemHook { + override fun beforeDrops(context: PlayerDeathContext, handling: PlayerDeathHandling) { + val player = context.player + releaseLootingBag(player, context, handling) + if (context.isPvpDeath) { + spillRunePouch(player, context, handling) + } + } + + private fun releaseLootingBag( + player: Player, + context: PlayerDeathContext, + handling: PlayerDeathHandling, + ) { + if (!player.hasLootingBag()) { + return + } + val bag = player.lootingBag + for (obj in bag.objs) { + if (obj == null) { + continue + } + val type = getInvObj(obj) + if (context.isPvpDeath && SupplyItems.isFoodOrPotion(type)) { + continue + } + drop(player, obj, type, context, handling) + } + player.invClear(bag) + player.saveLootingBag() + + if (context.isPvpDeath || context.inWilderness) { + for (slot in player.inv.indices) { + val obj = player.inv[slot] ?: continue + if (obj.id in LootingBags.ids) { + player.inv[slot] = null + } + } + } + } + + private fun spillRunePouch( + player: Player, + context: PlayerDeathContext, + handling: PlayerDeathHandling, + ) { + val slots = player.runePouchSlots() ?: return + val contents = player.readRunePouch(slots) + for (slot in 0 until slots) { + val count = contents.counts[slot] + if (count == 0) { + continue + } + val rune = RunePouchRunes.byCompact(contents.types[slot]) ?: continue + drop(player, InvObj(rune.id, count), rune.type, context, handling) + } + contents.clear() + player.writeRunePouch(contents) + } + + /** Mirrors how the regular death drops place an obj, including ironman ownership rules. */ + private fun drop( + player: Player, + item: InvObj, + type: ItemServerType, + context: PlayerDeathContext, + handling: PlayerDeathHandling, + ) { + val receiver = handling.dropReceiver + val params = + dropResolver.resolve( + GroundItemDropContext( + player = player, + type = type, + coords = context.coords, + source = GroundItemDropSource.Death, + receiver = receiver, + ), + duration = handling.dropDuration, + reveal = handling.revealDelay, + ) + val effectiveReceiver = if (params.ownerOnly) player else receiver + val obj = + when { + effectiveReceiver != null && effectiveReceiver !== player -> + Obj.fromPvp(effectiveReceiver, player, item) + effectiveReceiver != null -> Obj.fromOwner(effectiveReceiver, context.coords, item) + else -> Obj.fromServer(mapClock, context.coords, item) + } + objRepo.add(obj, params.duration, params.reveal) + } +} diff --git a/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/PouchesModule.kt b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/PouchesModule.kt new file mode 100644 index 000000000..e0a6a4abb --- /dev/null +++ b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/PouchesModule.kt @@ -0,0 +1,13 @@ +package org.rsmod.content.other.pouches + +import org.rsmod.api.death.PlayerDeathItemHook +import org.rsmod.api.player.hook.PlayerObjTakeRedirectHook +import org.rsmod.content.other.pouches.lootingbag.LootingBagTakeHook +import org.rsmod.plugin.module.PluginModule + +class PouchesModule : PluginModule() { + override fun bind() { + addSetBinding(PouchDeathHook::class.java) + addSetBinding(LootingBagTakeHook::class.java) + } +} diff --git a/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBagScript.kt b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBagScript.kt new file mode 100644 index 000000000..8e77e9b5e --- /dev/null +++ b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBagScript.kt @@ -0,0 +1,428 @@ +package org.rsmod.content.other.pouches.lootingbag + +import dev.openrune.definition.type.widget.IfEvent +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.aconverted.interf.IfButtonOp +import jakarta.inject.Inject +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.invtx.invClear +import org.rsmod.api.market.MarketPrices +import org.rsmod.api.player.events.interact.HeldBanksideEvents +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.startInvTransmit +import org.rsmod.api.player.stopInvTransmit +import org.rsmod.api.player.ui.ifSetEvents +import org.rsmod.api.player.ui.ifSetHide +import org.rsmod.api.player.vars.boolVarBit +import org.rsmod.api.repo.obj.ObjRepository +import org.rsmod.api.script.advanced.onDestroyHeld +import org.rsmod.api.script.onEvent +import org.rsmod.api.script.onIfClose +import org.rsmod.api.script.onIfModalButton +import org.rsmod.api.script.onOpHeld1 +import org.rsmod.api.script.onOpHeld2 +import org.rsmod.api.script.onOpHeld3 +import org.rsmod.api.script.onOpHeld4 +import org.rsmod.api.script.onOpHeldU +import org.rsmod.api.script.onPlayerLogin +import org.rsmod.content.interfaces.bank.QuantityMode +import org.rsmod.content.interfaces.bank.lastQtyInput +import org.rsmod.content.interfaces.bank.leftClickQtyMode +import org.rsmod.content.interfaces.bank.scripts.BankInvScript +import org.rsmod.game.entity.Player +import org.rsmod.game.obj.Obj +import org.rsmod.game.type.getInvObj +import org.rsmod.objtx.TransactionResult +import org.rsmod.objtx.isOk +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The looting bag: up to 28 stacks of tradeable items that can only be added in the Wilderness and + * only taken out again at a bank. + * + * The `wilderness_lootingbag` interface serves both the `Check` view (the bag inv) and the + * `Deposit` view (the player inventory with `Store` ops); its `wilderness_lootingbag_setup` cs2 + * decides which from the mode argument. In the bank, `View` on the bag reveals the + * `lootingbag_container` panel of the bankside interface, whose ops mirror the bankside deposit + * ops. + */ +class LootingBagScript +@Inject +constructor( + private val objRepo: ObjRepository, + private val bankInv: BankInvScript, + private val marketPrices: MarketPrices, +) : PluginScript() { + private var Player.depositAll by boolVarBit("varbit.lootingbag_useallitems") + private var Player.insideWilderness by boolVarBit("varbit.inside_wilderness") + + override fun ScriptContext.startup() { + onPlayerLogin { player.restoreLootingBag() } + + onOpHeld1(LootingBags.CLOSED) { swapBag(it.slot, LootingBags.CLOSED, LootingBags.OPEN) } + onOpHeld1(LootingBags.OPEN) { swapBag(it.slot, LootingBags.OPEN, LootingBags.CLOSED) } + for (bag in LootingBags.ALL) { + onOpHeld2(bag) { openCheck() } + onOpHeld3(bag) { openDeposit() } + onOpHeld4(bag) { settings() } + onOpHeldU(bag) { useOnBag(it.secondSlot) } + onEvent(bag.asRSCM(RSCMType.OBJ)) { showBankPanel(player) } + } + + onIfModalButton(COMPONENT_ITEMS) { bagInterfaceOp(it.comsub, it.op) } + onIfClose(INTERFACE) { player.onBagInterfaceClose() } + + onIfModalButton(BANKSIDE_ITEMS) { bankPanelOp(it.comsub, it.op) } + onIfModalButton(BANKSIDE_BANK_ALL) { bankAll() } + onIfModalButton(BANKSIDE_DISMISS) { player.dismissBankPanel() } + onIfClose("interface.bankside") { player.onBankClose() } + + onDestroyHeld { + if (type in LootingBags.ALL) { + player.destroyBag() + } + } + } + + private fun ProtectedAccess.swapBag(slot: Int, from: String, to: String) { + val removed = invDel(inv, from, slot = slot) + if (removed.failure) { + return + } + invAdd(inv, to, slot = slot) + } + + private fun ProtectedAccess.openCheck() { + val bag = player.lootingBag + player.attr[VIEW_ATTR] = BagView.Check + ifOpenMainModal(INTERFACE) + runClientScript(SETUP_SCRIPT, MODE_BAG_CONTENTS, TITLE) + invTransmit(bag) + ifSetEvents(COMPONENT_ITEMS, 0 until LootingBags.CAPACITY, IfEvent.Op10) + ifSetText(COMPONENT_TOTAL, "Total value: ${"%,d".format(bagValue())} coins") + } + + private fun ProtectedAccess.openDeposit() { + if (!player.insideWilderness) { + mes(MSG_WILDERNESS_ONLY) + return + } + player.attr[VIEW_ATTR] = BagView.Deposit + ifOpenMainModal(INTERFACE) + runClientScript(SETUP_SCRIPT, MODE_INVENTORY, TITLE) + ifSetEvents( + COMPONENT_ITEMS, + inv.indices, + IfEvent.Op1, + IfEvent.Op2, + IfEvent.Op3, + IfEvent.Op4, + IfEvent.Op9, + IfEvent.Op10, + ) + } + + /** Check view: op10 examines a bag slot. Deposit view: Store-1/5/All/X, then examine. */ + private suspend fun ProtectedAccess.bagInterfaceOp(slot: Int, op: IfButtonOp) { + when (player.attr[VIEW_ATTR]) { + BagView.Check -> { + if (op == IfButtonOp.Op10) { + objExamine(player.lootingBag, slot) + } + } + BagView.Deposit -> + when (op) { + IfButtonOp.Op1 -> deposit(slot, 1) + IfButtonOp.Op2 -> deposit(slot, 5) + IfButtonOp.Op3 -> deposit(slot, Int.MAX_VALUE) + IfButtonOp.Op4 -> { + val input = countDialog() + if (input > 0) { + deposit(slot, input) + } + } + IfButtonOp.Op9, + IfButtonOp.Op10 -> objExamine(inv, slot) + else -> {} + } + null -> {} + } + } + + private fun Player.onBagInterfaceClose() { + val view = attr[VIEW_ATTR] ?: return + attr.remove(VIEW_ATTR) + if (view == BagView.Check) { + stopInvTransmit(lootingBag) + } + } + + /** Using an item on the bag asks for a count unless the `Settings` op said to take them all. */ + private suspend fun ProtectedAccess.useOnBag(itemSlot: Int) { + val obj = inv[itemSlot] ?: return + if (!player.insideWilderness) { + mes(MSG_WILDERNESS_ONLY) + return + } + val rejection = LootingBags.rejection(obj, getInvObj(obj)) + if (rejection != null) { + mes(rejection) + return + } + val carried = inv.objs.sumOf { if (it != null && it.id == obj.id) it.count else 0 } + val amount = + if (player.depositAll || carried == 1) { + Int.MAX_VALUE + } else { + val input = countDialog() + if (input <= 0) { + return + } + input + } + val current = inv[itemSlot] + if (current == null || current.id != obj.id) { + return + } + deposit(itemSlot, amount) + } + + private suspend fun ProtectedAccess.settings() { + val depositAll = + choice2( + "Ask how many to deposit.", + false, + "Deposit all of that item without asking.", + true, + title = "When using an item on the looting bag:", + ) + player.depositAll = depositAll + val message = + if (depositAll) { + "Using an item on the bag will now deposit all of that item." + } else { + "Using an item on the bag will now ask how many to deposit." + } + mes(message) + } + + /** + * Moves up to [amount] of the obj in inventory [slot] into the bag. Non-stackable objs are taken + * one slot at a time, starting with the clicked slot. + * + * @return `true` if anything was moved. + */ + private fun ProtectedAccess.deposit(slot: Int, amount: Int): Boolean { + val obj = inv[slot] ?: return false + if (!player.insideWilderness) { + mes(MSG_WILDERNESS_ONLY) + return false + } + val type = getInvObj(obj) + val rejection = LootingBags.rejection(obj, type) + if (rejection != null) { + mes(rejection) + return false + } + val bag = player.lootingBag + val sources = + if (type.stackable) { + listOf(slot) + } else { + listOf(slot) + inv.indices.filter { it != slot && inv[it]?.id == obj.id } + } + var moved = 0 + var full = false + for (source in sources) { + if (moved >= amount) { + break + } + val stack = inv[source] ?: continue + val take = if (type.stackable) minOf(amount, stack.count) else 1 + val result = invMoveFromSlot(from = inv, into = bag, fromSlot = source, count = take, strict = false) + val outcome = result[0] + if (outcome == TransactionResult.NotEnoughSpace) { + full = true + break + } + if (!outcome.isOk()) { + break + } + moved += take + } + if (moved > 0) { + player.saveLootingBag() + } + if (full) { + mes(if (moved == 0) "Your looting bag is full." else "Your looting bag is now full.") + } + return moved > 0 + } + + /* Bank */ + + private fun showBankPanel(player: Player) { + player.attr[BANK_PANEL_ATTR] = true + player.ifSetHide(BANKSIDE_CONTAINER, false) + player.startInvTransmit(player.lootingBag) + player.ifSetEvents( + BANKSIDE_ITEMS, + 0 until LootingBags.CAPACITY, + IfEvent.Op1, + IfEvent.Op2, + IfEvent.Op3, + IfEvent.Op4, + IfEvent.Op5, + IfEvent.Op6, + IfEvent.Op7, + IfEvent.Op10, + ) + } + + private fun Player.dismissBankPanel() { + if (!onBankClose()) { + return + } + ifSetHide(BANKSIDE_CONTAINER, true) + } + + /** @return `true` if the panel was showing. */ + private fun Player.onBankClose(): Boolean { + if (attr[BANK_PANEL_ATTR] != true) { + return false + } + attr.remove(BANK_PANEL_ATTR) + stopInvTransmit(lootingBag) + return true + } + + /** Same op layout as the bankside inventory: left-click quantity, 1, 5, 10, last X, X, All. */ + private suspend fun ProtectedAccess.bankPanelOp(slot: Int, op: IfButtonOp) { + val bag = player.lootingBag + if (op == IfButtonOp.Op10) { + objExamine(bag, slot) + return + } + if (bag[slot] == null) { + return + } + val count = + when (op) { + IfButtonOp.Op1 -> bankLeftClickQuantity() + IfButtonOp.Op2 -> 1 + IfButtonOp.Op3 -> 5 + IfButtonOp.Op4 -> 10 + IfButtonOp.Op5 -> maxOf(1, lastQtyInput) + IfButtonOp.Op6 -> { + val input = countDialog() + if (input <= 0) { + return + } + lastQtyInput = input + input + } + IfButtonOp.Op7 -> Int.MAX_VALUE + else -> return + } + bankFromBag(slot, count) + } + + private fun ProtectedAccess.bankLeftClickQuantity(): Int = + when (leftClickQtyMode) { + QuantityMode.One -> 1 + QuantityMode.Five -> 5 + QuantityMode.Ten -> 10 + QuantityMode.X -> maxOf(1, lastQtyInput) + QuantityMode.All -> Int.MAX_VALUE + } + + private fun ProtectedAccess.bankFromBag(slot: Int, count: Int): Boolean { + val bag = player.lootingBag + val deposited = with(bankInv) { invDeposit(slot, count, bag) } + if (deposited) { + invCompress(bag) + player.saveLootingBag() + } + return deposited + } + + private fun ProtectedAccess.bankAll() { + val bag = player.lootingBag + if (bag.isEmpty()) { + mes("The bag is empty.") + return + } + for (slot in bag.indices.reversed()) { + if (bag[slot] == null) { + continue + } + with(bankInv) { invDeposit(slot, Int.MAX_VALUE, bag) } + } + invCompress(bag) + player.saveLootingBag() + } + + /* Destroy */ + + /** Destroying the bag in the Wilderness spills its contents; anywhere else they are lost. */ + private fun Player.destroyBag() { + val bag = lootingBag + if (bag.isEmpty()) { + return + } + if (insideWilderness) { + for (obj in bag.objs) { + if (obj == null) { + continue + } + objRepo.add(Obj.fromOwner(this, coords, obj), SPILL_DURATION) + } + } + invClear(bag) + saveLootingBag() + } + + private fun ProtectedAccess.bagValue(): Long = + player.lootingBag.objs.sumOf { obj -> + if (obj == null) { + 0L + } else { + val type = getInvObj(obj) + (marketPrices[type] ?: type.cost).toLong() * obj.count + } + } + + private enum class BagView { + Check, + Deposit, + } + + private companion object { + private const val INTERFACE = "interface.wilderness_lootingbag" + private const val COMPONENT_ITEMS = "component.wilderness_lootingbag:items" + private const val COMPONENT_TOTAL = "component.wilderness_lootingbag:total" + + private const val BANKSIDE_CONTAINER = "component.bankside:lootingbag_container" + private const val BANKSIDE_ITEMS = "component.bankside:lootingbag_items" + private const val BANKSIDE_BANK_ALL = "component.bankside:lootingbag_bankall" + private const val BANKSIDE_DISMISS = "component.bankside:lootingbag_dismiss" + + /** `[clientscript,wilderness_lootingbag_setup](int mode, string title)`. */ + private const val SETUP_SCRIPT = 495 + private const val MODE_BAG_CONTENTS = 1 + private const val MODE_INVENTORY = 0 + private const val TITLE = "Looting bag" + + private const val MSG_WILDERNESS_ONLY = + "You can only put items in the looting bag while you're in the Wilderness." + + /** Ticks the spilled contents of a destroyed bag stay on the floor. */ + private const val SPILL_DURATION = 300 + + private val VIEW_ATTR = AttributeKey() + private val BANK_PANEL_ATTR = AttributeKey() + } +} diff --git a/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBagTakeHook.kt b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBagTakeHook.kt new file mode 100644 index 000000000..a6511e9fc --- /dev/null +++ b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBagTakeHook.kt @@ -0,0 +1,38 @@ +package org.rsmod.content.other.pouches.lootingbag + +import dev.openrune.types.ItemServerType +import dev.openrune.types.util.UncheckedType +import jakarta.inject.Inject +import org.rsmod.api.invtx.invAdd +import org.rsmod.api.player.hook.PlayerObjTakeRedirectHook +import org.rsmod.game.entity.Player +import org.rsmod.game.inv.InvObj +import org.rsmod.game.obj.Obj + +/** + * With an open looting bag, tradeable loot picked up in the Wilderness goes straight into the bag. + * Anything the bag refuses, or no longer fits, takes the normal inventory route. + */ +@OptIn(UncheckedType::class) +class LootingBagTakeHook @Inject constructor() : PlayerObjTakeRedirectHook { + override fun redirects(player: Player, obj: Obj, objType: ItemServerType): Boolean { + if (!player.hasOpenLootingBag()) { + return false + } + if (player.vars["varbit.inside_wilderness"] != 1) { + return false + } + if (LootingBags.rejection(InvObj(obj.type, obj.count), objType) != null) { + return false + } + return player.invAdd(player.lootingBag, obj.type, obj.count, autoCommit = false).success + } + + override fun take(player: Player, obj: Obj, objType: ItemServerType): Boolean { + val result = player.invAdd(player.lootingBag, obj.type, obj.count) + if (result.success) { + player.saveLootingBag() + } + return result.success + } +} diff --git a/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBags.kt b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBags.kt new file mode 100644 index 000000000..e6b156872 --- /dev/null +++ b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBags.kt @@ -0,0 +1,95 @@ +package org.rsmod.content.other.pouches.lootingbag + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import dev.openrune.types.util.UncheckedType +import org.rsmod.api.attr.AttributeKey +import org.rsmod.game.entity.Player +import org.rsmod.game.inv.InvObj +import org.rsmod.game.inv.Inventory + +object LootingBags { + const val CLOSED = "obj.looting_bag" + const val OPEN = "obj.looting_bag_open" + const val INV = "inv.looting_bag" + const val CAPACITY = 28 + + val ALL = listOf(CLOSED, OPEN) + + val ids: Set by lazy { ALL.map { it.asRSCM(RSCMType.OBJ) }.toSet() } + val openId: Int by lazy { OPEN.asRSCM(RSCMType.OBJ) } + private val coinsId: Int by lazy { "obj.coins".asRSCM(RSCMType.OBJ) } + + /** Why [obj] cannot go into the bag, or `null` when it can. */ + fun rejection(obj: InvObj, type: ItemServerType): String? = + when { + obj.id in ids -> "You may be surprised to learn that bagception is not permitted." + !type.tradeable || obj.id == coinsId -> "Only tradeable items can be put in the bag." + else -> null + } +} + +/** + * The bag contents live in the cache's `inv.looting_bag`, which is a temporary inv, so they are + * mirrored into this persistent attribute (flat `[obj id, count, obj id, count, ...]`) whenever + * they change and restored on login. + */ +val LOOTING_BAG_ATTR: AttributeKey> = AttributeKey(persistenceKey = "looting_bag") + +val Player.lootingBag: Inventory + get() = invMap.getOrPut(LootingBags.INV) + +fun Player.hasLootingBag(): Boolean = inv.any { it != null && it.id in LootingBags.ids } + +fun Player.hasOpenLootingBag(): Boolean = inv.any { it != null && it.id == LootingBags.openId } + +fun Player.saveLootingBag() { + val encoded = LootingBagCodec.encode(lootingBag.objs) + if (encoded.isEmpty()) { + attr.remove(LOOTING_BAG_ATTR) + } else { + attr[LOOTING_BAG_ATTR] = encoded + } +} + +fun Player.restoreLootingBag() { + val saved = attr[LOOTING_BAG_ATTR] ?: return + val bag = lootingBag + val decoded = LootingBagCodec.decode(saved, bag.size) + for (slot in bag.indices) { + bag[slot] = decoded[slot] + } +} + +object LootingBagCodec { + fun encode(objs: Array): MutableList { + val encoded = ArrayList() + for (obj in objs) { + if (obj == null) { + continue + } + encoded.add(obj.id) + encoded.add(obj.count) + } + return encoded + } + + /** Fills the slots in order, skipping malformed pairs; anything past [size] is dropped. */ + @OptIn(UncheckedType::class) + fun decode(encoded: List, size: Int): Array { + val objs = arrayOfNulls(size) + var slot = 0 + var index = 0 + while (index + 1 < encoded.size && slot < size) { + val id = encoded[index] + val count = encoded[index + 1] + index += 2 + if (id <= 0 || count <= 0) { + continue + } + objs[slot++] = InvObj(id, count) + } + return objs + } +} diff --git a/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouchContents.kt b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouchContents.kt new file mode 100644 index 000000000..d2e66de68 --- /dev/null +++ b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouchContents.kt @@ -0,0 +1,122 @@ +package org.rsmod.content.other.pouches.runepouch + +/** + * The runes a pouch holds: one compact rune id (see `enum.rune_compact_ids`, `0` = empty) and a + * count per slot. A regular pouch has three slots, a divine pouch four. + * + * This is a plain value so the storage rules can be tested without a player; the script reads and + * writes it through the `rune_pouch_type_n` / `rune_pouch_quantity_n` varbits. + */ +class RunePouchContents(val types: IntArray, val counts: IntArray) { + init { + require(types.size == counts.size) { "types and counts must be the same size" } + } + + val slots: Int + get() = types.size + + fun isEmpty(): Boolean = counts.all { it == 0 } + + /** The slot already holding [compactId], or `-1`. */ + fun slotOf(compactId: Int): Int = + types.indices.firstOrNull { types[it] == compactId && counts[it] > 0 } ?: -1 + + fun freeSlot(): Int = types.indices.firstOrNull { counts[it] == 0 } ?: -1 + + /** + * Stores up to [amount] of [compactId], topping up an existing stack or taking a free slot. + * + * @return the amount actually stored; `0` when the pouch has no room for that rune. + */ + fun add(compactId: Int, amount: Int): Int { + if (amount <= 0) { + return 0 + } + var slot = slotOf(compactId) + if (slot == -1) { + slot = freeSlot() + } + if (slot == -1) { + return 0 + } + val stored = minOf(MAX_STACK - counts[slot], amount) + if (stored <= 0) { + return 0 + } + types[slot] = compactId + counts[slot] += stored + return stored + } + + /** Takes up to [amount] out of [slot], clearing the slot type once it is empty. */ + fun remove(slot: Int, amount: Int): Int { + val removed = minOf(counts[slot], amount).coerceAtLeast(0) + counts[slot] -= removed + if (counts[slot] == 0) { + types[slot] = 0 + } + return removed + } + + fun clear() { + types.fill(0) + counts.fill(0) + } + + companion object { + /** Each rune type caps at 16,000 in both the regular and divine pouch. */ + const val MAX_STACK: Int = 16_000 + + fun empty(slots: Int): RunePouchContents = RunePouchContents(IntArray(slots), IntArray(slots)) + } +} + +/** What a store/withdraw op on a pouch or inventory slot resolves to. */ +sealed class PouchAmount { + data class Fixed(val count: Int) : PouchAmount() + + data object All : PouchAmount() + + /** Ask the player for a number. */ + data object Prompt : PouchAmount() +} + +/** + * The quantity buttons of the rune pouch interface and how the client labels the slot ops for + * each selection (`rune_pouch_drawinventory_slot` / `rune_pouch_drawpouch_slot`). + * + * Op1 is always the selected quantity; ops 2-6 are fixed to 1, 5, the saved custom amount, an X + * prompt and All. Op10 is Examine. + */ +object RunePouchQuantity { + const val MODE_ONE: Int = 0 + const val MODE_FIVE: Int = 1 + const val MODE_X: Int = 2 + const val MODE_ALL: Int = 3 + + /** `rune_pouch_customquantity` is a 14-bit varbit. */ + const val MAX_CUSTOM: Int = 16_383 + + const val OP_EXAMINE: Int = 10 + + /** @param op the 1-based op the client sent; [mode] and [custom] are the two quantity varbits. */ + fun resolve(op: Int, mode: Int, custom: Int): PouchAmount? = + when (op) { + 1 -> + when (mode) { + MODE_FIVE -> PouchAmount.Fixed(5) + MODE_X -> customOrPrompt(custom) + MODE_ALL -> PouchAmount.All + else -> PouchAmount.Fixed(1) + } + 2 -> PouchAmount.Fixed(1) + 3 -> PouchAmount.Fixed(5) + 4 -> customOrPrompt(custom) + 5 -> PouchAmount.Prompt + 6 -> PouchAmount.All + else -> null + } + + private fun customOrPrompt(custom: Int): PouchAmount = + if (custom > 0) PouchAmount.Fixed(custom) else PouchAmount.Prompt +} diff --git a/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouchScript.kt b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouchScript.kt new file mode 100644 index 000000000..648503b6d --- /dev/null +++ b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouchScript.kt @@ -0,0 +1,304 @@ +package org.rsmod.content.other.pouches.runepouch + +import dev.openrune.definition.type.widget.IfEvent +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.aconverted.interf.IfButtonOp +import dev.openrune.types.util.UncheckedType +import jakarta.inject.Inject +import org.rsmod.api.invtx.invAdd +import org.rsmod.api.player.events.interact.HeldBanksideEvents +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.protect.ProtectedAccessLauncher +import org.rsmod.api.player.vars.intVarBit +import org.rsmod.api.repo.obj.ObjRepository +import org.rsmod.api.script.advanced.onDestroyHeld +import org.rsmod.api.script.onEvent +import org.rsmod.api.script.onIfModalButton +import org.rsmod.api.script.onOpHeld1 +import org.rsmod.api.script.onOpHeld3 +import org.rsmod.api.script.onOpHeld4 +import org.rsmod.game.entity.Player +import org.rsmod.game.inv.InvObj +import org.rsmod.game.obj.Obj +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The rune pouch and divine rune pouch. + * + * Runes live in the `rune_pouch_type_n` / `rune_pouch_quantity_n` varbits (three slots, four for + * the divine pouch), which is what the spell system already reads when casting. The `rune_pouch` + * interface draws itself from those varbits and the player inventory through its own cs2, so the + * server only opens it, enables the slot ops and moves runes when a slot is clicked. + */ +@OptIn(UncheckedType::class) +class RunePouchScript +@Inject +constructor(private val objRepo: ObjRepository, private val protectedAccess: ProtectedAccessLauncher) : + PluginScript() { + private var Player.quantityMode by intVarBit("varbit.rune_pouch_selectedquantity") + private var Player.customQuantity by intVarBit("varbit.rune_pouch_customquantity") + + override fun ScriptContext.startup() { + for (pouch in RunePouches.ALL) { + onOpHeld1(pouch) { openPouch() } + onOpHeld4(pouch) { emptyPouch() } + onEvent(pouch.asRSCM(RSCMType.OBJ)) { banksideOp(player, op) } + } + onOpHeld3(RunePouches.DIVINE) { revertDivine(it.slot) } + + onIfModalButton(COMPONENT_POUCH) { pouchOp(it.comsub, it.op) } + onIfModalButton(COMPONENT_INVENTORY) { inventoryOp(it.comsub, it.op) } + onIfModalButton(COMPONENT_QUANTITY_1) { player.quantityMode = RunePouchQuantity.MODE_ONE } + onIfModalButton(COMPONENT_QUANTITY_5) { player.quantityMode = RunePouchQuantity.MODE_FIVE } + onIfModalButton(COMPONENT_QUANTITY_X) { selectCustomQuantity(it.op) } + onIfModalButton(COMPONENT_QUANTITY_ALL) { player.quantityMode = RunePouchQuantity.MODE_ALL } + + onDestroyHeld { + if (type in RunePouches.ALL) { + spillRunes(player) + } + } + } + + private fun ProtectedAccess.openPouch() { + ifOpenMainModal(INTERFACE) + ifSetEvents(COMPONENT_POUCH, 0 until RunePouches.DIVINE_SLOTS, *SLOT_EVENTS) + ifSetEvents(COMPONENT_INVENTORY, inv.indices, *SLOT_EVENTS) + } + + /** A rune in the inventory panel: store it. Non-runes only carry a `Store` op that refuses. */ + private suspend fun ProtectedAccess.inventoryOp(slot: Int, op: IfButtonOp) { + if (op == IfButtonOp.Op10) { + objExamine(inv, slot) + return + } + val obj = inv[slot] ?: return + val slots = player.runePouchSlots() + if (slots == null) { + ifClose() + return + } + val rune = RunePouchRunes.byObj(obj.id) + if (rune == null) { + val message = + if (obj.id in RunePouches.ids) "Don't be silly." else "You can only store runes in the pouch." + mes(message) + return + } + val amount = resolveAmount(op) ?: return + val current = inv[slot] + if (current == null || current.id != obj.id) { + return + } + val contents = player.readRunePouch(slots) + val carried = inv.objs.sumOf { if (it != null && it.id == obj.id) it.count else 0 } + val stored = contents.add(rune.compactId, minOf(amount, carried)) + if (stored == 0) { + val message = + if (contents.slotOf(rune.compactId) != -1) { + "Your pouch can't hold any more of that rune." + } else { + "Your rune pouch is full." + } + mes(message) + return + } + val removed = invDel(inv, rune.name, count = stored) + if (removed.success) { + player.writeRunePouch(contents) + } + } + + /** A rune in the pouch panel: withdraw it. */ + private suspend fun ProtectedAccess.pouchOp(slot: Int, op: IfButtonOp) { + val slots = player.runePouchSlots() + if (slots == null) { + ifClose() + return + } + if (slot !in 0 until slots) { + return + } + val contents = player.readRunePouch(slots) + val rune = RunePouchRunes.byCompact(contents.types[slot]) ?: return + if (contents.counts[slot] == 0) { + return + } + if (op == IfButtonOp.Op10) { + mes(rune.type.examine) + return + } + val amount = resolveAmount(op) ?: return + val latest = player.readRunePouch(slots) + if (latest.types[slot] != rune.compactId || latest.counts[slot] == 0) { + return + } + val take = minOf(amount, latest.counts[slot]) + val added = invAdd(inv, rune.name, count = take) + if (added.failure) { + mes("You don't have enough inventory space.") + return + } + latest.remove(slot, take) + player.writeRunePouch(latest) + } + + private suspend fun ProtectedAccess.resolveAmount(op: IfButtonOp): Int? = + when (val amount = RunePouchQuantity.resolve(op.ordinal + 1, player.quantityMode, player.customQuantity)) { + is PouchAmount.Fixed -> amount.count + PouchAmount.All -> Int.MAX_VALUE + PouchAmount.Prompt -> promptQuantity() + null -> null + } + + /** Asks for a number, which also becomes the saved custom quantity shown on the `X` ops. */ + private suspend fun ProtectedAccess.promptQuantity(): Int? { + val input = countDialog() + if (input <= 0) { + return null + } + player.customQuantity = input.coerceAtMost(RunePouchQuantity.MAX_CUSTOM) + return input + } + + /** Op1 selects the `X` quantity (asking for one if none is saved); op2 is "Set custom quantity". */ + private suspend fun ProtectedAccess.selectCustomQuantity(op: IfButtonOp) { + if (op == IfButtonOp.Op2 || player.customQuantity == 0) { + promptQuantity() ?: return + } + player.quantityMode = RunePouchQuantity.MODE_X + } + + private fun ProtectedAccess.emptyPouch() { + val slots = player.runePouchSlots() ?: return + val contents = player.readRunePouch(slots) + if (contents.isEmpty()) { + mes("Your rune pouch is empty.") + return + } + var partial = false + for (slot in 0 until slots) { + val count = contents.counts[slot] + if (count == 0) { + continue + } + val rune = RunePouchRunes.byCompact(contents.types[slot]) ?: continue + val added = invAdd(inv, rune.name, count = count) + if (added.success) { + contents.remove(slot, count) + } else { + partial = true + } + } + player.writeRunePouch(contents) + if (partial) { + mes("You don't have enough inventory space to empty the whole pouch.") + } + } + + /** + * The bank draws `Empty` (op9) and `Configure` (op1) on a pouch in the bankside inventory. + * Emptying puts the runes straight into the bank; configuring opens the pouch itself, which + * replaces the bank modal. + */ + private fun banksideOp(player: Player, op: IfButtonOp) { + if (op == IfButtonOp.Op1) { + protectedAccess.launch(player) { openPouch() } + return + } + val slots = player.runePouchSlots() ?: return + val contents = player.readRunePouch(slots) + if (contents.isEmpty()) { + player.mes("Your rune pouch is empty.") + return + } + val bank = player.invMap.getOrPut("inv.bank") + for (slot in 0 until slots) { + val count = contents.counts[slot] + if (count == 0) { + continue + } + val rune = RunePouchRunes.byCompact(contents.types[slot]) ?: continue + val added = player.invAdd(bank, rune.name, count) + if (added.success) { + contents.remove(slot, count) + } + } + player.writeRunePouch(contents) + player.mes("You empty the runes from your pouch into your bank.") + } + + /** Reverting a divine pouch turns it back into a rune pouch; the fourth slot must be empty first. */ + private suspend fun ProtectedAccess.revertDivine(slot: Int) { + val obj = inv[slot] ?: return + val contents = player.readRunePouch(RunePouches.DIVINE_SLOTS) + if (contents.counts[RunePouches.DIVINE_SLOTS - 1] > 0) { + mes("You need to empty the fourth rune slot before reverting the pouch.") + return + } + val confirm = + choice2( + "Yes, revert it to a rune pouch.", + true, + "No, keep it as it is.", + false, + title = "Revert the divine rune pouch?", + ) + if (!confirm) { + return + } + val current = inv[slot] + if (current == null || current.id != obj.id) { + return + } + val removed = invDel(inv, RunePouches.DIVINE, slot = slot) + if (removed.failure) { + return + } + invAdd(inv, RunePouches.REGULAR, slot = slot) + mes("You revert the divine rune pouch into a regular rune pouch.") + } + + /** "If you destroy the pouch, all the contents will fall out." */ + private fun spillRunes(player: Player) { + val contents = player.readRunePouch(RunePouches.DIVINE_SLOTS) + for (slot in 0 until contents.slots) { + val count = contents.counts[slot] + if (count == 0) { + continue + } + val rune = RunePouchRunes.byCompact(contents.types[slot]) ?: continue + val obj = Obj.fromOwner(player, player.coords, InvObj(rune.id, count)) + objRepo.add(obj, SPILL_DURATION) + } + contents.clear() + player.writeRunePouch(contents) + } + + private companion object { + private const val INTERFACE = "interface.rune_pouch" + private const val COMPONENT_POUCH = "component.rune_pouch:pouch" + private const val COMPONENT_INVENTORY = "component.rune_pouch:inventory" + private const val COMPONENT_QUANTITY_1 = "component.rune_pouch:1" + private const val COMPONENT_QUANTITY_5 = "component.rune_pouch:5" + private const val COMPONENT_QUANTITY_X = "component.rune_pouch:x" + private const val COMPONENT_QUANTITY_ALL = "component.rune_pouch:all" + + /** Ticks the spilled runes of a destroyed pouch stay on the floor. */ + private const val SPILL_DURATION = 300 + + private val SLOT_EVENTS = + arrayOf( + IfEvent.Op1, + IfEvent.Op2, + IfEvent.Op3, + IfEvent.Op4, + IfEvent.Op5, + IfEvent.Op6, + IfEvent.Op10, + ) + } +} diff --git a/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouches.kt b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouches.kt new file mode 100644 index 000000000..389f823ac --- /dev/null +++ b/content/other/pouches/src/main/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouches.kt @@ -0,0 +1,81 @@ +package org.rsmod.content.other.pouches.runepouch + +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import org.rsmod.api.enums.RuneEnums.rune_compact_ids +import org.rsmod.api.player.vars.VarPlayerIntMapSetter +import org.rsmod.game.entity.Player + +object RunePouches { + const val REGULAR = "obj.bh_rune_pouch" + const val REGULAR_LOCKED = "obj.bh_rune_pouch_trouver" + const val DIVINE = "obj.divine_rune_pouch" + const val DIVINE_LOCKED = "obj.divine_rune_pouch_trouver" + + val ALL = listOf(REGULAR, REGULAR_LOCKED, DIVINE, DIVINE_LOCKED) + + const val REGULAR_SLOTS = 3 + const val DIVINE_SLOTS = 4 + + val ids: Set by lazy { ALL.map { it.asRSCM(RSCMType.OBJ) }.toSet() } + val divineIds: Set by lazy { + setOf(DIVINE.asRSCM(RSCMType.OBJ), DIVINE_LOCKED.asRSCM(RSCMType.OBJ)) + } + + val TYPE_VARBITS = + listOf( + "varbit.rune_pouch_type_1", + "varbit.rune_pouch_type_2", + "varbit.rune_pouch_type_3", + "varbit.rune_pouch_type_4", + ) + + val COUNT_VARBITS = + listOf( + "varbit.rune_pouch_quantity_1", + "varbit.rune_pouch_quantity_2", + "varbit.rune_pouch_quantity_3", + "varbit.rune_pouch_quantity_4", + ) +} + +/** The runes a pouch accepts, keyed both ways between obj id and compact id. */ +object RunePouchRunes { + class Rune(val id: Int, val name: String, val compactId: Int, val type: ItemServerType) + + private val runes: List by lazy { + rune_compact_ids.backing.mapNotNull { (type, compact) -> + compact?.let { Rune(type.id, RSCM.getReverseMapping(RSCMType.OBJ, type.id), it, type) } + } + } + + private val byObjId: Map by lazy { runes.associateBy { it.id } } + private val byCompactId: Map by lazy { runes.associateBy { it.compactId } } + + fun byObj(objId: Int): Rune? = byObjId[objId] + + fun byCompact(compactId: Int): Rune? = byCompactId[compactId] +} + +/** Slots of the pouch the player carries, or `null` without one. A divine pouch wins if both. */ +fun Player.runePouchSlots(): Int? = + when { + inv.any { it != null && it.id in RunePouches.divineIds } -> RunePouches.DIVINE_SLOTS + inv.any { it != null && it.id in RunePouches.ids } -> RunePouches.REGULAR_SLOTS + else -> null + } + +fun Player.readRunePouch(slots: Int): RunePouchContents = + RunePouchContents( + types = IntArray(slots) { vars[RunePouches.TYPE_VARBITS[it]] }, + counts = IntArray(slots) { vars[RunePouches.COUNT_VARBITS[it]] }, + ) + +fun Player.writeRunePouch(contents: RunePouchContents) { + for (slot in 0 until contents.slots) { + VarPlayerIntMapSetter.set(this, RunePouches.TYPE_VARBITS[slot], contents.types[slot]) + VarPlayerIntMapSetter.set(this, RunePouches.COUNT_VARBITS[slot], contents.counts[slot]) + } +} diff --git a/content/other/pouches/src/test/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBagCodecTest.kt b/content/other/pouches/src/test/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBagCodecTest.kt new file mode 100644 index 000000000..7330e24b0 --- /dev/null +++ b/content/other/pouches/src/test/kotlin/org/rsmod/content/other/pouches/lootingbag/LootingBagCodecTest.kt @@ -0,0 +1,44 @@ +package org.rsmod.content.other.pouches.lootingbag + +import dev.openrune.types.util.UncheckedType +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.rsmod.game.inv.InvObj + +@OptIn(UncheckedType::class) +class LootingBagCodecTest { + @Test + fun `contents survive an encode and decode round trip`() { + val objs = arrayOfNulls(28) + objs[0] = InvObj(995, 12_345) + objs[1] = InvObj(4151, 1) + objs[5] = InvObj(560, 300) + + val encoded = LootingBagCodec.encode(objs) + val decoded = LootingBagCodec.decode(encoded, 28) + + assertEquals(listOf(995, 12_345, 4151, 1, 560, 300), encoded) + assertEquals(InvObj(995, 12_345), decoded[0]) + assertEquals(InvObj(4151, 1), decoded[1]) + // Gaps are compacted away: the bag never has holes after a bank withdrawal either. + assertEquals(InvObj(560, 300), decoded[2]) + assertNull(decoded[3]) + } + + @Test + fun `an empty bag encodes to nothing`() { + val decoded = LootingBagCodec.decode(LootingBagCodec.encode(arrayOfNulls(28)), 28) + assertArrayEquals(arrayOfNulls(28), decoded) + } + + @Test + fun `malformed pairs and overflow are ignored`() { + val encoded = listOf(0, 5, 4151, 0, 4151, 1, 4151, 1, 7) + val decoded = LootingBagCodec.decode(encoded, 1) + + assertEquals(InvObj(4151, 1), decoded[0]) + assertEquals(1, decoded.size) + } +} diff --git a/content/other/pouches/src/test/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouchContentsTest.kt b/content/other/pouches/src/test/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouchContentsTest.kt new file mode 100644 index 000000000..046e77dd2 --- /dev/null +++ b/content/other/pouches/src/test/kotlin/org/rsmod/content/other/pouches/runepouch/RunePouchContentsTest.kt @@ -0,0 +1,106 @@ +package org.rsmod.content.other.pouches.runepouch + +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class RunePouchContentsTest { + @Test + fun `a new rune takes the first free slot`() { + val pouch = RunePouchContents.empty(3) + + assertEquals(100, pouch.add(AIR, 100)) + assertEquals(50, pouch.add(FIRE, 50)) + + assertArrayEquals(intArrayOf(AIR, FIRE, 0), pouch.types) + assertArrayEquals(intArrayOf(100, 50, 0), pouch.counts) + } + + @Test + fun `an existing stack is topped up instead of using a new slot`() { + val pouch = RunePouchContents.empty(3) + pouch.add(AIR, 100) + + assertEquals(25, pouch.add(AIR, 25)) + + assertEquals(0, pouch.slotOf(AIR)) + assertEquals(125, pouch.counts[0]) + assertEquals(1, pouch.freeSlot()) + } + + @Test + fun `stacks cap at the maximum and report what fit`() { + val pouch = RunePouchContents.empty(3) + pouch.add(AIR, RunePouchContents.MAX_STACK - 10) + + assertEquals(10, pouch.add(AIR, 500)) + assertEquals(RunePouchContents.MAX_STACK, pouch.counts[0]) + assertEquals(0, pouch.add(AIR, 1)) + } + + @Test + fun `a full pouch refuses a fourth rune type`() { + val pouch = RunePouchContents.empty(3) + pouch.add(AIR, 1) + pouch.add(FIRE, 1) + pouch.add(WATER, 1) + + assertEquals(0, pouch.add(EARTH, 1)) + assertEquals(-1, pouch.freeSlot()) + } + + @Test + fun `the divine pouch has a fourth slot`() { + val pouch = RunePouchContents.empty(4) + pouch.add(AIR, 1) + pouch.add(FIRE, 1) + pouch.add(WATER, 1) + + assertEquals(1, pouch.add(EARTH, 1)) + assertEquals(3, pouch.slotOf(EARTH)) + } + + @Test + fun `removing the last rune frees the slot type`() { + val pouch = RunePouchContents.empty(3) + pouch.add(AIR, 10) + + assertEquals(4, pouch.remove(0, 4)) + assertEquals(AIR, pouch.types[0]) + assertEquals(6, pouch.remove(0, 100)) + assertEquals(0, pouch.types[0]) + assertTrue(pouch.isEmpty()) + } + + @Test + fun `op1 follows the selected quantity button`() { + assertEquals(PouchAmount.Fixed(1), RunePouchQuantity.resolve(1, RunePouchQuantity.MODE_ONE, 0)) + assertEquals(PouchAmount.Fixed(5), RunePouchQuantity.resolve(1, RunePouchQuantity.MODE_FIVE, 0)) + assertEquals(PouchAmount.Fixed(40), RunePouchQuantity.resolve(1, RunePouchQuantity.MODE_X, 40)) + assertEquals(PouchAmount.Prompt, RunePouchQuantity.resolve(1, RunePouchQuantity.MODE_X, 0)) + assertEquals(PouchAmount.All, RunePouchQuantity.resolve(1, RunePouchQuantity.MODE_ALL, 0)) + } + + @Test + fun `the remaining ops are fixed regardless of the selected button`() { + for (mode in RunePouchQuantity.MODE_ONE..RunePouchQuantity.MODE_ALL) { + assertEquals(PouchAmount.Fixed(1), RunePouchQuantity.resolve(2, mode, 7)) + assertEquals(PouchAmount.Fixed(5), RunePouchQuantity.resolve(3, mode, 7)) + assertEquals(PouchAmount.Fixed(7), RunePouchQuantity.resolve(4, mode, 7)) + assertEquals(PouchAmount.Prompt, RunePouchQuantity.resolve(4, mode, 0)) + assertEquals(PouchAmount.Prompt, RunePouchQuantity.resolve(5, mode, 7)) + assertEquals(PouchAmount.All, RunePouchQuantity.resolve(6, mode, 7)) + } + assertNull(RunePouchQuantity.resolve(RunePouchQuantity.OP_EXAMINE, RunePouchQuantity.MODE_ONE, 0)) + } + + private companion object { + /* Compact ids from `enum.rune_compact_ids`. */ + const val AIR = 1 + const val WATER = 2 + const val EARTH = 3 + const val FIRE = 4 + } +} diff --git a/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/SpecialAttackModule.kt b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/SpecialAttackModule.kt index bc142a500..28eebbfc7 100644 --- a/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/SpecialAttackModule.kt +++ b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/SpecialAttackModule.kt @@ -2,8 +2,14 @@ package org.rsmod.content.other.special.attacks import org.rsmod.api.specials.SpecialAttackMap import org.rsmod.content.other.special.attacks.boost.StatBoostSpecialAttacks +import org.rsmod.content.other.special.attacks.melee.AbyssalSpecialAttacks import org.rsmod.content.other.special.attacks.melee.DragonLongswordSpecialAttack +import org.rsmod.content.other.special.attacks.melee.DragonWeaponSpecialAttacks +import org.rsmod.content.other.special.attacks.melee.GodswordSpecialAttacks +import org.rsmod.content.other.special.attacks.melee.HammerSpecialAttacks +import org.rsmod.content.other.special.attacks.melee.MiscMeleeSpecialAttacks import org.rsmod.content.other.special.attacks.ranged.DarkBowSpecialAttack +import org.rsmod.content.other.special.attacks.ranged.RangedSpecialAttacks import org.rsmod.plugin.module.PluginModule class SpecialAttackModule : PluginModule() { @@ -11,5 +17,11 @@ class SpecialAttackModule : PluginModule() { addSetBinding(StatBoostSpecialAttacks::class.java) addSetBinding(DarkBowSpecialAttack::class.java) addSetBinding(DragonLongswordSpecialAttack::class.java) + addSetBinding(DragonWeaponSpecialAttacks::class.java) + addSetBinding(GodswordSpecialAttacks::class.java) + addSetBinding(AbyssalSpecialAttacks::class.java) + addSetBinding(HammerSpecialAttacks::class.java) + addSetBinding(MiscMeleeSpecialAttacks::class.java) + addSetBinding(RangedSpecialAttacks::class.java) } } diff --git a/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/TargetStats.kt b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/TargetStats.kt new file mode 100644 index 000000000..668adadb3 --- /dev/null +++ b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/TargetStats.kt @@ -0,0 +1,80 @@ +package org.rsmod.content.other.special.attacks + +import kotlin.math.max +import org.rsmod.api.player.stat.stat +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.player.stat.statSub +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PathingEntity +import org.rsmod.game.entity.Player + +/** + * Stat drains that work on either kind of combat target. Players go through the stat api so the + * client is updated; npcs have their current levels lowered directly, which is what the combat + * formulas read. + */ +internal object TargetStats { + const val ATTACK = "stat.attack" + const val STRENGTH = "stat.strength" + const val DEFENCE = "stat.defence" + const val RANGED = "stat.ranged" + const val MAGIC = "stat.magic" + const val PRAYER = "stat.prayer" + + fun current(target: PathingEntity, stat: String): Int = + when (target) { + is Player -> target.stat(stat) + is Npc -> + when (stat) { + ATTACK -> target.attackLvl + STRENGTH -> target.strengthLvl + DEFENCE -> target.defenceLvl + RANGED -> target.rangedLvl + MAGIC -> target.magicLvl + else -> 0 + } + } + + fun base(target: PathingEntity, stat: String): Int = + when (target) { + is Player -> target.statBase(stat) + is Npc -> + when (stat) { + ATTACK -> target.baseAttackLvl + STRENGTH -> target.baseStrengthLvl + DEFENCE -> target.baseDefenceLvl + RANGED -> target.baseRangedLvl + MAGIC -> target.baseMagicLvl + else -> 0 + } + } + + /** Lowers [stat] on [target] by a flat [amount], never below zero. Returns the amount drained. */ + fun drain(target: PathingEntity, stat: String, amount: Int): Int { + if (amount <= 0) { + return 0 + } + val before = current(target, stat) + val drained = amount.coerceAtMost(before) + when (target) { + is Player -> target.statSub(stat, constant = drained, percent = 0) + is Npc -> + when (stat) { + ATTACK -> target.attackLvl = max(0, target.attackLvl - drained) + STRENGTH -> target.strengthLvl = max(0, target.strengthLvl - drained) + DEFENCE -> target.defenceLvl = max(0, target.defenceLvl - drained) + RANGED -> target.rangedLvl = max(0, target.rangedLvl - drained) + MAGIC -> target.magicLvl = max(0, target.magicLvl - drained) + } + } + return drained + } + + /** Lowers [stat] on [target] by [percent] of its **current** level. Returns the amount drained. */ + fun drainPercentOfCurrent(target: PathingEntity, stat: String, percent: Int): Int = + drain(target, stat, (current(target, stat) * percent) / 100) + + /** Lowers [stat] on [target] by [percent] of its **base** level. Returns the amount drained. */ + fun drainPercentOfBase(target: PathingEntity, stat: String, percent: Int): Int = + drain(target, stat, (base(target, stat) * percent) / 100) +} diff --git a/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/AbyssalSpecialAttacks.kt b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/AbyssalSpecialAttacks.kt new file mode 100644 index 000000000..ce3bbbea3 --- /dev/null +++ b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/AbyssalSpecialAttacks.kt @@ -0,0 +1,184 @@ +package org.rsmod.content.other.special.attacks.melee + +import jakarta.inject.Inject +import kotlin.math.min +import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.api.combat.commons.CombatEffects +import org.rsmod.api.config.Constants +import org.rsmod.api.config.constants +import org.rsmod.api.mechanics.toxins.impl.PlayerPoison +import org.rsmod.api.player.output.UpdateRun +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.stat +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.api.specials.SpecialAttackManager +import org.rsmod.api.specials.SpecialAttackMap +import org.rsmod.api.specials.SpecialAttackRepository +import org.rsmod.api.specials.combat.MeleeSpecialAttack +import org.rsmod.content.other.special.attacks.TargetStats +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PathingEntity +import org.rsmod.game.entity.Player + +/** + * The abyssal weapon specials: Energy Drain (whip), Binding Tentacle (tentacle), Abyssal Puncture + * (dagger) and Penance (bludgeon). + */ +class AbyssalSpecialAttacks @Inject constructor(private val worldRepo: WorldRepository) : + SpecialAttackMap { + override fun SpecialAttackRepository.register(manager: SpecialAttackManager) { + val energyDrain = EnergyDrain(manager, worldRepo) + registerMelee("obj.abyssal_whip", energyDrain) + registerMelee("obj.abyssal_whip_lava", energyDrain) + registerMelee("obj.abyssal_whip_ice", energyDrain) + + registerMelee("obj.abyssal_tentacle", BindingTentacle(manager, worldRepo)) + + val abyssalPuncture = AbyssalPuncture(manager) + registerMelee("obj.abyssal_dagger", abyssalPuncture) + registerMelee("obj.abyssal_dagger_p", abyssalPuncture) + registerMelee("obj.abyssal_dagger_p+", abyssalPuncture) + registerMelee("obj.abyssal_dagger_p++", abyssalPuncture) + + registerMelee("obj.abyssal_bludgeon", Penance(manager)) + } + + /** + * Abyssal whip: a hit at +25% accuracy. Against a player it also transfers a tenth of their + * run energy to the attacker. + */ + private class EnergyDrain( + private val manager: SpecialAttackManager, + private val worldRepo: WorldRepository, + ) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee): Boolean { + lash(target, attack) + return true + } + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee): Boolean { + val damage = lash(target, attack) + if (damage > 0) { + val stolen = target.runEnergy / 10 + target.runEnergy -= stolen + UpdateRun.energy(target, target.runEnergy) + player.runEnergy = min(Constants.run_max_energy, player.runEnergy + stolen) + UpdateRun.energy(player, player.runEnergy) + } + return true + } + + private fun ProtectedAccess.lash(target: PathingEntity, attack: CombatAttack.Melee): Int { + anim("seq.slayer_whip_sp_attack") + target.spotanim("spotanim.sp_attack_abyssal_whip", height = 96) + worldRepo.soundArea(player, WHIP_SOUND, radius = SOUND_RADIUS) + val damage = manager.rollMeleeDamage(this, target, attack, 1.25, 1.0) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + manager.continueCombat(this, target) + return damage + } + } + + /** + * Abyssal tentacle: a hit at +25% accuracy that freezes a player target for five seconds and + * has an even chance of poisoning them whether or not it lands. + */ + private class BindingTentacle( + private val manager: SpecialAttackManager, + private val worldRepo: WorldRepository, + ) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee): Boolean { + lash(target, attack) + return true + } + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee): Boolean { + val damage = lash(target, attack) + if (damage > 0) { + CombatEffects.freeze(target, TENTACLE_BIND_TICKS) + } + if (random.of(2) == 0) { + PlayerPoison.tryPoison(target, initialDamage = TENTACLE_POISON_DAMAGE) + } + return true + } + + private fun ProtectedAccess.lash(target: PathingEntity, attack: CombatAttack.Melee): Int { + anim("seq.slayer_whip_sp_attack") + target.spotanim("spotanim.sp_attack_abyssal_whip", height = 96) + worldRepo.soundArea(player, WHIP_SOUND, radius = SOUND_RADIUS) + val damage = manager.rollMeleeDamage(this, target, attack, 1.25, 1.0) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + manager.continueCombat(this, target) + return damage + } + } + + /** Abyssal dagger: two hits at +25% accuracy and -15% damage that both land or both miss. */ + private class AbyssalPuncture(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + puncture(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + puncture(target, attack) + + private fun ProtectedAccess.puncture( + target: PathingEntity, + attack: CombatAttack.Melee, + ): Boolean { + anim("seq.abyssal_dagger_special") + spotanim( + "spotanim.abyssal_dagger_special_spotanim", + height = 96, + slot = constants.spotanim_slot_combat, + ) + val landed = + manager.rollMeleeAccuracy(this, target, attack.type, attack.style, attack.type, 1.25) + val first = + if (landed) manager.rollMeleeMaxHit(this, target, attack.type, attack.style, 0.85) else 0 + val second = + if (landed) manager.rollMeleeMaxHit(this, target, attack.type, attack.style, 0.85) else 0 + manager.giveCombatXp(this, target, attack, first + second) + manager.queueMeleeHit(this, target, first) + manager.queueMeleeHit(this, target, second) + manager.continueCombat(this, target) + return true + } + } + + /** Abyssal bludgeon: +0.5% damage for every prayer point the player is missing. */ + private class Penance(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + penance(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + penance(target, attack) + + private fun ProtectedAccess.penance(target: PathingEntity, attack: CombatAttack.Melee): Boolean { + anim("seq.abyssal_bludgeon_special_attack") + spotanim( + "spotanim.abyssal_miasma_spotanim_bludgeon", + height = 96, + slot = constants.spotanim_slot_combat, + ) + val missing = player.statBase(TargetStats.PRAYER) - player.stat(TargetStats.PRAYER) + val multiplier = 1.0 + (missing.coerceAtLeast(0) * PENANCE_PER_POINT) + val damage = manager.rollMeleeDamage(this, target, attack, 1.0, multiplier) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + manager.continueCombat(this, target) + return true + } + } + + private companion object { + private const val SOUND_RADIUS = 10 + private const val WHIP_SOUND = 2713 + private const val TENTACLE_BIND_TICKS = 8 + private const val TENTACLE_POISON_DAMAGE = 4 + private const val PENANCE_PER_POINT = 0.005 + } +} diff --git a/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/DragonWeaponSpecialAttacks.kt b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/DragonWeaponSpecialAttacks.kt new file mode 100644 index 000000000..37e2ef61b --- /dev/null +++ b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/DragonWeaponSpecialAttacks.kt @@ -0,0 +1,399 @@ +package org.rsmod.content.other.special.attacks.melee + +import dev.openrune.types.hunt.HuntVis +import jakarta.inject.Inject +import kotlin.math.max +import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.api.combat.commons.CombatEffects +import org.rsmod.api.combat.commons.types.MeleeAttackType +import org.rsmod.api.config.constants +import org.rsmod.api.hunt.NpcSearch +import org.rsmod.api.npc.isValidTarget +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.api.specials.SpecialAttackManager +import org.rsmod.api.specials.SpecialAttackMap +import org.rsmod.api.specials.SpecialAttackRepository +import org.rsmod.api.specials.combat.MeleeSpecialAttack +import org.rsmod.content.other.special.attacks.TargetStats +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PathingEntity +import org.rsmod.game.entity.Player + +/** + * The dragon melee weapon specials: Puncture (dagger), Shatter (mace), Sever (scimitar), Wild + * Stab (sword), Powerstab (2h sword), Sweep (halberd), Shove (spear, Zamorakian spear and hasta), + * Rampage (battleaxe) and Slice and Dice (claws). + */ +class DragonWeaponSpecialAttacks +@Inject +constructor(private val worldRepo: WorldRepository, private val npcSearch: NpcSearch) : + SpecialAttackMap { + override fun SpecialAttackRepository.register(manager: SpecialAttackManager) { + val puncture = Puncture(manager, worldRepo) + registerMelee("obj.dragon_dagger", puncture) + registerMelee("obj.dragon_dagger_p", puncture) + registerMelee("obj.dragon_dagger_p+", puncture) + registerMelee("obj.dragon_dagger_p++", puncture) + + registerMelee("obj.dragon_mace", Shatter(manager)) + + val sever = Sever(manager, worldRepo) + registerMelee("obj.dragon_scimitar", sever) + registerMelee("obj.dragon_scimitar_ornament", sever) + + registerMelee("obj.dragon_shortsword", WildStab(manager)) + registerMelee("obj.dragon_2h_sword", Powerstab(manager, npcSearch)) + registerMelee("obj.dragon_halberd", Sweep(manager)) + + val shove = Shove(manager, "seq.shove") + registerMelee("obj.dragon_spear", shove) + registerMelee("obj.dragon_spear_p", shove) + registerMelee("obj.dragon_spear_p+", shove) + registerMelee("obj.dragon_spear_p++", shove) + registerMelee("obj.zamorak_spear", shove) + registerMelee("obj.zamorak_hasta", Shove(manager, "seq.shove_1h")) + + registerInstant("obj.dragon_battleaxe") { rampage(worldRepo) } + + val sliceAndDice = SliceAndDice(manager) + registerMelee("obj.dragon_claws", sliceAndDice) + registerMelee("obj.dragon_claws_ornament", sliceAndDice) + } + + /** Dragon dagger: two quick stabs at +15% accuracy and +15% damage. */ + private class Puncture( + private val manager: SpecialAttackManager, + private val worldRepo: WorldRepository, + ) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + puncture(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + puncture(target, attack) + + private fun ProtectedAccess.puncture( + target: PathingEntity, + attack: CombatAttack.Melee, + ): Boolean { + anim("seq.puncture") + spotanim("spotanim.sp_attack_puncture_spotanim", height = 96, slot = COMBAT_SLOT) + worldRepo.soundArea(player, PUNCTURE_SOUND, radius = SOUND_RADIUS) + + val first = manager.rollMeleeDamage(this, target, attack, 1.15, 1.15) + val second = manager.rollMeleeDamage(this, target, attack, 1.15, 1.15) + manager.giveCombatXp(this, target, attack, first + second) + manager.queueMeleeHit(this, target, first) + manager.queueMeleeHit(this, target, second) + manager.continueCombat(this, target) + return true + } + } + + /** Dragon mace: one hit at +25% accuracy and +50% damage. */ + private class Shatter(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + shatter(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + shatter(target, attack) + + private fun ProtectedAccess.shatter( + target: PathingEntity, + attack: CombatAttack.Melee, + ): Boolean { + anim("seq.shatter") + spotanim("spotanim.sp_attack_shatter_spotanim", height = 96, slot = COMBAT_SLOT) + singleHit(manager, target, attack, accuracy = 1.25, damage = 1.5) + return true + } + } + + /** + * Dragon scimitar: one hit at +25% accuracy. Officially it also disables a player's protection + * prayers for five seconds; prayer deactivation is not exposed to content yet, so the hit + * stands alone. + */ + private class Sever( + private val manager: SpecialAttackManager, + private val worldRepo: WorldRepository, + ) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + sever(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + sever(target, attack) + + private fun ProtectedAccess.sever(target: PathingEntity, attack: CombatAttack.Melee): Boolean { + anim("seq.sp_attack_dragon_scimitar") + spotanim( + "spotanim.sp_attack_dragon_scimitar_trail_spotanim", + height = 96, + slot = COMBAT_SLOT, + ) + worldRepo.soundArea(player, SEVER_SOUND, radius = SOUND_RADIUS) + singleHit(manager, target, attack, accuracy = 1.25, damage = 1.0) + return true + } + } + + /** + * Dragon sword: one stab at +25% accuracy and +25% damage. The official special also ignores + * Protect from Melee; that bypass is not exposed to content yet. + */ + private class WildStab(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + wildStab(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + wildStab(target, attack) + + private fun ProtectedAccess.wildStab( + target: PathingEntity, + attack: CombatAttack.Melee, + ): Boolean { + anim("seq.human_dragon_sword_spec") + spotanim("spotanim.dragon_sword_spec_spotanim", height = 96, slot = COMBAT_SLOT) + singleHit( + manager, + target, + attack, + accuracy = 1.25, + damage = 1.25, + blockType = MeleeAttackType.Stab, + ) + return true + } + } + + /** + * Dragon 2h sword: a spinning stab that hits the target and, in a multi-combat area, every + * other attackable npc standing next to the player (up to fourteen). + */ + private class Powerstab( + private val manager: SpecialAttackManager, + private val npcSearch: NpcSearch, + ) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + powerstab(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + powerstab(target, attack) + + private fun ProtectedAccess.powerstab( + target: PathingEntity, + attack: CombatAttack.Melee, + ): Boolean { + anim("seq.dragon_two_handed_sword") + spotanim("spotanim.dragon_two_handed_sword_blast", height = 96, slot = COMBAT_SLOT) + + val damage = manager.rollMeleeDamage(this, target, attack, 1.0, 1.0) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + + if (mapMultiway()) { + val others = + npcSearch + .findAllAny(coords, distance = 1, vis = HuntVis.Off) + .filter { it != target && it.isValidTarget() && it.type.isAttackable() } + .take(POWERSTAB_MAX_EXTRA_TARGETS) + for (npc in others) { + val extra = manager.rollMeleeDamage(this, npc, attack, 1.0, 1.0) + manager.giveCombatXp(this, npc, attack, extra) + manager.queueMeleeHit(this, npc, extra) + } + } + manager.continueCombat(this, target) + return true + } + + private fun dev.openrune.types.NpcServerType.isAttackable(): Boolean = + actions.getOpOrNull(1)?.equals("attack", ignoreCase = true) == true + } + + /** + * Dragon halberd: a wide sweep at +10% damage. Large monsters (bigger than one tile) are hit a + * second time at -25% accuracy. The sweep graphic faces the target. + */ + private class Sweep(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + sweep(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + sweep(target, attack) + + private fun ProtectedAccess.sweep(target: PathingEntity, attack: CombatAttack.Melee): Boolean { + anim("seq.dragon_halberd_special_attack") + spotanim(sweepSpot(target), height = 96, slot = COMBAT_SLOT) + + val first = manager.rollMeleeDamage(this, target, attack, 1.0, 1.1) + var total = first + manager.queueMeleeHit(this, target, first) + if (target is Npc && target.size > 1) { + val second = manager.rollMeleeDamage(this, target, attack, 0.75, 1.1) + total += second + manager.queueMeleeHit(this, target, second) + } + manager.giveCombatXp(this, target, attack, total) + manager.continueCombat(this, target) + return true + } + + private fun ProtectedAccess.sweepSpot(target: PathingEntity): String { + val dx = target.coords.x - coords.x + val dz = target.coords.z - coords.z + return when { + kotlin.math.abs(dx) > kotlin.math.abs(dz) && dx > 0 -> + "spotanim.dragon_halberd_special_east_red" + kotlin.math.abs(dx) > kotlin.math.abs(dz) -> "spotanim.dragon_halberd_special_west_red" + dz > 0 -> "spotanim.dragon_halberd_special_north_red" + else -> "spotanim.dragon_halberd_special_south_red" + } + } + } + + /** + * Dragon spear, Zamorakian spear and hasta: no damage, but the target is stunned for three + * seconds - a player is held in place with the stunned graphic and animation; an npc has its + * next attack delayed by the same time. + */ + private class Shove(private val manager: SpecialAttackManager, private val seq: String) : + MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee): Boolean { + shove(target) + target.actionDelay = max(target.actionDelay, mapClock + STUN_TICKS) + manager.continueCombat(this, target) + return true + } + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee): Boolean { + shove(target) + target.anim("seq.stunned_shove") + // A stun, not a freeze: it ignores freeze immunity and grants none afterwards. + CombatEffects.stun(target, STUN_TICKS) + manager.continueCombat(this, target) + return true + } + + private fun ProtectedAccess.shove(target: PathingEntity) { + anim(seq) + spotanim("spotanim.sp_attack_shove_spotanim", height = 96, slot = COMBAT_SLOT) + target.spotanim("spotanim.stunned_shove", height = 124) + } + } + + /** + * Dragon claws: up to four accuracy rolls. The first roll to succeed sets the damage of the + * remaining hits, so an early success is worth far more than a late one. + */ + private class SliceAndDice(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + sliceAndDice(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + sliceAndDice(target, attack) + + private fun ProtectedAccess.sliceAndDice( + target: PathingEntity, + attack: CombatAttack.Melee, + ): Boolean { + anim("seq.human_dragon_claws_spec") + spotanim("spotanim.dragon_claws_spot", height = 96, slot = COMBAT_SLOT) + + val maxHit = manager.calculateMeleeMaxHit(this, target, attack.type, attack.style, 1.0) + val hits = rollClaws(target, attack, maxHit) + manager.giveCombatXp(this, target, attack, hits.sum()) + manager.queueMeleeHit(this, target, hits[0], delay = 1) + manager.queueMeleeHit(this, target, hits[1], delay = 1) + manager.queueMeleeHit(this, target, hits[2], delay = 2) + manager.queueMeleeHit(this, target, hits[3], delay = 2) + manager.continueCombat(this, target) + return true + } + + private fun ProtectedAccess.rollClaws( + target: PathingEntity, + attack: CombatAttack.Melee, + maxHit: Int, + ): IntArray { + fun accurate(): Boolean = + manager.rollMeleeAccuracy(this, target, attack.type, attack.style, attack.type, 1.0) + if (accurate()) { + val first = random.of(maxHit / 2..maxHit) + val second = first / 2 + val third = second / 2 + return intArrayOf(first, second, third, first - second - third) + } + if (accurate()) { + val second = random.of((maxHit * 3) / 8..(maxHit * 7) / 8) + val third = second / 2 + return intArrayOf(0, second, third, second - third) + } + if (accurate()) { + val third = random.of(maxHit / 4..(maxHit * 3) / 4) + return intArrayOf(0, 0, third, third) + } + if (accurate()) { + val fourth = random.of(maxHit / 4..(maxHit * 5) / 4) + return intArrayOf(0, 0, 0, fourth) + } + return intArrayOf(0, 0, 1, 1) + } + } + + private companion object { + private const val COMBAT_SLOT = constants.spotanim_slot_combat + private const val SOUND_RADIUS = 10 + private const val PUNCTURE_SOUND = 2537 + private const val SEVER_SOUND = 2540 + private const val STUN_TICKS = 5 + private const val POWERSTAB_MAX_EXTRA_TARGETS = 13 + + /** Dragon battleaxe: drains the other combat stats by 10% to fuel a big Strength boost. */ + private fun ProtectedAccess.rampage(worldRepo: WorldRepository): Boolean { + var drained = 0 + for (stat in + listOf( + TargetStats.ATTACK, + TargetStats.DEFENCE, + TargetStats.RANGED, + TargetStats.MAGIC, + )) { + drained += TargetStats.drainPercentOfCurrent(player, stat, RAMPAGE_DRAIN_PERCENT) + } + val boost = RAMPAGE_BASE_BOOST + drained / 4 + statBoost(TargetStats.STRENGTH, constant = boost, percent = 0) + say("Raarrrrrgggggghhhhhhh!") + anim("seq.rampage") + spotanim("spotanim.sp_attackglow_red", height = 96, slot = COMBAT_SLOT) + soundArea(worldRepo, coords, "synth.rampage", radius = SOUND_RADIUS) + return true + } + + private const val RAMPAGE_DRAIN_PERCENT = 10 + private const val RAMPAGE_BASE_BOOST = 10 + + private fun ProtectedAccess.singleHit( + manager: SpecialAttackManager, + target: PathingEntity, + attack: CombatAttack.Melee, + accuracy: Double, + damage: Double, + blockType: MeleeAttackType? = attack.type, + ): Int { + val rolled = + manager.rollMeleeDamage( + source = this, + target = target, + attack = attack, + accuracyMultiplier = accuracy, + maxHitMultiplier = damage, + blockType = blockType, + ) + manager.giveCombatXp(this, target, attack, rolled) + manager.queueMeleeHit(this, target, rolled) + manager.continueCombat(this, target) + return rolled + } + } +} diff --git a/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/GodswordSpecialAttacks.kt b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/GodswordSpecialAttacks.kt new file mode 100644 index 000000000..46bb3e0c5 --- /dev/null +++ b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/GodswordSpecialAttacks.kt @@ -0,0 +1,208 @@ +package org.rsmod.content.other.special.attacks.melee + +import jakarta.inject.Inject +import kotlin.math.max +import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.api.combat.commons.CombatEffects +import org.rsmod.api.config.constants +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.statHeal +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.api.specials.SpecialAttackManager +import org.rsmod.api.specials.SpecialAttackMap +import org.rsmod.api.specials.SpecialAttackRepository +import org.rsmod.api.specials.combat.MeleeSpecialAttack +import org.rsmod.content.other.special.attacks.TargetStats +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PathingEntity +import org.rsmod.game.entity.Player + +/** + * The godsword specials - The Judgement (Armadyl), Warstrike (Bandos), Healing Blade (Saradomin) + * and Ice Cleave (Zamorak) - and the Saradomin sword's Saradomin's Lightning. Every godsword + * special doubles accuracy; what differs is the damage bonus and the effect on a successful hit. + */ +class GodswordSpecialAttacks @Inject constructor(private val worldRepo: WorldRepository) : + SpecialAttackMap { + override fun SpecialAttackRepository.register(manager: SpecialAttackManager) { + registerMelee("obj.ags", Godsword(manager, worldRepo, "seq.ags_special_player", ARMADYL)) + registerMelee("obj.agsg", Godsword(manager, worldRepo, "seq.ags_special_player", ARMADYL)) + registerMelee("obj.bgs", Godsword(manager, worldRepo, "seq.bgs_special_player", BANDOS)) + registerMelee( + "obj.bgsg", + Godsword(manager, worldRepo, "seq.bgs_special_ornate_player", BANDOS), + ) + registerMelee("obj.sgs", Godsword(manager, worldRepo, "seq.sgs_special_player", SARADOMIN)) + registerMelee( + "obj.sgsg", + Godsword(manager, worldRepo, "seq.sgs_special_ornate_player", SARADOMIN), + ) + registerMelee("obj.zgs", Godsword(manager, worldRepo, "seq.zgs_special_player", ZAMORAK)) + registerMelee( + "obj.zgsg", + Godsword(manager, worldRepo, "seq.zgs_special_ornate_player", ZAMORAK), + ) + + registerMelee( + "obj.saradomin_sword", + SaradominsLightning(manager, "seq.saradomin_sword_special_player"), + ) + registerMelee( + "obj.blessed_saradomin_sword", + SaradominsLightning(manager, "seq.blessed_saradomin_sword_special_player"), + ) + registerMelee( + "obj.blessed_saradomin_sword_degraded", + SaradominsLightning(manager, "seq.blessed_saradomin_sword_special_player"), + ) + } + + private data class GodswordEffect( + val spot: String, + val damageMultiplier: Double, + val onHit: ProtectedAccess.(target: PathingEntity, damage: Int) -> Unit, + ) + + private class Godsword( + private val manager: SpecialAttackManager, + private val worldRepo: WorldRepository, + private val seq: String, + private val effect: GodswordEffect, + ) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + strike(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + strike(target, attack) + + private fun ProtectedAccess.strike(target: PathingEntity, attack: CombatAttack.Melee): Boolean { + anim(seq) + spotanim(effect.spot, height = 96, slot = constants.spotanim_slot_combat) + worldRepo.soundArea(player, GODSWORD_SOUND, radius = SOUND_RADIUS) + + val damage = + manager.rollMeleeDamage( + source = this, + target = target, + attack = attack, + accuracyMultiplier = 2.0, + maxHitMultiplier = effect.damageMultiplier, + ) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + if (damage > 0) { + effect.onHit(this, target, damage) + } + manager.continueCombat(this, target) + return true + } + } + + /** + * Saradomin sword: a melee hit at +10% damage plus a burst of lightning that always lands + * for 1-16 magic damage. + */ + private class SaradominsLightning( + private val manager: SpecialAttackManager, + private val seq: String, + ) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + lightning(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + lightning(target, attack) + + private fun ProtectedAccess.lightning( + target: PathingEntity, + attack: CombatAttack.Melee, + ): Boolean { + anim(seq) + target.spotanim("spotanim.godwars_saradomin_sword_attack_spot", height = 96) + + val melee = manager.rollMeleeDamage(this, target, attack, 1.0, 1.1) + val magic = random.of(LIGHTNING_MIN..LIGHTNING_MAX) + manager.giveCombatXp(this, target, attack, melee + magic) + manager.queueMeleeHit(this, target, melee) + manager.queueMagicHit(this, target, magic, clientDelay = 0, hitDelay = 1) + manager.continueCombat(this, target) + return true + } + } + + private companion object { + private const val SOUND_RADIUS = 10 + private const val GODSWORD_SOUND = 3869 + private const val LIGHTNING_MIN = 1 + private const val LIGHTNING_MAX = 16 + private const val ICE_CLEAVE_FREEZE_TICKS = 33 + private const val HEALING_BLADE_MIN_HEAL = 10 + private const val HEALING_BLADE_MIN_PRAYER = 5 + + /** Armadyl: double accuracy and +37.5% damage, nothing else. */ + private val ARMADYL = + GodswordEffect( + spot = "spotanim.dh_sword_update_armadyl_special_spotanim", + damageMultiplier = 1.375, + onHit = { _, _ -> }, + ) + + /** + * Bandos: +21% damage; the damage dealt is drained from the target's Defence, and whatever + * is left over spills into Strength, Prayer, Attack, Magic and Ranged in turn. + */ + private val BANDOS = + GodswordEffect( + spot = "spotanim.dh_sword_update_bandos_special_spotanim", + damageMultiplier = 1.21, + onHit = { target, damage -> warstrike(target, damage) }, + ) + + /** Saradomin: +10% damage; heals half the damage and restores a quarter as prayer. */ + private val SARADOMIN = + GodswordEffect( + spot = "spotanim.dh_sword_update_saradomin_special_spotanim", + damageMultiplier = 1.1, + onHit = { _, damage -> + val heal = max(HEALING_BLADE_MIN_HEAL, damage / 2) + val prayer = max(HEALING_BLADE_MIN_PRAYER, damage / 4) + player.statHeal("stat.hitpoints", constant = heal, percent = 0) + player.statHeal(TargetStats.PRAYER, constant = prayer, percent = 0) + }, + ) + + /** Zamorak: +10% damage; a player hit is frozen for twenty seconds. */ + private val ZAMORAK = + GodswordEffect( + spot = "spotanim.dh_sword_update_zamorak_special_spotanim", + damageMultiplier = 1.1, + onHit = { target, _ -> + if (target is Player) { + target.spotanim("spotanim.ice_barrage_impact", height = 0) + CombatEffects.freeze(target, ICE_CLEAVE_FREEZE_TICKS) + } + }, + ) + + private fun warstrike(target: PathingEntity, damage: Int) { + var remaining = damage + val order = + listOf( + TargetStats.DEFENCE, + TargetStats.STRENGTH, + TargetStats.PRAYER, + TargetStats.ATTACK, + TargetStats.MAGIC, + TargetStats.RANGED, + ) + for (stat in order) { + if (remaining <= 0) { + return + } + if (stat == TargetStats.PRAYER && target !is Player) { + continue + } + remaining -= TargetStats.drain(target, stat, remaining) + } + } + } +} diff --git a/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/HammerSpecialAttacks.kt b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/HammerSpecialAttacks.kt new file mode 100644 index 000000000..c7aa1b1d1 --- /dev/null +++ b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/HammerSpecialAttacks.kt @@ -0,0 +1,229 @@ +package org.rsmod.content.other.special.attacks.melee + +import jakarta.inject.Inject +import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.api.config.constants +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.statHeal +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.api.specials.SpecialAttackManager +import org.rsmod.api.specials.SpecialAttackMap +import org.rsmod.api.specials.SpecialAttackRepository +import org.rsmod.api.specials.combat.MeleeSpecialAttack +import org.rsmod.content.other.special.attacks.TargetStats +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PathingEntity +import org.rsmod.game.entity.Player + +/** + * The hammer and mace specials: Smash (dragon warhammer), Pulverize (elder maul), Quick Smash + * (granite maul), Hammer Blow (granite hammer), Sunder (barrelchest anchor) and Favour of the War + * God (ancient mace). + */ +class HammerSpecialAttacks @Inject constructor(private val worldRepo: WorldRepository) : + SpecialAttackMap { + override fun SpecialAttackRepository.register(manager: SpecialAttackManager) { + val smash = Smash(manager) + registerMelee("obj.dragon_warhammer", smash) + registerMelee("obj.dragon_warhammer_ornament", smash) + + val pulverize = Pulverize(manager) + registerMelee("obj.elder_maul", pulverize) + registerMelee("obj.elder_maul_ornament", pulverize) + + val quickSmash = QuickSmash(manager, worldRepo) + registerMelee("obj.granite_maul", quickSmash) + registerMelee("obj.granite_maul_pretty", quickSmash) + registerMelee("obj.granite_maul_plus", quickSmash) + registerMelee("obj.granite_maul_pretty_plus", quickSmash) + + registerMelee("obj.granite_hammer", HammerBlow(manager)) + + val sunder = Sunder(manager) + registerMelee("obj.brain_anchor", sunder) + registerMelee("obj.bh_brain_anchor_imbue", sunder) + + registerMelee("obj.ancient_goblin_mace", FavourOfTheWarGod(manager)) + } + + /** Dragon warhammer: +50% damage; a successful hit lowers the target's Defence by 30%. */ + private class Smash(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + smash(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + smash(target, attack) + + private fun ProtectedAccess.smash(target: PathingEntity, attack: CombatAttack.Melee): Boolean { + anim("seq.dragon_warhammer_sa_player") + spotanim("spotanim.dragon_warhammer_sa_spotanim", height = 96, slot = COMBAT_SLOT) + val damage = manager.rollMeleeDamage(this, target, attack, 1.0, 1.5) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + if (damage > 0) { + TargetStats.drainPercentOfCurrent(target, TargetStats.DEFENCE, SMASH_DEFENCE_PERCENT) + } + manager.continueCombat(this, target) + return true + } + } + + /** Elder maul: +25% accuracy; a successful hit lowers the target's Defence by 35%. */ + private class Pulverize(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + pulverize(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + pulverize(target, attack) + + private fun ProtectedAccess.pulverize( + target: PathingEntity, + attack: CombatAttack.Melee, + ): Boolean { + anim("seq.human_elder_maul_spec") + spotanim("spotanim.spotanim_elder_maul_special", height = 96, slot = COMBAT_SLOT) + val damage = manager.rollMeleeDamage(this, target, attack, 1.25, 1.0) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + if (damage > 0) { + target.spotanim("spotanim.spotanim_elder_maul_special_impact", height = 96) + TargetStats.drainPercentOfCurrent( + target, + TargetStats.DEFENCE, + PULVERIZE_DEFENCE_PERCENT, + ) + } + manager.continueCombat(this, target) + return true + } + } + + /** + * Granite maul: the maul's special is an instant extra blow. It lands as a second hit straight + * after the regular swing, so activating it in combat still doubles up the damage. + */ + private class QuickSmash( + private val manager: SpecialAttackManager, + private val worldRepo: WorldRepository, + ) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + quickSmash(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + quickSmash(target, attack) + + private fun ProtectedAccess.quickSmash( + target: PathingEntity, + attack: CombatAttack.Melee, + ): Boolean { + anim("seq.slayer_granite_maul_special_attack") + spotanim("spotanim.sp_attack_maul_spotanim", height = 96, slot = COMBAT_SLOT) + worldRepo.soundArea(player, QUICK_SMASH_SOUND, radius = SOUND_RADIUS) + val first = manager.rollMeleeDamage(this, target, attack, 1.0, 1.0) + val second = manager.rollMeleeDamage(this, target, attack, 1.0, 1.0) + manager.giveCombatXp(this, target, attack, first + second) + manager.queueMeleeHit(this, target, first) + manager.queueMeleeHit(this, target, second) + manager.continueCombat(this, target) + return true + } + } + + /** Granite hammer: +50% accuracy, and five extra damage even when the blow misses. */ + private class HammerBlow(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + hammerBlow(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + hammerBlow(target, attack) + + private fun ProtectedAccess.hammerBlow( + target: PathingEntity, + attack: CombatAttack.Melee, + ): Boolean { + anim("seq.dragon_warhammer_sa_player") + spotanim("spotanim.granite_hammer_sa_spotanim", height = 96, slot = COMBAT_SLOT) + val damage = manager.rollMeleeDamage(this, target, attack, 1.5, 1.0) + HAMMER_BLOW_BONUS + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + manager.continueCombat(this, target) + return true + } + } + + /** + * Barrelchest anchor: double accuracy and +10% damage; a successful hit drains one of the + * target's combat stats by a tenth of the damage dealt. + */ + private class Sunder(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + sunder(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + sunder(target, attack) + + private fun ProtectedAccess.sunder(target: PathingEntity, attack: CombatAttack.Melee): Boolean { + anim("seq.brain_player_anchor_special_attack") + spotanim("spotanim.brain_anchor_special_attack_spot", height = 96, slot = COMBAT_SLOT) + val damage = manager.rollMeleeDamage(this, target, attack, 2.0, 1.1) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + if (damage > 0) { + val stat = random.pick(SUNDER_STATS) + TargetStats.drain(target, stat, damage / 10) + } + manager.continueCombat(this, target) + return true + } + } + + /** + * Ancient mace: a successful hit drains a player target's Prayer by the damage dealt and + * restores the same amount to the attacker, even above their base level. + */ + private class FavourOfTheWarGod(private val manager: SpecialAttackManager) : + MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + favour(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + favour(target, attack) + + private fun ProtectedAccess.favour(target: PathingEntity, attack: CombatAttack.Melee): Boolean { + anim("seq.slice_player_mace_special_attack") + spotanim( + "spotanim.slice_player_mace_special_attack_spotanim", + height = 96, + slot = COMBAT_SLOT, + ) + val damage = manager.rollMeleeDamage(this, target, attack, 1.0, 1.0) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + if (damage > 0) { + if (target is Player) { + TargetStats.drain(target, TargetStats.PRAYER, damage) + } + player.statHeal(TargetStats.PRAYER, constant = damage, percent = 0) + } + manager.continueCombat(this, target) + return true + } + } + + private companion object { + private const val COMBAT_SLOT = constants.spotanim_slot_combat + private const val SOUND_RADIUS = 10 + private const val QUICK_SMASH_SOUND = 2715 + private const val SMASH_DEFENCE_PERCENT = 30 + private const val PULVERIZE_DEFENCE_PERCENT = 35 + private const val HAMMER_BLOW_BONUS = 5 + private val SUNDER_STATS = + listOf( + TargetStats.ATTACK, + TargetStats.STRENGTH, + TargetStats.DEFENCE, + TargetStats.RANGED, + TargetStats.MAGIC, + ) + } +} diff --git a/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/MiscMeleeSpecialAttacks.kt b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/MiscMeleeSpecialAttacks.kt new file mode 100644 index 000000000..1e9682000 --- /dev/null +++ b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/melee/MiscMeleeSpecialAttacks.kt @@ -0,0 +1,227 @@ +package org.rsmod.content.other.special.attacks.melee + +import jakarta.inject.Inject +import kotlin.math.min +import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.api.config.constants +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.api.specials.SpecialAttackManager +import org.rsmod.api.specials.SpecialAttackMap +import org.rsmod.api.specials.SpecialAttackRepository +import org.rsmod.api.specials.combat.MeleeSpecialAttack +import org.rsmod.content.other.special.attacks.TargetStats +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PathingEntity +import org.rsmod.game.entity.Player + +/** + * The remaining melee specials: Weaken (Darklight and Arclight), Sanctuary (Excalibur), Backstab + * (bone dagger), Liquify (brine sabre), Disrupt (Voidwaker) and Eviscerate (Osmumten's fang). + */ +class MiscMeleeSpecialAttacks @Inject constructor(private val worldRepo: WorldRepository) : + SpecialAttackMap { + override fun SpecialAttackRepository.register(manager: SpecialAttackManager) { + registerMelee("obj.darklight", Weaken(manager, demonPercent = DARKLIGHT_DEMON_PERCENT)) + registerMelee("obj.arclight", Weaken(manager, demonPercent = ARCLIGHT_DEMON_PERCENT)) + registerMelee( + "obj.arclight_inactive", + Weaken(manager, demonPercent = ARCLIGHT_DEMON_PERCENT), + ) + + registerInstant("obj.excalibur") { sanctuary(worldRepo) } + + val backstab = Backstab(manager) + registerMelee("obj.dttd_bone_dagger", backstab) + registerMelee("obj.dttd_bone_dagger_p", backstab) + registerMelee("obj.dttd_bone_dagger_p+", backstab) + registerMelee("obj.dttd_bone_dagger_p++", backstab) + + registerMelee("obj.olaf2_brine_sabre", Liquify(manager)) + registerMelee("obj.voidwaker", Disrupt(manager)) + + val eviscerate = Eviscerate(manager) + registerMelee("obj.osmumtens_fang", eviscerate) + registerMelee("obj.osmumtens_fang_ornament", eviscerate) + } + + /** + * Darklight and Arclight: a successful hit lowers the target's Attack, Strength and Defence by + * 5% of their current levels, or more against demons. + */ + private class Weaken(private val manager: SpecialAttackManager, private val demonPercent: Int) : + MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee): Boolean { + val percent = if (target.isDemon()) demonPercent else WEAKEN_PERCENT + return weaken(target, attack, percent) + } + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee): Boolean = + weaken(target, attack, WEAKEN_PERCENT) + + private fun ProtectedAccess.weaken( + target: PathingEntity, + attack: CombatAttack.Melee, + percent: Int, + ): Boolean { + anim("seq.dark_spec_player") + spotanim("spotanim.dark_spec_spot", height = 96, slot = COMBAT_SLOT) + val damage = manager.rollMeleeDamage(this, target, attack, 1.0, 1.0) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + if (damage > 0) { + for (stat in listOf(TargetStats.ATTACK, TargetStats.STRENGTH, TargetStats.DEFENCE)) { + TargetStats.drainPercentOfCurrent(target, stat, percent) + } + } + manager.continueCombat(this, target) + return true + } + + private fun Npc.isDemon(): Boolean = type.name.contains("demon", ignoreCase = true) + } + + /** + * Bone dagger: guaranteed to hit if the target is not already fighting the player; a + * successful hit drains the target's Defence by the damage dealt. + */ + private class Backstab(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee): Boolean { + val unaware = !target.isFighting(player) + return backstab(target, attack, unaware) + } + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee): Boolean = + backstab(target, attack, unaware = false) + + private fun ProtectedAccess.backstab( + target: PathingEntity, + attack: CombatAttack.Melee, + unaware: Boolean, + ): Boolean { + anim("seq.dttd_player_stab_bone_dagger") + spotanim("spotanim.dttd_dagger_sp_attack_spotanim", height = 96, slot = COMBAT_SLOT) + val damage = + if (unaware) { + manager.rollMeleeMaxHit(this, target, attack.type, attack.style, 1.0) + } else { + manager.rollMeleeDamage(this, target, attack, 1.0, 1.0) + } + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + if (damage > 0) { + TargetStats.drain(target, TargetStats.DEFENCE, damage) + } + manager.continueCombat(this, target) + return true + } + } + + /** + * Brine sabre: double accuracy; a successful hit boosts Attack, Strength and Defence by a + * quarter of the damage, capped at three plus a tenth of the base level. + */ + private class Liquify(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + liquify(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + liquify(target, attack) + + private fun ProtectedAccess.liquify(target: PathingEntity, attack: CombatAttack.Melee): Boolean { + anim("seq.olaf2_brine_sabre_special") + spotanim("spotanim.olaf2_brine_sabre_special_spot", height = 96, slot = COMBAT_SLOT) + val damage = manager.rollMeleeDamage(this, target, attack, 2.0, 1.0) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + if (damage > 0) { + for (stat in listOf(TargetStats.ATTACK, TargetStats.STRENGTH, TargetStats.DEFENCE)) { + val cap = LIQUIFY_BASE_CAP + TargetStats.base(player, stat) / 10 + val boost = min(cap, damage / 4) + statBoost(stat, constant = boost, percent = 0) + } + } + manager.continueCombat(this, target) + return true + } + } + + /** + * Voidwaker: a guaranteed magic hit for 50-150% of the player's melee max hit. It rolls no + * accuracy at all - the damage is what varies. + */ + private class Disrupt(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + disrupt(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + disrupt(target, attack) + + private fun ProtectedAccess.disrupt(target: PathingEntity, attack: CombatAttack.Melee): Boolean { + anim("seq.human_special02_voidwaker") + spotanim("spotanim.fx_voidwaker02_special", height = 96, slot = COMBAT_SLOT) + val maxHit = manager.calculateMeleeMaxHit(this, target, attack.type, attack.style, 1.0) + val damage = random.of(maxHit / 2..(maxHit * 3) / 2) + target.spotanim("spotanim.fx_voidwaker_impact", height = 96) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMagicHit(this, target, damage, clientDelay = 0, hitDelay = 1) + manager.continueCombat(this, target) + return true + } + } + + /** + * Osmumten's fang: +50% accuracy, and the damage rolls between 15% and 85% of the max hit + * rather than from zero. + */ + private class Eviscerate(private val manager: SpecialAttackManager) : MeleeSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Melee) = + eviscerate(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Melee) = + eviscerate(target, attack) + + private fun ProtectedAccess.eviscerate( + target: PathingEntity, + attack: CombatAttack.Melee, + ): Boolean { + anim("seq.human_osmumtens_fang") + val landed = + manager.rollMeleeAccuracy(this, target, attack.type, attack.style, attack.type, 1.5) + val damage = + if (landed) { + val maxHit = manager.calculateMeleeMaxHit(this, target, attack.type, attack.style, 1.0) + random.of((maxHit * 15) / 100..(maxHit * 85) / 100) + } else { + 0 + } + manager.giveCombatXp(this, target, attack, damage) + manager.queueMeleeHit(this, target, damage) + manager.continueCombat(this, target) + return true + } + } + + private companion object { + private const val COMBAT_SLOT = constants.spotanim_slot_combat + private const val SOUND_RADIUS = 10 + private const val WEAKEN_PERCENT = 5 + private const val DARKLIGHT_DEMON_PERCENT = 10 + private const val ARCLIGHT_DEMON_PERCENT = 15 + private const val SANCTUARY_DEFENCE_BOOST = 8 + private const val LIQUIFY_BASE_CAP = 3 + + /** Excalibur: raises Defence by eight levels. */ + private fun ProtectedAccess.sanctuary(worldRepo: WorldRepository): Boolean { + statBoost(TargetStats.DEFENCE, constant = SANCTUARY_DEFENCE_BOOST, percent = 0) + say("For Camelot!") + anim("seq.sanctuary") + spotanim("spotanim.sp_attackglow_blue", height = 96, slot = COMBAT_SLOT) + soundArea(worldRepo, coords, "synth.rampage", radius = SOUND_RADIUS) + return true + } + + private fun Npc.isFighting(player: Player): Boolean = + vars["varn.attacking_player"] == player.uid.packed + } +} diff --git a/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/ranged/DarkBowSpecialAttack.kt b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/ranged/DarkBowSpecialAttack.kt index 1bdc69ebb..2add95b63 100644 --- a/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/ranged/DarkBowSpecialAttack.kt +++ b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/ranged/DarkBowSpecialAttack.kt @@ -92,11 +92,18 @@ class DarkBowSpecialAttack @Inject constructor(private val ammunition: RangedAmm quiverType: ItemServerType, travelSpot: String, ) { - val launchSpot = quiverType.paramOrNull(params.proj_launch_double) + // Broad and ogre arrows carry no double-launch graphic; fall back to the single one. + val launchSpot = + quiverType.paramOrNull(params.proj_launch_double) + ?: quiverType.paramOrNull(params.proj_launch) anim("seq.human_bow") soundSynth("synth.darkbow_doublefire") soundSynth("synth.darkbow_shadow_attack") - spotanim(RSCM.getReverseMapping(RSCMType.SPOTANIM,launchSpot!!.id), height = 96, slot = constants.spotanim_slot_combat) + spotanim( + launchSpot?.let { RSCM.getReverseMapping(RSCMType.SPOTANIM, it.id) }, + height = 96, + slot = constants.spotanim_slot_combat, + ) val descentTravel = "spotanim.darkbow_generic_smoke_arrow_flight" val descentImpact = "spotanim.darkbow_smoke_arrow_impact" @@ -115,7 +122,7 @@ class DarkBowSpecialAttack @Inject constructor(private val ammunition: RangedAmm target.spotanim(descentImpact, height = 96, delay = clientDelay2) val damage = - calculateDamage(target, attack, damageRange = 5..Int.MAX_VALUE, multiplier = 1.3) + calculateDamage(target, attack, damageRange = 5..48, multiplier = 1.3) val hitDelay1 = proj1.serverCycles val hitDelay2 = proj2.serverCycles @@ -150,11 +157,18 @@ class DarkBowSpecialAttack @Inject constructor(private val ammunition: RangedAmm quiverType: ItemServerType, travelSpot: String, ) { - val launchSpot = quiverType.paramOrNull(params.proj_launch_double) + // Broad and ogre arrows carry no double-launch graphic; fall back to the single one. + val launchSpot = + quiverType.paramOrNull(params.proj_launch_double) + ?: quiverType.paramOrNull(params.proj_launch) anim("seq.human_bow") soundSynth("synth.darkbow_doublefire") soundSynth("synth.darkbow_dragon_attack") - spotanim(RSCM.getReverseMapping(RSCMType.SPOTANIM,launchSpot!!.id), height = 96, slot = constants.spotanim_slot_combat) + spotanim( + launchSpot?.let { RSCM.getReverseMapping(RSCMType.SPOTANIM, it.id) }, + height = 96, + slot = constants.spotanim_slot_combat, + ) val descentTravel = "spotanim.darkbow_dragon_head_flying_projanim" val descentImpact = "spotanim.darkbow_dragon_head_flying_impact_anim" @@ -226,8 +240,10 @@ class DarkBowSpecialAttack @Inject constructor(private val ammunition: RangedAmm multiplier = multiplier, boltSpecDamage = 0, ) - val first = if (!accuracySuccess()) 0 else random.of(0..damage).coerceIn(damageRange) - val second = if (!accuracySuccess()) 0 else random.of(0..damage).coerceIn(damageRange) + // Each arrow always deals at least the floor of its range, even when the accuracy + // roll fails, and never more than its cap. + val first = (if (accuracySuccess()) random.of(0..damage) else 0).coerceIn(damageRange) + val second = (if (accuracySuccess()) random.of(0..damage) else 0).coerceIn(damageRange) return DescentHit(first, second) } diff --git a/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/ranged/RangedSpecialAttacks.kt b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/ranged/RangedSpecialAttacks.kt new file mode 100644 index 000000000..a1386d93a --- /dev/null +++ b/content/other/special-attacks/src/main/kotlin/org/rsmod/content/other/special/attacks/ranged/RangedSpecialAttacks.kt @@ -0,0 +1,420 @@ +package org.rsmod.content.other.special.attacks.ranged + +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import dev.openrune.types.hunt.HuntVis +import jakarta.inject.Inject +import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.api.combat.manager.RangedAmmoManager +import org.rsmod.api.config.constants +import org.rsmod.api.config.refs.params +import org.rsmod.api.hunt.NpcSearch +import org.rsmod.api.npc.isValidTarget +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.quiver +import org.rsmod.api.player.righthand +import org.rsmod.api.specials.SpecialAttackManager +import org.rsmod.api.specials.SpecialAttackMap +import org.rsmod.api.specials.SpecialAttackRepository +import org.rsmod.api.specials.combat.RangedSpecialAttack +import org.rsmod.content.other.special.attacks.TargetStats +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PathingEntity +import org.rsmod.game.entity.Player +import org.rsmod.game.type.getInvObj +import org.rsmod.game.type.getOrNull + +/** + * The bow, crossbow and thrown-weapon specials: Snapshot (magic shortbow), Powershot (magic + * longbow and comp bow), Soulshot (Seercull), Snipe (Dorgeshuun crossbow), Annihilate (dragon + * crossbow), Armadyl Eye (Armadyl crossbow), Concentrated Shot (ballistas), Duality (dragon + * knife) and Momentum Throw (dragon thrownaxe). + */ +class RangedSpecialAttacks +@Inject +constructor(private val ammunition: RangedAmmoManager, private val npcSearch: NpcSearch) : + SpecialAttackMap { + override fun SpecialAttackRepository.register(manager: SpecialAttackManager) { + val snapshot = Snapshot(manager, ammunition) + registerRanged("obj.magic_shortbow", snapshot) + registerRanged("obj.magic_shortbow_i", snapshot) + + val powershot = Powershot(manager, ammunition) + registerRanged("obj.magic_longbow", powershot) + registerRanged("obj.trail_composite_bow_magic", powershot) + + registerRanged("obj.daganoth_cave_magic_shortbow", Soulshot(manager, ammunition)) + registerRanged("obj.dttd_bone_crossbow", Snipe(manager, ammunition)) + registerRanged("obj.xbows_crossbow_dragon", Annihilate(manager, ammunition, npcSearch)) + registerRanged("obj.acb", ArmadylEye(manager, ammunition)) + + val concentratedShot = ConcentratedShot(manager, ammunition) + registerRanged("obj.light_ballista", concentratedShot) + registerRanged("obj.heavy_ballista", concentratedShot) + registerRanged("obj.heavy_ballista_ornament", concentratedShot) + + registerRanged("obj.dragon_knife", Duality(manager, ammunition, DRAGON_KNIFE_TRAVEL)) + registerRanged("obj.dragon_knife_p", Duality(manager, ammunition, DRAGON_KNIFE_TRAVEL_P)) + registerRanged("obj.dragon_knife_p+", Duality(manager, ammunition, DRAGON_KNIFE_TRAVEL_P)) + registerRanged("obj.dragon_knife_p++", Duality(manager, ammunition, DRAGON_KNIFE_TRAVEL_P)) + + registerRanged("obj.dragon_thrownaxe", MomentumThrow(manager, ammunition)) + } + + /** Shared plumbing for specials fired from a bow or crossbow with ammunition in the quiver. */ + private abstract class AmmoSpecial( + protected val manager: SpecialAttackManager, + protected val ammunition: RangedAmmoManager, + ) : RangedSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Ranged) = + fire(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Ranged) = + fire(target, attack) + + private fun ProtectedAccess.fire(target: PathingEntity, attack: CombatAttack.Ranged): Boolean { + val weaponType = getInvObj(attack.weapon) + val quiverType = getOrNull(player.quiver) + if (!ammunition.attemptAmmoUsage(player, weaponType, quiverType)) { + manager.stopCombat(this) + return false + } + if (quiverType == null) { + manager.stopCombat(this) + mes("You have no ammunition to fire.") + return false + } + val count = player.quiver?.count ?: 0 + if (count < ammoRequired) { + manager.stopCombat(this) + mes("You need at least $ammoRequired pieces of ammunition for this special attack.") + return false + } + val travel = travelSpot(quiverType) + if (travel == null) { + manager.stopCombat(this) + mes("You are unable to fire your ammunition.") + return false + } + shoot(target, attack, weaponType, quiverType, travel) + manager.continueCombat(this, target) + return true + } + + /** How many pieces of ammunition the special needs loaded. */ + protected open val ammoRequired: Int = 1 + + /** The projectile graphic; defaults to the ammunition's own. */ + protected open fun travelSpot(quiver: ItemServerType): String? = + quiver.paramOrNull(params.proj_travel)?.let { + RSCM.getReverseMapping(RSCMType.SPOTANIM, it.id) + } + + protected abstract fun ProtectedAccess.shoot( + target: PathingEntity, + attack: CombatAttack.Ranged, + weapon: ItemServerType, + quiver: ItemServerType, + travel: String, + ) + + /** Fires one piece of ammunition at [target] and applies [damage] when it lands. */ + protected fun ProtectedAccess.launch( + target: PathingEntity, + quiver: ItemServerType, + travel: String, + projanim: String, + damage: Int, + firstHit: Boolean = true, + ) { + val proj = manager.spawnProjectile(this, target, travel, projanim) + ammunition.useQuiverAmmo(player, quiver, target.coords, dropDelay = proj.serverCycles) + if (firstHit) { + manager.queueRangedHit(this, target, quiver, damage, proj.clientCycles, proj.serverCycles) + } else { + manager.queueRangedDamage(this, target, quiver, damage, proj.serverCycles) + } + } + + protected fun ProtectedAccess.guaranteedDamage( + target: PathingEntity, + attack: CombatAttack.Ranged, + multiplier: Double = 1.0, + ): Int = manager.rollRangedMaxHit(this, target, attack.type, attack.style, multiplier, 0) + } + + /** Magic shortbow: two arrows in quick succession. */ + private class Snapshot(manager: SpecialAttackManager, ammunition: RangedAmmoManager) : + AmmoSpecial(manager, ammunition) { + override val ammoRequired: Int = 2 + + override fun travelSpot(quiver: ItemServerType): String = GLOW_ARROW_TRAVEL + + override fun ProtectedAccess.shoot( + target: PathingEntity, + attack: CombatAttack.Ranged, + weapon: ItemServerType, + quiver: ItemServerType, + travel: String, + ) { + anim("seq.snapshot") + spotanim("spotanim.sp_attack_snapshot_spotanim", height = 96, slot = COMBAT_SLOT) + val first = manager.rollRangedDamage(this, target, attack) + val second = manager.rollRangedDamage(this, target, attack) + manager.giveCombatXp(this, target, attack, first + second) + launch(target, quiver, travel, "projanim.doublearrow_one", first) + launch(target, quiver, travel, "projanim.doublearrow_two", second, firstHit = false) + } + } + + /** Magic longbow and comp bow: one arrow that cannot miss. */ + private class Powershot(manager: SpecialAttackManager, ammunition: RangedAmmoManager) : + AmmoSpecial(manager, ammunition) { + override fun travelSpot(quiver: ItemServerType): String = GLOW_ARROW_TRAVEL + + override fun ProtectedAccess.shoot( + target: PathingEntity, + attack: CombatAttack.Ranged, + weapon: ItemServerType, + quiver: ItemServerType, + travel: String, + ) { + anim("seq.human_bow") + spotanim("spotanim.sp_attack_glow_arrow_launch", height = 96, slot = COMBAT_SLOT) + val damage = guaranteedDamage(target, attack) + manager.giveCombatXp(this, target, attack, damage) + launch(target, quiver, travel, "projanim.arrow", damage) + } + } + + /** Seercull: one arrow that cannot miss and drains the target's Magic by the damage dealt. */ + private class Soulshot(manager: SpecialAttackManager, ammunition: RangedAmmoManager) : + AmmoSpecial(manager, ammunition) { + override fun travelSpot(quiver: ItemServerType): String = + "spotanim.sp_attack_glow_arrow_travel_white" + + override fun ProtectedAccess.shoot( + target: PathingEntity, + attack: CombatAttack.Ranged, + weapon: ItemServerType, + quiver: ItemServerType, + travel: String, + ) { + anim("seq.human_bow") + spotanim("spotanim.sp_attack_glow_arrow_launch_white", height = 96, slot = COMBAT_SLOT) + val damage = guaranteedDamage(target, attack) + manager.giveCombatXp(this, target, attack, damage) + launch(target, quiver, travel, "projanim.arrow", damage) + TargetStats.drain(target, TargetStats.MAGIC, damage) + } + } + + /** + * Dorgeshuun crossbow: a bolt that cannot miss an npc not already fighting the player, and + * drains the target's Defence by the damage dealt. + */ + private class Snipe(manager: SpecialAttackManager, ammunition: RangedAmmoManager) : + AmmoSpecial(manager, ammunition) { + override fun travelSpot(quiver: ItemServerType): String = + "spotanim.dttd_bone_crossbowbolt_travel_sp_attack" + + override fun ProtectedAccess.shoot( + target: PathingEntity, + attack: CombatAttack.Ranged, + weapon: ItemServerType, + quiver: ItemServerType, + travel: String, + ) { + anim("seq.xbows_human_fire_and_reload") + val unaware = target is Npc && target.vars["varn.attacking_player"] != player.uid.packed + val damage = + if (unaware) guaranteedDamage(target, attack) + else manager.rollRangedDamage(this, target, attack) + manager.giveCombatXp(this, target, attack, damage) + launch(target, quiver, travel, "projanim.bolt", damage) + if (damage > 0) { + TargetStats.drain(target, TargetStats.DEFENCE, damage) + } + } + } + + /** + * Dragon crossbow: the bolt hits the target for +20% damage and, in a multi-combat area, every + * attackable npc standing next to the target for -20% damage. + */ + private class Annihilate( + manager: SpecialAttackManager, + ammunition: RangedAmmoManager, + private val npcSearch: NpcSearch, + ) : AmmoSpecial(manager, ammunition) { + override fun ProtectedAccess.shoot( + target: PathingEntity, + attack: CombatAttack.Ranged, + weapon: ItemServerType, + quiver: ItemServerType, + travel: String, + ) { + anim("seq.xbows_human_fire_and_reload") + val damage = manager.rollRangedDamage(this, target, attack, maxHitMultiplier = 1.2) + manager.giveCombatXp(this, target, attack, damage) + launch(target, quiver, travel, "projanim.bolt", damage) + if (!mapMultiway()) { + return + } + val others = + npcSearch + .findAllAny(target.coords, distance = 1, vis = HuntVis.Off) + .filter { it != target && it.isValidTarget() && it.type.hasOp(2) } + .take(ANNIHILATE_MAX_EXTRA_TARGETS) + for (npc in others) { + val splash = manager.rollRangedDamage(this, npc, attack, maxHitMultiplier = 0.8) + manager.giveCombatXp(this, npc, attack, splash) + val proj = manager.spawnProjectile(this, npc, travel, "projanim.bolt") + manager.queueRangedDamage(this, npc, quiver, splash, proj.serverCycles) + } + } + } + + /** + * Armadyl crossbow: a bolt at double accuracy. Officially it also doubles the chance of an + * enchanted bolt effect; bolt effects are not implemented, so only the accuracy applies. + */ + private class ArmadylEye(manager: SpecialAttackManager, ammunition: RangedAmmoManager) : + AmmoSpecial(manager, ammunition) { + override fun ProtectedAccess.shoot( + target: PathingEntity, + attack: CombatAttack.Ranged, + weapon: ItemServerType, + quiver: ItemServerType, + travel: String, + ) { + anim("seq.xbows_human_fire_and_reload") + spotanim("spotanim.acb_specialattack", height = 96, slot = COMBAT_SLOT) + val damage = manager.rollRangedDamage(this, target, attack, accuracyMultiplier = 2.0) + manager.giveCombatXp(this, target, attack, damage) + launch(target, quiver, travel, "projanim.bolt", damage) + } + } + + /** Ballistas: a javelin at +25% accuracy and +25% damage. */ + private class ConcentratedShot(manager: SpecialAttackManager, ammunition: RangedAmmoManager) : + AmmoSpecial(manager, ammunition) { + override fun ProtectedAccess.shoot( + target: PathingEntity, + attack: CombatAttack.Ranged, + weapon: ItemServerType, + quiver: ItemServerType, + travel: String, + ) { + anim("seq.ballista_special_attack") + spotanim("spotanim.ballista_special", height = 96, slot = COMBAT_SLOT) + val projanim = + weapon.paramOrNull(params.proj_type)?.let { + RSCM.getReverseMapping(RSCMType.PROJANIM, it.id) + } ?: "projanim.bolt" + val damage = manager.rollRangedDamage(this, target, attack, 1.25, 1.25) + manager.giveCombatXp(this, target, attack, damage) + launch(target, quiver, travel, projanim, damage) + } + } + + /** Shared plumbing for specials that throw the wielded weapon itself. */ + private abstract class ThrownSpecial( + protected val manager: SpecialAttackManager, + protected val ammunition: RangedAmmoManager, + private val required: Int, + ) : RangedSpecialAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Ranged) = + fire(target, attack) + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Ranged) = + fire(target, attack) + + private fun ProtectedAccess.fire(target: PathingEntity, attack: CombatAttack.Ranged): Boolean { + val weaponType = getInvObj(attack.weapon) + val count = player.righthand?.count ?: 0 + if (count < required) { + manager.stopCombat(this) + mes("You need at least $required of them to use this special attack.") + return false + } + throwAt(target, attack, weaponType) + if (player.righthand == null) { + mes("That was your last one!") + return true + } + manager.continueCombat(this, target) + return true + } + + protected abstract fun ProtectedAccess.throwAt( + target: PathingEntity, + attack: CombatAttack.Ranged, + weapon: ItemServerType, + ) + + protected fun ProtectedAccess.throwOne( + target: PathingEntity, + weapon: ItemServerType, + travel: String, + damage: Int, + firstHit: Boolean = true, + ) { + val proj = manager.spawnProjectile(this, target, travel, "projanim.thrown") + ammunition.useThrownWeapon(player, weapon, target.coords, dropDelay = proj.serverCycles) + if (firstHit) { + manager.queueRangedHit(this, target, null, damage, proj.clientCycles, proj.serverCycles) + } else { + manager.queueRangedDamage(this, target, null, damage, proj.serverCycles) + } + } + } + + /** Dragon knife: two knives thrown at once, each rolled separately. */ + private class Duality( + manager: SpecialAttackManager, + ammunition: RangedAmmoManager, + private val travel: String, + ) : ThrownSpecial(manager, ammunition, required = 2) { + override fun ProtectedAccess.throwAt( + target: PathingEntity, + attack: CombatAttack.Ranged, + weapon: ItemServerType, + ) { + anim("seq.human_dragon_tknives_spec") + spotanim("spotanim.dragon_tknife_launch", height = 96, slot = COMBAT_SLOT) + val first = manager.rollRangedDamage(this, target, attack) + val second = manager.rollRangedDamage(this, target, attack) + manager.giveCombatXp(this, target, attack, first + second) + throwOne(target, weapon, travel, first) + throwOne(target, weapon, travel, second, firstHit = false) + } + } + + /** Dragon thrownaxe: +25% accuracy, and the next attack is ready on the very next tick. */ + private class MomentumThrow(manager: SpecialAttackManager, ammunition: RangedAmmoManager) : + ThrownSpecial(manager, ammunition, required = 1) { + override fun ProtectedAccess.throwAt( + target: PathingEntity, + attack: CombatAttack.Ranged, + weapon: ItemServerType, + ) { + anim("seq.human_dragon_taxe_spec") + spotanim("spotanim.dragon_taxe_launch_spec", height = 96, slot = COMBAT_SLOT) + val damage = manager.rollRangedDamage(this, target, attack, accuracyMultiplier = 1.25) + manager.giveCombatXp(this, target, attack, damage) + throwOne(target, weapon, "spotanim.dragon_taxe_travel_spec", damage) + manager.setNextAttackDelay(this, MOMENTUM_ATTACK_DELAY) + } + } + + private companion object { + private const val COMBAT_SLOT = constants.spotanim_slot_combat + private const val GLOW_ARROW_TRAVEL = "spotanim.sp_attack_glow_arrow_travel" + private const val DRAGON_KNIFE_TRAVEL = "spotanim.dragon_tknife_travel_spec" + private const val DRAGON_KNIFE_TRAVEL_P = "spotanim.dragon_tknife_travel_spec_p" + private const val ANNIHILATE_MAX_EXTRA_TARGETS = 8 + private const val MOMENTUM_ATTACK_DELAY = 1 + } +} diff --git a/content/other/transform-rings/build.gradle.kts b/content/other/transform-rings/build.gradle.kts new file mode 100644 index 000000000..25f50f31d --- /dev/null +++ b/content/other/transform-rings/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.attr) + implementation(projects.api.pluginCommons) +} diff --git a/content/other/transform-rings/src/main/kotlin/org/rsmod/content/other/transformrings/TransformRingsScript.kt b/content/other/transform-rings/src/main/kotlin/org/rsmod/content/other/transformrings/TransformRingsScript.kt new file mode 100644 index 000000000..c42160f68 --- /dev/null +++ b/content/other/transform-rings/src/main/kotlin/org/rsmod/content/other/transformrings/TransformRingsScript.kt @@ -0,0 +1,142 @@ +package org.rsmod.content.other.transformrings + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.NpcServerType +import dev.openrune.util.Wearpos +import jakarta.inject.Inject +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.player.events.interact.HeldEquipEvents +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.worn.WornUnequipOp +import org.rsmod.api.player.worn.WornUnequipResult +import org.rsmod.api.script.onEvent +import org.rsmod.api.script.onPlayerCoordsChanged +import org.rsmod.api.script.onPlayerLogout +import org.rsmod.game.entity.Player +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The transformation rings: Ring of stone, Ring of coins and Ring of nature. + * + * Wearing one of these rings turns the player into a rock, a pile of coins or a bush by + * transmogrifying their appearance into the matching npc. The disguise lasts until the player + * moves (or removes the ring); the Ring of stone additionally unequips itself when the player + * unmorphs, as it does officially. + * + * The rings have no `Rub` or `Operate` option - equipping is the trigger - so this listens for + * the ring wearpos changing rather than for an obj op. + */ +class TransformRingsScript @Inject constructor(private val unequipOp: WornUnequipOp) : + PluginScript() { + private val rings: Map = + TransformRing.entries.associateBy { it.obj.asRSCM(RSCMType.OBJ) } + + override fun ScriptContext.startup() { + onEvent { + if (wearpos == Wearpos.Ring) { + onRingChange(player) + } + } + onPlayerCoordsChanged { player.revertIfMoved() } + onPlayerLogout { player.clearTransform() } + } + + private fun onRingChange(player: Player) { + val worn = player.worn[Wearpos.Ring.slot] + val ring = worn?.let { rings[it.id] } + if (ring == null) { + player.clearTransform() + return + } + player.transform(ring) + } + + private fun Player.transform(ring: TransformRing) { + val npcType = ring.npcType() + transmog = npcType + attr[TRANSFORM_RING_ATTR] = ring + attr[TRANSFORM_COORDS_ATTR] = coords + mes(ring.message) + } + + /** + * The coords-changed event fires every cycle whether or not the player moved, so the tile the + * player transformed on is what decides whether they have moved since. + */ + private fun Player.revertIfMoved() { + val ring = attr[TRANSFORM_RING_ATTR] ?: return + val origin = attr[TRANSFORM_COORDS_ATTR] + if (origin == coords) { + return + } + clearTransform() + if (ring.unequipOnRevert) { + unequipRing(ring) + } + } + + private fun Player.clearTransform() { + if (attr[TRANSFORM_RING_ATTR] == null) { + return + } + attr.remove(TRANSFORM_RING_ATTR) + attr.remove(TRANSFORM_COORDS_ATTR) + transmog = null + } + + /** + * Moves the ring back to the inventory. The ring stays worn if the inventory is full - the + * player has already unmorphed either way. + */ + private fun Player.unequipRing(ring: TransformRing) { + val worn = worn[Wearpos.Ring.slot] ?: return + if (rings[worn.id] != ring) { + return + } + val result = unequipOp.unequip(this, Wearpos.Ring.slot, this.worn, inv) + if (result is WornUnequipResult.Fail.NotEnoughInvSpace) { + mes("Your ring stays on as you have no free inventory space.") + } + } + + private enum class TransformRing( + val obj: String, + val npc: String, + val message: String, + val unequipOnRevert: Boolean, + ) { + Stone( + obj = "obj.enchanted_onyx_ring", + npc = "npc.tzhaar_morph_rock", + message = "You turn into a rock. Moving will turn you back.", + unequipOnRevert = true, + ), + Coins( + obj = "obj.ring_of_coins", + npc = "npc.coins_transmog", + message = "You turn into a pile of coins. Moving will turn you back.", + unequipOnRevert = false, + ), + Nature( + obj = "obj.ring_of_nature", + npc = "npc.bush_transmog", + message = "You turn into a bush. Moving will turn you back.", + unequipOnRevert = false, + ); + + fun npcType(): NpcServerType = + ServerCacheManager.getNpc(npc.asRSCM(RSCMType.NPC)) ?: error("Missing npc type: $npc") + } + + private companion object { + /** The ring the player is currently disguised by; absent when they look like themselves. */ + private val TRANSFORM_RING_ATTR = AttributeKey() + + /** Where the player stood when they transformed; leaving that tile ends the disguise. */ + private val TRANSFORM_COORDS_ATTR = AttributeKey() + } +} diff --git a/content/other/xp-rates/build.gradle.kts b/content/other/xp-rates/build.gradle.kts new file mode 100644 index 000000000..37535943f --- /dev/null +++ b/content/other/xp-rates/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(libs.bundles.logging) + implementation(libs.jackson.dataformat.yaml) + implementation(libs.jackson.module.kotlin) + implementation(projects.api.attr) + implementation(projects.api.pluginCommons) + implementation(projects.api.realm) +} diff --git a/content/other/xp-rates/src/main/kotlin/org/rsmod/content/other/xprates/XpLevelReset.kt b/content/other/xp-rates/src/main/kotlin/org/rsmod/content/other/xprates/XpLevelReset.kt new file mode 100644 index 000000000..2383b9655 --- /dev/null +++ b/content/other/xp-rates/src/main/kotlin/org/rsmod/content/other/xprates/XpLevelReset.kt @@ -0,0 +1,70 @@ +package org.rsmod.content.other.xprates + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.StatType +import jakarta.inject.Inject +import org.rsmod.api.player.output.UpdateStat +import org.rsmod.api.player.stat.PlayerSkillXP +import org.rsmod.api.player.ui.PlayerInterfaceUpdates +import org.rsmod.api.stats.levelmod.InvisibleLevels +import org.rsmod.game.entity.Player +import org.rsmod.game.stat.PlayerSkillXPTable + +/** + * Drops every skill back to the values a brand new character starts with: level 1 with no xp, plus + * Hitpoints at its `minLevel` (10 in Old School RuneScape) with the xp to match. + * + * Those are exactly the values [org.rsmod.api.stats.plugin.InitialStatsScript] seeds a new account + * with, read from the same cache field, so a reset character is indistinguishable from a fresh one. + * Skills are all this touches - inventory, bank, quest progress, location and appearance are left + * alone. + * + * There is deliberately no shared engine helper for lowering xp; both admin plugins in this repo + * carry their own local copy, with a note that xp reduction is not a normal gameplay operation. + * Unlike their general "set this skill to level N" helpers, this one only ever moves down to a + * known-good starting point, so it can write the stat map directly rather than unwinding level by + * level. + */ +class XpLevelReset @Inject constructor(private val invisibleLevels: InvisibleLevels) { + /** + * Resets every released skill on [player] and re-sends them to the client. + * + * @return the number of skills that were above their starting values, so callers can stay quiet + * when there was nothing to reset (a brand new character, or a second pass). + */ + fun reset(player: Player): Int { + var changed = 0 + for (type in releasedStats()) { + if (player.resetStat(type)) { + changed++ + } + } + player.appearance.combatLevel = PlayerSkillXP.calculateCombatLevel(player) + PlayerInterfaceUpdates.updateCombatLevel(player) + return changed + } + + private fun Player.resetStat(type: StatType): Boolean { + val internal = RSCM.getReverseMapping(RSCMType.STAT, type.id) + + // `minLevel` is 10 for Hitpoints and 1 for everything else; the table has no entry for 0. + val level = type.minLevel.coerceAtLeast(1) + val fineXp = PlayerSkillXPTable.getFineXPFromLevel(level) + val changed = statMap.getFineXP(internal) != fineXp + + statMap.setFineXP(internal, fineXp) + statMap.setBaseLevel(internal, level.toByte()) + statMap.setCurrentLevel(internal, level.toByte()) + + val hiddenLevel = level + invisibleLevels.get(this, internal) + UpdateStat.update(this, type, statMap.getXP(internal), level, hiddenLevel) + return changed + } + + private companion object { + fun releasedStats(): List = + ServerCacheManager.getStats().values.filterNot(StatType::unreleased) + } +} diff --git a/content/other/xp-rates/src/main/kotlin/org/rsmod/content/other/xprates/XpRateConfig.kt b/content/other/xp-rates/src/main/kotlin/org/rsmod/content/other/xprates/XpRateConfig.kt new file mode 100644 index 000000000..b98de2b01 --- /dev/null +++ b/content/other/xp-rates/src/main/kotlin/org/rsmod/content/other/xprates/XpRateConfig.kt @@ -0,0 +1,135 @@ +package org.rsmod.content.other.xprates + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import com.github.michaelbull.logging.InlineLogger +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.exists +import kotlin.io.path.writeText + +/** + * Experience rate settings, read from `xp-rates.yml` next to `game.yml`. + * + * @param rate The per-player experience multiplier. `1.0` is authentic Old School RuneScape. + * @param globalRate A second multiplier compounded on top of [rate], meant for temporary + * server-wide events such as a double xp weekend. + * @param enforceOnLogin When `true`, [rate] is written onto every character as they log in, + * replacing whatever rate is stored on their account. Turn this off if you hand out individual + * rates with `::setxprate` and want them to stick. + * @param resetExistingLevels When `true`, each character has their skills reset to fresh-account + * values the first time they log in, to clear levels earned at an old inflated rate. This is + * destructive and irreversible, so it defaults to `false`. Every character is flagged once it has + * been reset, so this only ever fires once per character and is safe to leave on. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +data class XpRateConfig( + val rate: Double = AUTHENTIC_RATE, + @JsonProperty("global-rate") val globalRate: Double = AUTHENTIC_RATE, + @JsonProperty("enforce-on-login") val enforceOnLogin: Boolean = true, + @JsonProperty("reset-existing-levels") val resetExistingLevels: Boolean = false, +) { + /** + * Returns a copy with both rates clamped into [MIN_RATE]`..`[MAX_RATE], falling back to + * [AUTHENTIC_RATE] for values that are not finite. A hand-edited config should never be able to + * push [org.rsmod.api.player.stat.statAdvance] into the overflow it throws on. + */ + fun sanitized(): XpRateConfig = + copy(rate = clampRate(rate), globalRate = clampRate(globalRate)) + + companion object { + /** Old School RuneScape's own rate. See https://oldschool.runescape.wiki/w/Experience. */ + const val AUTHENTIC_RATE: Double = 1.0 + + const val MIN_RATE: Double = 0.01 + const val MAX_RATE: Double = 10_000.0 + + fun clampRate(rate: Double): Double = + if (rate.isFinite()) rate.coerceIn(MIN_RATE, MAX_RATE) else AUTHENTIC_RATE + } +} + +/** + * Reads [XpRateConfig] from disk, writing a commented default file the first time the server boots + * with this plugin installed. + * + * The default file is written from a literal template rather than serialized from [XpRateConfig] so + * that the comments explaining each knob survive; a server owner editing this by hand is the whole + * point of the file existing. + */ +class XpRateConfigLoader { + private val logger = InlineLogger() + + fun loadOrCreate(file: Path = DEFAULT_FILE): XpRateConfig { + if (!file.exists()) { + file.writeText(DEFAULT_FILE_CONTENTS) + logger.info { "Created default xp rate config in file: $file" } + return XpRateConfig() + } + return load(file) + } + + fun load(file: Path): XpRateConfig { + val parsed = + runCatching { yamlMapper.readValue(file.toFile(), XpRateConfig::class.java) } + .getOrElse { error -> + logger.warn(error) { "Could not read '$file'; using authentic xp rates." } + return XpRateConfig() + } + val sanitized = parsed.sanitized() + if (sanitized != parsed) { + logger.warn { + "Clamped out-of-range xp rates in '$file': $parsed -> $sanitized " + + "(valid range: ${XpRateConfig.MIN_RATE}-${XpRateConfig.MAX_RATE})" + } + } + return sanitized + } + + companion object { + val DEFAULT_FILE: Path = Paths.get("./", "xp-rates.yml") + + private val yamlMapper: ObjectMapper = ObjectMapper(YAMLFactory()).registerKotlinModule() + + private val DEFAULT_FILE_CONTENTS = + """ + # Experience rates. + # + # 1.0 is authentic Old School RuneScape. Every skill keeps the xp values Jagex uses - + # 4 xp per damage in combat, 25 xp per willow log, and so on - and this multiplier is + # applied on top of them. + # + # https://oldschool.runescape.wiki/w/Experience + # https://oldschool.runescape.wiki/w/Skills + # + # `rate` is each player's personal multiplier. It is stored on the character, so it can + # also be set per player in game with `::setxprate [player]`. + rate: 1.0 + + # `global-rate` is compounded on top of `rate` and applies to everyone. It is meant for + # temporary events like a double xp weekend - set it to 2.0, then back to 1.0 after. + global-rate: 1.0 + + # Re-apply `rate` to every character as they log in, replacing the rate stored on their + # account. Leave this on to keep the whole server at `rate`; turn it off if you want + # `::setxprate` to grant lasting per-player rates. + enforce-on-login: true + + # Wipe levels that were earned at an old, inflated rate. + # + # When this is on, each character's skills are reset to fresh-account values (all 1, + # Hitpoints 10) the first time they log in. Items, bank, quests and location are not + # touched. Each character is flagged once it has been reset, so this fires once per + # character and is safe to leave on afterwards. + # + # THIS IS DESTRUCTIVE AND CANNOT BE UNDONE. Turn it on deliberately, once, after you + # have settled on `rate` above. `::resetxp [player]` and `::resetxpall` do the same + # thing on demand if you would rather pick who and when. + reset-existing-levels: false + """ + .trimIndent() + "\n" + } +} diff --git a/content/other/xp-rates/src/main/kotlin/org/rsmod/content/other/xprates/XpRatesScript.kt b/content/other/xp-rates/src/main/kotlin/org/rsmod/content/other/xprates/XpRatesScript.kt new file mode 100644 index 000000000..4a770aff5 --- /dev/null +++ b/content/other/xp-rates/src/main/kotlin/org/rsmod/content/other/xprates/XpRatesScript.kt @@ -0,0 +1,345 @@ +package org.rsmod.content.other.xprates + +import com.github.michaelbull.logging.InlineLogger +import dev.or2.central.account.Rights +import jakarta.inject.Inject +import kotlin.math.roundToLong +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.protect.ProtectedAccessLauncher +import org.rsmod.api.realm.Realm +import org.rsmod.api.script.onCommand +import org.rsmod.api.script.onPlayerLogin +import org.rsmod.game.cheat.Cheat +import org.rsmod.game.entity.Player +import org.rsmod.game.entity.PlayerList +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Puts experience gain back on Old School RuneScape's own curve. + * + * The per-action xp values in `content/skills` are already authentic - they are read from the cache + * (`params.skill_xp`) or hard-coded from the wiki, and combat pays the usual 4 xp per damage with + * 1.33 hitpoints xp alongside. What inflates levelling is the *multiplier* applied on top of them + * in [org.rsmod.api.player.stat.statAdvance]: + * + * - [Player.xpRate] is the character's personal multiplier. It is seeded from the realm's + * `player_xp_rate_in_hundreds` when the character is created and then persisted on the character + * row, so it survives a realm config change. The bundled development realm seeds it at **150x**, + * which is where "levels go by way too fast" comes from. + * - [Player.globalXpRate] is a server-wide multiplier compounded on top, intended for events like a + * double xp weekend. Nothing currently assigns it, so it sits at `1.0`. + * + * This script owns both: it applies the rates from `xp-rates.yml` (default `1.0`, i.e. authentic) + * on login, which overwrites the inflated value already stored on existing characters. Because + * [Player.xpRate] is written back on save, a character only needs to log in once to be corrected + * permanently. + * + * #### Why the rate is not per-skill + * [org.rsmod.api.player.stat.statAdvance] takes a single multiplier and has no hook that sees which + * stat is being advanced, so a plugin cannot vary the rate by skill without an engine change. + * [org.rsmod.api.stats.xpmod.XpMod] looks like it could, but it is additive and only consulted by + * the gathering skills that opt into it - using it for rates would silently skip combat and every + * artisan skill. One honest global multiplier beats a per-skill one that only works in five places. + * + * #### Clearing levels earned at the old rate + * Changing the rate does not undo the levels it already handed out. [XpLevelReset] resets a + * character's skills to fresh-account values, driven either by `reset-existing-levels` (once per + * character, on login) or by `::resetxp` / `::resetxpall` on demand. + */ +class XpRatesScript +@Inject +constructor( + private val realm: Realm, + private val playerList: PlayerList, + private val levelReset: XpLevelReset, + private val protectedAccess: ProtectedAccessLauncher, +) : PluginScript() { + private val logger = InlineLogger() + private val loader = XpRateConfigLoader() + + private var config: XpRateConfig = XpRateConfig() + + /** Realms are only readable once services start, so the mismatch is reported once, on login. */ + private var warnedAboutRealmRate = false + + override fun ScriptContext.startup() { + config = loader.loadOrCreate() + logger.info { + "Xp rates loaded: rate=${config.rate}x global=${config.globalRate}x " + + "enforceOnLogin=${config.enforceOnLogin}" + } + + onPlayerLogin { player.applyRates() } + + onCommand("xprate") { + desc = "Show your current experience rate" + cheat { player.reportRates() } + } + + onCommand("setxprate") { + desc = "Set a player's personal experience rate" + requiredRights = Rights.ADMINISTRATOR + invalidArgs = "Usage: ::setxprate [player]" + cheat(::setXpRate) + } + + onCommand("globalxprate") { + desc = "Set the server-wide experience multiplier" + requiredRights = Rights.ADMINISTRATOR + invalidArgs = "Usage: ::globalxprate " + cheat(::setGlobalXpRate) + } + + onCommand("reloadxprates") { + desc = "Reload xp-rates.yml and re-apply it to everyone online" + requiredRights = Rights.ADMINISTRATOR + cheat(::reloadRates) + } + + onCommand("resetxp") { + desc = "Reset a player's skills to fresh-account levels" + requiredRights = Rights.ADMINISTRATOR + invalidArgs = "Usage: ::resetxp [player]" + cheat(::resetXp) + } + + onCommand("resetxpall") { + desc = "Reset every online player's skills to fresh-account levels" + requiredRights = Rights.ADMINISTRATOR + cheat(::resetXpAll) + } + } + + /* Login */ + + private fun Player.applyRates() { + warnAboutRealmRateOnce() + + // `globalXpRate` is not persisted on the character, so it is always re-applied. + globalXpRate = config.globalRate + + if (config.enforceOnLogin && xpRate != config.rate) { + val previous = xpRate + xpRate = config.rate + logger.info { + "Corrected stored xp rate for '$username': " + + "${format(previous)}x -> ${format(config.rate)}x" + } + } + + val effective = effectiveRate() + if (effective != XpRateConfig.AUTHENTIC_RATE) { + mes("Experience rate: ${format(effective)}x.") + } + + resetLevelsOnce() + } + + /** + * Clears levels earned at an old, inflated rate, once per character. + * + * The flag is stamped *before* the reset runs. If the reset were to throw halfway, resetting a + * character's skills again on every single login is a far worse failure than leaving one + * character half-reset for an admin to finish with `::resetxp`. + */ + private fun Player.resetLevelsOnce() { + if (!config.resetExistingLevels || attr.has(LEVELS_RESET)) { + return + } + attr[LEVELS_RESET] = RESET_STAMP + + val changed = levelReset.reset(this) + if (changed > 0) { + mes("Your skills have been reset to suit the server's experience rates.") + logger.info { "Reset $changed skills for '$username' (one-time xp rate migration)." } + } + } + + /** + * The realm's `player_xp_rate_in_hundreds` is what stamps a rate onto newly created characters. + * Overriding it on login fixes the symptom for every character, but leaving the realm row alone + * means the mismatch is invisible - so say it out loud once per boot. + */ + private fun warnAboutRealmRateOnce() { + if (warnedAboutRealmRate || !config.enforceOnLogin) { + return + } + warnedAboutRealmRate = true + val realmRate = runCatching { realm.config.baseXpRate }.getOrNull() ?: return + if (realmRate != config.rate) { + logger.warn { + "Realm '${realm.name}' assigns new characters ${format(realmRate)}x xp, but " + + "xp-rates.yml enforces ${format(config.rate)}x. Characters are corrected on " + + "login; set the realm's `player_xp_rate_in_hundreds` to " + + "${(config.rate * 100).roundToLong()} to fix it at the source." + } + } + } + + /* Commands */ + + private fun Player.reportRates() { + mes("Your experience rate: ${format(effectiveRate())}x.") + if (globalXpRate != XpRateConfig.AUTHENTIC_RATE) { + mes( + "(personal ${format(xpRate)}x " + + "x server-wide ${format(globalXpRate)}x)" + ) + } + } + + private fun setXpRate(cheat: Cheat) = + with(cheat) { + val rate = XpRateConfig.clampRate(args[0].toDouble()) + val targetName = args.getOrNull(1) + val target = + if (targetName == null) { + player + } else { + findOnline(targetName) + ?: run { + player.mes("No online player found named '$targetName'.") + return + } + } + + target.xpRate = rate + target.mes("Your experience rate is now ${format(rate)}x.") + if (target !== player) { + player.mes("Set ${target.displayName}'s experience rate to ${format(rate)}x.") + } + if (config.enforceOnLogin) { + player.mes( + "Note: `enforce-on-login` is on, so this resets to " + + "${format(config.rate)}x on their next login." + ) + } + } + + private fun setGlobalXpRate(cheat: Cheat) = + with(cheat) { + val rate = XpRateConfig.clampRate(args[0].toDouble()) + config = config.copy(globalRate = rate) + for (online in playerList) { + online.globalXpRate = rate + online.mes("Server-wide experience rate is now ${format(rate)}x.") + } + player.mes("Edit xp-rates.yml to keep this across a reboot.") + logger.info { "'${player.username}' set the server-wide xp rate to ${format(rate)}x." } + } + + private fun reloadRates(cheat: Cheat) = + with(cheat) { + config = loader.loadOrCreate() + for (online in playerList) { + online.globalXpRate = config.globalRate + if (config.enforceOnLogin) { + online.xpRate = config.rate + } + } + player.mes( + "Reloaded xp-rates.yml: rate ${format(config.rate)}x, " + + "server-wide ${format(config.globalRate)}x." + ) + } + + private fun resetXp(cheat: Cheat) = + with(cheat) { + val targetName = args.getOrNull(0) + val target = + if (targetName == null) { + player + } else { + findOnline(targetName) + ?: run { + player.mes("No online player found named '$targetName'.") + return + } + } + val label = if (target === player) "your own character" else target.displayName + + player.confirmThen("Reset every skill on $label?") { + val changed = applyReset(target) + if (target === player) { + player.mes("Reset $changed ${plural(changed, "skill")}.") + } else { + player.mes("Reset $changed ${plural(changed, "skill")} on ${target.displayName}.") + target.mes("An administrator has reset your skills.") + } + logger.info { + "'${player.username}' reset $changed skills on '${target.username}'." + } + } + } + + private fun resetXpAll(cheat: Cheat) = + with(cheat) { + val online = playerList.count() + player.confirmThen("Reset every skill on all $online online ${plural(online, "player")}?") { + // Re-read the list: players can log in or out while the dialogue is open. + var reset = 0 + for (target in playerList.toList()) { + applyReset(target) + if (target !== player) { + target.mes("An administrator has reset your skills.") + } + reset++ + } + player.mes("Reset the skills of $reset ${plural(reset, "player")}.") + logger.warn { "'${player.username}' reset the skills of $reset online players." } + } + } + + /* Helpers */ + + /** + * Resets [target]'s skills and marks them as migrated, so the one-time `reset-existing-levels` + * pass does not repeat the work on their next login. + */ + private fun applyReset(target: Player): Int { + val changed = levelReset.reset(target) + target.attr[LEVELS_RESET] = RESET_STAMP + return changed + } + + /** + * Puts a yes/no dialogue in front of an irreversible command. The cheat handler itself is not a + * coroutine, so the dialogue needs its own [ProtectedAccess]; if the admin is mid-interaction + * the launcher declines and says so, rather than silently doing nothing. + */ + private fun Player.confirmThen(question: String, action: () -> Unit) { + protectedAccess.launch(this, busyText = BUSY_TEXT) { + if (choice2("Yes", true, "No", false, title = question)) { + action() + } + } + } + + private fun Player.effectiveRate(): Double = xpRate * globalXpRate + + private fun plural(count: Int, noun: String): String = if (count == 1) noun else "${noun}s" + + private fun findOnline(name: String): Player? = + playerList.firstOrNull { it.displayName.equals(name.replace('_', ' '), ignoreCase = true) } + + private fun format(rate: Double): String { + val rounded = (rate * 100).roundToLong() / 100.0 + return if (rounded % 1.0 == 0.0) rounded.toLong().toString() else rounded.toString() + } + + private companion object { + private const val BUSY_TEXT = "Please finish what you're doing first." + + /** + * Marks a character as having had the one-time level reset. Persisted attributes round-trip + * through JSON, and the [AttributeKey] docs warn off floating point, so this is an `Int` + * holding [RESET_STAMP] rather than a boolean flag. + */ + private val LEVELS_RESET = AttributeKey(persistenceKey = "xp_rates_levels_reset") + + private const val RESET_STAMP = 1 + } +} diff --git a/content/other/xp-rates/src/test/kotlin/org/rsmod/content/other/xprates/XpRateConfigTest.kt b/content/other/xp-rates/src/test/kotlin/org/rsmod/content/other/xprates/XpRateConfigTest.kt new file mode 100644 index 000000000..852da8981 --- /dev/null +++ b/content/other/xp-rates/src/test/kotlin/org/rsmod/content/other/xprates/XpRateConfigTest.kt @@ -0,0 +1,102 @@ +package org.rsmod.content.other.xprates + +import java.nio.file.Path +import kotlin.io.path.exists +import kotlin.io.path.writeText +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +class XpRateConfigTest { + @Test + fun `defaults to authentic osrs rates`() { + val config = XpRateConfig() + + assertEquals(XpRateConfig.AUTHENTIC_RATE, config.rate) + assertEquals(XpRateConfig.AUTHENTIC_RATE, config.globalRate) + assertTrue(config.enforceOnLogin) + } + + /** + * Wiping every character's skills is destructive and irreversible, so installing this plugin + * must never do it without the owner turning it on by hand. + */ + @Test + fun `level reset is off by default`() { + assertFalse(XpRateConfig().resetExistingLevels) + assertFalse(XpRateConfigLoader().load(Path.of("does-not-exist.yml")).resetExistingLevels) + } + + @Test + fun `level reset can be turned on`(@TempDir dir: Path) { + val file = dir.resolve("xp-rates.yml") + file.writeText("reset-existing-levels: true\n") + + assertTrue(XpRateConfigLoader().load(file).resetExistingLevels) + } + + @Test + fun `sanitize clamps out of range rates`() { + val tooHigh = XpRateConfig(rate = 1_000_000.0, globalRate = 0.0).sanitized() + + assertEquals(XpRateConfig.MAX_RATE, tooHigh.rate) + assertEquals(XpRateConfig.MIN_RATE, tooHigh.globalRate) + } + + /** + * A non-finite rate makes `statAdvance` throw on the fine-xp conversion, killing whatever action + * granted the xp, so it must never reach a player. + */ + @Test + fun `sanitize replaces non-finite rates`() { + val broken = + XpRateConfig(rate = Double.POSITIVE_INFINITY, globalRate = Double.NaN).sanitized() + + assertEquals(XpRateConfig.AUTHENTIC_RATE, broken.rate) + assertEquals(XpRateConfig.AUTHENTIC_RATE, broken.globalRate) + } + + @Test + fun `loads kebab case keys`(@TempDir dir: Path) { + val file = dir.resolve("xp-rates.yml") + file.writeText("rate: 5.0\nglobal-rate: 2.0\nenforce-on-login: false\n") + + val config = XpRateConfigLoader().load(file) + + assertEquals(5.0, config.rate) + assertEquals(2.0, config.globalRate) + assertEquals(false, config.enforceOnLogin) + } + + @Test + fun `load clamps rates read from disk`(@TempDir dir: Path) { + val file = dir.resolve("xp-rates.yml") + file.writeText("rate: -3.0\n") + + assertEquals(XpRateConfig.MIN_RATE, XpRateConfigLoader().load(file).rate) + } + + @Test + fun `malformed file falls back to authentic rates`(@TempDir dir: Path) { + val file = dir.resolve("xp-rates.yml") + file.writeText("rate: [not, a, number\n") + + assertEquals(XpRateConfig(), XpRateConfigLoader().load(file)) + } + + /** + * The default file is a hand-written template rather than serialized output, so it can drift + * from [XpRateConfig]'s defaults without anything else noticing. + */ + @Test + fun `created default file parses back to the defaults`(@TempDir dir: Path) { + val file = dir.resolve("xp-rates.yml") + val loader = XpRateConfigLoader() + + assertEquals(XpRateConfig(), loader.loadOrCreate(file)) + assertTrue(file.exists()) + assertEquals(XpRateConfig(), loader.load(file)) + } +} diff --git a/content/quest/build.gradle.kts b/content/quest/build.gradle.kts index 2455c5984..74e617a10 100644 --- a/content/quest/build.gradle.kts +++ b/content/quest/build.gradle.kts @@ -6,5 +6,8 @@ plugins { dependencies { implementation(projects.api.pluginCommons) implementation(projects.api.attr) + implementation(projects.api.instances) + implementation(projects.api.music) + implementation(projects.content.other.pets) implementation(projects.api.serverConfig) } diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/DramenTree.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/DramenTree.kt new file mode 100644 index 000000000..0e40cd5c3 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/DramenTree.kt @@ -0,0 +1,157 @@ +package org.rsmod.content.quest.area.lumbridge.lostcity + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.hunt.HuntVis +import jakarta.inject.Inject +import org.rsmod.api.death.NpcDeath +import org.rsmod.api.hunt.NpcSearch +import org.rsmod.api.npc.access.StandardNpcAccess +import org.rsmod.api.npc.interact.AiPlayerInteractions +import org.rsmod.api.npc.opPlayer2 +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.protect.ProtectedAccessLauncher +import org.rsmod.api.player.stat.woodcuttingLvl +import org.rsmod.api.repo.npc.NpcRepository +import org.rsmod.api.script.onNpcQueue +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.stats.levelmod.InvisibleLevels +import org.rsmod.content.quest.area.lumbridge.lostcity.LostCityQuest.Companion.DRAMEN_BRANCH +import org.rsmod.content.quest.area.lumbridge.lostcity.LostCityQuest.Companion.STAGE_MET_SHAMUS +import org.rsmod.content.quest.area.lumbridge.lostcity.LostCityQuest.Companion.WOODCUTTING_REQ +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PlayerList +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext +import org.rsmod.routefinder.collision.CollisionFlagMap + +/** + * The Dramen tree in the caves beneath Entrana and the Tree spirit that guards it. + * + * The first swing at the tree summons the spirit, a level 101 undead guardian that goes straight + * for the player. Until it is beaten every attempt just provokes it again. Once it is dead the + * tree yields Dramen branches for the rest of the session; the spirit gives up and fades away if + * it is left alone for ten minutes, and comes back the next time someone raises an axe. + */ +class DramenTree +@Inject +constructor( + private val lostCity: LostCityQuest, + private val npcRepo: NpcRepository, + private val search: NpcSearch, + private val collision: CollisionFlagMap, + private val aiInteractions: AiPlayerInteractions, + private val invisibleLvls: InvisibleLevels, + private val death: NpcDeath, + private val playerList: PlayerList, + private val launcher: ProtectedAccessLauncher, +) : PluginScript() { + + private val quest + get() = lostCity.quest + + private val spiritType = + ServerCacheManager.getNpc(TREE_SPIRIT.asRSCM(RSCMType.NPC)) ?: error("Missing npc: $TREE_SPIRIT") + + override fun ScriptContext.startup() { + onOpLoc1(TREE) { chop(it.loc) } + onNpcQueue(spiritType, "queue.death") { spiritDefeated() } + } + + private suspend fun ProtectedAccess.chop(tree: BoundLocInfo) { + arriveDelay() + faceLoc(tree) + if (player.woodcuttingLvl < WOODCUTTING_REQ) { + mes("You need a Woodcutting level of $WOODCUTTING_REQ to chop down this tree.") + return + } + val axe = player.findWoodcuttingAxe() + if (axe == null) { + mes(NO_AXE_MESSAGE) + return + } + if (!lostCity.spiritDefeated.get(player)) { + provokeSpirit(tree) + return + } + if (lostCity.stage(player) == STAGE_MET_SHAMUS) { + // The kill itself advances the quest; this covers a kill that landed while the + // player was busy with something the launcher could not interrupt. + quest.advanceQuestStage(this) + } + if (inv.isFull()) { + mes("Your inventory is too full to hold any more dramen branches.") + soundSynth("synth.pillory_wrong") + return + } + val chopAnim = axe.woodcuttingAnim() + anim(chopAnim) + spam("You swing your axe at the tree.") + repeat(MAX_SWINGS) { + delay(SWING_TICKS) + if (statRandom("stat.woodcutting", CUT_LOW, CUT_HIGH, invisibleLvls)) { + resetAnim() + invAdd(inv, DRAMEN_BRANCH) + mes("You cut a branch from the Dramen tree.") + return + } + anim(chopAnim) + } + resetAnim() + } + + /** The guardian appears, or if it is already out, reminds the player who it is. */ + private fun ProtectedAccess.provokeSpirit(tree: BoundLocInfo) { + val spirit = + search.find(tree.coords, TREE_SPIRIT, SPIRIT_SEARCH_RADIUS, HuntVis.Off) + ?: summonSpirit(tree) + ?: return + spirit.facePlayer(player) + spirit.say("You must defeat me before touching the tree!") + spirit.opPlayer2(player, aiInteractions) + } + + private fun ProtectedAccess.summonSpirit(tree: BoundLocInfo): Npc? { + val tile = collision.freeTileBeside(tree, player.coords) ?: return null + val spirit = Npc(TREE_SPIRIT, tile) + npcRepo.add(spirit, SPIRIT_LINGER_TICKS) + return spirit + } + + /** + * The spirit's death queue. It drops nothing, so the default death (which would leave + * bones) is replaced; whoever dealt the most damage is the one it stops guarding against. + */ + private suspend fun StandardNpcAccess.spiritDefeated() { + val hero = findHero(playerList) + death.deathNoDrops(this) + if (hero == null) { + return + } + lostCity.spiritDefeated.set(hero, true) + launcher.launch(hero) { + if (lostCity.stage(player) == STAGE_MET_SHAMUS) { + quest.advanceQuestStage(this) + } + mesbox("With the Tree Spirit defeated you can now chop the tree.") + } + } + + private companion object { + const val TREE = "loc.dramentree" + const val TREE_SPIRIT = "npc.tree_spirit" + + /** Ten minutes of being ignored and the spirit gives up. */ + const val SPIRIT_LINGER_TICKS = 1000 + const val SPIRIT_SEARCH_RADIUS = 12 + + const val SWING_TICKS = 4 + const val MAX_SWINGS = 25 + + /** Roughly a regular tree's odds; the Dramen tree has no success-rate params of its own. */ + const val CUT_LOW = 64 + const val CUT_HIGH = 200 + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/EntranaBoat.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/EntranaBoat.kt new file mode 100644 index 000000000..21df15595 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/EntranaBoat.kt @@ -0,0 +1,204 @@ +package org.rsmod.content.quest.area.lumbridge.lostcity + +import dev.openrune.types.ItemServerType +import dev.openrune.util.Wearpos +import jakarta.inject.Inject +import org.rsmod.api.config.refs.params +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpc3 +import org.rsmod.game.entity.Player +import org.rsmod.game.type.getInvObj +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The monks' ship between Port Sarim and Entrana, and the gangplanks at both ends. + * + * Saradomin's edict: no weapons or armour may be taken to the island. The monks at Port Sarim + * search the player once per session and refuse passage while anything forbidden is carried or + * worn. What counts as forbidden follows the OSRS rules closely enough for the quest: anything + * that equips to the head, body, legs, weapon or shield slot and carries an attack, defence or + * strength bonus, with the wizard robes the island tolerates as the exception. Capes, jewellery, + * gloves, boots, ammunition and unequipable items are all allowed. + */ +class EntranaBoat @Inject constructor(private val lostCity: LostCityQuest) : PluginScript() { + + override fun ScriptContext.startup() { + for (monk in PORT_SARIM_MONKS) { + onOpNpc1(monk) { startDialogue(it.npc) { portSarimMonk() } } + onOpNpc3(monk) { startDialogue(it.npc) { boardForEntrana() } } + } + for (monk in ENTRANA_MONKS) { + onOpNpc1(monk) { startDialogue(it.npc) { entranaMonk() } } + onOpNpc3(monk) { sail(SARIM_DECK, ARRIVE_SARIM) } + } + onOpLoc1(SARIM_PLANK_ON) { cross(SARIM_DECK) } + onOpLoc1(SARIM_PLANK_OFF) { cross(SARIM_JETTY) } + onOpLoc1(ENTRANA_PLANK_ON) { cross(ENTRANA_DECK) } + onOpLoc1(ENTRANA_PLANK_OFF) { cross(ENTRANA_JETTY) } + } + + /* Port Sarim */ + + private suspend fun Dialogue.portSarimMonk() { + chatNpc(neutral, "Do you seek passage to holy Entrana? If so, you must leave your weaponry and armour behind. This is Saradomin's will.") + when ( + choice2( + "No, not right now.", 1, + "Yes, okay, I'm ready to go.", 2, + ) + ) { + 1 -> { + chatPlayer(neutral, "No, not right now.") + chatNpc(neutral, "Very well.") + } + 2 -> { + chatPlayer(happy, "Yes, okay, I'm ready to go.") + boardForEntrana() + } + } + } + + /** The search, the verdict, and the crossing if the player passes. */ + private suspend fun Dialogue.boardForEntrana() { + if (!lostCity.searchedByMonks.get(player)) { + chatNpc(neutral, "Very well. One moment please.") + mesbox("The monk quickly searches you.") + lostCity.searchedByMonks.set(player, true) + } + if (player.carriesForbiddenItem()) { + chatNpc(angry, "NO WEAPONS OR ARMOUR are permitted on holy Entrana AT ALL. We will not allow you to travel there in breach of mighty Saradomin's edict.") + chatNpc(angry, "Do not try and deceive us again. Come back when you have laid down your Zamorakian instruments of death.") + return + } + chatNpc(happy, "All is satisfactory. You may board the boat now.") + access.sail(ENTRANA_DECK, ARRIVE_ENTRANA) + } + + /* Entrana */ + + private suspend fun Dialogue.entranaMonk() { + chatNpc(quiz, "Do you wish to leave holy Entrana?") + when ( + choice2( + "Yes, I'm ready to go.", 1, + "Not just yet.", 2, + ) + ) { + 1 -> { + chatPlayer(happy, "Yes, I'm ready to go.") + chatNpc(happy, "Okay, let's board...") + access.sail(SARIM_DECK, ARRIVE_SARIM) + } + 2 -> chatPlayer(neutral, "Not just yet.") + } + } + + /* Travel */ + + private suspend fun ProtectedAccess.sail(deck: CoordGrid, arrival: String) { + fadeOverlay( + startColour = 0, + startTransparency = 255, + endColour = 0, + endTransparency = 0, + clientDuration = FADE_CLIENT_DURATION, + ) + clearHealthHud() + delay(FADE_TICKS) + telejump(deck) + delay(1) + fadeOverlay( + startColour = 0, + startTransparency = 0, + endColour = 0, + endTransparency = 255, + clientDuration = FADE_CLIENT_DURATION, + ) + clearHealthHud() + delay(FADE_TICKS) + closeFadeOverlay() + mesbox(arrival) + } + + private suspend fun ProtectedAccess.cross(dest: CoordGrid) { + arriveDelay() + delay(1) + telejump(dest) + } + + /* Saradomin's edict */ + + private fun Player.carriesForbiddenItem(): Boolean { + val carried = inv.filterNotNull { true } + worn.filterNotNull { true } + return carried.any { getInvObj(it).isForbiddenOnEntrana() } + } + + private fun ItemServerType.isForbiddenOnEntrana(): Boolean { + val slot = Wearpos[wearpos1] ?: return false + if (slot !in FORBIDDEN_SLOTS || internalName in TOLERATED) { + return false + } + return BONUS_PARAMS.any { (paramOrNull(it) ?: 0) != 0 } + } + + private companion object { + val PORT_SARIM_MONKS = listOf("npc.shipmonk", "npc.shipmonk1_b", "npc.shipmonk1_c") + val ENTRANA_MONKS = listOf("npc.shipmonk2", "npc.shipmonk2_b", "npc.shipmonk2_c") + + const val SARIM_PLANK_ON = "loc.ship_to_entrana_on" + const val SARIM_PLANK_OFF = "loc.ship_to_entrana_off" + const val ENTRANA_PLANK_ON = "loc.ship_from_entrana_on" + const val ENTRANA_PLANK_OFF = "loc.ship_from_entrana_off" + + /** The ship decks sit a level above the jetties, which are bridged down to ground level. */ + val SARIM_DECK = CoordGrid(3048, 3231, 1) + val SARIM_JETTY = CoordGrid(3048, 3234, 0) + val ENTRANA_DECK = CoordGrid(2834, 3331, 1) + val ENTRANA_JETTY = CoordGrid(2834, 3335, 0) + + const val ARRIVE_ENTRANA = "The ship arrives at Entrana." + const val ARRIVE_SARIM = "The ship arrives at Port Sarim." + + const val FADE_CLIENT_DURATION = 50 + const val FADE_TICKS = 3 + + val FORBIDDEN_SLOTS = + setOf(Wearpos.Hat, Wearpos.Torso, Wearpos.Legs, Wearpos.RightHand, Wearpos.LeftHand) + + /** Robes the monks let through despite their small magic bonuses. */ + val TOLERATED = + setOf( + "obj.wizards_robe", + "obj.wizards_robe_trim", + "obj.wizards_robe_trim_gold", + "obj.bluewizhat", + "obj.black_robe", + "obj.black_wizards_robe_trim", + "obj.black_wizards_robe_gold", + "obj.blackwizhat", + "obj.blue_skirt", + "obj.black_skirt", + ) + + val BONUS_PARAMS = + listOf( + params.attack_stab, + params.attack_slash, + params.attack_crush, + params.attack_magic, + params.attack_ranged, + params.defence_stab, + params.defence_slash, + params.defence_crush, + params.defence_magic, + params.defence_ranged, + params.melee_strength, + params.ranged_strength, + ) + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/EntranaDungeon.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/EntranaDungeon.kt new file mode 100644 index 000000000..193d1aaea --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/EntranaDungeon.kt @@ -0,0 +1,113 @@ +package org.rsmod.content.quest.area.lumbridge.lostcity + +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.prayerLvl +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext +import org.rsmod.routefinder.collision.CollisionFlagMap + +/** + * The way into and out of the caves beneath Entrana. The cave monk warns anyone about to climb + * down; the ladder is one way and the evil below drains the player's prayer to nothing. The only + * exit is the magic door in the south-east, which drops the player deep in the Wilderness. + */ +class EntranaDungeon @Inject constructor(private val collision: CollisionFlagMap) : PluginScript() { + + override fun ScriptContext.startup() { + onOpLoc1(LADDER_TOP) { climbDown(it.loc) } + onOpNpc1(CAVE_MONK) { startDialogue(it.npc) { warning() } } + onOpLoc1(MAGIC_DOOR) { magicDoor(it.loc) } + } + + private suspend fun ProtectedAccess.climbDown(ladder: BoundLocInfo) { + arriveDelay() + faceLoc(ladder) + var proceed = false + startDialogue { + chatNpcSpecific("Cave monk", CAVE_MONK, worried, WARNING_1) + chatNpcSpecific("Cave monk", CAVE_MONK, worried, WARNING_2) + chatNpcSpecific("Cave monk", CAVE_MONK, worried, WARNING_3) + when ( + choice2( + "I don't think I'm strong enough to enter then.", 1, + "Well that is a risk I will have to take.", 2, + ) + ) { + 1 -> chatPlayer(worried, "I don't think I'm strong enough to enter then.") + 2 -> { + chatPlayer(neutral, "Well that is a risk I will have to take.") + proceed = true + } + } + } + if (!proceed) { + return + } + anim(CLIMB_DOWN_SEQ) + delay(2) + telejump(collision.nearestFree(DUNGEON_ARRIVAL) ?: DUNGEON_ARRIVAL) + // The evil below cuts the monks' gods off; the prayer drain is the quest's whole reason + // for warning the player first. + val prayer = player.prayerLvl + if (prayer > 0) { + statSub("stat.prayer", constant = prayer, percent = 0) + } + } + + private suspend fun Dialogue.warning() { + chatNpc(worried, WARNING_1) + chatNpc(worried, WARNING_2) + chatNpc(worried, WARNING_3) + when ( + choice2( + "I don't think I'm strong enough to enter then.", 1, + "Well that is a risk I will have to take.", 2, + ) + ) { + 1 -> chatPlayer(worried, "I don't think I'm strong enough to enter then.") + 2 -> chatPlayer(neutral, "Well that is a risk I will have to take.") + } + } + + private suspend fun ProtectedAccess.magicDoor(door: BoundLocInfo) { + arriveDelay() + faceLoc(door) + soundSynth(DOOR_SOUND) + delay(1) + telejump(collision.nearestFree(WILDERNESS_EXIT) ?: WILDERNESS_EXIT) + mes("You step through the magic door and find yourself deep in the Wilderness.") + } + + private companion object { + const val LADDER_TOP = "loc.entranaladdertop" + const val CAVE_MONK = "npc.cave_monk" + + /** Despite the cache name, this is the wilderness door in the Entrana caves. */ + const val MAGIC_DOOR = "loc.zanarismagicdoor" + + const val CLIMB_DOWN_SEQ = "seq.human_reachforladder" + const val DOOR_SOUND = "synth.door_open" + + /** Foot of the one-way ladder, under the surface ladder at 2820,3374. */ + val DUNGEON_ARRIVAL = CoordGrid(2822, 9774, 0) + + /** Level 32 Wilderness, where the magic door leads. */ + val WILDERNESS_EXIT = CoordGrid(3250, 3772, 0) + + const val WARNING_1 = + "Be careful going in there! You are unarmed, and there is much evilness lurking " + + "down there! The evilness seems to block off our contact with our gods," + const val WARNING_2 = + "so our prayers seem to have less effect down there. Oh, also, you won't be able " + + "to come back this way - This ladder only goes one way!" + const val WARNING_3 = + "The only exit from the caves below is a portal which leads only to the deepest " + + "wilderness!" + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/LostCityQuest.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/LostCityQuest.kt new file mode 100644 index 000000000..dfdb6ba06 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/LostCityQuest.kt @@ -0,0 +1,156 @@ +package org.rsmod.content.quest.area.lumbridge.lostcity + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Singleton +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.righthand +import org.rsmod.content.quest.manager.ItemRewardDisplay +import org.rsmod.content.quest.manager.QuestScript +import org.rsmod.content.quest.manager.rewards +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Lost City. + * + * Stages (stored in `varp.zanaris`, endstate from `dbrow.quest_lostcity`): + * - [STAGE_STARTED]: the Warrior at the swamp camp let slip that a leprechaun in a nearby tree + * knows the way to Zanaris. + * - [STAGE_MET_SHAMUS]: Shamus explained the shed portal and the Dramen staff. + * - [STAGE_SPIRIT_DEFEATED]: the Tree spirit guarding the Dramen tree has been beaten. + * - Complete: the player entered Zanaris through the shed while wielding a Dramen staff. + * + * The quest varp has no varbits sharing it in this cache revision, so the stage is the only + * thing stored there; everything else lives in quest attributes. + */ +@Singleton +class LostCityQuest : QuestScript( + "quest_lostcity", + "varp.zanaris", + rewards { extra("Access to Zanaris") }, + ItemRewardDisplay(DRAMEN_STAFF), +) { + /** The Warrior has already been thanked once after the quest. */ + val thankedWarrior = quest.attribute(name = "THANKED_WARRIOR", default = false) + + /** + * The Tree spirit has been beaten this login session. It is not persisted on purpose: the + * spirit returns to guard the tree the next time the player comes back to the dungeon, as it + * does when it despawns after being ignored for too long. + */ + val spiritDefeated = quest.attribute(name = "SPIRIT_DEFEATED", default = false, temp = true) + + /** The Port Sarim monks have searched the player once this session. */ + val searchedByMonks = quest.attribute(name = "SEARCHED_BY_MONKS", default = false, temp = true) + + private val staffIds = FAIRY_STAFFS.map { it.asRSCM(RSCMType.OBJ) }.toSet() + + override fun ScriptContext.init() {} + + override fun subTitle(): String = + "talking to the Warrior at the adventurers' camp on the north-west " + + "edge of Lumbridge Swamp, south of Draynor Village." + + override fun questLog(player: ProtectedAccess) = + questJournal(player) { + objective( + "A group of adventurers have made camp in Lumbridge Swamp. The " + + "Warrior let slip that they are searching for Zanaris, a " + + "hidden city, and that a leprechaun hiding in one of the trees " + + "near the camp knows the way there.", + ) {} + + objective( + "I should look for the tree the leprechaun is hiding in and chop it " + + "to get him to come out.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_STARTED } + } + + objective( + "Shamus the leprechaun told me the doorway of the shed " + + "in the middle of the swamp is a portal to Zanaris, but only if I am " + + "carrying a Dramen staff.", + ) { + visibleWhen { quest.getQuestStage(access.player) >= STAGE_MET_SHAMUS } + } + + objective( + "Dramen staffs are crafted from branches of the Dramen tree, which " + + "grows in a cave on the island of Entrana. The monks run a ship " + + "there from Port Sarim, but they will not let me bring any " + + "weapons or armour.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_MET_SHAMUS } + } + + objective( + "I defeated the Tree spirit guarding the Dramen tree. I can now cut " + + "a Dramen branch from it and use a knife on the " + + "branch to carve a staff.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_SPIRIT_DEFEATED } + hasItem("dramen_branch", "I have a Dramen branch.").strike() + hasItem("dramen_staff", "I have made a Dramen staff.").strike() + } + + objective( + "I should wield the Dramen staff and enter the shed in " + + "the middle of Lumbridge Swamp.", + ) { + visibleWhen { + quest.getQuestStage(access.player) == STAGE_SPIRIT_DEFEATED && + (access.inv.contains(DRAMEN_STAFF) || wieldsDramenStaff(access.player)) + } + } + } + + override fun completedLog(player: ProtectedAccess): String = + completionJournal(player) { + line( + "A group of adventurers camped in Lumbridge Swamp were searching for the lost " + + "city of Zanaris. Their Warrior let slip that a leprechaun hiding in a " + + "nearby tree knew the way.", + ) + line( + "Shamus the leprechaun told me the shed in the swamp is a portal to Zanaris " + + "for anyone carrying a Dramen staff. I sailed to Entrana, fought the " + + "Tree spirit guarding the Dramen tree in the caves beneath the island, " + + "cut a branch and carved it into a staff.", + ) + line("With the staff in hand I stepped through the shed door into Zanaris.") + } + + fun stage(player: Player): Int = quest.getQuestStage(player) + + /** A Dramen or Lunar staff is in the player's weapon slot. */ + fun wieldsDramenStaff(player: Player): Boolean { + val weapon = player.righthand ?: return false + return weapon.id in staffIds + } + + /** Finishes the quest for a player who has just entered Zanaris. */ + fun complete(access: ProtectedAccess) { + val remaining = quest.maxSteps - stage(access.player) + if (remaining > 0) { + quest.advanceQuestStage(access, remaining) + } + } + + companion object { + const val STAGE_STARTED = 1 + const val STAGE_MET_SHAMUS = 2 + const val STAGE_SPIRIT_DEFEATED = 3 + + const val CRAFTING_REQ = 31 + const val WOODCUTTING_REQ = 36 + const val RECOMMENDED_COMBAT = 45 + + const val DRAMEN_BRANCH = "obj.dramen_branch" + const val DRAMEN_STAFF = "obj.dramen_staff" + + /** Staffs that open the way to Zanaris. */ + val FAIRY_STAFFS = listOf(DRAMEN_STAFF, "obj.lunar_moonclan_liminal_staff") + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/LostCitySupport.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/LostCitySupport.kt new file mode 100644 index 000000000..514dff557 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/LostCitySupport.kt @@ -0,0 +1,84 @@ +package org.rsmod.content.quest.area.lumbridge.lostcity + +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCMType +import org.rsmod.api.config.refs.params +import org.rsmod.api.player.righthand +import org.rsmod.api.player.stat.woodcuttingLvl +import org.rsmod.game.entity.Player +import org.rsmod.game.inv.InvObj +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.game.map.collision.isWalkBlocked +import org.rsmod.game.map.collision.isZoneValid +import org.rsmod.game.type.getInvObj +import org.rsmod.map.CoordGrid +import org.rsmod.routefinder.collision.CollisionFlagMap + +/* Helpers shared by the Lost City scripts. */ + +internal const val NO_AXE_MESSAGE = + "You do not have an axe which you have the woodcutting level to use." + +private const val AXE_CONTENT = "content.woodcutting_axe" + +/** + * The best axe the player is wielding or carrying that they have the Woodcutting level to use. + * The regular woodcutting script cannot be reused here because its axe ranking needs the + * success-rate params that the quest trees do not carry. + */ +internal fun Player.findWoodcuttingAxe(): InvObj? { + val candidates = buildList { + righthand?.let(::add) + addAll(inv.filterNotNull { true }) + } + return candidates + .filter { obj -> + val type = getInvObj(obj) + type.isContentType(AXE_CONTENT) && woodcuttingLvl >= type.axeLevelReq() + } + .maxByOrNull { getInvObj(it).axeLevelReq() } +} + +private fun dev.openrune.types.ItemServerType.axeLevelReq(): Int = + paramOrNull(params.levelrequire) ?: 1 + +/** The chopping animation the axe's cache params assign to it. */ +internal fun InvObj.woodcuttingAnim(): String { + val seq = getInvObj(this).param(params.skill_anim) + return RSCM.getReverseMapping(RSCMType.SEQ, seq.id) +} + +/** The walkable tile closest to [center] within [radius], or null if there is none. */ +internal fun CollisionFlagMap.nearestFree(center: CoordGrid, radius: Int = 2): CoordGrid? { + val tiles = mutableListOf() + for (dz in -radius..radius) { + for (dx in -radius..radius) { + tiles += center.translate(dx, dz) + } + } + return tiles + .sortedWith(compareBy({ it.chebyshevDistance(center) }, { it.z }, { it.x })) + .firstOrNull(::walkable) +} + +/** + * The walkable tile touching [loc]'s footprint that is closest to [near], or null if the loc is + * completely boxed in. + */ +internal fun CollisionFlagMap.freeTileBeside(loc: BoundLocInfo, near: CoordGrid): CoordGrid? { + val width = loc.adjustedWidth + val length = loc.adjustedLength + val tiles = mutableListOf() + for (dx in -1..width) { + for (dz in -1..length) { + val inside = dx in 0 until width && dz in 0 until length + if (!inside) { + tiles += loc.coords.translate(dx, dz) + } + } + } + return tiles.filter(::walkable).minByOrNull { it.chebyshevDistance(near) } +} + +private fun CollisionFlagMap.walkable(coords: CoordGrid): Boolean = + isZoneValid(coords) && !isWalkBlocked(coords) diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/ZanarisShed.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/ZanarisShed.kt new file mode 100644 index 000000000..5b2561774 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/ZanarisShed.kt @@ -0,0 +1,106 @@ +package org.rsmod.content.quest.area.lumbridge.lostcity + +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +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.LocShape +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The shed in the middle of Lumbridge Swamp. Its door is the portal to Zanaris: anyone opening + * it while wielding a Dramen or Lunar staff is carried off by fairy magic to the ring at the + * Zanaris entrance, and that ring brings them back into the shed. Without a staff it is just a + * shed door. + */ +class ZanarisShed @Inject constructor( + private val lostCity: LostCityQuest, + private val locRepo: LocRepository, +) : PluginScript() { + + override fun ScriptContext.startup() { + onOpLoc1(SHED_DOOR) { openDoor(it.loc) } + onOpLoc1(ENTRY_RING) { leaveZanaris() } + } + + private suspend fun ProtectedAccess.openDoor(door: BoundLocInfo) { + arriveDelay() + faceLoc(door) + if (lostCity.wieldsDramenStaff(player)) { + enterZanaris() + return + } + soundSynth(DOOR_OPEN_SOUND) + val opened = openedCoords(door) + locRepo.del(door, DOOR_TICKS) + locRepo.add(opened, SHED_DOOR_OPEN, DOOR_TICKS, door.turnAngle(1), door.shape) + } + + /** The same swing the generic single-door script gives a straight wall door. */ + private fun openedCoords(door: BoundLocInfo): CoordGrid = + when { + door.shape == LocShape.WallStraight && door.angle == LocAngle.West -> door.coords.translateX(-1) + door.shape == LocShape.WallStraight && door.angle == LocAngle.North -> door.coords.translateZ(1) + door.shape == LocShape.WallStraight && door.angle == LocAngle.East -> door.coords.translateX(1) + door.shape == LocShape.WallStraight && door.angle == LocAngle.South -> door.coords.translateZ(-1) + else -> door.coords + } + + /** The door swings open and the fairy magic in the staff whisks the player away. */ + private suspend fun ProtectedAccess.enterZanaris() { + soundSynth(DOOR_OPEN_SOUND) + delay(1) + fairyVanish() + telejump(ZANARIS_ARRIVAL) + anim(APPEAR_ANIM) + if (lostCity.quest.isQuestInProgress(player)) { + lostCity.complete(this) + } + } + + /** The ring at the Zanaris entrance is the way back out to the swamp. */ + private suspend fun ProtectedAccess.leaveZanaris() { + if (!lostCity.wieldsDramenStaff(player)) { + mes("The fairy rings only work for those who wield fairy magic.") + return + } + fairyVanish() + telejump(SHED_INSIDE) + anim(APPEAR_ANIM) + } + + /** The same send-off the fairy rings give: the vanish pose, a ring of flowers and the chime. */ + private suspend fun ProtectedAccess.fairyVanish() { + anim(VANISH_ANIM) + spotanim(FLOWER_RING) + soundSynth(TELEPORT_SOUND) + delay(VANISH_TICKS) + } + + private companion object { + const val SHED_DOOR = "loc.zanarisdoor" + const val SHED_DOOR_OPEN = "loc.zanarisdoor_open" + const val ENTRY_RING = "loc.fairy_mushroom_ring" + + const val DOOR_OPEN_SOUND = "synth.door_open" + + /** How long the opened door stays open before swinging shut on its own. */ + const val DOOR_TICKS = 500 + + /** The fairy ring just inside Zanaris, where the shed portal lets players out. */ + val ZANARIS_ARRIVAL = CoordGrid(2452, 4473, 0) + + /** Inside the swamp shed, where the entrance ring drops players off. */ + val SHED_INSIDE = CoordGrid(3203, 3169, 0) + + const val VANISH_ANIM = "seq.human_fairy_vanish" + const val APPEAR_ANIM = "seq.human_fairy_appear" + const val FLOWER_RING = "spotanim.fairy_flower_ring" + const val TELEPORT_SOUND = "synth.fairy_teleport" + const val VANISH_TICKS = 2 + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/npcs/Adventurers.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/npcs/Adventurers.kt new file mode 100644 index 000000000..54ce546b2 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/npcs/Adventurers.kt @@ -0,0 +1,302 @@ +package org.rsmod.content.quest.area.lumbridge.lostcity.npcs + +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.lumbridge.lostcity.LostCityQuest +import org.rsmod.content.quest.area.lumbridge.lostcity.LostCityQuest.Companion.CRAFTING_REQ +import org.rsmod.content.quest.area.lumbridge.lostcity.LostCityQuest.Companion.RECOMMENDED_COMBAT +import org.rsmod.content.quest.area.lumbridge.lostcity.LostCityQuest.Companion.STAGE_STARTED +import org.rsmod.content.quest.area.lumbridge.lostcity.LostCityQuest.Companion.WOODCUTTING_REQ +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The four adventurers camped on the north-west edge of Lumbridge Swamp. The Warrior starts the + * quest by letting far too much slip; the other three just want the player gone. + */ +class Adventurers @Inject constructor(private val lostCity: LostCityQuest) : PluginScript() { + + private val quest + get() = lostCity.quest + + override fun ScriptContext.startup() { + onOpNpc1(WARRIOR) { startDialogue(it.npc) { warrior() } } + onOpNpc1(ARCHER) { startDialogue(it.npc) { archer() } } + onOpNpc1(WIZARD) { startDialogue(it.npc) { wizard() } } + onOpNpc1(MONK) { startDialogue(it.npc) { monk() } } + } + + /* Warrior */ + + private suspend fun Dialogue.warrior() { + when { + quest.isQuestCompleted(player) -> warriorAfterQuest() + lostCity.stage(player) == STAGE_STARTED -> { + chatPlayer(quiz, "So let me get this straight: I need to search the trees around here for a leprechaun; and then when I find him, he will tell me where this 'Zanaris' is?") + chatNpc(worried, "What? How did you know that? Uh... I mean, no, no you're very wrong. Very wrong, and not right at all, and I definitely didn't tell you about that at all.") + } + quest.isQuestInProgress(player) -> { + chatPlayer(quiz, "Have you found anything yet?") + chatNpc(shifty, "We're still searching for Zanaris...GAH! I mean we're not doing anything here at all.") + chatPlayer(neutral, "I haven't found it yet either.") + } + else -> warriorBeforeQuest() + } + } + + private suspend fun Dialogue.warriorBeforeQuest() { + chatNpc(happy, "Hello there traveller.") + when ( + choice2( + "What are you camped out here for?", 1, + "Do you know any good adventures I can go on?", 2, + ) + ) { + 1 -> campedHere() + 2 -> goodAdventures() + } + } + + private suspend fun Dialogue.campedHere() { + chatPlayer(quiz, "What are you camped here for?") + chatNpc(shifty, "We're looking for Zanaris...GAH! I mean we're not here for any particular reason at all.") + zanarisQuestions() + } + + private suspend fun Dialogue.zanarisQuestions() { + when ( + choice3( + "Who's Zanaris?", 1, + "What's Zanaris?", 2, + "What makes you think it's out here?", 3, + ) + ) { + 1 -> whosZanaris() + 2 -> whatsZanaris() + 3 -> whatMakesYouThink() + } + } + + private suspend fun Dialogue.whosZanaris() { + chatPlayer(quiz, "Who's Zanaris?") + chatNpc(laugh, "Ahahahaha! Zanaris isn't a person! It's a magical hidden city filled with treasures and rich.. uh, nothing. It's nothing.") + when ( + choice2( + "If it's hidden how are you planning to find it?", 1, + "There's no such thing.", 2, + ) + ) { + 1 -> howToFindIt() + 2 -> noSuchThing() + } + } + + private suspend fun Dialogue.howToFindIt() { + chatPlayer(quiz, "If it's hidden how are you planning to find it?") + chatNpc(shifty, "Well, we don't want to tell anyone else about that, because we don't want anyone else sharing in all that glory and treasure.") + when ( + choice2( + "Please tell me.", 1, + "Looks like you don't know either.", 2, + ) + ) { + 1 -> pleaseTellMe() + 2 -> { + chatPlayer(neutral, "Well, it looks to me like YOU don't know EITHER seeing as you're all just sat around here.") + chatNpc(angry, "Of course we know! We just haven't found which tree the stupid leprechaun's hiding in yet!") + offerQuest() + } + } + } + + private suspend fun Dialogue.offerQuest() { + levelWarning() + when ( + choice2( + "Yes.", 1, + "No.", 2, + title = "Start the Lost City quest?", + ) + ) { + 1 -> { + chatPlayer(quiz, "Leprechaun?") + chatNpc(worried, "GAH! I didn't mean to tell you that! Look, just forget I said anything okay?") + chatPlayer(quiz, "So a leprechaun knows where Zanaris is eh?") + chatNpc(worried, "Ye.. uh, no. No, not at all. And even if he did - which he doesn't - he DEFINITELY ISN'T hiding in some tree around here. Nope, definitely not. Honestly.") + chatPlayer(happy, "Thanks for the help!") + chatNpc(worried, "Help? What help? I didn't help! Please don't say I did, I'll get in trouble!") + quest.advanceQuestStage(access) + } + 2 -> chatPlayer(neutral, "Right... Well good luck with it.") + } + } + + /** The pre-quest warning about levels the player is still short of. */ + private suspend fun Dialogue.levelWarning() { + val lowSkills = + access.statBase("stat.crafting") < CRAFTING_REQ || + access.statBase("stat.woodcutting") < WOODCUTTING_REQ + val lowCombat = player.combatLevel < RECOMMENDED_COMBAT + val text = + when { + lowSkills && lowCombat -> + "Before starting this quest, be aware that one or more of your skill levels " + + "are lower than what is required to fully complete it. Your combat level " + + "is also lower than the recommended level of $RECOMMENDED_COMBAT." + lowSkills -> + "Before starting this quest, be aware that one or more of your skill levels " + + "are lower than what is required to fully complete it." + lowCombat -> + "Before starting this quest, be aware that your combat level is lower than " + + "the recommended level of $RECOMMENDED_COMBAT." + else -> return + } + mesbox(text) + } + + private suspend fun Dialogue.noSuchThing() { + chatPlayer(neutral, "There's no such thing!") + chatNpc(shifty, "When we've found Zanaris you'll... GAH! I mean, we're not here for any particular reason at all.") + zanarisQuestions() + } + + private suspend fun Dialogue.whatsZanaris() { + chatPlayer(quiz, "What's Zanaris?") + chatNpc(shifty, "I don't think we want other people competing with us to find it. Forget I said anything.") + when ( + choice2( + "Please tell me.", 1, + "Oh well. Never mind.", 2, + ) + ) { + 1 -> pleaseTellMe() + 2 -> chatPlayer(neutral, "Oh well. Never mind.") + } + } + + private suspend fun Dialogue.whatMakesYouThink() { + chatPlayer(quiz, "What makes you think it's out here?") + chatNpc(shifty, "Don't you know of the legends that tell of the magical city, hidden in the swam... Uh, no, you're right, we're wasting our time here.") + when ( + choice2( + "If it's hidden how are you planning to find it?", 1, + "There's no such thing!", 2, + ) + ) { + 1 -> howToFindIt() + 2 -> noSuchThing() + } + } + + private suspend fun Dialogue.goodAdventures() { + chatPlayer(quiz, "Do you know any good adventures I can go on?") + chatNpc(neutral, "Well we're on an adventure right now. Mind you, this is OUR adventure and we don't want to share it - find your own!") + when ( + choice2( + "Please tell me.", 1, + "I don't think you've found a good adventure at all!", 2, + ) + ) { + 1 -> pleaseTellMe() + 2 -> { + chatPlayer(neutral, "I don't think you've found a good adventure at all!") + chatNpc(angry, "Hah! Adventurers of our calibre don't just hang around in forests for fun, whelp!") + chatPlayer(quiz, "Oh really?") + campedHere() + } + } + } + + private suspend fun Dialogue.pleaseTellMe() { + chatPlayer(quiz, "Please tell me?") + chatNpc(neutral, "No.") + chatPlayer(sad, "Please?") + chatNpc(angry, "No!") + chatPlayer(sad, "PLEEEEEEEEEEEEEEEEEEEEEASE?") + chatNpc(angry, "NO!") + } + + private suspend fun Dialogue.warriorAfterQuest() { + chatPlayer(happy, "Hey, thanks for all the information. It REALLY helped me out in finding the lost city of Zanaris and all.") + chatNpc(worried, "Oh please don't say that anymore! If the rest of my party knew I'd helped you they'd probably throw me out and make me walk home by myself!") + if (lostCity.thankedWarrior.get(player)) { + chatNpc(quiz, "So anyway, what have you found out? Where is the fabled Zanaris? Is it all the legends say it is?") + } else { + lostCity.thankedWarrior.set(player, true) + } + chatPlayer(shifty, "You know.... I think I'll keep that to myself.") + } + + /* Archer */ + + private suspend fun Dialogue.archer() { + when (lostCity.stage(player)) { + 0 -> { + chatPlayer(quiz, "Why are you guys hanging around here?") + chatNpc(angry, "(ahem)...'Guys'?") + chatPlayer(worried, "Uh... yeah, sorry about that. Why are you all standing around out here?") + chatNpc(neutral, "Well, that's really none of your business.") + } + STAGE_STARTED -> { + chatPlayer(quiz, "So I hear theres a leprechaun around here who can show me the way to Zanaris?") + chatNpc(shocked, "...W-what? How did you..?") + chatNpc(angry, "No. You're wrong. Now go away.") + } + else -> { + chatPlayer(quiz, "So you didn't find the entrance to Zanaris yet, huh?") + chatNpc(angry, "Don't tell me a novice like YOU has found it!") + chatPlayer(happy, "Yep. Found it REALLY easily too.") + chatNpc(angry, "...I cannot believe that someone like YOU could find the portal where experienced adventurers such as ourselves could not.") + chatPlayer(happy, "Believe what you want. Enjoy your little camp fire.") + } + } + } + + /* Wizard */ + + private suspend fun Dialogue.wizard() { + when (lostCity.stage(player)) { + 0 -> { + chatPlayer(quiz, "Why are all of you standing around here?") + chatNpc(laugh, "Hahaha you dare talk to a mighty wizard such as myself? I bet you can't even cast windstrike yet amateur!") + chatPlayer(bored, "...You're an idiot.") + } + STAGE_STARTED -> { + chatPlayer(quiz, "Found that leprechaun yet?") + chatNpc(laugh, "Hahaha go away amateur! You're not worthy of joining our great group!") + chatPlayer(bored, "...right.") + } + else -> { + chatNpc(laugh, "Hahaha you're such an amateur!") + chatNpc(laugh, "Go away and play with some cabbage amateur!") + chatPlayer(bored, "...right.") + } + } + } + + /* Monk */ + + private suspend fun Dialogue.monk() { + when (lostCity.stage(player)) { + 0 -> { + chatPlayer(quiz, "Why are all of you standing around here?") + chatNpc(angry, "None of your business. Get lost.") + } + STAGE_STARTED -> { + chatPlayer(quiz, "Have you found the tree with the leprechaun yet?") + chatNpc(neutral, "No, we've looked for ages but haven't... Hey! Wait a minute! How did you know about that?") + chatPlayer(happy, "Thanks for the information!") + chatNpc(angry, "...You tricked me. I'm not talking to you anymore.") + } + else -> chatNpc(angry, "I already told you. I'm not talking to you anymore.") + } + } + + private companion object { + const val WARRIOR = "npc.warrioradventurerpg" + const val ARCHER = "npc.archeradventurerpg" + const val WIZARD = "npc.wizardadventuterpg" + const val MONK = "npc.monkadventurerpg" + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/npcs/Shamus.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/npcs/Shamus.kt new file mode 100644 index 000000000..d7c704279 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/lostcity/npcs/Shamus.kt @@ -0,0 +1,169 @@ +package org.rsmod.content.quest.area.lumbridge.lostcity.npcs + +import dev.openrune.types.NpcMode +import dev.openrune.types.hunt.HuntVis +import jakarta.inject.Inject +import org.rsmod.api.hunt.NpcSearch +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.repo.npc.NpcRepository +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.lumbridge.lostcity.LostCityQuest +import org.rsmod.content.quest.area.lumbridge.lostcity.LostCityQuest.Companion.STAGE_STARTED +import org.rsmod.content.quest.area.lumbridge.lostcity.NO_AXE_MESSAGE +import org.rsmod.content.quest.area.lumbridge.lostcity.findWoodcuttingAxe +import org.rsmod.content.quest.area.lumbridge.lostcity.freeTileBeside +import org.rsmod.content.quest.area.lumbridge.lostcity.woodcuttingAnim +import org.rsmod.game.entity.Npc +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext +import org.rsmod.routefinder.collision.CollisionFlagMap + +/** + * Shamus, the leprechaun who lives in the tree west of the adventurers' camp. The tree is the + * one with a plain `Chop` option; taking an axe to it makes him pop out beside it, and he + * vanishes again as soon as he has finished talking. + */ +class Shamus +@Inject +constructor( + private val lostCity: LostCityQuest, + private val npcRepo: NpcRepository, + private val search: NpcSearch, + private val collision: CollisionFlagMap, +) : PluginScript() { + + private val quest + get() = lostCity.quest + + override fun ScriptContext.startup() { + onOpLoc1(TREE) { chopTree(it.loc) } + onOpNpc1(SHAMUS) { startDialogue(it.npc) { shamus(it.npc) } } + } + + private suspend fun ProtectedAccess.chopTree(tree: BoundLocInfo) { + arriveDelay() + faceLoc(tree) + val outside = search.find(tree.coords, SHAMUS, SEARCH_RADIUS, HuntVis.Off) + if (outside != null) { + outside.facePlayer(player) + startDialogue(outside) { + chatNpc(angry, "Hey! Yer big elephant! Don't go choppin' down me house, now!") + } + return + } + val axe = player.findWoodcuttingAxe() + if (axe == null) { + mes(NO_AXE_MESSAGE) + return + } + anim(axe.woodcuttingAnim()) + spam("You swing your axe at the tree.") + delay(CHOP_TICKS) + resetAnim() + + val tile = collision.freeTileBeside(tree, player.coords) + if (tile == null) { + mes("There's no room for anyone to come out of the tree.") + return + } + val shamus = Npc(SHAMUS, tile) + shamus.mode = NpcMode.None + npcRepo.add(shamus, SHAMUS_TICKS) + shamus.spotanim(PUFF) + soundSynth(PUFF_SOUND) + shamus.facePlayer(player) + delay(1) + startDialogue(shamus) { shamus(shamus) } + } + + private suspend fun Dialogue.shamus(npc: Npc) { + chatNpc(angry, "Ay yer big elephant! Yer've caught me, to be sure! What would an elephant like yer be wanting wid ol' Shamus then?") + when (lostCity.stage(player)) { + 0 -> { + chatPlayer(confused, "I'm not sure.") + chatNpc(angry, "Well you'll have to be catchin' me again when yer are, elephant!") + } + STAGE_STARTED -> firstTalk() + else -> laterTalks() + } + vanish(npc) + } + + private suspend fun Dialogue.firstTalk() { + chatPlayer(neutral, "I want to find Zanaris.") + chatNpc(quiz, "Zanaris is it now? Well well well... Yer'll be needing to be going to that funny little shed out there in the swamp, so you will.") + chatPlayer(confused, "...but... I thought... Zanaris was a city...?") + chatNpc(happy, "Aye that it is!") + when ( + choice2( + "How does it fit in a shed then?", 1, + "I've been in that shed, I didn't see a city.", 2, + ) + ) { + 1 -> { + chatPlayer(confused, "...How does it fit in a shed then?") + chatNpc(angry, "Ah yer stupid elephant! The city isn't IN the shed! The doorway to the shed is being a portal to Zanaris, so it is.") + chatPlayer(quiz, "So I just walk into the shed and end up in Zanaris then?") + } + 2 -> chatPlayer(neutral, "I've been in that shed. I didn't see a city.") + } + chatNpc(quiz, "Oh, was I fergetting to say? Yer need to be carrying a Dramenwood staff to be getting there! Otherwise Yer'll just be ending up in the shed.") + chatPlayer(quiz, "So where would I get a staff?") + chatNpc(neutral, "Dramenwood staffs are crafted from branches of the Dramen tree, so they are. I hear there's a Dramen tree over on the island of Entrana in a cave") + chatNpc(neutral, "or some such. There would probably be a good place for an elephant like yer to be starting looking I reckon.") + chatNpc(angry, "The monks are running a ship from Port Sarim to Entrana, I hear too. Now leave me alone yer elephant!") + if (lostCity.stage(player) == STAGE_STARTED) { + quest.advanceQuestStage(access) + } + } + + private suspend fun Dialogue.laterTalks() { + when ( + choice2( + "I'm not sure.", 1, + "How do I get to Zanaris again?", 2, + ) + ) { + 1 -> { + chatPlayer(confused, "I'm not sure.") + chatNpc(laugh, "Ha! Look at yer! Look at the stupid elephant who tries to go catching a leprechaun when he don't even be knowing what he wants!") + } + 2 -> { + chatPlayer(quiz, "How do I get to Zanaris again?") + chatNpc(angry, "Yer stupid elephant! I'll tell yer again! Yer need to be entering the shed in the middle of the swamp while holding a dramenwood staff! Yer can make the Dramen staff") + chatNpc(angry, "from a dramen tree branch, and there's a Dramen tree on Entrana! Now leave me alone yer great elephant!") + } + } + } + + /** Shamus is done talking and pops back into his tree. */ + private suspend fun Dialogue.vanish(npc: Npc) { + if (npc.isSlotAssigned) { + npc.spotanim(PUFF) + access.soundSynth(PUFF_SOUND) + access.delay(1) + if (npc.isSlotAssigned) { + npcRepo.del(npc, Int.MAX_VALUE) + } + } + mesbox("The leprechaun magically disappears.") + } + + private companion object { + const val TREE = "loc.leprechauntree" + const val SHAMUS = "npc.zanarisleprechaun" + + const val PUFF = "spotanim.smokepuff" + const val PUFF_SOUND = "synth.smokepuff" + + /** Swings taken at the tree before Shamus gives up hiding. */ + const val CHOP_TICKS = 3 + + /** How long Shamus waits beside his tree if the player wanders off mid-conversation. */ + const val SHAMUS_TICKS = 100 + const val SEARCH_RADIUS = 6 + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/GhostCoffin.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/GhostCoffin.kt new file mode 100644 index 000000000..444744be9 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/GhostCoffin.kt @@ -0,0 +1,158 @@ +package org.rsmod.content.quest.area.lumbridge.restlessghost + +import dev.openrune.types.hunt.HuntVis +import jakarta.inject.Inject +import org.rsmod.api.hunt.NpcSearch +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.repo.npc.NpcRepository +import org.rsmod.api.script.onOpHeld1 +import org.rsmod.api.script.onOpHeld5 +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpLoc2 +import org.rsmod.api.script.onOpLocU +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.GHOST_SKULL +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_GOT_SKULL +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.game.loc.LocAngle +import org.rsmod.game.loc.LocShape +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The ghost's coffin in the Lumbridge graveyard, and the skull that belongs in it. + * + * The shut coffin is a static map loc; opening it swaps in the open coffin loc for a while + * (everyone in the graveyard sees it open, as in the original). The open coffin is a cache + * multiloc that shows a complete skeleton once `varbit.restless_ghost_coffin_var` is set, which + * happens when the player returns the skull. + */ +class GhostCoffin +@Inject +constructor( + private val restlessGhost: RestlessGhostQuest, + private val locRepo: LocRepository, + private val npcRepo: NpcRepository, + private val search: NpcSearch, +) : PluginScript() { + + private val quest + get() = restlessGhost.quest + + override fun ScriptContext.startup() { + onOpLoc1(SHUT_COFFIN) { open(it.loc) } + onOpLocU(SHUT_COFFIN, GHOST_SKULL) { mesbox("Maybe I should open it first.") } + for (open in OPEN_COFFINS) { + onOpLoc1(open) { searchCoffin() } + onOpLoc2(open) { close(it.loc) } + onOpLocU(open, GHOST_SKULL) { returnSkull(it.loc, it.invSlot) } + } + onOpHeld1(GHOST_SKULL) { inspectSkull() } + onOpHeld5(GHOST_SKULL) { dropSkull() } + } + + private suspend fun ProtectedAccess.open(loc: BoundLocInfo) { + arriveDelay() + faceLoc(loc) + anim(OPEN_SEQ) + soundSynth("synth.coffin_open") + locRepo.add(loc.coords, OPEN_COFFIN, OPEN_TICKS, loc.angle(), loc.shape()) + mes("You open the coffin.") + } + + private suspend fun ProtectedAccess.close(loc: BoundLocInfo) { + arriveDelay() + faceLoc(loc) + anim(OPEN_SEQ) + soundSynth("synth.coffin_close") + // Re-adding the map's own loc cancels the timed open coffin. + locRepo.add(loc.coords, SHUT_COFFIN, Int.MAX_VALUE, loc.angle(), loc.shape()) + mes("You close the coffin.") + } + + private suspend fun ProtectedAccess.searchCoffin() { + arriveDelay() + anim("seq.human_pickuptable") + if (player.rgCoffinHasHead) { + mesbox("There's a nice and complete skeleton in here!") + } else { + mesbox("There's a skeleton without a skull in here.") + } + } + + /** The skull goes back where it belongs and the ghost is released. */ + private suspend fun ProtectedAccess.returnSkull(loc: BoundLocInfo, slot: Int) { + arriveDelay() + faceLoc(loc) + if (restlessGhost.stage(player) != STAGE_GOT_SKULL) { + mesbox("That doesn't belong in there.") + return + } + if (invDel(inv, GHOST_SKULL, 1, slot = slot).failure) { + return + } + anim("seq.human_pickuptable") + player.rgCoffinHasHead = true + player.rgSkeletonRisen = false + soundSynth("synth.coffin_close") + delay(1) + + val ghost = search.find(loc.coords, GHOST, GHOST_SEARCH_RADIUS, HuntVis.Off) + if (ghost != null) { + ghost.facePlayer(player) + ghost.say("Release! Thank you stranger..") + soundSynth("synth.ghost_disappear") + delay(3) + ghost.spotanim(VANISH_SPOT) + delay(1) + if (ghost.isSlotAssigned) { + npcRepo.hide(ghost, GHOST_AWAY_TICKS) + } + } + mesbox("The ghost's spirit drifts away over the river, finally at rest.") + quest.advanceQuestStage(this) + } + + private suspend fun ProtectedAccess.inspectSkull() { + objbox(GHOST_SKULL, "It's the skull of the ghost that is haunting Lumbridge graveyard. Maybe I should return this back to the ghost's coffin.") + } + + /** The skull is the only one of its kind; dropping it destroys it. */ + private suspend fun ProtectedAccess.dropSkull() { + val destroy = + confirmDestroy( + GHOST_SKULL, + 1, + "Dropping this skull here will destroy it!", + "Are you sure you want to drop the skull?", + ) + if (!destroy) { + return + } + invDel(inv, GHOST_SKULL) + mes("The skull crumbles to dust as it hits the ground.") + } + + private fun BoundLocInfo.angle(): LocAngle = + LocAngle.entries.firstOrNull { it.id == entity.angle } ?: LocAngle.West + + private fun BoundLocInfo.shape(): LocShape = + LocShape.entries.firstOrNull { it.id == entity.shape } ?: LocShape.CentrepieceStraight + + private companion object { + const val SHUT_COFFIN = "loc.shutghostcoffin" + const val OPEN_COFFIN = "loc.openghostcoffin" + val OPEN_COFFINS = listOf("loc.openghostcoffin_no_head", "loc.openghostcoffin_with_head") + const val GHOST = "npc.ghostx" + + const val OPEN_SEQ = "seq.human_pickupfloor" + const val VANISH_SPOT = "spotanim.smokepuff" + + /** How long the coffin stays open once lifted. */ + const val OPEN_TICKS = 100 + + /** How long the released ghost stays away before the graveyard has him back. */ + const val GHOST_AWAY_TICKS = 100 + const val GHOST_SEARCH_RADIUS = 10 + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/RestlessGhostQuest.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/RestlessGhostQuest.kt new file mode 100644 index 000000000..ee3e9d258 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/RestlessGhostQuest.kt @@ -0,0 +1,153 @@ +package org.rsmod.content.quest.area.lumbridge.restlessghost + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Singleton +import org.rsmod.api.player.front +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.vars.boolVarBit +import org.rsmod.content.quest.manager.ItemRewardDisplay +import org.rsmod.content.quest.manager.QuestScript +import org.rsmod.content.quest.manager.rewards +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The Restless Ghost. + * + * Stages (stored in `varp.prieststart`, endstate 5 from `dbrow.quest_restlessghost`): + * - [STAGE_STARTED]: Father Aereck asked for the graveyard ghost to be dealt with. + * - [STAGE_GOT_AMULET]: Father Urhney handed over the Amulet of Ghostspeak. + * - [STAGE_SPOKE_TO_GHOST]: the ghost explained that a warlock stole his skull. + * - [STAGE_GOT_SKULL]: the skull was taken from the altar in the Wizards' Tower basement. + * - [STAGE_COMPLETE]: the skull is back in the coffin and the ghost has been released. + * + * The coffin, altar and basement skeleton are cache multilocs driven by varbits on + * `varp.restless_ghost_control`, a separate varp from the quest stage, so those varbits can be + * used as-is. + */ +@Singleton +class RestlessGhostQuest : QuestScript( + "quest_restlessghost", + "varp.prieststart", + rewards { + xp("stat.prayer", 1125.0) + extra("Amulet of Ghostspeak") + }, + ItemRewardDisplay(GHOSTSPEAK_AMULET), +) { + override fun ScriptContext.init() {} + + override fun subTitle(): String = + "talking to Father Aereck in the Lumbridge church, " + + "south-east of the castle." + + override fun questLog(player: ProtectedAccess) = + questJournal(player) { + objective( + "Father Aereck wants me to get rid of a ghost haunting the " + + "graveyard beside the Lumbridge church.", + ) {} + + objective( + "His friend Father Urhney, who lives as a hermit in a shack in the far " + + "west of the Lumbridge swamp, is an expert on ghosts and may be able to help.", + ) { + visibleWhen { quest.getQuestStage(access.player) < STAGE_GOT_AMULET } + } + + objective( + "Father Urhney gave me an Amulet of Ghostspeak. While wearing it I can " + + "talk to the ghost, whose coffin is in the small building in the south-east " + + "corner of the graveyard.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_GOT_AMULET } + custom( + !access.hasAmuletAnywhere(), + "I have lost the Amulet of Ghostspeak. Father Urhney may have a spare.", + ) + } + + objective( + "The ghost cannot rest because a warlock stole his skull. He " + + "thinks it is somewhere in the Wizards' Tower, on the island south " + + "of Draynor Village. I should search the tower for it.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_SPOKE_TO_GHOST } + } + + objective( + "I found the ghost's skull on an altar in the Wizards' Tower basement. I should " + + "open the ghost's coffin in the Lumbridge graveyard and put the " + + "skull inside it.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_GOT_SKULL } + custom( + !access.hasSkullAnywhere(), + "I have lost the ghost's skull. I should search the altar in the Wizards' " + + "Tower basement again.", + ) + } + } + + override fun completedLog(player: ProtectedAccess): String = + completionJournal(player) { + line( + "Father Aereck asked me to get rid of a ghost haunting the Lumbridge graveyard. " + + "Father Urhney, a hermit in the swamp, gave me an Amulet of Ghostspeak so I " + + "could talk to it.", + ) + line( + "The ghost told me a warlock had stolen his skull. I found it on an altar in the " + + "basement of the Wizards' Tower, where a skeleton rose to stop me.", + ) + line( + "I put the skull back in his coffin and the ghost was finally able to rest in " + + "peace. Father Urhney let me keep the amulet.", + ) + } + + fun stage(player: Player): Int = quest.getQuestStage(player) + + /** Either ghostspeak amulet worn in the neck slot. */ + fun isWearingAmulet(player: Player): Boolean = player.front?.id in AMULET_IDS + + fun ProtectedAccess.hasAmuletAnywhere(): Boolean = + isWearingAmulet(player) || + AMULETS.any { inv.count(it) > 0 || bank.count(it) > 0 } + + fun ProtectedAccess.carriesAmulet(): Boolean = + isWearingAmulet(player) || AMULETS.any { inv.count(it) > 0 } + + fun ProtectedAccess.hasSkullAnywhere(): Boolean = + inv.count(GHOST_SKULL) > 0 || bank.count(GHOST_SKULL) > 0 + + companion object { + const val STAGE_STARTED = 1 + const val STAGE_GOT_AMULET = 2 + const val STAGE_SPOKE_TO_GHOST = 3 + const val STAGE_GOT_SKULL = 4 + const val STAGE_COMPLETE = 5 + + const val GHOSTSPEAK_AMULET = "obj.amulet_of_ghostspeak" + const val GHOSTSPEAK_AMULET_ENCHANTED = "obj.amulet_of_ghostspeak_enchanted" + const val GHOST_SKULL = "obj.ghostskull" + + val AMULETS = listOf(GHOSTSPEAK_AMULET, GHOSTSPEAK_AMULET_ENCHANTED) + val AMULET_IDS: Set by lazy { AMULETS.map { it.asRSCM(RSCMType.OBJ) }.toSet() } + } +} + +/* + * Mirrors of the cache multilocs on `varp.restless_ghost_control`. These are storage, not + * mirrors: the quest manager never touches that varp. + */ + +/** The skeleton in the corner of the Wizards' Tower basement has risen. */ +var Player.rgSkeletonRisen by boolVarBit("varbit.restless_ghost_skeleton_var") + +/** The ghost's coffin shows a complete skeleton (skull returned). */ +var Player.rgCoffinHasHead by boolVarBit("varbit.restless_ghost_coffin_var") + +/** The basement altar no longer holds the skull. */ +var Player.rgAltarEmpty by boolVarBit("varbit.restless_ghost_altar_var") diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/WarlockAltar.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/WarlockAltar.kt new file mode 100644 index 000000000..4664bb8bf --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/WarlockAltar.kt @@ -0,0 +1,101 @@ +package org.rsmod.content.quest.area.lumbridge.restlessghost + +import jakarta.inject.Inject +import org.rsmod.api.npc.interact.AiPlayerInteractions +import org.rsmod.api.npc.opPlayer2 +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.repo.npc.NpcRepository +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.GHOST_SKULL +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_COMPLETE +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_GOT_SKULL +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_SPOKE_TO_GHOST +import org.rsmod.game.entity.Npc +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The warlock's altar in the Wizards' Tower basement. Searching it once the ghost has explained + * himself yields his skull, at which point the skeleton in the corner rises and attacks. The + * altar is a cache multiloc that shows the skull until `varbit.restless_ghost_altar_var` is set. + */ +class WarlockAltar +@Inject +constructor( + private val restlessGhost: RestlessGhostQuest, + private val npcRepo: NpcRepository, + private val aiInteractions: AiPlayerInteractions, +) : PluginScript() { + + private val quest + get() = restlessGhost.quest + + override fun ScriptContext.startup() { + for (altar in ALTARS) { + onOpLoc1(altar) { searchAltar(it.loc) } + } + } + + private suspend fun ProtectedAccess.searchAltar(loc: BoundLocInfo) { + arriveDelay() + faceLoc(loc) + anim("seq.human_pickuptable") + delay(1) + val stage = restlessGhost.stage(player) + when { + stage >= STAGE_COMPLETE -> + mesbox("Surprisingly, this altar is empty. Probably because you took the skull off it earlier.") + stage < STAGE_SPOKE_TO_GHOST -> + mes("You search the altar but find nothing of interest.") + with(restlessGhost) { hasSkullAnywhere() } -> + mesbox("You already have the Ghost's skull.") + player.inv.freeSpace() < 1 -> + mes("You need a free space in your pack to take the skull.") + else -> takeSkull(loc) + } + } + + private suspend fun ProtectedAccess.takeSkull(loc: BoundLocInfo) { + invAdd(inv, GHOST_SKULL) + player.rgAltarEmpty = true + soundSynth("synth.pick2") + objbox(GHOST_SKULL, "You lift the skull off the altar.") + if (restlessGhost.stage(player) < STAGE_GOT_SKULL) { + quest.advanceQuestStage(this) + } + + mes("The skeleton in the corner suddenly comes to life!") + player.rgSkeletonRisen = true + soundSynth("synth.skeleton_resurrect") + val skeleton = Npc(SKELETON, skeletonTile(loc)) + npcRepo.add(skeleton, SKELETON_TICKS) + skeleton.facePlayer(player) + skeleton.opPlayer2(player, aiInteractions) + } + + /** A free tile near the altar for the skeleton to rise on, preferring the room's corner. */ + private fun ProtectedAccess.skeletonTile(loc: BoundLocInfo): CoordGrid { + val candidates = + listOf( + SKELETON_CORNER, + loc.coords.translate(-1, 0), + loc.coords.translate(2, 0), + loc.coords.translate(0, -1), + loc.coords.translate(0, 1), + ) + return candidates.firstOrNull { it != player.coords && !mapBlocked(it) } ?: player.coords + } + + private companion object { + val ALTARS = listOf("loc.restless_ghost_altar_skull", "loc.restless_ghost_altar_no_skull") + const val SKELETON = "npc.skull_skeleton" + + /** Just beside the altar in the basement's corner. */ + val SKELETON_CORNER = CoordGrid(3119, 9565, 0) + + /** How long the risen skeleton hangs around before crumbling again. */ + const val SKELETON_TICKS = 150 + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/npcs/FatherAereck.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/npcs/FatherAereck.kt new file mode 100644 index 000000000..51f2069c0 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/npcs/FatherAereck.kt @@ -0,0 +1,134 @@ +package org.rsmod.content.quest.area.lumbridge.restlessghost.npcs + +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.GHOST_SKULL +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_GOT_AMULET +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_GOT_SKULL +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_SPOKE_TO_GHOST +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_STARTED +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Father Aereck, priest of the Lumbridge church. Starts The Restless Ghost. */ +class FatherAereck @Inject constructor(private val restlessGhost: RestlessGhostQuest) : PluginScript() { + + private val quest + get() = restlessGhost.quest + + override fun ScriptContext.startup() { + onOpNpc1("npc.father_aereck") { startDialogue(it.npc) { aereck() } } + } + + private suspend fun Dialogue.aereck() { + when (quest.getQuestStage(player)) { + 0 -> beforeQuest() + STAGE_STARTED -> { + chatNpc(quiz, "Have you got rid of the ghost yet?") + chatPlayer(sad, "I can't find Father Urhney at the moment.") + chatNpc(neutral, "Well, you can get to the swamp he lives in by going south through the cemetery.") + chatNpc(neutral, "You'll have to go right into the far western depths of the swamp, near the coastline. That is where his house is.") + } + STAGE_GOT_AMULET -> { + chatNpc(quiz, "Have you got rid of the ghost yet?") + chatPlayer(neutral, "I had a talk with Father Urhney. He has given me this funny amulet to talk to the ghost with.") + chatNpc(happy, "I always wondered what that amulet was... Well, I hope it's useful. Tell me when you get rid of the ghost!") + } + STAGE_SPOKE_TO_GHOST -> { + chatNpc(quiz, "Have you got rid of the ghost yet?") + chatPlayer(neutral, "I've found out that the ghost's corpse has lost its skull. If I can find the skull, the ghost should leave.") + chatNpc(neutral, "That WOULD explain it. Hmmmm. Well, I haven't seen any skulls.") + chatPlayer(neutral, "Yes, I think a warlock has stolen it.") + chatNpc(angry, "I hate warlocks. Ah well, good luck!") + } + STAGE_GOT_SKULL -> { + chatNpc(quiz, "Have you got rid of the ghost yet?") + if (player.inv.count(GHOST_SKULL) > 0) { + chatPlayer(happy, "I've finally found the ghost's skull!") + chatNpc(happy, "Great! Put it in the ghost's coffin and see what happens!") + } else { + chatPlayer(worried, "I found the ghost's skull, but I seem to have mislaid it.") + chatNpc(neutral, "Then you'd best go back to wherever you found it. The ghost isn't going anywhere.") + } + } + else -> afterQuest() + } + } + + private suspend fun Dialogue.beforeQuest() { + chatNpc(happy, "Welcome to the church of holy Saradomin.") + when ( + choice3( + "Who's Saradomin?", 1, + "Nice place you've got here.", 2, + "I'm looking for a quest!", 3, + ) + ) { + 1 -> whoIsSaradomin() + 2 -> nicePlace() + 3 -> offerQuest() + } + } + + private suspend fun Dialogue.offerQuest() { + chatPlayer(happy, "I'm looking for a quest.") + chatNpc(happy, "That's lucky. I need someone to do a quest for me.") + when ( + choice2( + "Yes.", 1, + "No.", 2, + title = "Start The Restless Ghost quest?", + ) + ) { + 1 -> { + chatPlayer(happy, "Okay, let me help then.") + chatNpc(happy, "Thank you. The problem is, there is a ghost in the church graveyard. I would like you to get rid of it.") + chatNpc(neutral, "If you need any help, my friend Father Urhney is an expert on ghosts.") + chatNpc(neutral, "I believe he is currently living as a hermit in Lumbridge swamp. He has a little shack in the far west of the swamps.") + chatNpc(neutral, "Exit the graveyard through the south gate to reach the swamp. I'm sure if you told him that I sent you he'd be willing to help.") + chatNpc(happy, "My name is Father Aereck, by the way. Pleased to meet you.") + chatPlayer(happy, "Likewise.") + chatNpc(worried, "Take care travelling through the swamps. I have heard they can be quite dangerous.") + chatPlayer(neutral, "I will, thanks.") + quest.advanceQuestStage(access) + } + 2 -> { + chatPlayer(neutral, "Sorry, I don't have time right now.") + chatNpc(neutral, "Oh well. If you do have some spare time on your hands, come back and talk to me.") + } + } + } + + private suspend fun Dialogue.whoIsSaradomin() { + chatPlayer(quiz, "Who's Saradomin?") + chatNpc(shocked, "Well, if you don't know, I'm not sure I can do him justice in a few words. Saradomin is the god of order and wisdom, and the protector of this church.") + chatNpc(happy, "Take a look at the altar and the pews. Everything here is built in his honour.") + chatPlayer(neutral, "I'll keep that in mind.") + } + + private suspend fun Dialogue.nicePlace() { + chatPlayer(happy, "Nice place you've got here.") + chatNpc(happy, "It is, isn't it? It was built over two centuries ago, and we've kept it just as it was.") + chatNpc(neutral, "Feel free to stay and pray a while. The altar is open to all.") + } + + private suspend fun Dialogue.afterQuest() { + chatNpc(happy, "Welcome to the church of holy Saradomin. Thank you again for laying that poor ghost to rest.") + when ( + choice3( + "Who's Saradomin?", 1, + "Nice place you've got here.", 2, + "Any more ghosts need dealing with?", 3, + ) + ) { + 1 -> whoIsSaradomin() + 2 -> nicePlace() + 3 -> { + chatPlayer(quiz, "Any more ghosts need dealing with?") + chatNpc(happy, "Thankfully not. The graveyard has been quite peaceful since you found that skull. I hope it stays that way!") + } + } + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/npcs/FatherUrhney.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/npcs/FatherUrhney.kt new file mode 100644 index 000000000..4b11023c1 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/npcs/FatherUrhney.kt @@ -0,0 +1,135 @@ +package org.rsmod.content.quest.area.lumbridge.restlessghost.npcs + +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.GHOSTSPEAK_AMULET +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_GOT_AMULET +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_STARTED +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Father Urhney, the hermit in the shack at the west edge of Lumbridge swamp. */ +class FatherUrhney @Inject constructor(private val restlessGhost: RestlessGhostQuest) : PluginScript() { + + private val quest + get() = restlessGhost.quest + + override fun ScriptContext.startup() { + onOpNpc1("npc.father_urhney") { startDialogue(it.npc) { urhney() } } + } + + private suspend fun Dialogue.urhney() { + chatNpc(angry, "Go away! I'm meditating!") + val stage = quest.getQuestStage(player) + val pick = + when { + stage >= STAGE_GOT_AMULET -> + choice3( + "Well, that's friendly.", 1, + "I've lost the Amulet of Ghostspeak.", 3, + "I've come to repossess your house.", 4, + ) + stage == STAGE_STARTED -> + choice3( + "Well, that's friendly.", 1, + "Father Aereck sent me to talk to you.", 2, + "I've come to repossess your house.", 4, + ) + else -> + choice2( + "Well, that's friendly.", 1, + "I've come to repossess your house.", 4, + ) + } + when (pick) { + 1 -> { + chatPlayer(neutral, "Well, that's friendly.") + chatNpc(verymad, "I SAID go AWAY.") + chatPlayer(bored, "Okay, okay... sheesh, what a grouch.") + } + 2 -> sentByAereck() + 3 -> lostAmulet() + 4 -> repossess() + } + } + + private suspend fun Dialogue.sentByAereck() { + chatPlayer(neutral, "Father Aereck sent me to talk to you.") + chatNpc(bored, "I suppose I'd better talk to you then. What problems has he got himself into this time?") + when ( + choice2( + "He's got a ghost haunting his graveyard.", 1, + "You mean he gets himself into lots of problems?", 2, + ) + ) { + 2 -> { + chatPlayer(quiz, "You mean he gets himself into lots of problems?") + chatNpc(laugh, "Yeah. For example, when we were trainee priests he kept on getting stuck up bell ropes.") + chatNpc(bored, "Anyway. I don't have time for chitchat. What's his problem THIS time?") + } + } + chatPlayer(neutral, "He's got a ghost haunting his graveyard.") + chatNpc(bored, "Oh, the silly fool.") + chatNpc(angry, "I leave town for just five months, and ALREADY he can't manage.") + chatNpc(bored, "(sigh)") + chatNpc(neutral, "Well, I can't go back and exorcise it. I vowed not to leave this place until I had done a full two years of prayer and meditation.") + chatNpc(neutral, "Tell you what I can do though. Take this amulet.") + if (access.invAdd(access.inv, GHOSTSPEAK_AMULET).failure) { + chatNpc(angry, "Or I would, if you had any room in your pack for it. Clear a space and come back.") + return + } + access.soundSynth("synth.pick2") + objbox(GHOSTSPEAK_AMULET, "Father Urhney hands you an amulet.") + quest.advanceQuestStage(access) + chatNpc(neutral, "It is an Amulet of Ghostspeak.") + chatNpc(neutral, "So called because when you wear it you can speak to ghosts. A lot of ghosts are doomed to be ghosts because they have left some important task uncompleted.") + chatNpc(neutral, "Maybe if you know what this task is, you can get rid of the ghost. I'm not making any guarantees, mind you, but it is the best I can do right now.") + chatPlayer(happy, "Thank you. I'll give it a try!") + } + + private suspend fun Dialogue.lostAmulet() { + chatPlayer(sad, "I've lost the Amulet of Ghostspeak.") + mesbox("Father Urhney sighs.") + when { + with(restlessGhost) { access.carriesAmulet() } -> + chatNpc(angry, "What are you talking about? I can see you've got it with you!") + with(restlessGhost) { access.hasAmuletAnywhere() } -> + chatNpc(angry, "You come here wasting my time... Has it even occurred to you that you've got it stored somewhere? Now GO AWAY!") + player.inv.freeSpace() < 1 -> + chatNpc(angry, "How careless can you get? Those things aren't easy to come by, you know! Now clear some space in your pack and I'll give you another one.") + else -> { + chatNpc(angry, "How careless can you get? Those things aren't easy to come by, you know! It's a good job I've got a spare.") + access.invAdd(access.inv, GHOSTSPEAK_AMULET) + access.soundSynth("synth.pick2") + objbox(GHOSTSPEAK_AMULET, "Father Urhney hands you an amulet.") + chatNpc(neutral, "Be more careful this time.") + chatPlayer(neutral, "Okay, I'll try to be.") + } + } + } + + private suspend fun Dialogue.repossess() { + chatPlayer(neutral, "I've come to repossess your house.") + chatNpc(shocked, "Under what grounds???") + when ( + choice2( + "Repeated failure on mortgage repayments.", 1, + "I don't know. I just wanted this house.", 2, + ) + ) { + 1 -> { + chatPlayer(neutral, "Repeated failure on mortgage repayments.") + chatNpc(shocked, "What? But... I don't have a mortgage! I built this house myself!") + chatPlayer(shifty, "Sorry. I must have got the wrong address. All the houses look the same around here.") + chatNpc(confused, "What? What houses? What ARE you talking about???") + chatPlayer(neutral, "Never mind.") + } + 2 -> { + chatPlayer(shifty, "I don't know. I just wanted this house...") + chatNpc(angry, "Oh... go away and stop wasting my time!") + } + } + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/npcs/RestlessGhost.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/npcs/RestlessGhost.kt new file mode 100644 index 000000000..2d059f5b9 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/lumbridge/restlessghost/npcs/RestlessGhost.kt @@ -0,0 +1,277 @@ +package org.rsmod.content.quest.area.lumbridge.restlessghost.npcs + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpcU +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.GHOST_SKULL +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_COMPLETE +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_GOT_SKULL +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_SPOKE_TO_GHOST +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest.Companion.STAGE_STARTED +import org.rsmod.game.entity.Npc +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The Restless ghost, drifting about the little building in the south-east corner of the + * Lumbridge graveyard. Only an Amulet of Ghostspeak makes sense of him. + */ +class RestlessGhost @Inject constructor(private val restlessGhost: RestlessGhostQuest) : PluginScript() { + + private val quest + get() = restlessGhost.quest + + private val skullId = GHOST_SKULL.asRSCM(RSCMType.OBJ) + + override fun ScriptContext.startup() { + onOpNpc1(GHOST) { startDialogue(it.npc) { ghost(it.npc) } } + onOpNpcU(GHOST) { useItem(it.npc, it.objType.id) } + } + + private suspend fun ProtectedAccess.useItem(npc: Npc, objId: Int) { + arriveDelay() + faceEntitySquare(npc) + if (objId != skullId) { + mes("The ghost doesn't seem interested in that.") + return + } + mesbox("I can't give it to him. It goes right through him.") + if (restlessGhost.isWearingAmulet(player)) { + startDialogue(npc) { + chatNpc(neutral, "If you just put it in my coffin, that should do the trick...") + } + } else { + npc.say("Woooo woo!") + } + } + + private suspend fun Dialogue.ghost(npc: Npc) { + chatPlayer(happy, "Hello ghost, how are you?") + if (!restlessGhost.isWearingAmulet(player) || quest.getQuestStage(player) < STAGE_STARTED) { + wooing() + return + } + when (quest.getQuestStage(player)) { + in STAGE_STARTED until STAGE_SPOKE_TO_GHOST -> firstConversation() + STAGE_SPOKE_TO_GHOST, STAGE_GOT_SKULL -> skullHunt() + else -> { + chatNpc(happy, "Thank you again, stranger. At last I can rest.") + chatPlayer(happy, "Sleep well.") + } + } + } + + /** What the ghost sounds like to anyone without the amulet. */ + private suspend fun Dialogue.wooing() { + chatNpc(neutral, "Wooo wooo wooooo!") + when ( + choice3( + "Sorry, I don't speak ghost.", 1, + "Ooh, THAT'S interesting.", 2, + "Any hints where I can find some treasure?", 3, + ) + ) { + 1 -> dontSpeakGhost() + 2 -> { + chatPlayer(happy, "Ooh, THAT'S interesting.") + chatNpc(neutral, "Woo wooo. Woooooooooooooooooo!") + when ( + choice2( + "Did he really?", 1, + "Yeah, that's what I thought.", 2, + ) + ) { + 1 -> { + chatPlayer(quiz, "Did he really?") + chatNpc(neutral, "Woo.") + when ( + choice2( + "My brother had EXACTLY the same problem.", 1, + "Goodbye. Thanks for the chat.", 2, + ) + ) { + 1 -> { + chatPlayer(neutral, "My brother had EXACTLY the same problem.") + chatNpc(neutral, "Woo Wooooo!") + chatNpc(neutral, "Wooooo Woo woo woo!") + when ( + choice2( + "You'll have to give me the recipe some time...", 1, + "Goodbye. Thanks for the chat.", 2, + ) + ) { + 1 -> { + chatPlayer(happy, "You'll have to give me the recipe some time...") + chatNpc(neutral, "Wooooooo woo woooooooo.") + notSoSure() + } + 2 -> goodbye() + } + } + 2 -> goodbye() + } + } + 2 -> { + chatPlayer(neutral, "Yeah, that's what I thought.") + chatNpc(neutral, "Wooo woooooooooooooo...") + when ( + choice2( + "Goodbye. Thanks for the chat.", 1, + "Hmm, I'm not so sure about that.", 2, + ) + ) { + 1 -> goodbye() + 2 -> notSoSure() + } + } + } + } + 3 -> { + chatPlayer(quiz, "Any hints where I can find some treasure?") + chatNpc(neutral, "Wooooooo woo! Wooooo woo wooooo woowoowoo woo Woo wooo. Wooooo woo woo? Woooooooooooooooooo!") + when ( + choice2( + "Sorry, I don't speak ghost.", 1, + "Thank you. You've been very helpful.", 2, + ) + ) { + 1 -> dontSpeakGhost() + 2 -> { + chatPlayer(happy, "Thank you. You've been very helpful.") + chatNpc(neutral, "Wooooooo.") + } + } + } + } + } + + private suspend fun Dialogue.dontSpeakGhost() { + chatPlayer(neutral, "Sorry, I don't speak ghost.") + chatNpc(neutral, "Woo woo?") + chatPlayer(confused, "Nope, still don't understand you.") + chatNpc(angry, "WOOOOOOOOO!") + chatPlayer(neutral, "Never mind.") + } + + private suspend fun Dialogue.notSoSure() { + chatPlayer(quiz, "Hmm... I'm not so sure about that.") + chatNpc(neutral, "Wooo woo?") + chatPlayer(neutral, "Well, if you INSIST.") + chatNpc(happy, "Wooooooooo!") + chatPlayer(neutral, "Ah well, better be off now...") + chatNpc(neutral, "Woo.") + chatPlayer(neutral, "Bye.") + } + + private suspend fun Dialogue.goodbye() { + chatPlayer(happy, "Goodbye. Thanks for the chat.") + chatNpc(quiz, "Wooo wooo?") + } + + /** The first time the ghost is understood: he explains about the stolen skull. */ + private suspend fun Dialogue.firstConversation() { + chatNpc(sad, "Not very good, actually.") + chatPlayer(quiz, "What's the problem then?") + chatNpc(shocked, "Did you just understand what I said???") + when ( + choice3( + "Yep, now tell me what the problem is.", 1, + "No, you sound like you're speaking nonsense to me.", 2, + "Wow, this amulet works!", 3, + ) + ) { + 1 -> { + chatPlayer(neutral, "Yep, now tell me what the problem is.") + chatNpc(happy, "WOW! This is INCREDIBLE! I didn't expect anyone to ever understand me again!") + chatPlayer(neutral, "Okay, okay, I can understand you! But have you any idea WHY you're doomed to be a ghost?") + explainSkull() + } + 2 -> { + chatPlayer(neutral, "No, you sound like you're speaking nonsense to me.") + chatNpc(sad, "Oh, that's a pity. You got my hopes up there.") + chatPlayer(neutral, "Yeah, it is a pity. Sorry about that.") + chatNpc(shocked, "Hang on a second... you CAN understand me!") + when ( + choice2( + "No I can't.", 1, + "Yep, clever aren't I?", 2, + ) + ) { + 1 -> { + chatPlayer(neutral, "No I can't.") + chatNpc(bored, "Great. The first person I can speak to in ages... and they're a moron.") + } + 2 -> { + chatPlayer(happy, "Yep, clever aren't I?") + chatNpc(happy, "I'm impressed. You must be very powerful. I don't suppose you can stop me being a ghost?") + offerHelp() + } + } + } + 3 -> { + chatPlayer(happy, "Wow, this amulet works!") + chatNpc(happy, "Oh! It's your amulet that's doing it! I did wonder. I don't suppose you can help me? I don't like being a ghost.") + offerHelp() + } + } + } + + private suspend fun Dialogue.offerHelp() { + when ( + choice2( + "Yes, okay. Do you know WHY you're a ghost?", 1, + "No, you're scary!", 2, + ) + ) { + 1 -> { + chatPlayer(quiz, "Yes, okay. Do you know WHY you're a ghost?") + chatNpc(sad, "Nope. I just know I can't do much of anything like this!") + explainSkull() + } + 2 -> { + chatPlayer(worried, "No, you're scary!") + chatNpc(bored, "Great. The first person I can speak to in ages...") + chatNpc(bored, "...and they're an idiot.") + } + } + } + + private suspend fun Dialogue.explainSkull() { + chatNpc(sad, "Well, to be honest... I'm not sure.") + chatPlayer(neutral, "I've been told a certain task may need to be completed so you can rest in peace.") + chatNpc(neutral, "I should think it is probably because a warlock has come along and stolen my skull. If you look inside my coffin there, you'll find my corpse without a head on it.") + chatPlayer(quiz, "Do you know where this warlock might be now?") + chatNpc(neutral, "I think it was one of the warlocks who lives in the big tower by the sea, south-west from here.") + chatPlayer(happy, "Okay. I will try and get the skull back for you, then you can rest in peace.") + chatNpc(happy, "Ooh, thank you. That would be such a great relief!") + chatNpc(sad, "It is so dull being a ghost...") + val stage = quest.getQuestStage(player) + if (stage < STAGE_SPOKE_TO_GHOST) { + quest.advanceQuestStage(access, STAGE_SPOKE_TO_GHOST - stage) + } + } + + private suspend fun Dialogue.skullHunt() { + chatNpc(quiz, "How are you doing finding my skull?") + if (player.inv.count(GHOST_SKULL) > 0) { + chatPlayer(happy, "I have found it!") + chatNpc(happy, "Hurrah! Now I can stop being a ghost! You just need to put it in my coffin there, then I'll be free!") + return + } + chatPlayer(sad, "Sorry, I can't find it at the moment.") + chatNpc(neutral, "Ah well. Keep on looking.") + chatNpc(neutral, "I'm pretty sure it's somewhere in the tower south-west from here. There's a lot of levels to the tower, though. I suppose it might take a little while to find.") + if (quest.getQuestStage(player) >= STAGE_COMPLETE) { + return + } + } + + private companion object { + const val GHOST = "npc.ghostx" + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/HettysCauldron.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/HettysCauldron.kt new file mode 100644 index 000000000..3b93a8c3e --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/HettysCauldron.kt @@ -0,0 +1,42 @@ +package org.rsmod.content.quest.area.rimmington.witchspotion + +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.content.quest.area.rimmington.witchspotion.WitchsPotionQuest.Companion.STAGE_BREWED +import org.rsmod.content.quest.area.rimmington.witchspotion.WitchsPotionQuest.Companion.STAGE_COMPLETE +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Hetty's cauldron: the potion is drunk straight from it once she has brewed it. */ +class HettysCauldron @Inject constructor(private val witchsPotion: WitchsPotionQuest) : PluginScript() { + + private val quest + get() = witchsPotion.quest + + override fun ScriptContext.startup() { + onOpLoc1(CAULDRON) { drink(it.loc) } + } + + private suspend fun ProtectedAccess.drink(loc: BoundLocInfo) { + arriveDelay() + faceLoc(loc) + when (witchsPotion.stage(player)) { + STAGE_BREWED -> { + anim(DRINK_SEQ) + soundSynth("synth.drink") + delay(2) + mesbox("You drink from the cauldron. It tastes horrible! You feel yourself imbued with power.") + quest.advanceQuestStage(this) + } + STAGE_COMPLETE -> mes("You've had quite enough of that potion for one lifetime.") + else -> startDialogue { chatPlayer(bored, "As nice as that looks, I think I'll give it a miss for now.") } + } + } + + private companion object { + const val CAULDRON = "loc.hettycauldron" + const val DRINK_SEQ = "seq.human_eat" + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/WitchsPotionQuest.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/WitchsPotionQuest.kt new file mode 100644 index 000000000..08afb5f6d --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/WitchsPotionQuest.kt @@ -0,0 +1,96 @@ +package org.rsmod.content.quest.area.rimmington.witchspotion + +import jakarta.inject.Singleton +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.content.quest.manager.ItemRewardDisplay +import org.rsmod.content.quest.manager.QuestScript +import org.rsmod.content.quest.manager.rewards +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Witch's Potion. + * + * Stages (stored in `varp.hetty`, endstate 3 from `dbrow.quest_witchspotion`): + * - [STAGE_STARTED]: Hetty listed the four ingredients. + * - [STAGE_BREWED]: the ingredients are in the cauldron; the potion waits to be drunk. + * - [STAGE_COMPLETE]: the potion has been drunk. + */ +@Singleton +class WitchsPotionQuest : QuestScript( + "quest_witchspotion", + "varp.hetty", + rewards { xp("stat.magic", 325.0) }, + ItemRewardDisplay(EYE_OF_NEWT), +) { + override fun ScriptContext.init() {} + + override fun subTitle(): String = + "talking to Hetty in her house in Rimmington, " + + "south of Falador." + + override fun questLog(player: ProtectedAccess) = + questJournal(player) { + objective( + "Hetty the witch in Rimmington has offered to brew me a potion that " + + "will bring out my darker side. She needs four ingredients first.", + ) {} + + objective("An eye of newt. Betty's Magic Emporium in Port Sarim sells them.") { + visibleWhen { quest.getQuestStage(access.player) == STAGE_STARTED } + hasItem("eye_of_newt", "I have an eye of newt.").strike() + } + + objective("A rat's tail. The rats around Rimmington should provide one.") { + visibleWhen { quest.getQuestStage(access.player) == STAGE_STARTED } + hasItem("rats_tail", "I have a rat's tail. Ewww.").strike() + } + + objective("An onion. There is an onion field just north of Rimmington.") { + visibleWhen { quest.getQuestStage(access.player) == STAGE_STARTED } + hasItem("onion", "I have an onion.").strike() + } + + objective("A piece of burnt meat. Cooking some meat until it burns should do it.") { + visibleWhen { quest.getQuestStage(access.player) == STAGE_STARTED } + hasItem("burnt_meat", "I have some burnt meat.").strike() + } + + objective( + "Hetty has brewed the potion in her cauldron. All I have to do now is " + + "drink from it.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_BREWED } + } + } + + override fun completedLog(player: ProtectedAccess): String = + completionJournal(player) { + line( + "Hetty the witch in Rimmington offered to brew me a potion to bring out my " + + "darker side, in exchange for an eye of newt, a rat's tail, an onion and a " + + "piece of burnt meat.", + ) + line( + "I gathered them all, she brewed the potion in her cauldron, and I drank it. It " + + "tasted horrible, but I could feel the power in it.", + ) + } + + fun stage(player: Player): Int = quest.getQuestStage(player) + + fun hasAllIngredients(player: Player): Boolean = INGREDIENTS.all { player.inv.count(it) > 0 } + + companion object { + const val STAGE_STARTED = 1 + const val STAGE_BREWED = 2 + const val STAGE_COMPLETE = 3 + + const val EYE_OF_NEWT = "obj.eye_of_newt" + const val RATS_TAIL = "obj.rats_tail" + const val ONION = "obj.onion" + const val BURNT_MEAT = "obj.burnt_meat" + + val INGREDIENTS = listOf(RATS_TAIL, BURNT_MEAT, ONION, EYE_OF_NEWT) + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/npcs/Betty.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/npcs/Betty.kt new file mode 100644 index 000000000..406950aea --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/npcs/Betty.kt @@ -0,0 +1,47 @@ +package org.rsmod.content.quest.area.rimmington.witchspotion.npcs + +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpc3 +import org.rsmod.api.shops.Shops +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Betty, who runs Betty's Magic Emporium in Port Sarim: runes, hats and eyes of newt. */ +class Betty @Inject constructor(private val shops: Shops) : PluginScript() { + override fun ScriptContext.startup() { + onOpNpc1("npc.betty") { startDialogue(it.npc) { betty(it.npc) } } + onOpNpc3("npc.betty") { player.openShop(it.npc) } + } + + private fun Player.openShop(npc: Npc) { + shops.open(this, npc, SHOP_NAME, SHOP_INV) + } + + private suspend fun Dialogue.betty(npc: Npc) { + chatNpc(happy, "Welcome to the magic emporium.") + when ( + choice2( + "Can I see your wares?", 1, + "Sorry, I'm not into magic.", 2, + ) + ) { + 1 -> { + chatPlayer(happy, "Can I see your wares?") + player.openShop(npc) + } + 2 -> { + chatPlayer(neutral, "Sorry, I'm not into magic.") + chatNpc(neutral, "Well, if you see anyone who is, please send them my way.") + } + } + } + + private companion object { + const val SHOP_NAME = "Betty's Magic Emporium" + const val SHOP_INV = "inv.magicshop" + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/npcs/Hetty.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/npcs/Hetty.kt new file mode 100644 index 000000000..3ca8b3f69 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/rimmington/witchspotion/npcs/Hetty.kt @@ -0,0 +1,187 @@ +package org.rsmod.content.quest.area.rimmington.witchspotion.npcs + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.rimmington.witchspotion.WitchsPotionQuest +import org.rsmod.content.quest.area.rimmington.witchspotion.WitchsPotionQuest.Companion.BURNT_MEAT +import org.rsmod.content.quest.area.rimmington.witchspotion.WitchsPotionQuest.Companion.EYE_OF_NEWT +import org.rsmod.content.quest.area.rimmington.witchspotion.WitchsPotionQuest.Companion.INGREDIENTS +import org.rsmod.content.quest.area.rimmington.witchspotion.WitchsPotionQuest.Companion.ONION +import org.rsmod.content.quest.area.rimmington.witchspotion.WitchsPotionQuest.Companion.RATS_TAIL +import org.rsmod.content.quest.area.rimmington.witchspotion.WitchsPotionQuest.Companion.STAGE_BREWED +import org.rsmod.content.quest.area.rimmington.witchspotion.WitchsPotionQuest.Companion.STAGE_STARTED +import org.rsmod.game.entity.Npc +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Hetty, the witch of Rimmington. Starts Witch's Potion and brews it. */ +class Hetty +@Inject +constructor( + private val witchsPotion: WitchsPotionQuest, + private val locRepo: LocRepository, + private val worldRepo: WorldRepository, +) : PluginScript() { + + private val quest + get() = witchsPotion.quest + + private val cauldronType = + ServerCacheManager.getObject(CAULDRON.asRSCM(RSCMType.LOC)) ?: error("Missing loc: $CAULDRON") + + override fun ScriptContext.startup() { + onOpNpc1("npc.hetty") { startDialogue(it.npc) { hetty(it.npc) } } + } + + private suspend fun Dialogue.hetty(npc: Npc) { + when (quest.getQuestStage(player)) { + 0 -> beforeQuest() + STAGE_STARTED -> gathering(npc) + STAGE_BREWED -> { + chatNpc(quiz, "Well, are you going to drink the potion or not?") + chatPlayer(happy, "Yes, I will.") + } + else -> afterQuest() + } + } + + private suspend fun Dialogue.beforeQuest() { + chatNpc(quiz, "What could you want with an old woman like me?") + when ( + choice2( + "I am in search of a quest.", 1, + "I've heard that you are a witch.", 2, + ) + ) { + 1 -> offerQuest() + 2 -> { + chatPlayer(neutral, "I've heard that you are a witch.") + chatNpc(neutral, "Yes, it does seem to be getting fairly common knowledge.") + chatNpc(worried, "I fear I may be getting a visit from the witch hunters of Falador before long.") + when ( + choice2( + "I am in search of a quest.", 1, + "Goodbye.", 2, + ) + ) { + 1 -> offerQuest() + 2 -> chatPlayer(neutral, "Goodbye.") + } + } + } + } + + private suspend fun Dialogue.offerQuest() { + chatPlayer(neutral, "I am in search of a quest.") + chatNpc(neutral, "Hmmm... Maybe I can think of something for you.") + chatNpc(quiz, "Would you like to become more proficient in the dark arts?") + when ( + choice2( + "Yes.", 1, + "No.", 2, + title = "Start the Witch's Potion quest?", + ) + ) { + 1 -> { + chatPlayer(shifty, "Yes, help me become one with my darker side.") + chatNpc(happy, "Okay, I'm going to make a potion to help bring out your darker self.") + chatNpc(neutral, "You will need certain ingredients.") + chatPlayer(quiz, "What do I need?") + chatNpc(neutral, "You need an eye of newt, a rat's tail, an onion... Oh, and a piece of burnt meat.") + chatPlayer(happy, "Great, I'll go and get them.") + quest.advanceQuestStage(access) + } + 2 -> { + chatPlayer(neutral, "No, I have my principles and honour.") + chatNpc(bored, "Suit yourself, but you're missing out.") + } + } + } + + private suspend fun Dialogue.gathering(npc: Npc) { + chatPlayer(neutral, "I've been looking for those ingredients.") + chatNpc(quiz, "So what have you found so far?") + if (witchsPotion.hasAllIngredients(player)) { + brew(npc) + return + } + val held = INGREDIENTS.filter { player.inv.count(it) > 0 } + if (held.isEmpty()) { + chatPlayer(sad, "I'm afraid I don't have any of them yet.") + chatNpc(angry, "Well, I can't make the potion without them! Remember... you need an eye of newt, a rat's tail, an onion, and a piece of burnt meat. Off you go, dear!") + return + } + chatPlayer(neutral, ingredientReport(held)) + chatNpc(neutral, "Great, but I'll need the other ingredients as well.") + } + + /** "I have the rat's tail (ewww), I don't have any burnt meat, ..." for whatever is carried. */ + private fun ingredientReport(held: List): String { + val tail = if (RATS_TAIL in held) "I have the rat's tail (ewww)" else "I don't have a rat's tail" + val meat = if (BURNT_MEAT in held) "I have the burnt meat" else "I don't have any burnt meat" + val onion = if (ONION in held) "I have an onion" else "I don't have an onion" + val newt = if (EYE_OF_NEWT in held) "I have the eye of newt, yum!" else "I don't have an eye of newt." + return "$tail, $meat, $onion, and $newt" + } + + /** All four ingredients go into the cauldron and Hetty works her charm over it. */ + private suspend fun Dialogue.brew(npc: Npc) { + chatPlayer(happy, "In fact, I have everything!") + chatNpc(happy, "Excellent! Can I have them then?") + for (ingredient in INGREDIENTS) { + access.invDel(access.inv, ingredient) + } + val cauldron = locRepo.findExact(CAULDRON_TILE, cauldronType) + if (cauldron != null) { + npc.lockFacing(CAULDRON_TILE) + } + npc.anim(CHARM_SEQ) + worldRepo.soundArea(CAULDRON_TILE, "synth.cauldron_bubbling", radius = SOUND_RADIUS) + if (cauldron != null) { + worldRepo.locAnim(cauldron, CAULDRON_SEQ) + } + mesbox("You pass the ingredients to Hetty and she puts them all into her cauldron. Hetty closes her eyes and begins to chant. The cauldron bubbles mysteriously.") + npc.clearFacingLock() + npc.facePlayer(player) + quest.advanceQuestStage(access) + chatPlayer(quiz, "Well, is it ready?") + chatNpc(happy, "Okay, now drink from the cauldron.") + } + + private suspend fun Dialogue.afterQuest() { + chatNpc(quiz, "What could you want with an old woman like me?") + when ( + choice2( + "Could you brew me another potion?", 1, + "I've heard that you are a witch.", 2, + ) + ) { + 1 -> { + chatPlayer(quiz, "Could you brew me another potion?") + chatNpc(neutral, "One taste of your darker side is quite enough, dear. Any more and you'd never get the smell of newt out of your clothes.") + } + 2 -> { + chatPlayer(neutral, "I've heard that you are a witch.") + chatNpc(neutral, "And you drank my potion all the same. I'd say we're both past pretending, wouldn't you?") + } + } + } + + private companion object { + const val CAULDRON = "loc.hettycauldron" + + /** Hetty's cauldron in her house in Rimmington. */ + val CAULDRON_TILE = CoordGrid(2967, 3205, 0) + + const val CHARM_SEQ = "seq.witchcharms" + const val CAULDRON_SEQ = "seq.cauldron" + const val SOUND_RADIUS = 8 + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/Cutscenes.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/Cutscenes.kt new file mode 100644 index 000000000..e49dc4258 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/Cutscenes.kt @@ -0,0 +1,51 @@ +package org.rsmod.content.quest.area.varrock.demonslayer + +import org.rsmod.api.player.protect.ProtectedAccess + +/* Shared fade helpers for the Demon Slayer cutscenes. Transparency 255 is fully see-through. */ + +internal suspend fun ProtectedAccess.fadeToBlack() { + fadeOverlay( + startColour = 0, + startTransparency = 255, + endColour = 0, + endTransparency = 0, + clientDuration = FADE_CLIENT_DURATION, + ) + clearHealthHud() + delay(FADE_CYCLES) +} + +internal suspend fun ProtectedAccess.fadeFromBlack() { + fadeOverlay( + startColour = 0, + startTransparency = 0, + endColour = 0, + endTransparency = 255, + clientDuration = FADE_CLIENT_DURATION, + ) + clearHealthHud() + delay(FADE_CYCLES) +} + +internal fun ProtectedAccess.beginCutscene() { + camModeClose() + hideTopLevel() + hideEntityOps() + minimapHideMap() + hideHealthHud() + closeTopLevelTabsLenient() +} + +internal fun ProtectedAccess.endCutscene() { + camReset() + camModeReset() + showEntityOps() + minimapReset() + showTopLevel() + openTopLevelTabs() + showHealthHud() +} + +private const val FADE_CLIENT_DURATION = 50 +private const val FADE_CYCLES = 3 diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DelrithFight.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DelrithFight.kt new file mode 100644 index 000000000..8371f0404 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DelrithFight.kt @@ -0,0 +1,393 @@ +package org.rsmod.content.quest.area.varrock.demonslayer + +import com.github.michaelbull.logging.InlineLogger +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 org.rsmod.api.instances.InstanceManager +import org.rsmod.api.instances.InstanceNpc +import org.rsmod.api.instances.events.InstancePlayerLeaveEvent +import org.rsmod.api.instances.events.instanceEventId +import org.rsmod.api.music.MusicRepository +import org.rsmod.api.npc.access.StandardNpcAccess +import org.rsmod.api.npc.interact.AiPlayerInteractions +import org.rsmod.api.npc.opPlayer2 +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.music.MusicPlayMode +import org.rsmod.api.player.music.MusicPlayer +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.output.soundSynth +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.protect.ProtectedAccessLauncher +import org.rsmod.api.player.vars.enumVarp +import org.rsmod.api.player.vars.intVarBit +import org.rsmod.api.repo.npc.NpcRepository +import org.rsmod.api.script.onArea +import org.rsmod.api.script.onEvent +import org.rsmod.api.script.onNpcQueue +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.table.MusicRow +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player +import org.rsmod.game.entity.PlayerList +import org.rsmod.game.map.Direction +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The end of Demon Slayer: walking into the stone circle with Silverlight moves the player into + * a private copy of it, where Denath and his dark wizards summon Delrith. Reducing Delrith to + * zero hitpoints leaves him weakened; the player then has a short window to speak the incantation + * Aris showed them. The right words banish him and complete the quest, the wrong ones restore + * him to full strength, and waiting too long lets him slip away until the player re-enters. + */ +class DelrithFight +@Inject +constructor( + private val quest: DemonSlayerQuest, + private val circle: StoneCircle, + private val manager: InstanceManager, + private val npcRepo: NpcRepository, + private val playerList: PlayerList, + private val launcher: ProtectedAccessLauncher, + private val aiInteractions: AiPlayerInteractions, + private val musicPlayer: MusicPlayer, + private val musicRepo: MusicRepository, +) : PluginScript() { + + /** "Delrith", played on a loop from the summoning through the battle. */ + private val fightTrack: MusicRow by lazy { MusicRow.getRow(FIGHT_TRACK) } + + private var Player.musicMode by enumVarp("varp.musicplay") + private val Player.currentMusicId by intVarBit("varbit.music_curr_id") + + /** What each fighter was listening to before the fight track took over, by player uuid. */ + private val previousMusic = HashMap>() + + private val delrithType = + ServerCacheManager.getNpc(DELRITH.asRSCM(RSCMType.NPC)) ?: error("Missing npc: $DELRITH") + private val weakenedType = + ServerCacheManager.getNpc(WEAKENED.asRSCM(RSCMType.NPC)) ?: error("Missing npc: $WEAKENED") + private val denathType = + ServerCacheManager.getNpc(DENATH.asRSCM(RSCMType.NPC)) ?: error("Missing npc: $DENATH") + + override fun ScriptContext.startup() { + onArea(CIRCLE_AREA) { tryEnterFight() } + onNpcQueue(delrithType, "queue.death") { weaken() } + onNpcQueue(weakenedType, TIMEOUT_QUEUE) { timeout() } + onOpNpc1(WEAKENED) { banish(it.npc) } + onEvent(instanceEventId(KEY)) { endFightMusic(player) } + } + + private suspend fun ProtectedAccess.tryEnterFight() { + if (getStage() != DemonSlayerQuest.STAGE_SILVERLIGHT) { + return + } + if (!with(quest) { carriesSilverlight() }) { + return + } + if (manager.sessionForPlayer(player) != null) { + return + } + enterFight() + } + + private fun ProtectedAccess.getStage(): Int = quest.quest.getQuestStage(player) + + private suspend fun ProtectedAccess.enterFight() { + fadeToBlack() + val visit = + try { + with(circle) { enterCircle(KEY, WIZARD_SPAWNS) } + } catch (e: Exception) { + logger.error(e) { "Demon Slayer fight entry failed for ${player.displayName}." } + mes("Demon Slayer: could not enter the circle (${e::class.simpleName}: ${e.message}).") + with(circle) { exitToRoad() } + null + } + if (visit == null) { + fadeFromBlack() + closeFadeOverlay() + return + } + // Let the client rebuild the instance before the camera or overlay are touched. + delay(1) + if (quest.seenSummoning.get(player)) { + fadeFromBlack() + closeFadeOverlay() + startFightMusic() + val delrith = spawnDelrith(visit, summoned = false) + engage(delrith, circle.npcsIn(visit)) + return + } + var delrith: Npc? = null + try { + delrith = summoningCutscene(visit) + } catch (e: Exception) { + logger.error(e) { "Demon Slayer summoning cutscene failed for ${player.displayName}." } + mes("Demon Slayer: the summoning scene failed (${e::class.simpleName}: ${e.message}).") + } finally { + endCutscene() + fadeFromBlack() + closeFadeOverlay() + } + // The scene is only ever shown once; if it broke, skip it next time rather than loop. + quest.seenSummoning.set(player, true) + quest.syncVars(player) + val wizards = circle.npcsIn(visit).filter { it.id != delrithType.id && it.id != denathType.id } + for (wizard in wizards) { + wizard.clearFacingLock() + wizard.mode = wizard.type.defaultMode + } + engage(delrith ?: spawnDelrith(visit, summoned = false), wizards) + } + + /** Plays the summoning and returns the Delrith it spawned. */ + private suspend fun ProtectedAccess.summoningCutscene(visit: StoneCircle.Visit): Npc { + beginCutscene() + camMoveTo(visit.at(CAMERA_FROM), height = CAMERA_HEIGHT, rate = CAMERA_RATE, rate2 = CAMERA_RATE) + camLookAt(visit.at(CAMERA_AT), height = LOOK_HEIGHT, rate = CAMERA_RATE, rate2 = CAMERA_RATE) + + val wizards = circle.npcsIn(visit).filter { it.id != delrithType.id } + val denath = circle.spawn(visit, DENATH, DENATH_TILE, Direction.North) + for (wizard in wizards) { + wizard.mode = NpcMode.None + wizard.lockFacing(visit.at(StoneCircle.TABLE), targetWidth = 2, targetLength = 2) + } + delay(1) + + fadeFromBlack() + startFightMusic() + denath.say("Rise, mighty Delrith! Rise, and bring ruin to this soft, weak city!") + denath.anim(CHANT_SEQ) + soundSynth("synth.curse_cast_and_fire") + delay(4) + for (wizard in wizards) { + wizard.say("Rise, Delrith!") + wizard.anim(CHANT_SEQ) + } + soundSynth("synth.curse_cast_and_fire") + delay(3) + mes("The wizards cast an evil spell...") + circle.animateTable(visit, "seq.qip_ds_table_explosion") + soundSynth("synth.crumble_hit") + delay(2) + val delrith = spawnDelrith(visit, summoned = true) + soundSynth("synth.summon_npc") + delay(4) + denath.say("At last! Welcome back, my demonic brother. Rest now, then take your revenge on Varrock!") + delay(4) + wizards.firstOrNull()?.say("Master, who is that?") + delay(3) + denath.say("No! That's Silverlight! Delrith isn't ready yet!") + delay(3) + denath.say("I'm not staying to find out what happens next...") + denath.clearFacingLock() + denath.walk(visit.at(DENATH_ESCAPE)) + delay(3) + circle.remove(denath) + fadeToBlack() + return delrith + } + + private fun spawnDelrith(visit: StoneCircle.Visit, summoned: Boolean): Npc { + val delrith = circle.spawn(visit, DELRITH, DELRITH_TILE, Direction.South) + delrith.mode = delrithType.defaultMode + if (summoned) { + delrith.anim("seq.qip_ds_delrith_summoned") + } + return delrith + } + + /** + * Delrith always comes for the player. The dark wizards only bother with players they would + * be aggressive towards in the open world: the level 7 wizards below combat 15, the level + * 20 wizards below combat 41. + */ + private fun ProtectedAccess.engage(delrith: Npc, wizards: List) { + delrith.opPlayer2(player, aiInteractions) + for (wizard in wizards) { + if (wizard.id == delrithType.id || !wizard.isSlotAssigned) { + continue + } + val threshold = if (wizard.type.combatLevel >= 20) 41 else 15 + if (player.combatLevel < threshold) { + wizard.opPlayer2(player, aiInteractions) + } + } + } + + /** + * Plays the fight track and switches the player to single-track mode so it loops for as long + * as the fight lasts. [endFightMusic] puts their previous mode and track back. + */ + private fun ProtectedAccess.startFightMusic() { + val uuid = player.uuid ?: return + previousMusic.putIfAbsent(uuid, player.musicMode to player.currentMusicId) + player.musicMode = MusicPlayMode.Manual + musicPlay(fightTrack) + } + + /** Restores the music from before the fight; safe to call more than once. */ + private fun endFightMusic(player: Player) { + val uuid = player.uuid ?: return + val (mode, trackId) = previousMusic.remove(uuid) ?: return + player.musicMode = mode + val previousRow = musicRepo.forId(trackId)?.let { MusicRow.getRow(it.rowId) } + when { + mode == MusicPlayMode.Manual && previousRow != null -> musicPlayer.play(player, previousRow) + mode == MusicPlayMode.Manual -> musicPlayer.stop(player) + else -> musicPlayer.skipTrack(player) + } + } + + /** Delrith's death queue: he does not die, he weakens and waits for the incantation. */ + private fun StandardNpcAccess.weaken() { + val hero = findHero(playerList) + val instanceId = manager.instanceForNpc(npc) + val coords = npc.coords + npcRepo.del(npc, Int.MAX_VALUE) + + val weakened = Npc(weakenedType, coords) + weakened.mode = NpcMode.None + npcRepo.add(weakened, Int.MAX_VALUE) + if (instanceId != null) { + manager.attachNpc(instanceId, weakened) + } + weakened.queue(TIMEOUT_QUEUE, TIMEOUT_TICKS) + + if (hero != null) { + hero.mes("Delrith staggers, weakened. Now is the time for the incantation!") + hero.soundSynth("synth.weaken_all") + launcher.launch(hero) { banish(weakened) } + } + } + + /** The player took too long: Delrith gathers his strength and vanishes until they return. */ + private fun StandardNpcAccess.timeout() { + if (!npc.isSlotAssigned) { + return + } + val instanceId = manager.instanceForNpc(npc) + val session = instanceId?.let { manager.sessionForId(it) } + if (session != null) { + for (occupant in session.occupants) { + val player = playerList.firstOrNull { it.uuid == occupant } ?: continue + player.mes("Delrith recovers and sinks back into the stone. Leave the circle and return to face him again.") + } + } + npcRepo.del(npc, Int.MAX_VALUE) + } + + private suspend fun ProtectedAccess.banish(weakened: Npc) { + if (!weakened.isSlotAssigned) { + mes("Delrith has already slipped away.") + return + } + val words = DemonSlayerQuest.WORDS + val chosen = ArrayList(words.size) + startDialogue { + chatPlayer(quiz, "Right... how did that incantation go again?") + for (step in words.indices) { + val pick = + choice5( + words[0], 0, + words[1], 1, + words[2], 2, + words[3], 3, + words[4], 4, + title = "Word ${step + 1} of the incantation", + ) + chosen += pick + val suffix = if (step == words.lastIndex) "!" else "..." + say(words[pick] + suffix) + delay(1) + } + } + if (!weakened.isSlotAssigned) { + mes("Delrith has already slipped away.") + return + } + if (chosen == quest.incantationOrder(player)) { + banished(weakened) + } else { + wrongIncantation(weakened) + } + } + + private suspend fun ProtectedAccess.banished(weakened: Npc) { + weakened.clearQueue(TIMEOUT_QUEUE) + weakened.anim("seq.qip_ds_delrith_banished") + soundSynth("synth.aide_teleport_portal") + mesbox("Delrith is dragged into the vortex...") + mesbox("...and back to the dark dimension he came from.") + circle.remove(weakened) + + with(circle) { exitToRoad() } + endFightMusic(player) + mes("You slip away from the furious dark wizards, the demon defeated.") + quest.advance(this) + } + + private suspend fun ProtectedAccess.wrongIncantation(weakened: Npc) { + soundSynth("synth.spellfail") + mesbox("The vortex fizzles out. That was not the right incantation.") + if (!weakened.isSlotAssigned) { + return + } + weakened.clearQueue(TIMEOUT_QUEUE) + val instanceId = manager.instanceForNpc(weakened) + val coords = weakened.coords + circle.remove(weakened) + + val delrith = Npc(delrithType, coords) + npcRepo.add(delrith, Int.MAX_VALUE) + if (instanceId != null) { + manager.attachNpc(instanceId, delrith) + } + mes("Delrith's strength returns!") + delrith.opPlayer2(player, aiInteractions) + } + + private companion object { + private val logger = InlineLogger() + + const val KEY = "demonslayer_fight" + const val CIRCLE_AREA = "area.demon_slayer_stone_circle" + const val TIMEOUT_QUEUE = "queue.demonslayer_delrith_timeout" + + /** Ticks a weakened Delrith waits for the incantation before slipping away. */ + const val TIMEOUT_TICKS = 100 + + const val DELRITH = "npc.delrith" + const val WEAKENED = "npc.delrith_weakened" + const val DENATH = "npc.qip_ds_dark_wizard_denath" + const val CHANT_SEQ = "seq.qip_ds_dark_wizard_chanting" + const val FIGHT_TRACK = "dbrow.music_delrith_summoning" + + const val CAMERA_HEIGHT = 700 + const val LOOK_HEIGHT = 150 + const val CAMERA_RATE = 100 + + val CAMERA_FROM = CoordGrid(3227, 3355, 0) + val CAMERA_AT = CoordGrid(3228, 3370, 0) + + /** Delrith is 2x2; this puts him just north of the stone table. */ + val DELRITH_TILE = CoordGrid(3227, 3371, 0) + + /** South of the table, facing it. */ + val DENATH_TILE = CoordGrid(3227, 3367, 0) + val DENATH_ESCAPE = CoordGrid(3235, 3362, 0) + + /** Two level 7 and one level 20 dark wizard, on the world spawn tiles around the table. */ + val WIZARD_SPAWNS = + listOf( + InstanceNpc("npc.qip_ds_young_dark_wizard1", CoordGrid(3225, 3365, 0)), + InstanceNpc("npc.qip_ds_young_dark_wizard2", CoordGrid(3230, 3374, 0)), + InstanceNpc("npc.qip_ds_young_dark_wizard3", CoordGrid(3232, 3367, 0)), + ) + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DemonSlayerDrain.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DemonSlayerDrain.kt new file mode 100644 index 000000000..95bef8ca8 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DemonSlayerDrain.kt @@ -0,0 +1,110 @@ +package org.rsmod.content.quest.area.varrock.demonslayer + +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpLocU +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest.Companion.KEY_DRAIN +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Sir Prysin's key: stuck in the drain outside the palace kitchen until water is poured down it, + * then lying in the mud of the Varrock sewers. The drain and mud locs are cache multilocs driven + * by `varbit.delrith_drain_key`, so each player sees the state matching their own progress. + * + * Also lets the palace kitchen sink fill water containers, since the quest expects it to. + */ +class DemonSlayerDrain @Inject constructor(private val demonSlayer: DemonSlayerQuest) : PluginScript() { + + private val quest + get() = demonSlayer.quest + + override fun ScriptContext.startup() { + onOpLoc1(DRAIN_WITH_KEY) { searchDrain() } + onOpLoc1(DRAIN_NO_KEY) { searchDrain() } + for (drain in listOf(DRAIN_WITH_KEY, DRAIN_NO_KEY)) { + for ((full, _) in WATER_CONTAINERS) { + onOpLocU(drain, full) { pourWater(full) } + } + } + onOpLoc1(SEWER_KEY) { takeSewerKey() } + onOpLoc1(SEWER_PIPE) { mes("A pipe runs down from the palace above. Anything dropped in the drain up there ends up around here.") } + for ((full, empty) in WATER_CONTAINERS) { + onOpLocU(KITCHEN_SINK, empty) { fillContainer(empty, full) } + } + } + + private suspend fun ProtectedAccess.searchDrain() { + if (quest.isQuestInProgress(player) && demonSlayer.drainKey.get(player) == 0) { + startDialogue { + chatPlayer(quiz, "That must be the key Sir Prysin dropped.") + chatPlayer(neutral, "I can't quite reach it. If I could dislodge it somehow it might wash down into the sewers, where I could pick it up.") + } + return + } + mes("Nothing interesting seems to have been dropped down here today.") + } + + private suspend fun ProtectedAccess.pourWater(container: String) { + val empty = WATER_CONTAINERS.first { it.first == container }.second + anim("seq.human_pickuptable") + soundSynth("synth.waterstrike_hit") + invReplace(inv, container, 1, empty) + if (quest.isQuestInProgress(player) && demonSlayer.drainKey.get(player) == 0) { + demonSlayer.drainKey.set(player, 1) + demonSlayer.syncVars(player) + soundSynth("synth.coins_jingle_1", delay = 20) + mes("You pour the water down the drain and hear a faint clink from below.") + startDialogue { + chatPlayer(happy, "That should have washed the key down into the sewer. I'd better go down and fetch it!") + } + return + } + mes("You pour the water down the drain.") + } + + private suspend fun ProtectedAccess.takeSewerKey() { + if (demonSlayer.drainKey.get(player) != 1) { + mes("There is nothing here but mud.") + return + } + if (inv.freeSpace() < 1) { + mes("You don't have enough room in your pack to pick up the key.") + return + } + anim("seq.human_pickupfloor") + soundSynth("synth.pick2") + delay(1) + invAdd(inv, KEY_DRAIN) + demonSlayer.drainKey.set(player, 2) + demonSlayer.syncVars(player) + objbox(KEY_DRAIN, "You pick up an old rusty key.") + } + + private suspend fun ProtectedAccess.fillContainer(empty: String, full: String) { + anim("seq.human_pickuptable") + soundSynth("synth.waterstrike_hit") + delay(1) + invReplace(inv, empty, 1, full) + val container = empty.removePrefix("obj.").substringBefore("_") + mes("You fill the $container with water from the sink.") + } + + private companion object { + const val DRAIN_WITH_KEY = "loc.qip_ds_questdrain_key" + const val DRAIN_NO_KEY = "loc.qip_ds_questdrain_nokey" + const val SEWER_KEY = "loc.qip_ds_rustykey_mud" + const val SEWER_PIPE = "loc.qip_ds_keypipe" + const val KITCHEN_SINK = "loc.fai_varrock_posh_sink" + + /** Full container to its empty counterpart. */ + val WATER_CONTAINERS = + listOf( + "obj.bucket_water" to "obj.bucket_empty", + "obj.jug_water" to "obj.jug_empty", + "obj.bowl_water" to "obj.bowl_empty", + "obj.vial_water" to "obj.vial_empty", + ) + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DemonSlayerQuest.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DemonSlayerQuest.kt new file mode 100644 index 000000000..3f4ebec3e --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DemonSlayerQuest.kt @@ -0,0 +1,219 @@ +package org.rsmod.content.quest.area.varrock.demonslayer + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Singleton +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.righthand +import org.rsmod.api.script.onPlayerLogin +import org.rsmod.content.quest.manager.ItemRewardDisplay +import org.rsmod.content.quest.manager.QuestScript +import org.rsmod.content.quest.manager.rewards +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Demon Slayer. + * + * Stage layout (stored in `varp.demonstart`, endstate 3 from `dbrow.quest_demonslayer`): + * - 0: not started + * - [STAGE_STARTED]: Aris has shown the vision; the three keys must be collected. + * - [STAGE_SILVERLIGHT]: Sir Prysin handed over Silverlight; Delrith must be banished. + * - [STAGE_COMPLETE]: Delrith banished. + * + * Every finer detail (incantation order, which keys were handed out, drain key, bones given to + * Traiborn, summoning cutscene seen) lives in persistent quest attributes. The cache varbits for + * those details share `varp.demonstart` with the stage, and the quest manager writes the whole + * varp when it syncs the stage, so the varbits are only mirrors and are re-applied from the + * attributes by [syncVars] whenever they might have been wiped. + */ +@Singleton +class DemonSlayerQuest : QuestScript( + "quest_demonslayer", + "varp.demonstart", + rewards { extra("Silverlight") }, + ItemRewardDisplay(SILVERLIGHT), +) { + /** Five digits, each the index into [WORDS]; empty until Aris reveals the incantation. */ + val incantation = quest.attribute(name = "INCANTATION", default = "") + + /** 0 = key still in the drain, 1 = washed into the sewer, 2 = picked up. */ + val drainKey = quest.attribute(name = "DRAIN_KEY", default = 0) + + /** Sir Prysin has explained the three keys, so later visits skip the introduction. */ + val prysinExplained = quest.attribute(name = "PRYSIN_EXPLAINED", default = false) + + val rovinKeyGiven = quest.attribute(name = "ROVIN_KEY_GIVEN", default = false) + val traibornAsked = quest.attribute(name = "TRAIBORN_ASKED", default = false) + val traibornKeyGiven = quest.attribute(name = "TRAIBORN_KEY_GIVEN", default = false) + val bonesGiven = quest.attribute(name = "BONES_GIVEN", default = 0) + val silverlightGiven = quest.attribute(name = "SILVERLIGHT_GIVEN", default = false) + val seenSummoning = quest.attribute(name = "SEEN_SUMMONING", default = false) + + override fun ScriptContext.init() { + // Registered after the quest manager's own login hook, which writes the whole varp and + // therefore wipes the mirrored varbits. + onPlayerLogin { syncVars(player) } + } + + override fun subTitle(): String = + "talking to Aris in her tent on the west side of " + + "Varrock Square." + + override fun questLog(player: ProtectedAccess) = + questJournal(player) { + objective( + "Aris the fortune teller showed me a vision of Delrith, a demon that " + + "once tried to destroy Varrock. She says I am destined to stop him, using " + + "the sword Silverlight and the incantation the hero Wally used.", + ) {} + + objective( + "Silverlight is kept by Sir Prysin in Varrock Palace, locked in a " + + "case that needs three keys.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_STARTED } + } + + objective( + "Captain Rovin, at the top of the north-west tower of the palace, " + + "holds one key.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_STARTED } + attribute(rovinKeyGiven, "I have Captain Rovin's key.").strike() + } + + objective( + "Sir Prysin dropped his key down the drain outside the palace " + + "kitchen. Pouring water down it should wash the key into the sewers.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_STARTED } + custom(drainKey.get(access.player) == 1, "I washed the key into the sewers. I should climb down the manhole east of the palace and look for it.") + custom(drainKey.get(access.player) >= 2, "I have the key that was dropped down the drain.").strike() + } + + objective( + "Wizard Traiborn, on the first floor of the Wizards' Tower, holds " + + "the last key. He needs 25 sets of bones for the ritual that " + + "opens his wardrobe.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_STARTED } + custom( + traibornAsked.get(access.player) && !traibornKeyGiven.get(access.player), + "I have given Traiborn ${bonesGiven.get(access.player)} of the 25 sets of bones he needs.", + ) + attribute(traibornKeyGiven, "I have Wizard Traiborn's key.").strike() + } + + objective( + "Sir Prysin gave me Silverlight. Delrith is being summoned at the " + + "stone circle south of Varrock. I must weaken him with " + + "Silverlight and banish him with the incantation.", + ) { + visibleWhen { quest.getQuestStage(access.player) >= STAGE_SILVERLIGHT } + custom( + !access.hasSilverlight(), + "I have lost Silverlight. Sir Prysin may be able to help me find it.", + ) + } + + objective("If I forget the incantation, Aris can remind me of it.") { + visibleWhen { quest.getQuestStage(access.player) >= STAGE_STARTED } + } + } + + override fun completedLog(player: ProtectedAccess): String = + completionJournal(player) { + line( + "Aris the fortune teller saw the demon Delrith in her crystal ball and told me I " + + "was destined to stop him, just as the hero Wally did long ago.", + ) + line( + "I gathered the three keys to Silverlight's case from Captain Rovin, Wizard " + + "Traiborn and the palace drain, and Sir Prysin handed me the sword.", + ) + line( + "At the stone circle south of Varrock I struck down the newly summoned Delrith " + + "with Silverlight and banished him with the incantation.", + ) + } + + /** The incantation for [player], generating and storing a random order on first use. */ + fun incantationOrder(player: Player): List { + val stored = incantation.get(player) + if (stored.length == WORDS.size && stored.all { it.isDigit() }) { + return stored.map { it.digitToInt() } + } + val order = WORDS.indices.shuffled() + incantation.set(player, order.joinToString("")) + syncVars(player) + return order + } + + fun incantationWords(player: Player): List = incantationOrder(player).map { WORDS[it] } + + /** The spoken form, e.g. `Aber... Carlem... Gabindo... Purchai... Camerinthum!`. */ + fun incantationSpoken(player: Player): String { + val words = incantationWords(player) + return words.dropLast(1).joinToString(" ") { "$it..." } + " " + words.last() + "!" + } + + fun advance(access: ProtectedAccess) { + quest.advanceQuestStage(access) + syncVars(access.player) + } + + /** Re-applies the client-facing varbits from the persisted attributes. */ + fun syncVars(player: Player) { + val order = incantation.get(player) + player.dsIncantation1 = order.getOrNull(0)?.digitToIntOrNull()?.plus(1) ?: 0 + player.dsIncantation2 = order.getOrNull(1)?.digitToIntOrNull()?.plus(1) ?: 0 + player.dsIncantation3 = order.getOrNull(2)?.digitToIntOrNull()?.plus(1) ?: 0 + player.dsIncantation4 = order.getOrNull(3)?.digitToIntOrNull()?.plus(1) ?: 0 + player.dsIncantation5 = order.getOrNull(4)?.digitToIntOrNull()?.plus(1) ?: 0 + player.dsDrainKey = drainKey.get(player) + player.dsSilverlightCase = silverlightGiven.get(player) + player.dsSeenSummoning = seenSummoning.get(player) + } + + fun hasKey(player: Player, key: String): Boolean = player.inv.count(key) > 0 + + fun hasAllKeys(player: Player): Boolean = KEYS.all { hasKey(player, it) } + + fun keysHeld(player: Player): List = KEYS.filter { hasKey(player, it) } + + fun isWieldingSilverlight(player: Player): Boolean = player.righthand?.id == SILVERLIGHT_ID + + /** Silverlight wielded, carried or banked. */ + fun ProtectedAccess.hasSilverlight(): Boolean = + isWieldingSilverlight(player) || inv.count(SILVERLIGHT) > 0 || bank.count(SILVERLIGHT) > 0 + + fun ProtectedAccess.carriesSilverlight(): Boolean = + isWieldingSilverlight(player) || inv.count(SILVERLIGHT) > 0 + + companion object { + const val STAGE_STARTED = 1 + const val STAGE_SILVERLIGHT = 2 + const val STAGE_COMPLETE = 3 + + const val SILVERLIGHT = "obj.silverlight" + val SILVERLIGHT_ID: Int = SILVERLIGHT.asRSCM(RSCMType.OBJ) + + /** Wizard Traiborn's key. */ + const val KEY_TRAIBORN = "obj.silverlight_key_1" + + /** Captain Rovin's key. */ + const val KEY_ROVIN = "obj.silverlight_key_2" + + /** The key Sir Prysin dropped down the drain. */ + const val KEY_DRAIN = "obj.silverlight_key_3" + + val KEYS = listOf(KEY_ROVIN, KEY_DRAIN, KEY_TRAIBORN) + + const val BONES_REQUIRED = 25 + const val SILVERLIGHT_REPLACEMENT_COST = 500 + + /** The incantation words, in the fixed order the banish prompt offers them. */ + val WORDS = listOf("Carlem", "Aber", "Camerinthum", "Purchai", "Gabindo") + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DemonSlayerVars.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DemonSlayerVars.kt new file mode 100644 index 000000000..62d00999e --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/DemonSlayerVars.kt @@ -0,0 +1,25 @@ +package org.rsmod.content.quest.area.varrock.demonslayer + +import org.rsmod.api.player.vars.boolVarBit +import org.rsmod.api.player.vars.intVarBit +import org.rsmod.game.entity.Player + +/* + * Client-facing mirrors of the quest attributes. All of these share `varp.demonstart` with the + * quest stage; see [DemonSlayerQuest.syncVars]. + */ + +var Player.dsIncantation1 by intVarBit("varbit.delrith_incantation_1") +var Player.dsIncantation2 by intVarBit("varbit.delrith_incantation_2") +var Player.dsIncantation3 by intVarBit("varbit.delrith_incantation_3") +var Player.dsIncantation4 by intVarBit("varbit.delrith_incantation_4") +var Player.dsIncantation5 by intVarBit("varbit.delrith_incantation_5") + +/** Drives the drain and sewer-mud multilocs: 0 key in drain, 1 key in the sewer, 2 taken. */ +var Player.dsDrainKey by intVarBit("varbit.delrith_drain_key") + +/** Drives the sword case multiloc in Sir Prysin's room: full while the sword is still inside. */ +var Player.dsSilverlightCase by boolVarBit("varbit.delrith_silverlight_case") + +/** Drives the stone table multiloc at the circle: intact until the summoning has been seen. */ +var Player.dsSeenSummoning by boolVarBit("varbit.delrith_seen_summoning_cutscene") diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/SilverlightAttackHook.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/SilverlightAttackHook.kt new file mode 100644 index 000000000..78c0ab551 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/SilverlightAttackHook.kt @@ -0,0 +1,25 @@ +package org.rsmod.content.quest.area.varrock.demonslayer + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.death.NpcAttackValidateHook +import org.rsmod.api.death.NpcAttackValidateResult +import org.rsmod.api.player.righthand +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player + +/** Delrith can only be fought with Silverlight in hand. */ +class SilverlightAttackHook @Inject constructor() : NpcAttackValidateHook { + private val delrithId = "npc.delrith".asRSCM(RSCMType.NPC) + + override fun validate(player: Player, npc: Npc): NpcAttackValidateResult { + if (npc.id != delrithId) { + return NpcAttackValidateResult.Pass + } + if (player.righthand?.id != DemonSlayerQuest.SILVERLIGHT_ID) { + return NpcAttackValidateResult.Deny("You'll need to wield Silverlight to harm Delrith.") + } + return NpcAttackValidateResult.Pass + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/StoneCircle.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/StoneCircle.kt new file mode 100644 index 000000000..821e63823 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/StoneCircle.kt @@ -0,0 +1,169 @@ +package org.rsmod.content.quest.area.varrock.demonslayer + +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.instances.InstanceAccess +import org.rsmod.api.instances.InstanceArea +import org.rsmod.api.instances.InstanceManager +import org.rsmod.api.instances.InstanceNpc +import org.rsmod.api.instances.InstanceSession +import org.rsmod.api.instances.InstanceSpec +import org.rsmod.api.instances.RegionLocal +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.repo.npc.NpcRepository +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.game.entity.Npc +import org.rsmod.game.map.Direction +import org.rsmod.map.CoordGrid + +/** + * Private copies of the stone circle south of Varrock (map square 50,52). Both Demon Slayer + * cutscenes run in one: Aris' vision of Wally, and the summoning and banishing of Delrith. + * + * World coordinates are used everywhere in the quest scripts; [Visit.at] translates them into + * the copied region, which keeps the same local layout as the world map square. + */ +@Singleton +class StoneCircle +@Inject +constructor( + private val manager: InstanceManager, + private val npcRepo: NpcRepository, + private val locRepo: LocRepository, + private val worldRepo: WorldRepository, +) { + private val tableType = ServerCacheManager.getObject(TABLE_LOC.asRSCM(RSCMType.LOC)) + ?: error("Missing loc type: $TABLE_LOC") + + class Visit(val session: InstanceSession, val enter: CoordGrid) { + private val dx = enter.x - ENTER.x + private val dz = enter.z - ENTER.z + + /** The instance tile matching world tile ([x], [z]). */ + fun at(x: Int, z: Int): CoordGrid = CoordGrid(x + dx, z + dz, enter.level) + + fun at(world: CoordGrid): CoordGrid = at(world.x, world.z) + } + + /** + * Creates a private copy of the circle for the player and telejumps them to [ENTER]. Returns + * null (after messaging the player) when no instance could be made. + */ + fun ProtectedAccess.enterCircle(key: String, spawns: List): Visit? { + if (manager.sessionForPlayer(player) != null) { + mes("You are already inside an instance.") + return null + } + val area = + InstanceArea.copyRegions( + regionIds = listOf(REGION_ID), + level = 0, + enterCoord = RegionLocal(0, ENTER.mx, ENTER.mz, ENTER.lx, ENTER.lz), + exitCoord = EXIT, + npcSpawns = spawns, + ) + val spec = + InstanceSpec( + fee = 0, + maxPlayers = 1, + reclaimTicks = RECLAIM_TICKS, + graceTicks = GRACE_TICKS, + destroyWhenEmpty = true, + area = area, + settingsRowId = -1, + bossName = "Delrith", + ) + return when (val result = manager.create(player, key, spec, InstanceAccess.Private, mapClock)) { + is InstanceManager.Result.Failed -> { + mes(result.reason) + null + } + is InstanceManager.Result.Created -> settle(result.session, result.enter) + is InstanceManager.Result.Joined -> settle(result.session, result.enter) + } + } + + /** + * Moves the player into the copy. Cutscene teleports are exempt from the teleport validator: + * the world's dark wizards are usually mid-attack when the player walks into the circle, and + * a refused teleport would leave the scene playing to an empty camera. + */ + private fun ProtectedAccess.settle(session: InstanceSession, enter: CoordGrid): Visit? { + telejump(enter, TeleportType.Exempt) + if (player.coords != enter) { + mes("You can't enter the stone circle right now.") + manager.leave(player, session, mapClock) + return null + } + manager.finalizeEntry(player, session, mapClock) + return Visit(session, enter) + } + + /** Removes the player from their visit; the instance is destroyed once empty. */ + fun ProtectedAccess.leaveCircle(): CoordGrid? { + val session = manager.sessionForPlayer(player) ?: return null + return manager.leave(player, session, mapClock) + } + + /** Leaves any circle copy the player is in and puts them back on the road outside. */ + fun ProtectedAccess.exitToRoad() { + leaveCircle() + telejump(EXIT, TeleportType.Exempt) + } + + /** Spawns a cutscene npc at world tile [world] inside the visit and ties it to the instance. */ + fun spawn(visit: Visit, type: String, world: CoordGrid, face: Direction? = null): Npc { + val npc = Npc(type, visit.at(world)) + npc.mode = NpcMode.None + if (face != null) { + npc.respawnDir = face + } + npcRepo.add(npc, Int.MAX_VALUE) + manager.attachNpc(visit.session.id, npc) + if (face != null) { + npc.lockFacingDirection(face) + } + return npc + } + + fun remove(npc: Npc) { + if (npc.isSlotAssigned) { + npcRepo.del(npc, Int.MAX_VALUE) + } + } + + /** The npcs the instance itself spawned (the dark wizards), plus anything attached since. */ + fun npcsIn(visit: Visit): List = manager.npcsForInstance(visit.session.id) + + fun animateTable(visit: Visit, seq: String) { + val loc = locRepo.findExact(visit.at(TABLE), tableType) ?: return + worldRepo.locAnim(loc, seq) + } + + companion object { + const val REGION_ID = 12852 + + /** + * Just north of the circle, on the path down from Varrock's south gate. Close enough that + * every actor in the summoning scene is inside npc view range of the player. + */ + val ENTER = CoordGrid(3227, 3378, 0) + + /** Where the player lands after leaving an instance. */ + val EXIT = CoordGrid(3227, 3382, 0) + + /** South-west tile of the 2x2 stone table in the middle of the circle. */ + val TABLE = CoordGrid(3227, 3369, 0) + + const val TABLE_LOC = "loc.qip_ds_stone_table" + + private const val RECLAIM_TICKS = 100 + private const val GRACE_TICKS = 50 + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/WallyVision.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/WallyVision.kt new file mode 100644 index 000000000..761724831 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/WallyVision.kt @@ -0,0 +1,136 @@ +package org.rsmod.content.quest.area.varrock.demonslayer + +import com.github.michaelbull.logging.InlineLogger +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.music.MusicPlayer +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.table.MusicRow +import org.rsmod.game.map.Direction +import org.rsmod.map.CoordGrid + +/** + * Aris' crystal-ball vision: Wally striking down the newly summoned Delrith at the stone circle + * and chanting the incantation. Runs in a private copy of the circle, then returns the player to + * where they were standing. + * + * Whatever happens inside the scene, the player always ends up back at their origin with the + * camera, interface and fade overlay restored. [play] returns false when the scene could not be + * shown so the caller can narrate it instead. + */ +@Singleton +class WallyVision +@Inject +constructor( + private val circle: StoneCircle, + private val musicPlayer: MusicPlayer, +) { + private val visionTrack: MusicRow by lazy { MusicRow.getRow(VISION_TRACK) } + + suspend fun ProtectedAccess.play(quest: DemonSlayerQuest): Boolean { + val origin = player.coords + val words = quest.incantationSpoken(player) + + fadeToBlack() + val visit = with(circle) { enterCircle(KEY, emptyList()) } + if (visit == null) { + fadeFromBlack() + closeFadeOverlay() + return false + } + + var played = false + try { + // Let the client rebuild the instance before anything camera- or overlay-related is + // sent; packets sent in the same cycle as the teleport are applied to the old scene. + // The player must stay within npc view range (about 15 tiles) of the actors or the + // client is never told about them, so they park just behind the camera. + telejump(visit.at(PLAYER_PARK), TeleportType.Exempt) + delay(1) + if (player.coords.chebyshevDistance(visit.at(PLAYER_PARK)) > 1) { + logger.warn { "Demon Slayer vision: player did not arrive in the instance (coords=${player.coords})." } + return false + } + playScene(visit, words) + played = true + } catch (e: Exception) { + logger.error(e) { "Demon Slayer vision failed for ${player.displayName}." } + } finally { + fadeToBlack() + endCutscene() + with(circle) { leaveCircle() } + telejump(origin, TeleportType.Exempt) + delay(1) + fadeFromBlack() + closeFadeOverlay() + // Hand the music back to the area (or shuffle) once the vision is over. + musicPlayer.skipTrack(player) + } + return played + } + + private suspend fun ProtectedAccess.playScene(visit: StoneCircle.Visit, words: String) { + beginCutscene() + camMoveTo(visit.at(CAMERA_FROM), height = CAMERA_HEIGHT, rate = CAMERA_RATE, rate2 = CAMERA_RATE) + camLookAt(visit.at(CAMERA_AT), height = LOOK_HEIGHT, rate = CAMERA_RATE, rate2 = CAMERA_RATE) + + val wally = circle.spawn(visit, WALLY, WALLY_TILE, Direction.North) + val delrith = circle.spawn(visit, DELRITH, DELRITH_TILE, Direction.South) + val denath = circle.spawn(visit, DENATH, DENATH_TILE, Direction.East) + delay(1) + + fadeFromBlack() + musicPlay(visionTrack) + denath.say("Rise, Delrith! Rise and serve your master!") + soundSynth("synth.summon_npc") + delay(3) + wally.say("Begone, foul demon!") + wally.anim("seq.qip_ds_wally_cutscene") + soundSynth("synth.cleave") + delay(3) + wally.say("Now, how did that incantation go again?") + delay(3) + wally.say(words) + delay(4) + delrith.anim("seq.qip_ds_delrith_struck_down") + soundSynth("synth.weaken_all") + delay(3) + delrith.anim("seq.qip_ds_delrith_banished") + soundSynth("synth.aide_teleport_portal") + delay(3) + circle.remove(delrith) + denath.say("No! My master!") + delay(2) + wally.say("Ha! No demon can stand against Wally the demon slayer!") + delay(3) + } + + private companion object { + private val logger = InlineLogger() + + const val KEY = "demonslayer_vision" + + const val WALLY = "npc.qip_ds_wally" + const val DELRITH = "npc.delrith" + const val DENATH = "npc.qip_ds_dark_wizard_denath" + + /** "Wally the Hero", the music track the vision plays over. */ + const val VISION_TRACK = "dbrow.music_wally_cutscene" + const val CAMERA_HEIGHT = 650 + const val LOOK_HEIGHT = 150 + const val CAMERA_RATE = 100 + + val PLAYER_PARK = CoordGrid(3227, 3360, 0) + val CAMERA_FROM = CoordGrid(3227, 3357, 0) + val CAMERA_AT = CoordGrid(3228, 3370, 0) + + /** South of the table, facing it. */ + val WALLY_TILE = CoordGrid(3227, 3366, 0) + + /** Delrith is 2x2; this puts him just north of the table. */ + val DELRITH_TILE = CoordGrid(3227, 3371, 0) + + val DENATH_TILE = CoordGrid(3224, 3371, 0) + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/Aris.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/Aris.kt new file mode 100644 index 000000000..7ffaa3c14 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/Aris.kt @@ -0,0 +1,254 @@ +package org.rsmod.content.quest.area.varrock.demonslayer.npcs + +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest +import org.rsmod.content.quest.area.varrock.demonslayer.WallyVision +import org.rsmod.game.entity.Npc +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Aris, the fortune teller in the tent on the west side of Varrock Square. Starts Demon Slayer. */ +class Aris +@Inject +constructor( + private val demonSlayer: DemonSlayerQuest, + private val vision: WallyVision, +) : PluginScript() { + + private val quest + get() = demonSlayer.quest + + private companion object { + const val WALLY = "npc.qip_ds_wally" + } + + override fun ScriptContext.startup() { + onOpNpc1("npc.aris") { startDialogue(it.npc) { aris(it.npc) } } + } + + private suspend fun Dialogue.aris(npc: Npc) { + demonSlayer.syncVars(player) + when { + quest.isQuestCompleted(player) -> afterQuest() + quest.isQuestInProgress(player) -> duringQuest(npc) + else -> beforeQuest(npc) + } + } + + private suspend fun Dialogue.beforeQuest(npc: Npc) { + chatNpc(happy, "Greetings, young one. Cross my palm with a coin and I shall reveal what the future holds for you.") + if (access.invCoinTotal() < 1) { + chatPlayer(sad, "I'm afraid I haven't got a single coin on me.") + chatNpc(neutral, "Then come back when you do. The future can wait a little longer.") + return + } + val pay = + choice2( + "Yes.", true, + "No.", false, + title = "Pay Aris one coin for a reading?", + ) + if (!pay) { + chatPlayer(neutral, "No thanks. I don't put much faith in fortune telling.") + chatNpc(neutral, "Suit yourself, young one.") + return + } + if (!access.invTakeFee(1)) { + chatPlayer(sad, "I'm afraid I haven't got a single coin on me.") + return + } + chatPlayer(happy, "Here you go, one coin.") + npc.anim("seq.qip_ds_reading_crystalball") + access.soundSynth("synth.crystal_sing") + chatNpc(neutral, "Come closer and listen well, for the mists in the crystal ball are parting...") + chatNpc(neutral, "I see shapes forming. I see... you. You are holding a very fine sword. I'm sure I know that blade from somewhere...") + chatNpc(worried, "Now a great dark shadow looms behind you.") + chatNpc(shocked, "Aaargh!") + chatPlayer(worried, "Are you all right?") + chatNpc(shocked, "It's Delrith! Delrith is coming!") + chatPlayer(quiz, "Who is Delrith?") + chatNpc(worried, "Delrith is a powerful demon. I only hope he didn't notice me watching him through the crystal ball!") + chatNpc(neutral, "He tried to destroy this city a hundred and fifty years ago. He was stopped just in time by the great hero Wally.") + chatNpc(neutral, "With his magic sword Silverlight, Wally trapped the demon inside the stone circle just south of the city.") + chatNpc(shocked, "Ye gods! Silverlight was the very sword you were holding in my vision. You are the one destined to stop the demon this time!") + + demonSlayer.incantationOrder(player) + demonSlayer.advance(access) + questionsBeforeVision(npc) + } + + private suspend fun Dialogue.questionsBeforeVision(npc: Npc) { + while (true) { + when ( + choice4( + "How am I meant to fight a demon who can destroy cities?", 1, + "Okay, where is he? I'll kill him for you!", 2, + "Wally doesn't sound like a very heroic name.", 3, + "So how did Wally kill Delrith?", 4, + title = "What would you like to say?", + ) + ) { + 1 -> howToFight() + 2 -> whereIsHe() + 3 -> wallysName() + 4 -> { + howWallyWon() + questionsAfterVision(npc) + return + } + } + } + } + + private suspend fun Dialogue.questionsAfterVision(npc: Npc) { + while (true) { + when ( + choice5( + "What is the magical incantation?", 1, + "Where can I find Silverlight?", 2, + "How am I meant to fight a demon who can destroy cities?", 3, + "Okay, where is he? I'll kill him for you!", 4, + "Okay, thanks. I'll do my best to stop the demon.", 5, + title = "What would you like to say?", + ) + ) { + 1 -> incantation() + 2 -> whereIsSilverlight() + 3 -> howToFight() + 4 -> whereIsHe() + 5 -> { + chatPlayer(happy, "Okay, thanks. I'll do my best to stop the demon.") + chatNpc(happy, "Good luck, young one, and may Guthix watch over you!") + return + } + } + } + } + + private suspend fun Dialogue.howToFight() { + chatPlayer(worried, "How am I meant to fight a demon who can destroy cities?!") + chatNpc(neutral, "Face Delrith while he is still weak from being summoned, and use the right weapon, and the task will not be beyond you.") + chatNpc(happy, "Do not fear. Follow the path of the great hero Wally and you are sure to defeat the demon.") + } + + private suspend fun Dialogue.whereIsHe() { + chatPlayer(happy, "Okay, where is he? I'll kill him for you!") + chatNpc(laugh, "Ah, the confidence of the young!") + chatNpc(neutral, "Delrith cannot be harmed by ordinary weapons. You must face him with the same sword Wally used.") + } + + private suspend fun Dialogue.wallysName() { + chatPlayer(quiz, "Wally doesn't sound like a very heroic name.") + chatNpc(neutral, "Perhaps that is why history has forgotten him. He was a very great hero all the same.") + chatNpc(worried, "Who knows what suffering Delrith would have caused had Wally not stopped him! It seems you will need to match his heroics.") + } + + private suspend fun Dialogue.howWallyWon() { + chatPlayer(quiz, "So how did Wally kill Delrith?") + chatNpc(neutral, "Wally reached the stone circle just as a cult of dark wizards finished summoning Delrith...") + val played = with(vision) { access.play(demonSlayer) } + if (!played) { + narrateVision() + } + chatNpc(neutral, "By speaking the correct incantation and thrusting Silverlight into the newly summoned demon, Wally sealed Delrith inside the stone block at the heart of the circle.") + chatNpc(worried, "Delrith will come forth from that circle again. I expect some evil sorcerer is already at work on the rituals to summon him as we speak.") + } + + /** Told in dialogue when the instanced vision cannot be shown, so the words are never lost. */ + private suspend fun Dialogue.narrateVision() { + chatNpc(neutral, "The mists show me the scene as clearly as if I stood there. The wizards chant, the stone splits, and the demon rises...") + chatNpcSpecific("Wally", WALLY, angry, "Begone, foul demon!") + chatNpcSpecific("Wally", WALLY, confused, "Now, how did that incantation go again?") + chatNpcSpecific("Wally", WALLY, angry, demonSlayer.incantationSpoken(player)) + chatNpc(neutral, "And with those words, and Silverlight driven into the demon, Delrith was dragged back into the stone.") + } + + private suspend fun Dialogue.incantation() { + chatPlayer(quiz, "What is the magical incantation?") + chatNpc(neutral, "Oh yes, give me a moment to think...") + chatNpc(happy, "Right, I have it now. It goes: ${demonSlayer.incantationSpoken(player)} Have you got that?") + chatPlayer(neutral, "I think so, yes.") + } + + private suspend fun Dialogue.whereIsSilverlight() { + chatPlayer(quiz, "Where can I find Silverlight?") + chatNpc(neutral, "Silverlight has passed down through Wally's descendants. I believe it is currently kept by one of the King's knights, Sir Prysin.") + chatNpc(neutral, "He shouldn't be hard to find. He lives in the royal palace here in Varrock. Tell him Aris sent you.") + } + + private suspend fun Dialogue.duringQuest(npc: Npc) { + val stage = quest.getQuestStage(player) + chatNpc(happy, "Greetings, young one. How goes your quest?") + if (stage >= DemonSlayerQuest.STAGE_SILVERLIGHT) { + chatPlayer(happy, "I have the sword now. I just need to deal with the demon.") + chatNpc(happy, "Then hurry. Delrith will not wait for you to be ready.") + } else { + chatPlayer(neutral, "I found Sir Prysin, but I haven't got the sword yet. He has made it rather complicated.") + chatNpc(worried, "Then make haste. We haven't much time.") + } + while (true) { + val pick = + if (stage >= DemonSlayerQuest.STAGE_SILVERLIGHT) { + choice4( + "What is the magical incantation?", 1, + "Where can I find the demon?", 3, + "Stop calling me that!", 4, + "Well, I'd better press on with it.", 5, + title = "What would you like to say?", + ) + } else { + choice4( + "What is the magical incantation?", 1, + "Where can I find Silverlight?", 2, + "Stop calling me that!", 4, + "Well, I'd better press on with it.", 5, + title = "What would you like to say?", + ) + } + when (pick) { + 1 -> incantation() + 2 -> whereIsSilverlight() + 3 -> { + chatPlayer(quiz, "Where can I find the demon?") + chatNpc(neutral, "Head south out of the city gate. The stone circle lies just beyond it, east of the road.") + } + 4 -> stopCallingMeThat() + 5 -> { + chatPlayer(neutral, "Well, I'd better press on with it.") + chatNpc(happy, "Until next time, young one.") + return + } + } + } + } + + private suspend fun Dialogue.stopCallingMeThat() { + chatPlayer(angry, "Stop calling me that!") + chatNpc(neutral, "In the great scheme of things, you are very young indeed.") + when ( + choice2( + "Okay, but how old are you?", 1, + "Oh, if it's in the scheme of things, that's fine.", 2, + ) + ) { + 1 -> { + chatPlayer(quiz, "Okay, but how old are you?") + chatNpc(shifty, "Count the legs on every stool in the Blue Moon Inn, then multiply by seven.") + chatPlayer(confused, "Er... right. Whatever you say.") + } + 2 -> { + chatPlayer(neutral, "Oh, if it's in the scheme of things, that's fine.") + chatNpc(happy, "You show wisdom for one so young.") + } + } + } + + private suspend fun Dialogue.afterQuest() { + chatNpc(happy, "Greetings, demon slayer! The crystal ball showed me everything. Varrock owes you a great debt.") + chatPlayer(happy, "All in a day's work.") + chatNpc(neutral, "Rest while you can, young one. The mists have a habit of clearing again.") + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/CaptainRovin.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/CaptainRovin.kt new file mode 100644 index 000000000..a2f751685 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/CaptainRovin.kt @@ -0,0 +1,153 @@ +package org.rsmod.content.quest.area.varrock.demonslayer.npcs + +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest.Companion.KEY_ROVIN +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Captain Rovin, at the top of the north-west tower of Varrock Palace. Holds one key. */ +class CaptainRovin @Inject constructor(private val demonSlayer: DemonSlayerQuest) : PluginScript() { + + private val quest + get() = demonSlayer.quest + + override fun ScriptContext.startup() { + onOpNpc1("npc.captain_rovin") { startDialogue(it.npc) { rovin() } } + } + + private suspend fun Dialogue.rovin() { + chatNpc(angry, "What are you doing up here? Only the palace guards are allowed up here.") + val onQuest = quest.getQuestStage(player) == DemonSlayerQuest.STAGE_STARTED + val pick = + if (onQuest) { + choice4( + "I am one of the palace guards.", 1, + "What about the King?", 2, + "Yes I know, but this is important.", 3, + "Erm... I forgot.", 4, + ) + } else { + choice3( + "I am one of the palace guards.", 1, + "What about the King?", 2, + "Erm... I forgot.", 4, + ) + } + when (pick) { + 1 -> { + chatPlayer(shifty, "I am one of the palace guards.") + chatNpc(angry, "No, you're not! I know every guard in this palace and you're not one of them. Now get out!") + } + 2 -> { + chatPlayer(quiz, "What about the King?") + chatNpc(neutral, "Well, yes, the King can come up here too. He's the King, after all. You are not.") + } + 3 -> important() + 4 -> { + chatPlayer(confused, "Erm... I forgot.") + chatNpc(angry, "Then go and remember somewhere else!") + } + } + } + + private suspend fun Dialogue.important() { + chatPlayer(neutral, "Yes I know, but this is important.") + chatNpc(neutral, "Alright, I'm listening. What's so important?") + chatPlayer(worried, "There's a demon who wants to invade the city.") + if (demonSlayer.rovinKeyGiven.get(player)) { + keyAlreadyGiven() + return + } + chatNpc(quiz, "Is it a powerful demon?") + when ( + choice2( + "Not really.", 1, + "Yes, very.", 2, + ) + ) { + 1 -> { + chatPlayer(neutral, "Not really.") + chatNpc(neutral, "Then I'm sure the palace guards can handle it. Thank you for the warning.") + return + } + } + chatPlayer(worried, "Yes, very.") + chatNpc(worried, "The palace guards are good, but I'm not sure they're up to fighting a very powerful demon.") + when ( + choice2( + "Yeah, the palace guards are rubbish!", 1, + "It's not them who are going to fight the demon, it's me.", 2, + ) + ) { + 1 -> { + chatPlayer(laugh, "Yeah, the palace guards are rubbish!") + chatNpc(neutral, "Yeah, they're...") + chatNpc(verymad, "Wait! How dare you insult the palace guards? Get out of my sight!") + return + } + } + chatPlayer(neutral, "It's not them who are going to fight the demon, it's me.") + chatNpc(quiz, "What, all by yourself? And how do you plan to do that?") + chatPlayer(neutral, "With the sword Silverlight. I believe you hold one of the keys to its case?") + chatNpc(neutral, "I do. But why should I hand it over to you?") + while (true) { + when ( + choice3( + "Fortune-teller Aris said I was destined to kill the demon.", 1, + "Otherwise the demon will destroy the city!", 2, + "Sir Prysin said you would give me the key.", 3, + ) + ) { + 1 -> { + chatPlayer(neutral, "Fortune-teller Aris said I was destined to kill the demon.") + chatNpc(angry, "A fortune-teller? Destiny? I don't hold with that nonsense. I got where I am by hard work, not destiny! Why should I care what that old woman says?") + } + 2 -> { + chatPlayer(worried, "Otherwise the demon will destroy the city!") + chatNpc(angry, "You can't fool me. How do I know you haven't made the whole story up just to get my key?") + } + 3 -> { + chatPlayer(neutral, "Sir Prysin said you would give me the key.") + chatNpc(angry, "Oh, did he? Well, I don't answer to Sir Prysin. I answer to the King!") + chatNpc(verymad, "I didn't work my way up through the ranks to take orders from a puffed-up fool who only has his post because his great-grandfather was a hero with a silly name!") + chatPlayer(quiz, "Then why did he give you one of the keys?") + chatNpc(neutral, "Only because the King ordered it! The King couldn't make Sir Prysin part with his precious ancestral sword, so he made him lock it up where he couldn't lose it.") + chatNpc(neutral, "I got one key, and some wizard got another. What happened to the third?") + chatPlayer(laugh, "Sir Prysin dropped it down a drain!") + chatNpc(madlaugh, "Ha ha ha! The idiot!") + chatNpc(happy, "Fine, I'll give you the key, if only so that it's you who kills the demon and not Sir Prysin!") + giveKey() + return + } + } + } + } + + private suspend fun Dialogue.giveKey() { + if (access.invAdd(access.inv, KEY_ROVIN).failure) { + chatNpc(neutral, "Your pack is full. Clear some space and come back for it.") + return + } + demonSlayer.rovinKeyGiven.set(player, true) + access.soundSynth("synth.pick2") + objbox(KEY_ROVIN, "Captain Rovin hands you a key.") + } + + private suspend fun Dialogue.keyAlreadyGiven() { + chatNpc(neutral, "Yes, you said before. Haven't you killed it yet?") + chatPlayer(neutral, "I'm going to use the sword Silverlight, and I believe you hold one of the keys to its case?") + when { + player.inv.count(KEY_ROVIN) > 0 -> + chatNpc(neutral, "I already gave you my key. Check your pockets.") + access.bank.count(KEY_ROVIN) > 0 -> + chatNpc(neutral, "I already gave you my key. Perhaps you left it somewhere. Have you checked your bank?") + else -> { + chatNpc(angry, "I already gave you my key, and you've lost it? Luckily for you I keep a spare. Don't lose this one.") + giveKey() + } + } + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/SirPrysin.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/SirPrysin.kt new file mode 100644 index 000000000..ef8388b53 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/SirPrysin.kt @@ -0,0 +1,272 @@ +package org.rsmod.content.quest.area.varrock.demonslayer.npcs + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.repo.obj.ObjRepository +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest.Companion.KEY_DRAIN +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest.Companion.KEY_ROVIN +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest.Companion.KEY_TRAIBORN +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest.Companion.SILVERLIGHT +import org.rsmod.game.entity.Npc +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Sir Prysin, keeper of Silverlight, in the south-west of Varrock Palace's ground floor. */ +class SirPrysin +@Inject +constructor( + private val demonSlayer: DemonSlayerQuest, + private val objRepo: ObjRepository, +) : PluginScript() { + + private val quest + get() = demonSlayer.quest + + private val presentingType = + ServerCacheManager.getNpc(PRESENTING_NPC.asRSCM(RSCMType.NPC)) + ?: error("Missing npc: $PRESENTING_NPC") + + override fun ScriptContext.startup() { + onOpNpc1("npc.sir_prysin") { startDialogue(it.npc) { prysin(it.npc) } } + } + + private suspend fun Dialogue.prysin(npc: Npc) { + demonSlayer.syncVars(player) + when (quest.getQuestStage(player)) { + DemonSlayerQuest.STAGE_STARTED -> keyHunt(npc) + DemonSlayerQuest.STAGE_SILVERLIGHT -> beforeFight() + DemonSlayerQuest.STAGE_COMPLETE -> afterQuest() + else -> beforeQuest() + } + } + + private suspend fun Dialogue.beforeQuest() { + chatNpc(quiz, "Hello, who are you?") + when ( + choice2( + "I am a mighty adventurer. Who are you?", 1, + "I'm not sure. I was hoping you could tell me.", 2, + ) + ) { + 1 -> { + chatPlayer(happy, "I am a mighty adventurer. Who are you?") + chatNpc(happy, "I am Sir Prysin, a bold and famous knight of the realm.") + } + 2 -> { + chatPlayer(confused, "I'm not sure. I was hoping you could tell me.") + chatNpc(confused, "Well, I've certainly never met you before.") + } + } + } + + private suspend fun Dialogue.keyHunt(npc: Npc) { + if (demonSlayer.hasAllKeys(player)) { + handOverSilverlight(npc) + return + } + val held = demonSlayer.keysHeld(player) + if (!demonSlayer.prysinExplained.get(player)) { + introduceKeys() + return + } + chatNpc(quiz, "So, how are you getting on with those keys?") + if (held.isEmpty()) { + chatPlayer(sad, "I haven't found any of them yet.") + } else { + chatPlayer(happy, "I've got ${describeKeys(held)}.") + chatNpc(happy, "Good. Bring me the rest and Silverlight is yours.") + } + when ( + choice2( + "Can you remind me where all the keys were again?", 1, + "I'm still looking.", 2, + ) + ) { + 1 -> { + chatPlayer(quiz, "Can you remind me where all the keys were again?") + keyLocations() + } + 2 -> { + chatPlayer(neutral, "I'm still looking.") + chatNpc(neutral, "Very well. Let me know when you have them all.") + } + } + } + + private suspend fun Dialogue.introduceKeys() { + chatNpc(quiz, "Hello, who are you?") + when ( + choice3( + "I am a mighty adventurer. Who are you?", 1, + "I'm not sure. I was hoping you could tell me.", 2, + "Aris said I should come and talk to you.", 3, + ) + ) { + 1 -> { + chatPlayer(happy, "I am a mighty adventurer. Who are you?") + chatNpc(happy, "I am Sir Prysin, a bold and famous knight of the realm.") + return + } + 2 -> { + chatPlayer(confused, "I'm not sure. I was hoping you could tell me.") + chatNpc(confused, "Well, I've certainly never met you before.") + return + } + } + chatPlayer(neutral, "Aris said I should come and talk to you.") + chatNpc(quiz, "Aris? Is she still alive? I remember her from when I was a boy. Well, what do you need from me?") + chatPlayer(neutral, "I need to find Silverlight.") + chatNpc(quiz, "Whatever do you need that for?") + chatPlayer(neutral, "I need it to fight Delrith.") + chatNpc(shocked, "Delrith? I thought my great-grandfather rid the world of him for good.") + chatPlayer(neutral, "Aris' crystal ball seems to think otherwise.") + chatNpc(neutral, "Well, if the ball says so, I had better help you. The trouble is getting hold of Silverlight.") + chatPlayer(quiz, "You mean you don't have it?") + chatNpc(neutral, "Oh, I have it. But it is so powerful that the King made me lock it in a special case that needs three different keys to open, so it can't fall into the wrong hands.") + chatPlayer(neutral, "So give me the keys!") + chatNpc(worried, "Um... well... it isn't quite that simple.") + demonSlayer.prysinExplained.set(player, true) + keyLocations() + } + + private suspend fun Dialogue.keyLocations() { + chatNpc(neutral, "I kept one of the keys myself and gave the other two to others for safe keeping. One went to Rovin, captain of the palace guard, and the other to the wizard Traiborn.") + while (true) { + when ( + choice4( + "Can you give me your key?", 1, + "Where can I find Captain Rovin?", 2, + "Where does the wizard live?", 3, + "Well, I'd better go key hunting.", 4, + ) + ) { + 1 -> { + chatPlayer(quiz, "Can you give me your key?") + chatNpc(worried, "Um... ah... there is a small problem with that as well.") + chatNpc(sad, "I dropped it down the drain just outside the palace kitchen. I can see it, but I can't reach it.") + chatPlayer(quiz, "So what does the drain lead to?") + chatNpc(neutral, "It carries water from the kitchen sink down into the sewers beneath the palace. A good flush of water might carry the key down there, where you could fetch it.") + } + 2 -> { + chatPlayer(quiz, "Where can I find Captain Rovin?") + chatNpc(neutral, "Captain Rovin lives at the top of the guards' quarters, in the north-west wing of this palace.") + } + 3 -> { + chatPlayer(quiz, "Where does the wizard live?") + chatNpc(neutral, "Wizard Traiborn? He is one of the wizards in the tower on the little island off the south coast. I believe his quarters are on the first floor.") + } + 4 -> { + chatPlayer(neutral, "Well, I'd better go key hunting.") + chatNpc(neutral, "Good luck, and do try not to lose any of them.") + return + } + } + } + } + + private suspend fun Dialogue.handOverSilverlight(npc: Npc) { + chatNpc(quiz, "So, how are you getting on with those keys?") + chatPlayer(happy, "I've got all three keys!") + if (player.inv.freeSpace() < 1) { + chatNpc(neutral, "Excellent! Make some room in your pack and I'll fetch Silverlight for you.") + return + } + chatNpc(happy, "Excellent! Now I can give you Silverlight.") + access.invDel(access.inv, KEY_ROVIN, 1, KEY_DRAIN, 1) + access.invDel(access.inv, KEY_TRAIBORN) + + access.npcChangeType(npc, presentingType, PRESENTING_TICKS) + npc.anim("seq.qip_ds_presenting_silverlight_start") + access.soundSynth("synth.pillory_unlock") + delay(2) + npc.anim("seq.qip_ds_presenting_sword_middle") + delay(2) + npc.anim("seq.qip_ds_presenting_sword_end") + access.anim("seq.qip_ds_recieving_silverlight") + access.soundSynth("synth.found_gem") + access.invAddOrDrop(objRepo, SILVERLIGHT) + demonSlayer.silverlightGiven.set(player, true) + demonSlayer.advance(access) + objbox(SILVERLIGHT, "Sir Prysin hands you a gleaming sword.") + chatNpc(neutral, "That sword belonged to my great-grandfather. Treat it with respect!") + chatNpc(happy, "Now go and deal with that demon!") + } + + private suspend fun Dialogue.beforeFight() { + chatNpc(quiz, "Have you sorted that demon out yet?") + if (with(demonSlayer) { access.hasSilverlight() }) { + chatPlayer(neutral, "No, not yet.") + chatNpc(neutral, "Then get on with it! He'll be a good deal stronger once he reaches his full power.") + return + } + chatPlayer(sad, "Not yet. And I, um... lost Silverlight.") + if (player.inv.freeSpace() < 1) { + chatNpc(angry, "You did what? Someone handed it back in, luckily for you. Make some room in your pack and I'll give it to you again.") + return + } + chatNpc(angry, "Yes, I know. Someone handed it back in. Take better care of it this time!") + access.invAddOrDrop(objRepo, SILVERLIGHT) + access.soundSynth("synth.pick2") + objbox(SILVERLIGHT, "Sir Prysin hands Silverlight back to you.") + } + + private suspend fun Dialogue.afterQuest() { + chatNpc(happy, "So, the demon is dealt with? Well done. My great-grandfather would have been proud.") + if (with(demonSlayer) { access.hasSilverlight() }) { + chatPlayer(happy, "Silverlight served me well.") + chatNpc(neutral, "Keep it safe. A blade like that always finds more work.") + return + } + chatPlayer(sad, "I'm afraid I've lost Silverlight.") + chatNpc(neutral, "Lost it? Hmph. I can have another forged from the old designs, but it will cost you ${DemonSlayerQuest.SILVERLIGHT_REPLACEMENT_COST} coins.") + val buy = + choice2( + "Yes, I'll pay ${DemonSlayerQuest.SILVERLIGHT_REPLACEMENT_COST} coins.", true, + "No thanks.", false, + title = "Buy a replacement Silverlight?", + ) + if (!buy) { + chatPlayer(neutral, "No thanks, not right now.") + return + } + if (player.inv.freeSpace() < 1) { + chatNpc(neutral, "You'll need some room in your pack first.") + return + } + if (!access.invTakeFee(DemonSlayerQuest.SILVERLIGHT_REPLACEMENT_COST)) { + chatPlayer(sad, "I don't have enough coins on me.") + chatNpc(neutral, "Then come back when you do.") + return + } + access.invAddOrDrop(objRepo, SILVERLIGHT) + access.soundSynth("synth.coins_jingle_1") + objbox(SILVERLIGHT, "Sir Prysin hands you a freshly forged Silverlight.") + chatNpc(neutral, "Do try to hold on to this one.") + } + + private fun describeKeys(held: List): String { + val names = + held.map { + when (it) { + KEY_ROVIN -> "the key from Captain Rovin" + KEY_TRAIBORN -> "the key from Wizard Traiborn" + else -> "the key you dropped down the drain" + } + } + return when (names.size) { + 1 -> names[0] + else -> names.dropLast(1).joinToString(", ") + " and " + names.last() + } + } + + private companion object { + const val PRESENTING_NPC = "npc.sir_prysin_silverlight" + const val PRESENTING_TICKS = 8 + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/WizardTraiborn.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/WizardTraiborn.kt new file mode 100644 index 000000000..d63501ee2 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/demonslayer/npcs/WizardTraiborn.kt @@ -0,0 +1,252 @@ +package org.rsmod.content.quest.area.varrock.demonslayer.npcs + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest.Companion.BONES_REQUIRED +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest.Companion.KEY_TRAIBORN +import org.rsmod.game.entity.Npc +import org.rsmod.game.loc.LocAngle +import org.rsmod.game.loc.LocShape +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Wizard Traiborn, first floor of the Wizards' Tower. Trades his key for 25 sets of bones. */ +class WizardTraiborn +@Inject +constructor( + private val demonSlayer: DemonSlayerQuest, + private val locRepo: LocRepository, + private val worldRepo: WorldRepository, +) : PluginScript() { + + private val quest + get() = demonSlayer.quest + + override fun ScriptContext.startup() { + onOpNpc1("npc.traiborn") { startDialogue(it.npc) { traiborn(it.npc) } } + } + + private suspend fun Dialogue.traiborn(npc: Npc) { + chatNpc(happy, "Ello, young thingummywut.") + val onQuest = quest.getQuestStage(player) == DemonSlayerQuest.STAGE_STARTED + if (!onQuest) { + somethingElse() + return + } + when ( + choice2( + "Talk about Demon Slayer.", 1, + "Talk about something else.", 2, + ) + ) { + 1 -> demonSlayer(npc) + 2 -> somethingElse() + } + } + + private suspend fun Dialogue.somethingElse() { + chatPlayer(quiz, "What are you doing up here?") + chatNpc(confused, "Doing? Thinking, mostly. Thinking is very tiring, you know. Now, where did I put my hat?") + chatPlayer(neutral, "It's on your head.") + chatNpc(happy, "So it is! Marvellous. You're a clever young thingummywut.") + } + + private suspend fun Dialogue.demonSlayer(npc: Npc) { + when { + demonSlayer.traibornKeyGiven.get(player) -> afterKey() + !demonSlayer.traibornAsked.get(player) -> askAboutKey() + else -> bones(npc) + } + } + + private suspend fun Dialogue.askAboutKey() { + chatPlayer(neutral, "I need to get a key that Sir Prysin gave you.") + chatNpc(confused, "Sir Prysin? Who's that? And what would I want with his key?") + when ( + choice3( + "He told me you were looking after it for him.", 1, + "He's one of the King's knights.", 2, + "Well, have you got any keys knocking around?", 3, + ) + ) { + 1 -> { + chatPlayer(neutral, "He told me you were looking after it for him.") + chatNpc(neutral, "That wasn't very clever of him. I'd lose my own head if it weren't attached. Tell him to find someone else to mind his valuables in future.") + chatPlayer(neutral, "Okay, I'll tell him that.") + chatNpc(happy, "Oh, that's very kind, if it isn't too much trouble.") + } + 2 -> { + chatPlayer(neutral, "He's one of the King's knights.") + chatNpc(neutral, "I do remember one of the King's knights. He had lovely shoes... and didn't care for my home-made spinach rolls.") + } + } + chatPlayer(quiz, "Well, have you got any keys knocking around?") + chatNpc(happy, "Now that you mention it, yes, I do have a key. It's in my special wardrobe of valuable things. Now, how do I get into that again?") + chatNpc(neutral, "I sealed it with one of my magic rituals, so it stands to reason that another ritual would open it.") + chatPlayer(quiz, "So do you know which ritual to use?") + chatNpc(neutral, "Let me think a moment... yes, a simple Drazier-style ritual should do it. The trouble is I'll need $BONES_REQUIRED sets of bones for that. Where would I find such a thing?") + when ( + choice2( + "Hmm, that's too bad. I really need that key.", 1, + "I'll get the bones for you.", 2, + ) + ) { + 1 -> { + chatPlayer(sad, "Hmm, that's too bad. I really need that key.") + chatNpc(sad, "Ah well. Sorry I couldn't be more help.") + demonSlayer.traibornAsked.set(player, true) + } + 2 -> { + chatPlayer(happy, "I'll get the bones for you.") + chatNpc(happy, "Ooh, that would be very good of you.") + chatPlayer(neutral, "Okay, I'll speak to you when I've got some bones.") + demonSlayer.traibornAsked.set(player, true) + } + } + } + + private suspend fun Dialogue.bones(npc: Npc) { + chatNpc(quiz, "How are you getting on finding those bones?") + val carried = player.inv.count("obj.bones") + if (carried <= 0) { + chatPlayer(sad, "I haven't got any at the moment.") + chatNpc(neutral, "Never mind, keep at it. I still need ${BONES_REQUIRED - demonSlayer.bonesGiven.get(player)} sets.") + return + } + chatPlayer(happy, "I have some bones.") + chatNpc(happy, "Give 'em here, then.") + val needed = BONES_REQUIRED - demonSlayer.bonesGiven.get(player) + val handing = minOf(carried, needed) + access.invDel(access.inv, "obj.bones", handing) + val total = demonSlayer.bonesGiven.get(player) + handing + demonSlayer.bonesGiven.set(player, total) + mesbox("You give Traiborn $handing ${if (handing == 1) "set" else "sets"} of bones.") + if (total < BONES_REQUIRED) { + chatPlayer(neutral, "That's all of them for now.") + chatNpc(neutral, "I still need ${BONES_REQUIRED - total} more.") + chatPlayer(neutral, "Okay, I'll keep looking.") + return + } + ritual(npc) + } + + /** + * The Drazier ritual, timed from the sequence lengths: Traiborn casts facing the player while + * the bones gather at his feet, the wardrobe rises on the tile beside him, he turns and + * reaches into it, it sinks back into the floor and he turns back before handing the key over. + */ + private suspend fun Dialogue.ritual(npc: Npc) { + chatNpc(happy, "Hurrah! That's all $BONES_REQUIRED sets of bones.") + chatNpc(neutral, "Wings of dark and colour too, spreading in the morning dew; locked away I keep a key; return it now, I bid, to me.") + + npc.facePlayer(player) + npc.anim(CAST_SEQ) + npc.spotanim(BONE_SPOT) + worldRepo.soundArea(npc, "synth.bones_to_bananas_all", radius = SOUND_RADIUS) + delay(ticks(CAST_SEQ)) + + val wardrobeTile = access.wardrobeTile(npc) + if (wardrobeTile != null) { + val appear = ticks(APPEAR_SEQ) + val reach = ticks(REACH_SEQ) + val disappear = ticks(DISAPPEAR_SEQ) + val wardrobe = + locRepo.add( + wardrobeTile, + WARDROBE_LOC, + appear + reach + disappear + 1, + wardrobeAngle(npc.coords, wardrobeTile), + LocShape.CentrepieceStraight, + ) + worldRepo.locAnim(wardrobe, APPEAR_SEQ) + worldRepo.soundArea(wardrobeTile, "synth.summon_npc", radius = SOUND_RADIUS) + access.mes("A strange-looking wardrobe rises out of the floor.") + delay(appear) + + npc.lockFacing(wardrobeTile) + npc.anim(REACH_SEQ) + worldRepo.soundArea(npc, "synth.pick2", radius = SOUND_RADIUS) + delay(reach) + + worldRepo.locAnim(wardrobe, DISAPPEAR_SEQ) + worldRepo.soundArea(wardrobeTile, "synth.teleport_reverse", radius = SOUND_RADIUS) + delay(disappear) + npc.clearFacingLock() + npc.facePlayer(player) + } + + if (access.invAdd(access.inv, KEY_TRAIBORN).failure) { + chatNpc(neutral, "Your pack is full, young thingummywut. Clear a space and I'll fetch the key out again.") + demonSlayer.bonesGiven.set(player, BONES_REQUIRED) + return + } + demonSlayer.traibornKeyGiven.set(player, true) + objbox(KEY_TRAIBORN, "Traiborn hands you a key.") + chatPlayer(happy, "Thank you very much.") + chatNpc(happy, "Not a problem for a friend of Sir What's-his-face.") + } + + /** Server ticks a sequence plays for, with a small fallback for anything unmeasured. */ + private fun ticks(seq: String): Int = + ServerCacheManager.getAnim(seq.asRSCM(RSCMType.SEQ))?.tickDuration?.coerceAtLeast(1) ?: 2 + + /** + * Rotates the wardrobe so its doors face Traiborn. [FRONT_FACES_WEST] is the rotation at which + * the model's doors face west; each further quarter turn rotates the model clockwise. + */ + private fun wardrobeAngle(npc: CoordGrid, wardrobe: CoordGrid): LocAngle = + when { + wardrobe.x > npc.x -> FRONT_FACES_WEST + wardrobe.z > npc.z -> FRONT_FACES_WEST.turn(3) + wardrobe.x < npc.x -> FRONT_FACES_WEST.turn(2) + else -> FRONT_FACES_WEST.turn(1) + } + + private suspend fun Dialogue.afterKey() { + if (player.inv.count(KEY_TRAIBORN) > 0 || access.bank.count(KEY_TRAIBORN) > 0) { + chatNpc(neutral, "Don't you have somewhere to be, young thingummywut? You still have that key you asked me for.") + chatPlayer(neutral, "You're right. I've got a demon to slay.") + return + } + chatPlayer(sad, "I've lost the key you gave me.") + chatNpc(neutral, "Yes, I know. It found its way back into my wardrobe. If you want it again you'll have to bring me another $BONES_REQUIRED sets of bones.") + demonSlayer.traibornKeyGiven.set(player, false) + demonSlayer.bonesGiven.set(player, 0) + } + + /** + * A free tile next to Traiborn for the wardrobe to rise from (east first, as in the original + * scene), or null if he is boxed in. + */ + private fun ProtectedAccess.wardrobeTile(npc: Npc): CoordGrid? { + val candidates = + listOf( + npc.coords.translate(1, 0), + npc.coords.translate(-1, 0), + npc.coords.translate(0, 1), + npc.coords.translate(0, -1), + ) + return candidates.firstOrNull { it != player.coords && !mapBlocked(it) } + } + + private companion object { + const val WARDROBE_LOC = "loc.qip_ds_wizards_key_wardrobe_magic" + const val CAST_SEQ = "seq.qip_ds_bones_wizard_anim" + const val BONE_SPOT = "spotanim.qip_ds_bone_spotanim" + const val APPEAR_SEQ = "seq.qip_ds_wardrobe_appear" + const val DISAPPEAR_SEQ = "seq.qip_ds_wardrobe_disappear" + const val REACH_SEQ = "seq.human_pickuptable" + const val SOUND_RADIUS = 10 + /** Measured in game: rotation 1 puts the doors on the west side of the model. */ + val FRONT_FACES_WEST = LocAngle.North + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/DoogleSardines.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/DoogleSardines.kt new file mode 100644 index 000000000..d64e714e9 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/DoogleSardines.kt @@ -0,0 +1,44 @@ +package org.rsmod.content.quest.area.varrock.gertrudescat + +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpHeldU +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.DOOGLE_LEAVES +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.RAW_SARDINE +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.SEASONED_SARDINE +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Seasoning a raw sardine with doogle leaves, and the empty crates and barrels scattered + * around the lumber yard. The kitten itself hides in the mewing crates handled by + * [LumberYardCrates]. + */ +class DoogleSardines : PluginScript() { + override fun ScriptContext.startup() { + onOpHeldU(RAW_SARDINE, DOOGLE_LEAVES) { seasonSardine() } + onOpLoc1(EMPTY_CRATE) { searchEmpty("crate") } + onOpLoc1(EMPTY_BARREL) { searchEmpty("barrel") } + } + + private suspend fun ProtectedAccess.seasonSardine() { + val removed = invDel(inv, RAW_SARDINE, 1, DOOGLE_LEAVES, 1) + if (removed.failure) { + return + } + invAdd(inv, SEASONED_SARDINE) + anim("seq.human_pickuptable") + mes("You rub the doogle leaves all over the sardine.") + } + + private suspend fun ProtectedAccess.searchEmpty(what: String) { + arriveDelay() + anim("seq.human_pickuptable") + mes("You search the $what but find nothing.") + } + + private companion object { + const val EMPTY_CRATE = "loc.gertrudeempty_crate" + const val EMPTY_BARREL = "loc.gertrudeempty_barrel" + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/GertrudesCatQuest.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/GertrudesCatQuest.kt new file mode 100644 index 000000000..ce2b11c2a --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/GertrudesCatQuest.kt @@ -0,0 +1,169 @@ +package org.rsmod.content.quest.area.varrock.gertrudescat + +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.content.other.pets.cats.CatColour +import org.rsmod.content.other.pets.cats.CatPet +import org.rsmod.content.other.pets.cats.CatPetManager +import org.rsmod.content.other.pets.cats.CatStage +import org.rsmod.content.quest.manager.ItemRewardDisplay +import org.rsmod.content.quest.manager.QuestScript +import org.rsmod.content.quest.manager.rewards +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Gertrude's Cat. + * + * Stages (stored in `varp.fluffs`, endstate 6 from `dbrow.quest_gertrudescat`; the cache's + * Gertrude multi-npc switches to her post-quest form at 6): + * - [STAGE_STARTED]: Gertrude asked for help finding Fluffs. + * - [STAGE_PAID_KIDS]: Shilop and Wilough sold the location of the lumber yard. + * - [STAGE_GAVE_MILK]: Fluffs drank the milk. + * - [STAGE_GAVE_SARDINE]: Fluffs ate the seasoned sardine; her kitten is in one of the crates. + * - [STAGE_KITTEN_RETURNED]: Fluffs ran home with her kitten. + * - [STAGE_COMPLETE]: Gertrude handed over a kitten of her own. + */ +@Singleton +class GertrudesCatQuest @Inject constructor(private val cats: CatPetManager) : QuestScript( + "quest_gertrudescat", + "varp.fluffs", + rewards { + xp("stat.cooking", 1525.0) + item(CHOCOLATE_CAKE) + item(STEW) + extra("A kitten to raise") + }, + ItemRewardDisplay(KITTEN_DISPLAY), +) { + /** Index into [LumberYardCrates.CRATES] of the crate hiding Fluffs' kitten; -1 until set. */ + val kittenCrate = quest.attribute(name = "KITTEN_CRATE", default = -1) + + /** The player has met Fluffs at the lumber yard, so Gertrude can explain doogle sardines. */ + val foundFluffs = quest.attribute(name = "FOUND_FLUFFS", default = false) + + /** Gertrude has explained that Fluffs loves doogle sardines. */ + val toldSardines = quest.attribute(name = "TOLD_SARDINES", default = false) + + override fun ScriptContext.init() {} + + override fun subTitle(): String = + "talking to Gertrude in her house west of Varrock, " + + "south of the Cooks' Guild." + + override fun questLog(player: ProtectedAccess) = + questJournal(player) { + objective( + "Gertrude, who lives west of Varrock, has lost her cat Fluffs. " + + "She asked me to find her while she looks after her sons.", + ) {} + + objective( + "Gertrude's sons, Shilop and Wilough, saw Fluffs last. They " + + "should be in Varrock's market place.", + ) { + visibleWhen { quest.getQuestStage(access.player) < STAGE_GAVE_MILK } + stageAtLeast( + STAGE_PAID_KIDS, + "For 100 coins the boys told me Fluffs followed them to their secret play " + + "area: the old lumber yard north-east of Varrock, past the Jolly " + + "Boar Inn. I'll need to climb the broken fence to get in.", + ) + } + + objective( + "Fluffs is up the ladder in the lumber yard, but she hisses at me and won't " + + "leave. Maybe she is thirsty. A bucket of milk might help.", + ) { + visibleWhen { + val stage = quest.getQuestStage(access.player) + stage >= STAGE_PAID_KIDS && stage < STAGE_GAVE_MILK && foundFluffs.get(access.player) + } + } + + objective( + "Fluffs drank the milk but still won't leave. Gertrude says she loves " + + "doogle sardines: a raw sardine rubbed with doogle leaves, which grow " + + "in the woods behind Gertrude's house.", + ) { + visibleWhen { + val stage = quest.getQuestStage(access.player) + stage == STAGE_GAVE_MILK + } + custom( + !toldSardines.get(access.player), + "Fluffs drank the milk but still won't leave. Maybe she is hungry. Gertrude " + + "might know what she likes to eat.", + ) + } + + objective( + "Fluffs ate the sardine, but she still seems afraid to leave. I can hear " + + "kittens mewing from the crates around the lumber yard. I should " + + "search them.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_GAVE_SARDINE } + hasItem("gertrudekittens", "I found Fluffs' kitten in one of the crates. I should take it up to her.") + } + + objective( + "Fluffs ran off home with her kitten. I should tell Gertrude the good news.", + ) { + visibleWhen { quest.getQuestStage(access.player) == STAGE_KITTEN_RETURNED } + } + } + + override fun completedLog(player: ProtectedAccess): String = + completionJournal(player) { + line( + "Gertrude had lost her cat Fluffs. Her sons told me, for a price, that Fluffs had " + + "followed them to the old lumber yard north-east of Varrock.", + ) + line( + "Fluffs wouldn't leave until I gave her milk and a doogle sardine, and even then " + + "she stayed until I found her kitten in one of the crates and brought it to her.", + ) + line( + "Gertrude was so grateful that she gave me one of her kittens to raise, along " + + "with some food. She will sell me another kitten if I ever need one.", + ) + } + + /** A random kitten in one of the ordinary colours; the hellcat only comes from Kharidian rats. */ + fun randomKitten(): CatPet = CatPet(CatStage.KITTEN, KITTEN_COLOURS.random()) + + /** Whether the player already has any cat: following them, in their pack or in their bank. */ + fun ProtectedAccess.hasAnyCat(): Boolean { + if (cats.hasFollower(player)) { + return true + } + return CatPet.all.any { inv.count(it.obj) > 0 || bank.count(it.obj) > 0 } + } + + fun stage(player: Player): Int = quest.getQuestStage(player) + + companion object { + const val STAGE_STARTED = 1 + const val STAGE_PAID_KIDS = 2 + const val STAGE_GAVE_MILK = 3 + const val STAGE_GAVE_SARDINE = 4 + const val STAGE_KITTEN_RETURNED = 5 + const val STAGE_COMPLETE = 6 + + const val KITTEN_DISPLAY = "obj.kittenobject" + const val CHOCOLATE_CAKE = "obj.chocolate_cake" + const val STEW = "obj.stew" + const val FLUFFS_KITTEN = "obj.gertrudekittens" + const val SEASONED_SARDINE = "obj.seasoned_sardine" + const val RAW_SARDINE = "obj.raw_sardine" + const val DOOGLE_LEAVES = "obj.doogleleaves" + const val BUCKET_OF_MILK = "obj.bucket_milk" + const val BUCKET_EMPTY = "obj.bucket_empty" + + const val KIDS_FEE = 100 + const val KITTEN_PRICE = 100 + + val KITTEN_COLOURS = CatColour.entries.filter { it != CatColour.HELL } + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/LumberYardCrates.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/LumberYardCrates.kt new file mode 100644 index 000000000..44e8f6e6e --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/LumberYardCrates.kt @@ -0,0 +1,87 @@ +package org.rsmod.content.quest.area.varrock.gertrudescat + +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.random.GameRandom +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.api.script.onAiTimer +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.FLUFFS_KITTEN +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_GAVE_SARDINE +import org.rsmod.game.entity.Npc +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The six invisible "Crate" npcs around the lumber yard that mew now and then and can be + * searched. Once Fluffs has eaten the sardine, one of them (fixed per player) holds her kitten. + */ +class LumberYardCrates +@Inject +constructor( + private val gertrudesCat: GertrudesCatQuest, + private val random: GameRandom, + private val worldRepo: WorldRepository, +) : PluginScript() { + + override fun ScriptContext.startup() { + onAiTimer(MEW_CRATE) { npc.mewTimer() } + onOpNpc1(MEW_CRATE) { searchCrate(it.npc) } + } + + private fun Npc.mewTimer() { + aiTimer(random.of(MEW_MIN_TICKS..MEW_MAX_TICKS)) + say("Mew!") + worldRepo.soundArea(this, MEW_SOUND, radius = MEW_RADIUS) + } + + private suspend fun ProtectedAccess.searchCrate(npc: Npc) { + arriveDelay() + faceEntitySquare(npc) + anim("seq.human_pickuptable") + delay(1) + + val stage = gertrudesCat.stage(player) + if (stage != STAGE_GAVE_SARDINE || inv.count(FLUFFS_KITTEN) > 0) { + mes("You search the crate but find nothing.") + return + } + if (crateIndex(npc) != gertrudesCat.kittenCrate.get(player)) { + mes("You search the crate but find nothing. The mewing seems to be coming from somewhere else.") + return + } + if (inv.freeSpace() < 1) { + mes("You find a kitten, but you have no room in your pack to carry it.") + return + } + invAdd(inv, FLUFFS_KITTEN) + npc.say("Mew!") + soundSynth(MEW_SOUND) + mes("You find a kitten! You carefully place it in your backpack.") + } + + /** Which of [CRATES] this npc is, by the spawn tile it stands closest to. */ + private fun crateIndex(npc: Npc): Int = + CRATES.indices.minByOrNull { npc.coords.chebyshevDistance(CRATES[it]) } ?: -1 + + companion object { + const val MEW_CRATE = "npc.kittens_mew" + const val MEW_SOUND = "synth.kittens_mew" + + private const val MEW_MIN_TICKS = 20 + private const val MEW_MAX_TICKS = 45 + private const val MEW_RADIUS = 8 + + /** Spawn tiles of the mewing crate npcs, in the order the kitten crate is picked from. */ + val CRATES = + listOf( + CoordGrid(3298, 3514, 0), + CoordGrid(3303, 3506, 0), + CoordGrid(3305, 3500, 0), + CoordGrid(3307, 3507, 0), + CoordGrid(3310, 3499, 0), + CoordGrid(3315, 3515, 0), + ) + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/npcs/Fluffs.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/npcs/Fluffs.kt new file mode 100644 index 000000000..b8b88910a --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/npcs/Fluffs.kt @@ -0,0 +1,222 @@ +package org.rsmod.content.quest.area.varrock.gertrudescat.npcs + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.random.GameRandom +import org.rsmod.api.repo.npc.NpcRepository +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpc3 +import org.rsmod.api.script.onOpNpc4 +import org.rsmod.api.script.onOpNpcU +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.BUCKET_EMPTY +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.BUCKET_OF_MILK +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.FLUFFS_KITTEN +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.SEASONED_SARDINE +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_GAVE_MILK +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_GAVE_SARDINE +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_KITTEN_RETURNED +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_STARTED +import org.rsmod.content.quest.area.varrock.gertrudescat.LumberYardCrates +import org.rsmod.game.entity.Npc +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Fluffs, Gertrude's cat, hiding on the upper floor of the lumber yard's shed. */ +class Fluffs +@Inject +constructor( + private val gertrudesCat: GertrudesCatQuest, + private val npcRepo: NpcRepository, + private val random: GameRandom, +) : PluginScript() { + + private val milkId = BUCKET_OF_MILK.asRSCM(RSCMType.OBJ) + private val sardineId = SEASONED_SARDINE.asRSCM(RSCMType.OBJ) + private val kittenId = FLUFFS_KITTEN.asRSCM(RSCMType.OBJ) + + override fun ScriptContext.startup() { + onOpNpc1(FLUFFS) { pickUp(it.npc) } + onOpNpc3(FLUFFS) { stroke(it.npc) } + onOpNpc4(FLUFFS) { talkTo(it.npc) } + onOpNpcU(FLUFFS) { useItem(it.npc, it.objType, it.invSlot) } + } + + private suspend fun ProtectedAccess.talkTo(npc: Npc) { + arriveDelay() + faceEntitySquare(npc) + npc.facePlayer(player) + noteFound() + npc.say("Miaoww") + soundSynth(MEOW_SOUND) + } + + private suspend fun ProtectedAccess.pickUp(npc: Npc) { + hiss(npc) + } + + private suspend fun ProtectedAccess.stroke(npc: Npc) { + hiss(npc) + } + + /** Fluffs won't be handled until she is content, and hints at what she wants next. */ + private suspend fun ProtectedAccess.hiss(npc: Npc) { + arriveDelay() + faceEntitySquare(npc) + npc.facePlayer(player) + noteFound() + val stage = gertrudesCat.stage(player) + if (stage >= STAGE_KITTEN_RETURNED) { + npc.say("Purr...") + soundSynth(PURR_SOUND) + mes("Fluffs purrs contentedly.") + return + } + anim("seq.human_pickupfloor") + npc.anim("seq.cat_archback") + npc.say("Hisss!") + soundSynth(HISS_SOUND) + delay(1) + say("Ouch!") + delay(1) + mes( + when { + stage < STAGE_STARTED -> "The cat doesn't seem to want to be handled." + stage < STAGE_GAVE_MILK -> "Maybe the cat is thirsty?" + stage < STAGE_GAVE_SARDINE -> "Maybe the cat is hungry?" + else -> "The cat seems afraid to leave. In the distance you can hear kittens mewing..." + }, + ) + } + + private suspend fun ProtectedAccess.useItem(npc: Npc, objType: ItemServerType, slot: Int) { + arriveDelay() + faceEntitySquare(npc) + npc.facePlayer(player) + noteFound() + when (objType.id) { + milkId -> giveMilk(npc) + sardineId -> giveSardine(npc, slot) + kittenId -> returnKitten(npc, slot) + else -> mes("Fluffs isn't interested in that.") + } + } + + private suspend fun ProtectedAccess.giveMilk(npc: Npc) { + val stage = gertrudesCat.stage(player) + if (stage < STAGE_STARTED) { + mes("The cat eyes the bucket suspiciously and backs away.") + return + } + if (stage >= STAGE_GAVE_MILK) { + mes("Fluffs has had all the milk she wants.") + return + } + if (invReplace(inv, BUCKET_OF_MILK, 1, BUCKET_EMPTY).failure) { + return + } + anim("seq.human_pickupfloor") + npc.anim("seq.cat_paw") + npc.say("Mew!") + soundSynth(MEW_SOUND) + mes("Fluffs laps up the milk.") + gertrudesCat.quest.advanceQuestStage(access = this, amount = STAGE_GAVE_MILK - stage) + } + + private suspend fun ProtectedAccess.giveSardine(npc: Npc, slot: Int) { + val stage = gertrudesCat.stage(player) + if (stage < STAGE_GAVE_MILK) { + mes("The cat sniffs at the sardine but won't eat. Maybe the cat is thirsty?") + return + } + if (stage >= STAGE_GAVE_SARDINE) { + mes("Fluffs has eaten her fill.") + return + } + if (invDel(inv, SEASONED_SARDINE, 1, slot = slot).failure) { + return + } + anim("seq.human_pickupfloor") + npc.anim("seq.cat_paw") + npc.say("Mew!") + soundSynth(MEW_SOUND) + mes("Fluffs gobbles up the sardine.") + gertrudesCat.kittenCrate.set(player, random.of(LumberYardCrates.CRATES.indices)) + gertrudesCat.quest.advanceQuestStage(this) + delay(2) + mes("The cat still seems afraid to leave. In the distance you can hear kittens mewing...") + } + + /** Fluffs is reunited with her kitten and the two of them head home. */ + private suspend fun ProtectedAccess.returnKitten(npc: Npc, slot: Int) { + val stage = gertrudesCat.stage(player) + if (stage != STAGE_GAVE_SARDINE) { + mes("Fluffs doesn't seem interested in that right now.") + return + } + if (invDel(inv, FLUFFS_KITTEN, 1, slot = slot).failure) { + return + } + anim("seq.human_pickupfloor") + val kitten = Npc(KITTEN, kittenTile(npc)) + npcRepo.add(kitten, KITTEN_VISIBLE_TICKS) + kitten.facePlayer(player) + + npc.say("Purr...") + kitten.say("Purr...") + soundSynth(PURR_SOUND) + gertrudesCat.quest.advanceQuestStage(this) + delay(3) + + npc.walk(HOME_TILE) + kitten.walk(HOME_TILE.translate(-1, 0)) + delay(4) + mes("Fluffs has run off home with her kitten.") + if (kitten.isSlotAssigned) { + npcRepo.del(kitten, Int.MAX_VALUE) + } + if (npc.isSlotAssigned) { + npcRepo.hide(npc, FLUFFS_AWAY_TICKS) + } + } + + private fun ProtectedAccess.kittenTile(npc: Npc): CoordGrid { + val candidates = + listOf( + npc.coords.translate(1, 0), + npc.coords.translate(-1, 0), + npc.coords.translate(0, 1), + npc.coords.translate(0, -1), + ) + return candidates.firstOrNull { it != player.coords && !mapBlocked(it) } ?: npc.coords + } + + /** Remembers that the player has met Fluffs, which unlocks Gertrude's sardine advice. */ + private fun ProtectedAccess.noteFound() { + if (gertrudesCat.stage(player) >= STAGE_STARTED && !gertrudesCat.foundFluffs.get(player)) { + gertrudesCat.foundFluffs.set(player, true) + } + } + + private companion object { + const val FLUFFS = "npc.gertrudescat" + const val KITTEN = "npc.lostkitten" + + const val MEOW_SOUND = "synth.meeoow" + const val MEW_SOUND = "synth.kittens_mew" + const val HISS_SOUND = "synth.cat_hiss" + const val PURR_SOUND = "synth.purr" + + /** Beside the top of the shed ladder; Fluffs and her kitten head here to "go home". */ + val HOME_TILE = CoordGrid(3309, 3510, 1) + + const val KITTEN_VISIBLE_TICKS = 20 + + /** How long Fluffs stays away from the shed after running home. */ + const val FLUFFS_AWAY_TICKS = 100 + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/npcs/Gertrude.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/npcs/Gertrude.kt new file mode 100644 index 000000000..94d6d8c90 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/npcs/Gertrude.kt @@ -0,0 +1,208 @@ +package org.rsmod.content.quest.area.varrock.gertrudescat.npcs + +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpc3 +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.KITTEN_PRICE +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_GAVE_MILK +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_GAVE_SARDINE +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_KITTEN_RETURNED +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_PAID_KIDS +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_STARTED +import org.rsmod.game.entity.Npc +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Gertrude, in her house west of Varrock. The cache swaps her between a quest form and a + * post-quest form (with a "Kitten" option) on the quest varp, so both forms are handled here. + */ +class Gertrude @Inject constructor(private val gertrudesCat: GertrudesCatQuest) : PluginScript() { + + private val quest + get() = gertrudesCat.quest + + override fun ScriptContext.startup() { + // Ops on a multi-npc are dispatched with the *base* npc's id even though the client shows + // the transformed form's options, so the hooks go on the base type. The "Kitten" option + // only exists on the post-quest form, so op3 never arrives before completion. + onOpNpc1(GERTRUDE) { startDialogue(it.npc) { gertrude(it.npc) } } + onOpNpc3(GERTRUDE) { startDialogue(it.npc) { buyKitten() } } + } + + private suspend fun Dialogue.gertrude(npc: Npc) { + when (quest.getQuestStage(player)) { + 0 -> beforeQuest() + STAGE_STARTED -> lookingForShilop() + STAGE_PAID_KIDS -> foundShilop() + STAGE_GAVE_MILK -> sardineAdvice() + STAGE_GAVE_SARDINE -> afterSardine() + STAGE_KITTEN_RETURNED -> finishQuest() + else -> afterQuest() + } + } + + private suspend fun Dialogue.beforeQuest() { + chatPlayer(quiz, "Hello, are you okay?") + chatNpc(angry, "Do I look okay? Those kids drive me crazy.") + chatNpc(sad, "I'm sorry. It's just that I've lost her.") + chatPlayer(quiz, "Lost who?") + chatNpc(sad, "Fluffs. Poor Fluffs. She never hurt anyone.") + chatPlayer(quiz, "Who's Fluffs?") + chatNpc(sad, "My beloved feline friend, Fluffs. She's been purring by my side for almost a decade. Please, could you go and search for her while I look after the kids?") + when ( + choice2( + "Yes.", 1, + "No.", 2, + title = "Help Gertrude find Fluffs?", + ) + ) { + 1 -> { + chatPlayer(happy, "Well, I suppose I could.") + chatNpc(happy, "Really? Thank you so much! I really have no idea where she could be!") + chatNpc(neutral, "I think my sons, Shilop and Wilough, saw the cat last. They'll be out in the market place.") + chatPlayer(neutral, "Alright then, I'll see what I can do.") + quest.advanceQuestStage(access) + } + 2 -> { + chatPlayer(neutral, "Sorry, I'm too busy to play pet rescue.") + chatNpc(sad, "Well, okay then. I'll have to find someone else.") + } + } + } + + private suspend fun Dialogue.lookingForShilop() { + if (gertrudesCat.foundFluffs.get(player)) { + sardineAdvice() + return + } + chatPlayer(happy, "Hello Gertrude.") + chatNpc(quiz, "Have you seen my poor Fluffs?") + chatPlayer(sad, "I'm afraid not.") + chatNpc(quiz, "What about Shilop?") + chatPlayer(neutral, "No sign of him either.") + chatNpc(confused, "Hmmm... strange. He should be at the market.") + } + + private suspend fun Dialogue.foundShilop() { + if (gertrudesCat.foundFluffs.get(player)) { + sardineAdvice() + return + } + chatPlayer(happy, "Hello Gertrude.") + chatNpc(quiz, "Hello again. Did you manage to find Shilop? I can't keep an eye on him for the life of me.") + chatPlayer(neutral, "He does seem quite a handful.") + chatNpc(neutral, "You have no idea! Did he help at all?") + chatPlayer(neutral, "I think so. I'm just going to look now.") + chatNpc(happy, "Thanks again, adventurer.") + } + + private suspend fun Dialogue.sardineAdvice() { + chatPlayer(happy, "Hello again.") + chatNpc(quiz, "Hello. How's it going? Any luck?") + chatPlayer(happy, "Yes, I've found Fluffs!") + chatNpc(happy, "Well, well, you are clever! Did you bring her back?") + chatPlayer(worried, "Well, that's the thing. She refuses to leave.") + chatNpc(worried, "Oh dear, oh dear! Maybe she's just hungry. She loves doogle sardines, but I'm all out.") + chatPlayer(quiz, "Doogle sardines?") + chatNpc(neutral, "Yes, raw sardines seasoned with doogle leaves. Unfortunately I've used all my doogle leaves, but you may find some in the woods out the back.") + gertrudesCat.toldSardines.set(player, true) + } + + private suspend fun Dialogue.afterSardine() { + chatPlayer(happy, "Hi!") + chatNpc(quiz, "Hey, traveller. Did Fluffs eat the sardines?") + chatPlayer(neutral, "Yeah, she loved them, but she still won't leave.") + chatNpc(confused, "Well, that is strange. There must be a reason.") + } + + private suspend fun Dialogue.finishQuest() { + chatPlayer(happy, "Hello Gertrude. Fluffs ran off with her kitten.") + chatNpc(happy, "You're back! Thank you! Thank you! Fluffs just came home! I think she was just upset because she couldn't find her kitten.") + if (player.inv.freeSpace() < REWARD_SLOTS) { + chatNpc(neutral, "I've got something for you, but you'll need $REWARD_SLOTS free spaces in your pack first. Come straight back!") + return + } + mesbox("Gertrude gives you a hug.") + chatNpc(happy, "If you hadn't found her kitten it would have died out there!") + chatPlayer(happy, "That's okay, I like to do my bit.") + chatNpc(neutral, "I don't know how to thank you. I have no real material possessions. I do have kittens, though! I can only really look after one.") + chatPlayer(happy, "Well, if it needs a home...") + chatNpc(neutral, "I would sell it to my cousin in West Ardougne. I hear there's a rat epidemic there. But it's too far.") + chatNpc(happy, "Here you go. Look after her, and thank you again!") + chatNpc(neutral, "Oh, by the way: the kitten can live in your backpack, but to make it grow you must take it out and feed and stroke it often.") + + val kitten = gertrudesCat.randomKitten() + access.invAdd(access.inv, kitten.obj) + access.soundSynth("synth.kittens_mew") + objbox(kitten.obj, "Gertrude gives you a kitten.") + mesbox("...and some food!") + quest.advanceQuestStage(access) + } + + private suspend fun Dialogue.afterQuest() { + chatPlayer(happy, "Hello Gertrude.") + chatNpc(happy, "Hello, dear! Fluffs is curled up by the fire, thanks to you.") + when ( + choice2( + "How is the kitten doing?", 1, + "Could I have another kitten?", 2, + ) + ) { + 1 -> { + chatPlayer(quiz, "How is the kitten doing?") + if (with(gertrudesCat) { access.hasAnyCat() }) { + chatNpc(happy, "You'd know better than me! Just keep feeding it and giving it plenty of attention, and it'll grow up big and strong.") + } else { + chatNpc(worried, "Don't tell me you've lost it already? If you need another one, I can spare a kitten for $KITTEN_PRICE coins.") + } + } + 2 -> buyKitten() + } + } + + /** The "Kitten" option on post-quest Gertrude: one kitten at a time, for a small fee. */ + private suspend fun Dialogue.buyKitten() { + chatPlayer(quiz, "Could I have another kitten?") + if (with(gertrudesCat) { access.hasAnyCat() }) { + chatNpc(neutral, "You've already got a cat to look after! One is quite enough for anyone. Come back if you ever lose it.") + return + } + chatNpc(neutral, "I've got a few kittens that need a home. I can let you have one for $KITTEN_PRICE coins, to cover the food it's eaten.") + val buy = + choice2( + "Yes, I'll pay $KITTEN_PRICE coins.", true, + "No thanks.", false, + title = "Buy a kitten for $KITTEN_PRICE coins?", + ) + if (!buy) { + chatPlayer(neutral, "No thanks, not right now.") + return + } + if (player.inv.freeSpace() < 1) { + chatNpc(neutral, "You'll need a free space in your pack to carry it.") + return + } + if (!access.invTakeFee(KITTEN_PRICE)) { + chatPlayer(sad, "I don't have $KITTEN_PRICE coins on me.") + chatNpc(neutral, "Then come back when you do, dear.") + return + } + val kitten = gertrudesCat.randomKitten() + access.invAdd(access.inv, kitten.obj) + access.soundSynth("synth.kittens_mew") + objbox(kitten.obj, "Gertrude hands you a kitten.") + chatNpc(happy, "Take good care of her. Feed her and stroke her often, or she'll run away!") + } + + private companion object { + /** The base multi-npc; the cache swaps its form on `varp.fluffs`. */ + const val GERTRUDE = "npc.gertrude" + + /** Kitten, chocolate cake and stew. */ + const val REWARD_SLOTS = 3 + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/npcs/GertrudesKids.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/npcs/GertrudesKids.kt new file mode 100644 index 000000000..b60d26d71 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/area/varrock/gertrudescat/npcs/GertrudesKids.kt @@ -0,0 +1,134 @@ +package org.rsmod.content.quest.area.varrock.gertrudescat.npcs + +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.KIDS_FEE +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_GAVE_MILK +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_PAID_KIDS +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest.Companion.STAGE_STARTED +import org.rsmod.game.entity.Npc +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Shilop and Wilough, Gertrude's sons, loitering in Varrock's market place. Either of them will + * sell the location of their secret play area; the other chips in from the side. + */ +class GertrudesKids @Inject constructor(private val gertrudesCat: GertrudesCatQuest) : PluginScript() { + + private val quest + get() = gertrudesCat.quest + + override fun ScriptContext.startup() { + onOpNpc1(SHILOP) { startDialogue(it.npc) { kid(it.npc, SHILOP) } } + onOpNpc1(WILOUGH) { startDialogue(it.npc) { kid(it.npc, WILOUGH) } } + } + + private suspend fun Dialogue.kid(npc: Npc, type: String) { + val other = if (type == SHILOP) WILOUGH else SHILOP + val otherName = if (type == SHILOP) "Wilough" else "Shilop" + when (val stage = quest.getQuestStage(player)) { + 0 -> { + chatPlayer(happy, "Hello there.") + chatNpc(angry, "Get lost! Can't you see we're busy?") + } + STAGE_STARTED -> secretPlayArea(other, otherName) + STAGE_PAID_KIDS -> { + chatPlayer(quiz, "Where did you say you saw Fluffs?") + chatNpc(bored, "Weren't you listening? The flea bag is in the old lumber mill north-east of here. Walk past the Jolly Boar Inn and you'll find it.") + } + in STAGE_GAVE_MILK until GertrudesCatQuest.STAGE_COMPLETE -> toughGuy(other, otherName) + else -> { + chatPlayer(happy, "Hello again.") + chatNpc(happy, "Mum's stopped fussing over that cat, so I suppose we owe you one. Don't tell her about the ${KIDS_FEE} coins though.") + if (stage >= GertrudesCatQuest.STAGE_COMPLETE) { + chatPlayer(shifty, "My lips are sealed.") + } + } + } + } + + private suspend fun Dialogue.secretPlayArea(other: String, otherName: String) { + chatPlayer(happy, "Hello there. I've been looking for you.") + chatNpc(worried, "I didn't mean to take it! I just forgot to pay!") + chatPlayer(confused, "What? I'm trying to help your mum find Fluffs.") + chatNpc(neutral, "Ohh... well, in that case I might be able to help. Fluffs followed me to my secret play area, and I haven't seen her since.") + chatPlayer(quiz, "Where is this play area?") + chatNpc(shifty, "If I told you that, it wouldn't be a secret.") + when ( + choice3( + "Tell me sonny, or I will hurt you.", 1, + "What will make you tell me?", 2, + "Well, never mind, it's Fluffs' loss.", 3, + ) + ) { + 1 -> { + chatPlayer(angry, "Tell me sonny, or I will hurt you.") + chatNpc(shocked, "W..wh..what?! You wouldn't! A young lad like me! I'd have you behind bars before nightfall!") + mesbox("You decide it's best not to hurt the boy.") + } + 2 -> haggle(other, otherName) + 3 -> { + chatPlayer(neutral, "Well, never mind, it's Fluffs' loss.") + chatNpc(neutral, "I'm sure my mum will get over it.") + } + } + } + + private suspend fun Dialogue.haggle(other: String, otherName: String) { + chatPlayer(quiz, "What will make you tell me?") + chatNpc(shifty, "Well... now that you ask, I am a bit short on cash.") + chatPlayer(quiz, "How much?") + chatNpc(neutral, "10 coins.") + chatNpcSpecific(otherName, other, shocked, "10 coins?!") + chatNpcSpecific(otherName, other, neutral, "I'll handle this.") + chatNpcSpecific(otherName, other, shifty, "$KIDS_FEE coins should cover it.") + chatPlayer(shocked, "$KIDS_FEE coins! Why should I pay you?") + chatNpcSpecific(otherName, other, neutral, "You shouldn't, but we won't help otherwise. We never liked that cat anyway. So what do you say?") + when ( + choice2( + "I'm not paying you a penny.", 1, + "Okay then, I'll pay.", 2, + ) + ) { + 1 -> { + chatPlayer(angry, "I'm not paying you a penny.") + chatNpc(neutral, "Okay then. I'll find another way to make money.") + } + 2 -> pay(other, otherName) + } + } + + private suspend fun Dialogue.pay(other: String, otherName: String) { + chatPlayer(neutral, "Okay then, I'll pay.") + if (!access.invTakeFee(KIDS_FEE)) { + chatPlayer(sad, "Oh. I don't seem to have $KIDS_FEE coins on me.") + chatNpc(bored, "Then come back when you do. No coins, no secret.") + return + } + access.soundSynth("synth.coins_jingle_1") + mesbox("You give the lad $KIDS_FEE coins.") + quest.advanceQuestStage(access) + chatPlayer(quiz, "There you go. Now, where did you see Fluffs?") + chatNpc(happy, "I play at an abandoned lumber mill to the north-east, just beyond the Jolly Boar Inn. I saw Fluffs running around in there.") + chatPlayer(quiz, "Anything else?") + chatNpc(neutral, "Well, you'll have to find the broken fence to get in. I'm sure you can manage that.") + } + + private suspend fun Dialogue.toughGuy(other: String, otherName: String) { + chatPlayer(happy, "Hello again.") + chatNpc(angry, "You think you're tough, do you?") + chatPlayer(confused, "Pardon?") + chatNpc(angry, "I can beat anyone up!") + chatNpcSpecific(otherName, other, happy, "He can, you know!") + chatPlayer(quiz, "Really?") + mesbox("The boy begins to jump around with his fists up. You decide it's best not to kill him just yet.") + } + + private companion object { + const val SHILOP = "npc.shilop" + const val WILOUGH = "npc.wilough" + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/Quest.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/Quest.kt index 4fd5c9f79..8b16c4a55 100644 --- a/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/Quest.kt +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/Quest.kt @@ -5,6 +5,7 @@ import dev.openrune.ServerCacheManager import dev.openrune.rscm.RSCM.asRSCM import dev.openrune.rscm.RSCMType import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.player.midiJingle import org.rsmod.api.player.protect.ProtectedAccess import org.rsmod.api.player.vars.intVarBit import org.rsmod.api.player.vars.intVarp @@ -27,7 +28,9 @@ data class Quest( val questPoints: Int, val questVarp: String, val rewards : QuestReward, - val itemDisplay : ItemRewardDisplay + val itemDisplay : ItemRewardDisplay, + /** Js5 archive 11 group played when the quest is completed; see [QuestScript.completionJingle]. */ + val completionJingle: Int = DEFAULT_COMPLETION_JINGLE, ) { private var Player.questState by intVarp(questVarp) @@ -46,11 +49,29 @@ data class Quest( fun all(): Collection = questsByKey.values + /** + * Js5 archive 11 groups of the three "Quest Complete" jingles. The client plays a jingle + * by its archive group, which is not the number the `jingle.*` gamevals resolve to; the + * groups are the "Cache ID" on each jingle's OSRS wiki page (see + * [org.rsmod.api.player.midiJingle]). + */ + /** "Quest Complete 1": usually master-level quests. */ + const val QUEST_COMPLETE_1_JINGLE = 152 + + /** "Quest Complete 2": usually intermediate and expert quests. */ + const val QUEST_COMPLETE_2_JINGLE = 153 + + /** "Quest Complete 3": usually beginner and easy quests. */ + const val QUEST_COMPLETE_3_JINGLE = 154 + + const val DEFAULT_COMPLETION_JINGLE = QUEST_COMPLETE_3_JINGLE + fun register( rowKey: String, varp: String, itemDisplay : ItemRewardDisplay, - rewards : QuestReward + rewards : QuestReward, + completionJingle: Int = DEFAULT_COMPLETION_JINGLE, ): Quest { val rowKeyID = "dbrow.${rowKey}".asRSCM() @@ -66,7 +87,8 @@ data class Quest( questPoints = questRow.questpoints, questVarp = varp, itemDisplay = itemDisplay, - rewards = rewards + rewards = rewards, + completionJingle = completionJingle, ) questsByKey[rowKey.normalizedQuestKey()] = quest return quest @@ -123,6 +145,35 @@ data class Quest( return newStage } + /** + * Puts the quest back to "not started" for [player]: stage, varp, every registered quest + * attribute, and the quest points and completion count if it had been finished. Intended for + * testing via the `::resetquest` command. + */ + fun resetQuest(player: Player) { + val wasCompleted = isQuestCompleted(player) + player.attr[QUEST_STAGE_MAP_ATTR]?.remove(key) + player.questState = 0 + for (attribute in attributeRegistry.values) { + attribute.clear(player) + } + if (wasCompleted) { + player.questPoints = (player.questPoints - questPoints).coerceAtLeast(0) + player.questsCompleted = (player.questsCompleted - 1).coerceAtLeast(0) + } + } + + /** + * Jumps the quest to [stage] for [player] without running completion rewards. Intended for + * testing via the `::queststage` command; attributes are left untouched. + */ + fun jumpToStage(player: Player, stage: Int) { + val clamped = stage.coerceIn(0, maxSteps) + val stages = player.attr.getOrPut(QUEST_STAGE_MAP_ATTR) { mutableMapOf() } + stages[key] = clamped + player.questState = clamped + } + fun attribute( name: String, default: T, @@ -154,6 +205,7 @@ data class Quest( access.player.questPoints += questPoints access.player.questsCompleted++ + access.player.midiJingle(completionJingle) access.ifOpenMain("interface.questscroll") access.ifSetText("component.questscroll:quest_title", "You have completed ${displayName}!") diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/QuestCommands.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/QuestCommands.kt new file mode 100644 index 000000000..fa2108fa1 --- /dev/null +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/QuestCommands.kt @@ -0,0 +1,92 @@ +package org.rsmod.content.quest.manager + +import dev.or2.central.account.Rights +import jakarta.inject.Inject +import org.rsmod.api.player.output.mes +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.api.script.onCommand +import org.rsmod.game.loc.LocAngle +import org.rsmod.game.loc.LocShape +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Testing commands for quests. + * + * `::resetquest ` puts a quest back to "not started" (stage, varp, quest attributes, and the + * quest points if it was finished). The key is the dbrow name with or without the `quest_` + * prefix, e.g. `::resetquest demonslayer`. `::resetquest` with no key lists the registered quests. + * + * `::testloc ` raises a loc on the tile east of the player for a few seconds with the + * given rotation (0-3), to check which way a model faces at each rotation. + */ +class QuestCommands +@Inject +constructor( + private val locRepo: LocRepository, + private val worldRepo: WorldRepository, +) : PluginScript() { + override fun ScriptContext.startup() { + onCommand("resetquest") { + desc = "Reset a quest to not started (ex: ::resetquest demonslayer)" + invalidArgs = "Use as ::resetquest " + requiredRights = Rights.ADMINISTRATOR + cheat { + val key = args.firstOrNull()?.trim()?.lowercase() + if (key.isNullOrEmpty()) { + val names = Quest.all().joinToString(", ") { it.key.removePrefix("quest_") } + player.mes("Registered quests: $names") + return@cheat + } + val quest = Quest.get(key) ?: Quest.get("quest_$key") + if (quest == null) { + player.mes("No quest registered under '$key'.") + return@cheat + } + quest.resetQuest(player) + player.mes("Reset ${quest.displayName} to not started.") + } + } + + onCommand("queststage") { + desc = "Jump a quest to a stage without rewards (ex: ::queststage demonslayer 2)" + invalidArgs = "Use as ::queststage " + requiredRights = Rights.ADMINISTRATOR + cheat { + val key = args[0].trim().lowercase() + val stage = args[1].toInt() + val quest = Quest.get(key) ?: Quest.get("quest_$key") + if (quest == null) { + player.mes("No quest registered under '$key'.") + return@cheat + } + quest.jumpToStage(player, stage) + player.mes("${quest.displayName} is now at stage ${quest.getQuestStage(player)} of ${quest.maxSteps}.") + } + } + + onCommand("testloc") { + desc = "Spawn a loc east of you with a rotation (ex: ::testloc qip_ds_wizards_key_wardrobe_magic 0)" + invalidArgs = "Use as ::testloc " + requiredRights = Rights.ADMINISTRATOR + cheat { + val name = args[0].removePrefix("loc.") + val angleId = args.getOrNull(1)?.toIntOrNull() ?: 0 + val angle = LocAngle.entries.firstOrNull { it.id == angleId } + if (angle == null) { + player.mes("Angle must be 0-3.") + return@cheat + } + val tile = player.coords.translate(1, 0) + val loc = locRepo.add(tile, "loc.$name", TEST_LOC_TICKS, angle, LocShape.CentrepieceStraight) + worldRepo.locAnim(loc, "seq.qip_ds_wardrobe_appear") + player.mes("Spawned loc.$name east of you at rotation $angleId (${angle.name}) for $TEST_LOC_TICKS ticks.") + } + } + } + + private companion object { + const val TEST_LOC_TICKS = 25 + } +} diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/QuestModule.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/QuestModule.kt index 9e60bbeb5..5d83df29d 100644 --- a/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/QuestModule.kt +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/QuestModule.kt @@ -1,12 +1,29 @@ package org.rsmod.content.quest.manager +import org.rsmod.api.death.NpcAttackValidateHook import org.rsmod.content.quest.area.lumbridge.RuneMysteriesQuest +import org.rsmod.content.quest.area.lumbridge.lostcity.LostCityQuest +import org.rsmod.content.quest.area.lumbridge.restlessghost.RestlessGhostQuest +import org.rsmod.content.quest.area.rimmington.witchspotion.WitchsPotionQuest +import org.rsmod.content.quest.area.varrock.demonslayer.DemonSlayerQuest +import org.rsmod.content.quest.area.varrock.demonslayer.SilverlightAttackHook +import org.rsmod.content.quest.area.varrock.demonslayer.StoneCircle +import org.rsmod.content.quest.area.varrock.demonslayer.WallyVision +import org.rsmod.content.quest.area.varrock.gertrudescat.GertrudesCatQuest import org.rsmod.plugin.module.PluginModule public class QuestModule : PluginModule() { override fun bind() { bindInstance() bindInstance() + bindInstance() + bindInstance() + bindInstance() + bindInstance() + bindInstance() + bindInstance() + bindInstance() + addSetBinding(SilverlightAttackHook::class.java) } } diff --git a/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/QuestScript.kt b/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/QuestScript.kt index 94bbb58b7..297dfc718 100644 --- a/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/QuestScript.kt +++ b/content/quest/src/main/kotlin/org/rsmod/content/quest/manager/QuestScript.kt @@ -75,12 +75,17 @@ abstract class QuestScript( val questKey: String, val questVarp : String, val rewards: QuestReward, - val completedQuestItemDisplay: ItemRewardDisplay + val completedQuestItemDisplay: ItemRewardDisplay, + /** + * Js5 group of the jingle played with the completion scroll (one of the `Quest.QUEST_COMPLETE_*` + * constants); longer quests pass their own variant. + */ + val completionJingle: Int = Quest.DEFAULT_COMPLETION_JINGLE, ) : PluginScript() { private var Player.questState by intVarp(questVarp) - val quest = Quest.register(questKey, questVarp, completedQuestItemDisplay, rewards) + val quest = Quest.register(questKey, questVarp, completedQuestItemDisplay, rewards, completionJingle) abstract fun subTitle(): String diff --git a/content/quest/src/main/resources/gamevals.toml b/content/quest/src/main/resources/gamevals.toml new file mode 100644 index 000000000..ce1b1de7f --- /dev/null +++ b/content/quest/src/main/resources/gamevals.toml @@ -0,0 +1,10 @@ +# Sound effects used by the quest plugins. The vanilla gamevals dump carries no synth names, so +# the raw cache ids are declared here (ids taken from the OSRS wiki "List of sound IDs"). +[gamevals.synth] +coffin_open = 54 +coffin_close = 53 +skeleton_resurrect = 1687 +ghost_disappear = 1598 +drink = 2390 +cauldron_bubbling = 2195 +smokepuff = 1930 diff --git a/content/skills/agility/build.gradle.kts b/content/skills/agility/build.gradle.kts new file mode 100644 index 000000000..25f50f31d --- /dev/null +++ b/content/skills/agility/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.attr) + implementation(projects.api.pluginCommons) +} diff --git a/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/AgilityObstacles.kt b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/AgilityObstacles.kt new file mode 100644 index 000000000..94df69fa9 --- /dev/null +++ b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/AgilityObstacles.kt @@ -0,0 +1,311 @@ +package org.rsmod.content.skills.agility + +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.BasType +import kotlin.math.sign +import org.rsmod.api.config.constants +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.game.hit.HitType +import org.rsmod.map.CoordGrid + +/** Sequence gamevals shared by the rooftop courses and the agility shortcuts. */ +object AgilityAnims { + /** Climbing loop used for trees and rock faces. */ + const val CLIMB = "seq.human_climbing" + + /** Climb up a building wall; used for the rooftop course rough walls and beams. */ + const val WALL_CLIMB = "seq.human_climbing" + const val CLIMB_LADDER = "seq.human_reachforladder" + const val JUMP_UP = "seq.agility_shortcut_wall_jump2" + + /** Forward leap across a gap between roofs. */ + const val JUMP = "seq.human_longjump" + + /** Drop down from a roof to the ground. */ + const val JUMP_DOWN = "seq.agility_shortcut_wall_jumpdown2" + const val WALL_CLIMB_OVER = "seq.human_walk_style" + const val ROPE_SWING = "seq.human_ropeswing_long" + const val ZIPLINE_GRAB = "seq.zipline_bite" + const val ZIPLINE_SLIDE = "seq.zipline_slide" + const val POLE_VAULT = "seq.rooftops_pole_vault" + const val BALANCE_WALK = "seq.human_walk_logbalance" + const val BALANCE_READY = "seq.human_walk_logbalance_ready" + const val BALANCE_STUMBLE = "seq.human_walk_logbalance_stumble" + const val MONKEYBARS_ON = "seq.human_monkeybars_on" + const val MONKEYBARS_OFF = "seq.human_monkeybars_off" + const val MONKEYBARS_WALK = "seq.human_monkeybars_walk" + const val MONKEYBARS_READY = "seq.human_monkeybars_ready" + const val HANDHOLDS = "seq.agilityarena_handholds_middle" + const val PIPE_SQUEEZE = "seq.human_pipesqueeze" + const val PIPE_UNSQUEEZE = "seq.human_pipeunsqueeze" + const val CRACK_ENTER = "seq.agility_shortcut_crack_enter" + const val CRACK_LEAVE = "seq.agility_shortcut_crack_leave" + + /** Drop into, shuffle along and climb out of an underwall tunnel. */ + const val TUNNEL_ENTER = "seq.agilty_shortcut_enter_hole" + const val TUNNEL_WALK = "seq.agilty_shortcut_tunnel_walk" + const val TUNNEL_EXIT = "seq.agilty_shortcut_exit_hole" + const val STEPPING_STONE = "seq.human_steppingstonejump" +} + +/** + * Base animation set applied while a player is moved along a balance-type obstacle (tightropes, + * monkey bars, hand holds). Every walk direction maps to the same [walk] sequence so the client + * shows the balancing animation no matter which way the obstacle runs. + */ +enum class BalanceStyle( + val ready: String, + val walk: String, + val mount: String? = null, + val dismount: String? = null, +) { + Tightrope(AgilityAnims.BALANCE_READY, AgilityAnims.BALANCE_WALK), + Monkeybars( + AgilityAnims.MONKEYBARS_READY, + AgilityAnims.MONKEYBARS_WALK, + AgilityAnims.MONKEYBARS_ON, + AgilityAnims.MONKEYBARS_OFF, + ), + Handholds(AgilityAnims.HANDHOLDS, AgilityAnims.HANDHOLDS), +} + +/** Client cycles (20ms) in one server tick; `exactmove` delays are expressed in client cycles. */ +internal const val CLIENT_CYCLES_PER_TICK = 30 + +/** + * Chance (0-100) to pass a failable obstacle. It rises linearly from [baseChance] at the course's + * [requiredLevel] to 100 at [noFailLevel], the level at which the obstacle can no longer be failed. + */ +fun successChance(level: Int, requiredLevel: Int, noFailLevel: Int, baseChance: Int = 60): Int { + if (level >= noFailLevel) { + return 100 + } + val span = (noFailLevel - requiredLevel).coerceAtLeast(1) + val progress = (level - requiredLevel).coerceIn(0, span) + return baseChance + (100 - baseChance) * progress / span +} + +/** The `em_face_*` direction a player should face when moving from [from] towards [to]. */ +fun emFaceTowards(from: CoordGrid, to: CoordGrid): Int { + val dx = (to.x - from.x).sign + val dz = (to.z - from.z).sign + return when { + dx == 0 && dz > 0 -> constants.em_face_north + dx == 0 && dz < 0 -> constants.em_face_south + dx > 0 && dz == 0 -> constants.em_face_east + dx < 0 && dz == 0 -> constants.em_face_west + dx > 0 && dz > 0 -> constants.em_face_northeast + dx < 0 && dz > 0 -> constants.em_face_northwest + dx > 0 && dz < 0 -> constants.em_face_southeast + dx < 0 && dz < 0 -> constants.em_face_southwest + else -> constants.em_face_south + } +} + +/** + * Every tile from [from] (exclusive) to [to] (inclusive), moving diagonally while both axes still + * differ and then straight along the remaining axis. Used to spell out balance paths tersely. + */ +fun line(from: CoordGrid, to: CoordGrid): List { + require(from.level == to.level) { "Balance paths cannot change level: from=$from, to=$to" } + val tiles = mutableListOf() + var x = from.x + var z = from.z + while (x != to.x || z != to.z) { + x += (to.x - x).sign + z += (to.z - z).sign + tiles += CoordGrid(x, z, from.level) + } + return tiles +} + +/** + * Number of server ticks [seq] plays for, rounded up, or [fallback] when the cache holds no + * duration. Use it to wait for an animation to finish before moving the player. + */ +internal fun seqTicks(seq: String, fallback: Int): Int { + val type = ServerCacheManager.getAnim(seq.asRSCM(RSCMType.SEQ)) + val ticks = type?.tickDuration ?: 0 + return if (ticks > 0) ticks else fallback +} + +/** + * Number of whole server ticks [seq] plays for (rounded down, at least one), or [fallback] when the + * cache holds no duration. Use it for the length of a forced movement: the glide then ends while the + * closing frames of the animation are still playing, so the player never floats idle in mid-air. + */ +internal fun seqGlideTicks(seq: String, fallback: Int): Int { + val type = ServerCacheManager.getAnim(seq.asRSCM(RSCMType.SEQ)) + val cycles = type?.totalDelay ?: 0 + return if (cycles > 0) maxOf(1, cycles / CLIENT_CYCLES_PER_TICK) else fallback +} + +/** + * Puts the player on [start] before an obstacle begins so every forced movement starts from a + * known tile. Adjacent moves are sent as a walk step; anything further is an instant jump. + */ +internal suspend fun ProtectedAccess.stepOnto(start: CoordGrid) { + if (coords == start) { + return + } + if (coords.level == start.level && coords.chebyshevDistance(start) <= 2) { + teleport(start, TeleportType.Exempt) + } else { + telejump(start, TeleportType.Exempt) + } + delay(1) +} + +/** Plays [seq] for [ticks] cycles and then places the player on [dest]. */ +internal suspend fun ProtectedAccess.climbTo(dest: CoordGrid, seq: String, ticks: Int) { + faceSquare(dest) + anim(seq) + delay(ticks) + telejump(dest, TeleportType.Exempt) +} + +/** + * Moves the player from their current tile to [dest] with an `exactmove` lasting [ticks] cycles + * while [seq] plays. The server position is updated immediately, so nothing can interrupt the + * landing. + * + * The client cannot interpolate across levels, so a leap to another level glides entirely on + * [glideLevel], which defaults to the starting level; the level change is then applied on landing. + * Obstacles whose flight path was authored into the destination level's tile heights pass + * `glideLevel = dest.level` so the player is moved there first and the glide follows that terrain. + */ +internal suspend fun ProtectedAccess.leapTo( + dest: CoordGrid, + seq: String, + ticks: Int, + glideLevel: Int = coords.level, +) { + val start = coords + val glideStart = CoordGrid(start.x, start.z, glideLevel) + val glideEnd = CoordGrid(dest.x, dest.z, glideLevel) + anim(seq) + exactMove( + start = glideStart, + end = glideEnd, + delay1 = 0, + delay2 = ticks * CLIENT_CYCLES_PER_TICK, + dir = emFaceTowards(start, dest), + teleportType = TeleportType.Exempt, + ) + try { + delay(ticks) + } finally { + // A glide on another level than the destination parks the player on a tile of that level + // until it lands; if the script is torn down mid-flight (logout, interruption) they must + // still end up on the destination rather than saved on a roof or rope tile. + if (glideEnd != dest && player.coords != dest) { + telejump(dest, TeleportType.Exempt) + } + } +} + +/** + * Grabs a zip line and slides down it to [dest]. The grab sequence ends on a long held frame, so + * the slide starts once its motion has played out. The slide glides on the destination level: the + * tile heights along the rope are authored on that level so the player hangs one storey below the + * roof they left, exactly a reach below the rope, and touches down on the landing platform. + */ +internal suspend fun ProtectedAccess.zipTo(dest: CoordGrid, ticks: Int) { + faceSquare(dest) + anim(AgilityAnims.ZIPLINE_GRAB) + delay(seqTicks(AgilityAnims.ZIPLINE_GRAB, fallback = 2)) + leapTo(dest, AgilityAnims.ZIPLINE_SLIDE, ticks, glideLevel = dest.level) +} + +internal fun ProtectedAccess.setBalanceStyle(style: BalanceStyle) { + val ready = style.ready.asRSCM(RSCMType.SEQ) + val walk = style.walk.asRSCM(RSCMType.SEQ) + player.bas = + BasType( + id = -1, + readyAnim = ready, + turnOnSpot = ready, + walkForward = walk, + walkBack = walk, + walkLeft = walk, + walkRight = walk, + running = walk, + ) + rebuildAppearance() +} + +internal fun ProtectedAccess.clearBalanceStyle() { + if (player.bas != null) { + player.bas = null + rebuildAppearance() + } +} + +/** + * Walks the player along [path] one tile per tick. Each step is a teleport move, which the client + * renders as a walk step but which bypasses the route finder entirely, so the player can never be + * pushed off the obstacle by collision or by their own clicks. Returns `false` when the walk was + * cut short at index [stopAt] (the player is left standing on that tile for a fall to follow). + * + * The tiles of a balance obstacle are unwalkable, so a player left on one of them could not move + * again. If the coroutine is torn down mid-walk for any reason (logout, a competing action, an + * error) the player is therefore placed on the last tile of [path] before the walk unwinds. + */ +internal suspend fun ProtectedAccess.balanceAlong( + path: List, + style: BalanceStyle, + stopAt: Int = -1, +): Boolean { + setBalanceStyle(style) + var finished = false + try { + style.mount?.let { + anim(it) + delay(1) + } + for ((index, tile) in path.withIndex()) { + if (index == stopAt) { + finished = true + return false + } + teleport(tile, TeleportType.Exempt) + delay(1) + } + style.dismount?.let { anim(it) } + finished = true + return true + } finally { + clearBalanceStyle() + if (!finished && player.coords != path.last()) { + logger.warn { + "Balance walk interrupted at ${player.coords}; placing $player on ${path.last()}." + } + telejump(path.last(), TeleportType.Exempt) + } + } +} + +private val logger = InlineLogger() + +/** + * Fails an obstacle: plays [seq], drops the player on [landing] and deals between [minDamage] and + * [maxDamage] damage. Callers reset any course progress themselves. + */ +internal suspend fun ProtectedAccess.fallTo( + landing: CoordGrid, + seq: String, + minDamage: Int, + maxDamage: Int, +) { + anim(seq) + delay(1) + telejump(landing, TeleportType.Exempt) + val damage = random.of(minDamage, maxDamage) + queueHit(delay = 1, type = HitType.Typeless, damage = damage) + mes("You lose your footing and fall to the ground below.") + delay(1) +} diff --git a/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/MarksOfGrace.kt b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/MarksOfGrace.kt new file mode 100644 index 000000000..fc28a661f --- /dev/null +++ b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/MarksOfGrace.kt @@ -0,0 +1,39 @@ +package org.rsmod.content.skills.agility.rooftop + +import jakarta.inject.Inject +import org.rsmod.api.player.stat.baseAgilityLvl +import org.rsmod.api.random.GameRandom +import org.rsmod.api.repo.obj.ObjRepository +import org.rsmod.game.entity.Player + +/** Spawns marks of grace on a course's rooftops when a player completes a lap. */ +class MarksOfGrace @Inject constructor(private val objRepo: ObjRepository) { + /** + * Rolls the course's mark chance for [player] and, on success, drops a mark only they can see + * on one of the course's roofs. Players 20 or more levels above the course requirement spawn + * marks at a fifth of the normal rate. + */ + fun roll(player: Player, random: GameRandom, layout: CourseLayout): Boolean { + val course = layout.course + var denominator = course.markDenominator + if (player.baseAgilityLvl >= course.level + OVERLEVEL_THRESHOLD) { + denominator *= OVERLEVEL_PENALTY + } + if (random.of(denominator) >= course.markNumerator) { + return false + } + val tile = random.pick(layout.markTiles) + objRepo.add(MARK_OF_GRACE, tile, duration = MARK_DURATION, receiver = player) + return true + } + + companion object { + const val MARK_OF_GRACE = "obj.grace" + + /** Marks stay on the roof for ten minutes before vanishing. */ + const val MARK_DURATION = 1000 + + const val OVERLEVEL_THRESHOLD = 20 + const val OVERLEVEL_PENALTY = 5 + } +} diff --git a/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/RooftopCourse.kt b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/RooftopCourse.kt new file mode 100644 index 000000000..085d55786 --- /dev/null +++ b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/RooftopCourse.kt @@ -0,0 +1,129 @@ +package org.rsmod.content.skills.agility.rooftop + +import org.rsmod.content.skills.agility.AgilityAnims +import org.rsmod.content.skills.agility.BalanceStyle +import org.rsmod.map.CoordGrid + +/** + * The rooftop courses, with the Agility level needed to start them and the marks of grace chance + * ([markNumerator] in [markDenominator]) rolled when a full lap is completed. + */ +enum class RooftopCourse( + val displayName: String, + val level: Int, + val markNumerator: Int, + val markDenominator: Int, +) { + Draynor("Draynor Village", 1, 1, 3), + AlKharid("Al Kharid", 20, 1, 3), + Varrock("Varrock", 30, 1, 3), + Canifis("Canifis", 40, 2, 3), + Falador("Falador", 50, 1, 5), + Seers("Seers' Village", 60, 1, 3), + Pollnivneach("Pollnivneach", 70, 1, 3), + Rellekka("Rellekka", 80, 1, 3), + Ardougne("Ardougne", 90, 1, 3), +} + +/** How the player is moved once an obstacle is started. */ +sealed class ObstacleMove { + abstract val destination: CoordGrid + + /** + * Play [seq] for [ticks] cycles, then appear on [dest]. Used for walls, trees and crates. When + * [ticks] is null the move waits for the whole animation. + */ + data class Climb( + val dest: CoordGrid, + val seq: String = AgilityAnims.WALL_CLIMB, + val ticks: Int? = null, + ) : ObstacleMove() { + override val destination: CoordGrid get() = dest + } + + /** + * Glide to [dest] over [ticks] cycles with an `exactmove`. Used for gaps, swings and edges. When + * [ticks] is null the glide lasts as long as the animation, rounded down to whole ticks. + * + * A leap to another level glides on [glideLevel], the starting level unless given, and changes + * level on landing. Set it where the starting level has no terrain past the edge (the client + * then draws the glide rising instead of falling) or where the drop was authored into another + * level's tile heights. + */ + data class Leap( + val dest: CoordGrid, + val seq: String = AgilityAnims.JUMP, + val ticks: Int? = null, + val glideLevel: Int? = null, + ) : ObstacleMove() { + override val destination: CoordGrid get() = dest + } + + /** Walk [path] one tile per tick with the [style] animations. Used for ropes and bars. */ + data class Balance( + val path: List, + val style: BalanceStyle = BalanceStyle.Tightrope, + ) : ObstacleMove() { + init { + require(path.isNotEmpty()) { "Balance path must not be empty." } + } + + override val destination: CoordGrid get() = path.last() + } + + /** Grab a zip line and slide to [dest] over [ticks] cycles. */ + data class Zipline(val dest: CoordGrid, val ticks: Int = 3) : ObstacleMove() { + override val destination: CoordGrid get() = dest + } +} + +/** + * Failure rules for an obstacle: it can be failed until the player reaches [noFailLevel], dropping + * them on [landing] for [minDamage]..[maxDamage] damage. + */ +data class ObstacleFailure( + val noFailLevel: Int, + val landing: CoordGrid, + val minDamage: Int, + val maxDamage: Int, +) + +/** + * One obstacle of a rooftop course. + * + * @param locs Every loc gameval that starts this obstacle (some ledges are split over two locs). + * @param start The tile the player is placed on before the movement begins. + * @param xp Experience granted every time the obstacle is completed. + * @param lapBonusXp Extra experience granted when this obstacle completes a full, in-order lap. + * Only the final obstacle of a course has a bonus; it also rolls for a mark of grace. + * @param apRange When greater than zero the obstacle also starts once the player is within this + * many tiles of the loc with a line of sight to it, without having to reach it. Needed where the + * map fences the loc off from the tile it is used from (a railing between a landing platform + * and the tree that is swung from), which makes the loc unreachable to the route finder. + */ +data class RooftopObstacle( + val locs: List, + val name: String, + val xp: Double, + val start: CoordGrid, + val move: ObstacleMove, + val failure: ObstacleFailure? = null, + val lapBonusXp: Double = 0.0, + val apRange: Int = 0, +) { + val isFinish: Boolean get() = lapBonusXp > 0.0 + + val totalXp: Double get() = xp + lapBonusXp +} + +/** A course together with its obstacles (in lap order) and the tiles marks of grace spawn on. */ +class CourseLayout( + val course: RooftopCourse, + val obstacles: List, + val markTiles: List, +) { + /** Bit mask with one bit per obstacle; a lap is complete when every bit has been set. */ + val fullMask: Int = (1 shl obstacles.size) - 1 + + val lapXp: Double get() = obstacles.sumOf { it.totalXp } +} diff --git a/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/RooftopCourses.kt b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/RooftopCourses.kt new file mode 100644 index 000000000..fe9a0c65c --- /dev/null +++ b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/RooftopCourses.kt @@ -0,0 +1,787 @@ +package org.rsmod.content.skills.agility.rooftop + +import org.rsmod.content.skills.agility.AgilityAnims +import org.rsmod.content.skills.agility.BalanceStyle +import org.rsmod.content.skills.agility.line +import org.rsmod.content.skills.agility.rooftop.ObstacleMove.Balance +import org.rsmod.content.skills.agility.rooftop.ObstacleMove.Climb +import org.rsmod.content.skills.agility.rooftop.ObstacleMove.Leap +import org.rsmod.content.skills.agility.rooftop.ObstacleMove.Zipline +import org.rsmod.map.CoordGrid + +/** + * Layouts of the nine rooftop courses. + * + * Every start tile, landing tile and balance path was read from the game map: the obstacle loc + * positions and sizes come from the cache, and each tile a player is placed on was checked to be + * an unblocked roof tile on the level the server actually uses (Canifis' roofs are "bridge" + * tiles, so its obstacles live on level 2 even though they render on level 3). Movement never + * relies on the route finder, so players cannot be knocked off a course by collision. + * + * Experience values are the wiki's per-obstacle values; the final obstacle's total is split into a + * small base and a lap bonus that is only paid for an in-order lap. + */ +object RooftopCourses { + val layouts: List = + listOf( + draynor(), + alKharid(), + varrock(), + canifis(), + falador(), + seers(), + pollnivneach(), + rellekka(), + ardougne(), + ) + + fun layout(course: RooftopCourse): CourseLayout = layouts.first { it.course == course } + + private fun tile(x: Int, z: Int, level: Int): CoordGrid = CoordGrid(x, z, level) + + private fun draynor(): CourseLayout = + CourseLayout( + course = RooftopCourse.Draynor, + obstacles = + listOf( + RooftopObstacle( + locs = listOf("loc.rooftops_draynor_wallclimb"), + name = "Rough wall", + xp = 5.0, + start = tile(3103, 3279, 0), + move = Climb(tile(3102, 3279, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_draynor_tightrope_1"), + name = "Tightrope", + xp = 8.0, + start = tile(3099, 3277, 3), + move = + Balance( + line(tile(3099, 3277, 3), tile(3090, 3277, 3)) + tile(3090, 3276, 3) + ), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_draynor_tightrope_2"), + name = "Tightrope", + xp = 7.0, + start = tile(3091, 3276, 3), + move = + Balance( + listOf(tile(3092, 3276, 3)) + + line(tile(3092, 3276, 3), tile(3092, 3266, 3)) + ), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_draynor_wallcrossing"), + name = "Narrow wall", + xp = 7.0, + start = tile(3089, 3265, 3), + move = + Balance( + line(tile(3089, 3265, 3), tile(3089, 3262, 3)) + tile(3088, 3261, 3) + ), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_draynor_wallscramble"), + name = "Wall", + xp = 10.0, + start = tile(3088, 3257, 3), + move = Leap(tile(3088, 3255, 3), seq = AgilityAnims.JUMP_UP), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_draynor_leapdown"), + name = "Gap", + xp = 4.0, + start = tile(3094, 3255, 3), + move = Leap(tile(3096, 3256, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_draynor_crate"), + name = "Crate", + xp = 4.0, + lapBonusXp = 75.0, + start = tile(3101, 3261, 3), + // Level 1 carries the height of the crate top, so the hop lands on it. + move = + Leap(tile(3103, 3261, 0), seq = AgilityAnims.JUMP_DOWN, glideLevel = 1), + ), + ), + markTiles = + listOf( + tile(3100, 3280, 3), + tile(3089, 3275, 3), + tile(3093, 3266, 3), + tile(3088, 3259, 3), + tile(3090, 3255, 3), + tile(3098, 3259, 3), + ), + ) + + private fun alKharid(): CourseLayout = + CourseLayout( + course = RooftopCourse.AlKharid, + obstacles = + listOf( + RooftopObstacle( + locs = listOf("loc.rooftops_kharid_wallclimb"), + name = "Rough wall", + xp = 12.0, + start = tile(3273, 3195, 0), + move = Climb(tile(3273, 3192, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_kharid_tightrope_1"), + name = "Tightrope", + xp = 36.0, + start = tile(3272, 3182, 3), + move = Balance(line(tile(3272, 3182, 3), tile(3272, 3172, 3))), + failure = ObstacleFailure(30, tile(3272, 3177, 0), 1, 5), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_kharid_rope_swing"), + name = "Cable", + xp = 48.0, + start = tile(3268, 3166, 3), + move = Leap(tile(3283, 3166, 3), seq = AgilityAnims.ROPE_SWING, ticks = 3), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_kharid_slide_side"), + name = "Zip line", + xp = 48.0, + start = tile(3301, 3163, 3), + move = Zipline(tile(3315, 3163, 1), ticks = 3), + failure = ObstacleFailure(30, tile(3308, 3163, 0), 1, 5), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_kharid_bamboo_tree_top"), + name = "Tropical tree", + xp = 12.0, + start = tile(3318, 3165, 1), + move = Leap(tile(3317, 3175, 2), seq = AgilityAnims.ROPE_SWING, ticks = 3), + // The zip line landing platform is railed off from the tree. + apRange = 1, + ), + RooftopObstacle( + locs = listOf("loc.rooftops_kharid_wallclimb_2"), + name = "Roof top beams", + xp = 6.0, + start = tile(3316, 3178, 2), + move = Climb(tile(3316, 3180, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_kharid_tightrope_4"), + name = "Tightrope", + xp = 18.0, + start = tile(3314, 3186, 3), + move = Balance(line(tile(3314, 3186, 3), tile(3304, 3186, 3))), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_kharid_leapdown"), + name = "Gap", + xp = 6.0, + lapBonusXp = 30.0, + start = tile(3300, 3192, 3), + move = Leap(tile(3299, 3194, 0), seq = AgilityAnims.JUMP_DOWN), + ), + ), + markTiles = + listOf( + tile(3274, 3190, 3), + tile(3270, 3170, 3), + tile(3290, 3165, 3), + tile(3316, 3162, 1), + tile(3316, 3176, 2), + tile(3316, 3183, 3), + tile(3301, 3190, 3), + ), + ) + + private fun varrock(): CourseLayout = + CourseLayout( + course = RooftopCourse.Varrock, + obstacles = + listOf( + RooftopObstacle( + locs = listOf("loc.rooftops_varrock_wallclimb"), + name = "Rough wall", + xp = 13.5, + start = tile(3221, 3414, 0), + move = Climb(tile(3219, 3414, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_varrock_clothesline"), + name = "Clothes line", + xp = 23.0, + start = tile(3214, 3414, 3), + move = Balance(line(tile(3214, 3414, 3), tile(3208, 3414, 3))), + failure = ObstacleFailure(40, tile(3211, 3414, 0), 3, 8), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_varrock_leaptoruins"), + name = "Gap", + xp = 19.0, + start = tile(3201, 3416, 3), + // The roof level has no terrain over the gap; the ruin level is flat. + move = Leap(tile(3193, 3416, 1), glideLevel = 1), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_varrock_wallswing"), + name = "Wall", + xp = 28.0, + start = tile(3193, 3416, 1), + move = + Balance( + listOf(tile(3192, 3415, 3)) + + line(tile(3192, 3415, 3), tile(3192, 3407, 3)) + + tile(3193, 3406, 3) + ), + failure = ObstacleFailure(40, tile(3191, 3412, 0), 2, 5), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_varrock_wallscramble"), + name = "Gap", + xp = 10.0, + start = tile(3193, 3402, 3), + move = Leap(tile(3193, 3398, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_varrock_leaptobalcony"), + name = "Gap", + xp = 24.5, + start = tile(3208, 3397, 3), + move = Leap(tile(3218, 3397, 3), ticks = 3), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_varrock_leapdown"), + name = "Gap", + xp = 4.5, + start = tile(3232, 3402, 3), + move = Leap(tile(3236, 3403, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_varrock_stepuproof"), + name = "Ledge", + xp = 3.5, + start = tile(3236, 3408, 3), + move = Leap(tile(3236, 3410, 3), seq = AgilityAnims.JUMP_UP), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_varrock_finish"), + name = "Edge", + xp = 7.0, + lapBonusXp = 136.7, + start = tile(3236, 3415, 3), + move = Leap(tile(3236, 3417, 0), seq = AgilityAnims.JUMP_DOWN), + ), + ), + markTiles = + listOf( + tile(3219, 3417, 3), + tile(3208, 3400, 3), + tile(3195, 3404, 3), + tile(3218, 3399, 3), + tile(3225, 3402, 3), + tile(3237, 3406, 3), + tile(3237, 3413, 3), + ), + ) + + private fun canifis(): CourseLayout = + CourseLayout( + course = RooftopCourse.Canifis, + obstacles = + listOf( + RooftopObstacle( + locs = listOf("loc.rooftops_canifis_start_tree"), + name = "Tall tree", + xp = 10.0, + start = tile(3506, 3488, 0), + move = Climb(tile(3506, 3492, 2), seq = AgilityAnims.CLIMB), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_canifis_jump"), + name = "Gap", + xp = 8.0, + start = tile(3505, 3497, 2), + move = Leap(tile(3503, 3504, 2)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_canifis_jump_2"), + name = "Gap", + xp = 8.0, + start = tile(3497, 3505, 2), + move = Leap(tile(3491, 3504, 2), ticks = 3), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_canifis_jump_5"), + name = "Gap", + xp = 10.0, + start = tile(3487, 3499, 2), + move = Leap(tile(3479, 3499, 3)), + failure = ObstacleFailure(65, tile(3482, 3499, 0), 2, 5), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_canifis_jump_3"), + name = "Gap", + xp = 8.0, + start = tile(3478, 3493, 3), + move = Leap(tile(3478, 3487, 2)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_canifis_polevault"), + name = "Pole-vault", + xp = 10.0, + start = tile(3481, 3482, 2), + move = Leap(tile(3490, 3476, 3), seq = AgilityAnims.POLE_VAULT, ticks = 3), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_canifis_jump_4"), + name = "Gap", + xp = 11.0, + start = tile(3502, 3476, 3), + move = Leap(tile(3510, 3476, 2), ticks = 3), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_canifis_leapdown"), + name = "Gap", + xp = 10.0, + lapBonusXp = 165.0, + start = tile(3510, 3482, 2), + move = Leap(tile(3510, 3485, 0), seq = AgilityAnims.JUMP_DOWN), + ), + ), + markTiles = + listOf( + tile(3507, 3495, 2), + tile(3499, 3505, 2), + tile(3489, 3500, 2), + tile(3477, 3495, 3), + tile(3480, 3486, 2), + tile(3495, 3475, 3), + tile(3511, 3479, 2), + ), + ) + + private fun falador(): CourseLayout = + CourseLayout( + course = RooftopCourse.Falador, + obstacles = + listOf( + RooftopObstacle( + locs = listOf("loc.rooftops_falador_wallclimb"), + name = "Rough wall", + xp = 11.0, + start = tile(3036, 3341, 0), + move = Climb(tile(3036, 3342, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_tightrope_1"), + name = "Tightrope", + xp = 22.0, + start = tile(3039, 3343, 3), + move = Balance(line(tile(3039, 3343, 3), tile(3044, 3343, 3))), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_handholds_start"), + name = "Hand holds", + xp = 61.0, + start = tile(3050, 3349, 3), + move = + Balance( + line(tile(3050, 3349, 3), tile(3050, 3357, 3)), + style = BalanceStyle.Handholds, + ), + failure = ObstacleFailure(66, tile(3052, 3353, 0), 3, 8), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_gap_1"), + name = "Gap", + xp = 27.0, + start = tile(3048, 3358, 3), + move = Leap(tile(3048, 3361, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_gap_2"), + name = "Gap", + xp = 26.0, + start = tile(3046, 3361, 3), + move = Leap(tile(3041, 3361, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_tightrope_2"), + name = "Tightrope", + xp = 61.0, + start = tile(3035, 3361, 3), + move = + Balance( + listOf(tile(3034, 3361, 3)) + + line(tile(3034, 3361, 3), tile(3028, 3355, 3)) + + tile(3028, 3354, 3) + ), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_tightrope_3"), + name = "Tightrope", + xp = 53.0, + start = tile(3027, 3353, 3), + move = Balance(line(tile(3027, 3353, 3), tile(3020, 3353, 3))), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_gap_3"), + name = "Gap", + xp = 30.0, + start = tile(3017, 3353, 3), + move = Leap(tile(3017, 3349, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_ledge_1"), + name = "Ledge", + xp = 14.0, + start = tile(3016, 3345, 3), + move = Leap(tile(3014, 3345, 3), ticks = 1), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_ledge_2"), + name = "Ledge", + xp = 13.0, + start = tile(3012, 3344, 3), + move = Leap(tile(3012, 3342, 3), ticks = 1), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_ledge_3a", "loc.rooftops_falador_ledge_3b"), + name = "Ledge", + xp = 13.0, + start = tile(3013, 3335, 3), + move = Leap(tile(3013, 3333, 3), ticks = 1), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_ledge_4"), + name = "Ledge", + xp = 14.0, + start = tile(3017, 3332, 3), + move = Leap(tile(3021, 3332, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_falador_edge"), + name = "Edge", + xp = 11.0, + lapBonusXp = 230.0, + start = tile(3024, 3332, 3), + // The roof level ends a tile short of the landing; level 1 is flat there. + move = Leap(tile(3029, 3332, 0), glideLevel = 1), + ), + ), + markTiles = + listOf( + tile(3038, 3342, 3), + tile(3048, 3346, 3), + tile(3049, 3357, 3), + tile(3038, 3363, 3), + tile(3012, 3356, 3), + tile(3018, 3348, 3), + tile(3010, 3340, 3), + tile(3015, 3333, 3), + tile(3022, 3334, 3), + ), + ) + + private fun seers(): CourseLayout = + CourseLayout( + course = RooftopCourse.Seers, + obstacles = + listOf( + RooftopObstacle( + locs = listOf("loc.rooftops_seers_wallclimb"), + name = "Wall", + xp = 45.0, + start = tile(2729, 3489, 0), + move = Climb(tile(2729, 3490, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_seers_jump"), + name = "Gap", + xp = 20.0, + start = tile(2721, 3494, 3), + move = Leap(tile(2713, 3494, 2), ticks = 3), + failure = ObstacleFailure(79, tile(2716, 3494, 0), 2, 6), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_seers_tightrope"), + name = "Tightrope", + xp = 20.0, + start = tile(2710, 3490, 2), + move = Balance(line(tile(2710, 3490, 2), tile(2710, 3480, 2))), + failure = ObstacleFailure(79, tile(2711, 3485, 0), 2, 6), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_seers_jump_1"), + name = "Gap", + xp = 35.0, + start = tile(2710, 3477, 2), + move = Leap(tile(2710, 3472, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_seers_jump_2"), + name = "Gap", + xp = 15.0, + start = tile(2700, 3470, 3), + move = Leap(tile(2700, 3465, 2)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_seers_leapdown"), + name = "Edge", + xp = 15.0, + lapBonusXp = 420.0, + start = tile(2702, 3464, 2), + move = Leap(tile(2704, 3464, 0), seq = AgilityAnims.JUMP_DOWN), + ), + ), + markTiles = + listOf( + tile(2725, 3492, 3), + tile(2708, 3494, 2), + tile(2713, 3479, 2), + tile(2702, 3473, 3), + tile(2700, 3462, 2), + ), + ) + + private fun pollnivneach(): CourseLayout = + CourseLayout( + course = RooftopCourse.Pollnivneach, + obstacles = + listOf( + RooftopObstacle( + locs = listOf("loc.rooftops_pollnivneach_basket"), + name = "Basket", + xp = 10.0, + start = tile(3351, 2961, 0), + move = Climb(tile(3351, 2964, 1), seq = AgilityAnims.JUMP_UP), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_pollnivneach_marketstall"), + name = "Market stall", + xp = 45.0, + start = tile(3349, 2968, 1), + move = Leap(tile(3352, 2973, 1), ticks = 3), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_pollnivneach_hangingbanner"), + name = "Banner", + xp = 65.0, + start = tile(3356, 2976, 1), + move = Leap(tile(3360, 2978, 1), seq = AgilityAnims.ROPE_SWING), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_pollnivneach_gap"), + name = "Gap", + xp = 35.0, + start = tile(3362, 2977, 1), + move = Leap(tile(3366, 2976, 1)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_pollnivneach_tree"), + name = "Tree", + xp = 75.0, + start = tile(3367, 2976, 1), + move = Leap(tile(3367, 2982, 1), ticks = 3), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_pollnivneach_wallclimb"), + name = "Rough wall", + xp = 5.0, + start = tile(3365, 2982, 1), + move = Climb(tile(3365, 2983, 2)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_pollnivneach_monkeybars_start"), + name = "Monkeybars", + xp = 55.0, + start = tile(3358, 2984, 2), + move = + Balance( + line(tile(3358, 2984, 2), tile(3358, 2992, 2)), + style = BalanceStyle.Monkeybars, + ), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_pollnivneach_treetop"), + name = "Tree", + xp = 60.0, + start = tile(3359, 2995, 2), + move = Leap(tile(3359, 3000, 2), ticks = 3), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_pollnivneach_line"), + name = "Drying line", + xp = 20.0, + lapBonusXp = 520.0, + start = tile(3362, 3002, 2), + move = Leap(tile(3363, 2998, 0)), + ), + ), + markTiles = + listOf( + tile(3348, 2966, 1), + tile(3354, 2975, 1), + tile(3361, 2980, 1), + tile(3368, 2975, 1), + tile(3357, 2983, 2), + tile(3364, 2993, 2), + tile(3357, 3002, 2), + ), + ) + + private fun rellekka(): CourseLayout = + CourseLayout( + course = RooftopCourse.Rellekka, + obstacles = + listOf( + RooftopObstacle( + locs = listOf("loc.rooftops_rellekka_wallclimb"), + name = "Rough wall", + xp = 20.0, + start = tile(2625, 3677, 0), + move = Climb(tile(2625, 3676, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_rellekka_gap_1"), + name = "Gap", + xp = 30.0, + start = tile(2622, 3672, 3), + move = Leap(tile(2622, 3668, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_rellekka_tightrope_1"), + name = "Tightrope", + xp = 40.0, + start = tile(2622, 3658, 3), + move = + Balance( + listOf(tile(2623, 3658, 3)) + + line(tile(2623, 3658, 3), tile(2627, 3654, 3)) + ), + failure = ObstacleFailure(85, tile(2624, 3656, 0), 2, 6), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_rellekka_gap_2"), + name = "Gap", + xp = 85.0, + start = tile(2629, 3655, 3), + move = Leap(tile(2639, 3653, 3), ticks = 3), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_rellekka_gap_3"), + name = "Gap", + xp = 25.0, + start = tile(2643, 3653, 3), + move = Leap(tile(2643, 3657, 3), seq = AgilityAnims.JUMP_UP), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_rellekka_tightrope_3"), + name = "Tightrope", + xp = 105.0, + start = tile(2647, 3662, 3), + move = + Balance( + listOf(tile(2647, 3663, 3)) + + line(tile(2647, 3663, 3), tile(2653, 3669, 3)) + + line(tile(2653, 3669, 3), tile(2655, 3669, 3)) + ), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_rellekka_dropoff"), + name = "Pile of fish", + xp = 20.0, + lapBonusXp = 455.0, + start = tile(2655, 3676, 3), + // The ground slopes down over the fish pile; the roof level rises past it. + move = + Leap(tile(2652, 3676, 0), seq = AgilityAnims.JUMP_DOWN, glideLevel = 0), + ), + ), + markTiles = + listOf( + tile(2624, 3674, 3), + tile(2620, 3663, 3), + tile(2628, 3652, 3), + tile(2641, 3651, 3), + tile(2646, 3659, 3), + tile(2656, 3672, 3), + ), + ) + + private fun ardougne(): CourseLayout = + CourseLayout( + course = RooftopCourse.Ardougne, + obstacles = + listOf( + RooftopObstacle( + locs = listOf("loc.rooftops_ardy_wallclimb"), + name = "Wooden beams", + xp = 43.0, + start = tile(2673, 3298, 0), + move = Climb(tile(2671, 3299, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_ardy_jump"), + name = "Gap", + xp = 65.0, + start = tile(2671, 3309, 3), + move = Leap(tile(2665, 3318, 3), ticks = 3), + failure = ObstacleFailure(95, tile(2668, 3314, 0), 3, 8), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_ardy_plank"), + name = "Plank", + xp = 50.0, + start = tile(2662, 3318, 3), + move = Balance(line(tile(2662, 3318, 3), tile(2656, 3318, 3))), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_ardy_jump_2"), + name = "Gap", + xp = 21.0, + start = tile(2654, 3318, 3), + move = Leap(tile(2653, 3314, 3)), + failure = ObstacleFailure(95, tile(2653, 3316, 0), 3, 8), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_ardy_jump_3"), + name = "Gap", + xp = 28.0, + start = tile(2653, 3310, 3), + move = Leap(tile(2651, 3308, 3)), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_ardy_wallcrossing"), + name = "Steep roof", + xp = 57.0, + start = tile(2653, 3300, 3), + move = + Balance( + listOf(tile(2654, 3300, 3)) + + line(tile(2654, 3300, 3), tile(2657, 3297, 3)) + ), + ), + RooftopObstacle( + locs = listOf("loc.rooftops_ardy_jump_4"), + name = "Gap", + xp = 25.0, + lapBonusXp = 600.0, + start = tile(2657, 3297, 3), + // The roof level rises once the last building ends; glide at ground level. + move = Leap(tile(2668, 3297, 0), glideLevel = 0), + ), + ), + markTiles = + listOf( + tile(2671, 3305, 3), + tile(2664, 3318, 3), + tile(2653, 3312, 3), + tile(2651, 3306, 3), + tile(2653, 3302, 3), + ), + ) +} diff --git a/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/RooftopScript.kt b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/RooftopScript.kt new file mode 100644 index 000000000..588138127 --- /dev/null +++ b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/rooftop/RooftopScript.kt @@ -0,0 +1,150 @@ +package org.rsmod.content.skills.agility.rooftop + +import jakarta.inject.Inject +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.agilityLvl +import org.rsmod.api.script.onApLoc1 +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.content.skills.agility.AgilityAnims +import org.rsmod.content.skills.agility.balanceAlong +import org.rsmod.content.skills.agility.climbTo +import org.rsmod.content.skills.agility.fallTo +import org.rsmod.content.skills.agility.leapTo +import org.rsmod.content.skills.agility.seqGlideTicks +import org.rsmod.content.skills.agility.seqTicks +import org.rsmod.content.skills.agility.stepOnto +import org.rsmod.content.skills.agility.successChance +import org.rsmod.content.skills.agility.zipTo +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Handles every obstacle of the rooftop agility courses. + * + * Lap progress is a per-session bit mask of the obstacles completed on the current course. The + * final obstacle only pays its lap bonus (and rolls for a mark of grace) when every other obstacle + * of that course has been completed since the last finish, which mirrors the live game where + * skipping an obstacle forfeits the completion bonus. + */ +class RooftopScript @Inject constructor(private val marks: MarksOfGrace) : PluginScript() { + override fun ScriptContext.startup() { + for (layout in RooftopCourses.layouts) { + for ((index, obstacle) in layout.obstacles.withIndex()) { + for (loc in obstacle.locs) { + onOpLoc1(loc) { attempt(layout, index, obstacle) } + if (obstacle.apRange > 0) { + onApLoc1(loc) { + if (isWithinApRange(it.loc, obstacle.apRange)) { + attempt(layout, index, obstacle) + } + } + } + } + } + } + } + + private suspend fun ProtectedAccess.attempt( + layout: CourseLayout, + index: Int, + obstacle: RooftopObstacle, + ) { + val course = layout.course + arriveDelay() + if (player.agilityLvl < course.level) { + mes("You need an Agility level of ${course.level} to use this course.") + return + } + stepOnto(obstacle.start) + + val failure = obstacle.failure?.takeIf { rollFailure(course, it) } + val completed = perform(obstacle.move, failure) + if (!completed) { + player.courseProgress = 0 + return + } + + statAdvance(AGILITY, obstacle.xp) + val progress = player.progressOn(course) or (1 shl index) + if (!obstacle.isFinish) { + player.courseProgress = pack(course, progress) + return + } + + player.courseProgress = 0 + if (progress == layout.fullMask) { + statAdvance(AGILITY, obstacle.lapBonusXp) + marks.roll(player, random, layout) + } + } + + private fun ProtectedAccess.rollFailure(course: RooftopCourse, failure: ObstacleFailure): Boolean { + val level = player.agilityLvl + if (level >= failure.noFailLevel) { + return false + } + val chance = successChance(level, course.level, failure.noFailLevel) + return random.of(100) >= chance + } + + /** Runs [move], or the failure sequence when [failure] is set. Returns `false` on a fail. */ + private suspend fun ProtectedAccess.perform(move: ObstacleMove, failure: ObstacleFailure?): Boolean { + when (move) { + is ObstacleMove.Climb -> { + val ticks = move.ticks ?: seqTicks(move.seq, fallback = 2) + climbTo(move.dest, move.seq, ticks) + } + is ObstacleMove.Leap -> { + if (failure != null) { + fallTo(failure.landing, move.seq, failure.minDamage, failure.maxDamage) + return false + } + val ticks = move.ticks ?: seqGlideTicks(move.seq, fallback = 2) + leapTo(move.dest, move.seq, ticks, glideLevel = move.glideLevel ?: coords.level) + } + is ObstacleMove.Zipline -> { + if (failure != null) { + fallTo(failure.landing, AgilityAnims.ZIPLINE_GRAB, failure.minDamage, failure.maxDamage) + return false + } + zipTo(move.dest, move.ticks) + } + is ObstacleMove.Balance -> { + val stopAt = if (failure != null) move.path.size / 2 else -1 + val crossed = balanceAlong(move.path, move.style, stopAt) + if (!crossed && failure != null) { + fallTo( + failure.landing, + AgilityAnims.BALANCE_STUMBLE, + failure.minDamage, + failure.maxDamage, + ) + return false + } + } + } + return true + } + + private companion object { + const val AGILITY = "stat.agility" + + /** Packed `course ordinal shl 16 or completed-obstacle mask`; not persisted. */ + val COURSE_PROGRESS = AttributeKey() + + var Player.courseProgress: Int + get() = attr[COURSE_PROGRESS] ?: 0 + set(value) { + attr[COURSE_PROGRESS] = value + } + + fun Player.progressOn(course: RooftopCourse): Int { + val packed = courseProgress + return if (packed shr 16 == course.ordinal) packed and 0xFFFF else 0 + } + + fun pack(course: RooftopCourse, mask: Int): Int = (course.ordinal shl 16) or mask + } +} diff --git a/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/shortcuts/AgilityShortcut.kt b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/shortcuts/AgilityShortcut.kt new file mode 100644 index 000000000..f90597d0d --- /dev/null +++ b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/shortcuts/AgilityShortcut.kt @@ -0,0 +1,80 @@ +package org.rsmod.content.skills.agility.shortcuts + +import org.rsmod.content.skills.agility.AgilityAnims +import org.rsmod.map.CoordGrid + +/** How a player crosses a shortcut. Paths are given from side A to side B and reversed as needed. */ +sealed class ShortcutMove { + /** + * Play [seq] for [ticks] cycles (the whole animation when null) and appear on the far side. + * Rocks, chains, ladders. + */ + data class Climb(val seq: String = AgilityAnims.CLIMB, val ticks: Int? = null) : ShortcutMove() + + /** + * Glide to the far side over [ticks] cycles (the length of the animation when null) while + * [seq] plays. Fences, walls, ledges. + */ + data class Jump(val seq: String = AgilityAnims.JUMP_UP, val ticks: Int? = null) : ShortcutMove() + + /** Play [enter], pass through after [ticks] cycles and play [leave]. Tunnels, pipes, cracks. */ + data class Squeeze( + val enter: String = AgilityAnims.CRACK_ENTER, + val leave: String = AgilityAnims.CRACK_LEAVE, + val ticks: Int = 2, + ) : ShortcutMove() + + /** + * Drop into a hole, shuffle underground one tile per tick to the far side and climb out. + * Underwall tunnels. + */ + data class Tunnel( + val enter: String = AgilityAnims.TUNNEL_ENTER, + val walk: String = AgilityAnims.TUNNEL_WALK, + val leave: String = AgilityAnims.TUNNEL_EXIT, + ) : ShortcutMove() + + /** Balance across [tiles] (the tiles between the two sides) one per tick. Log balances. */ + data class Balance(val tiles: List) : ShortcutMove() + + /** Hop from stone to stone across [stones] (the tiles between the two sides). */ + data class Hop(val stones: List) : ShortcutMove() + + companion object { + val PIPE: Squeeze = Squeeze(AgilityAnims.PIPE_SQUEEZE, AgilityAnims.PIPE_UNSQUEEZE, ticks = 3) + } +} + +/** + * A two-way agility shortcut between [sideA] and [sideB]. The player is taken to whichever side is + * further from them. A loc may be shared by several shortcuts (the same crack model is reused + * around the world), in which case the shortcut nearest the player is used. + */ +data class AgilityShortcut( + val name: String, + val level: Int, + val xp: Double, + val locs: List, + val sideA: CoordGrid, + val sideB: CoordGrid, + val move: ShortcutMove, +) { + init { + require(sideA != sideB) { "Shortcut '$name' needs two distinct sides." } + require(locs.isNotEmpty()) { "Shortcut '$name' needs at least one loc." } + } + + /** Distance from [coords] to the nearest side, with other levels pushed far away. */ + fun distanceTo(coords: CoordGrid): Int = minOf(coords.distanceTo(sideA), coords.distanceTo(sideB)) + + /** True when a player at [coords] should be taken from [sideA] to [sideB]. */ + fun startsFromA(coords: CoordGrid): Boolean = + coords.distanceTo(sideA) <= coords.distanceTo(sideB) + + private fun CoordGrid.distanceTo(other: CoordGrid): Int = + chebyshevDistance(other) + LEVEL_PENALTY * kotlin.math.abs(level - other.level) + + private companion object { + const val LEVEL_PENALTY = 1000 + } +} diff --git a/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/shortcuts/AgilityShortcutScript.kt b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/shortcuts/AgilityShortcutScript.kt new file mode 100644 index 000000000..bcde148bc --- /dev/null +++ b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/shortcuts/AgilityShortcutScript.kt @@ -0,0 +1,93 @@ +package org.rsmod.content.skills.agility.shortcuts + +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.agilityLvl +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.content.skills.agility.AgilityAnims +import org.rsmod.content.skills.agility.BalanceStyle +import org.rsmod.content.skills.agility.balanceAlong +import org.rsmod.content.skills.agility.climbTo +import org.rsmod.content.skills.agility.leapTo +import org.rsmod.content.skills.agility.line +import org.rsmod.content.skills.agility.seqGlideTicks +import org.rsmod.content.skills.agility.seqTicks +import org.rsmod.content.skills.agility.stepOnto +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Moves players across the two-way agility shortcuts listed in [AgilityShortcuts]. */ +class AgilityShortcutScript : PluginScript() { + override fun ScriptContext.startup() { + val byLoc = mutableMapOf>() + for (shortcut in AgilityShortcuts.all) { + for (loc in shortcut.locs) { + byLoc.getOrPut(loc) { mutableListOf() } += shortcut + } + } + for ((loc, shortcuts) in byLoc) { + onOpLoc1(loc) { use(shortcuts) } + } + } + + private suspend fun ProtectedAccess.use(candidates: List) { + arriveDelay() + val shortcut = candidates.minBy { it.distanceTo(coords) } + if (player.agilityLvl < shortcut.level) { + mes("You need an Agility level of ${shortcut.level} to use this shortcut.") + return + } + val fromA = shortcut.startsFromA(coords) + val from = if (fromA) shortcut.sideA else shortcut.sideB + val dest = if (fromA) shortcut.sideB else shortcut.sideA + stepOnto(from) + cross(shortcut.move, dest, fromA) + if (shortcut.xp > 0.0) { + statAdvance("stat.agility", shortcut.xp) + } + } + + private suspend fun ProtectedAccess.cross(move: ShortcutMove, dest: CoordGrid, fromA: Boolean) { + when (move) { + is ShortcutMove.Climb -> { + val ticks = move.ticks ?: seqTicks(move.seq, fallback = 2) + climbTo(dest, move.seq, ticks) + } + is ShortcutMove.Jump -> { + val ticks = move.ticks ?: seqGlideTicks(move.seq, fallback = 2) + leapTo(dest, move.seq, ticks) + } + is ShortcutMove.Squeeze -> { + faceSquare(dest) + anim(move.enter) + delay(move.ticks) + telejump(dest, TeleportType.Exempt) + anim(move.leave) + } + is ShortcutMove.Tunnel -> { + faceSquare(dest) + anim(move.enter) + delay(seqTicks(move.enter, fallback = 2)) + val path = line(coords, dest) + for (tile in path.dropLast(1)) { + teleport(tile, TeleportType.Exempt) + anim(move.walk) + delay(1) + } + telejump(dest, TeleportType.Exempt) + anim(move.leave) + } + is ShortcutMove.Balance -> { + val path = (if (fromA) move.tiles else move.tiles.reversed()) + dest + balanceAlong(path, BalanceStyle.Tightrope) + } + is ShortcutMove.Hop -> { + val stones = (if (fromA) move.stones else move.stones.reversed()) + dest + for (stone in stones) { + leapTo(stone, AgilityAnims.STEPPING_STONE, ticks = 1) + } + } + } + } +} diff --git a/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/shortcuts/AgilityShortcuts.kt b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/shortcuts/AgilityShortcuts.kt new file mode 100644 index 000000000..a82e06aa0 --- /dev/null +++ b/content/skills/agility/src/main/kotlin/org/rsmod/content/skills/agility/shortcuts/AgilityShortcuts.kt @@ -0,0 +1,422 @@ +package org.rsmod.content.skills.agility.shortcuts + +import org.rsmod.content.skills.agility.AgilityAnims +import org.rsmod.content.skills.agility.shortcuts.ShortcutMove.Balance +import org.rsmod.content.skills.agility.shortcuts.ShortcutMove.Climb +import org.rsmod.content.skills.agility.shortcuts.ShortcutMove.Hop +import org.rsmod.content.skills.agility.shortcuts.ShortcutMove.Jump +import org.rsmod.content.skills.agility.shortcuts.ShortcutMove.Squeeze +import org.rsmod.map.CoordGrid + +/** + * Agility shortcuts, in level order. Sides were read from the game map: each is the tile carrying + * the shortcut's map icon or the first free tile past the loc on that side. Shortcuts gated behind + * quests, diaries or grapples are left out until those systems exist. + */ +object AgilityShortcuts { + private fun tile(x: Int, z: Int, level: Int = 0): CoordGrid = CoordGrid(x, z, level) + + private val crawl = Squeeze() + private val tunnel = ShortcutMove.Tunnel() + private val pipe = ShortcutMove.PIPE + private val climbOver = Jump(AgilityAnims.WALL_CLIMB_OVER) + + val all: List = + listOf( + AgilityShortcut( + name = "Crumbling wall", + level = 5, + xp = 0.5, + locs = listOf("loc.fai_falador_castle_crumble_mid"), + sideA = tile(2934, 3355), + sideB = tile(2936, 3355), + move = climbOver, + ), + AgilityShortcut( + name = "Fence", + level = 13, + xp = 0.0, + locs = listOf("loc.lumbridge_sc_fencejump"), + sideA = tile(3240, 3335), + sideB = tile(3240, 3334), + move = Jump(), + ), + AgilityShortcut( + name = "Underwall tunnel", + level = 16, + xp = 0.0, + locs = listOf("loc.yanille_castlehole_sc", "loc.yanille_castlewall_sc"), + sideA = tile(2575, 3112), + sideB = tile(2575, 3107), + move = tunnel, + ), + AgilityShortcut( + name = "Crack", + level = 17, + xp = 0.0, + locs = listOf("loc.zeah_cata_crack"), + sideA = tile(1646, 10000), + sideB = tile(1648, 10009), + move = crawl, + ), + AgilityShortcut( + name = "Broken window", + level = 18, + xp = 3.0, + locs = listOf("loc.slayertower_window_shortcut_through"), + sideA = tile(3444, 3533), + sideB = tile(3442, 3531), + move = climbOver, + ), + AgilityShortcut( + name = "Log balance", + level = 20, + xp = 8.5, + locs = listOf("loc.mine_log_balance1"), + sideA = tile(2598, 3477), + sideB = tile(2603, 3477), + move = Balance(listOf(tile(2599, 3477), tile(2600, 3477), tile(2601, 3477), tile(2602, 3477))), + ), + AgilityShortcut( + name = "Underwall tunnel", + level = 21, + xp = 0.0, + locs = listOf("loc.varrock_sc_tunnel_west", "loc.varrock_sc_tunnel_east"), + sideA = tile(3137, 3517), + sideB = tile(3142, 3512), + move = tunnel, + ), + AgilityShortcut( + name = "Underwall tunnel", + level = 26, + xp = 0.0, + locs = listOf("loc.falador_sc_castlewall_south", "loc.falador_sc_castlewall_north"), + sideA = tile(2948, 3309), + sideB = tile(2948, 3313), + move = tunnel, + ), + AgilityShortcut( + name = "Rocks", + level = 29, + xp = 0.0, + locs = listOf("loc.mount_karuulm_shortcut_rocks_low"), + sideA = tile(1324, 3777), + sideB = tile(1324, 3785), + move = Climb(), + ), + AgilityShortcut( + name = "Stepping stones", + level = 30, + xp = 3.0, + locs = listOf("loc.zqrockjump1", "loc.zqrockjump3"), + sideA = tile(2925, 2951), + sideB = tile(2925, 2947), + move = Hop(listOf(tile(2925, 2950), tile(2925, 2949), tile(2925, 2948))), + ), + AgilityShortcut( + name = "Stepping stone", + level = 31, + xp = 3.0, + locs = listOf("loc.lumbridge_sc_stepstone"), + sideA = tile(3149, 3363), + sideB = tile(3154, 3363), + move = Hop(listOf(tile(3151, 3363))), + ), + AgilityShortcut( + name = "Log balance", + level = 33, + xp = 4.0, + locs = listOf("loc.ardougne_log_balance_left_sc", "loc.ardougne_log_balance_right_sc"), + sideA = tile(2598, 3336), + sideB = tile(2602, 3336), + move = Balance(listOf(tile(2599, 3336), tile(2600, 3336), tile(2601, 3336))), + ), + AgilityShortcut( + name = "Crack", + level = 34, + xp = 0.0, + locs = listOf("loc.zeah_cata_crack"), + sideA = tile(1706, 10078), + sideB = tile(1716, 10056), + move = crawl, + ), + AgilityShortcut( + name = "Rocks", + level = 37, + xp = 0.0, + locs = listOf("loc.gnome_stronghold_sc_rock_top", "loc.gnome_stronghold_sc_rock_bottom"), + sideA = tile(2486, 3515), + sideB = tile(2489, 3521), + move = Climb(), + ), + AgilityShortcut( + name = "Rocks", + level = 38, + xp = 0.0, + locs = listOf("loc.alkharid_mine_sc_top", "loc.alkharid_mine_sc_bottom"), + sideA = tile(3306, 3315), + sideB = tile(3302, 3315), + move = Climb(), + ), + AgilityShortcut( + name = "Narrow crevice", + level = 42, + xp = 0.0, + locs = listOf("loc.dwarf_mines_sc_wall_crack"), + sideA = tile(3028, 9806), + sideB = tile(3035, 9806), + move = crawl, + ), + AgilityShortcut( + name = "Underwall tunnel", + level = 42, + xp = 0.0, + locs = listOf("loc.draynor_diary_under_wall_w", "loc.draynor_diary_under_wall_e"), + sideA = tile(3066, 3257), + sideB = tile(3070, 3257), + move = tunnel, + ), + AgilityShortcut( + name = "Log balance", + level = 48, + xp = 4.0, + locs = listOf("loc.slayer_river_sc_logbalance_1", "loc.slayer_river_sc_logbalance_3"), + sideA = tile(2722, 3596), + sideB = tile(2722, 3592), + move = Balance(listOf(tile(2722, 3595), tile(2722, 3594), tile(2722, 3593))), + ), + AgilityShortcut( + name = "Obstacle pipe", + level = 51, + xp = 10.0, + locs = listOf("loc.varrock_dungeon_pipe_sc"), + sideA = tile(3149, 9906), + sideB = tile(3155, 9906), + move = pipe, + ), + AgilityShortcut( + name = "Broken fence", + level = 57, + xp = 0.0, + locs = listOf("loc.viking_pike_defence_broken"), + sideA = tile(2688, 3697), + sideB = tile(2691, 3697), + move = Jump(), + ), + AgilityShortcut( + name = "Tunnel", + level = 60, + xp = 0.0, + locs = + listOf( + "loc.cavewall_shortcut_royal_titans_east", + "loc.cavewall_shortcut_royal_titans_west", + ), + sideA = tile(2991, 9546), + sideB = tile(2968, 9550), + move = crawl, + ), + AgilityShortcut( + name = "Spikey chain", + level = 61, + xp = 0.0, + locs = listOf("loc.slayertower_sc_chainbottom", "loc.slayertower_sc_chaintop"), + sideA = tile(3421, 3550, 0), + sideB = tile(3421, 3550, 1), + move = Climb(AgilityAnims.CLIMB_LADDER), + ), + AgilityShortcut( + name = "Narrow crevice", + level = 61, + xp = 0.0, + locs = listOf("loc.slayer_dungeon_2_sc_wall_crack"), + sideA = tile(2730, 10008), + sideB = tile(2735, 10008), + move = crawl, + ), + AgilityShortcut( + name = "Rocks", + level = 62, + xp = 0.0, + locs = listOf("loc.mount_karuulm_shortcut_rocks"), + sideA = tile(1324, 3787), + sideB = tile(1324, 3795), + move = Climb(), + ), + AgilityShortcut( + name = "Loose railing", + level = 63, + xp = 0.0, + locs = listOf("loc.deepdungeonlooserailing"), + sideA = tile(2935, 9810), + sideB = tile(2936, 9810), + move = crawl, + ), + AgilityShortcut( + name = "Rocks", + level = 68, + xp = 0.0, + locs = listOf("loc.ice_mountain_shortcut_top", "loc.ice_mountain_shortcut_bottom"), + sideA = tile(2997, 3483), + sideB = tile(3002, 3483), + move = Climb(), + ), + AgilityShortcut( + name = "Obstacle pipe", + level = 70, + xp = 10.0, + locs = listOf("loc.taverly_dungeon_pipe_sc"), + sideA = tile(2886, 9799), + sideB = tile(2892, 9799), + move = pipe, + ), + AgilityShortcut( + name = "Spikey chain", + level = 71, + xp = 0.0, + locs = listOf("loc.slayertower_sc_chainbottom", "loc.slayertower_sc_chaintop"), + sideA = tile(3446, 3576, 1), + sideB = tile(3446, 3576, 2), + move = Climb(AgilityAnims.CLIMB_LADDER), + ), + AgilityShortcut( + name = "Tunnel", + level = 72, + xp = 0.0, + locs = listOf("loc.slayer_cave_tunnel_right", "loc.slayer_cave_tunnel_left"), + sideA = tile(2429, 9806), + sideB = tile(2435, 9806), + move = crawl, + ), + AgilityShortcut( + name = "Tunnel", + level = 72, + xp = 0.0, + locs = listOf("loc.cavewall_shortcut_wyvern_north", "loc.cavewall_shortcut_wyvern_south"), + sideA = tile(3025, 9571), + sideB = tile(3034, 9558), + move = crawl, + ), + AgilityShortcut( + name = "Dry stone wall", + level = 72, + xp = 25.0, + locs = listOf("loc.barrows_drystonewall_shortcut"), + sideA = tile(3545, 3282), + sideB = tile(3546, 3282), + move = Jump(), + ), + AgilityShortcut( + name = "Crevice", + level = 77, + xp = 10.0, + locs = listOf("loc.wilderness_slayer_cave_crevice"), + sideA = tile(3334, 10118), + sideB = tile(3334, 10122), + move = crawl, + ), + AgilityShortcut( + name = "Crevice", + level = 77, + xp = 10.0, + locs = listOf("loc.wilderness_slayer_cave_crevice"), + sideA = tile(3342, 10148), + sideB = tile(3342, 10154), + move = crawl, + ), + AgilityShortcut( + name = "Crevice", + level = 77, + xp = 10.0, + locs = listOf("loc.wilderness_slayer_cave_crevice"), + sideA = tile(3434, 10090), + sideB = tile(3434, 10094), + move = crawl, + ), + AgilityShortcut( + name = "Crevice", + level = 77, + xp = 10.0, + locs = listOf("loc.wilderness_slayer_cave_crevice"), + sideA = tile(3435, 10111), + sideB = tile(3435, 10116), + move = crawl, + ), + AgilityShortcut( + name = "Tight gap", + level = 78, + xp = 1.0, + locs = listOf("loc.prif_slayer_dungeon_shortcut_1a", "loc.prif_slayer_dungeon_shortcut_1b"), + sideA = tile(3222, 12441), + sideB = tile(3216, 12441), + move = crawl, + ), + AgilityShortcut( + name = "Strange floor", + level = 80, + xp = 12.5, + locs = listOf("loc.taverly_dungeon_floor_spikes_sc"), + sideA = tile(2878, 9813), + sideB = tile(2880, 9813), + move = Jump(AgilityAnims.JUMP), + ), + AgilityShortcut( + name = "Tunnel", + level = 82, + xp = 0.0, + locs = listOf("loc.cavewall_shortcut_wyvern_west", "loc.cavewall_shortcut_wyvern_east"), + sideA = tile(3013, 9549), + sideB = tile(3024, 9553), + move = crawl, + ), + AgilityShortcut( + name = "Tight gap", + level = 84, + xp = 1.5, + locs = listOf("loc.prif_slayer_dungeon_shortcut_2a", "loc.prif_slayer_dungeon_shortcut_2b"), + sideA = tile(3242, 12420), + sideB = tile(3232, 12420), + move = crawl, + ), + AgilityShortcut( + name = "Rocks", + level = 84, + xp = 0.0, + locs = listOf("loc.crandor_shortcut_top", "loc.crandor_shortcut_bottom"), + sideA = tile(2832, 3252), + sideB = tile(2832, 3247), + move = Climb(), + ), + AgilityShortcut( + name = "Rocks", + level = 85, + xp = 0.0, + locs = + listOf( + "loc.dagannoth_waterbirth_rock_climb_agility_shortcut_bottom", + "loc.dagannoth_waterbirth_rock_climb_agility_shortcut_top", + ), + sideA = tile(2547, 3749), + sideB = tile(2547, 3744), + move = Climb(), + ), + AgilityShortcut( + name = "Mysterious pipe", + level = 88, + xp = 0.0, + locs = listOf("loc.karuulm_dungeon_shortcut_pipe_active"), + sideA = tile(1316, 10213), + sideB = tile(1346, 10232), + move = pipe, + ), + AgilityShortcut( + name = "Crevice", + level = 96, + xp = 7.5, + locs = listOf("loc.legends_quest_cave_shortcut"), + sideA = tile(2764, 9339), + sideB = tile(2770, 9340), + move = crawl, + ), + ) +} diff --git a/content/skills/agility/src/test/kotlin/org/rsmod/content/skills/agility/AgilityShortcutsTest.kt b/content/skills/agility/src/test/kotlin/org/rsmod/content/skills/agility/AgilityShortcutsTest.kt new file mode 100644 index 000000000..648157f1d --- /dev/null +++ b/content/skills/agility/src/test/kotlin/org/rsmod/content/skills/agility/AgilityShortcutsTest.kt @@ -0,0 +1,57 @@ +package org.rsmod.content.skills.agility + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.rsmod.content.skills.agility.shortcuts.AgilityShortcuts +import org.rsmod.content.skills.agility.shortcuts.ShortcutMove +import org.rsmod.map.CoordGrid + +class AgilityShortcutsTest { + @Test + fun `shortcuts are listed in level order with sane requirements`() { + val levels = AgilityShortcuts.all.map { it.level } + assertEquals(levels.sorted(), levels, "shortcuts should be in level order") + assertTrue(levels.all { it in 1..99 }) + assertTrue(AgilityShortcuts.all.all { it.xp >= 0.0 }) + assertTrue(AgilityShortcuts.all.flatMap { it.locs }.all { it.startsWith("loc.") }) + } + + @Test + fun `balance and hop paths connect the two sides tile by tile`() { + for (shortcut in AgilityShortcuts.all) { + val tiles = + when (val move = shortcut.move) { + is ShortcutMove.Balance -> move.tiles + is ShortcutMove.Hop -> move.stones + else -> continue + } + assertTrue(tiles.isNotEmpty(), "${shortcut.name} has an empty path") + val full = listOf(shortcut.sideA) + tiles + shortcut.sideB + val maxStep = if (shortcut.move is ShortcutMove.Hop) 3 else 1 + for (index in 1 until full.size) { + val step = full[index - 1].chebyshevDistance(full[index]) + assertTrue(step in 1..maxStep, "${shortcut.name}: step $step from ${full[index - 1]}") + } + } + } + + @Test + fun `shared locs are told apart by distance`() { + val cracks = AgilityShortcuts.all.filter { "loc.zeah_cata_crack" in it.locs } + assertEquals(2, cracks.size) + val nearSouthWest = CoordGrid(1646, 10000, 0) + val chosen = cracks.minBy { it.distanceTo(nearSouthWest) } + assertEquals(17, chosen.level) + assertTrue(chosen.startsFromA(nearSouthWest)) + assertFalse(chosen.startsFromA(CoordGrid(1648, 10010, 0))) + } + + @Test + fun `level changes count as far away when picking a side`() { + val chain = AgilityShortcuts.all.first { it.name == "Spikey chain" && it.level == 61 } + assertTrue(chain.startsFromA(CoordGrid(3421, 3551, 0))) + assertFalse(chain.startsFromA(CoordGrid(3421, 3551, 1))) + } +} diff --git a/content/skills/agility/src/test/kotlin/org/rsmod/content/skills/agility/RooftopCoursesTest.kt b/content/skills/agility/src/test/kotlin/org/rsmod/content/skills/agility/RooftopCoursesTest.kt new file mode 100644 index 000000000..fdc5cd99a --- /dev/null +++ b/content/skills/agility/src/test/kotlin/org/rsmod/content/skills/agility/RooftopCoursesTest.kt @@ -0,0 +1,124 @@ +package org.rsmod.content.skills.agility + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.rsmod.content.skills.agility.rooftop.ObstacleMove +import org.rsmod.content.skills.agility.rooftop.RooftopCourse +import org.rsmod.content.skills.agility.rooftop.RooftopCourses +import org.rsmod.map.CoordGrid + +class RooftopCoursesTest { + @Test + fun `every course has a layout and every layout ends with its only finish obstacle`() { + for (course in RooftopCourse.entries) { + val layout = RooftopCourses.layout(course) + assertTrue(layout.obstacles.size >= 6, "$course has too few obstacles") + val finishes = layout.obstacles.filter { it.isFinish } + assertEquals(1, finishes.size, "$course must have exactly one finish obstacle") + assertTrue(layout.obstacles.last().isFinish, "$course finish must be the last obstacle") + assertTrue(layout.markTiles.isNotEmpty(), "$course needs mark of grace tiles") + } + } + + @Test + fun `lap experience matches the wiki totals`() { + val expected = + mapOf( + RooftopCourse.Draynor to 120.0, + RooftopCourse.AlKharid to 216.0, + RooftopCourse.Varrock to 269.7, + RooftopCourse.Canifis to 240.0, + RooftopCourse.Falador to 586.0, + RooftopCourse.Seers to 570.0, + RooftopCourse.Pollnivneach to 890.0, + RooftopCourse.Rellekka to 780.0, + RooftopCourse.Ardougne to 889.0, + ) + for ((course, xp) in expected) { + assertEquals(xp, RooftopCourses.layout(course).lapXp, 0.01, "$course lap xp") + } + } + + @Test + fun `courses start on the ground and finish on the ground`() { + for (layout in RooftopCourses.layouts) { + val first = layout.obstacles.first() + val last = layout.obstacles.last() + assertEquals(0, first.start.level, "${layout.course} should start at ground level") + assertEquals(0, last.move.destination.level, "${layout.course} should end at ground level") + } + } + + @Test + fun `every move leaves its start tile and balance paths step one tile at a time`() { + for (layout in RooftopCourses.layouts) { + for (obstacle in layout.obstacles) { + val label = "${layout.course} ${obstacle.name}" + assertNotEquals(obstacle.start, obstacle.move.destination, "$label does not move") + val move = obstacle.move + if (move is ObstacleMove.Balance) { + var previous = obstacle.start + for (tile in move.path) { + assertTrue( + previous.chebyshevDistance(tile) <= 1 || previous.level != tile.level, + "$label path jumps from $previous to $tile", + ) + previous = tile + } + } + obstacle.failure?.let { failure -> + assertEquals(0, failure.landing.level, "$label should fall to the ground") + assertTrue(failure.noFailLevel > layout.course.level, "$label no-fail level") + assertTrue(failure.minDamage in 1..failure.maxDamage, "$label damage range") + } + } + } + } + + @Test + fun `obstacles chain together without teleporting across the map`() { + for (layout in RooftopCourses.layouts) { + val obstacles = layout.obstacles + for (index in 1 until obstacles.size) { + val landed = obstacles[index - 1].move.destination + val next = obstacles[index].start + assertTrue( + landed.chebyshevDistance(next) <= 20, + "${layout.course}: landing $landed is too far from next start $next", + ) + } + } + } + + @Test + fun `locs are unique across all courses`() { + val locs = RooftopCourses.layouts.flatMap { it.obstacles }.flatMap { it.locs } + assertEquals(locs.size, locs.toSet().size, "duplicate obstacle locs: $locs") + assertTrue(locs.all { it.startsWith("loc.rooftops_") }) + } + + @Test + fun `line spells out straight and diagonal paths`() { + val straight = line(CoordGrid(10, 10, 3), CoordGrid(10, 7, 3)) + assertEquals(listOf(CoordGrid(10, 9, 3), CoordGrid(10, 8, 3), CoordGrid(10, 7, 3)), straight) + + val mixed = line(CoordGrid(0, 0, 1), CoordGrid(2, 3, 1)) + assertEquals( + listOf(CoordGrid(1, 1, 1), CoordGrid(2, 2, 1), CoordGrid(2, 3, 1)), + mixed, + ) + assertFalse(line(CoordGrid(5, 5, 0), CoordGrid(5, 5, 0)).isNotEmpty()) + } + + @Test + fun `success chance rises to certainty at the no-fail level`() { + assertEquals(60, successChance(level = 20, requiredLevel = 20, noFailLevel = 30)) + assertEquals(80, successChance(level = 25, requiredLevel = 20, noFailLevel = 30)) + assertEquals(100, successChance(level = 30, requiredLevel = 20, noFailLevel = 30)) + assertEquals(100, successChance(level = 99, requiredLevel = 20, noFailLevel = 30)) + assertEquals(60, successChance(level = 1, requiredLevel = 20, noFailLevel = 30)) + } +} diff --git a/content/skills/magic/spell-attacks/build.gradle.kts b/content/skills/magic/spell-attacks/build.gradle.kts index 262cfefda..231eed95b 100644 --- a/content/skills/magic/spell-attacks/build.gradle.kts +++ b/content/skills/magic/spell-attacks/build.gradle.kts @@ -3,6 +3,7 @@ plugins { } dependencies { + implementation(projects.api.attr) implementation(projects.api.combat.combatManager) implementation(projects.api.pluginCommons) implementation(projects.api.spells) diff --git a/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/EffectSpellAttack.kt b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/EffectSpellAttack.kt new file mode 100644 index 000000000..ac39a97e8 --- /dev/null +++ b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/EffectSpellAttack.kt @@ -0,0 +1,231 @@ +package org.rsmod.content.skills.magic.spell.attacks + +import kotlin.math.max +import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.api.combat.commons.CombatEffects +import org.rsmod.api.combat.commons.npc.queueCombatRetaliate +import org.rsmod.api.combat.commons.player.queueCombatRetaliate +import org.rsmod.api.combat.manager.MagicRuneManager.Companion.isFailure +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.magicLvl +import org.rsmod.api.player.stat.stat +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.player.stat.statSub +import org.rsmod.api.spells.attack.SpellAttack +import org.rsmod.api.spells.attack.SpellAttackManager +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PathingEntity +import org.rsmod.game.entity.Player +import org.rsmod.game.type.getOrNull + +/** + * A single-target spell that rolls magic accuracy, plays the book's graphics and sounds, and + * applies an effect when it lands. Covers everything from a curse (no damage, only the effect) to + * a god spell (damage and an effect). + * + * @param baseMaxHit the max hit at the caster's Magic level, or `0` for a spell that deals no + * damage. A damaging spell shows a hitsplat; a curse shows only its impact graphic. + * @param targetCheck rejects targets the spell does not affect (Crumble Undead on the living, a + * demonbane on a goblin); the returned message is shown and the cast is cancelled. + * @param onLand runs when the spell lands, with the damage dealt (`0` for a curse). + */ +class EffectSpellAttack( + private val manager: SpellAttackManager, + private val staffAnim: String, + private val unarmedAnim: String = staffAnim, + private val launch: String?, + private val travel: String?, + private val projanim: String = "projanim.magic_spell", + private val impact: String?, + private val impactHeight: Int = 124, + private val castSound: String?, + private val hitSound: String?, + private val baseMaxHit: (magicLvl: Int) -> Int = { 0 }, + private val targetCheck: (PathingEntity) -> String? = { null }, + private val onLand: ProtectedAccess.(target: PathingEntity, damage: Int) -> Unit = { _, _ -> }, +) : SpellAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Spell) { + cast(target, attack) + } + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Spell) { + cast(target, attack) + } + + private fun ProtectedAccess.cast(target: PathingEntity, attack: CombatAttack.Spell) { + val rejection = targetCheck(target) + if (rejection != null) { + manager.stopCombat(this) + mes(rejection) + return + } + val castResult = manager.attemptCast(this, attack) + if (castResult.isFailure()) { + return + } + val weaponType = getOrNull(attack.weapon) + val castAnim = + if (weaponType != null && weaponType.isCategoryType("category.staff")) staffAnim + else unarmedAnim + player.anim(castAnim, priority = 6) + launch?.let { spotanim(it, height = LAUNCH_HEIGHT) } + + val clientDelay: Int + val serverDelay: Int + if (travel != null) { + val proj = manager.spawnProjectile(this, target, travel, projanim) + clientDelay = proj.clientCycles + serverDelay = proj.serverCycles + } else { + clientDelay = NO_PROJECTILE_CLIENT_DELAY + serverDelay = NO_PROJECTILE_SERVER_DELAY + } + val spell = attack.spell.obj + + val splash = manager.rollSplash(this, target, attack, castResult) + if (splash) { + manager.playSplashFx(this, target, clientDelay, castSound, soundRadius = SOUND_RADIUS) + manager.queueSplashHit(this, target, spell, clientDelay, serverDelay) + manager.continueCombatIfAutocast(this, target) + return + } + + val maxHit = baseMaxHit(player.magicLvl) + val damage = if (maxHit > 0) manager.rollMaxHit(this, target, attack, castResult, maxHit) else 0 + manager.playHitFx( + source = this, + target = target, + clientDelay = clientDelay, + castSound = castSound, + soundRadius = SOUND_RADIUS, + hitSpot = impact, + hitSpotHeight = impactHeight, + hitSound = hitSound, + ) + if (maxHit > 0) { + manager.giveCombatXp(this, target, attack, damage) + manager.queueMagicHit(this, target, spell, damage, clientDelay, serverDelay) + } else { + // No hitsplat for a curse, but the target still turns on the caster. + retaliate(target, serverDelay) + } + onLand(this, target, damage) + manager.continueCombatIfAutocast(this, target) + } + + private fun ProtectedAccess.retaliate(target: PathingEntity, delay: Int) { + when (target) { + is Npc -> target.queueCombatRetaliate(player, delay = max(1, delay)) + is Player -> target.queueCombatRetaliate(player, delay = max(1, delay)) + } + } + + companion object { + const val LAUNCH_HEIGHT: Int = 92 + const val SOUND_RADIUS: Int = 8 + private const val NO_PROJECTILE_CLIENT_DELAY = 30 + private const val NO_PROJECTILE_SERVER_DELAY = 2 + } +} + +/** Effects shared by the curse, bind, god and Arceuus spells. */ +object SpellEffects { + const val ATTACK: String = "stat.attack" + const val STRENGTH: String = "stat.strength" + const val DEFENCE: String = "stat.defence" + const val MAGIC: String = "stat.magic" + const val PRAYER: String = "stat.prayer" + + /** + * Lowers [stat] by [percent] of the target's base level, only when the stat is still at (or + * above) its base: curses do not stack. + */ + fun drainPercent(target: PathingEntity, stat: String, percent: Int) { + when (target) { + is Player -> { + if (target.stat(stat) >= target.statBase(stat)) { + target.statSub(stat, constant = 0, percent = percent) + } + } + is Npc -> { + val base = npcBase(target, stat) + if (npcCurrent(target, stat) >= base) { + setNpcCurrent(target, stat, max(0, npcCurrent(target, stat) - (base * percent) / 100)) + } + } + } + } + + /** Lowers [stat] by a flat [amount]; used by Saradomin Strike's prayer drain. */ + fun drainFlat(target: PathingEntity, stat: String, amount: Int) { + when (target) { + is Player -> target.statSub(stat, constant = amount, percent = 0) + is Npc -> setNpcCurrent(target, stat, max(0, npcCurrent(target, stat) - amount)) + } + } + + /** Holds the target in place: players via the combat freeze, npcs via their movement lock. */ + fun freeze(target: PathingEntity, ticks: Int) { + when (target) { + is Player -> CombatEffects.freeze(target, ticks) + is Npc -> NpcFreeze.freeze(target, ticks) + } + } + + fun isUndead(npc: Npc): Boolean = UNDEAD.containsMatchIn(npc.type.name.lowercase()) + + fun isDemon(npc: Npc): Boolean = DEMON.containsMatchIn(npc.type.name.lowercase()) + + private val UNDEAD = + Regex("skeleton|zombie|ghost|ghast|shade|mummy|revenant|ankou|banshee|wight|undead|zogre|skogre|spectre|lich|crawling hand|bloodveld") + private val DEMON = + Regex("demon|imp\\b|nechryael|bloodveld|abyssal|tormented|balfrug|zakl|k'ril|tstanon|hellhound|jungle horror|cerberus|skotizo|porazdir|derwen|duke sucellus") + + private fun npcCurrent(npc: Npc, stat: String): Int = + when (stat) { + ATTACK -> npc.attackLvl + STRENGTH -> npc.strengthLvl + DEFENCE -> npc.defenceLvl + MAGIC -> npc.magicLvl + else -> 0 + } + + private fun npcBase(npc: Npc, stat: String): Int = + when (stat) { + ATTACK -> npc.baseAttackLvl + STRENGTH -> npc.baseStrengthLvl + DEFENCE -> npc.baseDefenceLvl + MAGIC -> npc.baseMagicLvl + else -> 0 + } + + private fun setNpcCurrent(npc: Npc, stat: String, value: Int) { + when (stat) { + ATTACK -> npc.attackLvl = value + STRENGTH -> npc.strengthLvl = value + DEFENCE -> npc.defenceLvl = value + MAGIC -> npc.magicLvl = value + } + } +} + +/** + * Npcs have no freeze mechanic of their own; a frozen npc has its movement locked until the + * combat freeze timer, handled in [SpellEffectsScript], releases it. + */ +object NpcFreeze { + const val TIMER: String = "timer.combat_freeze" + + fun freeze(npc: Npc, ticks: Int) { + if (npc.movementLocked) { + return + } + npc.movementLocked = true + npc.timer(TIMER, ticks) + } + + fun release(npc: Npc) { + npc.movementLocked = false + npc.clearTimer(TIMER) + } +} diff --git a/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/SpellAttacksModule.kt b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/SpellAttacksModule.kt index 494e37269..f3f730a58 100644 --- a/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/SpellAttacksModule.kt +++ b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/SpellAttacksModule.kt @@ -1,11 +1,21 @@ package org.rsmod.content.skills.magic.spell.attacks import org.rsmod.api.spells.attack.SpellAttackMap +import org.rsmod.content.skills.magic.spell.attacks.ancient.AncientSpells +import org.rsmod.content.skills.magic.spell.attacks.arceuus.ArceuusSpells +import org.rsmod.content.skills.magic.spell.attacks.lunar.VengeanceOtherSpell +import org.rsmod.content.skills.magic.spell.attacks.standard.CurseSpells import org.rsmod.content.skills.magic.spell.attacks.standard.ElementalSpells +import org.rsmod.content.skills.magic.spell.attacks.standard.StandardCombatSpells import org.rsmod.plugin.module.PluginModule class SpellAttacksModule : PluginModule() { override fun bind() { addSetBinding(ElementalSpells::class.java) + addSetBinding(AncientSpells::class.java) + addSetBinding(CurseSpells::class.java) + addSetBinding(StandardCombatSpells::class.java) + addSetBinding(ArceuusSpells::class.java) + addSetBinding(VengeanceOtherSpell::class.java) } } diff --git a/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/ancient/AncientSpells.kt b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/ancient/AncientSpells.kt new file mode 100644 index 000000000..46b16b6f6 --- /dev/null +++ b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/ancient/AncientSpells.kt @@ -0,0 +1,236 @@ +package org.rsmod.content.skills.magic.spell.attacks.ancient + +import dev.openrune.types.hunt.HuntVis +import jakarta.inject.Inject +import kotlin.math.max +import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.api.combat.manager.MagicRuneManager +import org.rsmod.api.combat.manager.MagicRuneManager.Companion.isFailure +import org.rsmod.api.hunt.NpcSearch +import org.rsmod.api.mechanics.toxins.impl.PlayerPoison +import org.rsmod.api.npc.isValidTarget +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.stat +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.player.stat.statHeal +import org.rsmod.api.player.stat.statSub +import org.rsmod.api.spells.attack.SpellAttack +import org.rsmod.api.spells.attack.SpellAttackManager +import org.rsmod.api.spells.attack.SpellAttackMap +import org.rsmod.api.spells.attack.SpellAttackRepository +import org.rsmod.content.skills.magic.spell.attacks.SpellEffects +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PathingEntity +import org.rsmod.game.entity.Player + +/** + * The Ancient Magicks combat spells: smoke, shadow, blood and ice; rush, burst, blitz and barrage. + * + * Every spell is a magic attack with the book's own casting animation, projectile and impact + * graphics. Bursts and barrages are area spells: in a multi-combat area they also hit every + * attackable npc standing next to the target, up to nine targets in all. The element decides the + * effect of a hit that lands: + * - **Smoke** poisons the target (2 damage from a rush or burst, 4 from a blitz or barrage). + * - **Shadow** lowers the target's Attack (10% from a rush or burst, 15% from a blitz or barrage). + * - **Blood** heals the caster for a quarter of the damage dealt. + * - **Ice** freezes the target in place (5, 10, 15 or 20 seconds by tier). + */ +class AncientSpells @Inject constructor(private val npcSearch: NpcSearch) : SpellAttackMap { + override fun SpellAttackRepository.register(manager: SpellAttackManager) { + for (tier in Tier.entries) { + for (element in Element.entries) { + register( + spell = "obj.${element.spellObj(tier)}", + attack = AncientSpellAttack(manager, npcSearch, element, tier), + ) + } + } + } + + private enum class Tier( + val name0: String, + val castAnim: String, + val maxHitOffset: Int, + val area: Boolean, + val freezeTicks: Int, + val poisonDamage: Int, + val attackDrainPercent: Int, + ) { + Rush("rush", "seq.zaros_casting", 0, false, 8, 2, 10), + Burst("burst", "seq.zaros_vertical_casting", 4, true, 16, 2, 10), + Blitz("blitz", "seq.zaros_casting", 10, false, 25, 4, 15), + Barrage("barrage", "seq.zaros_vertical_casting", 14, true, 33, 4, 15), + } + + private enum class Element(val name0: String, val baseMaxHit: Int) { + Smoke("smoke", 13), + Shadow("shadow", 14), + Blood("blood", 15), + Ice("ice", 16); + + /** `50_smoke_rush`, `94_ice_barrage`: the spell obj's gameval name. */ + fun spellObj(tier: Tier): String { + val level = SPELL_LEVELS.getValue(tier).getValue(this) + return "${level}_${name0}_${tier.name0}" + } + + fun maxHit(tier: Tier): Int = baseMaxHit + tier.maxHitOffset + + fun travel(tier: Tier): String? = + when { + // Blood and shadow area spells strike from above and have no projectile. + this == Blood && tier.area -> null + this == Shadow && tier.area -> null + else -> "spotanim.${name0}_${tier.name0}_travel" + } + + fun impact(tier: Tier): String = "spotanim.${name0}_${tier.name0}_impact" + + fun castSound(): String = "synth.${name0}_cast" + + fun hitSound(tier: Tier): String = "synth.${name0}_${tier.name0}_impact" + } + + private class AncientSpellAttack( + private val manager: SpellAttackManager, + private val npcSearch: NpcSearch, + private val element: Element, + private val tier: Tier, + ) : SpellAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Spell) { + cast(target, attack) + } + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Spell) { + cast(target, attack) + } + + private fun ProtectedAccess.cast(target: PathingEntity, attack: CombatAttack.Spell) { + val castResult = manager.attemptCast(this, attack) + if (castResult.isFailure()) { + return + } + player.anim(tier.castAnim, priority = 6) + + val travel = element.travel(tier) + val clientDelay: Int + val serverDelay: Int + if (travel != null) { + val proj = manager.spawnProjectile(this, target, travel, "projanim.magic_spell") + clientDelay = proj.clientCycles + serverDelay = proj.serverCycles + } else { + clientDelay = NO_PROJECTILE_CLIENT_DELAY + serverDelay = NO_PROJECTILE_SERVER_DELAY + } + + strike(target, attack, castResult, clientDelay, serverDelay, primary = true) + + if (tier.area && mapMultiway()) { + val others = + npcSearch + .findAllAny(target.coords, distance = 1, vis = HuntVis.Off) + .filter { it != target && it.isValidTarget() && it.type.hasOp(2) } + .take(MAX_AREA_TARGETS - 1) + for (npc in others) { + strike(npc, attack, castResult, clientDelay, serverDelay, primary = false) + } + } + manager.continueCombatIfAutocast(this, target) + } + + /** Rolls the spell against one target and applies the element's effect if it lands. */ + private fun ProtectedAccess.strike( + target: PathingEntity, + attack: CombatAttack.Spell, + castResult: MagicRuneManager.CastResult, + clientDelay: Int, + serverDelay: Int, + primary: Boolean, + ) { + val spell = attack.spell.obj + val castSound = if (primary) element.castSound() else null + val splash = manager.rollSplash(this, target, attack, castResult) + if (splash) { + manager.playSplashFx(this, target, clientDelay, castSound, soundRadius = 8) + manager.queueSplashHit(this, target, spell, clientDelay, serverDelay) + return + } + + val damage = manager.rollMaxHit(this, target, attack, castResult, element.maxHit(tier)) + manager.playHitFx( + source = this, + target = target, + clientDelay = clientDelay, + castSound = castSound, + soundRadius = 8, + hitSpot = element.impact(tier), + hitSpotHeight = IMPACT_HEIGHT, + hitSound = element.hitSound(tier), + ) + manager.giveCombatXp(this, target, attack, damage) + manager.queueMagicHit(this, target, spell, damage, clientDelay, serverDelay) + applyEffect(target, damage) + } + + private fun ProtectedAccess.applyEffect(target: PathingEntity, damage: Int) { + when (element) { + Element.Ice -> SpellEffects.freeze(target, tier.freezeTicks) + Element.Blood -> { + val heal = damage / 4 + if (heal > 0) { + player.statHeal("stat.hitpoints", constant = heal, percent = 0) + } + } + Element.Smoke -> + if (damage > 0 && target is Player) { + PlayerPoison.tryPoison(target, initialDamage = tier.poisonDamage) + } + Element.Shadow -> + if (damage > 0) { + drainAttack(target, tier.attackDrainPercent) + } + } + } + + /** Shadow spells lower Attack by a percentage of the base level, and do not stack. */ + private fun drainAttack(target: PathingEntity, percent: Int) { + when (target) { + is Player -> { + if (target.stat(ATTACK) >= target.statBase(ATTACK)) { + target.statSub(ATTACK, constant = 0, percent = percent) + } + } + is Npc -> { + if (target.attackLvl >= target.baseAttackLvl) { + val drain = (target.baseAttackLvl * percent) / 100 + target.attackLvl = max(0, target.attackLvl - drain) + } + } + } + } + } + + private companion object { + private const val ATTACK = "stat.attack" + private const val IMPACT_HEIGHT = 124 + private const val MAX_AREA_TARGETS = 9 + + /** Blood and shadow bursts and barrages have no projectile: the impact lands a moment later. */ + private const val NO_PROJECTILE_CLIENT_DELAY = 30 + private const val NO_PROJECTILE_SERVER_DELAY = 2 + + /** The Magic level in each spell's gameval name, by tier then element. */ + private val SPELL_LEVELS: Map> = + mapOf( + Tier.Rush to + mapOf(Element.Smoke to 50, Element.Shadow to 52, Element.Blood to 56, Element.Ice to 58), + Tier.Burst to + mapOf(Element.Smoke to 62, Element.Shadow to 64, Element.Blood to 68, Element.Ice to 70), + Tier.Blitz to + mapOf(Element.Smoke to 74, Element.Shadow to 76, Element.Blood to 80, Element.Ice to 82), + Tier.Barrage to + mapOf(Element.Smoke to 86, Element.Shadow to 88, Element.Blood to 92, Element.Ice to 94), + ) + } +} diff --git a/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/arceuus/ArceuusSpells.kt b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/arceuus/ArceuusSpells.kt new file mode 100644 index 000000000..4e2210ae3 --- /dev/null +++ b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/arceuus/ArceuusSpells.kt @@ -0,0 +1,99 @@ +package org.rsmod.content.skills.magic.spell.attacks.arceuus + +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCMType +import org.rsmod.api.config.refs.params +import org.rsmod.api.enums.SpellbookEnums +import org.rsmod.api.spells.attack.SpellAttackManager +import org.rsmod.api.spells.attack.SpellAttackMap +import org.rsmod.api.spells.attack.SpellAttackRepository +import org.rsmod.content.skills.magic.spell.attacks.EffectSpellAttack +import org.rsmod.content.skills.magic.spell.attacks.SpellEffects +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.PathingEntity + +/** + * The Arceuus spellbook's attack spells: the three grasps, which damage and hold the target, and + * the three demonbanes, which only affect demons. + * + * The Arceuus spell objs carry stale gameval names in this cache (`obj.dmm_ref_hat` is Ghostly + * Grasp), so the spells are found by the `spell_name` param on the entries of the Arceuus + * spellbook enum rather than by name. + */ +class ArceuusSpells : SpellAttackMap { + override fun SpellAttackRepository.register(manager: SpellAttackManager) { + val byName = + SpellbookEnums.arceuus_spellbook + .filterValuesNotNull() + .values + .associateBy { it.paramOrNull(params.spell_name).orEmpty() } + + fun registerNamed(spellName: String, attack: EffectSpellAttack) { + val obj = byName[spellName] + if (obj == null) { + logger.warn { "Arceuus spell not found in spellbook enum: $spellName" } + return + } + register(RSCM.getReverseMapping(RSCMType.OBJ, obj.id), attack) + } + + registerNamed("Ghostly Grasp", grasp(manager, "ghostly", GHOSTLY_GRASP_MAX_HIT, GHOSTLY_GRASP_TICKS)) + registerNamed("Skeletal Grasp", grasp(manager, "skeletal", SKELETAL_GRASP_MAX_HIT, SKELETAL_GRASP_TICKS)) + registerNamed("Undead Grasp", grasp(manager, "undead", UNDEAD_GRASP_MAX_HIT, UNDEAD_GRASP_TICKS)) + + registerNamed("Inferior Demonbane", demonbane(manager, "inferior", INFERIOR_DEMONBANE_MAX_HIT)) + registerNamed("Superior Demonbane", demonbane(manager, "superior", SUPERIOR_DEMONBANE_MAX_HIT)) + registerNamed("Dark Demonbane", demonbane(manager, "dark", DARK_DEMONBANE_MAX_HIT)) + } + + private fun grasp( + manager: SpellAttackManager, + name: String, + maxHit: Int, + freezeTicks: Int, + ): EffectSpellAttack = + EffectSpellAttack( + manager = manager, + staffAnim = "seq.human_spellcast_grasp", + launch = "spotanim.${name}_grasp_cast_spotanim", + travel = null, + impact = "spotanim.${name}_grasp_hit_spotanim", + impactHeight = 0, + castSound = null, + hitSound = null, + baseMaxHit = { maxHit }, + onLand = { target, damage -> if (damage > 0) SpellEffects.freeze(target, freezeTicks) }, + ) + + private fun demonbane(manager: SpellAttackManager, name: String, maxHit: Int): EffectSpellAttack = + EffectSpellAttack( + manager = manager, + staffAnim = "seq.human_spellcast_demonbane", + launch = "spotanim.${name}_demonbane_cast_spotanim", + travel = null, + impact = "spotanim.${name}_demonbane_hit_spotanim", + impactHeight = 0, + castSound = null, + hitSound = null, + baseMaxHit = { maxHit }, + targetCheck = ::demonsOnly, + ) + + private fun demonsOnly(target: PathingEntity): String? = + if (target is Npc && SpellEffects.isDemon(target)) null else "This spell only affects demons." + + private companion object { + private val logger = InlineLogger() + + private const val GHOSTLY_GRASP_MAX_HIT = 12 + private const val SKELETAL_GRASP_MAX_HIT = 17 + private const val UNDEAD_GRASP_MAX_HIT = 24 + private const val GHOSTLY_GRASP_TICKS = 4 + private const val SKELETAL_GRASP_TICKS = 8 + private const val UNDEAD_GRASP_TICKS = 12 + private const val INFERIOR_DEMONBANE_MAX_HIT = 16 + private const val SUPERIOR_DEMONBANE_MAX_HIT = 23 + private const val DARK_DEMONBANE_MAX_HIT = 30 + } +} diff --git a/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/lunar/LunarSpells.kt b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/lunar/LunarSpells.kt new file mode 100644 index 000000000..71ba40930 --- /dev/null +++ b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/lunar/LunarSpells.kt @@ -0,0 +1,175 @@ +package org.rsmod.content.skills.magic.spell.attacks.lunar + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.combat.commons.CombatAttack +import org.rsmod.api.combat.manager.MagicRuneManager +import org.rsmod.api.combat.manager.MagicRuneManager.Companion.isFailure +import org.rsmod.api.npc.hit.modifier.NpcHitModifier +import org.rsmod.api.npc.hit.queueHit +import org.rsmod.api.player.events.PlayerHitEvents +import org.rsmod.api.player.hit.modifier.NoopPlayerHitModifier +import org.rsmod.api.player.hit.queueHit +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onEvent +import org.rsmod.api.script.onIfOverlayButton +import org.rsmod.api.script.onNpcTimer +import org.rsmod.api.spells.MagicSpellRegistry +import org.rsmod.api.spells.attack.SpellAttack +import org.rsmod.api.spells.attack.SpellAttackManager +import org.rsmod.api.spells.attack.SpellAttackMap +import org.rsmod.api.spells.attack.SpellAttackRepository +import org.rsmod.content.skills.magic.spell.attacks.NpcFreeze +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.NpcList +import org.rsmod.game.entity.Player +import org.rsmod.game.entity.PlayerList +import org.rsmod.game.hit.Hit +import org.rsmod.game.hit.HitType +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The Lunar spellbook's combat spell, Vengeance, and its Vengeance Other. A player armed with + * vengeance returns three quarters of the next damage they take to whoever dealt it, shouting + * "Taste vengeance!"; the spell can be cast again thirty seconds after the last cast. + * + * Vengeance is cast on the caster from the spellbook button; Vengeance Other is cast on another + * player like an attack spell, arming them instead. + */ +class VengeanceScript +@Inject +constructor( + private val spells: MagicSpellRegistry, + private val runes: MagicRuneManager, + private val npcList: NpcList, + private val playerList: PlayerList, +) : PluginScript() { + override fun ScriptContext.startup() { + val vengeance = + ServerCacheManager.getItem(VENGEANCE_OBJ.asRSCM(RSCMType.OBJ))?.let(spells::getObjSpell) + if (vengeance != null) { + onIfOverlayButton(vengeance.component) { castVengeance() } + } + onEvent { onHit(player, hit) } + onNpcTimer(NpcFreeze.TIMER) { NpcFreeze.release(npc) } + } + + private suspend fun ProtectedAccess.castVengeance() { + val spellObj = ServerCacheManager.getItem(VENGEANCE_OBJ.asRSCM(RSCMType.OBJ)) ?: return + val spell = spells.getObjSpell(spellObj) ?: return + if (!Vengeance.canCast(player, mapClock)) { + mes("You can only cast vengeance spells every 30 seconds.") + return + } + val result = runes.attemptCast(player, spell) + if (result.isFailure()) { + return + } + statAdvance("stat.magic", spell.castXp) + anim(VENGEANCE_ANIM) + spotanim(VENGEANCE_SPOT, height = VENGEANCE_SPOT_HEIGHT) + Vengeance.arm(player, mapClock) + } + + private fun onHit(player: Player, hit: Hit) { + if (hit.damage <= 0 || !Vengeance.isArmed(player)) { + return + } + if (hit.type == HitType.Typeless) { + return + } + val reflected = (hit.damage * VENGEANCE_REFLECT_PERCENT) / 100 + if (reflected <= 0) { + return + } + Vengeance.disarm(player) + player.say("Taste vengeance!") + when { + hit.isFromNpc -> { + val npc = hit.resolveNpcSource(npcList) ?: return + npc.queueHit( + source = player, + delay = 1, + type = HitType.Typeless, + damage = reflected, + modifier = NpcHitModifier {}, + ) + } + hit.isFromPlayer -> { + val attacker = hit.resolvePlayerSource(playerList) ?: return + attacker.queueHit( + source = player, + delay = 1, + type = HitType.Typeless, + damage = reflected, + modifier = NoopPlayerHitModifier, + ) + } + } + } + + private companion object { + private const val VENGEANCE_OBJ = "obj.94_vengeance" + private const val VENGEANCE_ANIM = "seq.vengeance_spell_anim_nostalling" + private const val VENGEANCE_SPOT = "spotanim.quest_lunar_spellbook_vengeance_spot_anim" + private const val VENGEANCE_SPOT_HEIGHT = 0 + private const val VENGEANCE_REFLECT_PERCENT = 75 + } +} + +/** Vengeance Other: arms the target player with vengeance, subject to their own cooldown. */ +class VengeanceOtherSpell : SpellAttackMap { + override fun SpellAttackRepository.register(manager: SpellAttackManager) { + register("obj.93_vengeance_other", VengeanceOther(manager)) + } + + private class VengeanceOther(private val manager: SpellAttackManager) : SpellAttack { + override suspend fun ProtectedAccess.attack(target: Npc, attack: CombatAttack.Spell) { + manager.stopCombat(this) + mes("You can only cast that spell on another player.") + } + + override suspend fun ProtectedAccess.attack(target: Player, attack: CombatAttack.Spell) { + manager.stopCombat(this) + if (!Vengeance.canCast(target, mapClock)) { + mes("That player has cast a vengeance spell too recently.") + return + } + val castResult = manager.attemptCast(this, attack) + if (castResult.isFailure()) { + return + } + anim("seq.vengeance_spell_anim_nostalling") + target.spotanim("spotanim.quest_lunar_spellbook_vengeance_other_spot_anim") + Vengeance.arm(target, mapClock) + target.mes("${player.displayName} has cast Vengeance on you.") + } + } +} + +internal object Vengeance { + private val ARMED = AttributeKey(resetOnDeath = true) + private val LAST_CAST_TICK = AttributeKey() + private const val COOLDOWN_TICKS = 50 + + fun canCast(player: Player, mapClock: Int): Boolean { + val last = player.attr[LAST_CAST_TICK] ?: return true + return mapClock - last >= COOLDOWN_TICKS + } + + fun arm(player: Player, mapClock: Int) { + player.attr[ARMED] = true + player.attr[LAST_CAST_TICK] = mapClock + } + + fun isArmed(player: Player): Boolean = player.attr[ARMED] == true + + fun disarm(player: Player) { + player.attr.remove(ARMED) + } +} diff --git a/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/standard/CurseSpells.kt b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/standard/CurseSpells.kt new file mode 100644 index 000000000..866795f5f --- /dev/null +++ b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/standard/CurseSpells.kt @@ -0,0 +1,190 @@ +package org.rsmod.content.skills.magic.spell.attacks.standard + +import org.rsmod.api.spells.attack.SpellAttackManager +import org.rsmod.api.spells.attack.SpellAttackMap +import org.rsmod.api.spells.attack.SpellAttackRepository +import org.rsmod.content.skills.magic.spell.attacks.EffectSpellAttack +import org.rsmod.content.skills.magic.spell.attacks.SpellEffects + +/** + * The standard spellbook's curses and binds. Curses lower one of the target's combat stats by a + * share of its base level and do not stack; binds hold the target in place for 5, 10 or 15 + * seconds. None of them deal damage: a landed cast shows its impact graphic and the effect, a + * failed one splashes. + */ +class CurseSpells : SpellAttackMap { + override fun SpellAttackRepository.register(manager: SpellAttackManager) { + register( + spell = "obj.03_confuse", + attack = + curse( + manager, + anim = "seq.human_castconfuse", + staffAnim = "seq.human_castconfuse_staff", + name = "confuse", + castSound = "synth.confuse_cast_and_fire", + hitSound = "synth.confuse_hit", + stat = SpellEffects.ATTACK, + percent = 5, + ), + ) + register( + spell = "obj.11_weaken", + attack = + curse( + manager, + anim = "seq.human_castweaken", + staffAnim = "seq.human_castweaken_staff", + name = "weaken", + castSound = "synth.weaken_all", + hitSound = null, + stat = SpellEffects.STRENGTH, + percent = 5, + ), + ) + register( + spell = "obj.19_curse", + attack = + curse( + manager, + anim = "seq.human_castcurse", + staffAnim = "seq.human_castcurse_staff", + name = "curse", + castSound = "synth.curse_cast_and_fire", + hitSound = "synth.curse_hit", + stat = SpellEffects.DEFENCE, + percent = 5, + ), + ) + register( + spell = "obj.66_vulnerability", + attack = + curse( + manager, + anim = "seq.human_castcurse", + staffAnim = "seq.human_castcurse_staff", + name = "vulnerability", + castSound = "synth.vulnerability_all", + hitSound = null, + stat = SpellEffects.DEFENCE, + percent = 10, + projanim = "projanim.vulnerability", + ), + ) + register( + spell = "obj.73_enfeeble", + attack = + curse( + manager, + anim = "seq.human_castenfeeble", + staffAnim = "seq.human_castenfeeble_staff", + name = "enfeeble", + castSound = "synth.enfeeble_cast_and_fire", + hitSound = "synth.enfeeble_hit", + stat = SpellEffects.STRENGTH, + percent = 10, + projanim = "projanim.enfeeble", + ), + ) + register( + spell = "obj.80_stun", + attack = + curse( + manager, + anim = "seq.human_caststun", + staffAnim = "seq.human_caststun_staff", + name = "stun", + castSound = "synth.stun_all", + hitSound = null, + stat = SpellEffects.ATTACK, + percent = 10, + projanim = "projanim.stun", + ), + ) + + register( + spell = "obj.20_bind", + attack = + bind( + manager, + impact = "spotanim.bind_impact", + castSound = "synth.bind_cast", + hitSound = "synth.bind_impact", + ticks = BIND_TICKS, + ), + ) + register( + spell = "obj.50_snare", + attack = + bind( + manager, + impact = "spotanim.snare_impact", + castSound = "synth.snare_all", + hitSound = null, + ticks = SNARE_TICKS, + ), + ) + register( + spell = "obj.79_entangle", + attack = + bind( + manager, + impact = "spotanim.entangle_impact", + castSound = "synth.entangle_cast_and_fire", + hitSound = "synth.entangle_hit", + ticks = ENTANGLE_TICKS, + ), + ) + } + + private fun curse( + manager: SpellAttackManager, + anim: String, + staffAnim: String, + name: String, + castSound: String?, + hitSound: String?, + stat: String, + percent: Int, + projanim: String = "projanim.confuse", + ): EffectSpellAttack = + EffectSpellAttack( + manager = manager, + staffAnim = staffAnim, + unarmedAnim = anim, + launch = "spotanim.${name}_casting", + travel = "spotanim.${name}_travel", + projanim = projanim, + impact = "spotanim.${name}_impact", + castSound = castSound, + hitSound = hitSound, + onLand = { target, _ -> SpellEffects.drainPercent(target, stat, percent) }, + ) + + private fun bind( + manager: SpellAttackManager, + impact: String, + castSound: String?, + hitSound: String?, + ticks: Int, + ): EffectSpellAttack = + EffectSpellAttack( + manager = manager, + staffAnim = "seq.human_castentangle_staff", + unarmedAnim = "seq.human_castentangle", + launch = "spotanim.entangle_casting", + travel = "spotanim.entangle_travel", + projanim = "projanim.bind", + impact = impact, + impactHeight = 0, + castSound = castSound, + hitSound = hitSound, + onLand = { target, _ -> SpellEffects.freeze(target, ticks) }, + ) + + private companion object { + private const val BIND_TICKS = 8 + private const val SNARE_TICKS = 16 + private const val ENTANGLE_TICKS = 24 + } +} diff --git a/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/standard/StandardCombatSpells.kt b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/standard/StandardCombatSpells.kt new file mode 100644 index 000000000..7c2c95b46 --- /dev/null +++ b/content/skills/magic/spell-attacks/src/main/kotlin/org/rsmod/content/skills/magic/spell/attacks/standard/StandardCombatSpells.kt @@ -0,0 +1,127 @@ +package org.rsmod.content.skills.magic.spell.attacks.standard + +import org.rsmod.api.spells.attack.SpellAttackManager +import org.rsmod.api.spells.attack.SpellAttackMap +import org.rsmod.api.spells.attack.SpellAttackRepository +import org.rsmod.content.skills.magic.spell.attacks.EffectSpellAttack +import org.rsmod.content.skills.magic.spell.attacks.SpellEffects +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player + +/** + * The standard spellbook's non-elemental damage spells: Crumble Undead, Iban Blast, Magic Dart + * and the three god spells. Their staff and cape requirements come from the spell data and are + * checked when the runes are taken. + */ +class StandardCombatSpells : SpellAttackMap { + override fun SpellAttackRepository.register(manager: SpellAttackManager) { + register( + spell = "obj.39_crumble_undead", + attack = + EffectSpellAttack( + manager = manager, + staffAnim = "seq.human_castcrumbleundead_staff", + unarmedAnim = "seq.human_castcrumbleundead", + launch = "spotanim.crumbleundead_casting", + travel = "spotanim.crumbleundead_travel", + projanim = "projanim.crumble_undead", + impact = "spotanim.crumbleundead_impact", + castSound = "synth.crumble_cast_and_fire", + hitSound = "synth.crumble_hit", + baseMaxHit = { CRUMBLE_UNDEAD_MAX_HIT }, + targetCheck = { target -> + if (target is Npc && !SpellEffects.isUndead(target)) { + "This spell only affects skeletons, zombies, ghosts and shades." + } else if (target is Player) { + "This spell only affects skeletons, zombies, ghosts and shades." + } else { + null + } + }, + ), + ) + + register( + spell = "obj.50_iban_blast", + attack = + EffectSpellAttack( + manager = manager, + staffAnim = "seq.human_castibanblast", + launch = "spotanim.ibanblast_casting", + travel = "spotanim.ibanblast_travel", + projanim = "projanim.iban_blast", + impact = "spotanim.ibanblast_impact", + castSound = null, + hitSound = null, + baseMaxHit = { IBAN_BLAST_MAX_HIT }, + ), + ) + + val magicDart = + EffectSpellAttack( + manager = manager, + staffAnim = "seq.slayer_magicdart_cast", + launch = null, + travel = "spotanim.slayer_magicdart_travel", + impact = "spotanim.slayer_magicdart_impact", + castSound = null, + hitSound = "synth.magic_dart_hit", + baseMaxHit = { magicLvl -> MAGIC_DART_BASE + magicLvl / 10 }, + ) + register(spell = "obj.50_magic_dart", attack = magicDart) + register(spell = "obj.50_slayer_dart", attack = magicDart) + + register( + spell = "obj.60_saradomin_strike", + attack = + godSpell(manager, impact = "spotanim.saradomin_lightning", sound = "synth.saradomin_strike_cast") { target -> + if (target is Player) { + SpellEffects.drainFlat(target, SpellEffects.PRAYER, SARADOMIN_PRAYER_DRAIN) + } + }, + ) + register( + spell = "obj.60_claws_of_guthix", + attack = + godSpell(manager, impact = "spotanim.guthix_claw_green", sound = "synth.claws_of_guthix_cast") { target -> + SpellEffects.drainPercent(target, SpellEffects.DEFENCE, GOD_SPELL_DRAIN_PERCENT) + }, + ) + register( + spell = "obj.60_flames_of_zamorak", + attack = + godSpell(manager, impact = "spotanim.zamorak_flame", sound = "synth.flames_of_zamorak_cast") { target -> + SpellEffects.drainPercent(target, SpellEffects.MAGIC, GOD_SPELL_DRAIN_PERCENT) + }, + ) + } + + /** God spells strike straight down on the target: no projectile, just the impact. */ + private fun godSpell( + manager: SpellAttackManager, + impact: String, + sound: String, + effect: (target: org.rsmod.game.entity.PathingEntity) -> Unit, + ): EffectSpellAttack = + EffectSpellAttack( + manager = manager, + staffAnim = "seq.human_casting", + launch = null, + travel = null, + impact = impact, + impactHeight = 0, + castSound = sound, + hitSound = null, + baseMaxHit = { GOD_SPELL_MAX_HIT }, + onLand = { target, damage -> if (damage > 0) effect(target) }, + ) + + private companion object { + private const val CRUMBLE_UNDEAD_MAX_HIT = 15 + private const val IBAN_BLAST_MAX_HIT = 25 + private const val MAGIC_DART_BASE = 10 + private const val GOD_SPELL_MAX_HIT = 20 + private const val GOD_SPELL_DRAIN_PERCENT = 5 + private const val SARADOMIN_PRAYER_DRAIN = 1 + } +} diff --git a/content/skills/magic/spell-teleports/src/main/kotlin/org/rsmod/content/skills/magic/spell/teleports/SpellTeleportScript.kt b/content/skills/magic/spell-teleports/src/main/kotlin/org/rsmod/content/skills/magic/spell/teleports/SpellTeleportScript.kt index 1dfb689f5..0cf0be315 100644 --- a/content/skills/magic/spell-teleports/src/main/kotlin/org/rsmod/content/skills/magic/spell/teleports/SpellTeleportScript.kt +++ b/content/skills/magic/spell-teleports/src/main/kotlin/org/rsmod/content/skills/magic/spell/teleports/SpellTeleportScript.kt @@ -8,10 +8,10 @@ import dev.openrune.types.ItemServerType import dev.openrune.types.aconverted.interf.IfButtonOp import jakarta.inject.Inject import org.rsmod.api.area.checker.AreaChecker -import org.rsmod.api.config.refs.params import org.rsmod.api.combat.commons.magic.MagicSpell import org.rsmod.api.combat.manager.MagicRuneManager import org.rsmod.api.combat.manager.MagicRuneManager.Companion.isFailure +import org.rsmod.api.config.refs.params import org.rsmod.api.invtx.invTransaction import org.rsmod.api.invtx.select import org.rsmod.api.player.hook.PlayerTeleportValidator @@ -267,6 +267,9 @@ constructor( } fun canCast(access: ProtectedAccess): Boolean { + if (!ENFORCE_QUEST_REQUIREMENTS) { + return true + } val questKey = requiredQuest ?: return true val quest = Quest.get(questKey) if (quest?.isQuestCompleted(access.player) == true) { @@ -284,6 +287,12 @@ constructor( ) private companion object { + /** + * Quest requirements on spells are recorded per spell but not enforced: every spell is + * castable without its quest. Flip this to lock them again. + */ + private const val ENFORCE_QUEST_REQUIREMENTS = false + private val TeleportStartAnim = RSCM.getReverseMapping(RSCMType.SEQ, 714) private val TeleportEndAnim = RSCM.getReverseMapping(RSCMType.SEQ, 715) private val TeleportSpotanim = RSCM.getReverseMapping(RSCMType.SPOTANIM, 111) diff --git a/content/skills/thieving/build.gradle.kts b/content/skills/thieving/build.gradle.kts new file mode 100644 index 000000000..816063fb6 --- /dev/null +++ b/content/skills/thieving/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) + implementation(projects.api.attr) + implementation(projects.api.combat.combatCommons) +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/Thieving.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/Thieving.kt new file mode 100644 index 000000000..e6f44d818 --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/Thieving.kt @@ -0,0 +1,60 @@ +package org.rsmod.content.skills.thieving + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.repo.obj.ObjRepository +import org.rsmod.api.stats.xpmod.XpModifiers + +/** Constants and small helpers shared by the pickpocket, stall and chest scripts. */ +object Thieving { + const val STAT: String = "stat.thieving" + + /** Player animation for pickpocketing npcs and stealing from stalls and chests. */ + const val STEAL_ANIM: String = "seq.human_pickpocket" + + /** Birds-around-the-head graphic shown while stunned after a failed pickpocket. */ + const val STUN_SPOTANIM: String = "spotanim.stunned_thieving" + + /** Cycles a failed pickpocket holds the player in place (5.4 seconds). */ + const val STUN_CYCLES: Int = 9 + + /** The stun timer [org.rsmod.api.combat.commons.CombatEffects.stun] schedules. */ + const val STUN_TIMER: String = "timer.combat_stun" + + /** Successful pickpocket / stall theft sound. */ + const val STEAL_SYNTH: String = "synth.pick" + + /** Played when a failed pickpocket stuns the player. */ + const val STUN_SYNTH: String = "synth.thieving_stunned" + + fun objName(obj: String): String = + ServerCacheManager.getItem(obj.asRSCM(RSCMType.OBJ))?.name ?: obj.removePrefix("obj.") + + /** "a cake", "an apple", "some silk", "some bronze bolts". */ + fun withArticle(name: String): String { + val lower = name.lowercase() + val uncountable = lower in UNCOUNTABLE || (lower.endsWith("s") && !lower.endsWith("ss")) + return when { + uncountable -> "some $lower" + lower.firstOrNull() in VOWELS -> "an $lower" + else -> "a $lower" + } + } + + fun ProtectedAccess.giveThievingXp(xpMods: XpModifiers, xp: Double) { + statAdvance(STAT, xp * xpMods.get(player, STAT)) + } + + /** Adds every drop to the inventory, dropping to the floor when it does not fit. */ + fun ProtectedAccess.giveLoot(objRepo: ObjRepository, drops: List, multiplier: Int = 1) { + for (drop in drops) { + invAddOrDrop(objRepo, drop.obj, drop.count * multiplier) + } + } + + private val VOWELS = setOf('a', 'e', 'i', 'o', 'u') + private val UNCOUNTABLE = + setOf("silk", "fur", "grey wolf fur", "spice", "coins", "tokkul", "bread", "garlic") +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/ThievingLoot.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/ThievingLoot.kt new file mode 100644 index 000000000..fe83a3c1e --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/ThievingLoot.kt @@ -0,0 +1,81 @@ +package org.rsmod.content.skills.thieving + +import org.rsmod.api.random.GameRandom + +/** One item that a thieving action can hand out. */ +data class LootEntry(val obj: String, val count: IntRange, val weight: Int) + +/** An item rolled with an independent `1 in [oneIn]` chance on every successful action. */ +data class TertiaryEntry(val obj: String, val count: IntRange, val oneIn: Int) + +/** One rolled reward. */ +data class LootDrop(val obj: String, val count: Int) + +/** + * The reward set of a pickpocket target, stall or chest: everything in [guaranteed] is handed out + * on every success, one entry of [weighted] is picked by weight, and each [tertiary] entry is + * rolled on its own. + */ +class LootTable( + val guaranteed: List, + val weighted: List, + val tertiary: List, +) { + private val totalWeight: Int = weighted.sumOf { it.weight } + + fun roll(random: GameRandom): List { + val drops = ArrayList(guaranteed.size + 2) + for (entry in guaranteed) { + drops += LootDrop(entry.obj, random.of(entry.count)) + } + if (totalWeight > 0) { + var pick = random.of(maxExclusive = totalWeight) + for (entry in weighted) { + pick -= entry.weight + if (pick < 0) { + drops += LootDrop(entry.obj, random.of(entry.count)) + break + } + } + } + for (entry in tertiary) { + if (random.of(maxExclusive = entry.oneIn) == 0) { + drops += LootDrop(entry.obj, random.of(entry.count)) + } + } + return drops + } + + /** All objs this table can hand out, for inventory-space checks. */ + val objs: Set + get() = (guaranteed + weighted).mapTo(HashSet()) { it.obj } + tertiary.map { it.obj } + + companion object { + val EMPTY: LootTable = LootTable(emptyList(), emptyList(), emptyList()) + } +} + +class LootBuilder { + private val guaranteed = mutableListOf() + private val weighted = mutableListOf() + private val tertiary = mutableListOf() + + /** Given on every success. */ + fun always(obj: String, count: IntRange = 1..1) { + guaranteed += LootEntry(obj, count, weight = 0) + } + + /** One weighted entry is chosen per success. */ + fun item(weight: Int, obj: String, count: IntRange = 1..1) { + weighted += LootEntry(obj, count, weight) + } + + /** Rolled independently at `1 in [oneIn]`. */ + fun rare(oneIn: Int, obj: String, count: IntRange = 1..1) { + tertiary += TertiaryEntry(obj, count, oneIn) + } + + fun build(): LootTable = LootTable(guaranteed.toList(), weighted.toList(), tertiary.toList()) +} + +fun loot(block: LootBuilder.() -> Unit): LootTable = LootBuilder().apply(block).build() diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/chests/ChestScript.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/chests/ChestScript.kt new file mode 100644 index 000000000..77308a3a4 --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/chests/ChestScript.kt @@ -0,0 +1,98 @@ +package org.rsmod.content.skills.thieving.chests + +import dev.openrune.types.ObjectServerType +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.hitpoints +import org.rsmod.api.player.stat.thievingLvl +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.repo.obj.ObjRepository +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpLoc2 +import org.rsmod.api.stats.xpmod.XpModifiers +import org.rsmod.content.skills.thieving.Thieving +import org.rsmod.content.skills.thieving.Thieving.giveLoot +import org.rsmod.content.skills.thieving.Thieving.giveThievingXp +import org.rsmod.game.hit.HitType +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Trapped chests. `Search for traps` disarms the chest and loots it; `Open` without searching + * springs the trap, which hurts in proportion to the player's current hitpoints. + */ +class ChestScript +@Inject +constructor( + private val locRepo: LocRepository, + private val objRepo: ObjRepository, + private val xpMods: XpModifiers, +) : PluginScript() { + + override fun ScriptContext.startup() { + for (chest in ChestTarget.entries) { + onOpLoc1(chest.loc) { open(it.loc) } + onOpLoc2(chest.loc) { searchForTraps(it.loc, it.type, chest) } + } + // Several chests share one looted form, so register each empty loc only once. + for (empty in ChestTarget.entries.map { it.empty }.distinct()) { + onOpLoc1(empty) { openEmpty() } + onOpLoc2(empty) { openEmpty() } + } + } + + private suspend fun ProtectedAccess.open(loc: BoundLocInfo) { + arriveDelay() + faceLoc(loc) + anim(Thieving.STEAL_ANIM) + mes("You have activated a trap on the chest.") + val damage = (player.hitpoints / TRAP_DAMAGE_DIVISOR).coerceAtLeast(1) + queueHit(delay = 1, type = HitType.Typeless, damage = damage) + delay(2) + } + + private suspend fun ProtectedAccess.openEmpty() { + arriveDelay() + mes("It looks like this chest has already been looted.") + } + + private suspend fun ProtectedAccess.searchForTraps( + loc: BoundLocInfo, + type: ObjectServerType, + chest: ChestTarget, + ) { + if (player.thievingLvl < chest.level) { + mes("You need to be at least level ${chest.level} Thieving to disarm this chest's trap.") + return + } + if (!inv.hasFreeSpace()) { + mes("You don't have enough inventory space to loot the chest.") + return + } + + arriveDelay() + faceLoc(loc) + anim(Thieving.STEAL_ANIM) + mes("You search the chest for traps.") + delay(2) + mes("You find a trap on the chest... You disarm the trap and open the chest.") + delay(1) + + if (locRepo.findExact(loc.coords, type) == null) { + mes("Someone has already looted this chest.") + return + } + locRepo.change(loc, chest.empty, chest.respawnCycles) + + giveLoot(objRepo, chest.loot.roll(random)) + giveThievingXp(xpMods, chest.xp) + soundSynth(Thieving.STEAL_SYNTH) + mes("You steal the treasure from the chest.") + } + + private companion object { + /** A sprung trap takes an eighth of the player's remaining hitpoints. */ + const val TRAP_DAMAGE_DIVISOR: Int = 8 + } +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/chests/ChestTarget.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/chests/ChestTarget.kt new file mode 100644 index 000000000..cc2f7f419 --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/chests/ChestTarget.kt @@ -0,0 +1,102 @@ +package org.rsmod.content.skills.thieving.chests + +import org.rsmod.content.skills.thieving.LootTable +import org.rsmod.content.skills.thieving.loot + +/** + * A trapped chest that can be searched for traps and looted. The stocked [loc] turns into + * [empty] for [respawnCycles] once looted. + */ +enum class ChestTarget( + val label: String, + val level: Int, + val xp: Double, + val respawnCycles: Int, + val loot: LootTable, + val loc: String, + val empty: String, +) { + TEN_COINS( + label = "10 coin chest", + level = 13, + xp = 7.8, + respawnCycles = 6, + loot = loot { always("obj.coins", 10..10) }, + loc = "loc.trapchest1", + empty = "loc.emptytrapchest", + ), + NATURE_RUNE( + label = "nature rune chest", + level = 28, + xp = 25.0, + respawnCycles = 13, + loot = + loot { + always("obj.coins", 3..3) + always("obj.naturerune") + }, + loc = "loc.trapchest2", + empty = "loc.emptytrapchest", + ), + FIFTY_COINS( + label = "50 coin chest", + level = 43, + xp = 125.0, + respawnCycles = 75, + loot = loot { always("obj.coins", 50..50) }, + loc = "loc.trapchest3", + empty = "loc.emptytrapchest", + ), + STEEL_ARROWTIPS( + label = "steel arrowtips chest", + level = 47, + xp = 150.0, + respawnCycles = 125, + loot = + loot { + always("obj.coins", 20..20) + always("obj.steel_arrowheads", 5..5) + }, + loc = "loc.trapchest4", + empty = "loc.emptytrapchest", + ), + BLOOD_RUNES( + label = "blood rune chest", + level = 59, + xp = 250.0, + respawnCycles = 200, + loot = + loot { + always("obj.coins", 500..500) + always("obj.bloodrune", 2..2) + }, + loc = "loc.trapchest5", + empty = "loc.emptytrapchest", + ), + ARDOUGNE_CASTLE( + label = "Ardougne castle chest", + level = 72, + xp = 500.0, + respawnCycles = 400, + loot = + loot { + always("obj.coins", 1000..1000) + always("obj.raw_shark") + }, + loc = "loc.pickchest3", + empty = "loc.emptypickchest", + ), + ROGUES_CASTLE( + label = "Rogues' Castle chest", + level = 84, + xp = 701.7, + respawnCycles = 34, + loot = + loot { + always("obj.coins", 1000..1000) + always("obj.dragonstone") + }, + loc = "loc.wilderness_rogue_chest", + empty = "loc.wilderness_rogue_chest_open", + ), +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/equipment/ThievingEquipment.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/equipment/ThievingEquipment.kt new file mode 100644 index 000000000..c73f53f31 --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/equipment/ThievingEquipment.kt @@ -0,0 +1,166 @@ +package org.rsmod.content.skills.thieving.equipment + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.util.Wearpos +import jakarta.inject.Singleton +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.random.GameRandom +import org.rsmod.game.entity.Player + +/** + * The worn gear that changes how pickpocketing plays out. + * + * - Rogue outfit: each of the five pieces adds a 15% chance of doubling pickpocket loot; the full + * set always doubles it. Stalls and chests are unaffected. + * - Gloves of silence: +5% success chance. They wear out after 62 failed pickpockets. + * - Dodgy necklace: a 25% chance to shrug off the stun and damage of a failed pickpocket. Each + * time it does, one of its 10 charges is spent; the last charge crumbles it to dust. + * - Thieving cape: +10% success chance. + * + * Wear on the gloves and charges on the necklace are kept per player, so a replacement pair or + * necklace starts fresh only once the old one is gone. + */ +@Singleton +class ThievingEquipment { + + fun roguePieces(player: Player): Int = ROGUE_PIECES.count { (wearpos, obj) -> player.wears(wearpos, obj) } + + /** Rolls whether the worn rogue pieces double this pickpocket's loot. */ + fun rollRogueDoubleLoot(player: Player, random: GameRandom): Boolean { + val pieces = roguePieces(player) + if (pieces == 0) { + return false + } + val chance = if (pieces >= ROGUE_PIECES.size) 100 else pieces * ROGUE_PIECE_CHANCE + return random.of(maxExclusive = 100) < chance + } + + /** Multiplier applied to the pickpocket success chance. */ + fun successMultiplier(player: Player): Double { + var multiplier = 1.0 + if (player.wearsGlovesOfSilence()) { + multiplier += GLOVES_BONUS + } + if (player.wears(Wearpos.Back, THIEVING_CAPE) || player.wears(Wearpos.Back, THIEVING_CAPE_TRIMMED)) { + multiplier += CAPE_BONUS + } + return multiplier + } + + /** Called on every failed pickpocket: worn gloves of silence take one point of wear. */ + fun wearGlovesOfSilence(access: ProtectedAccess) { + val player = access.player + if (!player.wearsGlovesOfSilence()) { + return + } + val wear = player.attr.getOrDefault(GLOVES_WEAR, 0) + 1 + if (wear < GLOVES_LIFETIME) { + player.attr[GLOVES_WEAR] = wear + if (wear == GLOVES_LIFETIME - 1) { + access.mes("Your gloves of silence are about to fall apart!") + } + return + } + player.attr.remove(GLOVES_WEAR) + if (!access.invDel(player.worn, GLOVES_OF_SILENCE, 1).failure) { + player.rebuildAppearance() + } + access.mes("Your gloves of silence have worn out completely and fall apart.") + } + + /** + * Returns `true` when a worn dodgy necklace absorbs this failed pickpocket, spending a charge + * and crumbling on the last one. + */ + fun dodgyNecklaceProtects(access: ProtectedAccess): Boolean { + val player = access.player + if (!player.wears(Wearpos.Front, DODGY_NECKLACE)) { + return false + } + if (access.random.of(maxExclusive = 100) >= DODGY_PROTECT_CHANCE) { + return false + } + val charges = player.attr.getOrDefault(DODGY_CHARGES, DODGY_MAX_CHARGES) - 1 + if (charges > 0) { + player.attr[DODGY_CHARGES] = charges + access.mes("Your dodgy necklace protects you. It has $charges ${plural("charge", charges)} left.") + return true + } + player.attr.remove(DODGY_CHARGES) + if (!access.invDel(player.worn, DODGY_NECKLACE, 1).failure) { + player.rebuildAppearance() + } + access.mes("Your dodgy necklace protects you. It then crumbles to dust.") + return true + } + + /** `Check` on gloves of silence. */ + fun checkGloves(access: ProtectedAccess) { + val remaining = GLOVES_LIFETIME - access.player.attr.getOrDefault(GLOVES_WEAR, 0) + val line = + when { + remaining >= GLOVES_LIFETIME -> "Your gloves are new." + remaining > 33 -> "Your gloves are in good condition." + remaining > 19 -> "Your gloves are starting to look quite shabby." + remaining > 9 -> "Your gloves are starting to need repair." + remaining > 1 -> "Your gloves are in need of repair!" + else -> "Your gloves are about to fall apart!" + } + access.mes(line) + } + + /** `Check` on a dodgy necklace. */ + fun checkNecklace(access: ProtectedAccess) { + val charges = access.player.attr.getOrDefault(DODGY_CHARGES, DODGY_MAX_CHARGES) + access.mes("Your dodgy necklace has $charges ${plural("charge", charges)} left.") + } + + /** `Break` on a dodgy necklace: destroys it and forgets its charges. */ + fun breakNecklace(access: ProtectedAccess, slot: Int) { + if (access.invDel(access.inv, DODGY_NECKLACE, 1, slot = slot).failure) { + return + } + access.player.attr.remove(DODGY_CHARGES) + access.mes("You break the dodgy necklace and it crumbles to dust.") + } + + private fun Player.wearsGlovesOfSilence(): Boolean = wears(Wearpos.Hands, GLOVES_OF_SILENCE) + + private fun Player.wears(wearpos: Wearpos, obj: String): Boolean = + worn[wearpos.slot]?.id == obj.asRSCM(RSCMType.OBJ) + + private fun plural(word: String, count: Int): String = if (count == 1) word else "${word}s" + + companion object { + const val GLOVES_OF_SILENCE: String = "obj.hunting_silent_gloves" + const val DODGY_NECKLACE: String = "obj.dodgy_necklace" + const val THIEVING_CAPE: String = "obj.skillcape_thieving" + const val THIEVING_CAPE_TRIMMED: String = "obj.skillcape_thieving_trimmed" + + val ROGUE_PIECES: Map = + mapOf( + Wearpos.Hat to "obj.roguesden_helm", + Wearpos.Torso to "obj.roguesden_body", + Wearpos.Legs to "obj.roguesden_legs", + Wearpos.Hands to "obj.roguesden_gloves", + Wearpos.Feet to "obj.roguesden_boots", + ) + + /** Percent chance of double loot per rogue piece worn (the full set is 100%). */ + private const val ROGUE_PIECE_CHANCE: Int = 15 + + private const val GLOVES_BONUS: Double = 0.05 + private const val CAPE_BONUS: Double = 0.10 + + /** Failed pickpockets a pair of gloves survives. */ + const val GLOVES_LIFETIME: Int = 62 + + private const val DODGY_PROTECT_CHANCE: Int = 25 + const val DODGY_MAX_CHARGES: Int = 10 + + private val GLOVES_WEAR = AttributeKey(persistenceKey = "thieving.gloves_of_silence_wear") + private val DODGY_CHARGES = AttributeKey(persistenceKey = "thieving.dodgy_necklace_charges") + } +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/equipment/ThievingEquipmentScript.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/equipment/ThievingEquipmentScript.kt new file mode 100644 index 000000000..864f9be13 --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/equipment/ThievingEquipmentScript.kt @@ -0,0 +1,32 @@ +package org.rsmod.content.skills.thieving.equipment + +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpHeld3 +import org.rsmod.api.script.onOpHeld4 +import org.rsmod.api.script.onOpWorn1 +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** Inventory and worn `Check` / `Break` options on gloves of silence and the dodgy necklace. */ +class ThievingEquipmentScript @Inject constructor(private val equipment: ThievingEquipment) : + PluginScript() { + + override fun ScriptContext.startup() { + onOpHeld3(ThievingEquipment.GLOVES_OF_SILENCE) { equipment.checkGloves(this) } + onOpWorn1(ThievingEquipment.GLOVES_OF_SILENCE) { equipment.checkGloves(this) } + + onOpHeld3(ThievingEquipment.DODGY_NECKLACE) { equipment.checkNecklace(this) } + onOpWorn1(ThievingEquipment.DODGY_NECKLACE) { equipment.checkNecklace(this) } + onOpHeld4(ThievingEquipment.DODGY_NECKLACE) { confirmBreak(it.slot) } + } + + private suspend fun ProtectedAccess.confirmBreak(slot: Int) { + startDialogue { + val choice = choice2("Yes, break it.", 1, "No, keep it.", 2) + if (choice == 1) { + equipment.breakNecklace(access, slot) + } + } + } +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pets/RockyPet.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pets/RockyPet.kt new file mode 100644 index 000000000..86cab9dd9 --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pets/RockyPet.kt @@ -0,0 +1,25 @@ +package org.rsmod.content.skills.thieving.pets + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType + +/** + * The three forms of the Thieving skill pet. Feeding the follower the matching berries turns it + * into that form; white berries turn it back into the raccoon. + */ +enum class RockyPet(val obj: String, val npc: String, val petName: String, val berries: String) { + RACCOON("obj.skillpetthieving", "npc.skillpet_thieving", "Rocky", "obj.white_berries"), + RED_PANDA("obj.skillpetthieving_panda", "npc.skillpet_thieving_panda", "Red", "obj.redberries"), + TANUKI("obj.skillpetthieving_tanuki", "npc.skillpet_thieving_tanuki", "Ziggy", "obj.poisonivy_berries"); + + val objId: Int + get() = obj.asRSCM(RSCMType.OBJ) + + companion object { + private val byObj: Map by lazy { entries.associateBy { it.objId } } + + fun fromObj(objId: Int): RockyPet? = byObj[objId] + + fun fromBerries(obj: String): RockyPet? = entries.firstOrNull { it.berries == obj } + } +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pets/RockyPetManager.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pets/RockyPetManager.kt new file mode 100644 index 000000000..14dfe3cdb --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pets/RockyPetManager.kt @@ -0,0 +1,197 @@ +package org.rsmod.content.skills.thieving.pets + +import dev.openrune.types.NpcMode +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.npc.owner.assignSpawnOwner +import org.rsmod.api.npc.owner.isSpawnOwnedBy +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.statBase +import org.rsmod.api.player.vars.VarPlayerIntMapSetter +import org.rsmod.api.player.vars.resyncVar +import org.rsmod.api.repo.npc.NpcRepository +import org.rsmod.game.MapClock +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player +import org.rsmod.game.map.Direction +import org.rsmod.game.map.collision.isWalkBlocked +import org.rsmod.game.map.collision.isZoneValid +import org.rsmod.map.CoordGrid +import org.rsmod.routefinder.collision.CollisionFlagMap + +/** + * Owns a player's following Rocky: which form is out, the npc that represents it, and the + * per-cycle upkeep that keeps it at the owner's heels. The form that is out persists so it comes + * back on login. + */ +@Singleton +class RockyPetManager +@Inject +constructor( + private val npcRepo: NpcRepository, + private val mapClock: MapClock, + private val collision: CollisionFlagMap, +) { + fun following(player: Player): Npc? = player.attr[PET_NPC] + + fun followingPet(player: Player): RockyPet? = player.attr[FOLLOWING_OBJ]?.let(RockyPet::fromObj) + + fun isOwnedBy(npc: Npc, player: Player): Boolean = + npc.isSpawnOwnedBy(player) && following(player) === npc + + /** Whether anything at all follows the player: Rocky, a cat, any other pet. */ + fun hasAnyFollower(player: Player): Boolean = + following(player) != null || player.vars[FOLLOWER_VARP] > 0 + + /** + * Rolls the pet at `1 in (base - level * 25)` after a successful pickpocket, stall or chest + * theft. The pet starts following unless something already does, in which case it slips + * into the backpack; it is lost only when both are impossible. + */ + fun roll(access: ProtectedAccess, base: Int) { + val player = access.player + val chance = (base - player.statBase(THIEVING) * LEVEL_SCALE).coerceAtLeast(1) + if (access.random.of(maxExclusive = chance) != 0) { + return + } + if (followingPet(player) != null) { + return + } + if (!hasAnyFollower(player)) { + spawn(player, RockyPet.RACCOON) + access.mes("You have a funny feeling like you're being followed.") + return + } + if (access.inv.hasFreeSpace()) { + access.invAdd(access.inv, RockyPet.RACCOON.obj, 1) + access.mes("You feel something weird sneaking into your backpack.") + } + } + + /** Spawns [pet] beside [player] and starts the per-cycle follow timer. */ + fun spawn(player: Player, pet: RockyPet): Npc { + following(player)?.let { remove(it) } + val npc = Npc(pet.npc, spawnTile(player)) + npc.mode = NpcMode.PlayerFollow + npcRepo.add(npc, Int.MAX_VALUE) + // `add` with a permanent duration flags the npc as respawning; a pet never does. + npc.respawns = false + npc.assignSpawnOwner(player, mapClock.cycle) + npc.facePlayer(player) + setFollowerVarp(player, npc) + + player.attr[PET_NPC] = npc + player.attr[FOLLOWING_OBJ] = pet.objId + player.softTimer(TICK_TIMER, 1) + return npc + } + + /** Swaps the follower for another form in place. */ + fun transform(player: Player, pet: RockyPet): Npc = spawn(player, pet) + + /** Removes the follower npc but keeps the pet "out", so it respawns on the next login. */ + fun despawn(player: Player) { + val npc = following(player) ?: return + remove(npc) + clearFollowerVarp(player) + player.attr.remove(PET_NPC) + player.clearSoftTimer(TICK_TIMER) + } + + /** The pet is picked up: nothing follows any more. */ + fun release(player: Player) { + despawn(player) + player.attr.remove(FOLLOWING_OBJ) + } + + /** Called once per cycle while Rocky is out. */ + fun tick(player: Player) { + val pet = followingPet(player) + val npc = following(player) + if (pet == null || npc == null) { + despawn(player) + return + } + if (!npc.isSlotAssigned) { + // The engine dropped the npc (e.g. an exception in its cycle); put the pet back. + spawn(player, pet) + return + } + if (player.attr[RESYNC_FOLLOWER_VARP] == true) { + player.attr.remove(RESYNC_FOLLOWER_VARP) + player.resyncVar(FOLLOWER_VARP) + } + if (!npc.isFacingPlayer) { + npc.facePlayer(player) + } + if (npc.mode != NpcMode.PlayerFollow) { + npc.mode = NpcMode.PlayerFollow + } + } + + /** + * A free tile beside the player so the pet is not hidden under them when it appears. Cardinal + * neighbours are tried before diagonals; the player's own tile is the last resort. + */ + private fun spawnTile(player: Player): CoordGrid { + val origin = player.coords + for (direction in SPAWN_DIRECTIONS) { + val tile = origin.translate(direction.xOff, direction.zOff) + if (collision.isZoneValid(tile) && !collision.isWalkBlocked(tile)) { + return tile + } + } + return origin + } + + /** + * The client only shows right-click options on a follower-flagged npc when `varp.follower_npc` + * names it: the npc type id in the high 16 bits and its slot index in the low 16 bits. + */ + private fun setFollowerVarp(player: Player, npc: Npc) { + val packed = (npc.visType.id shl 16) or (npc.slotId and 0xFFFF) + VarPlayerIntMapSetter.set(player, FOLLOWER_VARP, packed) + // The setter does not transmit before the player's first processed cycle (login spawns), + // so the first tick pushes the value again. + player.attr[RESYNC_FOLLOWER_VARP] = true + } + + private fun clearFollowerVarp(player: Player) { + VarPlayerIntMapSetter.set(player, FOLLOWER_VARP, NO_FOLLOWER) + } + + private fun remove(npc: Npc) { + if (npc.isSlotAssigned) { + npcRepo.del(npc, Int.MAX_VALUE) + } + } + + companion object { + const val TICK_TIMER: String = "timer.pet_rocky_tick" + + private const val THIEVING: String = "stat.thieving" + private const val LEVEL_SCALE: Int = 25 + + private const val FOLLOWER_VARP: String = "varp.follower_npc" + private const val NO_FOLLOWER: Int = -1 + + private val SPAWN_DIRECTIONS: List = + listOf( + Direction.South, + Direction.West, + Direction.East, + Direction.North, + Direction.SouthWest, + Direction.SouthEast, + Direction.NorthWest, + Direction.NorthEast, + ) + + /** Obj id of the form currently out following (persists so it respawns on login). */ + private val FOLLOWING_OBJ = AttributeKey(persistenceKey = "pets.rocky.following_obj") + + private val PET_NPC = AttributeKey(temp = true) + private val RESYNC_FOLLOWER_VARP = AttributeKey(temp = true) + } +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pets/RockyPetScript.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pets/RockyPetScript.kt new file mode 100644 index 000000000..ecf17a3c5 --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pets/RockyPetScript.kt @@ -0,0 +1,190 @@ +package org.rsmod.content.skills.thieving.pets + +import dev.or2.central.account.Rights +import jakarta.inject.Inject +import org.rsmod.api.invtx.invAdd +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onCommand +import org.rsmod.api.script.onOpHeld5 +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpc3 +import org.rsmod.api.script.onOpNpcU +import org.rsmod.api.script.onPlayerLogin +import org.rsmod.api.script.onPlayerLogout +import org.rsmod.api.script.onPlayerSoftTimer +import org.rsmod.game.entity.Npc +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Rocky, the Thieving skill pet. Dropping the held pet releases it as a follower, `Pick-up` + * returns it to the inventory, `Talk-to` chats, and using berries on it changes its form: white + * berries for the raccoon, redberries for Red the red panda, poison ivy berries for Ziggy the + * tanuki. + */ +class RockyPetScript @Inject constructor(private val rocky: RockyPetManager) : PluginScript() { + + override fun ScriptContext.startup() { + onPlayerLogin { rocky.followingPet(player)?.let { rocky.spawn(player, it) } } + onPlayerLogout { rocky.despawn(player) } + onPlayerSoftTimer(RockyPetManager.TICK_TIMER) { rocky.tick(player) } + + for (pet in RockyPet.entries) { + onOpHeld5(pet.obj) { release(pet, it.slot) } + onOpNpc1(pet.npc) { talkTo(it.npc, pet) } + onOpNpc3(pet.npc) { pickUp(it.npc, pet) } + onOpNpcU(pet.npc) { useItem(it.npc, pet, it.objType.internalName, it.invSlot) } + } + + onCommand("rocky") { + desc = "Spawn Rocky, the Thieving pet, into your inventory" + requiredRights = Rights.ADMINISTRATOR + cheat { + if (player.inv.isFull()) { + player.mes("You don't have enough inventory space.") + return@cheat + } + player.invAdd(player.inv, RockyPet.RACCOON.obj, count = 1) + player.mes("Spawned Rocky. Drop it to have it follow you.") + } + } + } + + private suspend fun ProtectedAccess.release(pet: RockyPet, slot: Int) { + if (rocky.hasAnyFollower(player)) { + mes("You already have a follower.") + return + } + if (invDel(inv, pet.obj, count = 1, slot = slot).failure) { + return + } + rocky.spawn(player, pet) + anim("seq.human_pickupfloor") + mes("You put down ${pet.petName} and it starts to follow you.") + } + + private suspend fun ProtectedAccess.pickUp(npc: Npc, pet: RockyPet) { + if (!rocky.isOwnedBy(npc, player)) { + return + } + arriveDelay() + if (inv.isFull()) { + mes("You don't have enough inventory space to pick up ${pet.petName}.") + return + } + faceEntitySquare(npc) + anim("seq.human_pickupfloor") + if (invAdd(inv, pet.obj, count = 1).failure) { + return + } + rocky.release(player) + mes("You pick up ${pet.petName}.") + } + + private suspend fun ProtectedAccess.useItem(npc: Npc, current: RockyPet, obj: String, invSlot: Int) { + if (!rocky.isOwnedBy(npc, player)) { + return + } + val target = RockyPet.fromBerries(obj) + if (target == null) { + mes("${current.petName} doesn't seem interested in that.") + return + } + arriveDelay() + if (target == current) { + mes("${current.petName} is already in that form.") + return + } + if (invDel(inv, obj, count = 1, slot = invSlot).failure) { + return + } + faceEntitySquare(npc) + anim("seq.human_pickupfloor") + rocky.transform(player, target) + mes("${current.petName} gobbles up the berries and transforms into ${target.petName}!") + } + + private suspend fun ProtectedAccess.talkTo(npc: Npc, pet: RockyPet) { + if (!rocky.isOwnedBy(npc, player)) { + return + } + arriveDelay() + startDialogue(npc) { + when (pet) { + RockyPet.RACCOON -> raccoonChat(random.of(maxExclusive = 4)) + RockyPet.RED_PANDA -> redPandaChat(random.of(maxExclusive = 3)) + RockyPet.TANUKI -> tanukiChat(random.of(maxExclusive = 3)) + } + } + } + + private suspend fun Dialogue.raccoonChat(variant: Int) { + when (variant) { + 0 -> { + chatPlayer(happy, "*whistles*") + chatNpc(shifty, "What are you doing?") + chatPlayer(happy, "Nothing...") + chatNpc(angry, "Then why is your hand in my pocket?") + chatPlayer(sad, "Sorry, force of habit.") + } + 1 -> { + chatPlayer(quiz, "Do you steal shiny things like magpies do?") + chatNpc(happy, "Magpies? Amateurs. They take anything that glitters.") + chatNpc(happy, "A raccoon only takes what's valuable. And what isn't nailed down.") + } + 2 -> { + chatPlayer(quiz, "Fancy helping me rob the bank?") + chatNpc(neutral, "No thanks. I've gone straight.") + chatNpc(happy, "Besides, Rodney the banker is rather charming...") + chatPlayer(confused, "Rodney?") + } + else -> { + chatNpc(shifty, "Hey, is that your coin on the floor?") + chatPlayer(confused, "Where? I don't see any... hey! Where did my coin go?") + chatNpc(laugh, "Relax, here it is. Watch your pockets!") + } + } + } + + private suspend fun Dialogue.redPandaChat(variant: Int) { + when (variant) { + 0 -> { + chatPlayer(quiz, "Aren't pandas supposed to be black and white?") + chatNpc(angry, "Pandas are red. Red! Whoever heard of a blue panda?") + chatPlayer(confused, "I didn't say blue...") + } + 1 -> { + chatNpc(happy, "Did you see my cartwheel?") + chatPlayer(confused, "No?") + chatNpc(happy, "Exactly. It's invisible. Faster than the eye can see.") + } + else -> { + chatPlayer(quiz, "Do you know a raccoon called Dufresne?") + chatNpc(shifty, "Never heard of him. Why would a panda know a raccoon?") + chatNpc(shifty, "...Did he mention the tunnel?") + } + } + } + + private suspend fun Dialogue.tanukiChat(variant: Int) { + when (variant) { + 0 -> { + chatPlayer(quiz, "Are you related to Rocky?") + chatNpc(neutral, "Rocky? Never heard of him. I'm a tanuki.") + chatNpc(shifty, "We are masters of disguise, you know.") + } + 1 -> { + chatPlayer(quiz, "Do you know any plumbers?") + chatNpc(confused, "Why would I know any plumbers? I follow you around all day.") + } + else -> { + chatPlayer(happy, "I've got a treat for you! Guess which hand.") + chatNpc(happy, "That one!") + chatPlayer(laugh, "Nope, it was empty. Both of them were.") + chatNpc(angry, "Rrowl! Hiss!") + } + } + } +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pickpocket/PickpocketNpcs.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pickpocket/PickpocketNpcs.kt new file mode 100644 index 000000000..5392c6c17 --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pickpocket/PickpocketNpcs.kt @@ -0,0 +1,538 @@ +package org.rsmod.content.skills.thieving.pickpocket + +/** + * Every npc in the cache that carries a `Pickpocket` op, grouped by the pickpocket target it + * belongs to. Generated from the server cache npc definitions; the number is the op slot the + * `Pickpocket` option occupies (1-based). + */ +object PickpocketNpcs { + val MAN_WOMAN: List = + listOf( + PickpocketNpc("npc.varrock_man1", 3), + PickpocketNpc("npc.varrock_woman1", 3), + PickpocketNpc("npc.man", 3), + PickpocketNpc("npc.man2", 3), + PickpocketNpc("npc.man3", 3), + PickpocketNpc("npc.man4", 3), + PickpocketNpc("npc.man4_for_musa_point", 3), + PickpocketNpc("npc.woman", 3), + PickpocketNpc("npc.woman2", 3), + PickpocketNpc("npc.woman3", 3), + PickpocketNpc("npc.al_kharid_man", 3), + PickpocketNpc("npc.falador_man1", 3), + PickpocketNpc("npc.falador_man2", 3), + PickpocketNpc("npc.falador_man3", 3), + PickpocketNpc("npc.falador_woman2", 3), + PickpocketNpc("npc.falador_doric_area_man1", 3), + PickpocketNpc("npc.falador_doric_area_man2", 3), + PickpocketNpc("npc.falador_doric_area_man3", 3), + PickpocketNpc("npc.falador_doric_area_man4", 3), + PickpocketNpc("npc.rimmington_hengel", 3), + PickpocketNpc("npc.rimmington_anja", 3), + PickpocketNpc("npc.ardougnian_male1", 3), + PickpocketNpc("npc.ardougnian_female1", 3), + PickpocketNpc("npc.karamja_man", 3), + PickpocketNpc("npc.death_man_indoors1", 3), + PickpocketNpc("npc.man_indoor", 3), + PickpocketNpc("npc.zeah_man", 3), + PickpocketNpc("npc.zeah_man2", 3), + PickpocketNpc("npc.zeah_man3", 3), + PickpocketNpc("npc.zeah_woman", 3), + PickpocketNpc("npc.zeah_woman2", 3), + PickpocketNpc("npc.zeah_woman3", 3), + PickpocketNpc("npc.zeah_woman_outside", 3), + PickpocketNpc("npc.shayzien_woman_1", 3), + PickpocketNpc("npc.shayzien_woman_2", 3), + PickpocketNpc("npc.shayzien_man_1", 3), + PickpocketNpc("npc.shayzien_man_2", 3), + PickpocketNpc("npc.varlamore_citizen_poor_m_1", 3), + PickpocketNpc("npc.varlamore_citizen_poor_m_2", 3), + PickpocketNpc("npc.varlamore_citizen_poor_m_3", 3), + PickpocketNpc("npc.varlamore_citizen_poor_m_4", 3), + PickpocketNpc("npc.varlamore_citizen_poor_m_5", 3), + PickpocketNpc("npc.varlamore_citizen_poor_f_1", 3), + PickpocketNpc("npc.varlamore_citizen_poor_f_2", 3), + PickpocketNpc("npc.varlamore_citizen_poor_f_3", 3), + PickpocketNpc("npc.varlamore_citizen_poor_f_4", 3), + PickpocketNpc("npc.varlamore_citizen_poor_f_5", 3), + PickpocketNpc("npc.varlamore_citizen_normal_m_1", 3), + PickpocketNpc("npc.varlamore_citizen_normal_m_2", 3), + PickpocketNpc("npc.varlamore_citizen_normal_m_3", 3), + PickpocketNpc("npc.varlamore_citizen_normal_m_4", 3), + PickpocketNpc("npc.varlamore_citizen_normal_m_5", 3), + PickpocketNpc("npc.varlamore_citizen_normal_f_1", 3), + PickpocketNpc("npc.varlamore_citizen_normal_f_2", 3), + PickpocketNpc("npc.varlamore_citizen_normal_f_3", 3), + PickpocketNpc("npc.varlamore_citizen_normal_f_4", 3), + PickpocketNpc("npc.varlamore_citizen_normal_f_5", 3), + PickpocketNpc("npc.varlamore_citizen_rich_m_1", 3), + PickpocketNpc("npc.varlamore_citizen_rich_m_2", 3), + PickpocketNpc("npc.varlamore_citizen_rich_m_3", 3), + PickpocketNpc("npc.varlamore_citizen_rich_m_4", 3), + PickpocketNpc("npc.varlamore_citizen_rich_m_5", 3), + PickpocketNpc("npc.varlamore_citizen_rich_f_1", 3), + PickpocketNpc("npc.varlamore_citizen_rich_f_2", 3), + PickpocketNpc("npc.varlamore_citizen_rich_f_3", 3), + PickpocketNpc("npc.varlamore_citizen_rich_f_4", 3), + PickpocketNpc("npc.varlamore_citizen_rich_f_5", 3), + PickpocketNpc("npc.aldarin_citizen_poor_m_1", 3), + PickpocketNpc("npc.aldarin_citizen_poor_m_2", 3), + PickpocketNpc("npc.aldarin_citizen_poor_m_3", 3), + PickpocketNpc("npc.aldarin_citizen_poor_f_1", 3), + PickpocketNpc("npc.aldarin_citizen_poor_f_2", 3), + PickpocketNpc("npc.aldarin_citizen_poor_f_3", 3), + PickpocketNpc("npc.aldarin_citizen_normal_m_1", 3), + PickpocketNpc("npc.aldarin_citizen_normal_m_2", 3), + PickpocketNpc("npc.aldarin_citizen_normal_m_3", 3), + PickpocketNpc("npc.aldarin_citizen_normal_m_4", 3), + PickpocketNpc("npc.aldarin_citizen_normal_f_1", 3), + PickpocketNpc("npc.aldarin_citizen_normal_f_2", 3), + PickpocketNpc("npc.aldarin_citizen_normal_f_3", 3), + PickpocketNpc("npc.aldarin_citizen_normal_f_4", 3), + PickpocketNpc("npc.aldarin_citizen_normal_f_5", 3), + PickpocketNpc("npc.aldarin_citizen_rich_m_1", 3), + PickpocketNpc("npc.aldarin_citizen_rich_m_2", 3), + PickpocketNpc("npc.aldarin_citizen_rich_f_1", 3), + PickpocketNpc("npc.aldarin_citizen_rich_f_2", 3), + PickpocketNpc("npc.auburn_citizen_normal_m_1", 3), + PickpocketNpc("npc.auburn_citizen_normal_m_2", 3), + PickpocketNpc("npc.auburn_citizen_normal_m_3", 3), + PickpocketNpc("npc.auburn_citizen_normal_f_1", 3), + PickpocketNpc("npc.auburn_citizen_normal_f_2", 3), + PickpocketNpc("npc.auburn_citizen_normal_f_3", 3), + PickpocketNpc("npc.auburn_citizen_rich_m_1", 3), + PickpocketNpc("npc.auburn_citizen_rich_f_1", 3), + PickpocketNpc("npc.kastori_citizen_normal_m_1", 3), + PickpocketNpc("npc.kastori_citizen_normal_m_2", 3), + PickpocketNpc("npc.kastori_citizen_normal_m_3", 3), + PickpocketNpc("npc.kastori_citizen_normal_f_1", 3), + PickpocketNpc("npc.kastori_citizen_normal_f_2", 3), + PickpocketNpc("npc.kastori_citizen_normal_f_3", 3), + PickpocketNpc("npc.kastori_citizen_rich_m_1", 3), + PickpocketNpc("npc.kastori_citizen_rich_f_1", 3), + PickpocketNpc("npc.tal_teklan_citizen_normal_m_1", 3), + PickpocketNpc("npc.tal_teklan_citizen_normal_m_2", 3), + PickpocketNpc("npc.tal_teklan_citizen_normal_m_3", 3), + PickpocketNpc("npc.tal_teklan_citizen_normal_f_1", 3), + PickpocketNpc("npc.tal_teklan_citizen_normal_f_2", 3), + PickpocketNpc("npc.tal_teklan_citizen_normal_f_3", 3), + PickpocketNpc("npc.tal_teklan_citizen_rich_m_1", 3), + PickpocketNpc("npc.tal_teklan_citizen_rich_f_1", 3), + PickpocketNpc("npc.man5", 3), + PickpocketNpc("npc.woman4", 3), + ) + + val FARMER: List = + listOf( + PickpocketNpc("npc.farmer1", 3), + PickpocketNpc("npc.farmer2", 3), + PickpocketNpc("npc.farmer3", 3), + PickpocketNpc("npc.farmer1_f", 3), + PickpocketNpc("npc.farmer2_f", 3), + PickpocketNpc("npc.farmer3_f", 3), + PickpocketNpc("npc.farmer4", 3), + PickpocketNpc("npc.varlamore_farmer_m_1", 3), + PickpocketNpc("npc.varlamore_farmer_m_2", 3), + PickpocketNpc("npc.varlamore_farmer_m_3", 3), + PickpocketNpc("npc.varlamore_farmer_m_4", 3), + PickpocketNpc("npc.varlamore_farmer_f_1", 3), + PickpocketNpc("npc.varlamore_farmer_f_2", 3), + PickpocketNpc("npc.varlamore_farmer_f_3", 3), + PickpocketNpc("npc.varlamore_farmer_f_4", 3), + PickpocketNpc("npc.kastori_farmer_m_1", 3), + PickpocketNpc("npc.kastori_farmer_m_2", 3), + PickpocketNpc("npc.kastori_farmer_f_1", 3), + PickpocketNpc("npc.kastori_farmer_f_2", 3), + PickpocketNpc("npc.tal_teklan_farmer", 3), + ) + + val HAM_MEMBER: List = + listOf( + PickpocketNpc("npc.favour_male_ham_civilian", 1), + PickpocketNpc("npc.favour_female_ham_civilian", 1), + ) + + val WARRIOR: List = + listOf( + PickpocketNpc("npc.warrior_woman", 3), + PickpocketNpc("npc.al_kharid_warrior", 3), + PickpocketNpc("npc.warrior_woman_variant01", 3), + PickpocketNpc("npc.warrior_woman_variant02", 3), + PickpocketNpc("npc.warrior_man", 3), + PickpocketNpc("npc.warrior_man_variant01", 3), + PickpocketNpc("npc.warrior_man_variant02", 3), + ) + + val VILLAGER: List = + listOf( + PickpocketNpc("npc.feud_villager_1_1", 3), + PickpocketNpc("npc.feud_villager_1_2", 3), + PickpocketNpc("npc.feud_villager_1_3", 3), + PickpocketNpc("npc.feud_villager_2_1", 3), + PickpocketNpc("npc.feud_villager_2_2", 3), + PickpocketNpc("npc.feud_villager_2_3", 3), + PickpocketNpc("npc.feud_villager_3_1", 3), + PickpocketNpc("npc.feud_villager_3_2", 3), + PickpocketNpc("npc.feud_villager_3_3", 3), + ) + + val ROGUE: List = + listOf( + PickpocketNpc("npc.rogue", 3), + ) + + val CAVE_GOBLIN: List = + listOf( + PickpocketNpc("npc.dorgesh_male_1", 3), + PickpocketNpc("npc.dorgesh_male_2", 3), + PickpocketNpc("npc.dorgesh_male_3", 3), + PickpocketNpc("npc.dorgesh_male_4", 3), + PickpocketNpc("npc.dorgesh_male_5", 3), + PickpocketNpc("npc.dorgesh_male_6", 3), + PickpocketNpc("npc.dorgesh_male_7", 3), + PickpocketNpc("npc.dorgesh_male_8", 3), + PickpocketNpc("npc.dorgesh_male_9", 3), + PickpocketNpc("npc.dorgesh_female_1", 3), + PickpocketNpc("npc.dorgesh_female_2", 3), + PickpocketNpc("npc.dorgesh_female_3", 3), + PickpocketNpc("npc.dorgesh_female_4", 3), + PickpocketNpc("npc.dorgesh_female_5", 3), + PickpocketNpc("npc.dorgesh_female_6", 3), + PickpocketNpc("npc.dorgesh_female_7", 3), + PickpocketNpc("npc.dorgesh_female_8", 3), + PickpocketNpc("npc.dorgesh_female_9", 3), + ) + + val MASTER_FARMER: List = + listOf( + PickpocketNpc("npc.master_farmer_1", 3), + PickpocketNpc("npc.master_farmer_2", 3), + PickpocketNpc("npc.martin_the_master_farmer", 3), + PickpocketNpc("npc.master_farmer_1_f", 3), + PickpocketNpc("npc.master_farmer_2_f", 3), + PickpocketNpc("npc.varlamore_master_farmer_m_1", 3), + PickpocketNpc("npc.varlamore_master_farmer_m_2", 3), + PickpocketNpc("npc.varlamore_master_farmer_m_3", 3), + PickpocketNpc("npc.varlamore_master_farmer_m_4", 3), + PickpocketNpc("npc.varlamore_master_farmer_f_1", 3), + PickpocketNpc("npc.varlamore_master_farmer_f_2", 3), + PickpocketNpc("npc.varlamore_master_farmer_f_3", 3), + PickpocketNpc("npc.varlamore_master_farmer_f_4", 3), + PickpocketNpc("npc.kastori_master_farmer_m_1", 3), + PickpocketNpc("npc.kastori_master_farmer_m_2", 3), + PickpocketNpc("npc.kastori_master_farmer_f_1", 3), + PickpocketNpc("npc.kastori_master_farmer_f_2", 3), + ) + + val GUARD: List = + listOf( + PickpocketNpc("npc.hos_town_guard_01", 3), + PickpocketNpc("npc.hos_town_guard_02", 3), + PickpocketNpc("npc.hos_town_guard_03", 3), + PickpocketNpc("npc.hos_town_guard_04", 3), + PickpocketNpc("npc.jail_guard_1", 3), + PickpocketNpc("npc.jail_guard_2", 3), + PickpocketNpc("npc.jail_guard_3", 3), + PickpocketNpc("npc.jail_guard_4", 3), + PickpocketNpc("npc.jail_guard_5", 3), + PickpocketNpc("npc.fai_varrock_guard", 3), + PickpocketNpc("npc.fai_varrock_guard_captain", 3), + PickpocketNpc("npc.guard1", 3), + PickpocketNpc("npc.fai_falador_guard1", 3), + PickpocketNpc("npc.fai_falador_guard2", 3), + PickpocketNpc("npc.fai_falador_guard3", 3), + PickpocketNpc("npc.fai_falador_guard4", 3), + PickpocketNpc("npc.fai_falador_guard5", 3), + PickpocketNpc("npc.fai_falador_guard6", 3), + PickpocketNpc("npc.falador_doric_area_guard", 3), + PickpocketNpc("npc.dttd_ham_guard_1_postquest", 1), + PickpocketNpc("npc.dttd_ham_guard_2_postquest", 1), + PickpocketNpc("npc.dttd_ham_guard_3_postquest", 1), + PickpocketNpc("npc.dttd_ham_guard_4_postquest", 1), + PickpocketNpc("npc.dttd_ham_guard_5_postquest", 1), + PickpocketNpc("npc.ardougne_guard", 3), + PickpocketNpc("npc.kourend_guard_m1", 3), + PickpocketNpc("npc.kourend_guard_m1_big", 3), + PickpocketNpc("npc.kourend_guard_m2", 3), + PickpocketNpc("npc.kourend_guard_m2_big", 3), + PickpocketNpc("npc.kourend_guard_m3", 3), + PickpocketNpc("npc.kourend_guard_m3_big", 3), + PickpocketNpc("npc.kourend_guard_m4", 3), + PickpocketNpc("npc.kourend_guard_m4_big", 3), + PickpocketNpc("npc.kourend_guard_f1", 3), + PickpocketNpc("npc.kourend_guard_f1_big", 3), + PickpocketNpc("npc.kourend_guard_f2", 3), + PickpocketNpc("npc.kourend_guard_f2_big", 3), + PickpocketNpc("npc.kourend_guard_f3", 3), + PickpocketNpc("npc.kourend_guard_f3_big", 3), + PickpocketNpc("npc.kourend_guard_f4", 3), + PickpocketNpc("npc.kourend_guard_f4_big", 3), + PickpocketNpc("npc.fai_varrock_guard02", 3), + PickpocketNpc("npc.fai_varrock_guard02_variant01", 3), + PickpocketNpc("npc.fai_varrock_guard02_variant02", 3), + PickpocketNpc("npc.fai_varrock_guard02_f", 3), + PickpocketNpc("npc.fai_varrock_guard02_f_variant01", 3), + PickpocketNpc("npc.fai_varrock_guard02_f_variant02", 3), + PickpocketNpc("npc.fai_varrock_guard_captain02", 3), + PickpocketNpc("npc.guard1_variant01", 3), + PickpocketNpc("npc.guard1_f", 3), + PickpocketNpc("npc.guard1_f_variant01", 3), + PickpocketNpc("npc.ardougne_guard_variant01", 3), + PickpocketNpc("npc.ardougne_guard_f", 3), + PickpocketNpc("npc.ardougne_guard_f_variant01", 3), + PickpocketNpc("npc.fai_falador_guard1_variant01", 3), + PickpocketNpc("npc.fai_falador_guard1_f", 3), + PickpocketNpc("npc.fai_falador_guard1_variant02", 3), + PickpocketNpc("npc.fai_falador_guard2_f", 3), + PickpocketNpc("npc.fai_falador_guard3_f", 3), + PickpocketNpc("npc.fai_falador_guard4_f", 3), + PickpocketNpc("npc.varlamore_guard_m_1", 3), + PickpocketNpc("npc.varlamore_guard_m_2", 3), + PickpocketNpc("npc.varlamore_guard_m_3", 3), + PickpocketNpc("npc.varlamore_guard_m_4", 3), + PickpocketNpc("npc.varlamore_guard_m_5", 3), + PickpocketNpc("npc.varlamore_guard_f_1", 3), + PickpocketNpc("npc.varlamore_guard_f_2", 3), + PickpocketNpc("npc.varlamore_guard_f_3", 3), + PickpocketNpc("npc.varlamore_guard_f_4", 3), + PickpocketNpc("npc.varlamore_guard_f_5", 3), + PickpocketNpc("npc.aldarin_guard_m_1", 3), + PickpocketNpc("npc.aldarin_guard_m_2", 3), + PickpocketNpc("npc.aldarin_guard_m_3", 3), + PickpocketNpc("npc.aldarin_guard_m_4", 3), + PickpocketNpc("npc.aldarin_guard_m_5", 3), + PickpocketNpc("npc.aldarin_guard_f_1", 3), + PickpocketNpc("npc.aldarin_guard_f_2", 3), + PickpocketNpc("npc.aldarin_guard_f_3", 3), + PickpocketNpc("npc.aldarin_guard_f_4", 3), + PickpocketNpc("npc.aldarin_guard_f_5", 3), + PickpocketNpc("npc.auburnvale_guard_m_1", 3), + PickpocketNpc("npc.auburnvale_guard_m_2", 3), + PickpocketNpc("npc.auburnvale_guard_m_3", 3), + PickpocketNpc("npc.auburnvale_guard_m_4", 3), + PickpocketNpc("npc.auburnvale_guard_f_1", 3), + PickpocketNpc("npc.auburnvale_guard_f_2", 3), + PickpocketNpc("npc.auburnvale_guard_f_3", 3), + PickpocketNpc("npc.auburnvale_guard_f_4", 3), + PickpocketNpc("npc.tlati_guard_m_1", 3), + PickpocketNpc("npc.tlati_guard_m_2", 3), + PickpocketNpc("npc.tlati_guard_m_3", 3), + PickpocketNpc("npc.tlati_guard_m_4", 3), + PickpocketNpc("npc.tlati_guard_f_1", 3), + PickpocketNpc("npc.tlati_guard_f_2", 3), + PickpocketNpc("npc.tlati_guard_f_3", 3), + PickpocketNpc("npc.tlati_guard_f_4", 3), + PickpocketNpc("npc.port_roberts_guard_m1", 3), + PickpocketNpc("npc.port_roberts_guard_m2", 3), + PickpocketNpc("npc.port_roberts_guard_f1", 3), + PickpocketNpc("npc.port_roberts_guard_f2", 3), + ) + + val FREMENNIK_CITIZEN: List = + listOf( + PickpocketNpc("npc.viking_man", 3), + PickpocketNpc("npc.viking_man2", 3), + PickpocketNpc("npc.viking_man3", 3), + PickpocketNpc("npc.viking_man4", 3), + PickpocketNpc("npc.viking_man5", 3), + PickpocketNpc("npc.viking_woman2", 3), + PickpocketNpc("npc.viking_woman3", 3), + PickpocketNpc("npc.viking_woman4", 3), + PickpocketNpc("npc.viking_woman_indoors", 3), + ) + + val BEARDED_BANDIT: List = + listOf( + PickpocketNpc("npc.feud_arabian_guard1_1", 3), + PickpocketNpc("npc.feud_arabian_guard1_2", 3), + ) + + val WEALTHY_CITIZEN: List = + listOf( + PickpocketNpc("npc.varlamore_wealthy_citizen_a", 1), + PickpocketNpc("npc.varlamore_wealthy_citizen_b", 1), + PickpocketNpc("npc.varlamore_wealthy_citizen_c", 1), + PickpocketNpc("npc.varlamore_wealthy_citizen_d", 1), + ) + + val DESERT_BANDIT: List = + listOf( + PickpocketNpc("npc.fourdiamonds_sword_bandit_1", 3), + PickpocketNpc("npc.fourdiamonds_sword_bandit_free", 3), + ) + + val KNIGHT: List = + listOf( + PickpocketNpc("npc.knight_of_ardougne", 3), + PickpocketNpc("npc.knight_of_ardougne2", 3), + PickpocketNpc("npc.knight_of_ardougne_west_vis", 3), + PickpocketNpc("npc.knight_of_ardougne_f_west_vis", 3), + PickpocketNpc("npc.knight_of_ardougne_f", 3), + PickpocketNpc("npc.varlamore_knight_m_1", 3), + PickpocketNpc("npc.varlamore_knight_m_2", 3), + PickpocketNpc("npc.varlamore_knight_m_3", 3), + PickpocketNpc("npc.varlamore_knight_f_1", 3), + PickpocketNpc("npc.varlamore_knight_f_2", 3), + PickpocketNpc("npc.varlamore_knight_f_3", 3), + ) + + val POLLNIVNIAN_BANDIT: List = + listOf( + PickpocketNpc("npc.feud_arabian_guard2_1", 3), + PickpocketNpc("npc.feud_arabian_guard2_2", 3), + ) + + val PIRATE: List = + listOf( + PickpocketNpc("npc.pirate_pickpocketable_1", 3), + PickpocketNpc("npc.pirate_pickpocketable_2", 3), + PickpocketNpc("npc.pirate_pickpocketable_3", 3), + PickpocketNpc("npc.pirate_pickpocketable_4", 3), + PickpocketNpc("npc.pirate_pickpocketable_5", 3), + ) + + val WATCHMAN: List = + listOf( + PickpocketNpc("npc.yanille_watchman", 3), + ) + + val MENAPHITE_THUG: List = + listOf( + PickpocketNpc("npc.feud_egyptian_doorman_2", 3), + ) + + val PALADIN: List = + listOf( + PickpocketNpc("npc.paladin", 3), + PickpocketNpc("npc.paladin2", 3), + PickpocketNpc("npc.paladin_west_vis", 3), + PickpocketNpc("npc.paladin_west_f_vis", 3), + PickpocketNpc("npc.paladin_variant01", 3), + PickpocketNpc("npc.paladin_variant02", 3), + PickpocketNpc("npc.paladin_f", 3), + PickpocketNpc("npc.paladin_f_variant01", 3), + ) + + val GNOME: List = + listOf( + PickpocketNpc("npc.gnomechildgreen", 3), + PickpocketNpc("npc.gnomechildblue", 3), + PickpocketNpc("npc.gnomechild", 3), + PickpocketNpc("npc.gnomefemale", 3), + PickpocketNpc("npc.gnomefemale_dskinned", 3), + PickpocketNpc("npc.gnome", 3), + PickpocketNpc("npc.browclothedgnome", 3), + PickpocketNpc("npc.darkskinned_gnome", 3), + ) + + val HERO: List = + listOf( + PickpocketNpc("npc.hero", 3), + PickpocketNpc("npc.hero_variant01", 3), + PickpocketNpc("npc.hero_f", 3), + ) + + val VYRE: List = + listOf( + PickpocketNpc("npc.valentin_rasputin", 3), + PickpocketNpc("npc.von_van_von", 3), + PickpocketNpc("npc.vlad_bechstein", 3), + PickpocketNpc("npc.draconis_sanguine", 3), + PickpocketNpc("npc.mort_nightshade", 3), + PickpocketNpc("npc.vampyrus_diaemus", 3), + PickpocketNpc("npc.carnivus_belamorta", 3), + PickpocketNpc("npc.vormar_vakan", 3), + PickpocketNpc("npc.misdrievus_shadum", 3), + PickpocketNpc("npc.vlad_diaemus", 3), + PickpocketNpc("npc.noctillion_lugosi", 3), + PickpocketNpc("npc.alek_constantine", 3), + PickpocketNpc("npc.grigor_rasputin", 3), + PickpocketNpc("npc.haemas_lamescus", 3), + PickpocketNpc("npc.remus_kaninus", 3), + PickpocketNpc("npc.vallessia_dracyula", 3), + PickpocketNpc("npc.violetta_sanguine", 3), + PickpocketNpc("npc.diphylla_bechstein", 3), + PickpocketNpc("npc.episcula_helsing", 3), + PickpocketNpc("npc.vampyressa_van_von", 3), + PickpocketNpc("npc.vallessia_von_pitt", 3), + PickpocketNpc("npc.vonnetta_varnis", 3), + PickpocketNpc("npc.natalidae_shadum", 3), + PickpocketNpc("npc.mortina_daubenton", 3), + PickpocketNpc("npc.lasenna_rasputin", 3), + PickpocketNpc("npc.caninelle_draynar", 3), + PickpocketNpc("npc.valentina_diaemus", 3), + PickpocketNpc("npc.nakasa_jovkai", 3), + PickpocketNpc("npc.crimsonette_van_marr", 3), + PickpocketNpc("npc.pipistrelle_draynar", 3), + ) + + val ELF: List = + listOf( + PickpocketNpc("npc.mourning_town_elf_1", 3), + PickpocketNpc("npc.mourning_town_elf_3", 3), + PickpocketNpc("npc.mourning_town_elf_4", 3), + PickpocketNpc("npc.mourning_town_elf_5_vis", 3), + PickpocketNpc("npc.prif_citizen_miriel", 3), + PickpocketNpc("npc.prif_citizen_curufin", 3), + PickpocketNpc("npc.prif_citizen_enerdhil", 3), + PickpocketNpc("npc.prif_citizen_tatie", 3), + PickpocketNpc("npc.prif_citizen_finduilas", 3), + PickpocketNpc("npc.prif_citizen_gelmir", 3), + PickpocketNpc("npc.prif_citizen_mithrellas", 3), + PickpocketNpc("npc.prif_citizen_erestor", 3), + PickpocketNpc("npc.prif_citizen_lindir", 3), + PickpocketNpc("npc.prif_citizen_idril", 3), + PickpocketNpc("npc.prif_citizen_ingwion", 3), + PickpocketNpc("npc.prif_citizen_thingol", 3), + PickpocketNpc("npc.prif_citizen_elenwe", 3), + PickpocketNpc("npc.prif_citizen_orophin", 3), + PickpocketNpc("npc.prif_citizen_vaire", 3), + PickpocketNpc("npc.prif_citizen_elladan", 3), + PickpocketNpc("npc.prif_citizen_guilin", 3), + PickpocketNpc("npc.prif_citizen_ingwe", 3), + PickpocketNpc("npc.prif_citizen_cirdan", 3), + PickpocketNpc("npc.prif_citizen_glorfindel", 3), + PickpocketNpc("npc.prif_citizen_aredhel", 3), + PickpocketNpc("npc.prif_citizen_celegorm", 3), + PickpocketNpc("npc.prif_citizen_anaire", 3), + PickpocketNpc("npc.prif_citizen_maeglin", 3), + PickpocketNpc("npc.prif_citizen_edrahil", 3), + PickpocketNpc("npc.prif_citizen_fingon", 3), + PickpocketNpc("npc.prif_citizen_salgant", 3), + PickpocketNpc("npc.prif_citizen_celebrian", 3), + PickpocketNpc("npc.prif_citizen_imin", 3), + PickpocketNpc("npc.prif_citizen_oropher", 3), + PickpocketNpc("npc.prif_citizen_fingolfin", 3), + PickpocketNpc("npc.prif_citizen_mahtan", 3), + PickpocketNpc("npc.prif_citizen_indis", 3), + PickpocketNpc("npc.prif_citizen_iminye", 3), + PickpocketNpc("npc.prif_citizen_feanor", 3), + PickpocketNpc("npc.prif_citizen_saeros", 3), + PickpocketNpc("npc.prif_citizen_nellas", 3), + PickpocketNpc("npc.prif_citizen_enelye", 3), + PickpocketNpc("npc.prif_citizen_nerdanel", 3), + PickpocketNpc("npc.prif_citizen_nimloth", 3), + PickpocketNpc("npc.prif_citizen_findis", 3), + PickpocketNpc("npc.prif_citizen_earwen", 3), + PickpocketNpc("npc.prif_citizen_caranthir", 3), + PickpocketNpc("npc.prif_citizen_enel", 3), + PickpocketNpc("npc.prif_citizen_hendor", 3), + PickpocketNpc("npc.prif_citizen_galathil", 3), + PickpocketNpc("npc.prif_citizen_turgon", 3), + PickpocketNpc("npc.prif_citizen_lenwe", 3), + PickpocketNpc("npc.prif_citizen_aranwe", 3), + ) + + val TZHAAR_HUR: List = + listOf( + PickpocketNpc("npc.tzhaar_hur_city1", 3), + PickpocketNpc("npc.tzhaar_hur_city2", 3), + PickpocketNpc("npc.tzhaar_hur_city3", 3), + PickpocketNpc("npc.tzhaar_hur_city4", 3), + PickpocketNpc("npc.tzhaar_hur_city5", 3), + PickpocketNpc("npc.tzhaar_hur_city6", 3), + ) + +} + +data class PickpocketNpc(val npc: String, val op: Int) diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pickpocket/PickpocketScript.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pickpocket/PickpocketScript.kt new file mode 100644 index 000000000..5d770e008 --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pickpocket/PickpocketScript.kt @@ -0,0 +1,161 @@ +package org.rsmod.content.skills.thieving.pickpocket + +import jakarta.inject.Inject +import org.rsmod.api.combat.commons.CombatEffects +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.thievingLvl +import org.rsmod.api.repo.obj.ObjRepository +import org.rsmod.api.script.onOpHeld1 +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.api.script.onOpNpc2 +import org.rsmod.api.script.onOpNpc3 +import org.rsmod.api.script.onOpNpc4 +import org.rsmod.api.script.onOpNpc5 +import org.rsmod.api.stats.xpmod.XpModifiers +import org.rsmod.content.skills.thieving.Thieving +import org.rsmod.content.skills.thieving.Thieving.giveLoot +import org.rsmod.content.skills.thieving.Thieving.giveThievingXp +import org.rsmod.content.skills.thieving.equipment.ThievingEquipment +import org.rsmod.content.skills.thieving.pets.RockyPetManager +import org.rsmod.game.entity.Npc +import org.rsmod.game.hit.HitType +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Pickpocketing. Every npc with a `Pickpocket` op in the cache is grouped under a + * [PickpocketTarget]; a successful attempt hands out the target's loot (coins arrive as the + * target's coin pouch), a failed one has the npc shout, deals the stun damage and holds the + * player for [Thieving.STUN_CYCLES]. + * + * Worn gear matters: gloves of silence and the Thieving cape raise the success chance, the dodgy + * necklace can absorb a failure, and the rogue outfit can double the loot. See + * [ThievingEquipment]. Every success also rolls for Rocky. + */ +class PickpocketScript +@Inject +constructor( + private val xpMods: XpModifiers, + private val objRepo: ObjRepository, + private val equipment: ThievingEquipment, + private val rocky: RockyPetManager, +) : PluginScript() { + + override fun ScriptContext.startup() { + for (target in PickpocketTarget.entries) { + for ((npc, op) in target.npcs) { + when (op) { + 1 -> onOpNpc1(npc) { pickpocket(it.npc, target) } + 2 -> onOpNpc2(npc) { pickpocket(it.npc, target) } + 3 -> onOpNpc3(npc) { pickpocket(it.npc, target) } + 4 -> onOpNpc4(npc) { pickpocket(it.npc, target) } + 5 -> onOpNpc5(npc) { pickpocket(it.npc, target) } + } + } + } + for ((pouch, target) in PickpocketTarget.byPouch) { + onOpHeld1(pouch) { openPouches(target) } + } + } + + private suspend fun ProtectedAccess.pickpocket(npc: Npc, target: PickpocketTarget) { + val name = npc.type.name.lowercase() + if (player.thievingLvl < target.level) { + mes("You need to be at least level ${target.level} Thieving to pick the $name's pocket.") + return + } + if (Thieving.STUN_TIMER in player.timerMap) { + mes("You're still stunned.") + return + } + if (!hasRoomFor(target)) { + mes("You don't have enough inventory space to steal anything.") + return + } + + arriveDelay() + faceEntitySquare(npc) + anim(Thieving.STEAL_ANIM) + spam("You attempt to pick the $name's pocket.") + delay(2) + + if (!npc.isSlotAssigned) { + return + } + + val chance = target.successChance(player.thievingLvl) * equipment.successMultiplier(player) + if (random.of(maxExclusive = 256) < chance.toInt()) { + succeed(npc, target, name) + } else { + fail(npc, target, name) + } + } + + private fun ProtectedAccess.succeed(npc: Npc, target: PickpocketTarget, name: String) { + // Holding 28 pouches already: they burst open to make room for the next one. + val pouch = target.pouch + if (pouch != null && invTotal(inv, pouch) >= MAX_POUCHES) { + spam("You can't carry any more coin pouches, so you open the ones you have.") + openPouches(target) + } + + val multiplier = if (equipment.rollRogueDoubleLoot(player, random)) 2 else 1 + giveLoot(objRepo, target.loot.roll(random), multiplier) + giveThievingXp(xpMods, target.xp) + soundSynth(Thieving.STEAL_SYNTH) + spam("You pick the $name's pocket.") + if (multiplier > 1) { + spam("Your rogue outfit lets you steal twice as much!") + } + rocky.roll(this, target.petBase) + } + + private fun ProtectedAccess.fail(npc: Npc, target: PickpocketTarget, name: String) { + equipment.wearGlovesOfSilence(this) + spam("You fail to pick the $name's pocket.") + npc.facePlayer(player) + npc.say(target.shout) + + if (equipment.dodgyNecklaceProtects(this)) { + return + } + + val damage = random.of(target.stunDamage) + queueHit(source = npc, delay = 1, type = HitType.Typeless, damage = damage) + spotanim(Thieving.STUN_SPOTANIM, height = STUN_SPOTANIM_HEIGHT) + soundSynth(Thieving.STUN_SYNTH) + CombatEffects.stun(player, Thieving.STUN_CYCLES) + mes("You've been stunned!") + } + + /** Coin pouches stack, so an existing stack counts as room; anything else needs a slot. */ + private fun ProtectedAccess.hasRoomFor(target: PickpocketTarget): Boolean { + if (inv.hasFreeSpace()) { + return true + } + val pouch = target.pouch ?: return false + return target.loot.guaranteed.size == 1 && invTotal(inv, pouch) > 0 + } + + /** `Open-all` on a coin pouch: every pouch of that kind is emptied into a single coin stack. */ + private fun ProtectedAccess.openPouches(target: PickpocketTarget) { + val pouch = target.pouch ?: return + val count = invTotal(inv, pouch) + if (count <= 0) { + return + } + if (invDel(inv, pouch, count).failure) { + return + } + var coins = 0 + repeat(count) { coins += random.of(target.coins) } + invAddOrDrop(objRepo, "obj.coins", coins) + val pouches = if (count == 1) "the coin pouch" else "all $count coin pouches" + mes("You open $pouches and find $coins coins.") + } + + private companion object { + const val MAX_POUCHES: Int = 28 + const val STUN_SPOTANIM_HEIGHT: Int = 100 + } +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pickpocket/PickpocketTarget.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pickpocket/PickpocketTarget.kt new file mode 100644 index 000000000..a647b010a --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/pickpocket/PickpocketTarget.kt @@ -0,0 +1,549 @@ +package org.rsmod.content.skills.thieving.pickpocket + +import org.rsmod.content.skills.thieving.LootTable +import org.rsmod.content.skills.thieving.loot + +/** + * A pickpocket target as listed on the OSRS wiki Thieving page: the level and experience, the + * stun damage dealt on failure, the success-rate curve, the Rocky base chance, and the loot. + * + * Success is rolled out of 256. At [level] the chance is [lowChance]; it climbs linearly and is + * guaranteed (255) at [perfectLevel]. Targets that never become fail-proof use a [perfectLevel] + * above 99 so the curve tops out around 240 at level 99. + * + * Coins come as the target's own coin pouch ([pouch]) worth [coins] when opened; a target with no + * pouch in the cache hands the coins out directly. + */ +enum class PickpocketTarget( + val level: Int, + val xp: Double, + val stunDamage: IntRange, + val shout: String, + val lowChance: Int, + val perfectLevel: Int, + val petBase: Int, + val pouch: String?, + val coins: IntRange, + val loot: LootTable, + val npcs: List, +) { + MAN_WOMAN( + level = 1, + xp = 8.0, + stunDamage = 1..1, + shout = "What do you think you're doing?", + lowChance = 180, + perfectLevel = 85, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_citizen", + coins = 3..3, + loot = loot { always("obj.pickpocket_coin_pouch_citizen") }, + npcs = PickpocketNpcs.MAN_WOMAN, + ), + FARMER( + level = 10, + xp = 14.5, + stunDamage = 1..1, + shout = "Cor blimey mate, what are ye doing in me pockets?", + lowChance = 170, + perfectLevel = 90, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_farmer", + coins = 9..9, + loot = + loot { + item(8, "obj.pickpocket_coin_pouch_farmer") + item(1, "obj.potato_seed") + }, + npcs = PickpocketNpcs.FARMER, + ), + HAM_MEMBER( + level = 15, + xp = 22.2, + stunDamage = 1..3, + shout = "Hey! Get your hands off me!", + lowChance = 160, + perfectLevel = 91, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_ham", + coins = 1..21, + loot = + loot { + item(17, "obj.pickpocket_coin_pouch_ham") + item(4, "obj.digsitebuttons") + item(4, "obj.digsitearmour1") + item(4, "obj.digsitesword") + item(3, "obj.bronze_arrow", 1..13) + item(3, "obj.bronze_axe") + item(3, "obj.bronze_dagger") + item(3, "obj.bronze_pickaxe") + item(3, "obj.iron_axe") + item(3, "obj.iron_dagger") + item(3, "obj.iron_pickaxe") + item(3, "obj.leather_armour") + item(2, "obj.steel_arrow", 1..13) + item(2, "obj.steel_axe") + item(2, "obj.steel_dagger") + item(2, "obj.steel_pickaxe") + item(3, "obj.feather", 1..7) + item(3, "obj.thread", 1..10) + item(3, "obj.logs") + item(3, "obj.cow_hide") + item(2, "obj.coal") + item(2, "obj.iron_ore") + item(2, "obj.uncut_jade") + item(2, "obj.uncut_opal") + item(2, "obj.knife") + item(2, "obj.needle") + item(2, "obj.raw_anchovies") + item(2, "obj.raw_chicken") + item(2, "obj.tinderbox") + item(1, "obj.ham_boots") + item(1, "obj.ham_cloak") + item(1, "obj.ham_gloves") + item(1, "obj.ham_hood") + item(1, "obj.ham_badge") + item(1, "obj.ham_robe") + item(1, "obj.ham_shirt") + item(1, "obj.unidentified_guam") + item(1, "obj.unidentified_marentill") + }, + npcs = PickpocketNpcs.HAM_MEMBER, + ), + WARRIOR( + level = 25, + xp = 26.0, + stunDamage = 2..2, + shout = "What do you think you're doing?", + lowChance = 150, + perfectLevel = 93, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_warrior", + coins = 18..18, + loot = loot { always("obj.pickpocket_coin_pouch_warrior") }, + npcs = PickpocketNpcs.WARRIOR, + ), + VILLAGER( + level = 30, + xp = 8.0, + stunDamage = 2..2, + shout = "Get your hands out of my pockets!", + lowChance = 150, + perfectLevel = 120, + petBase = 257_211, + pouch = null, + coins = 1..8, + loot = loot { always("obj.coins", 1..8) }, + npcs = PickpocketNpcs.VILLAGER, + ), + ROGUE( + level = 32, + xp = 36.5, + stunDamage = 2..2, + shout = "Hey! Nobody steals from a rogue!", + lowChance = 150, + perfectLevel = 94, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_rogue", + coins = 25..40, + loot = + loot { + item(60, "obj.pickpocket_coin_pouch_rogue") + item(10, "obj.lockpick") + item(10, "obj.airrune", 8..8) + item(10, "obj.jug_wine") + item(5, "obj.iron_dagger_p") + }, + npcs = PickpocketNpcs.ROGUE, + ), + CAVE_GOBLIN( + level = 36, + xp = 40.0, + stunDamage = 1..1, + shout = "Oi! Get away from me!", + lowChance = 150, + perfectLevel = 120, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_cavegoblin", + coins = 10..50, + loot = + loot { + item(7, "obj.pickpocket_coin_pouch_cavegoblin") + item(1, "obj.dorgesh_bat_shish") + item(1, "obj.dorgesh_crispy_froglegs") + item(1, "obj.dorgesh_wall_beast_fingers") + item(1, "obj.dorgesh_frog_burger") + item(1, "obj.dorgesh_frog_spawn_gumbo") + item(1, "obj.dorgesh_green_gloop_soup") + item(1, "obj.bullseye_lantern_unlit") + item(1, "obj.dorgesh_wire", 1..2) + item(1, "obj.iron_ore", 1..4) + item(1, "obj.oil_lantern_unlit") + item(1, "obj.swamp_tar") + item(1, "obj.tinderbox") + item(1, "obj.torch_unlit") + }, + npcs = PickpocketNpcs.CAVE_GOBLIN, + ), + MASTER_FARMER( + level = 38, + xp = 43.0, + stunDamage = 3..3, + shout = "Cor blimey mate, what are ye doing in me pockets?", + lowChance = 128, + perfectLevel = 94, + petBase = 257_211, + pouch = null, + coins = 0..0, + loot = + loot { + item(1770, "obj.potato_seed", 1..4) + item(1328, "obj.onion_seed", 1..3) + item(694, "obj.cabbage_seed", 1..3) + item(637, "obj.tomato_seed", 1..2) + item(221, "obj.sweetcorn_seed", 1..2) + item(111, "obj.strawberry_seed") + item(53, "obj.watermelon_seed") + item(38, "obj.snape_grass_seed") + item(556, "obj.barley_seed", 1..12) + item(556, "obj.hammerstone_hop_seed", 1..9) + item(418, "obj.asgarnian_hop_seed", 1..6) + item(415, "obj.jute_seed", 1..9) + item(277, "obj.yanillian_hop_seed", 1..6) + item(139, "obj.krandorian_hop_seed", 1..6) + item(70, "obj.wildblood_hop_seed", 1..3) + item(459, "obj.marigold_seed") + item(304, "obj.nasturtium_seed") + item(196, "obj.rosemary_seed") + item(145, "obj.woad_seed") + item(116, "obj.limpwurt_seed") + item(388, "obj.redberry_bush_seed") + item(272, "obj.cadavaberry_bush_seed") + item(194, "obj.dwellberry_bush_seed") + item(78, "obj.jangerberry_bush_seed") + item(28, "obj.whiteberry_bush_seed") + item(11, "obj.poisonivy_bush_seed") + item(20, "obj.mushroom_seed") + item(12, "obj.belladonna_seed") + item(8, "obj.cactus_seed") + item(5, "obj.seaweed_seed") + item(4, "obj.potato_cactus_seed") + item(160, "obj.guam_seed") + item(105, "obj.marrentill_seed") + item(71, "obj.tarromin_seed") + item(49, "obj.harralander_seed") + item(30, "obj.ranarr_seed") + item(23, "obj.toadflax_seed") + item(15, "obj.irit_seed") + item(11, "obj.avantoe_seed") + item(7, "obj.kwuarm_seed") + item(4, "obj.snapdragon_seed") + item(3, "obj.cadantine_seed") + item(2, "obj.lantadyme_seed") + item(1, "obj.dwarf_weed_seed") + item(1, "obj.torstol_seed") + }, + npcs = PickpocketNpcs.MASTER_FARMER, + ), + GUARD( + level = 40, + xp = 46.8, + stunDamage = 2..2, + shout = "Hey, what do you think you're doing?", + lowChance = 128, + perfectLevel = 95, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_guard", + coins = 30..30, + loot = loot { always("obj.pickpocket_coin_pouch_guard") }, + npcs = PickpocketNpcs.GUARD, + ), + FREMENNIK_CITIZEN( + level = 45, + xp = 65.0, + stunDamage = 2..2, + shout = "Outerlander thief! Get away from me!", + lowChance = 128, + perfectLevel = 120, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_fremennik", + coins = 40..40, + loot = loot { always("obj.pickpocket_coin_pouch_fremennik") }, + npcs = PickpocketNpcs.FREMENNIK_CITIZEN, + ), + BEARDED_BANDIT( + level = 45, + xp = 65.0, + stunDamage = 5..5, + shout = "I'll kill you for that!", + lowChance = 128, + perfectLevel = 120, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_bandit", + coins = 40..40, + loot = loot { always("obj.pickpocket_coin_pouch_bandit") }, + npcs = PickpocketNpcs.BEARDED_BANDIT, + ), + WEALTHY_CITIZEN( + level = 50, + xp = 96.0, + stunDamage = 3..3, + shout = "How dare you! Guards!", + lowChance = 128, + perfectLevel = 120, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_varlamore_wealthy", + coins = 60..120, + loot = loot { always("obj.pickpocket_coin_pouch_varlamore_wealthy") }, + npcs = PickpocketNpcs.WEALTHY_CITIZEN, + ), + DESERT_BANDIT( + level = 53, + xp = 79.4, + stunDamage = 3..3, + shout = "What do you think you're doing?", + lowChance = 128, + perfectLevel = 95, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_desertbandit", + coins = 30..30, + loot = + loot { + item(10, "obj.pickpocket_coin_pouch_desertbandit") + item(1, "obj.3doseantipoison") + item(1, "obj.lockpick") + }, + npcs = PickpocketNpcs.DESERT_BANDIT, + ), + KNIGHT( + level = 55, + xp = 84.3, + stunDamage = 3..3, + shout = "What do you think you're doing?", + lowChance = 128, + perfectLevel = 95, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_knight", + coins = 50..50, + loot = loot { always("obj.pickpocket_coin_pouch_knight") }, + npcs = PickpocketNpcs.KNIGHT, + ), + POLLNIVNIAN_BANDIT( + level = 55, + xp = 84.3, + stunDamage = 5..5, + shout = "I'll kill you for that!", + lowChance = 128, + perfectLevel = 120, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_bandit2", + coins = 50..50, + loot = loot { always("obj.pickpocket_coin_pouch_bandit2") }, + npcs = PickpocketNpcs.POLLNIVNIAN_BANDIT, + ), + PIRATE( + level = 60, + xp = 72.0, + stunDamage = 3..3, + shout = "Arr! Keep yer hands off me booty!", + lowChance = 128, + perfectLevel = 120, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_pirate", + coins = 4..55, + loot = + loot { + item(61, "obj.pickpocket_coin_pouch_pirate") + item(6, "obj.iron_dagger") + item(4, "obj.bronze_scimitar") + item(1, "obj.iron_platebody") + item(10, "obj.xbows_crossbow_bolts_iron", 2..12) + item(6, "obj.chaosrune", 2..2) + item(5, "obj.naturerune", 2..2) + item(5, "obj.bronze_arrow", 9..12) + item(2, "obj.airrune", 10..10) + item(2, "obj.earthrune", 9..9) + item(2, "obj.firerune", 5..5) + item(1, "obj.lawrune", 2..2) + item(12, "obj.eye_patch") + item(1, "obj.chefs_hat") + item(1, "obj.iron_bar") + item(1, "obj.uncut_sapphire") + }, + npcs = PickpocketNpcs.PIRATE, + ), + WATCHMAN( + level = 65, + xp = 137.5, + stunDamage = 3..3, + shout = "Hey! Get your hands off me!", + lowChance = 128, + perfectLevel = 120, + petBase = 134_625, + pouch = "obj.pickpocket_coin_pouch_watchman", + coins = 60..60, + loot = + loot { + always("obj.pickpocket_coin_pouch_watchman") + always("obj.bread") + }, + npcs = PickpocketNpcs.WATCHMAN, + ), + MENAPHITE_THUG( + level = 65, + xp = 137.5, + stunDamage = 5..5, + shout = "I'll kill you for that!", + lowChance = 128, + perfectLevel = 120, + petBase = 257_211, + pouch = "obj.pickpocket_coin_pouch_menaphite", + coins = 60..60, + loot = loot { always("obj.pickpocket_coin_pouch_menaphite") }, + npcs = PickpocketNpcs.MENAPHITE_THUG, + ), + PALADIN( + level = 70, + xp = 131.8, + stunDamage = 3..3, + shout = "What do you think you're doing?", + lowChance = 128, + perfectLevel = 120, + petBase = 127_056, + pouch = "obj.pickpocket_coin_pouch_paladin", + coins = 80..80, + loot = + loot { + always("obj.pickpocket_coin_pouch_paladin") + always("obj.chaosrune", 2..2) + }, + npcs = PickpocketNpcs.PALADIN, + ), + GNOME( + level = 75, + xp = 133.3, + stunDamage = 1..1, + shout = "What do you think you're doing?", + lowChance = 128, + perfectLevel = 120, + petBase = 108_718, + pouch = "obj.pickpocket_coin_pouch_gnome", + coins = 300..300, + loot = + loot { + item(8, "obj.pickpocket_coin_pouch_gnome") + item(1, "obj.earthrune") + item(1, "obj.gold_ore") + item(1, "obj.fire_orb") + item(1, "obj.swamp_toad") + item(1, "obj.king_worm") + }, + npcs = PickpocketNpcs.GNOME, + ), + HERO( + level = 80, + xp = 163.3, + stunDamage = 3..3, + shout = "What do you think you're doing?", + lowChance = 128, + perfectLevel = 120, + petBase = 99_175, + pouch = "obj.pickpocket_coin_pouch_hero", + coins = 200..300, + loot = + loot { + item(8, "obj.pickpocket_coin_pouch_hero") + item(1, "obj.deathrune", 2..2) + item(1, "obj.bloodrune") + item(1, "obj.gold_ore") + item(1, "obj.jug_wine") + item(1, "obj.diamond") + item(1, "obj.fire_orb") + }, + npcs = PickpocketNpcs.HERO, + ), + VYRE( + level = 82, + xp = 306.9, + stunDamage = 5..5, + shout = "How dare you touch me, you filthy creature!", + lowChance = 128, + perfectLevel = 120, + petBase = 99_175, + pouch = "obj.pickpocket_coin_pouch_vyre", + coins = 230..315, + loot = + loot { + item(109, "obj.pickpocket_coin_pouch_vyre") + item(8, "obj.deathrune", 2..2) + item(2, "obj.bloodrune", 4..4) + item(6, "obj.blood_pint") + item(5, "obj.uncut_ruby") + item(1, "obj.diamond") + item(1, "obj.cooked_mystery_meat") + rare(5000, "obj.blood_shard") + }, + npcs = PickpocketNpcs.VYRE, + ), + ELF( + level = 85, + xp = 353.3, + stunDamage = 5..5, + shout = "Keep your filthy hands to yourself!", + lowChance = 128, + perfectLevel = 120, + petBase = 99_175, + pouch = "obj.pickpocket_coin_pouch_elf", + coins = 280..350, + loot = + loot { + item(100, "obj.pickpocket_coin_pouch_elf") + item(8, "obj.deathrune", 2..2) + item(6, "obj.naturerune", 3..3) + item(3, "obj.jug_wine") + item(2, "obj.diamond") + item(2, "obj.gold_ore") + item(2, "obj.fire_orb") + rare(1000, "obj.prif_crystal_shard") + rare(1024, "obj.prif_teleport_seed") + }, + npcs = PickpocketNpcs.ELF, + ), + TZHAAR_HUR( + level = 90, + xp = 103.4, + stunDamage = 4..4, + shout = "JalYt thief! Get away from Hur!", + lowChance = 128, + perfectLevel = 120, + petBase = 176_743, + pouch = null, + coins = 1..25, + loot = + loot { + item(20, "obj.tzhaar_token", 1..25) + item(3, "obj.uncut_sapphire") + item(2, "obj.uncut_emerald") + item(1, "obj.uncut_ruby") + item(1, "obj.uncut_diamond") + }, + npcs = PickpocketNpcs.TZHAAR_HUR, + ); + + /** Success chance out of 256 for a player at [thievingLevel]. */ + fun successChance(thievingLevel: Int): Int { + if (thievingLevel >= perfectLevel) { + return 255 + } + val span = perfectLevel - level + val progress = (thievingLevel - level).coerceAtLeast(0) + val chance = lowChance + (255 - lowChance) * progress / span + return chance.coerceIn(lowChance, 255) + } + + companion object { + /** Coin pouch obj -> the target whose coins it holds. */ + val byPouch: Map = + entries.filter { it.pouch != null }.associateBy { it.pouch!! } + } +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/stalls/StallScript.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/stalls/StallScript.kt new file mode 100644 index 000000000..7e7c1dff9 --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/stalls/StallScript.kt @@ -0,0 +1,82 @@ +package org.rsmod.content.skills.thieving.stalls + +import dev.openrune.types.ObjectServerType +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.thievingLvl +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.repo.obj.ObjRepository +import org.rsmod.api.script.onOpLoc2 +import org.rsmod.api.stats.xpmod.XpModifiers +import org.rsmod.content.skills.thieving.Thieving +import org.rsmod.content.skills.thieving.Thieving.giveLoot +import org.rsmod.content.skills.thieving.Thieving.giveThievingXp +import org.rsmod.content.skills.thieving.pets.RockyPetManager +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * `Steal-from` on market stalls. A theft always succeeds once the level is met: the stall is + * swapped for its empty form (or removed) until it restocks, one item from the stall's stock is + * handed over and Rocky is rolled. + */ +class StallScript +@Inject +constructor( + private val locRepo: LocRepository, + private val objRepo: ObjRepository, + private val xpMods: XpModifiers, + private val rocky: RockyPetManager, +) : PluginScript() { + + override fun ScriptContext.startup() { + for (stall in StallTarget.entries) { + for ((loc, empty) in stall.locs) { + onOpLoc2(loc) { steal(it.loc, it.type, stall, empty) } + } + } + } + + private suspend fun ProtectedAccess.steal( + loc: BoundLocInfo, + type: ObjectServerType, + stall: StallTarget, + empty: String?, + ) { + if (player.thievingLvl < stall.level) { + mes("You need to be at least level ${stall.level} Thieving to steal from this stall.") + return + } + if (!inv.hasFreeSpace()) { + mes("You don't have enough inventory space to steal from the ${stall.label}.") + return + } + + arriveDelay() + faceLoc(loc) + anim(Thieving.STEAL_ANIM) + delay(1) + + // Somebody else may have cleared the stall while the animation played. + if (locRepo.findExact(loc.coords, type) == null) { + mes("Someone has already taken everything from the ${stall.label}.") + return + } + if (empty != null) { + locRepo.change(loc, empty, stall.respawnCycles) + } else { + locRepo.del(loc, stall.respawnCycles) + } + + val drops = stall.loot.roll(random) + giveLoot(objRepo, drops) + giveThievingXp(xpMods, stall.xp) + soundSynth(Thieving.STEAL_SYNTH) + val stolen = drops.firstOrNull() + if (stolen != null) { + spam("You steal ${Thieving.withArticle(Thieving.objName(stolen.obj))}.") + } + rocky.roll(this, stall.petBase) + } +} diff --git a/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/stalls/StallTarget.kt b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/stalls/StallTarget.kt new file mode 100644 index 000000000..f52885006 --- /dev/null +++ b/content/skills/thieving/src/main/kotlin/org/rsmod/content/skills/thieving/stalls/StallTarget.kt @@ -0,0 +1,391 @@ +package org.rsmod.content.skills.thieving.stalls + +import org.rsmod.content.skills.thieving.LootTable +import org.rsmod.content.skills.thieving.loot + +/** + * A market stall that can be stolen from. [locs] maps every stocked stall loc to the empty stall + * it turns into for [respawnCycles]; a `null` empty loc means the stall simply disappears until + * it restocks. + */ +enum class StallTarget( + val label: String, + val level: Int, + val xp: Double, + val respawnCycles: Int, + val petBase: Int, + val loot: LootTable, + val locs: Map, +) { + VEGETABLE( + label = "vegetable stall", + level = 2, + xp = 10.0, + respawnCycles = 2, + petBase = 206_777, + loot = + loot { + item(3, "obj.onion") + item(3, "obj.cabbage") + item(3, "obj.potato") + item(3, "obj.tomato") + item(1, "obj.garlic") + }, + locs = + mapOf( + "loc.misc_veg_market" to "loc.viking_market", + "loc.etc_veg_market" to "loc.viking_market", + "loc.port_roberts_market_stall_veg" to "loc.port_roberts_market_stall", + ), + ), + BAKERY( + label = "baker's stall", + level = 5, + xp = 16.0, + respawnCycles = 4, + petBase = 124_066, + loot = + loot { + item(6, "obj.cake") + item(3, "obj.bread") + item(1, "obj.chocolate_slice") + }, + locs = + mapOf( + "loc.cakethiefstall" to "loc.market", + "loc.hos_stall_bread" to "loc.hos_stall_empty", + "loc.dwarf_market_bakery" to "loc.dwarf_market_empty_stall", + "loc.fortis_market_stall_bakers" to "loc.fortis_market_stall", + ), + ), + TEA( + label = "tea stall", + level = 5, + xp = 16.0, + respawnCycles = 4, + petBase = 68_926, + loot = loot { always("obj.cup_of_tea") }, + locs = + mapOf( + "loc.tea_stall" to "loc.market", + "loc.icthalarins_tea_stall" to "loc.icthalarins_market", + "loc.contact_tea_stall" to "loc.contact_market", + ), + ), + MONKEY_FOOD( + label = "food stall", + level = 5, + xp = 16.0, + respawnCycles = 6, + petBase = 47_718, + loot = loot { always("obj.banana") }, + locs = mapOf("loc.mm_stall_food" to null), + ), + CRAFTING( + label = "crafting stall", + level = 5, + xp = 20.0, + respawnCycles = 8, + petBase = 47_718, + loot = + loot { + item(4, "obj.chisel") + item(2, "obj.ring_mould") + item(2, "obj.necklace_mould") + item(1, "obj.amulet_mould") + item(1, "obj.gold_bar") + }, + locs = + mapOf( + "loc.mm_stall_crafting" to null, + "loc.dwarf_market_crafting" to "loc.dwarf_market_empty_stall", + ), + ), + MONKEY_GENERAL( + label = "general stall", + level = 5, + xp = 25.0, + respawnCycles = 8, + petBase = 47_718, + loot = + loot { + item(1, "obj.pot_empty") + item(1, "obj.hammer") + item(1, "obj.tinderbox") + }, + locs = mapOf("loc.mm_stall_general" to null), + ), + ROCK_CAKE( + label = "counter", + level = 15, + xp = 6.4, + respawnCycles = 12, + petBase = 47_718, + loot = loot { always("obj.rockcake") }, + locs = mapOf("loc.rockcounter_withcakes" to "loc.empty_rock_cake_counter"), + ), + SILK( + label = "silk stall", + level = 20, + xp = 24.0, + respawnCycles = 8, + petBase = 68_926, + loot = loot { always("obj.silk") }, + locs = + mapOf( + "loc.silkthiefstall" to "loc.market", + "loc.prif_marketstall_silk" to "loc.prif_marketstall_empty", + "loc.fortis_market_stall_silk" to "loc.fortis_market_stall", + "loc.port_roberts_market_stall_silk" to "loc.port_roberts_market_stall", + ), + ), + FRUIT( + label = "fruit stall", + level = 25, + xp = 28.5, + respawnCycles = 4, + petBase = 124_066, + loot = + loot { + item(6, "obj.cooking_apple") + item(6, "obj.banana") + item(4, "obj.strawberry") + item(2, "obj.jangerberries") + item(2, "obj.lemon") + item(2, "obj.redberries") + item(2, "obj.lime") + item(1, "obj.pineapple") + item(1, "obj.papaya") + item(1, "obj.golovanova_top") + }, + locs = mapOf("loc.hos_fruit_stall_02" to null), + ), + SEED( + label = "seed stall", + level = 27, + xp = 10.0, + respawnCycles = 4, + petBase = 36_490, + loot = + loot { + item(40, "obj.potato_seed", 1..4) + item(30, "obj.onion_seed", 1..3) + item(25, "obj.cabbage_seed", 1..3) + item(20, "obj.tomato_seed", 1..2) + item(10, "obj.sweetcorn_seed") + item(6, "obj.strawberry_seed") + item(3, "obj.watermelon_seed") + item(8, "obj.marigold_seed") + item(6, "obj.rosemary_seed") + item(6, "obj.nasturtium_seed") + item(4, "obj.woad_seed") + item(3, "obj.limpwurt_seed") + item(5, "obj.barley_seed", 1..4) + item(4, "obj.hammerstone_hop_seed", 1..4) + item(3, "obj.asgarnian_hop_seed", 1..3) + item(3, "obj.jute_seed", 1..3) + item(2, "obj.yanillian_hop_seed", 1..2) + item(1, "obj.krandorian_hop_seed") + item(1, "obj.guam_seed") + item(1, "obj.marrentill_seed") + }, + locs = mapOf("loc.seed_stall" to "loc.market"), + ), + FUR( + label = "fur stall", + level = 35, + xp = 45.0, + respawnCycles = 12, + petBase = 36_490, + loot = + loot { + item(9, "obj.fur") + item(1, "obj.grey_wolf_fur") + }, + locs = + mapOf( + "loc.furthiefstall" to "loc.market", + "loc.viking_fur_market" to "loc.viking_market", + "loc.fortis_market_stall_fur" to "loc.fortis_market_stall", + "loc.port_roberts_market_stall_fur" to "loc.port_roberts_market_stall", + ), + ), + FISH( + label = "fish stall", + level = 42, + xp = 42.0, + respawnCycles = 12, + petBase = 36_490, + loot = + loot { + item(4, "obj.raw_salmon") + item(3, "obj.raw_tuna") + item(1, "obj.raw_lobster") + }, + locs = + mapOf( + "loc.viking_fish_market" to "loc.viking_market", + "loc.misc_fish_market" to "loc.viking_market", + "loc.etc_fish_market" to "loc.viking_market", + "loc.fish_stall_warrens" to null, + "loc.port_roberts_market_stall_fish" to "loc.port_roberts_market_stall", + ), + ), + CROSSBOW( + label = "crossbow stall", + level = 49, + xp = 52.0, + respawnCycles = 8, + petBase = 36_490, + loot = + loot { + item(4, "obj.bolt", 1..12) + item(2, "obj.xbows_crossbow_bolts_mithril", 1..3) + item(3, "obj.xbows_crossbow_limbs_bronze") + item(3, "obj.xbows_crossbow_stock_wood") + }, + locs = mapOf("loc.xbows_dwarf_market" to "loc.dwarf_market_empty_stall"), + ), + SILVER( + label = "silver stall", + level = 50, + xp = 205.0, + respawnCycles = 32, + petBase = 36_490, + loot = + loot { + item(5, "obj.silver_ore") + item(2, "obj.silver_bar") + item(1, "obj.tiara") + }, + locs = + mapOf( + "loc.silverthiefstall" to "loc.market", + "loc.dwarf_market_silver" to "loc.dwarf_market_empty_stall", + "loc.prif_marketstall_silver" to "loc.prif_marketstall_empty", + "loc.port_roberts_market_stall_silver" to "loc.port_roberts_market_stall", + ), + ), + SPICE( + label = "spice stall", + level = 65, + xp = 92.0, + respawnCycles = 10, + petBase = 36_490, + loot = loot { always("obj.spicespot") }, + locs = + mapOf( + "loc.spicethiefstall" to "loc.market", + "loc.prif_marketstall_spice" to "loc.prif_marketstall_empty", + "loc.fortis_market_stall_spice" to "loc.fortis_market_stall", + "loc.port_roberts_market_stall_spice" to "loc.port_roberts_market_stall", + ), + ), + MAGIC( + label = "magic stall", + level = 65, + xp = 90.0, + respawnCycles = 12, + petBase = 36_490, + loot = + loot { + item(10, "obj.airrune", 1..10) + item(10, "obj.earthrune", 1..10) + item(10, "obj.firerune", 1..10) + item(4, "obj.naturerune", 1..3) + item(2, "obj.lawrune", 1..2) + }, + locs = mapOf("loc.mm_stall_magic" to null), + ), + SCIMITAR( + label = "scimitar stall", + level = 65, + xp = 210.0, + respawnCycles = 32, + petBase = 36_490, + loot = + loot { + item(6, "obj.iron_scimitar") + item(3, "obj.steel_scimitar") + item(2, "obj.mithril_scimitar") + item(1, "obj.adamant_scimitar") + }, + locs = mapOf("loc.mm_stall_scimitar" to null), + ), + GEM( + label = "gem stall", + level = 75, + xp = 408.0, + respawnCycles = 100, + petBase = 36_490, + loot = + loot { + item(8, "obj.uncut_sapphire") + item(4, "obj.uncut_emerald") + item(2, "obj.uncut_ruby") + item(1, "obj.uncut_diamond") + }, + locs = + mapOf( + "loc.gemthiefstall" to "loc.market", + "loc.dwarf_market_gems" to "loc.dwarf_market_empty_stall", + "loc.prif_marketstall_gem" to "loc.prif_marketstall_empty", + "loc.fortis_market_stall_gems" to "loc.fortis_market_stall", + "loc.port_roberts_market_stall_gems" to "loc.port_roberts_market_stall", + ), + ), + TZHAAR_GEM( + label = "gem stall", + level = 75, + xp = 160.0, + respawnCycles = 100, + petBase = 36_490, + loot = + loot { + item(8, "obj.uncut_sapphire") + item(4, "obj.uncut_emerald") + item(2, "obj.uncut_ruby") + item(1, "obj.uncut_diamond") + }, + locs = mapOf("loc.tzhaar_shopcounter_gem" to "loc.tzhaar_shopcounter_empty"), + ), + ORE( + label = "ore stall", + level = 82, + xp = 350.0, + respawnCycles = 50, + petBase = 36_490, + loot = + loot { + item(8, "obj.coal") + item(8, "obj.iron_ore") + item(4, "obj.silver_ore") + item(4, "obj.gold_ore") + item(2, "obj.mithril_ore") + item(1, "obj.adamantite_ore") + item(1, "obj.runite_ore") + }, + locs = + mapOf( + "loc.tzhaar_shopcounter_ore" to "loc.tzhaar_shopcounter_empty", + "loc.port_roberts_market_stall_ore" to "loc.port_roberts_market_stall", + ), + ), + CANNONBALL( + label = "cannonball stall", + level = 87, + xp = 223.0, + respawnCycles = 4, + petBase = 36_490, + loot = + loot { + item(8, "obj.bronze_cannonball", 1..4) + item(6, "obj.iron_cannonball", 1..4) + item(4, "obj.mcannonball", 1..4) + item(2, "obj.mithril_cannonball", 1..3) + item(1, "obj.adamant_cannonball", 1..2) + item(1, "obj.rune_cannonball", 1..2) + }, + locs = mapOf("loc.port_roberts_market_stall_cball" to "loc.port_roberts_market_stall"), + ), +} diff --git a/content/skills/thieving/src/main/resources/gamevals.toml b/content/skills/thieving/src/main/resources/gamevals.toml new file mode 100644 index 000000000..e356cfaff --- /dev/null +++ b/content/skills/thieving/src/main/resources/gamevals.toml @@ -0,0 +1,7 @@ +[gamevals.timer] +# Per-cycle soft timer that keeps Rocky following its owner. +pet_rocky_tick = 121 + +[gamevals.synth] +# "stunned" sound from the OSRS wiki list of sound IDs; plays when a failed pickpocket stuns you. +thieving_stunned = 2727 diff --git a/content/travel/fairy-rings/build.gradle.kts b/content/travel/fairy-rings/build.gradle.kts new file mode 100644 index 000000000..a7e5cb889 --- /dev/null +++ b/content/travel/fairy-rings/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) +} diff --git a/content/travel/fairy-rings/src/main/kotlin/org/rsmod/content/travel/fairyrings/FairyRingDials.kt b/content/travel/fairy-rings/src/main/kotlin/org/rsmod/content/travel/fairyrings/FairyRingDials.kt new file mode 100644 index 000000000..d46810975 --- /dev/null +++ b/content/travel/fairy-rings/src/main/kotlin/org/rsmod/content/travel/fairyrings/FairyRingDials.kt @@ -0,0 +1,47 @@ +package org.rsmod.content.travel.fairyrings + +/** + * The three dials of the fairy ring interface. + * + * Each dial is a value from 0 to 3 held in `varbit.fairyring_1`, `varbit.fairyring_2` and + * `varbit.fairyring_3`; rotating clockwise adds one and anticlockwise subtracts one. The letter a + * value stands for follows the cache's `dbtable.fairyring`, whose `id` column is the three dial + * values written as a base-ten number (`AIP` = 0, `AIS` = 1, `DIP` = 100, `BJQ` = 333), which + * pins the order down as A D C B, I L K J and P S R Q. + */ +object FairyRingDials { + const val COUNT: Int = 3 + const val POSITIONS: Int = 4 + + private val LETTERS: List> = + listOf(listOf('A', 'D', 'C', 'B'), listOf('I', 'L', 'K', 'J'), listOf('P', 'S', 'R', 'Q')) + + /** Turns three dial values into the code they spell, e.g. `(0, 0, 3)` into `"AIQ"`. */ + fun code(dials: List): String { + require(dials.size == COUNT) { "Expected $COUNT dial values, got $dials." } + return dials + .mapIndexed { dial, value -> + LETTERS[dial].getOrNull(value) ?: error("Dial ${dial + 1} cannot show $value.") + } + .joinToString("") + } + + /** The dial values that spell [code], or `null` when [code] is not a fairy ring code. */ + fun dials(code: String): List? { + val letters = code.uppercase().filter { it != ' ' } + if (letters.length != COUNT) { + return null + } + val dials = letters.mapIndexed { dial, letter -> LETTERS[dial].indexOf(letter) } + return dials.takeIf { values -> values.none { it == -1 } } + } + + /** The `dbcol.fairyring:id` value for the code spelt by [dials]. */ + fun tableId(dials: List): Int = dials.fold(0) { acc, value -> acc * 10 + value } + + /** Inverse of [tableId]. */ + fun fromTableId(id: Int): List = listOf(id / 100 % 10, id / 10 % 10, id % 10) + + /** Wraps a rotated dial value back into `0 until 4`. */ + fun rotate(value: Int, steps: Int): Int = Math.floorMod(value + steps, POSITIONS) +} diff --git a/content/travel/fairy-rings/src/main/kotlin/org/rsmod/content/travel/fairyrings/FairyRingScript.kt b/content/travel/fairy-rings/src/main/kotlin/org/rsmod/content/travel/fairyrings/FairyRingScript.kt new file mode 100644 index 000000000..5504c8f3b --- /dev/null +++ b/content/travel/fairy-rings/src/main/kotlin/org/rsmod/content/travel/fairyrings/FairyRingScript.kt @@ -0,0 +1,288 @@ +package org.rsmod.content.travel.fairyrings + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.aconverted.interf.IfButtonOp +import dev.openrune.util.Wearpos +import jakarta.inject.Inject +import org.rsmod.api.area.checker.AreaChecker +import org.rsmod.api.player.hook.PlayerTeleportValidator +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.output.ChatType +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.vars.intVarBit +import org.rsmod.api.script.onIfModalButton +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.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Fairy rings: the mushroom circles that teleport a player wielding a dramen or lunar staff to + * whichever ring the three-letter code on the dials names. + * + * What the cache provides and this script leans on: + * - `dbtable.fairyring` lists every code with its landing tile, its travel log entry and its + * `multiloc_state` index; see [FairyRings]. + * - The ring interface (`interface.fairyrings`) rotates its dials client-side by writing + * `varbit.fairyring_1..3` and sends each rotation as a button press, so the server mirrors the + * rotation into the same varbits and reads them back when `Confirm` arrives. + * - The travel log (`interface.fairyrings_log`) draws only the entries whose text is non-empty, + * so the visited codes and the favourites are written into their components before the pair is + * opened, and again (with the side panel re-opened) whenever a favourite changes. + * - Favourite slots are `varbit.fairyring_fave__1..3` (the dial values) plus + * `varbit.fairyring_fave_set_`; the last destination is `varbit.fairyring_lastloc`. + * + * Codes the cache points at its placeholder tile, or that match the tile the player stands on, + * only earn the "hardly moved at all" line, as they do officially. + */ +class FairyRingScript +@Inject +constructor( + private val teleportValidator: PlayerTeleportValidator, + private val areaChecker: AreaChecker, +) : PluginScript() { + private lateinit var rings: FairyRings + private val staffs: Set = STAFFS.map { it.asRSCM(RSCMType.OBJ) }.toSet() + + private var ProtectedAccess.lastDestination by intVarBit("varbit.fairyring_lastloc") + + override fun ScriptContext.startup() { + rings = FairyRings.load() + + for (loc in RING_LOCS) { + onOpLoc1(loc) { zanaris() } + onOpLoc2(loc) { configure() } + onOpLoc3(loc) { lastDestination() } + onOpLoc4(loc) { favourites() } + } + + for ((dial, varbit) in DIAL_VARBITS.withIndex()) { + onIfModalButton("component.fairyrings:${dial + 1}_clockwise") { rotate(varbit, 1) } + onIfModalButton("component.fairyrings:${dial + 1}_anticlockwise") { + rotate(varbit, -1) + } + } + onIfModalButton("component.fairyrings:confirm") { confirm() } + + for (ring in rings.all) { + onIfModalButton(ring.logComponent) { + when (it.op) { + IfButtonOp.Op1 -> useCode(ring) + IfButtonOp.Op2 -> addFavourite(ring) + else -> {} + } + } + onIfModalButton(ring.favouriteIconComponent) { addFavourite(ring) } + } + for (slot in 1..FAVOURITE_SLOTS) { + onIfModalButton("component.fairyrings_log:fave_$slot") { + when (it.op) { + IfButtonOp.Op1 -> favourite(slot)?.let { ring -> useCode(ring) } + IfButtonOp.Op2 -> removeFavourite(slot) + else -> {} + } + } + onIfModalButton("component.fairyrings_log:fave_icon_$slot") { removeFavourite(slot) } + } + } + + /* Ring ops */ + + private suspend fun ProtectedAccess.zanaris() { + val ring = rings.byCode(ZANARIS_CODE) ?: return + travel(ring) + } + + private fun ProtectedAccess.configure() { + if (!hasFairyMagic()) { + mes(NO_FAIRY_MAGIC) + return + } + refreshLog() + ifOpenMainSidePair(RING_INTERFACE, LOG_INTERFACE) + } + + private suspend fun ProtectedAccess.lastDestination() { + val ring = rings.byLogIndex(lastDestination) + if (ring == null || ring.destination == null) { + mes("You have not travelled anywhere by fairy ring yet.") + return + } + travel(ring) + } + + private suspend fun ProtectedAccess.favourites() { + if (!hasFairyMagic()) { + mes(NO_FAIRY_MAGIC) + return + } + val favourites = (1..FAVOURITE_SLOTS).mapNotNull { favourite(it) } + if (favourites.isEmpty()) { + mes("You have no favourite fairy ring codes. Add some from the travel log.") + return + } + val labels = favourites.map { ring -> "${ring.code} - ${ring.description ?: "Nowhere"}" } + val picked = menu("Favourite fairy rings", hotkeys = true, choices = labels + "Cancel") + val ring = favourites.getOrNull(picked) ?: return + travel(ring) + } + + /* Ring interface */ + + private fun ProtectedAccess.rotate(varbit: String, steps: Int) { + vars[varbit] = FairyRingDials.rotate(vars[varbit], steps) + } + + private suspend fun ProtectedAccess.confirm() { + val ring = rings.byDials(dials()) ?: return + travel(ring) + } + + private fun ProtectedAccess.dials(): List = DIAL_VARBITS.map { vars[it] } + + /** Spins the dials round to [ring]'s code; the client redraws them from the varbits. */ + private fun ProtectedAccess.useCode(ring: FairyRing) { + for ((dial, varbit) in DIAL_VARBITS.withIndex()) { + vars[varbit] = ring.dials[dial] + } + } + + /* Travel log */ + + private fun ProtectedAccess.refreshLog() { + for (ring in rings.all) { + val text = if (hasVisited(ring)) ring.logText else "" + ifSetText(ring.logComponent, text) + } + for (slot in 1..FAVOURITE_SLOTS) { + val ring = favourite(slot) + // The favourites list draws the code from `fave_code_` (in its own colour) over + // the entry, so the entry itself only carries the location on its second line. + val description = ring?.description?.let { "
$it" }.orEmpty() + ifSetText("component.fairyrings_log:fave_$slot", description) + ifSetText("component.fairyrings_log:fave_code_$slot", ring?.spacedCode.orEmpty()) + } + } + + private fun ProtectedAccess.reopenLog() { + refreshLog() + ifOpenSide(LOG_INTERFACE) + } + + private fun ProtectedAccess.hasVisited(ring: FairyRing): Boolean { + val varbit = ring.visitedVarBit ?: return false + return vars[varbit] != 0 + } + + private fun ProtectedAccess.markVisited(ring: FairyRing) { + val varbit = ring.visitedVarBit ?: return + vars[varbit] = 1 + } + + /* Favourites */ + + private fun ProtectedAccess.favourite(slot: Int): FairyRing? { + if (vars["varbit.fairyring_fave_set_$slot"] == 0) { + return null + } + val dials = (1..FairyRingDials.COUNT).map { vars["varbit.fairyring_fave_${slot}_$it"] } + return rings.byDials(dials) + } + + private fun ProtectedAccess.addFavourite(ring: FairyRing) { + if ((1..FAVOURITE_SLOTS).any { favourite(it) == ring }) { + mes("${ring.code} is already one of your favourite codes.") + return + } + val slot = (1..FAVOURITE_SLOTS).firstOrNull { favourite(it) == null } + if (slot == null) { + mes("You can only have $FAVOURITE_SLOTS favourite fairy ring codes.") + return + } + for ((dial, value) in ring.dials.withIndex()) { + vars["varbit.fairyring_fave_${slot}_${dial + 1}"] = value + } + vars["varbit.fairyring_fave_set_$slot"] = 1 + reopenLog() + } + + private fun ProtectedAccess.removeFavourite(slot: Int) { + if (favourite(slot) == null) { + return + } + vars["varbit.fairyring_fave_set_$slot"] = 0 + reopenLog() + } + + /* Teleport */ + + private fun ProtectedAccess.hasFairyMagic(): Boolean { + val weapon = player.worn[Wearpos.RightHand.slot] ?: return false + return weapon.id in staffs + } + + private suspend fun ProtectedAccess.travel(ring: FairyRing) { + if (!hasFairyMagic()) { + mes(NO_FAIRY_MAGIC) + return + } + if (actionDelay > mapClock) { + return + } + val denial = teleportValidator.validate(player, TeleportType.Standard, areaChecker) + if (denial != null) { + mes(denial, ChatType.Engine) + return + } + ifClose() + actionDelay = mapClock + ACTION_DELAY + anim(VANISH_ANIM) + spotanim(FLOWER_RING) + soundSynth(TELEPORT_SOUND) + delay(VANISH_DELAY) + val destination = ring.destination + if (destination == null || destination == coords) { + anim(APPEAR_ANIM) + mes(HARDLY_MOVED) + return + } + // Validated before the ring started spinning; a teleblock landing mid-spin must not stop + // a teleport that has already begun. + telejump(destination, TeleportType.Exempt) + anim(APPEAR_ANIM) + markVisited(ring) + lastDestination = ring.logIndex + } + + private companion object { + private const val RING_INTERFACE = "interface.fairyrings" + private const val LOG_INTERFACE = "interface.fairyrings_log" + + /** Every ring the player can operate; the Zanaris hub has no `Zanaris` op of its own. */ + private val RING_LOCS = + listOf("loc.fairyring_minorhub", "loc.fairyring_homehub", "loc.poh_fairy_ring") + + private val STAFFS = listOf("obj.dramen_staff", "obj.lunar_moonclan_liminal_staff") + + private val DIAL_VARBITS = + listOf("varbit.fairyring_1", "varbit.fairyring_2", "varbit.fairyring_3") + + private const val FAVOURITE_SLOTS = 10 + private const val ZANARIS_CODE = "BKS" + + private const val NO_FAIRY_MAGIC = "The fairy rings only work for those who wield fairy magic." + private const val HARDLY_MOVED = "Wow, fairy magic sure is useful, I hardly moved at all!" + + private const val VANISH_ANIM = "seq.human_fairy_vanish" + private const val APPEAR_ANIM = "seq.human_fairy_appear" + + /** The ring of flowers that springs up around a player as the fairy magic takes them. */ + private const val FLOWER_RING = "spotanim.fairy_flower_ring" + private const val TELEPORT_SOUND = "synth.fairy_teleport" + private const val VANISH_DELAY = 2 + private const val ACTION_DELAY = 4 + } +} diff --git a/content/travel/fairy-rings/src/main/kotlin/org/rsmod/content/travel/fairyrings/FairyRings.kt b/content/travel/fairy-rings/src/main/kotlin/org/rsmod/content/travel/fairyrings/FairyRings.kt new file mode 100644 index 000000000..e5d428c4b --- /dev/null +++ b/content/travel/fairy-rings/src/main/kotlin/org/rsmod/content/travel/fairyrings/FairyRings.kt @@ -0,0 +1,102 @@ +package org.rsmod.content.travel.fairyrings + +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.definition.constants.ConstantProvider +import org.rsmod.api.table.FairyringRow +import org.rsmod.map.CoordGrid + +/** + * One three-letter code and where it leads. + * + * @param code The letters without spaces, e.g. `"AIQ"`. + * @param dials The dial values that spell [code]. + * @param logIndex The code's `multiloc_state` in the cache table, which is also what + * `varbit.fairyring_lastloc` stores for the ring's `Last-destination` op. + * @param destination Where the code lands the player, or `null` for codes that go nowhere (the + * cache points those at a placeholder tile so the player "hardly moves at all"). + * @param description The cache's location text without its leading line break, e.g. `"Asgarnia: + * Mudskipper Point"`, or `null` for codes that go nowhere. + * @param visitedVarBit The varbit set once the player has travelled to this ring, which is what + * lists the code in the travel log; `null` when the cache has no such varbit for the code. + */ +data class FairyRing( + val code: String, + val dials: List, + val logIndex: Int, + val destination: CoordGrid?, + val description: String?, + val visitedVarBit: String?, +) { + /** The code the way the cache and the interface write it: `"A I Q"`. */ + val spacedCode: String = code.toCharArray().joinToString(" ") + + /** The travel log entry that lists this code (`Use code` / `Add Favourite`). */ + val logComponent: String = "component.fairyrings_log:${code.lowercase()}" + + /** The star next to the travel log entry (`Add Favourite`). */ + val favouriteIconComponent: String = "component.fairyrings_log:${code.lowercase()}_fave" + + /** The text shown for this code in the travel log and the favourites list. */ + val logText: String = + buildString { + append("").append(spacedCode).append("") + if (description != null) { + append("
").append(description) + } + } +} + +/** Every fairy ring code in the cache's `dbtable.fairyring`, keyed the ways the scripts need. */ +class FairyRings private constructor(val all: List) { + private val byTableId: Map = all.associateBy { FairyRingDials.tableId(it.dials) } + private val byCode: Map = all.associateBy { it.code } + private val byLogIndex: Map = all.associateBy { it.logIndex } + + fun byDials(dials: List): FairyRing? = byTableId[FairyRingDials.tableId(dials)] + + fun byCode(code: String): FairyRing? = byCode[code.uppercase().filter { it != ' ' }] + + fun byLogIndex(index: Int): FairyRing? = byLogIndex[index] + + companion object { + private val logger = InlineLogger() + + /** + * Codes that go nowhere point at this tile in the cache; the interface treats them as + * "hardly moved at all". + */ + private val NOWHERE = CoordGrid(65, 65, 1) + + fun load(): FairyRings { + val varbits = ConstantProvider.mappings["varbit"].orEmpty() + val rings = + FairyringRow.all().map { row -> + val code = row.code.filter { it != ' ' }.uppercase() + val dials = FairyRingDials.fromTableId(row.id) + check(FairyRingDials.code(dials) == code) { + "Fairy ring row ${row.rowId} spells ${FairyRingDials.code(dials)} but is $code." + } + val destination = + row.destCoord.takeUnless { it == NOWHERE || it.packed == 0 } + val visitedVarBit = + "varbit.fairyrings_log_${code.lowercase()}".takeIf { it in varbits } + FairyRing( + code = code, + dials = dials, + logIndex = row.multilocState, + destination = destination, + description = row.desc?.removePrefix("
")?.trim()?.ifEmpty { null }, + visitedVarBit = visitedVarBit, + ) + } + logger.info { + val reachable = rings.count { it.destination != null } + "Loaded ${rings.size} fairy ring codes, $reachable with a destination: " + + rings + .filter { it.destination != null } + .joinToString { "${it.code}=${it.destination}" } + } + return FairyRings(rings) + } + } +} diff --git a/content/travel/fairy-rings/src/main/resources/gamevals.toml b/content/travel/fairy-rings/src/main/resources/gamevals.toml new file mode 100644 index 000000000..c3ad8d18d --- /dev/null +++ b/content/travel/fairy-rings/src/main/resources/gamevals.toml @@ -0,0 +1,4 @@ +# Sound effects used by the fairy ring network. The vanilla gamevals dump carries no synth +# names, so the raw cache id is declared here (from the OSRS wiki "List of sound IDs"). +[gamevals.synth] +fairy_teleport = 1098 diff --git a/content/travel/fairy-rings/src/test/kotlin/org/rsmod/content/travel/fairyrings/FairyRingDialsTest.kt b/content/travel/fairy-rings/src/test/kotlin/org/rsmod/content/travel/fairyrings/FairyRingDialsTest.kt new file mode 100644 index 000000000..bbbd474e0 --- /dev/null +++ b/content/travel/fairy-rings/src/test/kotlin/org/rsmod/content/travel/fairyrings/FairyRingDialsTest.kt @@ -0,0 +1,48 @@ +package org.rsmod.content.travel.fairyrings + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test + +class FairyRingDialsTest { + @Test + fun `dial values spell the codes the cache table numbers them by`() { + assertEquals("AIP", FairyRingDials.code(listOf(0, 0, 0))) + assertEquals("AIQ", FairyRingDials.code(listOf(0, 0, 3))) + assertEquals("BKS", FairyRingDials.code(listOf(3, 2, 1))) + assertEquals("DKR", FairyRingDials.code(listOf(1, 2, 2))) + assertEquals("CLQ", FairyRingDials.code(listOf(2, 1, 3))) + } + + @Test + fun `codes round-trip through dial values and table ids`() { + for (first in 0 until FairyRingDials.POSITIONS) { + for (second in 0 until FairyRingDials.POSITIONS) { + for (third in 0 until FairyRingDials.POSITIONS) { + val dials = listOf(first, second, third) + val code = FairyRingDials.code(dials) + assertEquals(dials, FairyRingDials.dials(code), code) + assertEquals(dials, FairyRingDials.dials("${code[0]} ${code[1]} ${code[2]}")) + assertEquals(dials, FairyRingDials.fromTableId(FairyRingDials.tableId(dials))) + } + } + } + assertEquals(321, FairyRingDials.tableId(listOf(3, 2, 1))) + } + + @Test + fun `letters that are not on a dial are rejected`() { + assertNull(FairyRingDials.dials("AIX")) + assertNull(FairyRingDials.dials("IAP")) + assertNull(FairyRingDials.dials("AI")) + assertNull(FairyRingDials.dials("AIPS")) + } + + @Test + fun `rotation wraps around the four positions`() { + assertEquals(1, FairyRingDials.rotate(0, 1)) + assertEquals(0, FairyRingDials.rotate(3, 1)) + assertEquals(3, FairyRingDials.rotate(0, -1)) + assertEquals(2, FairyRingDials.rotate(3, -1)) + } +} diff --git a/content/travel/jewellery/build.gradle.kts b/content/travel/jewellery/build.gradle.kts new file mode 100644 index 000000000..a7e5cb889 --- /dev/null +++ b/content/travel/jewellery/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) +} diff --git a/content/travel/jewellery/src/main/kotlin/org/rsmod/content/travel/jewellery/JewelleryTeleportScript.kt b/content/travel/jewellery/src/main/kotlin/org/rsmod/content/travel/jewellery/JewelleryTeleportScript.kt new file mode 100644 index 000000000..115aa3f21 --- /dev/null +++ b/content/travel/jewellery/src/main/kotlin/org/rsmod/content/travel/jewellery/JewelleryTeleportScript.kt @@ -0,0 +1,282 @@ +package org.rsmod.content.travel.jewellery + +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import jakarta.inject.Inject +import org.rsmod.api.area.checker.AreaChecker +import org.rsmod.api.player.hook.PlayerTeleportValidator +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.api.player.output.ChatType +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpHeld1 +import org.rsmod.api.script.onOpHeld2 +import org.rsmod.api.script.onOpHeld3 +import org.rsmod.api.script.onOpHeld4 +import org.rsmod.api.script.onOpHeldSubOp +import org.rsmod.api.script.onOpWorn2 +import org.rsmod.api.script.onOpWorn3 +import org.rsmod.api.script.onOpWorn4 +import org.rsmod.api.script.onOpWorn5 +import org.rsmod.api.script.onOpWorn6 +import org.rsmod.api.script.onOpWorn7 +import org.rsmod.game.inv.Inventory +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Teleport jewellery: amulets of glory, rings of dueling and wealth, games, skills, passage and + * digsite necklaces, combat bracelets, burning amulets and slayer rings. + * + * Three ways in, all ending in the same teleport: + * - `Rub` (or `Teleport`) on the held item opens a menu of destinations plus "Nowhere". + * - The client's sub-menu under `Rub` sends a sub-op with the index of the destination clicked. + * - The worn item's ops from op 2 onwards are the destinations in the same order as the item's + * `wear_op` params; the slayer ring instead has a `Teleport` worn op that opens the menu. + * + * Each teleport spends a charge: the obj turns into its next lower charge, then into its uncharged + * form, or crumbles to dust for jewellery that has no uncharged form. Level-30 items may teleport + * from deeper Wilderness than the standard level-20 limit; [PlayerTeleportValidator] enforces both. + */ +class JewelleryTeleportScript +@Inject +constructor( + private val teleportValidator: PlayerTeleportValidator, + private val areaChecker: AreaChecker, +) : PluginScript() { + override fun ScriptContext.startup() { + for (item in JewelleryTeleports.all) { + for (obj in item.objs) { + register(item, obj) + } + } + } + + private fun ScriptContext.register(item: Jewellery, obj: String) { + val type = + ServerCacheManager.getItem(obj.asRSCM(RSCMType.OBJ)) + ?: error("Missing teleport jewellery obj: $obj") + val ops = type.interfaceOptions + + // Items whose `Wear` op sits in slot 1 (slayer rings) lose the engine's default equip, so + // give it back. + if (ops.getOrNull(0) == "Wear") { + onOpHeld1(obj) { invEquip(it.slot) } + } + // The op whose sub-menu lists the destinations: `Teleport` when the item has one (slayer + // rings, whose `Rub` is for contacting a Slayer master), otherwise `Rub`. + val teleportOpIndex = ops.indexOf("Teleport").takeIf { it != -1 } ?: ops.indexOf("Rub") + if (teleportOpIndex == -1) { + logger.warn { "No rub or teleport op on $obj" } + return + } + onHeldOp(teleportOpIndex, obj) { inv, slot -> rub(item, inv, slot) } + val rubIndex = ops.indexOf("Rub") + if (rubIndex != -1 && rubIndex != teleportOpIndex) { + onHeldOp(rubIndex, obj) { _, _ -> mes("Contacting your Slayer master is not available yet.") } + } + onOpHeldSubOp(obj) { event -> + if (event.op.slot != teleportOpIndex + 1) { + mes("That option is not available yet.") + return@onOpHeldSubOp + } + val destination = item.destinations.getOrNull(subopIndex(event.subop)) + if (destination == null) { + logger.warn { + "Unknown sub-op ${event.subop} for $obj (${item.destinations.size} destinations)" + } + return@onOpHeldSubOp + } + teleport(item, event.inventory, event.slot, destination) + } + + val menuOp = item.wornMenuOp + if (menuOp != null) { + onWornOp(menuOp, obj) { slot -> rub(item, worn, slot) } + return + } + for ((index, destination) in item.destinations.withIndex()) { + onWornOp(index + FIRST_WORN_DESTINATION_OP, obj) { slot -> + teleport(item, worn, slot, destination) + } + } + } + + private fun ScriptContext.onHeldOp( + index: Int, + obj: String, + action: suspend ProtectedAccess.(inv: Inventory, slot: Int) -> Unit, + ) { + when (index) { + 0 -> onOpHeld1(obj) { action(it.inventory, it.slot) } + 1 -> onOpHeld2(obj) { action(it.inventory, it.slot) } + 2 -> onOpHeld3(obj) { action(it.inventory, it.slot) } + 3 -> onOpHeld4(obj) { action(it.inventory, it.slot) } + else -> logger.warn { "Unsupported held op slot $index for $obj" } + } + } + + private fun ScriptContext.onWornOp( + op: Int, + obj: String, + action: suspend ProtectedAccess.(slot: Int) -> Unit, + ) { + when (op) { + 2 -> onOpWorn2(obj) { action(it.slot) } + 3 -> onOpWorn3(obj) { action(it.slot) } + 4 -> onOpWorn4(obj) { action(it.slot) } + 5 -> onOpWorn5(obj) { action(it.slot) } + 6 -> onOpWorn6(obj) { action(it.slot) } + 7 -> onOpWorn7(obj) { action(it.slot) } + else -> logger.warn { "Unsupported worn op $op for $obj" } + } + } + + /** + * The client numbers sub-menu entries from one (clicking "Stronghold", the first entry of the + * slayer ring, arrives as sub-op 1). A zero is tolerated as the first entry in case a client + * ever counts from zero. + */ + private fun subopIndex(subop: Int): Int = if (subop <= 0) 0 else subop - 1 + + private suspend fun ProtectedAccess.rub(item: Jewellery, inv: Inventory, slot: Int) { + val names = item.destinations.map { it.name } + val choice = + when (names.size) { + 1 -> choice2(names[0], 0, NOWHERE, -1, title = MENU_TITLE) + 2 -> choice3(names[0], 0, names[1], 1, NOWHERE, -1, title = MENU_TITLE) + 3 -> + choice4( + names[0], + 0, + names[1], + 1, + names[2], + 2, + NOWHERE, + -1, + title = MENU_TITLE, + ) + 4 -> + choice5( + names[0], + 0, + names[1], + 1, + names[2], + 2, + names[3], + 3, + NOWHERE, + -1, + title = MENU_TITLE, + ) + else -> { + val picked = menu(MENU_TITLE, hotkeys = true, choices = names + NOWHERE) + if (picked in names.indices) picked else -1 + } + } + val destination = item.destinations.getOrNull(choice) ?: return + teleport(item, inv, slot, destination) + } + + private suspend fun ProtectedAccess.teleport( + item: Jewellery, + inv: Inventory, + slot: Int, + destination: JewelleryDestination, + ) { + val coords = destination.coords + if (coords == null) { + mes("${destination.name} has not been added to this server yet.") + return + } + if (actionDelay > mapClock) { + return + } + if (destination.wilderness) { + val confirm = + choice2( + "Yes, teleport me into the Wilderness.", + true, + "No, I will stay here.", + false, + title = "${destination.name} is in the Wilderness. Teleport anyway?", + ) + if (!confirm) { + return + } + } + val denial = teleportValidator.validate(player, item.teleportType, areaChecker) + if (denial != null) { + mes(denial, ChatType.Engine) + return + } + if (!spendCharge(item, inv, slot)) { + return + } + actionDelay = mapClock + ACTION_DELAY + anim(TELEPORT_ANIM) + spotanim(TELEPORT_SPOTANIM, height = TELEPORT_SPOTANIM_HEIGHT) + soundSynth(TELEPORT_SOUND) + delay(TELEPORT_DELAY) + // Validated (and the charge spent) before the animation began; a teleblock landing + // during the cast must not stop a teleport that has already started. + telejump(coords, TeleportType.Exempt) + anim(TELEPORT_END_ANIM) + } + + /** Turns the obj in [slot] into its next charge state. Returns `false` if that failed. */ + private fun ProtectedAccess.spendCharge(item: Jewellery, inv: Inventory, slot: Int): Boolean { + val obj = inv[slot] ?: return false + val name = RSCM.getReverseMapping(RSCMType.OBJ, obj.id) + if (name !in item.charged) { + return true + } + val next = item.afterCharge(name) + val success = + if (next == null) { + invDel(inv, name, count = 1, slot = slot).success + } else { + invReplaceSlot(inv, slot, count = 1, replacement = next.type()).success + } + if (!success) { + return false + } + if (inv === worn) { + rebuildAppearance() + } + val left = item.chargesAfter(name) ?: 0 + when { + next == null -> mes("Your ${item.label} crumbles to dust.") + left == 0 -> mes("Your ${item.label} has run out of charges.") + left == 1 -> mes("Your ${item.label} has one charge left.") + else -> mes("Your ${item.label} has $left charges left.") + } + return true + } + + private fun String.type(): ItemServerType = + ServerCacheManager.getItem(asRSCM(RSCMType.OBJ)) ?: error("Missing obj: $this") + + private companion object { + private val logger = InlineLogger() + + private const val MENU_TITLE = "Where would you like to teleport to?" + private const val NOWHERE = "Nowhere" + + /** Worn op 1 is `Remove`; the item's `wear_op1` param is worn op 2. */ + private const val FIRST_WORN_DESTINATION_OP = 2 + + private const val TELEPORT_ANIM = "seq.human_castteleport" + private const val TELEPORT_END_ANIM = "seq.human_castteleport_reverse" + private const val TELEPORT_SPOTANIM = "spotanim.teleport_casting" + private const val TELEPORT_SPOTANIM_HEIGHT = 92 + private const val TELEPORT_SOUND = "synth.teleport_all" + private const val TELEPORT_DELAY = 3 + private const val ACTION_DELAY = 4 + } +} diff --git a/content/travel/jewellery/src/main/kotlin/org/rsmod/content/travel/jewellery/JewelleryTeleports.kt b/content/travel/jewellery/src/main/kotlin/org/rsmod/content/travel/jewellery/JewelleryTeleports.kt new file mode 100644 index 000000000..f29915296 --- /dev/null +++ b/content/travel/jewellery/src/main/kotlin/org/rsmod/content/travel/jewellery/JewelleryTeleports.kt @@ -0,0 +1,220 @@ +package org.rsmod.content.travel.jewellery + +import org.rsmod.api.player.hook.TeleportType +import org.rsmod.map.CoordGrid + +/** + * One option on a piece of teleport jewellery. A `null` [coords] is a destination whose content + * does not exist on this server yet; picking it tells the player so instead of teleporting. + */ +data class JewelleryDestination( + val name: String, + val coords: CoordGrid?, + /** The landing tile is inside the Wilderness, so the player is asked to confirm first. */ + val wilderness: Boolean = false, +) + +/** + * A family of teleport jewellery: every charge variant of one item, in the order the charges run + * down, plus where it can take the player. + * + * @param charged Obj names from the highest charge to the lowest. Using a charge turns the obj + * into the next entry; using the last entry turns it into [uncharged], or destroys it when that + * is `null`. + * @param infinite Variants that never lose a charge (eternal glory, eternal slayer ring). + * @param wornMenuOp When set, this worn op opens the destination menu rather than the worn ops + * mapping one-to-one onto [destinations] from op 2 upwards. + */ +data class Jewellery( + val label: String, + val charged: List, + val destinations: List, + val teleportType: TeleportType = TeleportType.Standard, + val infinite: List = emptyList(), + val uncharged: String? = null, + val wornMenuOp: Int? = null, +) { + val objs: List + get() = charged + infinite + + init { + require(charged.isNotEmpty() || infinite.isNotEmpty()) { "$label needs at least one obj." } + require(destinations.isNotEmpty()) { "$label needs at least one destination." } + require(objs.all { it.startsWith("obj.") }) { "$label has a non-obj entry." } + require(objs.toSet().size == objs.size) { "$label lists an obj twice." } + } + + /** The obj [obj] turns into after one charge is spent, or `null` when it crumbles to dust. */ + fun afterCharge(obj: String): String? { + val index = charged.indexOf(obj) + if (index == -1) { + return obj + } + return charged.getOrNull(index + 1) ?: uncharged + } + + /** Charges left on [obj] after it has been used once, or `null` for infinite variants. */ + fun chargesAfter(obj: String): Int? { + val index = charged.indexOf(obj) + return if (index == -1) null else charged.size - index - 1 + } +} + +object JewelleryTeleports { + private fun tile(x: Int, z: Int, level: Int = 0) = CoordGrid(x, z, level) + + private fun dest(name: String, x: Int, z: Int, level: Int = 0, wilderness: Boolean = false) = + JewelleryDestination(name, tile(x, z, level), wilderness) + + private fun missing(name: String) = JewelleryDestination(name, null) + + private fun charges(base: String, from: Int, to: Int = 1): List = + (from downTo to).map { "obj.${base}_$it" } + + private val GLORY_DESTINATIONS = + listOf( + dest("Edgeville", 3087, 3496), + dest("Karamja", 2918, 3176), + dest("Draynor Village", 3105, 3251), + dest("Al Kharid", 3293, 3163), + ) + + private val WEALTH_DESTINATIONS = + listOf( + dest("Miscellania", 2535, 3861), + dest("Grand Exchange", 3164, 3477), + dest("Falador Park", 2995, 3375), + dest("Dondakan's Rock", 2828, 10166), + ) + + val all: List = + listOf( + Jewellery( + label = "amulet", + charged = charges("amulet_of_glory", 6), + infinite = listOf("obj.amulet_of_glory_inf"), + uncharged = "obj.amulet_of_glory", + teleportType = TeleportType.MemberLevel30, + destinations = GLORY_DESTINATIONS, + ), + Jewellery( + label = "amulet", + charged = charges("trail_amulet_of_glory", 6), + uncharged = "obj.trail_amulet_of_glory", + teleportType = TeleportType.MemberLevel30, + destinations = GLORY_DESTINATIONS, + ), + Jewellery( + label = "ring", + charged = charges("ring_of_dueling", 8), + destinations = + listOf( + dest("Emir's Arena", 3316, 3235), + dest("Castle Wars", 2441, 3090), + dest("Ferox Enclave", 3151, 3635), + missing("Fortis Colosseum"), + ), + ), + Jewellery( + label = "necklace", + charged = charges("necklace_of_minigames", 8), + destinations = + listOf( + dest("Burthorpe", 2898, 3553), + dest("Barbarian Outpost", 2520, 3571), + dest("Corporeal Beast", 2967, 4384), + dest("Tears of Guthix", 3244, 9501), + dest("Wintertodt Camp", 1628, 3938), + ), + ), + Jewellery( + label = "necklace", + charged = charges("jewl_necklace_of_skills", 6), + uncharged = "obj.jewl_necklace_of_skills", + teleportType = TeleportType.MemberLevel30, + destinations = + listOf( + dest("Fishing Guild", 2611, 3390), + dest("Mining Guild", 3051, 9763), + dest("Crafting Guild", 2933, 3295), + dest("Cooking Guild", 3143, 3442), + dest("Woodcutting Guild", 1662, 3505), + dest("Farming Guild", 1249, 3719), + ), + ), + Jewellery( + label = "bracelet", + charged = charges("jewl_bracelet_of_combat", 6), + uncharged = "obj.jewl_bracelet_of_combat", + teleportType = TeleportType.MemberLevel30, + destinations = + listOf( + dest("Warriors' Guild", 2879, 3544), + dest("Champions' Guild", 3190, 3367), + dest("Monastery", 3051, 3490), + dest("Ranging Guild", 2656, 3441), + ), + ), + Jewellery( + label = "ring", + charged = charges("ring_of_wealth", 5), + uncharged = "obj.ring_of_wealth", + teleportType = TeleportType.MemberLevel30, + destinations = WEALTH_DESTINATIONS, + ), + Jewellery( + label = "ring", + charged = (5 downTo 1).map { "obj.ring_of_wealth_i$it" }, + uncharged = "obj.ring_of_wealth_i", + teleportType = TeleportType.MemberLevel30, + destinations = WEALTH_DESTINATIONS, + ), + Jewellery( + label = "necklace", + charged = charges("necklace_of_passage", 5), + destinations = + listOf( + dest("Wizards' Tower", 3114, 3181), + dest("The Outpost", 2431, 3348), + dest("Eagles' Eyrie", 2333, 3579), + missing("Wyrmscraig"), + ), + ), + Jewellery( + label = "amulet", + charged = charges("burning_amulet", 5), + destinations = + listOf( + dest("Chaos Temple", 3236, 3635, wilderness = true), + dest("Bandit Camp", 3038, 3651, wilderness = true), + dest("Lava Maze", 3028, 3842, wilderness = true), + ), + ), + Jewellery( + label = "pendant", + charged = charges("necklace_of_digsite", 5), + destinations = + listOf( + dest("Digsite", 3341, 3445), + dest("Fossil Island", 3764, 3869), + dest("Lithkren", 3549, 10456), + ), + ), + Jewellery( + label = "ring", + charged = charges("slayer_ring", 8), + infinite = listOf("obj.slayer_ring_eternal"), + teleportType = TeleportType.MemberLevel30, + wornMenuOp = 3, + destinations = + listOf( + dest("Stronghold Slayer Cave", 2433, 3423), + dest("Slayer Tower", 3422, 3537), + dest("Fremennik Slayer Dungeon", 2802, 10001), + dest("Tarn's Lair", 3185, 4601), + dest("Dark Beasts", 2028, 4636), + missing("Wyrmscraig Cavern"), + ), + ), + ) +} diff --git a/content/travel/jewellery/src/test/kotlin/org/rsmod/content/travel/jewellery/JewelleryTeleportsTest.kt b/content/travel/jewellery/src/test/kotlin/org/rsmod/content/travel/jewellery/JewelleryTeleportsTest.kt new file mode 100644 index 000000000..390f2d1ce --- /dev/null +++ b/content/travel/jewellery/src/test/kotlin/org/rsmod/content/travel/jewellery/JewelleryTeleportsTest.kt @@ -0,0 +1,40 @@ +package org.rsmod.content.travel.jewellery + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class JewelleryTeleportsTest { + @Test + fun `no obj belongs to two pieces of jewellery`() { + val objs = JewelleryTeleports.all.flatMap { it.objs } + assertEquals(objs.toSet().size, objs.size) + } + + @Test + fun `charges run down to the uncharged form or to dust`() { + val glory = JewelleryTeleports.all.first { "obj.amulet_of_glory_4" in it.charged } + assertEquals("obj.amulet_of_glory_3", glory.afterCharge("obj.amulet_of_glory_4")) + assertEquals("obj.amulet_of_glory", glory.afterCharge("obj.amulet_of_glory_1")) + assertEquals(0, glory.chargesAfter("obj.amulet_of_glory_1")) + assertEquals(3, glory.chargesAfter("obj.amulet_of_glory_4")) + assertNull(glory.chargesAfter("obj.amulet_of_glory_inf")) + assertEquals("obj.amulet_of_glory_inf", glory.afterCharge("obj.amulet_of_glory_inf")) + + val dueling = JewelleryTeleports.all.first { "obj.ring_of_dueling_8" in it.charged } + assertNull(dueling.afterCharge("obj.ring_of_dueling_1")) + } + + @Test + fun `every destination has a name and known destinations sit on the map`() { + for (item in JewelleryTeleports.all) { + for (destination in item.destinations) { + assertTrue(destination.name.isNotBlank()) + val coords = destination.coords ?: continue + assertTrue(coords.x in 1024..4096, "${destination.name} x=${coords.x}") + assertTrue(coords.z in 2048..12800, "${destination.name} z=${coords.z}") + } + } + } +} diff --git a/docs/aggression.md b/docs/aggression.md new file mode 100644 index 000000000..b56eae59c --- /dev/null +++ b/docs/aggression.md @@ -0,0 +1,33 @@ +# Monster aggression + +Which monsters attack players on sight, and when they stop. + +## Rules (as in OSRS) + +- An aggressive monster attacks players whose combat level is at most twice its own. A level 28 + Hill Giant attacks players up to level 56; level 63 and above monsters attack everyone. +- Inside the Wilderness the level rule is ignored: aggressive monsters attack every player. +- After ten minutes in the same map square a player is *tolerated* and monsters that use the level + rule leave them alone until they leave the square and come back. Monsters that hunt everyone + regardless of level (bosses, God Wars creatures, the always-aggressive kinds) never tire. + +## Where it lives + +| Piece | Location | +|---|---| +| Which monsters are aggressive | `content/other/npc-aggression/src/main/resources/npc-aggression.toml`, generated from the wiki infobox `aggressive` field of every attackable npc name | +| Hand corrections | `npc-aggression-overrides.toml` next to it (`aggressive = false` silences a generated entry) | +| Assigning hunt modes at boot | `NpcAggressionScript`: `stalk.constant_melee` for melee monsters, `stalk.constant_ranged` for ranged and magic ones, skipped for npcs whose own config already sets a hunt mode | +| Level rule and Wilderness override | `NpcPlayerHuntProcessor` (engine), driven by the hunt mode's `checkNotTooStrong` | +| Tolerance timer | `AggressionTolerance` (engine), ticked from `PlayerPostTickProcess` | + +The hunt modes themselves are defined in `.data/raw-cache/server/hunt.toml`; the `stalk.*` modes +carry the level check and tolerance, the `aggressive_*` modes do not. + +## Regenerating the list + +The generator queries the wiki API for the wikitext of every attackable npc name from +`osrs-dumps/dump.npc` (batches of 50 titles, one request every half second) and reads the +`aggressive`/`id` pairs from each `Infobox Monster` version. Any value starting with "Yes" counts +as aggressive; the rest of the value is kept in `note` when it names a condition the server does +not model yet (an item that pacifies the monster, a quest that removes the aggression). diff --git a/docs/passages.md b/docs/passages.md new file mode 100644 index 000000000..a1dc29b15 --- /dev/null +++ b/docs/passages.md @@ -0,0 +1,35 @@ +# Generic passages + +Doors, gates, ladders, staircases, trapdoors, cave mouths, stiles and the Wilderness ditch that +no dedicated script claims are handled by `content/generic/generic-locs` under `locs/passages`. + +The data-driven [door](doors.md), [gate](gates.md) and ladder scripts keep priority: the generic +handler is registered on the *default* loc op events, which the engine only fires when a loc op +has no type, content-group or category handler. + +## What it does + +| Loc name / op | Behaviour | +|---|---| +| `Door`, `Gate`, `Large door`, ... with `Open` / `Close` (or any wall-shaped loc) | Finds the open/closed twin by name, size and first op within a few ids. A closed panel with a matching panel beside it is one half of a double door or a two-panel gate and both halves swing together, using the `DoubleDoorScript` and `PicketGate` geometry. Opened panels are remembered until they reset, so closing puts the originals back exactly. No twin: the door is removed for 500 cycles instead. | +| `Trapdoor` with `Open` | Swaps in the open trapdoor (twin with `Climb-down`) or climbs straight down. | +| `Ladder`, `Staircase`, `Stairs`, `Rope`, ... with `Climb-up` / `Climb-down` / `Climb` | Moves one level, or between the surface and the dungeon copy of the map 6400 tiles north. Lands on the nearest free tile so a blocked ladder tile puts the player beside it. `Climb` asks which way. | +| `Cave`, `Cave entrance`, `Tunnel`, `Hole`, `Passage`, ... with `Enter` / `Exit` / `Leave` | Same surface/dungeon swap as ladders. | +| `Stile`, `Fence` with `Climb-over` | Hops to the tile past the far edge. | +| `loc.ditch_wilderness_cover` `Cross` | `WildernessDitchScript`: jumps the ditch to whichever side the player is not on. | +| Passages listed in `LinkedPassages` (e.g. the Wilderness Slayer Cave entrances) | `LinkedPassageScript`: explicit two-way links for caves whose far side is not 6400 tiles away. Add an entry there when the generic rule strands a player. | + +Anything else still gets the engine's "Nothing interesting happens." message. A guessed landing +is refused when nothing (no wall, loc or blocked tile) exists within 8 tiles of it, since that is +the black filler of a map square rather than a room. + +The rules themselves (`Passages.kt`) are pure functions with unit tests; the script only wires +them to the map and the loc registry. When a specific door, ladder or cave needs exact behaviour, +give it a content group or a `loc.toml` entry and the generic path stops applying to it. + +## Teleport jewellery + +`content/travel/jewellery` covers amulets of glory, rings of dueling and wealth, games, skills, +passage and digsite necklaces, combat bracelets, burning amulets and slayer rings. Destinations, +charge sequences and Wilderness limits live in `JewelleryTeleports.kt`; a destination with +`coords = null` is one whose content is not on the server yet and tells the player so. diff --git a/docs/plugins.md b/docs/plugins.md new file mode 100644 index 000000000..9143369dc --- /dev/null +++ b/docs/plugins.md @@ -0,0 +1,481 @@ +# Plugins + +How content plugins are discovered, loaded and wired in OpenRune, and the rules to follow so a +plugin cannot break the server it is dropped into. + +Everything under `content/` is a plugin. There is no registry file, no manifest and no +`enabled = true` switch — a directory with a `build.gradle.kts` **is** a plugin, and the server +finds its code by classpath scan at boot. + +--- + +## Quick mental model + +1. **Gradle discovery.** `settings.gradle.kts` walks `content/` and includes every directory that + has a `build.gradle.kts`. `server/shared/build.gradle.kts` then adds *all* of those projects as + `api(...)` dependencies, so every plugin is on the server's runtime classpath automatically. +2. **Guice modules** (`PluginModule`) are loaded first, before the injector exists, and contribute + bindings and hooks. +3. **Scripts** (`PluginScript`) are instantiated by Guice after the cache and map are loaded, and + their `startup()` subscribes handlers to the event bus. +4. **Cache content** (`PluginPack`) is a *separate* build-time path: it packs new configs, db + tables, interfaces, models, sprites and CS2 into the cache. It never runs on the game server. +5. **Names, not ids.** Content is referenced by RSCM strings (`"npc.hans"`, `"varp.playtime"`). + Numeric ids for anything custom live in a plugin-local `gamevals.toml`. + +--- + +## Boot order + +From `server/app/src/main/kotlin/org/rsmod/server/app/GameServer.kt`: + +| Phase | What happens | What you can rely on | +|---|---|---| +| `plugin-modules` | `PluginModuleLoader` ClassGraph-scans for `PluginModule` subclasses and instantiates them via a **no-arg constructor** | Nothing injected yet | +| `guice` | `GameServerModule` + every plugin module combined into one injector | — | +| `config` | `ServerConfig` read from `game.yml` | — | +| `cache` | `ServerCacheManager.init(revision)` | Cache types resolvable; RSCM strings resolve | +| `map` | Map decode; npc/obj spawns **queued**, not flushed | — | +| `scripts` | `PluginScriptLoader` scans for `PluginScript` subclasses, `injector.getInstance(...)` each, then calls `startup()` | Everything above | +| — | `EntityDelayedProcess.flush()` — map spawns actually fire | Your `NpcStateEvents.Create` handlers exist before any npc spawns | +| `services-start` | Network opens once `PluginScriptBootGate.markReady()` has run | No player can log in before your `startup()` ran | + +Two consequences worth internalising: + +- **`startup()` is registration, not gameplay.** It runs once, on one thread, with no players + online. Subscribe handlers; do not touch player state. +- **Map spawns are deliberately deferred** so `onEvent` in a plugin sees the + world's initial spawns. If you register spawn handlers lazily (e.g. inside a login handler) you + will miss them. + +--- + +## The scan contract (easy to get wrong) + +`server/shared/src/main/kotlin/org/rsmod/server/shared/PluginConstants.kt`: + +```kotlin +val searchPackages = arrayOf("org.rsmod.api", "org.rsmod.content") +``` + +- Your classes **must** live under `org.rsmod.content.…`. A plugin in `com.myserver.foo` compiles, + ships on the classpath, and is silently never loaded. +- `ScannerModule` additionally rejects `org.rsmod.content.*.integration` — that package name is + reserved for test sources. +- `PluginScriptLoader` uses `getSubclasses(...)` (any depth) and skips abstract classes and + interfaces. An abstract base script is fine; it just will not be instantiated itself. +- `PluginModuleLoader` uses `getSubclasses(...).directOnly()` and `clazz.getConstructor()`. A module + must be a **direct** subclass of `PluginModule` with a **public no-arg constructor**. An + intermediate abstract module class will not be found. +- Script loading is strict by default: a construction failure rethrows and kills boot. That is + intentional — a half-registered plugin is worse than no server. + +--- + +## Anatomy of a plugin + +Minimal, complete plugin (`content/other/windmill`): + +``` +content/other/windmill/ +├── build.gradle.kts +└── src/main/kotlin/org/rsmod/content/other/windmill/WindmillLadderScript.kt +``` + +```kotlin +// build.gradle.kts +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.pluginCommons) +} +``` + +`api.pluginCommons` is an aggregate `api(...)` module: script DSL, player/npc api, combat, invtx, +config, repo, random, engine game/map/events. Depend on it and you have most of what content needs. +Add narrower modules (`projects.api.attr`, `projects.api.shops`, `projects.api.instances`) only when +you actually need them. + +```kotlin +class WindmillLadderScript : PluginScript() { + override fun ScriptContext.startup() { + onOpLoc1("loc.qip_cook_ladder_top") { climbDown() } + onOpLoc1("loc.qip_cook_ladder") { climbUp() } + onOpLoc2("loc.qip_cook_ladder_middle") { climbUp() } + } + + private suspend fun ProtectedAccess.climbUp() { + arriveDelay() + val dest = player.coords.translateLevel(1) + anim("seq.human_reachforladder") + delay(1) + telejump(dest) + } +} +``` + +That is the whole contract: extend `PluginScript`, override `ScriptContext.startup()`. + +### Dependency injection + +Constructor injection with `jakarta.inject.Inject`. Guice builds the script, so anything bound in +the injector is available: + +```kotlin +class PlaytimeScript @Inject constructor(private val playerList: PlayerList) : PluginScript() { … } +``` + +`ScriptContext` itself carries only `eventBus`, `cheatCommandMap` and `engineQueueCache` — the DSL +functions are extensions on it. + +### Cross-plugin dependencies + +Plugins may depend on each other via `implementation(projects.content.…)`; several already do +(`skills.cooking → generic.genericLocs`, `areas.wilderness → interfaces.collectionLog`). Keep it +one-directional and prefer depending on a shared `…/utils` module over on a sibling feature. + +--- + +## Referencing game content: RSCM + +Every handler and helper takes a namespaced string resolved through `RSCM`. The valid prefixes are +the `RSCMType` enum (`or-cache/src/main/kotlin/dev/openrune/rscm/RSCM.kt`): + +``` +area bas category clientscript component content controller droptrigger currency dbcol dbrow +dbtable enum font headbar hitmark interface inv jingle loc mesanim midi models npc obj param +projanim queue seq spotanim stat synth timer varbit varcon varn varobj varp walktrigger +``` + +`"loc.qip_cook_ladder".asRSCM(RSCMType.LOC)` validates the prefix and fails loudly on a typo, at +boot, rather than silently binding to id 0. Prefer the string overloads for this reason. + +Names resolve from, in order (`GameValProvider.sourceFiles`): + +1. `.data/gamevals-binary/gamevals.dat` — dumped from the vanilla cache by `freshCache` +2. `.data/gamevals-binary/.dat` +3. every `gamevals.toml` under `content/` +4. every `gamevals.toml` under `api/` +5. `.data/gamevals/*.rscm` + +So a plugin's own `gamevals.toml` is read directly from source at runtime — you do not need to +regenerate anything for the *server* to see a new name. + +### Declaring custom ids + +`src/main/resources/gamevals.toml`, one table per RSCM prefix: + +```toml +[gamevals.varp] +spawn_menu = 65479 + +[gamevals.varbit] +spawn_include_nulls = 65450 + +[gamevals.interface] +spawn_menu = 1099 +``` + +Rules: + +- **Ids must be strictly greater than the vanilla max for that table.** The reserved ranges are in + `.data/gamevals-binary/max-ids.toml` (`GameValMaxIdManifest`), regenerated by `freshCache` and + committed so CI can detect collisions. In practice existing plugins count *down* from `65535`. +- The table name must be a real RSCM prefix; `PluginGamevalMerger` hard-fails otherwise. +- `-1` is a supported "unassigned" marker that the cache build fills in and writes back to your + TOML — but no in-repo plugin uses it. Assigning by hand is the beaten path. +- `gradlew :or-cache:mergePluginGamevals` appends these into `.data/gamevals/*.rscm`. This is only + needed for CS2/Neptune symbol resolution and release builds; the merger never overwrites an + existing key, it only appends new ones. + +--- + +## `ProtectedAccess` — the safety model + +This is the single most important concept for writing correct content. + +Most interaction handlers give you a `suspend ProtectedAccess.(Event) -> Unit` receiver rather than +a raw `Player`. `ProtectedAccess` is a **capability token**: holding one means this coroutine owns +the player's action slot right now. + +```kotlin +public fun ScriptContext.onOpLoc1( + type: String, + action: suspend ProtectedAccess.(LocEvents.Op1) -> Unit, +): Unit = onProtectedEvent(type.asRSCM(RSCMType.LOC), action) +``` + +What it buys you: + +- **Mutual exclusion.** `ProtectedAccessLauncher` refuses to start a second protected block while + `player.isAccessProtected`, so two interactions cannot interleave and duplicate items. +- **Re-validation across suspension.** Every `delay`, dialogue and `playerWalk` suspends, and on + resume re-acquires protected access. If the player logged out, died, was teleported, or started + another interaction, it throws `ProtectedAccessLostException` and your handler unwinds. You do not + have to re-check anything after a suspension — that is the point. +- **Correct UI handling.** `delay()` snapshots the open main modal and restores it on resume. + +The rules that follow from this: + +- **Never stash a `ProtectedAccess` reference** in a field, a map, or a lambda that outlives the + handler. It is valid only for the current coroutine. Capture `player` (or a `PlayerUid`) instead. +- **Do all validation, then all mutation.** Anything before a `delay` may be stale after it; the + exception protects the *player*, not your local variables. +- **`delay(n)` delays exactly `n` ticks** — this deliberately diverges from the "official" + convention. `delay(0)` is rejected (`require(cycles > 0)`). +- **Use `arriveDelay()`** at the top of loc/npc handlers — it burns one tick only if the player + moved last cycle, which is what the real game does. +- To *start* a protected block from unprotected code (a timer, an event, a command), inject + `ProtectedAccessLauncher` and call `launch(player, busyText) { … }`. It returns `false` if the + player was busy. Do not construct `ProtectedAccess` yourself; `launchLenient` is `@InternalApi`. + +--- + +## The event DSL + +All in `api/script` (and `api/script-advanced` for lower-level defaults). Roughly 200 entry points; +the naming is systematic: + +| Family | Meaning | +|---|---| +| `onOpX1..5` | Player clicked option 1–5 on a loc / npc / obj / worn / held / player | +| `onApX1..5` | "Approach" variant — fired at range before walking into melee distance | +| `onOpXU` / `onApXU` | Use item **on** target | +| `onOpXT` | Cast spell / use interface component on target | +| `onOpContentX*` | Same, but keyed on a `content.*` group instead of a single type | +| `onOpLocCategoryX` | Keyed on a `category.*` | +| `onDefaultOpX` / `onUnimplementedOpX` | Fallback when nothing else handled it | +| `onAiOpPlayerX` / `onAiApPlayerX` | Npc-initiated interaction against a player | + +Plus lifecycle and state: + +`onGameStartup`, `onPlayerInit`, `onPlayerLogin`, `onPlayerLogout`, `onPlayerCoordsChanged`, +`onPlayerHitpointsChanged`, `onChangeStat`, `onAdvanceStat`, `onPlayerHit`, `onNpcHit`, +`onModifyNpcHit`, `onArea`/`onAreaExit`, `onZone`/`onZoneExit`, `onMapzone`/`onMapzoneExit`, +`onPlayerTimer`, `onPlayerSoftTimer`, `onPlayerQueue`, `onPlayerSoftQueue`, `onNpcTimer`, +`onNpcQueue`, `onIfOpen`/`onIfClose`/`onIfOverlayButton`/`onIfModalButton`, `onCommand`. + +For anything without a wrapper, drop to `onEvent` / `onProtectedEvent`: + +```kotlin +onEvent { if (npc.type.id in bossIds) resetGorilla(npc) } +``` + +**Prefer content groups over ids.** `onOpContentLoc1("content.tree")` covers every tree the cache +tags, present and future; `onOpLoc1("loc.oak_tree")` covers one. Content/category/param tagging is +how this codebase avoids the id-list rot that plagues older frameworks. + +### Commands + +```kotlin +onCommand("playtime") { + desc = "Show how long you have played for" // required — build() errors without it + requiredRights = Rights.ADMINISTRATOR // optional gate + cheat { + player.mes("…") + } +} +``` + +`CheatHandlerBuilder` already catches `NumberFormatException` / `IndexOutOfBoundsException` (→ +"Invalid arguments!") and logs anything else, so argument parsing can be direct. + +--- + +## State: where to put what + +| Kind | Mechanism | Persists? | Use for | +|---|---|---|---| +| Varp / varbit | `by intVarp("varp.x")`, `by intVarBit("varbit.x")`, `by boolVarBit(...)` on `Player` or `ProtectedAccess` | Yes, saved with the account | Anything the client must see, or that must survive logout | +| Attribute | `AttributeKey()` + `player.attr[…]` | Only if `persistenceKey != null` | Server-side flags; transient combat/admin state | +| Timer | `timer("timer.x", ticks)` / `softTimer(...)` | Per-session | Repeating per-player work | +| Queue | `queue`, `weakQueue`, `strongQueue`, `softQueue`, `longQueue` | Per-session | Deferred one-shot actions with interrupt semantics | + +Idiomatic varp access, scoped to the plugin that owns it: + +```kotlin +private var Player.playtime by intVarp("varp.playtime") +``` + +Attribute caveat from `AttributeKey`: do not use `Double`/`Float` for persistent keys — store scaled +`Int`s instead. + +Prefer varps for anything that already exists in the cache; only invent an attribute when there is +no client-visible representation. + +--- + +## `PluginModule` — bindings and hooks + +Use a module when the engine needs to *call into* your plugin, rather than your plugin subscribing +to events. Hooks are contributed as Guice multibindings: + +```kotlin +class SlayerModule : PluginModule() { + override fun bind() { + addSetBinding(SlayerNpcKillHook::class.java) + addSetBinding(SlayerSuperiorAttackHook::class.java) + addSetBinding(SlayerSuperiorEvents::class.java) + } +} +``` + +Helpers available: `bindInstance()`, `bindSingleton(instance)`, `bindProvider(…)`, +`bindBaseInstance(Impl::class.java)`, `newSetBinding()`, `addSetBinding(…)`. + +Remember the loader constraints: direct subclass, public no-arg constructor, package under +`org.rsmod.content`. + +--- + +## `PluginPack` — shipping new cache content + +A plugin that needs data the vanilla cache does not have puts it in a **separate `pack` +submodule**. This is a hard convention, not a style choice: `or-cache` adds only projects whose +name ends in `-pack` to the cache-build classpath, so building the cache never has to compile your +game scripts or their api dependencies. + +``` +content/other/spawn/ +├── build.gradle.kts # the plugin (api.pluginCommons) +├── src/main/resources/gamevals.toml # id assignments +├── src/main/kotlin/…/SpawnMenuScript.kt +└── pack/ + ├── build.gradle.kts # or2 cache libs only + ├── src/main/kotlin/…/pack/SpawnPluginPack.kt + └── src/main/resources/pack/ + ├── configs/spawn.toml + └── cs2/{script,symbols}/… +``` + +The directory **must** be named `pack` — `settings.gradle.kts` renames the Gradle project to +`-pack` so the coordinates stay unique. + +```kotlin +class SpawnPluginPack : PluginPack() { + override fun interfaces(): List = listOf(buildSpawnMenuInterface()) +} +``` + +Overridable surface: `dbTables()`, `interfaces()`, `extraTasks()`, `isEnabled(projectRoot)`, +`shouldAlwaysPack()`, `validate(projectRoot)`. Convention-located resource dirs, all optional: +`pack/configs`, `pack/models`, `pack/sprites`, `pack/cs2`. + +`configs/*.toml` declares server-side types — invs, varps, varbits, npcs, locs, objs: + +```toml +[[inventory]] +id = "inv.spawn_results" +size = 60 +scope = "Temp" +stack = "Always" + +[[varp]] +id = "varp.spawn_menu" +scope = "Perm" +transmit = "Never" + +[[varbit]] +id = "varbit.spawn_include_nulls" +varp = "varp.spawn_menu" +startBit = 0 +endBit = 0 +``` + +Db tables are built with the `dbTable` DSL (see `ShootingStarsTable.kt`) and returned from +`dbTables()`. + +After changing anything in a pack: `gradlew :or-cache:buildCache`. After adding gamevals used by +CS2: also `gradlew :or-cache:mergePluginGamevals`. + +--- + +## Workflow + +One-time: fetch and build the cache, generate `game.yml` and RSA keys. + +```bash +./gradlew install +``` + +Boot the server (alias for `:server:app:run`). + +```bash +./gradlew run +``` + +Rebuild the cache after touching any `pack/` module. + +```bash +./gradlew :or-cache:buildCache +``` + +Append plugin gamevals into `.data/gamevals/*.rscm` (CS2 symbols, release builds). + +```bash +./gradlew :or-cache:mergePluginGamevals +``` + +A new plugin needs **no** registration anywhere — create the directory with a `build.gradle.kts` +and re-sync Gradle. + +### Testing + +`base-conventions` pulls in `test-conventions`, so plain JUnit 5 `src/test` works in any plugin +and runs with `gradlew test`. Use it for the logic you can isolate — data tables, formatting, +label/encoding invariants (see `content/other/cheat-menu/src/test/…/CheatMenuDataTest.kt`). + +Be aware: several plugins carry `src/integration` sources against +`org.rsmod.api.testing.GameTestState`, but **the `api:testing` module does not exist in this +repository** and no content `build.gradle.kts` applies `integration-test-suite`. Those sources are +not compiled or run today. Do not add new ones expecting them to execute. + +### Formatting + +Spotless + ktlint 1.5.0, ratcheted from `origin/main` — only files changed relative to main are +checked. Rule selection lives in `.editorconfig`. Run `gradlew spotlessApply` before committing. + +--- + +## Safety checklist + +Before shipping a plugin: + +- [ ] Package is under `org.rsmod.content.…` (otherwise it is silently never loaded). +- [ ] `PluginModule`, if any, is a direct subclass with a public no-arg constructor. +- [ ] `startup()` only registers handlers — no player/world mutation, no blocking I/O, no + `Thread.sleep`, no heavy work. It runs on the boot path and delays login for everyone. +- [ ] All type references are RSCM strings with the right prefix, so typos fail at boot. +- [ ] No `ProtectedAccess` stored beyond its handler; long-lived references use `Player`/`PlayerUid`. +- [ ] Validate-then-mutate around every `delay`/dialogue; assume anything can change across a + suspension. +- [ ] Randomness goes through `GameRandom` (`random` on `ProtectedAccess`), never `Math.random()` or + a private `Random` — this keeps content testable and reproducible. +- [ ] Custom ids are above the table max in `.data/gamevals-binary/max-ids.toml`, and live in the + plugin's own `gamevals.toml`. +- [ ] Cache data is in a `pack/` submodule, not in the plugin module. +- [ ] Prefer `content.*` / `category.*` / params over hardcoded id lists. +- [ ] `gradlew spotlessApply` is clean, and the server boots (`gradlew run`) with the plugin present. + +## Known sharp edges + +Beyond `docs/quirks.md`: + +- Boot is fail-fast for scripts. A single plugin that throws in its constructor takes the whole + server down. That is the correct trade — but it means a plugin's constructor must not do + anything that can fail on a fresh install. +- `PluginGamevalMerger` only ever *appends*. Renumbering a gameval leaves the stale id behind in + `.data/gamevals/*.rscm`; delete the line by hand or the old name keeps resolving. +- `PluginPacks.discover` scans `dev.openrune.pack` and `org.rsmod.content` only — a pack class + outside those packages is ignored with no error. +- The `-pack` project-name rewrite in `settings.gradle.kts` exists because Gradle would otherwise + conflict-resolve every `org.rsmod:pack` down to one. Do not rename the directory. + +## See also + +- `docs/quirks.md` — engine-level design decisions and gotchas +- `docs/drops.md` — NPC loot tables (TOML + `@RegisterDropTable`) +- `docs/instances.md` — instanced bosses via `InstanceScript` +- `docs/doors.md`, `docs/gates.md`, `docs/boss-hp-bar.md`, `docs/ironman.md` diff --git a/engine/game/src/main/kotlin/org/rsmod/game/entity/player/Appearance.kt b/engine/game/src/main/kotlin/org/rsmod/game/entity/player/Appearance.kt index 43013a58d..96453000d 100644 --- a/engine/game/src/main/kotlin/org/rsmod/game/entity/player/Appearance.kt +++ b/engine/game/src/main/kotlin/org/rsmod/game/entity/player/Appearance.kt @@ -93,8 +93,16 @@ public class Appearance { this.rebuild = true } + /** + * @param identKit the ident-kit style for [index], or [NO_IDENT_KIT] to leave the slot empty. + * The backing array already starts as [NO_IDENT_KIT], and the appearance encoder passes the + * value straight through to a protocol that treats `-1` as "no model" - a body type B jaw, + * for instance, has no model rather than a blank one. + */ public fun setIdentKit(index: Int, identKit: Int) { - require(identKit in 0..65535) { "identKit must be in range [0..65535]. ($identKit)" } + require(identKit == NO_IDENT_KIT || identKit in 0..65535) { + "identKit must be $NO_IDENT_KIT (none) or in range [0..65535]. ($identKit)" + } this.identKit[index] = identKit.toShort() this.rebuild = true } @@ -134,6 +142,9 @@ public class Appearance { } public companion object { + /** Leaves an ident-kit slot empty; the protocol encodes it as "no model". */ + public const val NO_IDENT_KIT: Int = -1 + public const val PRONOUN_HE: Int = 0 public const val PRONOUN_SHE: Int = 1 public const val PRONOUN_THEY: Int = 2 diff --git a/engine/game/src/main/kotlin/org/rsmod/game/queue/PlayerQueueList.kt b/engine/game/src/main/kotlin/org/rsmod/game/queue/PlayerQueueList.kt index f77a4a230..4cc9c52b4 100644 --- a/engine/game/src/main/kotlin/org/rsmod/game/queue/PlayerQueueList.kt +++ b/engine/game/src/main/kotlin/org/rsmod/game/queue/PlayerQueueList.kt @@ -139,6 +139,30 @@ public class PlayerQueueList { return false } + /** + * Whether any queue of [category] is due to fire on [cycle]: its remaining cycles, after the + * decrement the processor applies once per cycle, would reach zero. + */ + public fun anyDue(category: QueueCategory, cycle: Int): Boolean { + if (isEmpty) { + return false + } + + var current = first + while (current != null) { + val queue = current.queue + if (queue.category == category.id) { + val remaining = + if (queue.processedCycle != cycle) queue.remainingCycles - 1 else queue.remainingCycles + if (remaining <= 0) { + return true + } + } + current = current.next ?: break + } + return false + } + public fun clear() { iterator?.cleanUp() diff --git a/engine/game/src/main/kotlin/org/rsmod/game/timer/PlayerTimerMap.kt b/engine/game/src/main/kotlin/org/rsmod/game/timer/PlayerTimerMap.kt index c84981a35..da9c1c641 100644 --- a/engine/game/src/main/kotlin/org/rsmod/game/timer/PlayerTimerMap.kt +++ b/engine/game/src/main/kotlin/org/rsmod/game/timer/PlayerTimerMap.kt @@ -20,6 +20,9 @@ public class PlayerTimerMap( timers.remove(timer.asRSCM(RSCMType.TIMER).toShort()) } + public operator fun contains(timer: String): Boolean = + timers.containsKey(timer.asRSCM(RSCMType.TIMER).toShort()) + @OptIn(InternalApi::class) public fun schedule(timer: String, mapClock: Int, interval: Int) { put(timer.asRSCM(RSCMType.TIMER).toShort(), mapClock, interval) diff --git a/or-cache/src/main/kotlin/dev/openrune/impl/Music.kt b/or-cache/src/main/kotlin/dev/openrune/impl/Music.kt index b5ba8df06..cd6bf7e8d 100644 --- a/or-cache/src/main/kotlin/dev/openrune/impl/Music.kt +++ b/or-cache/src/main/kotlin/dev/openrune/impl/Music.kt @@ -30,24 +30,751 @@ fun packVertex(mx: Int, mz: Int, lx: Int, lz: Int): Int { object Music { + /** + * A music area and the tracks that shuffle inside it. [area] is the bare area gameval name + * (`area.` must exist in `.data/gamevals/area.rscm` and have a polygon under + * `.data/raw-cache/map/area`), and [tracks] are `dbrow.music_*` rows from the cache. + */ + private data class ModernArea(val row: String, val area: String, val tracks: List) + + private fun modern(area: String, vararg tracks: String) = + ModernArea("dbrow.music_modern_$area", area, tracks.map { "dbrow.music_$it" }) + + /** Track lists follow the OSRS wiki "Music" section of each location. */ + private val MODERN_AREAS = + listOf( + modern("lumbridge", "autumn_voyage", "book_of_spells", "dream", "flute_salad", "harmony", "yesteryear"), + modern("draynor_village", "start", "unknown_land", "wander"), + modern("draynor_manor", "spooky"), + modern("wizards_tower", "vision"), + modern("zanaris", "faerie", "crystal_cave"), + modern( + "varrock", + "adventure", + "garden", + "medieval", + "spirit", + "looking_back", + "doorways", + "expanse", + "greatness", + "still_night", + ), + // The Grand Exchange plays its own track; its footprint is carved out of the Varrock + // polygon so the two areas never overlap. + modern("grand_exchange", "the_trade_parade"), + modern("barbarian_village", "barbarianism"), + modern("edgeville", "forever"), + // The Wilderness gameplay area already has an onArea script, and an area may only have + // one, so its music lives on a twin area with the same polygon. + modern( + "wilderness_music", + "army_of_darkness", + "close_quarters", + "dangerous", + "everlasting_fire", + "faithless", + "forbidden", + "inspiration", + "moody", + "pirates_of_peril", + "regal", + "scape_sad", + "scape_wild", + "shining", + "troubled", + "undercurrent", + "underground", + "wild_isle", + "wild_side", + "wilderness", + "wilderness2", + "wilderness3", + "witching", + "wonder", + "crystal_sword", + "dark", + "dead_can_dance", + "deep_wildy", + "venomous", + "wildwood", + ), + modern("falador", "arrival", "fanfare", "workshop"), + modern("falador_south", "long_way_home", "miles_away", "nightfall", "wander"), + modern("falador_north", "lightness", "scape_soft"), + modern("taverley", "horizon", "splendour"), + modern("rimmington", "attention", "emperor", "long_way_home"), + modern("burthorpe", "principality", "kingdom"), + modern("goblin_village", "goblin_village", "too_many_cooks"), + modern("ice_mountain", "alone"), + modern("asgarnian_ice_dungeon", "starlight", "woe_of_the_wyvern"), + modern("taverley_dungeon", "arabique", "courage", "dunjun", "royale", "underground"), + modern("catherby", "fishing", "lightwalk"), + modern("seers_village", "overture", "prime_time", "twilight", "talking_forest"), + modern("camelot", "camelot"), + modern("rellekka", "rellekka"), + modern("brineratcavern", "rising_damp"), + modern("barbarian_outpost", "legion"), + modern("al_kharid", "al_kharid", "arabian", "arabian2"), + modern("port_sarim", "sea_shanty2"), + modern("mudskipper_point", "mudskipper_melody"), + modern("fishing_guild", "mellow", "voyage", "waterfall"), + modern("east_ardougne", "baroque", "knightly", "the_tower"), + modern("west_ardougne", "sad_meadow"), + modern("ardougne_sewers", "the_cellar_dwellers"), + modern("south_ardougne", "ballad_of_enchantment", "upcoming"), + modern("legends_guild", "trinity"), + modern("mourner_tunnels", "fight_or_flight"), + modern("emirs_arena_music", "duelarena"), + modern("mage_training_arena", "shine"), + // Since the February 2026 music rework the three stronghold tracks shuffle everywhere + // inside the walls, while Gnomeball is confined to the ball field, which is carved out + // of the stronghold polygon. + modern("tree_gnome_stronghold", "gnome_king", "gnome_village", "gnome_village2"), + modern("gnome_ball_field", "gnomeball"), + modern("yanille", "big_chords", "magic_dance"), + modern("yanille_chain", "long_ago"), + modern("yanille_agility_dungeon", "cavern"), + // "Castle Wars" plays in the lobby and the arena; "Ready for Battle" in the waiting + // rooms and the tunnels underneath. + modern("castle_wars", "castlewars"), + modern("castle_wars_underground", "ready_for_battle"), + // Everything below was traced from the OSRS wiki music map (Map:Music tracks); + // the area polygons live in .data/raw-cache/map/area as rectangles generated from + // that map. Areas are named after the wiki polygon: a location where several tracks + // shuffle, otherwise the single track that plays there. + modern("a_dangerous_game", "revenant_slayer_cave"), + modern("a_desolate_past", "mokhaiotl"), + modern("a_forgotten_religion", "forthos_dungeon"), + modern("a_mothers_curse", "toa_boss_kephri"), + modern("a_taste_of_hope", "ranisduel"), + modern("a_walk_in_the_woods", "woodland_walk"), + modern("aldarin", "aldarin", "aldarin_2"), + modern("alls_fairy_in_love_and_war", "alls_fairy_in_love_n_war"), + modern("alone", "alone"), + modern("altar_ego", "altarego"), + modern("alternative_root", "alternative_root"), + modern("amascuts_promise", "toa_boss_wardens"), + modern("ambient_jungle", "ambient_jungle"), + modern("ape_atoll", "monkey_madness", "anywhere", "island_life"), + modern("ape_ex_predator", "toa_boss_baba"), + modern("arachnids_of_vampyrium", "nylocas_combat"), + modern("arboretum", "arboretum"), + modern("arcane", "arceuus"), + modern("are_you_not_entertained", "fortis_colosseum"), + modern("armadyl_alliance", "armadyl_alliance"), + modern("armageddon", "armageddon"), + modern("artistry", "artistry"), + modern("ascent", "ascent"), + modern("assault_and_battery", "assault_and_battery"), + modern("attack_1", "attack1"), + modern("attack_2", "attack2"), + modern("attack_3", "attack3"), + modern("attack_4", "attack4"), + modern("attack_5", "attack5"), + modern("attack_6", "attack6"), + modern("auburn_valley", "auburn_valley_1", "auburn_valley_2"), + modern("avium_savannah", "avium_savannah", "avium_savannah_2", "avium_savannah_3"), + modern("aye_car_rum_ba", "aye_car_rum_ba"), + modern("aztec", "aztec"), + modern("back_to_life", "back_to_life"), + modern("background", "background"), + modern("bandit_camp", "bandit_camp"), + modern("bandos_battalion", "bandos_battalion"), + modern("bane", "corporealbeast"), + modern("barb_wire", "barb_wire"), + modern("barking_mad", "barking_mad"), + modern("barren_land", "barrenland"), + modern("beetle_juice", "beetle_juice"), + modern("below_the_conch", "great_conch_underground"), + modern("beneath_cursed_sands", "toa_ambience"), + modern("beneath_the_kingdom", "kourend_catacombs"), + modern("beneath_the_stronghold", "beneath_the_stronghold"), + modern("beyond", "beyond"), + modern("beyond_the_meadow", "hosidius_outskirts"), + modern("black_of_knight", "black_of_knight"), + modern("blistering_barnacles", "blistering_barnacles"), + modern("bloodbath", "bloodbath"), + modern("body_parts", "body_parts"), + modern("bone_dance", "bone_dance"), + modern("bone_dry", "bone_dry"), + modern("box_of_delights", "boxofdelights"), + modern("brew_hoo_hoo", "brew_hoo_hoo"), + modern("brimhaven", "high_seas", "landlubber", "jollyr"), + modern("brimstails_scales", "brimstails_scales"), + modern("bubble_and_squeak", "bubble_and_squeak"), + modern("burning_desire", "burning_desire"), + modern("business_as_unusual", "red_rock_island"), + modern("cabin_fever", "cabin_fever"), + modern("call_of_the_tlati", "tlati_rainforest"), + modern("cannonballin", "sailing_combat_02"), + modern("catacombs_and_tombs", "catacombs_and_tombs"), + modern("catch_me_if_you_can", "catch_me_if_you_can"), + modern("cave_background", "cave_background"), + modern("cave_of_beasts", "cave_of_beasts"), + modern("cave_of_the_goblins", "cave_of_the_goblins"), + modern("cavern", "cavern"), + modern("cellar_song", "cellar_song"), + modern("chain_of_command", "chain_of_command"), + modern("chamber", "chamber"), + modern("chickened_out", "chickened_out"), + modern("city_of_the_dead", "city_of_the_dead"), + modern("civitas_illa_fortis", "civitas_illa_fortis", "civitas_illa_fortis_2"), + modern("clan_wars", "clanwars"), + modern("clanliness", "clanliness"), + modern("claustrophobia", "claustrophobia"), + modern("coil", "coil"), + modern("colossus_of_the_deep", "dt2_leviathan"), + modern("competition", "competition"), + modern("complication", "complication"), + modern("conspiracy", "conspiracy"), + modern("corporal_punishment", "corporal_punishment"), + modern("corridors_of_power", "corridors_of_power"), + modern("country_jig", "hosidius"), + modern("creature_cruelty", "creature_cruelty"), + modern("creeping_vines", "hespori_cave"), + modern("crystal_castle", "crystal_castle"), + modern("crystal_sword", "crystal_sword"), + modern("cursed", "cursed"), + modern("dagannoth_dawn", "dagannoth_dawn"), + modern("dance_of_death", "dance_of_death"), + modern("dance_of_the_undead", "dance_of_the_undead"), + modern("dangerous_logic", "dangerous_logic"), + modern("dangerous_road", "dangerous_road"), + modern("dangerous_way", "dangerous_way"), + modern("darkly_altared", "darklyaltared"), + modern("darkmeyer_music", "darkmeyer"), + modern("darkness_in_the_depths", "darknessinthedepths"), + modern("davy_jones_locker", "davy_jones_locker"), + modern("dead_quiet", "dead_quiet"), + modern("dead_quiet_2", "stagnant", "waterlogged", "dead_quiet"), + modern("deadlands", "deadlands"), + modern("deep_down", "deep_down"), + modern("devils_may_care", "devils_may_care"), + modern("digsite", "lullaby", "venture"), + modern("dimension_x", "dimension_x"), + modern("distant_land", "distant_land"), + modern("distillery_hilarity", "distillery_hilarity"), + modern("dogs_of_war", "dogs_of_war"), + modern("doorways", "doorways"), + modern("dorgeshuun_city", "dorgeshuun_city"), + modern("dorgeshuun_deep", "dorgeshuun_deep"), + modern("down_and_out", "down_and_out"), + modern("down_below", "down_below"), + modern("down_by_the_docks", "piscarilius"), + modern("down_to_earth", "down_to_earth"), + modern("dragontooth_island", "dragontooth_island"), + modern("dreamstate", "dreamstate"), + modern("dunes_of_eternity", "necropolis"), + modern("dusk_in_yubiusk", "dusk_in_yubiusk"), + modern("dwarf_theme", "dwarf_theme"), + modern("dwarven_domain", "lovakengj"), + modern("dynasty", "dynasty"), + modern("eagles_peak", "eagle_peak"), + modern("east_ardougne_music", "baroque", "the_tower", "knightly"), + modern("elder_wisdom", "great_conch_2"), + modern("elven_seed", "elven_seed"), + modern("emissaries_of_twilight", "twilight_temple"), + modern("emotion", "emotion"), + modern("escape", "escape"), + modern("espionage", "espionage"), + modern("etceteria", "etceteria"), + modern("everywhere", "everywhere"), + modern("evil_bobs_island", "evil_bobs_island"), + modern("expecting", "expecting"), + modern("expedition", "expedition"), + modern("exposed", "exposed"), + modern("fanfare_2", "fanfare2"), + modern("fanfare_3", "fanfare3"), + modern("fangs_for_the_memory", "fangs_for_the_memory"), + modern("far_away", "far_away"), + modern("farming_guild", "a_farmers_grind", "grow_grow_grow", "hoe_down"), + modern("fe_fi_fo_fum", "fe_fi_fo_fum"), + modern("fear_and_loathing", "fear_and_loathing"), + modern("feldip_hills", "chompy_hunt", "grumpy", "jungle_hunt"), + modern("fenkenstrains_refrain", "fenkenstrains"), + modern("find_my_way", "find_my_way"), + modern("fire_and_brimstone", "fire_and_brimstone"), + modern("fire_in_the_deep", "olmbattle"), + modern("food_for_thought", "food_for_thought"), + modern("forever", "forever"), + modern("forgettable_melody", "forgettable_melody", "right_on_track"), + modern("fossilised", "fossil_underground"), + modern("fremennik_province", "legend", "saga", "borderland"), + modern("frogland", "frogland"), + modern("frostbite", "frostbite"), + modern("fruits_de_mer", "fruits_de_mer"), + modern("gaol", "gaol"), + modern("garden_of_autumn", "garden_of_autumn"), + modern("garden_of_spring", "garden_of_spring"), + modern("garden_of_summer", "garden_of_summer"), + modern("garden_of_winter", "garden_of_winter"), + modern("getting_down_to_business", "getting_down_to_business"), + modern("gill_bill", "gill_bill"), + modern("goblin_game", "goblin_game"), + modern("grotto", "grotto"), + modern("guardians_of_the_rift", "gotr_minigame"), + modern("h_a_m_and_seek", "ham_and_seek"), + modern("h_a_m_attack", "ham_attack"), + modern("h_a_m_fisted", "ham_fisted"), + modern("harmony", "harmony", "chef_surprise"), + modern("harmony_2", "harmony2"), + modern("haunted_mine", "haunted_mine"), + modern("have_a_blast", "have_a_blast"), + modern("have_an_ice_day", "have_an_ice_day"), + modern("head_to_head", "head_to_head"), + modern("heart_and_mind", "heart_and_mind"), + modern("hells_bells", "hells_bells"), + modern("hemenster", "lasting", "wonderous", "magical_journey"), + modern("hermit", "hermit"), + modern("home_sweet_home", "home_sweet_home"), + modern("horizon", "horizon"), + modern("iban", "iban"), + modern("ice_and_fire", "wintertodt"), + modern("ice_melody", "ice_melody"), + modern("impetuous", "impetuous"), + modern("in_between", "in_between"), + modern("in_the_brine", "in_the_brine"), + modern("in_the_clink", "in_the_clink"), + modern("in_the_manor", "in_the_manor"), + modern("in_the_pits", "in_the_pits"), + modern("inferno", "inferno"), + modern("insect_queen", "insect_queen"), + modern("into_the_abyss", "into_the_abyss"), + modern("into_the_tombs", "toa_lobby"), + modern("intrepid", "intrepid"), + modern("invader", "sire"), + modern( + "isafdar", + "riverside", + "breeze", + "elven_mist", + "meridian", + "forest", + "woodland", + ), + modern("island_life", "island_life"), + modern("island_of_the_trolls", "island_of_the_trolls"), + modern("its_not_over_til", "verzik_ambience", "verzik_combat"), + modern("itsy_bitsy", "araxyte"), + modern("jaws_of_gluttony", "toa_boss_zebak"), + modern("judgement_of_the_depths", "judge_of_yama"), + modern( + "karamja", + "reggae", + "reggae2", + "tribal2", + "jungly1", + "jungly2", + "jungly3", + "tribal", + "jungle_troubles", + ), + modern( + "karuulm_slayer_dungeon_music", + "ful_to_the_brim", + "kanon_of_khalith", + "way_of_the_wyrm", + ), + modern("keldagrim_music", "land_of_the_dwarves", "tale_of_keldagrim"), + modern( + "kharidian_desert", + "scarab", + "egypt", + "the_desert", + "arabian3", + "desert_voyage", + "sunburn", + "desert_heat", + "sphinx", + ), + modern("knightmare", "knightmare"), + modern("kourend_the_magnificent", "kourendthemagnificent"), + modern("la_mort", "la_mort"), + modern("labyrinth", "labyrinth"), + modern("lagoon", "fossil_underwater"), + modern("laid_to_rest", "toa_victory"), + modern("lair", "lair"), + modern("lair_of_the_basilisk", "basilisk_lair"), + modern("lament", "lament"), + modern("lament_for_the_hallowed", "icyene_graveyard"), + modern("lament_of_meiyerditch", "lament_of_meiyerditch"), + modern("lamistards_labyrinth", "lamistards_labyrinth"), + modern("land_down_under", "land_down_under"), + modern("last_man_standing", "lastmanstanding"), + modern("lava_is_mine", "fossil_lava_mine"), + modern("legion", "legion"), + modern("life_at_sea", "industrial_islands"), + modern("lighthouse", "lighthouse"), + modern("little_cave_of_horrors", "little_cave_of_horrors"), + modern("lonesome", "lonesome"), + modern("looking_back", "looking_back"), + modern("lord_of_the_larvae", "maggot_boss"), + modern("lore_and_order", "lore_and_order"), + modern("lost_soul", "lost_soul"), + modern("lost_to_time", "old_one_ruins"), + modern("lower_depths", "lowerdepths"), + modern("lucid_dream", "luciddream"), + modern("lucid_nightmare", "lucidnightmare"), + modern("lumbering", "weiss_town"), + modern("lunar_isle", "isle_of_everywhere", "the_lunar_isle"), + modern("lurking_threats", "dangerous_islands"), + modern("mad_eadgar", "mad_eadgar"), + modern("mage_arena", "mage_arena"), + modern("magic_magic_magic", "magic_magic_magic"), + modern("major_miner", "major_miner"), + modern("making_waves", "making_waves"), + modern("malady", "malady"), + modern("march_of_the_shayzien", "shayzienmarch"), + modern("marooned", "marooned"), + modern("marzipan", "marzipan"), + modern("masquerade", "masquerade"), + modern("mausoleum", "mausoleum"), + modern("maws_jaws_and_claws", "mawsjawsclaws"), + modern("method_of_madness", "method_of_madness"), + modern("miles_away", "miles_away"), + modern("military_life", "shayzien_camp"), + modern("mind_over_matter", "mind_over_matter"), + modern("miracle_dance", "miracle_dance"), + modern("mirage", "mirage"), + modern("miscellania", "miscellania"), + modern("mistrock", "mistrock"), + modern("monarch_waltz", "monarch_waltz"), + modern("monkey_business", "mm2_kruk"), + modern("monkey_madness", "monkey_madness"), + modern("monkey_sadness", "mm2_bunkerlab"), + modern("monkey_trouble", "mm2_bunkercorridors"), + modern("monster_melee", "monster_melee"), + modern("mor_ul_rek", "mor_ul_rek"), + modern("more_than_meets_the_eye", "muspah_boss"), + modern("morytania", "morytania"), + modern("mos_leharmless", "in_the_brine", "lifes_a_beach"), + modern("mouse_trap", "mouse_trap"), + modern("musa_point", "sea_shanty", "jungle_island"), + modern("museum_medley", "museummedley"), + modern("mutant_medley", "mutant_medley"), + modern("my_arms_journey", "my_arms_journey"), + modern("mysterious_lands", "mysterious_islands"), + modern("mystics_of_nature", "amlodd"), + modern("mythical", "mythsguild"), + modern("narnodes_theme", "narnodes"), + modern("natural", "natural"), + modern("nether_realm", "karamja_jam", "7th_realm", "pathways", "nether_realm"), + modern("neverland", "neverland"), + modern("newbie_farming", "newbie_farming"), + modern("newbie_melody", "newbie_melody"), + modern( + "night_of_the_vampyre", + "lament_of_meiyerditch", + "the_last_shanty", + "night_of_the_vampyre", + ), + modern("no_pasaran", "no_pasaran"), + modern("no_way_out", "no_way_out"), + modern("nomad", "nomad"), + modern("norse_code", "norse_code"), + modern("not_a_moment_of_relief", "arceuus_outskirts"), + modern("noxious_awakening", "araxxor"), + modern("null_and_void", "null_and_void"), + modern("observatory", "soundscape", "serenade", "melodrama"), + modern("ogre_the_top", "ogre_the_top"), + modern("oh_rats", "rat_boss"), + modern("on_the_frontline", "battlefront"), + modern("on_the_wing", "on_the_wing"), + modern("on_thin_ice", "zombiedragon"), + modern("oriental", "oriental"), + modern("out_at_the_mines", "lovakengj_outskirts"), + modern("out_of_the_deep", "out_of_the_deep"), + modern("outpost", "tree_spirits", "neverland", "march"), + modern("over_the_mountains", "custodia_mountains"), + modern("over_to_nardah", "over_to_nardah"), + modern("overpass", "overpass"), + modern("parade", "parade"), + modern("path_of_peril", "path_of_peril"), + modern("pest_control", "pest_control"), + modern("pharaohs_tomb", "pharaohs_tomb"), + modern("phasmatys", "phasmatys"), + modern("pheasant_peasant", "pheasant_peasant"), + modern("pick_and_shovel", "pick_and_shovel"), + modern("pinball_wizard", "pinball_wizard"), + modern("pirates_of_penance", "pirates_of_penance"), + modern("piscatoris_hunter_area", "joy_of_the_hunt", "on_the_wing"), + modern("poles_apart", "poles_apart"), + modern("power_of_the_shadow_realm", "sotetseg_combat"), + modern("predator_xarpus", "xarpus_ambience", "xarpus_combat"), + modern("preservation", "fossil_overground_2"), + modern("preserved", "fossil_overground_1"), + modern( + "prifddinas", + "ithell", + "dance_of_the_meilyr", + "city_guardians", + "faith_of_the_hefin", + "iowerths_lament", + "amlodd", + "crwys", + "the_tower_of_voices", + "traehern_toil", + ), + modern("prime_time", "prime_time"), + modern("prospering_fortune", "civitas_illa_fortis_2"), + modern("quest", "quest"), + modern("race_against_the_clock", "camdozaal_vault"), + modern("rat_a_tat_tat", "rat_a_tat_tat"), + modern("rat_hunt", "rat_hunt"), + modern("ready_for_the_hunt", "hunter_guild"), + modern("regal_pomp", "coronation"), + modern("relics", "dragonkintemple"), + modern("remnants", "sangvesti"), + modern("rest_in_peace", "restinpeace"), + modern("revenants", "wilderness_cave"), + modern("righteousness", "righteousness"), + modern("romancing_the_crone", "romancing_the_crone"), + modern("romper_chomper", "romper_chomper"), + modern("roots_and_flutes", "roots_and_flutes"), + modern("rose", "rose"), + modern("rugged_terrain", "ruggedterrain"), + modern("ruins_of_isolation", "ullek"), + modern("rune_essence", "rune_essence"), + modern("sad_meadow", "sad_meadow"), + modern("safety_in_numbers", "sourhog"), + modern("saga", "saga"), + modern("sands_of_time", "toa_boss_akkha"), + modern("sarachnis", "sarachnis_lair"), + modern("sarcophagus", "sarcophagus"), + modern("sarims_vermin", "sarims_vermin"), + modern("scape_cave", "scape_cave"), + modern("scape_main", "scape_main"), + modern("scar_tissue", "dt2_scar"), + modern("schools_out", "schools_out"), + modern("scorpia_dances", "scorpia_dances"), + modern("sea_shanty_2", "sea_shanty2"), + modern("secrets_of_the_north", "ghorrock_dungeon"), + modern("serene", "serene"), + modern("servants_of_strife", "molch"), + modern("settlement", "settlement"), + modern("seventh_realm", "7th_realm"), + modern("shadowland", "shadowland"), + modern("sharp_end_of_the_crystal", "iowerth_slayer_dungeon"), + modern("shining_spirit", "shiningspirit"), + modern("shipwrecked", "shipwrecked"), + modern("showdown", "showdown"), + modern("sigmunds_showdown", "sigmunds_showdown"), + modern("sign_here", "yama_combat"), + modern("slice_of_silent_movie", "slice_of_silent_movie"), + modern("slice_of_station", "slice_of_station"), + modern("slither_and_thither", "slither_and_thither"), + modern("slug_a_bug_ball", "slug_a_bug_ball"), + modern("solitude", "sugadintis_hideout"), + modern("song_of_the_elves", "warped_library"), + modern("soul_fall", "soulfall"), + modern("soul_wars", "soul_wars"), + modern("spirits_of_the_elid", "spirits_of_elid"), + modern("spooky", "spooky"), + modern("spooky_2", "spooky2"), + modern("spooky_jungle", "spookyjungle"), + modern("stagnant", "stagnant"), + modern("stalkers", "stalker_dungeon"), + modern("stillness", "stillness"), + modern("stones_of_old", "quetzacalli_gorge"), + modern("storeroom_shuffle", "storeroom_shuffle"), + modern("storm_brew", "storm_brew"), + modern("stranded", "stranded"), + modern("strange_place", "strange_place"), + modern("strangled", "dt2_stranglewood"), + modern("stratosphere", "stratosphere"), + modern("strength_of_saradomin", "strength_of_saradomin"), + modern("stuck_in_the_mire", "stuck_in_the_mire"), + modern("subterranea", "subterranea"), + modern("superstition", "superstition"), + modern("suspicious", "suspicious"), + modern("tears_of_guthix", "tears_of_guthix_music"), + modern("technology", "technology"), + modern("teklan", "tal_teklan"), + modern("tempest", "gargoyleboss"), + modern("temple", "temple"), + modern("temple_desecrated", "temple_desecrated"), + modern("temple_of_light", "temple_of_light"), + modern("temple_of_the_eye", "temple_of_the_eye"), + modern("temple_of_tribes", "temple_of_tribes"), + modern("tempor_of_the_storm", "tempoross"), + modern("that_sullen_hall", "thatsullenhall"), + modern("the_ancient_prison", "ancient_prison"), + modern("the_angels_fury", "the_angels_fury"), + modern("the_bane_of_ashihama", "nightmare_combat"), + modern("the_chosen", "the_chosen"), + modern("the_consortium", "giant_dwarf_meeting"), + modern("the_curtain_closes", "verzik_defeated"), + modern("the_depths", "the_depths"), + modern("the_desolate_isle", "the_desolate_isle"), + modern("the_desolate_mage", "desolatemage"), + modern("the_doom", "doom_boss"), + modern("the_doors_of_dinh", "wasteland"), + modern("the_enchanter", "the_enchanter", "roll_the_bones", "golden_touch"), + modern("the_enclave", "enclave"), + modern("the_everlasting_slumber", "slepe_dungeon"), + modern("the_fallen_empire", "dt2_lassar"), + modern("the_far_side", "the_far_side"), + modern("the_feathered_serpent", "hueycoatl"), + modern("the_fires_of_lletya", "lletyas_destruction"), + modern("the_forests_of_shayzien", "shayzien_outskirts"), + modern("the_forgotten_tomb", "mehhars_tomb"), + modern("the_forlorn_homestead", "forlornhomestead"), + modern("the_forsaken", "dragonkintheme"), + modern("the_forsaken_tower", "forsaken_tower"), + modern("the_foundry", "giants_foundry"), + modern("the_fragment", "the_dark_fragment"), + modern("the_galleon", "the_galleon"), + modern("the_gauntlet", "gauntlet_minigame"), + modern("the_genie", "the_genie"), + modern("the_golem", "the_golem"), + modern("the_great_conch", "great_conch_2", "great_conch"), + modern("the_guardian_of_tapoyauik", "amoxliatl"), + modern("the_guidance_of_ralos", "ralos_rise"), + modern("the_houses_of_kourend", "kourend_houses"), + modern("the_longramble_scramble", "longramble_scramble"), + modern("the_lost_melody", "the_lost_melody"), + modern("the_lost_tribe", "the_lost_tribe"), + modern("the_mad_mole", "the_mad_mole"), + modern("the_maidens_anger", "maiden_combat"), + modern("the_maze", "melzars_maze"), + modern("the_militia", "shayzien"), + modern("the_mollusc_menace", "the_mollusc_menace"), + modern("the_monsters_below", "the_monsters_below"), + modern("the_moons_of_ruin", "perilous_moons"), + modern("the_navigator", "the_navigator"), + modern("the_nightmare_continues", "pestilent_bloat_combat"), + modern("the_north", "the_north"), + modern("the_other_side", "the_other_side"), + modern("the_pandemonium", "honkytonky_sea_shanty2", "yo_ho_ho"), + modern("the_part_where_you_die", "xamphur"), + modern("the_plundered_tomb", "the_plundered_tomb"), + modern("the_power_of_tears", "the_power_of_tears"), + modern("the_quizmaster", "the_quizmaster"), + modern("the_rogues_den", "the_rogues_den"), + modern("the_route_of_all_evil", "the_route_of_all_evil"), + modern("the_ruins_of_camdozaal", "camdozaal_ruins"), + modern("the_shadow", "the_shadow"), + modern("the_slayer", "the_slayer"), + modern("the_sound_of_guthix", "the_sound_of_guthix"), + modern("the_spurned_demon", "zalcano_combat"), + modern("the_talkasti_people", "kastori"), + modern("the_terrible_tower", "the_terrible_tower"), + modern("the_terrible_tunnels", "terrible_tunnels"), + modern("the_tortugan_way", "great_conch_2", "great_conch"), + modern("the_vault", "dt2_vault"), + modern("the_waiting_game", "waiting_game"), + modern("the_walking_dead", "shayziencrypt"), + modern("theme", "theme"), + modern("thrall_of_the_serpent", "thrall_of_the_serpent"), + modern("throne_of_the_demon", "throne_of_the_demon", "forgotten"), + modern("time_out", "time_out"), + modern("time_to_mine", "time_to_mine"), + modern("tiptoe", "tiptoe"), + modern("title_fight", "title_fight"), + modern("tomb_raider", "tomb_raider"), + modern("tomorrow", "tomorrow"), + modern("too_many_cooks", "too_many_cooks"), + modern("trahaearn_toil", "traehern_toil"), + modern("tranquillity", "calm_islands"), + modern("trawler", "trawler"), + modern("trawler_minor", "trawler_minor"), + modern("tribal_background", "tribal_background"), + modern("trinity", "trinity"), + modern("troll_country", "tremble", "sojourn", "contest"), + modern("troll_shuffle", "trollshuffle"), + modern("trouble_brewing", "trouble_brewing"), + modern("troubled_waters", "troubled_waters"), + modern("twilight", "twilight"), + modern("tzhaar", "tzhaar"), + modern("undead_dungeon", "undead_dungeon"), + modern("under_the_mountain", "cam_torum"), + modern("underground", "underground"), + modern("underground_pass", "upass"), + modern("understanding", "understanding"), + modern("untouchable", "untouchable"), + modern("unturned_stones", "dt2_digsite"), + modern("upcoming", "upcoming"), + modern("upir_likhyi", "upir_likhyi"), + modern("upper_depths", "upperdepths"), + modern("varlamores_sunset", "sunset_coast"), + modern("venture_2", "venture2"), + modern("victory_is_mine", "victory_is_mine"), + modern("village", "village"), + modern("volcanic_vikings", "volcanic_vikings"), + modern("voodoo_cult", "voodoo_cult"), + modern("waking_dream", "waking_dream"), + modern("warrior", "warrior"), + modern("warriors_guild", "warriors_guild"), + modern("waste_defaced", "waste_defaced"), + modern("watch_your_step", "hallowed_sepulchre"), + modern("waterfall", "waterfall"), + modern("waterlogged", "waterlogged"), + modern("way_of_the_enchanter", "way_of_the_enchanter"), + modern("wayward", "wayward"), + modern("we_are_the_fairies", "we_are_the_fairies"), + modern("welcome_to_the_theatre", "thetheatre"), + modern("well_hallowed_air", "hallowed_sepulchre_lobby"), + modern("well_of_voyage", "well_of_voyage"), + modern("what_happens_below", "warrens"), + modern("what_the_shell", "gryphon_boss"), + modern("where_eagles_lair", "where_eagles_lair"), + modern("whispering_wind", "hailstorm_mountains"), + modern( + "wilderness_2", + "venomous", + "underground", + "wild_isle", + "wildwood", + "moody", + "witching", + "wonder", + "deep_wildy", + "wilderness2", + "wilderness3", + "close_quarters", + "dark", + "inspiration", + "shining", + "forbidden", + "army_of_darkness", + "crystal_sword", + "dangerous", + "troubled", + "regal", + "scape_sad", + "scape_wild", + "faithless", + "dead_can_dance", + "everlasting_fire", + "undercurrent", + "wild_side", + "wilderness", + ), + modern("wings_of_madness", "mad_angel_boss"), + modern("wonder", "wonder"), + modern("work_work_work", "work_work_work"), + modern("wrath_and_ruin", "wrath_and_ruin"), + modern("xenophobe", "xenophobe"), + modern("zamorak_zoo", "zamorak_zoo"), + modern("zaniks_theme", "zaniks_theme"), + modern("zealot", "zealot"), + modern("zogre_dance", "zogre_dance"), + modern("zombiism", "zombiism"), + ) + fun musicModern() = dbTable("dbtable.music_modern", serverOnly = true) { column("area", 0, VarType.STRING) column("tracks", 1, VarType.DBROW) column("auto_script", 2, VarType.BOOLEAN) - row("dbrow.music_modern_lumbridge") { - column(0, "lumbridge") - columnRSCM( - 1, - "dbrow.music_autumn_voyage", - "dbrow.music_book_of_spells", - "dbrow.music_dream", - "dbrow.music_flute_salad", - "dbrow.music_harmony", - "dbrow.music_yesteryear", - ) - column(2, true) + for (entry in MODERN_AREAS) { + row(entry.row) { + column(0, entry.area) + columnRSCM(1, *entry.tracks.toTypedArray()) + column(2, true) + } } } diff --git a/or-cache/src/main/kotlin/dev/openrune/map/area/MapAreaDecoder.kt b/or-cache/src/main/kotlin/dev/openrune/map/area/MapAreaDecoder.kt index 70c874bfe..3b6535656 100644 --- a/or-cache/src/main/kotlin/dev/openrune/map/area/MapAreaDecoder.kt +++ b/or-cache/src/main/kotlin/dev/openrune/map/area/MapAreaDecoder.kt @@ -30,8 +30,8 @@ public object MapAreaDecoder { areas } - // Zone areas - cursor = buf.readUnsignedByte(cursor) + // Zone areas (up to 256 entries, so the count is a short) + cursor = buf.readShort(cursor) val zoneCount = cursor.value val zoneAreas: Byte2ObjectMap = if (zoneCount == 0) { diff --git a/or-cache/src/main/kotlin/dev/openrune/map/area/MapAreaEncoder.kt b/or-cache/src/main/kotlin/dev/openrune/map/area/MapAreaEncoder.kt index bd6e43cfb..92ebbfcd0 100644 --- a/or-cache/src/main/kotlin/dev/openrune/map/area/MapAreaEncoder.kt +++ b/or-cache/src/main/kotlin/dev/openrune/map/area/MapAreaEncoder.kt @@ -35,7 +35,9 @@ public object MapAreaEncoder { data.writeShort(area.toInt()) } - data.writeByte(zoneAreas.size) + // A map square split between areas on every level has up to 256 zone entries, + // which does not fit in a byte. + data.writeShort(zoneAreas.size) for ((packed, areas) in zoneAreas) { check(areas.isNotEmpty()) { val localZone = LocalMapSquareZone(packed.toInt()) diff --git a/or-cache/src/main/kotlin/dev/openrune/rscm/RSCM.kt b/or-cache/src/main/kotlin/dev/openrune/rscm/RSCM.kt index 6f3c4765f..53da5c6ec 100644 --- a/or-cache/src/main/kotlin/dev/openrune/rscm/RSCM.kt +++ b/or-cache/src/main/kotlin/dev/openrune/rscm/RSCM.kt @@ -2,6 +2,7 @@ package dev.openrune.rscm import dev.openrune.definition.constants.ConstantProvider import dev.openrune.rscm.RSCMType.Companion.RSCM_PREFIXES +import java.util.concurrent.ConcurrentHashMap enum class RSCMType(val prefix: String) { AREA("area"), @@ -47,10 +48,8 @@ enum class RSCMType(val prefix: String) { companion object { val RSCM_PREFIXES = entries.map { it.prefix }.toSet() } - } - object RSCM { val NONE = "NONE" @@ -84,9 +83,37 @@ object RSCM { } } + /** + * Id-to-name index of one gameval table. [size] is the size of the table it was built from, + * so a table that has grown since (through `ConstantProvider.addMapping`) is re-indexed. + */ + private class ReverseIndex(val size: Int, val names: Map) + + private val reverseCache = ConcurrentHashMap() + + /** + * The gameval name mapped to [value] in [table]. + * + * `ConstantProvider.getReverseMapping` scans the whole table on every call, and combat, stat + * updates, shops and interactions ask for names every cycle, so each table is indexed once + * here. Like the provider, the first name mapped to an id wins, and an unknown id or table + * still throws. + */ fun getReverseMapping(table: RSCMType, value: Int): String { if (value == -1) return "-1" - return ConstantProvider.getReverseMapping(table.prefix, value) + val prefix = table.prefix + val mappings = ConstantProvider.mappings[prefix] + ?: return ConstantProvider.getReverseMapping(prefix, value) + var index = reverseCache[prefix] + if (index == null || index.size != mappings.size) { + val names = HashMap(mappings.size * 2) + for ((name, id) in mappings) { + names.putIfAbsent(id, name) + } + index = ReverseIndex(mappings.size, names) + reverseCache[prefix] = index + } + return index.names[value] ?: ConstantProvider.getReverseMapping(prefix, value) } fun getRSCM(entity: String): Int { diff --git a/or-cache/src/main/kotlin/dev/openrune/tables/ShopCurrencyTable.kt b/or-cache/src/main/kotlin/dev/openrune/tables/ShopCurrencyTable.kt index a87913e59..2e677b2c9 100644 --- a/or-cache/src/main/kotlin/dev/openrune/tables/ShopCurrencyTable.kt +++ b/or-cache/src/main/kotlin/dev/openrune/tables/ShopCurrencyTable.kt @@ -31,5 +31,13 @@ object ShopCurrencyTable { column(PLURAL_NAME, "stardust") columnRSCM(OBJ, "obj.star_dust") } + + // Emir's Arena reward points live in a varbit rather than an obj. + row("dbrow.shop_currency_pvp_arena_points") { + column(KEY, "currency.pvp_arena_points") + column(SINGULAR_NAME, "PvP Arena reward point") + column(PLURAL_NAME, "PvP Arena reward points") + columnRSCM(VARBIT, "varbit.pvpa_points_currency") + } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index e674e6c43..fc37ac13a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,6 +34,7 @@ include( "or-cache", "tools:osrs-mcp", "tools:wiki-dumping", + "tools:combat-anims", ) includeProjects(project(":api")) diff --git a/tools/DeleteAccount.java b/tools/DeleteAccount.java new file mode 100644 index 000000000..37beffedf --- /dev/null +++ b/tools/DeleteAccount.java @@ -0,0 +1,53 @@ +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.List; + +/** + * Lists or deletes accounts in the embedded game database. + * + * The server must be running (the embedded PostgreSQL only listens while the server is up). + * + * Usage (from the repo root): + * java -cp tools/DeleteAccount.java list + * java -cp tools/DeleteAccount.java delete + */ +public class DeleteAccount { + public static void main(String[] args) throws Exception { + if (args.length == 0) { + System.err.println("usage: list | delete "); + System.exit(2); + } + List pid = Files.readAllLines(Path.of(".data", "postgres", "postmaster.pid")); + int port = Integer.parseInt(pid.get(3).trim()); + String url = "jdbc:postgresql://localhost:" + port + "/postgres"; + try (Connection c = DriverManager.getConnection(url, "postgres", "")) { + if (args[0].equals("list")) { + String sql = + "SELECT a.id, a.account_name, a.rights, ch.id AS character_id, ch.display_name " + + "FROM accounts a LEFT JOIN account_characters ch ON ch.account_id = a.id " + + "ORDER BY a.id"; + try (PreparedStatement ps = c.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { + System.out.println("account_id | account_name | rights | character_id | display_name"); + while (rs.next()) { + System.out.println(rs.getInt(1) + " | " + rs.getString(2) + " | " + rs.getString(3) + + " | " + rs.getObject(4) + " | " + rs.getString(5)); + } + } + } else if (args[0].equals("delete") && args.length == 2) { + String sql = "DELETE FROM accounts WHERE lower(account_name) = lower(?)"; + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, args[1]); + int n = ps.executeUpdate(); + System.out.println(n == 0 ? "No account named '" + args[1] + "'" : "Deleted account '" + args[1] + "' and its character"); + } + } else { + System.err.println("usage: list | delete "); + System.exit(2); + } + } + } +} diff --git a/tools/combat-anims/build.gradle.kts b/tools/combat-anims/build.gradle.kts new file mode 100644 index 000000000..472efa2a2 --- /dev/null +++ b/tools/combat-anims/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.orCache) + implementation(projects.api.config) + implementation(libs.or2.all.cache) + implementation("com.michael-bull.kotlin-inline-logger:kotlin-inline-logger:1.0.6") +} + +tasks.register("dumpNpcCombatAnims") { + group = "application" + description = + "Derives attack, block and death animations for every attackable npc the cache leaves " + + "silent and writes them to content/other/npc-combat-anims. " + + "Example: ./gradlew :tools:combat-anims:dumpNpcCombatAnims" + classpath = sourceSets["main"].runtimeClasspath + mainClass.set("org.rsmod.tools.combatanims.NpcCombatAnimDumperKt") + // ServerCacheManager and GameValProvider resolve `.data/` against the working directory. + workingDir = rootProject.projectDir +} diff --git a/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/AnimationFamilies.kt b/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/AnimationFamilies.kt new file mode 100644 index 000000000..6804a1795 --- /dev/null +++ b/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/AnimationFamilies.kt @@ -0,0 +1,182 @@ +package org.rsmod.tools.combatanims + +/** + * Derives a monster's attack, block and death animations from the name of its ready animation. + * + * Jagex names an npc's animation set as a family: `giant_update_basic_ready`, + * `giant_update_basic_attack`, `giant_update_basic_defend`, `giant_update_basic_death`. A variant + * suffix on the ready animation carries over to the rest of the set: `zombie_update_ready_weapon` + * pairs with `zombie_update_attack_weapon`. This object knows those conventions, plus the sound + * effects of the families whose sounds the cache never attaches to the npc ([familySounds]) - it + * works on plain sequence names so it can be tested without a cache. + * + * Names are the bare gameval names (`giant_update_basic_ready`), without the `seq.` prefix. + */ +object AnimationFamilies { + data class Family( + val attack: String?, + val defend: String?, + val death: String?, + val attackSound: Int? = null, + val defendSound: Int? = null, + val deathSound: Int? = null, + ) { + val isEmpty: Boolean + get() = attack == null && defend == null && death == null + } + + /** The synth ids a family plays when it attacks, is hit and dies. */ + data class Sounds(val attack: Int, val defend: Int, val death: Int) + + /** + * Sound effects by family prefix, for the monster families whose npcs carry no `attack_sound`, + * `defend_sound` or `death_sound` params in the cache. The ids are the ones the cache does + * attach to a member of the family where one exists (the giant spider `npc.giantspider1` and + * the small `npc.spider`), and the OSRS wiki "List of sound IDs" names otherwise + * (`ghost_attack`, `ghost_hit`, `ghost_death`). + */ + private val familySounds: Map = + mapOf( + "ghost_update_tendrill" to Sounds(attack = 436, defend = 439, death = 438), + "ghost_update_normal" to Sounds(attack = 436, defend = 439, death = 438), + "spider_update" to Sounds(attack = 537, defend = 539, death = 538), + "small_spider_update" to Sounds(attack = 3604, defend = 3609, death = 3608), + ) + + /** + * Suffixes that mark a sequence as an attack when the family does not use `_attack`: + * `olaf2_undead_sword_lunge`, `barrow_dharok_slash`. + */ + private val attackWords = + listOf("_attack", "_slash", "_lunge", "_stab", "_crush", "_hack", "_punch") + + /** Ready-animation markers, longest first so `_ready_update` wins over `_ready`. */ + private val readyMarkers = + listOf("_just_ready_update", "_ready_update", "_readyanim", "_ready", "_idle", "_stand") + + fun resolve(readyAnim: String, sequences: Set): Family { + val splits = split(readyAnim) + if (splits.isEmpty()) { + return Family(null, null, null) + } + val sounds = splits.firstNotNullOfOrNull { (prefix, _) -> familySounds[prefix] } + return Family( + attack = splits.firstNotNullOfOrNull { attackFor(it, sequences) }, + defend = splits.firstNotNullOfOrNull { defendFor(it, sequences) }, + death = splits.firstNotNullOfOrNull { deathFor(it, sequences) }, + attackSound = sounds?.attack, + defendSound = sounds?.defend, + deathSound = sounds?.death, + ) + } + + /** `zombie_update_ready_weapon` -> (`zombie_update`, `_weapon`); `cow_ready` -> (`cow`, ``). */ + internal fun split(readyAnim: String): List> { + val out = mutableListOf>() + for (marker in readyMarkers) { + val index = readyAnim.indexOf(marker) + if (index <= 0) { + continue + } + val prefix = readyAnim.substring(0, index) + val variant = readyAnim.substring(index + marker.length) + out += prefix to variant + // A variant like `_sword` may not exist for every member of the set; try without it. + if (variant.isNotEmpty()) { + out += prefix to "" + } + // The variant can also sit before the marker: `ork_update_weapon_ready` pairs with + // `ork_update_defend`, and `mummy_update_soldier_ready` with `mummy_update_death`. + val infix = prefix.lastIndexOf('_') + if (infix > 0 && variant.isEmpty()) { + out += prefix.substring(0, infix) to prefix.substring(infix) + out += prefix.substring(0, infix) to "" + } + } + return out.distinct() + } + + private fun attackFor(split: Pair, sequences: Set): String? { + val (prefix, variant) = split + val exact = + listOf( + "${prefix}_attack$variant", + "$prefix${variant}_attack", + "${prefix}_attack", + "${prefix}_unarmed_attack", + "${prefix}_attack_unarmed", + "${prefix}_attack_normal", + "${prefix}_attack_melee", + "${prefix}_melee$variant", + "${prefix}_melee", + "${prefix}_slash$variant", + "${prefix}_slash", + "${prefix}_crush", + "${prefix}_stab", + "${prefix}_lunge", + "${prefix}_hack", + "${prefix}_punch", + ) + exact.firstOrNull { it in sequences }?.let { + return it + } + // No exact member. If the family has attack animations, pick the plain melee one when + // that is unambiguous; a family with several equally plausible attacks stays unresolved + // so a wrong animation is never guessed. + val candidates = + sequences.filter { seq -> + seq.startsWith("${prefix}_") && + (seq.startsWith("${prefix}_attack") || attackWords.any { seq.endsWith(it) }) && + !seq.endsWith("_transparent") && + !seq.contains("_spotanim") && + !seq.contains("_projanim") && + !seq.contains("_special") && + !seq.contains("_ranged") && + !seq.contains("_magic") && + !seq.contains("_cast") + } + if (candidates.size == 1) { + return candidates.single() + } + val preferred = candidates.filter { it.contains("weapon") || it.contains("unarmed") } + return preferred.singleOrNull() + } + + private fun defendFor(split: Pair, sequences: Set): String? { + val (prefix, variant) = split + val exact = + listOf( + "${prefix}_defend$variant", + "$prefix${variant}_defend", + "${prefix}_block$variant", + "$prefix${variant}_block", + "${prefix}_parry$variant", + "$prefix${variant}_parry", + "${prefix}_def$variant", + "${prefix}_defend", + "${prefix}_block", + "${prefix}_parry", + "${prefix}_def", + "${prefix}_defence", + "${prefix}_sword_def", + "${prefix}_sword_defend", + "${prefix}_sword_block", + ) + return exact.firstOrNull { it in sequences } + } + + private fun deathFor(split: Pair, sequences: Set): String? { + val (prefix, variant) = split + val exact = + listOf( + "${prefix}_death$variant", + "$prefix${variant}_death", + "${prefix}_die$variant", + "${prefix}_death", + "${prefix}_die", + "${prefix}_dead", + "${prefix}_dies", + ) + return exact.firstOrNull { it in sequences } + } +} diff --git a/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimDumper.kt b/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimDumper.kt new file mode 100644 index 000000000..8891043e2 --- /dev/null +++ b/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimDumper.kt @@ -0,0 +1,191 @@ +package org.rsmod.tools.combatanims + +import com.github.michaelbull.logging.InlineLogger +import dev.openrune.OsrsCacheProvider +import dev.openrune.ServerCacheManager +import dev.openrune.definition.type.ItemType +import dev.openrune.definition.type.NpcType +import dev.openrune.filesystem.Cache +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ItemServerType +import dev.openrune.util.WeaponCategory +import dev.openrune.util.Wearpos +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import org.rsmod.api.config.refs.params + +/** + * Writes `npc-combat-anims.toml` for the `npc-combat-anims` plugin. + * + * Reads the server cache for npc and item data and the client cache for the models each npc and + * item is drawn with. A humanoid npc holding the same wear model as a weapon item is holding that + * weapon, so it gets the weapon's player animations; every other attackable npc gets its + * animation family from its ready animation. The decisions themselves live in + * [NpcCombatAnimResolver] and [AnimationFamilies]; this file only gathers the facts and writes + * the result. + * + * Run from the repository root (the Gradle task sets the working directory): + * `./gradlew :tools:combat-anims:dumpNpcCombatAnims`. Options: `--out=` to write elsewhere. + */ +fun main(args: Array) { + val root = Paths.get(System.getProperty("user.dir")).toAbsolutePath().normalize() + val out = + args.firstOrNull { it.startsWith("--out=") }?.substringAfter("=")?.let(root::resolve) + ?: root.resolve(NpcCombatAnimDumper.DEFAULT_OUTPUT) + NpcCombatAnimDumper(root).dump(out) +} + +class NpcCombatAnimDumper(private val root: Path) { + fun dump(out: Path) { + val revision = readRevision(root.resolve("game.yml")) + logger.info { "Loading server cache (revision $revision)..." } + ServerCacheManager.init(revision) + + logger.info { "Loading client npc and item models..." } + val live = Cache.load(root.resolve(".data/cache/LIVE")) + val clientItems = HashMap() + val clientNpcs = HashMap() + OsrsCacheProvider.ItemDecoder(revision).load(live, clientItems) + OsrsCacheProvider.NPCDecoder(revision).load(live, clientNpcs) + + val sequences = sequenceNames() + val weaponsByModel = indexWeapons(clientItems) + val shieldsByModel = indexShields(clientItems) + logger.info { + "Indexed ${weaponsByModel.size} weapon and ${shieldsByModel.size} shield wear models, " + + "${sequences.size} sequences." + } + + val results = mutableListOf() + val counts = sortedMapOf() + for ((id, client) in clientNpcs.toSortedMap()) { + val server = ServerCacheManager.getNpc(id) ?: continue + val rscm = rscmOrNull(RSCMType.NPC, id) ?: continue + val facts = + NpcFacts( + rscm = rscm, + name = client.name, + readyAnim = bareName(RSCMType.SEQ, client.standAnim), + attack = client.attack, + ranged = client.ranged, + magic = client.magic, + attackable = client.actions.getOpOrNull(1) == "Attack", + hasAttackAnim = server.paramMap?.contains(params.attack_anim) == true, + ) + val models = client.models.orEmpty() + val weapon = + models.firstNotNullOfOrNull(weaponsByModel::get) + ?: models.firstNotNullOfOrNull(NpcCombatAnimResolver.npcOnlyWeapons::get) + val shield = models.firstNotNullOfOrNull(shieldsByModel::get) + val resolved = NpcCombatAnimResolver.resolve(facts, weapon, shield, sequences) ?: continue + results += resolved + counts.merge(resolved.source.substringBefore(':'), 1, Int::plus) + } + + Files.createDirectories(out.parent) + Files.writeString(out, NpcCombatAnimToml.render(results)) + logger.info { "Wrote ${results.size} npcs to $out" } + for ((source, count) in counts) { + logger.info { " $source: $count" } + } + } + + /** Weapon wear model -> the weapon's combat facts. The lowest item id claims a shared model. */ + private fun indexWeapons(clientItems: Map): Map { + val stanceAttackAnim = "param.attack_anim_stance1".asRSCM(RSCMType.PARAM) + val stanceAttackSound = "param.attack_sound_stance1".asRSCM(RSCMType.PARAM) + val lungeAttackAnim = "param.attack_anim_stance3".asRSCM(RSCMType.PARAM) + val lungeAttackSound = "param.attack_sound_stance3".asRSCM(RSCMType.PARAM) + val index = HashMap() + for ((id, server) in ServerCacheManager.getItems().toSortedMap()) { + if (server.wearpos1 != Wearpos.RightHand.slot) { + continue + } + if (server.weaponCategory == WeaponCategory.Unarmed) { + continue + } + val attackAnim = paramName(server, stanceAttackAnim, RSCMType.SEQ) ?: continue + val client = clientItems[id] ?: continue + val facts = + WeaponFacts( + rscm = rscmOrNull(RSCMType.OBJ, id) ?: continue, + category = server.weaponCategory, + attackAnim = attackAnim, + attackSound = paramInt(server, stanceAttackSound), + defendAnim = paramName(server, params.defend_anim.id, RSCMType.SEQ), + projTravel = paramName(server, params.proj_travel.id, RSCMType.SPOTANIM), + projType = paramName(server, params.proj_type.id, RSCMType.PROJANIM), + attackRange = paramInt(server, params.attackrange.id), + lungeAnim = paramName(server, lungeAttackAnim, RSCMType.SEQ), + lungeSound = paramInt(server, lungeAttackSound), + ) + for (model in wearModels(client)) { + index.putIfAbsent(model, facts) + } + } + return index + } + + private fun indexShields(clientItems: Map): Map { + val index = HashMap() + for ((id, server) in ServerCacheManager.getItems().toSortedMap()) { + if (server.wearpos1 != Wearpos.LeftHand.slot) { + continue + } + val client = clientItems[id] ?: continue + val rscm = rscmOrNull(RSCMType.OBJ, id) ?: continue + val facts = ShieldFacts(rscm, server.name) + for (model in wearModels(client)) { + index.putIfAbsent(model, facts) + } + } + return index + } + + private fun wearModels(item: ItemType): List = + listOf( + item.maleModel0, + item.maleModel1, + item.maleModel2, + item.femaleModel0, + item.femaleModel1, + ) + .filter { it > 0 } + + private fun sequenceNames(): Set = + ServerCacheManager.getAnims().keys.mapNotNullTo(HashSet()) { bareName(RSCMType.SEQ, it) } + + private fun paramInt(item: ItemServerType, param: Int): Int? = + item.paramMap?.primitiveMap?.get(param) as? Int + + private fun paramName(item: ItemServerType, param: Int, type: RSCMType): String? = + paramInt(item, param)?.let { bareName(type, it) } + + /** `seq.human_ready` -> `human_ready`; `null` for an unnamed or unset id. */ + private fun bareName(type: RSCMType, id: Int): String? = + rscmOrNull(type, id)?.substringAfter('.') + + private fun rscmOrNull(type: RSCMType, id: Int): String? { + if (id < 0) { + return null + } + return runCatching { RSCM.getReverseMapping(type, id) }.getOrNull() + } + + private fun readRevision(gameYml: Path): Int { + val line = + Files.readAllLines(gameYml).firstOrNull { it.trimStart().startsWith("revision:") } + ?: error("No revision line in $gameYml") + return line.substringAfter("revision:").trim().substringBefore('.').toInt() + } + + companion object { + const val DEFAULT_OUTPUT: String = + "content/other/npc-combat-anims/src/main/resources/npc-combat-anims.toml" + + private val logger = InlineLogger() + } +} diff --git a/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimResolver.kt b/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimResolver.kt new file mode 100644 index 000000000..dbbc76eb6 --- /dev/null +++ b/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimResolver.kt @@ -0,0 +1,436 @@ +package org.rsmod.tools.combatanims + +import dev.openrune.util.WeaponCategory + +/** What the resolver needs to know about an npc. All animation names are bare gameval names. */ +data class NpcFacts( + val rscm: String, + val name: String, + val readyAnim: String?, + val attack: Int, + val ranged: Int, + val magic: Int, + val attackable: Boolean, + /** The npc already declares `param.attack_anim`; the cache data wins and it is left alone. */ + val hasAttackAnim: Boolean, +) + +/** The weapon an npc is holding, identified by its wear model, with the weapon's own combat data. */ +data class WeaponFacts( + val rscm: String, + val category: WeaponCategory, + val attackAnim: String?, + val attackSound: Int?, + val defendAnim: String?, + val projTravel: String?, + val projType: String?, + val attackRange: Int?, + /** The weapon's third-stance attack (`attack_anim_stance3`): the spear lunge, the axe smash. */ + val lungeAnim: String? = null, + val lungeSound: Int? = null, +) + +/** The shield an npc is holding, identified by its wear model. */ +data class ShieldFacts(val rscm: String, val name: String) + +/** The resolved combat animation set for one npc. `null` fields are left as the cache has them. */ +data class NpcCombatAnims( + val npc: String, + val source: String, + val attackAnim: String? = null, + val attackType: String? = null, + val attackSound: Int? = null, + val defendAnim: String? = null, + val defendSound: Int? = null, + val deathAnim: String? = null, + val deathSound: Int? = null, + val projTravel: String? = null, + val projType: String? = null, + val attackRange: Int? = null, +) { + val isEmpty: Boolean + get() = + attackAnim == null && + attackType == null && + defendAnim == null && + deathAnim == null && + projTravel == null +} + +/** + * Decides which combat animations an npc should play, from what it is and what it holds. + * + * Two kinds of npc are handled: + * - **Humanoids** share the player skeleton, so they use the player animations of the weapon + * they hold: the weapon's own `attack_anim_stance1`, `defend_anim`, sound and projectile. A + * shield overrides the block animation, as it does for players. Casters - staff or bare-handed + * npcs whose magic level leads, or whose name says wizard, druid, necromancer and so on - cast + * a strike spell instead of hitting with the staff. A humanoid whose weapon is not an item at + * all (the barbarians' giant axes, great hammers and two-handed swords, the ice warriors' + * swords) is recognised by its ready stance instead - see [stanceWeapons] - so it swings + * rather than punches. + * - **Monsters** get their animation family from the name of their ready animation, via + * [AnimationFamilies]. + * + * The resolver is pure: it takes facts and returns names, so it is unit tested without a cache. + */ +object NpcCombatAnimResolver { + private const val SHIELD_BLOCK = "human_shield_defence" + private const val DEFENDER_BLOCK = "warguild_parry_defend" + private const val CAST_UNARMED = "human_caststrike" + private const val CAST_STAFF = "human_caststrike_staff" + + private const val TYPE_STAB = "category.attacktype_stab" + private const val TYPE_SLASH = "category.attacktype_slash" + private const val TYPE_CRUSH = "category.attacktype_crush" + private const val TYPE_RANGED = "category.attacktype_standard" + private const val TYPE_MAGIC = "category.attacktype_magic" + + private const val DEFAULT_RANGED_RANGE = 7 + private const val DEFAULT_MAGIC_RANGE = 7 + + /** The player sounds for the weapon kinds the stance fallbacks stand in for. */ + private const val TWO_HANDED_SOUND = 2503 + private const val TWO_HANDED_CHOP_SOUND = 2502 + private const val AXE_SOUND = 2498 + private const val STAFF_SOUND = 2555 + + /** Weapon categories whose npcs use the third-stance (lunge) attack rather than the first. */ + private val lungeCategories = setOf(WeaponCategory.Spear, WeaponCategory.Polearm) + + /** + * The spear spike; any weapon that spikes lunges instead. Halberds spike but have no lunge + * stance of their own, so they borrow the spear's. + */ + private const val SPEAR_SPIKE = "human_spear_spike" + private const val SPEAR_LUNGE = "human_spear_lunge" + private const val SPEAR_LUNGE_SOUND = 2555 + + /** Axe npcs chop rather than hack. */ + private const val AXE_HACK = "human_axe_hack" + private const val AXE_CHOP = "human_axe_chop" + + /** Ready animations that are humanoid but do not start with `human_`. */ + private val humanoidReadyAnims = + setOf("barrow_dharok_ready", "wanted_ranger_ready", "walk_walkingstick") + + /** + * Player-skeleton weapons that exist only as npc models, so no item wear model matches them. + * These are the weapons the barbarian villagers, Gunthor and the skeleton heavies are drawn + * with: giant axes, great hammers and two-handed swords, all swung with the two-handed sword + * animations. + */ + val npcOnlyWeapons: Map = + listOf(11789, 11790, 11792, 11793).associateWith { model -> + twoHanded("model:$model") + } + + /** + * What a humanoid with no recognisable weapon is holding, going by the stance it stands in. + * `human_dh_weapon_ready` is the two-handed stance, `human_transready` the ice and earth + * warriors' sword stance, and `human_staffready` a staff (used only when the npc is not a + * caster, who would cast instead). + */ + private val stanceWeapons: Map = + mapOf( + "human_dh_weapon_ready" to twoHanded("stance:human_dh_weapon_ready"), + "human_transready" to + WeaponFacts( + rscm = "stance:human_transready", + category = WeaponCategory.Axe, + attackAnim = "human_trans_axe_chop", + attackSound = AXE_SOUND, + defendAnim = "human_trans_axe_def", + projTravel = null, + projType = null, + attackRange = null, + ), + "human_staffready" to + WeaponFacts( + rscm = "stance:human_staffready", + category = WeaponCategory.Staff, + attackAnim = "human_staff_pound", + attackSound = STAFF_SOUND, + defendAnim = "human_staff_block", + projTravel = null, + projType = null, + attackRange = null, + ), + "barrow_dharok_ready" to + WeaponFacts( + rscm = "stance:barrow_dharok_ready", + category = WeaponCategory.Axe, + attackAnim = "barrow_dharok_slash", + attackSound = TWO_HANDED_SOUND, + defendAnim = null, + projTravel = null, + projType = null, + attackRange = null, + ), + ) + + /** Giant axes, great hammers and two-handed swords all chop with the two-handed chop. */ + private fun twoHanded(rscm: String): WeaponFacts = + WeaponFacts( + rscm = rscm, + category = WeaponCategory.TwoHandedSword, + attackAnim = "human_dhsword_chop", + attackSound = TWO_HANDED_CHOP_SOUND, + defendAnim = "human_dhsword_block", + projTravel = null, + projType = null, + attackRange = null, + ) + + private val casterNames = + Regex( + "wizard|mage|magus|druid|necromancer|sorcer|warlock|witch|shaman|mystic|priest|cultist|seer" + ) + + private val rangedNames = Regex("archer|ranger|bowman|marksman") + + private val staffCategories = + setOf(WeaponCategory.Staff, WeaponCategory.BladedStaff, WeaponCategory.PoweredStaff) + + private val rangedCategories = + setOf( + WeaponCategory.Bow, + WeaponCategory.Crossbow, + WeaponCategory.Thrown, + WeaponCategory.Chinchompas, + WeaponCategory.Gun, + ) + + private val attackTypes: Map = + mapOf( + WeaponCategory.StabSword to TYPE_STAB, + WeaponCategory.Spear to TYPE_STAB, + WeaponCategory.Polearm to TYPE_STAB, + WeaponCategory.SlashSword to TYPE_SLASH, + WeaponCategory.Axe to TYPE_SLASH, + WeaponCategory.TwoHandedSword to TYPE_SLASH, + WeaponCategory.GodSword to TYPE_SLASH, + WeaponCategory.Claw to TYPE_SLASH, + WeaponCategory.Scythe to TYPE_SLASH, + WeaponCategory.Whip to TYPE_SLASH, + WeaponCategory.Salamander to TYPE_SLASH, + WeaponCategory.Blunt to TYPE_CRUSH, + WeaponCategory.Spiked to TYPE_CRUSH, + WeaponCategory.Pickaxe to TYPE_CRUSH, + WeaponCategory.Bludgeon to TYPE_CRUSH, + WeaponCategory.Bulwark to TYPE_CRUSH, + WeaponCategory.Banner to TYPE_CRUSH, + WeaponCategory.Staff to TYPE_CRUSH, + WeaponCategory.BladedStaff to TYPE_CRUSH, + WeaponCategory.PoweredStaff to TYPE_CRUSH, + WeaponCategory.Bow to TYPE_RANGED, + WeaponCategory.Crossbow to TYPE_RANGED, + WeaponCategory.Thrown to TYPE_RANGED, + WeaponCategory.Chinchompas to TYPE_RANGED, + WeaponCategory.Gun to TYPE_RANGED, + ) + + /** Block animation per category for weapons whose own data leaves it off (scimitars, mostly). */ + private val categoryBlocks: Map = + mapOf( + WeaponCategory.SlashSword to "human_sword_def", + WeaponCategory.StabSword to "human_sword_def", + WeaponCategory.Axe to "human_axe_block", + WeaponCategory.Blunt to "human_blunt_block", + WeaponCategory.Spiked to "human_blunt_block", + WeaponCategory.TwoHandedSword to "human_dhsword_block", + WeaponCategory.Spear to "human_spear_block", + WeaponCategory.Polearm to "human_spear_block", + WeaponCategory.Staff to "human_staff_block", + ) + + fun resolve( + npc: NpcFacts, + weapon: WeaponFacts?, + shield: ShieldFacts?, + sequences: Set, + ): NpcCombatAnims? { + if (!npc.attackable || npc.hasAttackAnim) { + return null + } + val result = + if (isHumanoid(npc, weapon, shield)) { + resolveHumanoid(npc, weapon ?: stanceWeapon(npc, sequences), shield, sequences) + } else { + resolveMonster(npc, sequences) + } + return result?.takeUnless { it.isEmpty } + } + + /** + * The weapon a humanoid with no item weapon is holding, judged by its stance. Casters keep + * their hands free so they cast instead; everyone else in a weapon stance swings it. + */ + private fun stanceWeapon(npc: NpcFacts, sequences: Set): WeaponFacts? { + val ready = npc.readyAnim ?: return null + val weapon = stanceWeapons[ready] ?: return null + if (isCaster(npc, null)) { + return null + } + if (weapon.attackAnim != null && weapon.attackAnim !in sequences) { + return null + } + return weapon + } + + fun isHumanoid(npc: NpcFacts, weapon: WeaponFacts?, shield: ShieldFacts?): Boolean { + val ready = npc.readyAnim + return weapon != null || + shield != null || + (ready != null && (ready.startsWith("human_") || ready in humanoidReadyAnims)) + } + + private fun resolveHumanoid( + npc: NpcFacts, + weapon: WeaponFacts?, + shield: ShieldFacts?, + sequences: Set, + ): NpcCombatAnims? { + val shieldBlock = shield?.let(::shieldBlock) + return when { + isCaster(npc, weapon) -> caster(npc, weapon, shieldBlock, sequences) + weapon != null && weapon.category in rangedCategories -> ranged(npc, weapon, shieldBlock) + weapon != null -> melee(npc, weapon, shieldBlock) + isUnarmedRanger(npc) -> bareBow(npc, shieldBlock) + shieldBlock != null -> + NpcCombatAnims(npc.rscm, "shield:${shield?.rscm}", defendAnim = shieldBlock) + else -> null + } + } + + private fun isCaster(npc: NpcFacts, weapon: WeaponFacts?): Boolean { + val handsFree = weapon == null || weapon.category in staffCategories + if (!handsFree) { + return false + } + // Strictly ahead: a villager with every stat at 1 is not a caster. + val magicLeads = npc.magic > npc.attack && npc.magic > npc.ranged + // The gameval name often says what the display name does not: `colosseum_warbander_mage` + // is shown as "Fremennik warband seer". + return magicLeads || + casterNames.containsMatchIn(npc.name.lowercase()) || + casterNames.containsMatchIn(npc.rscm.lowercase()) + } + + private fun isUnarmedRanger(npc: NpcFacts): Boolean = + npc.ranged > npc.attack && rangedNames.containsMatchIn(npc.name.lowercase()) + + private fun caster( + npc: NpcFacts, + weapon: WeaponFacts?, + shieldBlock: String?, + sequences: Set, + ): NpcCombatAnims { + val staff = weapon != null + val cast = if (staff && CAST_STAFF in sequences) CAST_STAFF else CAST_UNARMED + return NpcCombatAnims( + npc = npc.rscm, + source = if (staff) "caster:${weapon?.rscm}" else "caster", + attackAnim = cast, + attackType = TYPE_MAGIC, + defendAnim = shieldBlock ?: weapon?.let(::weaponBlock), + projTravel = strikeSpell(npc.name), + projType = "magic_spell", + attackRange = DEFAULT_MAGIC_RANGE, + ) + } + + /** The strike spell a caster throws: its element when the name gives one, wind otherwise. */ + private fun strikeSpell(name: String): String { + val lowercase = name.lowercase() + val element = + when { + "fire" in lowercase || "flame" in lowercase -> "fire" + "water" in lowercase || "ice" in lowercase -> "water" + "earth" in lowercase || "rock" in lowercase -> "earth" + else -> "wind" + } + return "${element}strike_travel" + } + + private fun ranged(npc: NpcFacts, weapon: WeaponFacts, shieldBlock: String?): NpcCombatAnims { + val (travel, type) = defaultProjectile(weapon.category) + return NpcCombatAnims( + npc = npc.rscm, + source = "weapon:${weapon.rscm}", + attackAnim = weapon.attackAnim, + attackType = TYPE_RANGED, + attackSound = weapon.attackSound, + defendAnim = shieldBlock, + projTravel = weapon.projTravel ?: travel, + projType = weapon.projType ?: type, + attackRange = weapon.attackRange ?: DEFAULT_RANGED_RANGE, + ) + } + + private fun bareBow(npc: NpcFacts, shieldBlock: String?): NpcCombatAnims = + NpcCombatAnims( + npc = npc.rscm, + source = "ranger", + attackAnim = "human_bow", + attackType = TYPE_RANGED, + defendAnim = shieldBlock, + projTravel = "iron_arrow_travel", + projType = "arrow", + attackRange = DEFAULT_RANGED_RANGE, + ) + + private fun defaultProjectile(category: WeaponCategory): Pair = + when (category) { + WeaponCategory.Bow -> "iron_arrow_travel" to "arrow" + WeaponCategory.Crossbow -> "crossbowbolt_travel" to "bolt" + WeaponCategory.Thrown -> null to "thrown" + else -> null to null + } + + private fun melee(npc: NpcFacts, weapon: WeaponFacts, shieldBlock: String?): NpcCombatAnims { + // Spear and halberd npcs lunge rather than spike, and axe npcs chop rather than hack, + // each with that stance's own sound so the audio matches the swing. + val lunge = weapon.category in lungeCategories || weapon.attackAnim == SPEAR_SPIKE + val chop = weapon.category == WeaponCategory.Axe && weapon.attackAnim == AXE_HACK + val attackAnim = + when { + lunge -> weapon.lungeAnim ?: SPEAR_LUNGE + chop -> AXE_CHOP + else -> weapon.attackAnim + } + val attackSound = if (lunge) weapon.lungeSound ?: SPEAR_LUNGE_SOUND else weapon.attackSound + return NpcCombatAnims( + npc = npc.rscm, + source = "weapon:${weapon.rscm}", + attackAnim = attackAnim, + attackType = attackTypes[weapon.category] ?: TYPE_CRUSH, + attackSound = attackSound, + defendAnim = shieldBlock ?: weaponBlock(weapon), + ) + } + + private fun weaponBlock(weapon: WeaponFacts): String? = + weapon.defendAnim ?: categoryBlocks[weapon.category] + + private fun shieldBlock(shield: ShieldFacts): String = + if ("defender" in shield.name.lowercase()) DEFENDER_BLOCK else SHIELD_BLOCK + + private fun resolveMonster(npc: NpcFacts, sequences: Set): NpcCombatAnims? { + val ready = npc.readyAnim ?: return null + val family = AnimationFamilies.resolve(ready, sequences) + if (family.isEmpty) { + return null + } + return NpcCombatAnims( + npc = npc.rscm, + source = "family:$ready", + attackAnim = family.attack, + attackSound = family.attackSound, + defendAnim = family.defend, + defendSound = family.defendSound, + deathAnim = family.death, + deathSound = family.deathSound, + ) + } +} diff --git a/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimToml.kt b/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimToml.kt new file mode 100644 index 000000000..3c6609145 --- /dev/null +++ b/tools/combat-anims/src/main/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimToml.kt @@ -0,0 +1,48 @@ +package org.rsmod.tools.combatanims + +/** + * Renders resolved npc animation sets as the TOML the `npc-combat-anims` plugin reads: one inline + * table per npc under a single `npc` array, so a diff shows one changed npc per line. + */ +object NpcCombatAnimToml { + private const val HEADER = + """# Combat animations for npcs the cache leaves silent - generated, do not edit by hand. +# +# Regenerate with: ./gradlew :tools:combat-anims:dumpNpcCombatAnims +# Hand corrections: npc-combat-anims-overrides.toml (same format; those always win). +# +# Each entry names the npc, where its animations came from (a held weapon, a spell caster, or the +# animation family of its ready animation) and the values the plugin sets at boot. Only fields +# present are applied, and only where the npc's own data does not already declare them. +""" + + fun render(entries: List): String = + buildString { + append(HEADER) + append('\n') + append("npc = [\n") + for (entry in entries.sortedBy { it.npc }) { + append(" ") + append(renderEntry(entry)) + append(",\n") + } + append("]\n") + } + + fun renderEntry(entry: NpcCombatAnims): String { + val fields = mutableListOf() + fields += "id = \"${entry.npc}\"" + fields += "source = \"${entry.source}\"" + entry.attackAnim?.let { fields += "attack_anim = \"seq.$it\"" } + entry.attackType?.let { fields += "attack_type = \"$it\"" } + entry.attackSound?.let { fields += "attack_sound = $it" } + entry.defendAnim?.let { fields += "defend_anim = \"seq.$it\"" } + entry.defendSound?.let { fields += "defend_sound = $it" } + entry.deathAnim?.let { fields += "death_anim = \"seq.$it\"" } + entry.deathSound?.let { fields += "death_sound = $it" } + entry.projTravel?.let { fields += "proj_travel = \"spotanim.$it\"" } + entry.projType?.let { fields += "proj_type = \"projanim.$it\"" } + entry.attackRange?.let { fields += "attack_range = $it" } + return fields.joinToString(", ", prefix = "{ ", postfix = " }") + } +} diff --git a/tools/combat-anims/src/test/kotlin/org/rsmod/tools/combatanims/AnimationFamiliesTest.kt b/tools/combat-anims/src/test/kotlin/org/rsmod/tools/combatanims/AnimationFamiliesTest.kt new file mode 100644 index 000000000..279912f55 --- /dev/null +++ b/tools/combat-anims/src/test/kotlin/org/rsmod/tools/combatanims/AnimationFamiliesTest.kt @@ -0,0 +1,193 @@ +package org.rsmod.tools.combatanims + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class AnimationFamiliesTest { + private val sequences = + setOf( + "giant_update_basic_ready", + "giant_update_basic_attack", + "giant_update_basic_defend", + "giant_update_basic_death", + "cow_just_ready_update", + "cow_attack", + "cow_block", + "cow_death", + "zombie_update_ready_weapon", + "zombie_update_attack_weapon", + "zombie_update_defend_weapon", + "zombie_update_death_weapon", + "zombie_update_ready_normal", + "zombie_update_attack_normal", + "slice_surface_goblin_squat_ready", + "slice_surface_goblin_squat_unarmed_attack", + "slice_surface_goblin_squat_ready_spear", + "slice_surface_goblin_squat_attack_spear", + "scorpion_update_ready", + "scorpion_update_attack_tail", + "scorpion_update_defend", + "scorpion_update_death", + "skeleton_update_ready", + "skeleton_update_attack_weapon", + "skeleton_update_attack_weapon_transparent", + "skeleton_update_attack_sword", + "skeleton_update_defend", + "skeleton_update_death", + "godwars_armadyl_ready", + "godwars_armadyl_cannon_attack", + "godwars_armadyl_spear_attack", + "godwars_armadyl_sword_attack", + "godwars_armadyl_defend", + "godwars_armadyl_death", + "troll_ready_sword", + "troll_attack", + "troll_block", + "troll_death", + "thzaar_parry", + "thzaar_death", + "thzaar_ready", + "thzaar_unarmed_attack", + "thzaar_armed_attack", + "thzaar_magic_attack", + "maiden_idle", + "maiden_attack", + "ork_update_weapon_ready", + "ork_update_weapon_walk", + "ork_update_double_grip_attack", + "ork_update_defend", + "ork_update_death", + "olaf2_undead_ready", + "olaf2_undead_walk", + "olaf2_undead_sword_lunge", + "olaf2_undead_sword_def", + "mummy_update_soldier_ready", + "mummy_update_civillian_attack", + "mummy_update_sword_attack", + "mummy_update_defend", + "mummy_update_death", + ) + + @Test + fun `a stance word before the ready marker still finds the family`() { + val ork = AnimationFamilies.resolve("ork_update_weapon_ready", sequences) + assertEquals("ork_update_double_grip_attack", ork.attack) + assertEquals("ork_update_defend", ork.defend) + assertEquals("ork_update_death", ork.death) + } + + @Test + fun `a lunge or slash counts as the attack when the family has no attack member`() { + val undead = AnimationFamilies.resolve("olaf2_undead_ready", sequences) + assertEquals("olaf2_undead_sword_lunge", undead.attack) + assertEquals("olaf2_undead_sword_def", undead.defend) + } + + @Test + fun `two equally plausible named attacks stay unresolved`() { + val mummy = AnimationFamilies.resolve("mummy_update_soldier_ready", sequences) + assertNull(mummy.attack) + assertEquals("mummy_update_defend", mummy.defend) + assertEquals("mummy_update_death", mummy.death) + } + + @Test + fun `plain family resolves every member`() { + val family = AnimationFamilies.resolve("giant_update_basic_ready", sequences) + assertEquals("giant_update_basic_attack", family.attack) + assertEquals("giant_update_basic_defend", family.defend) + assertEquals("giant_update_basic_death", family.death) + } + + @Test + fun `families with a known voice carry their sounds and the large variant shares them`() { + val spiders = + sequences + + setOf( + "spider_update_ready", + "spider_update_attack", + "spider_update_defend", + "spider_update_death", + "spider_update_ready_large", + "spider_update_attack_large", + ) + val spider = AnimationFamilies.resolve("spider_update_ready", spiders) + assertEquals(537, spider.attackSound) + assertEquals(539, spider.defendSound) + assertEquals(538, spider.deathSound) + val large = AnimationFamilies.resolve("spider_update_ready_large", spiders) + assertEquals("spider_update_attack_large", large.attack) + assertEquals(537, large.attackSound) + // Unknown families stay silent. + val giant = AnimationFamilies.resolve("giant_update_basic_ready", sequences) + assertNull(giant.attackSound) + assertNull(giant.defendSound) + assertNull(giant.deathSound) + } + + @Test + fun `long ready markers are stripped whole`() { + val family = AnimationFamilies.resolve("cow_just_ready_update", sequences) + assertEquals("cow_attack", family.attack) + assertEquals("cow_block", family.defend) + assertEquals("cow_death", family.death) + } + + @Test + fun `a ready variant carries over to the other members`() { + val family = AnimationFamilies.resolve("zombie_update_ready_weapon", sequences) + assertEquals("zombie_update_attack_weapon", family.attack) + assertEquals("zombie_update_defend_weapon", family.defend) + assertEquals("zombie_update_death_weapon", family.death) + } + + @Test + fun `the variant can sit before the member name`() { + val unarmed = AnimationFamilies.resolve("slice_surface_goblin_squat_ready", sequences) + assertEquals("slice_surface_goblin_squat_unarmed_attack", unarmed.attack) + val spear = AnimationFamilies.resolve("slice_surface_goblin_squat_ready_spear", sequences) + assertEquals("slice_surface_goblin_squat_attack_spear", spear.attack) + } + + @Test + fun `a family with a single attack uses it whatever it is called`() { + assertEquals( + "scorpion_update_attack_tail", + AnimationFamilies.resolve("scorpion_update_ready", sequences).attack, + ) + assertEquals("maiden_attack", AnimationFamilies.resolve("maiden_idle", sequences).attack) + } + + @Test + fun `several attacks prefer the generic weapon one and otherwise stay unresolved`() { + assertEquals( + "skeleton_update_attack_weapon", + AnimationFamilies.resolve("skeleton_update_ready", sequences).attack, + ) + val armadyl = AnimationFamilies.resolve("godwars_armadyl_ready", sequences) + assertNull(armadyl.attack) + assertEquals("godwars_armadyl_defend", armadyl.defend) + } + + @Test + fun `a variant with no member of its own falls back to the base family`() { + val family = AnimationFamilies.resolve("troll_ready_sword", sequences) + assertEquals("troll_attack", family.attack) + assertEquals("troll_block", family.defend) + } + + @Test + fun `unarmed attack beats an armed sibling`() { + assertEquals("thzaar_unarmed_attack", AnimationFamilies.resolve("thzaar_ready", sequences).attack) + assertEquals("thzaar_parry", AnimationFamilies.resolve("thzaar_ready", sequences).defend) + } + + @Test + fun `an unknown family is empty`() { + val family = AnimationFamilies.resolve("human_ready", sequences) + assertTrue(family.isEmpty) + assertTrue(AnimationFamilies.resolve("no_marker_here", sequences).isEmpty) + } +} diff --git a/tools/combat-anims/src/test/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimResolverTest.kt b/tools/combat-anims/src/test/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimResolverTest.kt new file mode 100644 index 000000000..7f7c26245 --- /dev/null +++ b/tools/combat-anims/src/test/kotlin/org/rsmod/tools/combatanims/NpcCombatAnimResolverTest.kt @@ -0,0 +1,332 @@ +package org.rsmod.tools.combatanims + +import dev.openrune.util.WeaponCategory +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test + +class NpcCombatAnimResolverTest { + private val sequences = + setOf( + "human_ready", + "human_caststrike", + "human_caststrike_staff", + "giant_update_basic_ready", + "giant_update_basic_attack", + "giant_update_basic_defend", + "giant_update_basic_death", + "human_dh_weapon_ready", + "human_dhsword_chop", + "human_dhsword_block", + "human_transready", + "human_trans_axe_chop", + "human_trans_axe_def", + "human_staffready", + "human_staff_pound", + "human_staff_block", + ) + + private val sword = + WeaponFacts( + rscm = "obj.iron_sword", + category = WeaponCategory.StabSword, + attackAnim = "human_sword_stab", + attackSound = 2549, + defendAnim = "human_sword_def", + projTravel = null, + projType = null, + attackRange = null, + ) + + private val battleaxe = + sword.copy( + rscm = "obj.iron_battleaxe", + category = WeaponCategory.Axe, + attackAnim = "human_axe_hack", + attackSound = 2498, + defendAnim = "human_axe_block", + ) + + private val mace = + sword.copy( + rscm = "obj.iron_mace", + category = WeaponCategory.Spiked, + attackAnim = "human_blunt_pound", + attackSound = 2508, + defendAnim = "human_blunt_block", + ) + + private val scimitar = + sword.copy( + rscm = "obj.iron_scimitar", + category = WeaponCategory.SlashSword, + attackAnim = "human_sword_slash", + attackSound = 2500, + defendAnim = null, + ) + + private val shortbow = + sword.copy( + rscm = "obj.shortbow", + category = WeaponCategory.Bow, + attackAnim = "human_bow", + attackSound = 2693, + defendAnim = "human_unarmedblock", + projType = "arrow", + attackRange = 7, + ) + + private val staff = + sword.copy( + rscm = "obj.staff_of_air", + category = WeaponCategory.Staff, + attackAnim = "human_stafforb_pummel", + attackSound = 2555, + defendAnim = "human_stafforb_block", + ) + + private val spear = + sword.copy( + rscm = "obj.bronze_spear", + category = WeaponCategory.Spear, + attackAnim = "human_spear_spike", + attackSound = 2562, + defendAnim = "human_spear_block", + lungeAnim = "human_spear_lunge", + lungeSound = 2555, + ) + + private val kiteshield = ShieldFacts("obj.iron_kiteshield", "Iron kiteshield") + private val defender = ShieldFacts("obj.iron_defender", "Iron defender") + + @Test + fun `a sword guard stabs with the sword and blocks with the shield`() { + val guard = human("Guard", attack = 15) + val result = NpcCombatAnimResolver.resolve(guard, sword, kiteshield, sequences) + assertNotNull(result) + assertEquals("human_sword_stab", result!!.attackAnim) + assertEquals("category.attacktype_stab", result.attackType) + assertEquals(2549, result.attackSound) + assertEquals("human_shield_defence", result.defendAnim) + assertEquals("weapon:obj.iron_sword", result.source) + assertNull(result.projTravel) + } + + @Test + fun `battleaxe and mace use their own animations and attack types`() { + val axe = NpcCombatAnimResolver.resolve(human("Barbarian"), battleaxe, null, sequences)!! + // Axe npcs chop rather than hack; the chop is the same stance's sound. + assertEquals("human_axe_chop", axe.attackAnim) + assertEquals(2498, axe.attackSound) + assertEquals("category.attacktype_slash", axe.attackType) + assertEquals("human_axe_block", axe.defendAnim) + + val blunt = NpcCombatAnimResolver.resolve(human("Thug"), mace, null, sequences)!! + assertEquals("human_blunt_pound", blunt.attackAnim) + assertEquals("category.attacktype_crush", blunt.attackType) + assertEquals("human_blunt_block", blunt.defendAnim) + } + + @Test + fun `a scimitar without a block animation falls back to the sword block`() { + val result = NpcCombatAnimResolver.resolve(human("Pirate"), scimitar, null, sequences)!! + assertEquals("human_sword_slash", result.attackAnim) + assertEquals("human_sword_def", result.defendAnim) + } + + @Test + fun `a defender parries instead of the shield block`() { + val result = NpcCombatAnimResolver.resolve(human("Knight"), sword, defender, sequences)!! + assertEquals("warguild_parry_defend", result.defendAnim) + } + + @Test + fun `an archer fires arrows from range`() { + val archer = human("Archer", attack = 20, ranged = 40) + val result = NpcCombatAnimResolver.resolve(archer, shortbow, null, sequences)!! + assertEquals("human_bow", result.attackAnim) + assertEquals("category.attacktype_standard", result.attackType) + assertEquals("iron_arrow_travel", result.projTravel) + assertEquals("arrow", result.projType) + assertEquals(7, result.attackRange) + // The bow's bare-handed block is not worth writing; the default already is that. + assertNull(result.defendAnim) + } + + @Test + fun `a halberd npc lunges like a spear`() { + // Halberds spike but have no lunge stance of their own; they borrow the spear's. + val halberd = + spear.copy( + rscm = "obj.bronze_halberd", + category = WeaponCategory.Polearm, + lungeAnim = null, + lungeSound = null, + ) + val result = NpcCombatAnimResolver.resolve(human("Guard", attack = 20), halberd, null, sequences)!! + assertEquals("human_spear_lunge", result.attackAnim) + assertEquals(2555, result.attackSound) + } + + @Test + fun `a spear npc lunges with the lunge sound`() { + val guard = human("Guard", attack = 20) + val result = NpcCombatAnimResolver.resolve(guard, spear, null, sequences)!! + assertEquals("human_spear_lunge", result.attackAnim) + assertEquals(2555, result.attackSound) + assertEquals("category.attacktype_stab", result.attackType) + assertEquals("human_spear_block", result.defendAnim) + } + + @Test + fun `a two-handed stance with no item weapon chops with a two-handed weapon`() { + val barbarian = human("Barbarian", attack = 20).copy(readyAnim = "human_dh_weapon_ready") + val result = NpcCombatAnimResolver.resolve(barbarian, null, null, sequences)!! + assertEquals("human_dhsword_chop", result.attackAnim) + assertEquals("category.attacktype_slash", result.attackType) + assertEquals(2502, result.attackSound) + assertEquals("human_dhsword_block", result.defendAnim) + assertEquals("weapon:stance:human_dh_weapon_ready", result.source) + } + + @Test + fun `an ice warrior in the transformed stance chops with its sword`() { + val warrior = human("Ice warrior", attack = 40).copy(readyAnim = "human_transready") + val result = NpcCombatAnimResolver.resolve(warrior, null, null, sequences)!! + assertEquals("human_trans_axe_chop", result.attackAnim) + assertEquals("human_trans_axe_def", result.defendAnim) + } + + @Test + fun `a staff stance bashes unless the npc is a caster`() { + val man = human("Man", attack = 5).copy(readyAnim = "human_staffready") + val bash = NpcCombatAnimResolver.resolve(man, null, null, sequences)!! + assertEquals("human_staff_pound", bash.attackAnim) + assertEquals("category.attacktype_crush", bash.attackType) + + val wizard = human("Wizard", attack = 5, magic = 20).copy(readyAnim = "human_staffready") + val cast = NpcCombatAnimResolver.resolve(wizard, null, null, sequences)!! + assertEquals("human_caststrike", cast.attackAnim) + assertEquals("category.attacktype_magic", cast.attackType) + } + + @Test + fun `a caster is recognised by its gameval name when the display name says nothing`() { + val seer = + human("Fremennik warband seer", attack = 60, magic = 60) + .copy(rscm = "npc.colosseum_warbander_mage_male", readyAnim = "human_staffready") + val result = NpcCombatAnimResolver.resolve(seer, null, null, sequences)!! + assertEquals("human_caststrike", result.attackAnim) + assertEquals("category.attacktype_magic", result.attackType) + } + + @Test + fun `a two-handed stance is ignored when the ready animation is unknown to the cache`() { + val barbarian = human("Barbarian", attack = 20).copy(readyAnim = "human_dh_weapon_ready") + val without = sequences - "human_dhsword_chop" + assertNull(NpcCombatAnimResolver.resolve(barbarian, null, null, without)) + } + + @Test + fun `npc-only weapon models map to the two-handed chop set`() { + val weapon = NpcCombatAnimResolver.npcOnlyWeapons.getValue(11793) + assertEquals(WeaponCategory.TwoHandedSword, weapon.category) + assertEquals("human_dhsword_chop", weapon.attackAnim) + } + + @Test + fun `an unarmed ranger by name and stats still gets a bow`() { + val ranger = human("Ranger", attack = 10, ranged = 40) + val result = NpcCombatAnimResolver.resolve(ranger, null, null, sequences)!! + assertEquals("human_bow", result.attackAnim) + assertEquals("ranger", result.source) + } + + @Test + fun `a wizard casts a strike spell rather than punching`() { + val wizard = human("Wizard", attack = 8, magic = 10) + val result = NpcCombatAnimResolver.resolve(wizard, null, null, sequences)!! + assertEquals("human_caststrike", result.attackAnim) + assertEquals("category.attacktype_magic", result.attackType) + assertEquals("windstrike_travel", result.projTravel) + assertEquals("magic_spell", result.projType) + assertEquals(7, result.attackRange) + } + + @Test + fun `a staff-holding caster uses the staff cast and the staff block`() { + val druid = human("Chaos druid", attack = 8, magic = 10) + val result = NpcCombatAnimResolver.resolve(druid, staff, null, sequences)!! + assertEquals("human_caststrike_staff", result.attackAnim) + assertEquals("human_stafforb_block", result.defendAnim) + assertEquals("caster:obj.staff_of_air", result.source) + } + + @Test + fun `a caster by name keeps casting even when melee stats lead`() { + val dark = human("Dark wizard", attack = 20, magic = 15) + val result = NpcCombatAnimResolver.resolve(dark, null, null, sequences)!! + assertEquals("human_caststrike", result.attackAnim) + } + + @Test + fun `a fire named caster throws fire strike`() { + val result = NpcCombatAnimResolver.resolve(human("Fire wizard", magic = 20), null, null, sequences)!! + assertEquals("firestrike_travel", result.projTravel) + } + + @Test + fun `a staff wielder with melee stats hits with the staff`() { + val tribesman = human("Tribesman", attack = 23, magic = 1) + val result = NpcCombatAnimResolver.resolve(tribesman, staff, null, sequences)!! + assertEquals("human_stafforb_pummel", result.attackAnim) + assertEquals("category.attacktype_crush", result.attackType) + } + + @Test + fun `an unarmed humanoid without a shield is left on the defaults`() { + assertNull(NpcCombatAnimResolver.resolve(human("Man"), null, null, sequences)) + // Every stat equal is not a magic lead. + val villager = human("Al-Kharid man", attack = 1, ranged = 1, magic = 1) + assertNull(NpcCombatAnimResolver.resolve(villager, null, null, sequences)) + } + + @Test + fun `a shield alone still fixes the block animation`() { + val result = NpcCombatAnimResolver.resolve(human("Man"), null, kiteshield, sequences)!! + assertNull(result.attackAnim) + assertEquals("human_shield_defence", result.defendAnim) + } + + @Test + fun `monsters take their whole family from the ready animation`() { + val giant = human("Hill Giant", attack = 18).copy(readyAnim = "giant_update_basic_ready") + val result = NpcCombatAnimResolver.resolve(giant, null, null, sequences)!! + assertEquals("giant_update_basic_attack", result.attackAnim) + assertEquals("giant_update_basic_defend", result.defendAnim) + assertEquals("giant_update_basic_death", result.deathAnim) + assertEquals("family:giant_update_basic_ready", result.source) + assertNull(result.attackType) + } + + @Test + fun `npcs with their own attack animation or no attack option are skipped`() { + val done = human("Man").copy(hasAttackAnim = true) + assertNull(NpcCombatAnimResolver.resolve(done, sword, null, sequences)) + val peaceful = human("Banker").copy(attackable = false) + assertNull(NpcCombatAnimResolver.resolve(peaceful, sword, null, sequences)) + } + + private fun human(name: String, attack: Int = 5, ranged: Int = 1, magic: Int = 1): NpcFacts = + NpcFacts( + rscm = "npc.${name.lowercase().replace(' ', '_')}", + name = name, + readyAnim = "human_ready", + attack = attack, + ranged = ranged, + magic = magic, + attackable = true, + hasAttackAnim = false, + ) +}