From 920cffda2d79577e96733e5c18bacdefba795835 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Sat, 31 Aug 2024 06:15:00 -0500 Subject: [PATCH] KDL3: Version 2.0.0 (#3323) * initial work on procedure patch * more flexibility load default procedure for version 5 patches add args for procedure add default extension for tokens and bsdiff allow specifying additional required extensions for generation * pushing current changes to go fix tloz bug * move tokens into a separate inheritable class * forgot the commit to remove token from ProcedurePatch * further cleaning from bad commit * start on docstrings * further work on docstrings and typing * improve docstrings * fix incorrect docstring * cleanup * clean defaults and docstring * define interface that has only the bare minimum required for `Patch.create_rom_file` * change to dictionary.get * remove unnecessary if statement * update to explicitly check for procedure, restore compatible version and manual override * Update Files.py * remove struct uses * Update Rom.py * convert KDL3 to APPP * change class variables to instance variables * Update worlds/Files.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Update worlds/Files.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * move required_extensions to tuple * fix missing tuple ellipsis * fix classvar mixup * rename tokens to _tokens. use hasattr * type hint cleanup * Update Files.py * initial base for local items, need to finish * coo not clean * handle local items for real, appp cleanup * actually make bosses send their locations * fix cloudy park 4 rule, zero deathlink message * remove redundant door_shuffle bool when generic ER gets in, this whole function gets rewritten. So just clean it a little now. * properly fix deathlink messages, fix fill error * update docs * add prefill items * fix kine fill error * Update Rom.py * Update Files.py * mypy and softlock fix * Update Gifting.py * mypy phase 1 * fix rare async client bug * Update __init__.py * typing cleanup * fix stone softlock because of the way Kine's Stone works, you can't clear the stone blocks before clearing the burning blocks, so we have to bring Burning from outside * Update Rom.py * Add option groups * Rename to lowercase * finish rename * whoops broke the world * fix animal duplication bug * overhaul filler generation * add Miku flavor * Update gifting.py * fix issues related to max_hs increase * Update test_locations.py * fix boss shuffle not working if level shuffle is disabled * fix bleeding default levels * Update options.py * thought this would print seed * yay bad merges * forgot options too * yeah lets just break generation while at it * this is probably a problem * cap required heart stars * Revert "cap required heart stars" This reverts commit 759efd3e2b14ec2855082de041ac989cb9c5d500. * fix duplication removal placement, deprecated test option * forgot that we need to account for what we place * move location ids * rewrite trap handling * further stage renumber fixes * forgot one more * basic UT support * fix local heart star checks * fix pattern --------- Co-authored-by: beauxq Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> --- worlds/kdl3/Locations.py | 940 ------------------ worlds/kdl3/Rom.py | 577 ----------- worlds/kdl3/Room.py | 95 -- worlds/kdl3/__init__.py | 185 ++-- worlds/kdl3/{Aesthetics.py => aesthetics.py} | 35 +- worlds/kdl3/{Client.py => client.py} | 70 +- .../kdl3/{ClientAddrs.py => client_addrs.py} | 0 .../kdl3/{Compression.py => compression.py} | 0 worlds/kdl3/data/kdl3_basepatch.bsdiff4 | Bin 2411 -> 2646 bytes worlds/kdl3/{Gifting.py => gifting.py} | 19 +- worlds/kdl3/{Items.py => items.py} | 8 +- worlds/kdl3/locations.py | 940 ++++++++++++++++++ worlds/kdl3/{Names => names}/__init__.py | 0 .../animal_friend_spawns.py} | 11 + .../enemy_abilities.py} | 2 +- .../location_name.py} | 0 worlds/kdl3/{Options.py => options.py} | 66 +- worlds/kdl3/{Presets.py => presets.py} | 1 + worlds/kdl3/{Regions.py => regions.py} | 143 +-- worlds/kdl3/rom.py | 602 +++++++++++ worlds/kdl3/room.py | 133 +++ worlds/kdl3/{Rules.py => rules.py} | 145 +-- worlds/kdl3/{data => src}/APPauseIcons.dat | Bin worlds/kdl3/src/kdl3_basepatch.asm | 180 +++- worlds/kdl3/test/__init__.py | 2 + worlds/kdl3/test/test_goal.py | 14 +- worlds/kdl3/test/test_locations.py | 50 +- worlds/kdl3/test/test_shuffles.py | 258 +++-- 28 files changed, 2420 insertions(+), 2056 deletions(-) delete mode 100644 worlds/kdl3/Locations.py delete mode 100644 worlds/kdl3/Rom.py delete mode 100644 worlds/kdl3/Room.py rename worlds/kdl3/{Aesthetics.py => aesthetics.py} (91%) rename worlds/kdl3/{Client.py => client.py} (90%) rename worlds/kdl3/{ClientAddrs.py => client_addrs.py} (100%) rename worlds/kdl3/{Compression.py => compression.py} (100%) rename worlds/kdl3/{Gifting.py => gifting.py} (90%) rename worlds/kdl3/{Items.py => items.py} (95%) create mode 100644 worlds/kdl3/locations.py rename worlds/kdl3/{Names => names}/__init__.py (100%) rename worlds/kdl3/{Names/AnimalFriendSpawns.py => names/animal_friend_spawns.py} (95%) rename worlds/kdl3/{Names/EnemyAbilities.py => names/enemy_abilities.py} (99%) rename worlds/kdl3/{Names/LocationName.py => names/location_name.py} (100%) rename worlds/kdl3/{Options.py => options.py} (82%) rename worlds/kdl3/{Presets.py => presets.py} (98%) rename worlds/kdl3/{Regions.py => regions.py} (66%) create mode 100644 worlds/kdl3/rom.py create mode 100644 worlds/kdl3/room.py rename worlds/kdl3/{Rules.py => rules.py} (70%) rename worlds/kdl3/{data => src}/APPauseIcons.dat (100%) diff --git a/worlds/kdl3/Locations.py b/worlds/kdl3/Locations.py deleted file mode 100644 index 4d039a13..00000000 --- a/worlds/kdl3/Locations.py +++ /dev/null @@ -1,940 +0,0 @@ -import typing -from BaseClasses import Location, Region -from .Names import LocationName - -if typing.TYPE_CHECKING: - from .Room import KDL3Room - - -class KDL3Location(Location): - game: str = "Kirby's Dream Land 3" - room: typing.Optional["KDL3Room"] = None - - def __init__(self, player: int, name: str, address: typing.Optional[int], parent: typing.Union[Region, None]): - super().__init__(player, name, address, parent) - if not address: - self.show_in_spoiler = False - - -stage_locations = { - 0x770001: LocationName.grass_land_1, - 0x770002: LocationName.grass_land_2, - 0x770003: LocationName.grass_land_3, - 0x770004: LocationName.grass_land_4, - 0x770005: LocationName.grass_land_5, - 0x770006: LocationName.grass_land_6, - 0x770007: LocationName.ripple_field_1, - 0x770008: LocationName.ripple_field_2, - 0x770009: LocationName.ripple_field_3, - 0x77000A: LocationName.ripple_field_4, - 0x77000B: LocationName.ripple_field_5, - 0x77000C: LocationName.ripple_field_6, - 0x77000D: LocationName.sand_canyon_1, - 0x77000E: LocationName.sand_canyon_2, - 0x77000F: LocationName.sand_canyon_3, - 0x770010: LocationName.sand_canyon_4, - 0x770011: LocationName.sand_canyon_5, - 0x770012: LocationName.sand_canyon_6, - 0x770013: LocationName.cloudy_park_1, - 0x770014: LocationName.cloudy_park_2, - 0x770015: LocationName.cloudy_park_3, - 0x770016: LocationName.cloudy_park_4, - 0x770017: LocationName.cloudy_park_5, - 0x770018: LocationName.cloudy_park_6, - 0x770019: LocationName.iceberg_1, - 0x77001A: LocationName.iceberg_2, - 0x77001B: LocationName.iceberg_3, - 0x77001C: LocationName.iceberg_4, - 0x77001D: LocationName.iceberg_5, - 0x77001E: LocationName.iceberg_6, -} - -heart_star_locations = { - 0x770101: LocationName.grass_land_tulip, - 0x770102: LocationName.grass_land_muchi, - 0x770103: LocationName.grass_land_pitcherman, - 0x770104: LocationName.grass_land_chao, - 0x770105: LocationName.grass_land_mine, - 0x770106: LocationName.grass_land_pierre, - 0x770107: LocationName.ripple_field_kamuribana, - 0x770108: LocationName.ripple_field_bakasa, - 0x770109: LocationName.ripple_field_elieel, - 0x77010A: LocationName.ripple_field_toad, - 0x77010B: LocationName.ripple_field_mama_pitch, - 0x77010C: LocationName.ripple_field_hb002, - 0x77010D: LocationName.sand_canyon_mushrooms, - 0x77010E: LocationName.sand_canyon_auntie, - 0x77010F: LocationName.sand_canyon_caramello, - 0x770110: LocationName.sand_canyon_hikari, - 0x770111: LocationName.sand_canyon_nyupun, - 0x770112: LocationName.sand_canyon_rob, - 0x770113: LocationName.cloudy_park_hibanamodoki, - 0x770114: LocationName.cloudy_park_piyokeko, - 0x770115: LocationName.cloudy_park_mrball, - 0x770116: LocationName.cloudy_park_mikarin, - 0x770117: LocationName.cloudy_park_pick, - 0x770118: LocationName.cloudy_park_hb007, - 0x770119: LocationName.iceberg_kogoesou, - 0x77011A: LocationName.iceberg_samus, - 0x77011B: LocationName.iceberg_kawasaki, - 0x77011C: LocationName.iceberg_name, - 0x77011D: LocationName.iceberg_shiro, - 0x77011E: LocationName.iceberg_angel, -} - -boss_locations = { - 0x770200: LocationName.grass_land_whispy, - 0x770201: LocationName.ripple_field_acro, - 0x770202: LocationName.sand_canyon_poncon, - 0x770203: LocationName.cloudy_park_ado, - 0x770204: LocationName.iceberg_dedede, -} - -consumable_locations = { - 0x770300: LocationName.grass_land_1_u1, - 0x770301: LocationName.grass_land_1_m1, - 0x770302: LocationName.grass_land_2_u1, - 0x770303: LocationName.grass_land_3_u1, - 0x770304: LocationName.grass_land_3_m1, - 0x770305: LocationName.grass_land_4_m1, - 0x770306: LocationName.grass_land_4_u1, - 0x770307: LocationName.grass_land_4_m2, - 0x770308: LocationName.grass_land_4_m3, - 0x770309: LocationName.grass_land_6_u1, - 0x77030A: LocationName.grass_land_6_u2, - 0x77030B: LocationName.ripple_field_2_u1, - 0x77030C: LocationName.ripple_field_2_m1, - 0x77030D: LocationName.ripple_field_3_m1, - 0x77030E: LocationName.ripple_field_3_u1, - 0x77030F: LocationName.ripple_field_4_m2, - 0x770310: LocationName.ripple_field_4_u1, - 0x770311: LocationName.ripple_field_4_m1, - 0x770312: LocationName.ripple_field_5_u1, - 0x770313: LocationName.ripple_field_5_m2, - 0x770314: LocationName.ripple_field_5_m1, - 0x770315: LocationName.sand_canyon_1_u1, - 0x770316: LocationName.sand_canyon_2_u1, - 0x770317: LocationName.sand_canyon_2_m1, - 0x770318: LocationName.sand_canyon_4_m1, - 0x770319: LocationName.sand_canyon_4_u1, - 0x77031A: LocationName.sand_canyon_4_m2, - 0x77031B: LocationName.sand_canyon_5_u1, - 0x77031C: LocationName.sand_canyon_5_u3, - 0x77031D: LocationName.sand_canyon_5_m1, - 0x77031E: LocationName.sand_canyon_5_u4, - 0x77031F: LocationName.sand_canyon_5_u2, - 0x770320: LocationName.cloudy_park_1_m1, - 0x770321: LocationName.cloudy_park_1_u1, - 0x770322: LocationName.cloudy_park_4_u1, - 0x770323: LocationName.cloudy_park_4_m1, - 0x770324: LocationName.cloudy_park_5_m1, - 0x770325: LocationName.cloudy_park_6_u1, - 0x770326: LocationName.iceberg_3_m1, - 0x770327: LocationName.iceberg_5_u1, - 0x770328: LocationName.iceberg_5_u2, - 0x770329: LocationName.iceberg_5_u3, - 0x77032A: LocationName.iceberg_6_m1, - 0x77032B: LocationName.iceberg_6_u1, -} - -level_consumables = { - 1: [0, 1], - 2: [2], - 3: [3, 4], - 4: [5, 6, 7, 8], - 6: [9, 10], - 8: [11, 12], - 9: [13, 14], - 10: [15, 16, 17], - 11: [18, 19, 20], - 13: [21], - 14: [22, 23], - 16: [24, 25, 26], - 17: [27, 28, 29, 30, 31], - 19: [32, 33], - 22: [34, 35], - 23: [36], - 24: [37], - 27: [38], - 29: [39, 40, 41], - 30: [42, 43], -} - -star_locations = { - 0x770401: LocationName.grass_land_1_s1, - 0x770402: LocationName.grass_land_1_s2, - 0x770403: LocationName.grass_land_1_s3, - 0x770404: LocationName.grass_land_1_s4, - 0x770405: LocationName.grass_land_1_s5, - 0x770406: LocationName.grass_land_1_s6, - 0x770407: LocationName.grass_land_1_s7, - 0x770408: LocationName.grass_land_1_s8, - 0x770409: LocationName.grass_land_1_s9, - 0x77040a: LocationName.grass_land_1_s10, - 0x77040b: LocationName.grass_land_1_s11, - 0x77040c: LocationName.grass_land_1_s12, - 0x77040d: LocationName.grass_land_1_s13, - 0x77040e: LocationName.grass_land_1_s14, - 0x77040f: LocationName.grass_land_1_s15, - 0x770410: LocationName.grass_land_1_s16, - 0x770411: LocationName.grass_land_1_s17, - 0x770412: LocationName.grass_land_1_s18, - 0x770413: LocationName.grass_land_1_s19, - 0x770414: LocationName.grass_land_1_s20, - 0x770415: LocationName.grass_land_1_s21, - 0x770416: LocationName.grass_land_1_s22, - 0x770417: LocationName.grass_land_1_s23, - 0x770418: LocationName.grass_land_2_s1, - 0x770419: LocationName.grass_land_2_s2, - 0x77041a: LocationName.grass_land_2_s3, - 0x77041b: LocationName.grass_land_2_s4, - 0x77041c: LocationName.grass_land_2_s5, - 0x77041d: LocationName.grass_land_2_s6, - 0x77041e: LocationName.grass_land_2_s7, - 0x77041f: LocationName.grass_land_2_s8, - 0x770420: LocationName.grass_land_2_s9, - 0x770421: LocationName.grass_land_2_s10, - 0x770422: LocationName.grass_land_2_s11, - 0x770423: LocationName.grass_land_2_s12, - 0x770424: LocationName.grass_land_2_s13, - 0x770425: LocationName.grass_land_2_s14, - 0x770426: LocationName.grass_land_2_s15, - 0x770427: LocationName.grass_land_2_s16, - 0x770428: LocationName.grass_land_2_s17, - 0x770429: LocationName.grass_land_2_s18, - 0x77042a: LocationName.grass_land_2_s19, - 0x77042b: LocationName.grass_land_2_s20, - 0x77042c: LocationName.grass_land_2_s21, - 0x77042d: LocationName.grass_land_3_s1, - 0x77042e: LocationName.grass_land_3_s2, - 0x77042f: LocationName.grass_land_3_s3, - 0x770430: LocationName.grass_land_3_s4, - 0x770431: LocationName.grass_land_3_s5, - 0x770432: LocationName.grass_land_3_s6, - 0x770433: LocationName.grass_land_3_s7, - 0x770434: LocationName.grass_land_3_s8, - 0x770435: LocationName.grass_land_3_s9, - 0x770436: LocationName.grass_land_3_s10, - 0x770437: LocationName.grass_land_3_s11, - 0x770438: LocationName.grass_land_3_s12, - 0x770439: LocationName.grass_land_3_s13, - 0x77043a: LocationName.grass_land_3_s14, - 0x77043b: LocationName.grass_land_3_s15, - 0x77043c: LocationName.grass_land_3_s16, - 0x77043d: LocationName.grass_land_3_s17, - 0x77043e: LocationName.grass_land_3_s18, - 0x77043f: LocationName.grass_land_3_s19, - 0x770440: LocationName.grass_land_3_s20, - 0x770441: LocationName.grass_land_3_s21, - 0x770442: LocationName.grass_land_3_s22, - 0x770443: LocationName.grass_land_3_s23, - 0x770444: LocationName.grass_land_3_s24, - 0x770445: LocationName.grass_land_3_s25, - 0x770446: LocationName.grass_land_3_s26, - 0x770447: LocationName.grass_land_3_s27, - 0x770448: LocationName.grass_land_3_s28, - 0x770449: LocationName.grass_land_3_s29, - 0x77044a: LocationName.grass_land_3_s30, - 0x77044b: LocationName.grass_land_3_s31, - 0x77044c: LocationName.grass_land_4_s1, - 0x77044d: LocationName.grass_land_4_s2, - 0x77044e: LocationName.grass_land_4_s3, - 0x77044f: LocationName.grass_land_4_s4, - 0x770450: LocationName.grass_land_4_s5, - 0x770451: LocationName.grass_land_4_s6, - 0x770452: LocationName.grass_land_4_s7, - 0x770453: LocationName.grass_land_4_s8, - 0x770454: LocationName.grass_land_4_s9, - 0x770455: LocationName.grass_land_4_s10, - 0x770456: LocationName.grass_land_4_s11, - 0x770457: LocationName.grass_land_4_s12, - 0x770458: LocationName.grass_land_4_s13, - 0x770459: LocationName.grass_land_4_s14, - 0x77045a: LocationName.grass_land_4_s15, - 0x77045b: LocationName.grass_land_4_s16, - 0x77045c: LocationName.grass_land_4_s17, - 0x77045d: LocationName.grass_land_4_s18, - 0x77045e: LocationName.grass_land_4_s19, - 0x77045f: LocationName.grass_land_4_s20, - 0x770460: LocationName.grass_land_4_s21, - 0x770461: LocationName.grass_land_4_s22, - 0x770462: LocationName.grass_land_4_s23, - 0x770463: LocationName.grass_land_4_s24, - 0x770464: LocationName.grass_land_4_s25, - 0x770465: LocationName.grass_land_4_s26, - 0x770466: LocationName.grass_land_4_s27, - 0x770467: LocationName.grass_land_4_s28, - 0x770468: LocationName.grass_land_4_s29, - 0x770469: LocationName.grass_land_4_s30, - 0x77046a: LocationName.grass_land_4_s31, - 0x77046b: LocationName.grass_land_4_s32, - 0x77046c: LocationName.grass_land_4_s33, - 0x77046d: LocationName.grass_land_4_s34, - 0x77046e: LocationName.grass_land_4_s35, - 0x77046f: LocationName.grass_land_4_s36, - 0x770470: LocationName.grass_land_4_s37, - 0x770471: LocationName.grass_land_5_s1, - 0x770472: LocationName.grass_land_5_s2, - 0x770473: LocationName.grass_land_5_s3, - 0x770474: LocationName.grass_land_5_s4, - 0x770475: LocationName.grass_land_5_s5, - 0x770476: LocationName.grass_land_5_s6, - 0x770477: LocationName.grass_land_5_s7, - 0x770478: LocationName.grass_land_5_s8, - 0x770479: LocationName.grass_land_5_s9, - 0x77047a: LocationName.grass_land_5_s10, - 0x77047b: LocationName.grass_land_5_s11, - 0x77047c: LocationName.grass_land_5_s12, - 0x77047d: LocationName.grass_land_5_s13, - 0x77047e: LocationName.grass_land_5_s14, - 0x77047f: LocationName.grass_land_5_s15, - 0x770480: LocationName.grass_land_5_s16, - 0x770481: LocationName.grass_land_5_s17, - 0x770482: LocationName.grass_land_5_s18, - 0x770483: LocationName.grass_land_5_s19, - 0x770484: LocationName.grass_land_5_s20, - 0x770485: LocationName.grass_land_5_s21, - 0x770486: LocationName.grass_land_5_s22, - 0x770487: LocationName.grass_land_5_s23, - 0x770488: LocationName.grass_land_5_s24, - 0x770489: LocationName.grass_land_5_s25, - 0x77048a: LocationName.grass_land_5_s26, - 0x77048b: LocationName.grass_land_5_s27, - 0x77048c: LocationName.grass_land_5_s28, - 0x77048d: LocationName.grass_land_5_s29, - 0x77048e: LocationName.grass_land_6_s1, - 0x77048f: LocationName.grass_land_6_s2, - 0x770490: LocationName.grass_land_6_s3, - 0x770491: LocationName.grass_land_6_s4, - 0x770492: LocationName.grass_land_6_s5, - 0x770493: LocationName.grass_land_6_s6, - 0x770494: LocationName.grass_land_6_s7, - 0x770495: LocationName.grass_land_6_s8, - 0x770496: LocationName.grass_land_6_s9, - 0x770497: LocationName.grass_land_6_s10, - 0x770498: LocationName.grass_land_6_s11, - 0x770499: LocationName.grass_land_6_s12, - 0x77049a: LocationName.grass_land_6_s13, - 0x77049b: LocationName.grass_land_6_s14, - 0x77049c: LocationName.grass_land_6_s15, - 0x77049d: LocationName.grass_land_6_s16, - 0x77049e: LocationName.grass_land_6_s17, - 0x77049f: LocationName.grass_land_6_s18, - 0x7704a0: LocationName.grass_land_6_s19, - 0x7704a1: LocationName.grass_land_6_s20, - 0x7704a2: LocationName.grass_land_6_s21, - 0x7704a3: LocationName.grass_land_6_s22, - 0x7704a4: LocationName.grass_land_6_s23, - 0x7704a5: LocationName.grass_land_6_s24, - 0x7704a6: LocationName.grass_land_6_s25, - 0x7704a7: LocationName.grass_land_6_s26, - 0x7704a8: LocationName.grass_land_6_s27, - 0x7704a9: LocationName.grass_land_6_s28, - 0x7704aa: LocationName.grass_land_6_s29, - 0x7704ab: LocationName.ripple_field_1_s1, - 0x7704ac: LocationName.ripple_field_1_s2, - 0x7704ad: LocationName.ripple_field_1_s3, - 0x7704ae: LocationName.ripple_field_1_s4, - 0x7704af: LocationName.ripple_field_1_s5, - 0x7704b0: LocationName.ripple_field_1_s6, - 0x7704b1: LocationName.ripple_field_1_s7, - 0x7704b2: LocationName.ripple_field_1_s8, - 0x7704b3: LocationName.ripple_field_1_s9, - 0x7704b4: LocationName.ripple_field_1_s10, - 0x7704b5: LocationName.ripple_field_1_s11, - 0x7704b6: LocationName.ripple_field_1_s12, - 0x7704b7: LocationName.ripple_field_1_s13, - 0x7704b8: LocationName.ripple_field_1_s14, - 0x7704b9: LocationName.ripple_field_1_s15, - 0x7704ba: LocationName.ripple_field_1_s16, - 0x7704bb: LocationName.ripple_field_1_s17, - 0x7704bc: LocationName.ripple_field_1_s18, - 0x7704bd: LocationName.ripple_field_1_s19, - 0x7704be: LocationName.ripple_field_2_s1, - 0x7704bf: LocationName.ripple_field_2_s2, - 0x7704c0: LocationName.ripple_field_2_s3, - 0x7704c1: LocationName.ripple_field_2_s4, - 0x7704c2: LocationName.ripple_field_2_s5, - 0x7704c3: LocationName.ripple_field_2_s6, - 0x7704c4: LocationName.ripple_field_2_s7, - 0x7704c5: LocationName.ripple_field_2_s8, - 0x7704c6: LocationName.ripple_field_2_s9, - 0x7704c7: LocationName.ripple_field_2_s10, - 0x7704c8: LocationName.ripple_field_2_s11, - 0x7704c9: LocationName.ripple_field_2_s12, - 0x7704ca: LocationName.ripple_field_2_s13, - 0x7704cb: LocationName.ripple_field_2_s14, - 0x7704cc: LocationName.ripple_field_2_s15, - 0x7704cd: LocationName.ripple_field_2_s16, - 0x7704ce: LocationName.ripple_field_2_s17, - 0x7704cf: LocationName.ripple_field_3_s1, - 0x7704d0: LocationName.ripple_field_3_s2, - 0x7704d1: LocationName.ripple_field_3_s3, - 0x7704d2: LocationName.ripple_field_3_s4, - 0x7704d3: LocationName.ripple_field_3_s5, - 0x7704d4: LocationName.ripple_field_3_s6, - 0x7704d5: LocationName.ripple_field_3_s7, - 0x7704d6: LocationName.ripple_field_3_s8, - 0x7704d7: LocationName.ripple_field_3_s9, - 0x7704d8: LocationName.ripple_field_3_s10, - 0x7704d9: LocationName.ripple_field_3_s11, - 0x7704da: LocationName.ripple_field_3_s12, - 0x7704db: LocationName.ripple_field_3_s13, - 0x7704dc: LocationName.ripple_field_3_s14, - 0x7704dd: LocationName.ripple_field_3_s15, - 0x7704de: LocationName.ripple_field_3_s16, - 0x7704df: LocationName.ripple_field_3_s17, - 0x7704e0: LocationName.ripple_field_3_s18, - 0x7704e1: LocationName.ripple_field_3_s19, - 0x7704e2: LocationName.ripple_field_3_s20, - 0x7704e3: LocationName.ripple_field_3_s21, - 0x7704e4: LocationName.ripple_field_4_s1, - 0x7704e5: LocationName.ripple_field_4_s2, - 0x7704e6: LocationName.ripple_field_4_s3, - 0x7704e7: LocationName.ripple_field_4_s4, - 0x7704e8: LocationName.ripple_field_4_s5, - 0x7704e9: LocationName.ripple_field_4_s6, - 0x7704ea: LocationName.ripple_field_4_s7, - 0x7704eb: LocationName.ripple_field_4_s8, - 0x7704ec: LocationName.ripple_field_4_s9, - 0x7704ed: LocationName.ripple_field_4_s10, - 0x7704ee: LocationName.ripple_field_4_s11, - 0x7704ef: LocationName.ripple_field_4_s12, - 0x7704f0: LocationName.ripple_field_4_s13, - 0x7704f1: LocationName.ripple_field_4_s14, - 0x7704f2: LocationName.ripple_field_4_s15, - 0x7704f3: LocationName.ripple_field_4_s16, - 0x7704f4: LocationName.ripple_field_4_s17, - 0x7704f5: LocationName.ripple_field_4_s18, - 0x7704f6: LocationName.ripple_field_4_s19, - 0x7704f7: LocationName.ripple_field_4_s20, - 0x7704f8: LocationName.ripple_field_4_s21, - 0x7704f9: LocationName.ripple_field_4_s22, - 0x7704fa: LocationName.ripple_field_4_s23, - 0x7704fb: LocationName.ripple_field_4_s24, - 0x7704fc: LocationName.ripple_field_4_s25, - 0x7704fd: LocationName.ripple_field_4_s26, - 0x7704fe: LocationName.ripple_field_4_s27, - 0x7704ff: LocationName.ripple_field_4_s28, - 0x770500: LocationName.ripple_field_4_s29, - 0x770501: LocationName.ripple_field_4_s30, - 0x770502: LocationName.ripple_field_4_s31, - 0x770503: LocationName.ripple_field_4_s32, - 0x770504: LocationName.ripple_field_4_s33, - 0x770505: LocationName.ripple_field_4_s34, - 0x770506: LocationName.ripple_field_4_s35, - 0x770507: LocationName.ripple_field_4_s36, - 0x770508: LocationName.ripple_field_4_s37, - 0x770509: LocationName.ripple_field_4_s38, - 0x77050a: LocationName.ripple_field_4_s39, - 0x77050b: LocationName.ripple_field_4_s40, - 0x77050c: LocationName.ripple_field_4_s41, - 0x77050d: LocationName.ripple_field_4_s42, - 0x77050e: LocationName.ripple_field_4_s43, - 0x77050f: LocationName.ripple_field_4_s44, - 0x770510: LocationName.ripple_field_4_s45, - 0x770511: LocationName.ripple_field_4_s46, - 0x770512: LocationName.ripple_field_4_s47, - 0x770513: LocationName.ripple_field_4_s48, - 0x770514: LocationName.ripple_field_4_s49, - 0x770515: LocationName.ripple_field_4_s50, - 0x770516: LocationName.ripple_field_4_s51, - 0x770517: LocationName.ripple_field_5_s1, - 0x770518: LocationName.ripple_field_5_s2, - 0x770519: LocationName.ripple_field_5_s3, - 0x77051a: LocationName.ripple_field_5_s4, - 0x77051b: LocationName.ripple_field_5_s5, - 0x77051c: LocationName.ripple_field_5_s6, - 0x77051d: LocationName.ripple_field_5_s7, - 0x77051e: LocationName.ripple_field_5_s8, - 0x77051f: LocationName.ripple_field_5_s9, - 0x770520: LocationName.ripple_field_5_s10, - 0x770521: LocationName.ripple_field_5_s11, - 0x770522: LocationName.ripple_field_5_s12, - 0x770523: LocationName.ripple_field_5_s13, - 0x770524: LocationName.ripple_field_5_s14, - 0x770525: LocationName.ripple_field_5_s15, - 0x770526: LocationName.ripple_field_5_s16, - 0x770527: LocationName.ripple_field_5_s17, - 0x770528: LocationName.ripple_field_5_s18, - 0x770529: LocationName.ripple_field_5_s19, - 0x77052a: LocationName.ripple_field_5_s20, - 0x77052b: LocationName.ripple_field_5_s21, - 0x77052c: LocationName.ripple_field_5_s22, - 0x77052d: LocationName.ripple_field_5_s23, - 0x77052e: LocationName.ripple_field_5_s24, - 0x77052f: LocationName.ripple_field_5_s25, - 0x770530: LocationName.ripple_field_5_s26, - 0x770531: LocationName.ripple_field_5_s27, - 0x770532: LocationName.ripple_field_5_s28, - 0x770533: LocationName.ripple_field_5_s29, - 0x770534: LocationName.ripple_field_5_s30, - 0x770535: LocationName.ripple_field_5_s31, - 0x770536: LocationName.ripple_field_5_s32, - 0x770537: LocationName.ripple_field_5_s33, - 0x770538: LocationName.ripple_field_5_s34, - 0x770539: LocationName.ripple_field_5_s35, - 0x77053a: LocationName.ripple_field_5_s36, - 0x77053b: LocationName.ripple_field_5_s37, - 0x77053c: LocationName.ripple_field_5_s38, - 0x77053d: LocationName.ripple_field_5_s39, - 0x77053e: LocationName.ripple_field_5_s40, - 0x77053f: LocationName.ripple_field_5_s41, - 0x770540: LocationName.ripple_field_5_s42, - 0x770541: LocationName.ripple_field_5_s43, - 0x770542: LocationName.ripple_field_5_s44, - 0x770543: LocationName.ripple_field_5_s45, - 0x770544: LocationName.ripple_field_5_s46, - 0x770545: LocationName.ripple_field_5_s47, - 0x770546: LocationName.ripple_field_5_s48, - 0x770547: LocationName.ripple_field_5_s49, - 0x770548: LocationName.ripple_field_5_s50, - 0x770549: LocationName.ripple_field_5_s51, - 0x77054a: LocationName.ripple_field_6_s1, - 0x77054b: LocationName.ripple_field_6_s2, - 0x77054c: LocationName.ripple_field_6_s3, - 0x77054d: LocationName.ripple_field_6_s4, - 0x77054e: LocationName.ripple_field_6_s5, - 0x77054f: LocationName.ripple_field_6_s6, - 0x770550: LocationName.ripple_field_6_s7, - 0x770551: LocationName.ripple_field_6_s8, - 0x770552: LocationName.ripple_field_6_s9, - 0x770553: LocationName.ripple_field_6_s10, - 0x770554: LocationName.ripple_field_6_s11, - 0x770555: LocationName.ripple_field_6_s12, - 0x770556: LocationName.ripple_field_6_s13, - 0x770557: LocationName.ripple_field_6_s14, - 0x770558: LocationName.ripple_field_6_s15, - 0x770559: LocationName.ripple_field_6_s16, - 0x77055a: LocationName.ripple_field_6_s17, - 0x77055b: LocationName.ripple_field_6_s18, - 0x77055c: LocationName.ripple_field_6_s19, - 0x77055d: LocationName.ripple_field_6_s20, - 0x77055e: LocationName.ripple_field_6_s21, - 0x77055f: LocationName.ripple_field_6_s22, - 0x770560: LocationName.ripple_field_6_s23, - 0x770561: LocationName.sand_canyon_1_s1, - 0x770562: LocationName.sand_canyon_1_s2, - 0x770563: LocationName.sand_canyon_1_s3, - 0x770564: LocationName.sand_canyon_1_s4, - 0x770565: LocationName.sand_canyon_1_s5, - 0x770566: LocationName.sand_canyon_1_s6, - 0x770567: LocationName.sand_canyon_1_s7, - 0x770568: LocationName.sand_canyon_1_s8, - 0x770569: LocationName.sand_canyon_1_s9, - 0x77056a: LocationName.sand_canyon_1_s10, - 0x77056b: LocationName.sand_canyon_1_s11, - 0x77056c: LocationName.sand_canyon_1_s12, - 0x77056d: LocationName.sand_canyon_1_s13, - 0x77056e: LocationName.sand_canyon_1_s14, - 0x77056f: LocationName.sand_canyon_1_s15, - 0x770570: LocationName.sand_canyon_1_s16, - 0x770571: LocationName.sand_canyon_1_s17, - 0x770572: LocationName.sand_canyon_1_s18, - 0x770573: LocationName.sand_canyon_1_s19, - 0x770574: LocationName.sand_canyon_1_s20, - 0x770575: LocationName.sand_canyon_1_s21, - 0x770576: LocationName.sand_canyon_1_s22, - 0x770577: LocationName.sand_canyon_2_s1, - 0x770578: LocationName.sand_canyon_2_s2, - 0x770579: LocationName.sand_canyon_2_s3, - 0x77057a: LocationName.sand_canyon_2_s4, - 0x77057b: LocationName.sand_canyon_2_s5, - 0x77057c: LocationName.sand_canyon_2_s6, - 0x77057d: LocationName.sand_canyon_2_s7, - 0x77057e: LocationName.sand_canyon_2_s8, - 0x77057f: LocationName.sand_canyon_2_s9, - 0x770580: LocationName.sand_canyon_2_s10, - 0x770581: LocationName.sand_canyon_2_s11, - 0x770582: LocationName.sand_canyon_2_s12, - 0x770583: LocationName.sand_canyon_2_s13, - 0x770584: LocationName.sand_canyon_2_s14, - 0x770585: LocationName.sand_canyon_2_s15, - 0x770586: LocationName.sand_canyon_2_s16, - 0x770587: LocationName.sand_canyon_2_s17, - 0x770588: LocationName.sand_canyon_2_s18, - 0x770589: LocationName.sand_canyon_2_s19, - 0x77058a: LocationName.sand_canyon_2_s20, - 0x77058b: LocationName.sand_canyon_2_s21, - 0x77058c: LocationName.sand_canyon_2_s22, - 0x77058d: LocationName.sand_canyon_2_s23, - 0x77058e: LocationName.sand_canyon_2_s24, - 0x77058f: LocationName.sand_canyon_2_s25, - 0x770590: LocationName.sand_canyon_2_s26, - 0x770591: LocationName.sand_canyon_2_s27, - 0x770592: LocationName.sand_canyon_2_s28, - 0x770593: LocationName.sand_canyon_2_s29, - 0x770594: LocationName.sand_canyon_2_s30, - 0x770595: LocationName.sand_canyon_2_s31, - 0x770596: LocationName.sand_canyon_2_s32, - 0x770597: LocationName.sand_canyon_2_s33, - 0x770598: LocationName.sand_canyon_2_s34, - 0x770599: LocationName.sand_canyon_2_s35, - 0x77059a: LocationName.sand_canyon_2_s36, - 0x77059b: LocationName.sand_canyon_2_s37, - 0x77059c: LocationName.sand_canyon_2_s38, - 0x77059d: LocationName.sand_canyon_2_s39, - 0x77059e: LocationName.sand_canyon_2_s40, - 0x77059f: LocationName.sand_canyon_2_s41, - 0x7705a0: LocationName.sand_canyon_2_s42, - 0x7705a1: LocationName.sand_canyon_2_s43, - 0x7705a2: LocationName.sand_canyon_2_s44, - 0x7705a3: LocationName.sand_canyon_2_s45, - 0x7705a4: LocationName.sand_canyon_2_s46, - 0x7705a5: LocationName.sand_canyon_2_s47, - 0x7705a6: LocationName.sand_canyon_2_s48, - 0x7705a7: LocationName.sand_canyon_3_s1, - 0x7705a8: LocationName.sand_canyon_3_s2, - 0x7705a9: LocationName.sand_canyon_3_s3, - 0x7705aa: LocationName.sand_canyon_3_s4, - 0x7705ab: LocationName.sand_canyon_3_s5, - 0x7705ac: LocationName.sand_canyon_3_s6, - 0x7705ad: LocationName.sand_canyon_3_s7, - 0x7705ae: LocationName.sand_canyon_3_s8, - 0x7705af: LocationName.sand_canyon_3_s9, - 0x7705b0: LocationName.sand_canyon_3_s10, - 0x7705b1: LocationName.sand_canyon_4_s1, - 0x7705b2: LocationName.sand_canyon_4_s2, - 0x7705b3: LocationName.sand_canyon_4_s3, - 0x7705b4: LocationName.sand_canyon_4_s4, - 0x7705b5: LocationName.sand_canyon_4_s5, - 0x7705b6: LocationName.sand_canyon_4_s6, - 0x7705b7: LocationName.sand_canyon_4_s7, - 0x7705b8: LocationName.sand_canyon_4_s8, - 0x7705b9: LocationName.sand_canyon_4_s9, - 0x7705ba: LocationName.sand_canyon_4_s10, - 0x7705bb: LocationName.sand_canyon_4_s11, - 0x7705bc: LocationName.sand_canyon_4_s12, - 0x7705bd: LocationName.sand_canyon_4_s13, - 0x7705be: LocationName.sand_canyon_4_s14, - 0x7705bf: LocationName.sand_canyon_4_s15, - 0x7705c0: LocationName.sand_canyon_4_s16, - 0x7705c1: LocationName.sand_canyon_4_s17, - 0x7705c2: LocationName.sand_canyon_4_s18, - 0x7705c3: LocationName.sand_canyon_4_s19, - 0x7705c4: LocationName.sand_canyon_4_s20, - 0x7705c5: LocationName.sand_canyon_4_s21, - 0x7705c6: LocationName.sand_canyon_4_s22, - 0x7705c7: LocationName.sand_canyon_4_s23, - 0x7705c8: LocationName.sand_canyon_5_s1, - 0x7705c9: LocationName.sand_canyon_5_s2, - 0x7705ca: LocationName.sand_canyon_5_s3, - 0x7705cb: LocationName.sand_canyon_5_s4, - 0x7705cc: LocationName.sand_canyon_5_s5, - 0x7705cd: LocationName.sand_canyon_5_s6, - 0x7705ce: LocationName.sand_canyon_5_s7, - 0x7705cf: LocationName.sand_canyon_5_s8, - 0x7705d0: LocationName.sand_canyon_5_s9, - 0x7705d1: LocationName.sand_canyon_5_s10, - 0x7705d2: LocationName.sand_canyon_5_s11, - 0x7705d3: LocationName.sand_canyon_5_s12, - 0x7705d4: LocationName.sand_canyon_5_s13, - 0x7705d5: LocationName.sand_canyon_5_s14, - 0x7705d6: LocationName.sand_canyon_5_s15, - 0x7705d7: LocationName.sand_canyon_5_s16, - 0x7705d8: LocationName.sand_canyon_5_s17, - 0x7705d9: LocationName.sand_canyon_5_s18, - 0x7705da: LocationName.sand_canyon_5_s19, - 0x7705db: LocationName.sand_canyon_5_s20, - 0x7705dc: LocationName.sand_canyon_5_s21, - 0x7705dd: LocationName.sand_canyon_5_s22, - 0x7705de: LocationName.sand_canyon_5_s23, - 0x7705df: LocationName.sand_canyon_5_s24, - 0x7705e0: LocationName.sand_canyon_5_s25, - 0x7705e1: LocationName.sand_canyon_5_s26, - 0x7705e2: LocationName.sand_canyon_5_s27, - 0x7705e3: LocationName.sand_canyon_5_s28, - 0x7705e4: LocationName.sand_canyon_5_s29, - 0x7705e5: LocationName.sand_canyon_5_s30, - 0x7705e6: LocationName.sand_canyon_5_s31, - 0x7705e7: LocationName.sand_canyon_5_s32, - 0x7705e8: LocationName.sand_canyon_5_s33, - 0x7705e9: LocationName.sand_canyon_5_s34, - 0x7705ea: LocationName.sand_canyon_5_s35, - 0x7705eb: LocationName.sand_canyon_5_s36, - 0x7705ec: LocationName.sand_canyon_5_s37, - 0x7705ed: LocationName.sand_canyon_5_s38, - 0x7705ee: LocationName.sand_canyon_5_s39, - 0x7705ef: LocationName.sand_canyon_5_s40, - 0x7705f0: LocationName.cloudy_park_1_s1, - 0x7705f1: LocationName.cloudy_park_1_s2, - 0x7705f2: LocationName.cloudy_park_1_s3, - 0x7705f3: LocationName.cloudy_park_1_s4, - 0x7705f4: LocationName.cloudy_park_1_s5, - 0x7705f5: LocationName.cloudy_park_1_s6, - 0x7705f6: LocationName.cloudy_park_1_s7, - 0x7705f7: LocationName.cloudy_park_1_s8, - 0x7705f8: LocationName.cloudy_park_1_s9, - 0x7705f9: LocationName.cloudy_park_1_s10, - 0x7705fa: LocationName.cloudy_park_1_s11, - 0x7705fb: LocationName.cloudy_park_1_s12, - 0x7705fc: LocationName.cloudy_park_1_s13, - 0x7705fd: LocationName.cloudy_park_1_s14, - 0x7705fe: LocationName.cloudy_park_1_s15, - 0x7705ff: LocationName.cloudy_park_1_s16, - 0x770600: LocationName.cloudy_park_1_s17, - 0x770601: LocationName.cloudy_park_1_s18, - 0x770602: LocationName.cloudy_park_1_s19, - 0x770603: LocationName.cloudy_park_1_s20, - 0x770604: LocationName.cloudy_park_1_s21, - 0x770605: LocationName.cloudy_park_1_s22, - 0x770606: LocationName.cloudy_park_1_s23, - 0x770607: LocationName.cloudy_park_2_s1, - 0x770608: LocationName.cloudy_park_2_s2, - 0x770609: LocationName.cloudy_park_2_s3, - 0x77060a: LocationName.cloudy_park_2_s4, - 0x77060b: LocationName.cloudy_park_2_s5, - 0x77060c: LocationName.cloudy_park_2_s6, - 0x77060d: LocationName.cloudy_park_2_s7, - 0x77060e: LocationName.cloudy_park_2_s8, - 0x77060f: LocationName.cloudy_park_2_s9, - 0x770610: LocationName.cloudy_park_2_s10, - 0x770611: LocationName.cloudy_park_2_s11, - 0x770612: LocationName.cloudy_park_2_s12, - 0x770613: LocationName.cloudy_park_2_s13, - 0x770614: LocationName.cloudy_park_2_s14, - 0x770615: LocationName.cloudy_park_2_s15, - 0x770616: LocationName.cloudy_park_2_s16, - 0x770617: LocationName.cloudy_park_2_s17, - 0x770618: LocationName.cloudy_park_2_s18, - 0x770619: LocationName.cloudy_park_2_s19, - 0x77061a: LocationName.cloudy_park_2_s20, - 0x77061b: LocationName.cloudy_park_2_s21, - 0x77061c: LocationName.cloudy_park_2_s22, - 0x77061d: LocationName.cloudy_park_2_s23, - 0x77061e: LocationName.cloudy_park_2_s24, - 0x77061f: LocationName.cloudy_park_2_s25, - 0x770620: LocationName.cloudy_park_2_s26, - 0x770621: LocationName.cloudy_park_2_s27, - 0x770622: LocationName.cloudy_park_2_s28, - 0x770623: LocationName.cloudy_park_2_s29, - 0x770624: LocationName.cloudy_park_2_s30, - 0x770625: LocationName.cloudy_park_2_s31, - 0x770626: LocationName.cloudy_park_2_s32, - 0x770627: LocationName.cloudy_park_2_s33, - 0x770628: LocationName.cloudy_park_2_s34, - 0x770629: LocationName.cloudy_park_2_s35, - 0x77062a: LocationName.cloudy_park_2_s36, - 0x77062b: LocationName.cloudy_park_2_s37, - 0x77062c: LocationName.cloudy_park_2_s38, - 0x77062d: LocationName.cloudy_park_2_s39, - 0x77062e: LocationName.cloudy_park_2_s40, - 0x77062f: LocationName.cloudy_park_2_s41, - 0x770630: LocationName.cloudy_park_2_s42, - 0x770631: LocationName.cloudy_park_2_s43, - 0x770632: LocationName.cloudy_park_2_s44, - 0x770633: LocationName.cloudy_park_2_s45, - 0x770634: LocationName.cloudy_park_2_s46, - 0x770635: LocationName.cloudy_park_2_s47, - 0x770636: LocationName.cloudy_park_2_s48, - 0x770637: LocationName.cloudy_park_2_s49, - 0x770638: LocationName.cloudy_park_2_s50, - 0x770639: LocationName.cloudy_park_2_s51, - 0x77063a: LocationName.cloudy_park_2_s52, - 0x77063b: LocationName.cloudy_park_2_s53, - 0x77063c: LocationName.cloudy_park_2_s54, - 0x77063d: LocationName.cloudy_park_3_s1, - 0x77063e: LocationName.cloudy_park_3_s2, - 0x77063f: LocationName.cloudy_park_3_s3, - 0x770640: LocationName.cloudy_park_3_s4, - 0x770641: LocationName.cloudy_park_3_s5, - 0x770642: LocationName.cloudy_park_3_s6, - 0x770643: LocationName.cloudy_park_3_s7, - 0x770644: LocationName.cloudy_park_3_s8, - 0x770645: LocationName.cloudy_park_3_s9, - 0x770646: LocationName.cloudy_park_3_s10, - 0x770647: LocationName.cloudy_park_3_s11, - 0x770648: LocationName.cloudy_park_3_s12, - 0x770649: LocationName.cloudy_park_3_s13, - 0x77064a: LocationName.cloudy_park_3_s14, - 0x77064b: LocationName.cloudy_park_3_s15, - 0x77064c: LocationName.cloudy_park_3_s16, - 0x77064d: LocationName.cloudy_park_3_s17, - 0x77064e: LocationName.cloudy_park_3_s18, - 0x77064f: LocationName.cloudy_park_3_s19, - 0x770650: LocationName.cloudy_park_3_s20, - 0x770651: LocationName.cloudy_park_3_s21, - 0x770652: LocationName.cloudy_park_3_s22, - 0x770653: LocationName.cloudy_park_4_s1, - 0x770654: LocationName.cloudy_park_4_s2, - 0x770655: LocationName.cloudy_park_4_s3, - 0x770656: LocationName.cloudy_park_4_s4, - 0x770657: LocationName.cloudy_park_4_s5, - 0x770658: LocationName.cloudy_park_4_s6, - 0x770659: LocationName.cloudy_park_4_s7, - 0x77065a: LocationName.cloudy_park_4_s8, - 0x77065b: LocationName.cloudy_park_4_s9, - 0x77065c: LocationName.cloudy_park_4_s10, - 0x77065d: LocationName.cloudy_park_4_s11, - 0x77065e: LocationName.cloudy_park_4_s12, - 0x77065f: LocationName.cloudy_park_4_s13, - 0x770660: LocationName.cloudy_park_4_s14, - 0x770661: LocationName.cloudy_park_4_s15, - 0x770662: LocationName.cloudy_park_4_s16, - 0x770663: LocationName.cloudy_park_4_s17, - 0x770664: LocationName.cloudy_park_4_s18, - 0x770665: LocationName.cloudy_park_4_s19, - 0x770666: LocationName.cloudy_park_4_s20, - 0x770667: LocationName.cloudy_park_4_s21, - 0x770668: LocationName.cloudy_park_4_s22, - 0x770669: LocationName.cloudy_park_4_s23, - 0x77066a: LocationName.cloudy_park_4_s24, - 0x77066b: LocationName.cloudy_park_4_s25, - 0x77066c: LocationName.cloudy_park_4_s26, - 0x77066d: LocationName.cloudy_park_4_s27, - 0x77066e: LocationName.cloudy_park_4_s28, - 0x77066f: LocationName.cloudy_park_4_s29, - 0x770670: LocationName.cloudy_park_4_s30, - 0x770671: LocationName.cloudy_park_4_s31, - 0x770672: LocationName.cloudy_park_4_s32, - 0x770673: LocationName.cloudy_park_4_s33, - 0x770674: LocationName.cloudy_park_4_s34, - 0x770675: LocationName.cloudy_park_4_s35, - 0x770676: LocationName.cloudy_park_4_s36, - 0x770677: LocationName.cloudy_park_4_s37, - 0x770678: LocationName.cloudy_park_4_s38, - 0x770679: LocationName.cloudy_park_4_s39, - 0x77067a: LocationName.cloudy_park_4_s40, - 0x77067b: LocationName.cloudy_park_4_s41, - 0x77067c: LocationName.cloudy_park_4_s42, - 0x77067d: LocationName.cloudy_park_4_s43, - 0x77067e: LocationName.cloudy_park_4_s44, - 0x77067f: LocationName.cloudy_park_4_s45, - 0x770680: LocationName.cloudy_park_4_s46, - 0x770681: LocationName.cloudy_park_4_s47, - 0x770682: LocationName.cloudy_park_4_s48, - 0x770683: LocationName.cloudy_park_4_s49, - 0x770684: LocationName.cloudy_park_4_s50, - 0x770685: LocationName.cloudy_park_5_s1, - 0x770686: LocationName.cloudy_park_5_s2, - 0x770687: LocationName.cloudy_park_5_s3, - 0x770688: LocationName.cloudy_park_5_s4, - 0x770689: LocationName.cloudy_park_5_s5, - 0x77068a: LocationName.cloudy_park_5_s6, - 0x77068b: LocationName.cloudy_park_6_s1, - 0x77068c: LocationName.cloudy_park_6_s2, - 0x77068d: LocationName.cloudy_park_6_s3, - 0x77068e: LocationName.cloudy_park_6_s4, - 0x77068f: LocationName.cloudy_park_6_s5, - 0x770690: LocationName.cloudy_park_6_s6, - 0x770691: LocationName.cloudy_park_6_s7, - 0x770692: LocationName.cloudy_park_6_s8, - 0x770693: LocationName.cloudy_park_6_s9, - 0x770694: LocationName.cloudy_park_6_s10, - 0x770695: LocationName.cloudy_park_6_s11, - 0x770696: LocationName.cloudy_park_6_s12, - 0x770697: LocationName.cloudy_park_6_s13, - 0x770698: LocationName.cloudy_park_6_s14, - 0x770699: LocationName.cloudy_park_6_s15, - 0x77069a: LocationName.cloudy_park_6_s16, - 0x77069b: LocationName.cloudy_park_6_s17, - 0x77069c: LocationName.cloudy_park_6_s18, - 0x77069d: LocationName.cloudy_park_6_s19, - 0x77069e: LocationName.cloudy_park_6_s20, - 0x77069f: LocationName.cloudy_park_6_s21, - 0x7706a0: LocationName.cloudy_park_6_s22, - 0x7706a1: LocationName.cloudy_park_6_s23, - 0x7706a2: LocationName.cloudy_park_6_s24, - 0x7706a3: LocationName.cloudy_park_6_s25, - 0x7706a4: LocationName.cloudy_park_6_s26, - 0x7706a5: LocationName.cloudy_park_6_s27, - 0x7706a6: LocationName.cloudy_park_6_s28, - 0x7706a7: LocationName.cloudy_park_6_s29, - 0x7706a8: LocationName.cloudy_park_6_s30, - 0x7706a9: LocationName.cloudy_park_6_s31, - 0x7706aa: LocationName.cloudy_park_6_s32, - 0x7706ab: LocationName.cloudy_park_6_s33, - 0x7706ac: LocationName.iceberg_1_s1, - 0x7706ad: LocationName.iceberg_1_s2, - 0x7706ae: LocationName.iceberg_1_s3, - 0x7706af: LocationName.iceberg_1_s4, - 0x7706b0: LocationName.iceberg_1_s5, - 0x7706b1: LocationName.iceberg_1_s6, - 0x7706b2: LocationName.iceberg_2_s1, - 0x7706b3: LocationName.iceberg_2_s2, - 0x7706b4: LocationName.iceberg_2_s3, - 0x7706b5: LocationName.iceberg_2_s4, - 0x7706b6: LocationName.iceberg_2_s5, - 0x7706b7: LocationName.iceberg_2_s6, - 0x7706b8: LocationName.iceberg_2_s7, - 0x7706b9: LocationName.iceberg_2_s8, - 0x7706ba: LocationName.iceberg_2_s9, - 0x7706bb: LocationName.iceberg_2_s10, - 0x7706bc: LocationName.iceberg_2_s11, - 0x7706bd: LocationName.iceberg_2_s12, - 0x7706be: LocationName.iceberg_2_s13, - 0x7706bf: LocationName.iceberg_2_s14, - 0x7706c0: LocationName.iceberg_2_s15, - 0x7706c1: LocationName.iceberg_2_s16, - 0x7706c2: LocationName.iceberg_2_s17, - 0x7706c3: LocationName.iceberg_2_s18, - 0x7706c4: LocationName.iceberg_2_s19, - 0x7706c5: LocationName.iceberg_3_s1, - 0x7706c6: LocationName.iceberg_3_s2, - 0x7706c7: LocationName.iceberg_3_s3, - 0x7706c8: LocationName.iceberg_3_s4, - 0x7706c9: LocationName.iceberg_3_s5, - 0x7706ca: LocationName.iceberg_3_s6, - 0x7706cb: LocationName.iceberg_3_s7, - 0x7706cc: LocationName.iceberg_3_s8, - 0x7706cd: LocationName.iceberg_3_s9, - 0x7706ce: LocationName.iceberg_3_s10, - 0x7706cf: LocationName.iceberg_3_s11, - 0x7706d0: LocationName.iceberg_3_s12, - 0x7706d1: LocationName.iceberg_3_s13, - 0x7706d2: LocationName.iceberg_3_s14, - 0x7706d3: LocationName.iceberg_3_s15, - 0x7706d4: LocationName.iceberg_3_s16, - 0x7706d5: LocationName.iceberg_3_s17, - 0x7706d6: LocationName.iceberg_3_s18, - 0x7706d7: LocationName.iceberg_3_s19, - 0x7706d8: LocationName.iceberg_3_s20, - 0x7706d9: LocationName.iceberg_3_s21, - 0x7706da: LocationName.iceberg_4_s1, - 0x7706db: LocationName.iceberg_4_s2, - 0x7706dc: LocationName.iceberg_4_s3, - 0x7706dd: LocationName.iceberg_5_s1, - 0x7706de: LocationName.iceberg_5_s2, - 0x7706df: LocationName.iceberg_5_s3, - 0x7706e0: LocationName.iceberg_5_s4, - 0x7706e1: LocationName.iceberg_5_s5, - 0x7706e2: LocationName.iceberg_5_s6, - 0x7706e3: LocationName.iceberg_5_s7, - 0x7706e4: LocationName.iceberg_5_s8, - 0x7706e5: LocationName.iceberg_5_s9, - 0x7706e6: LocationName.iceberg_5_s10, - 0x7706e7: LocationName.iceberg_5_s11, - 0x7706e8: LocationName.iceberg_5_s12, - 0x7706e9: LocationName.iceberg_5_s13, - 0x7706ea: LocationName.iceberg_5_s14, - 0x7706eb: LocationName.iceberg_5_s15, - 0x7706ec: LocationName.iceberg_5_s16, - 0x7706ed: LocationName.iceberg_5_s17, - 0x7706ee: LocationName.iceberg_5_s18, - 0x7706ef: LocationName.iceberg_5_s19, - 0x7706f0: LocationName.iceberg_5_s20, - 0x7706f1: LocationName.iceberg_5_s21, - 0x7706f2: LocationName.iceberg_5_s22, - 0x7706f3: LocationName.iceberg_5_s23, - 0x7706f4: LocationName.iceberg_5_s24, - 0x7706f5: LocationName.iceberg_5_s25, - 0x7706f6: LocationName.iceberg_5_s26, - 0x7706f7: LocationName.iceberg_5_s27, - 0x7706f8: LocationName.iceberg_5_s28, - 0x7706f9: LocationName.iceberg_5_s29, - 0x7706fa: LocationName.iceberg_5_s30, - 0x7706fb: LocationName.iceberg_5_s31, - 0x7706fc: LocationName.iceberg_5_s32, - 0x7706fd: LocationName.iceberg_5_s33, - 0x7706fe: LocationName.iceberg_5_s34, - 0x7706ff: LocationName.iceberg_6_s1, - -} - -location_table = { - **stage_locations, - **heart_star_locations, - **boss_locations, - **consumable_locations, - **star_locations -} diff --git a/worlds/kdl3/Rom.py b/worlds/kdl3/Rom.py deleted file mode 100644 index 5a846ab8..00000000 --- a/worlds/kdl3/Rom.py +++ /dev/null @@ -1,577 +0,0 @@ -import typing -from pkgutil import get_data - -import Utils -from typing import Optional, TYPE_CHECKING -import hashlib -import os -import struct - -import settings -from worlds.Files import APDeltaPatch -from .Aesthetics import get_palette_bytes, kirby_target_palettes, get_kirby_palette, gooey_target_palettes, \ - get_gooey_palette -from .Compression import hal_decompress -import bsdiff4 - -if TYPE_CHECKING: - from . import KDL3World - -KDL3UHASH = "201e7658f6194458a3869dde36bf8ec2" -KDL3JHASH = "b2f2d004ea640c3db66df958fce122b2" - -level_pointers = { - 0x770001: 0x0084, - 0x770002: 0x009C, - 0x770003: 0x00B8, - 0x770004: 0x00D8, - 0x770005: 0x0104, - 0x770006: 0x0124, - 0x770007: 0x014C, - 0x770008: 0x0170, - 0x770009: 0x0190, - 0x77000A: 0x01B0, - 0x77000B: 0x01E8, - 0x77000C: 0x0218, - 0x77000D: 0x024C, - 0x77000E: 0x0270, - 0x77000F: 0x02A0, - 0x770010: 0x02C4, - 0x770011: 0x02EC, - 0x770012: 0x0314, - 0x770013: 0x03CC, - 0x770014: 0x0404, - 0x770015: 0x042C, - 0x770016: 0x044C, - 0x770017: 0x0478, - 0x770018: 0x049C, - 0x770019: 0x04E4, - 0x77001A: 0x0504, - 0x77001B: 0x0530, - 0x77001C: 0x0554, - 0x77001D: 0x05A8, - 0x77001E: 0x0640, - 0x770200: 0x0148, - 0x770201: 0x0248, - 0x770202: 0x03C8, - 0x770203: 0x04E0, - 0x770204: 0x06A4, - 0x770205: 0x06A8, -} - -bb_bosses = { - 0x770200: 0xED85F1, - 0x770201: 0xF01360, - 0x770202: 0xEDA3DF, - 0x770203: 0xEDC2B9, - 0x770204: 0xED7C3F, - 0x770205: 0xEC29D2, -} - -level_sprites = { - 0x19B2C6: 1827, - 0x1A195C: 1584, - 0x19F6F3: 1679, - 0x19DC8B: 1717, - 0x197900: 1872 -} - -stage_tiles = { - 0: [ - 0, 1, 2, - 16, 17, 18, - 32, 33, 34, - 48, 49, 50 - ], - 1: [ - 3, 4, 5, - 19, 20, 21, - 35, 36, 37, - 51, 52, 53 - ], - 2: [ - 6, 7, 8, - 22, 23, 24, - 38, 39, 40, - 54, 55, 56 - ], - 3: [ - 9, 10, 11, - 25, 26, 27, - 41, 42, 43, - 57, 58, 59, - ], - 4: [ - 12, 13, 64, - 28, 29, 65, - 44, 45, 66, - 60, 61, 67 - ], - 5: [ - 14, 15, 68, - 30, 31, 69, - 46, 47, 70, - 62, 63, 71 - ] -} - -heart_star_address = 0x2D0000 -heart_star_size = 456 -consumable_address = 0x2F91DD -consumable_size = 698 - -stage_palettes = [0x60964, 0x60B64, 0x60D64, 0x60F64, 0x61164] - -music_choices = [ - 2, # Boss 1 - 3, # Boss 2 (Unused) - 4, # Boss 3 (Miniboss) - 7, # Dedede - 9, # Event 2 (used once) - 10, # Field 1 - 11, # Field 2 - 12, # Field 3 - 13, # Field 4 - 14, # Field 5 - 15, # Field 6 - 16, # Field 7 - 17, # Field 8 - 18, # Field 9 - 19, # Field 10 - 20, # Field 11 - 21, # Field 12 (Gourmet Race) - 23, # Dark Matter in the Hyper Zone - 24, # Zero - 25, # Level 1 - 26, # Level 2 - 27, # Level 4 - 28, # Level 3 - 29, # Heart Star Failed - 30, # Level 5 - 31, # Minigame - 38, # Animal Friend 1 - 39, # Animal Friend 2 - 40, # Animal Friend 3 -] -# extra room pointers we don't want to track other than for music -room_pointers = [ - 3079990, # Zero - 2983409, # BB Whispy - 3150688, # BB Acro - 2991071, # BB PonCon - 2998969, # BB Ado - 2980927, # BB Dedede - 2894290 # BB Zero -] - -enemy_remap = { - "Waddle Dee": 0, - "Bronto Burt": 2, - "Rocky": 3, - "Bobo": 5, - "Chilly": 6, - "Poppy Bros Jr.": 7, - "Sparky": 8, - "Polof": 9, - "Broom Hatter": 11, - "Cappy": 12, - "Bouncy": 13, - "Nruff": 15, - "Glunk": 16, - "Togezo": 18, - "Kabu": 19, - "Mony": 20, - "Blipper": 21, - "Squishy": 22, - "Gabon": 24, - "Oro": 25, - "Galbo": 26, - "Sir Kibble": 27, - "Nidoo": 28, - "Kany": 29, - "Sasuke": 30, - "Yaban": 32, - "Boten": 33, - "Coconut": 34, - "Doka": 35, - "Icicle": 36, - "Pteran": 39, - "Loud": 40, - "Como": 41, - "Klinko": 42, - "Babut": 43, - "Wappa": 44, - "Mariel": 45, - "Tick": 48, - "Apolo": 49, - "Popon Ball": 50, - "KeKe": 51, - "Magoo": 53, - "Raft Waddle Dee": 57, - "Madoo": 58, - "Corori": 60, - "Kapar": 67, - "Batamon": 68, - "Peran": 72, - "Bobin": 73, - "Mopoo": 74, - "Gansan": 75, - "Bukiset (Burning)": 76, - "Bukiset (Stone)": 77, - "Bukiset (Ice)": 78, - "Bukiset (Needle)": 79, - "Bukiset (Clean)": 80, - "Bukiset (Parasol)": 81, - "Bukiset (Spark)": 82, - "Bukiset (Cutter)": 83, - "Waddle Dee Drawing": 84, - "Bronto Burt Drawing": 85, - "Bouncy Drawing": 86, - "Kabu (Dekabu)": 87, - "Wapod": 88, - "Propeller": 89, - "Dogon": 90, - "Joe": 91 -} - -miniboss_remap = { - "Captain Stitch": 0, - "Yuki": 1, - "Blocky": 2, - "Jumper Shoot": 3, - "Boboo": 4, - "Haboki": 5 -} - -ability_remap = { - "No Ability": 0, - "Burning Ability": 1, - "Stone Ability": 2, - "Ice Ability": 3, - "Needle Ability": 4, - "Clean Ability": 5, - "Parasol Ability": 6, - "Spark Ability": 7, - "Cutter Ability": 8, -} - - -class RomData: - def __init__(self, file: str, name: typing.Optional[str] = None): - self.file = bytearray() - self.read_from_file(file) - self.name = name - - def read_byte(self, offset: int): - return self.file[offset] - - def read_bytes(self, offset: int, length: int): - return self.file[offset:offset + length] - - def write_byte(self, offset: int, value: int): - self.file[offset] = value - - def write_bytes(self, offset: int, values: typing.Sequence) -> None: - self.file[offset:offset + len(values)] = values - - def write_to_file(self, file: str): - with open(file, 'wb') as outfile: - outfile.write(self.file) - - def read_from_file(self, file: str): - with open(file, 'rb') as stream: - self.file = bytearray(stream.read()) - - def apply_patch(self, patch: bytes): - self.file = bytearray(bsdiff4.patch(bytes(self.file), patch)) - - def write_crc(self): - crc = (sum(self.file[:0x7FDC] + self.file[0x7FE0:]) + 0x01FE) & 0xFFFF - inv = crc ^ 0xFFFF - self.write_bytes(0x7FDC, [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF]) - - -def handle_level_sprites(stages, sprites, palettes): - palette_by_level = list() - for palette in palettes: - palette_by_level.extend(palette[10:16]) - for i in range(5): - for j in range(6): - palettes[i][10 + j] = palette_by_level[stages[i][j] - 1] - palettes[i] = [x for palette in palettes[i] for x in palette] - tiles_by_level = list() - for spritesheet in sprites: - decompressed = hal_decompress(spritesheet) - tiles = [decompressed[i:i + 32] for i in range(0, 2304, 32)] - tiles_by_level.extend([[tiles[x] for x in stage_tiles[stage]] for stage in stage_tiles]) - for world in range(5): - levels = [stages[world][x] - 1 for x in range(6)] - world_tiles: typing.List[typing.Optional[bytes]] = [None for _ in range(72)] - for i in range(6): - for x in range(12): - world_tiles[stage_tiles[i][x]] = tiles_by_level[levels[i]][x] - sprites[world] = list() - for tile in world_tiles: - sprites[world].extend(tile) - # insert our fake compression - sprites[world][0:0] = [0xe3, 0xff] - sprites[world][1026:1026] = [0xe3, 0xff] - sprites[world][2052:2052] = [0xe0, 0xff] - sprites[world].append(0xff) - return sprites, palettes - - -def write_heart_star_sprites(rom: RomData): - compressed = rom.read_bytes(heart_star_address, heart_star_size) - decompressed = hal_decompress(compressed) - patch = get_data(__name__, os.path.join("data", "APHeartStar.bsdiff4")) - patched = bytearray(bsdiff4.patch(decompressed, patch)) - rom.write_bytes(0x1AF7DF, patched) - patched[0:0] = [0xE3, 0xFF] - patched.append(0xFF) - rom.write_bytes(0x1CD000, patched) - rom.write_bytes(0x3F0EBF, [0x00, 0xD0, 0x39]) - - -def write_consumable_sprites(rom: RomData, consumables: bool, stars: bool): - compressed = rom.read_bytes(consumable_address, consumable_size) - decompressed = hal_decompress(compressed) - patched = bytearray(decompressed) - if consumables: - patch = get_data(__name__, os.path.join("data", "APConsumable.bsdiff4")) - patched = bytearray(bsdiff4.patch(bytes(patched), patch)) - if stars: - patch = get_data(__name__, os.path.join("data", "APStars.bsdiff4")) - patched = bytearray(bsdiff4.patch(bytes(patched), patch)) - patched[0:0] = [0xE3, 0xFF] - patched.append(0xFF) - rom.write_bytes(0x1CD500, patched) - rom.write_bytes(0x3F0DAE, [0x00, 0xD5, 0x39]) - - -class KDL3DeltaPatch(APDeltaPatch): - hash = [KDL3UHASH, KDL3JHASH] - game = "Kirby's Dream Land 3" - patch_file_ending = ".apkdl3" - - @classmethod - def get_source_data(cls) -> bytes: - return get_base_rom_bytes() - - def patch(self, target: str): - super().patch(target) - rom = RomData(target) - target_language = rom.read_byte(0x3C020) - rom.write_byte(0x7FD9, target_language) - write_heart_star_sprites(rom) - if rom.read_bytes(0x3D014, 1)[0] > 0: - stages = [struct.unpack("HHHHHHH", rom.read_bytes(0x3D020 + x * 14, 14)) for x in range(5)] - palettes = [rom.read_bytes(full_pal, 512) for full_pal in stage_palettes] - palettes = [[palette[i:i + 32] for i in range(0, 512, 32)] for palette in palettes] - sprites = [rom.read_bytes(offset, level_sprites[offset]) for offset in level_sprites] - sprites, palettes = handle_level_sprites(stages, sprites, palettes) - for addr, palette in zip(stage_palettes, palettes): - rom.write_bytes(addr, palette) - for addr, level_sprite in zip([0x1CA000, 0x1CA920, 0x1CB230, 0x1CBB40, 0x1CC450], sprites): - rom.write_bytes(addr, level_sprite) - rom.write_bytes(0x460A, [0x00, 0xA0, 0x39, 0x20, 0xA9, 0x39, 0x30, 0xB2, 0x39, 0x40, 0xBB, 0x39, - 0x50, 0xC4, 0x39]) - write_consumable_sprites(rom, rom.read_byte(0x3D018) > 0, rom.read_byte(0x3D01A) > 0) - rom_name = rom.read_bytes(0x3C000, 21) - rom.write_bytes(0x7FC0, rom_name) - rom.write_crc() - rom.write_to_file(target) - - -def patch_rom(world: "KDL3World", rom: RomData): - rom.apply_patch(get_data(__name__, os.path.join("data", "kdl3_basepatch.bsdiff4"))) - tiles = get_data(__name__, os.path.join("data", "APPauseIcons.dat")) - rom.write_bytes(0x3F000, tiles) - - # Write open world patch - if world.options.open_world: - rom.write_bytes(0x143C7, [0xAD, 0xC1, 0x5A, 0xCD, 0xC1, 0x5A, ]) - # changes the stage flag function to compare $5AC1 to $5AC1, - # always running the "new stage" function - # This has further checks present for bosses already, so we just - # need to handle regular stages - # write check for boss to be unlocked - - if world.options.consumables: - # reroute maxim tomatoes to use the 1-UP function, then null out the function - rom.write_bytes(0x3002F, [0x37, 0x00]) - rom.write_bytes(0x30037, [0xA9, 0x26, 0x00, # LDA #$0026 - 0x22, 0x27, 0xD9, 0x00, # JSL $00D927 - 0xA4, 0xD2, # LDY $D2 - 0x6B, # RTL - 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, # NOP #10 - ]) - - # stars handling is built into the rom, so no changes there - - rooms = world.rooms - if world.options.music_shuffle > 0: - if world.options.music_shuffle == 1: - shuffled_music = music_choices.copy() - world.random.shuffle(shuffled_music) - music_map = dict(zip(music_choices, shuffled_music)) - # Avoid putting star twinkle in the pool - music_map[5] = world.random.choice(music_choices) - # Heart Star music doesn't work on regular stages - music_map[8] = world.random.choice(music_choices) - for room in rooms: - room.music = music_map[room.music] - for room in room_pointers: - old_music = rom.read_byte(room + 2) - rom.write_byte(room + 2, music_map[old_music]) - for i in range(5): - # level themes - old_music = rom.read_byte(0x133F2 + i) - rom.write_byte(0x133F2 + i, music_map[old_music]) - # Zero - rom.write_byte(0x9AE79, music_map[0x18]) - # Heart Star success and fail - rom.write_byte(0x4A388, music_map[0x08]) - rom.write_byte(0x4A38D, music_map[0x1D]) - elif world.options.music_shuffle == 2: - for room in rooms: - room.music = world.random.choice(music_choices) - for room in room_pointers: - rom.write_byte(room + 2, world.random.choice(music_choices)) - for i in range(5): - # level themes - rom.write_byte(0x133F2 + i, world.random.choice(music_choices)) - # Zero - rom.write_byte(0x9AE79, world.random.choice(music_choices)) - # Heart Star success and fail - rom.write_byte(0x4A388, world.random.choice(music_choices)) - rom.write_byte(0x4A38D, world.random.choice(music_choices)) - - for room in rooms: - room.patch(rom) - - if world.options.virtual_console in [1, 3]: - # Flash Reduction - rom.write_byte(0x9AE68, 0x10) - rom.write_bytes(0x9AE8E, [0x08, 0x00, 0x22, 0x5D, 0xF7, 0x00, 0xA2, 0x08, ]) - rom.write_byte(0x9AEA1, 0x08) - rom.write_byte(0x9AEC9, 0x01) - rom.write_bytes(0x9AED2, [0xA9, 0x1F]) - rom.write_byte(0x9AEE1, 0x08) - - if world.options.virtual_console in [2, 3]: - # Hyper Zone BB colors - rom.write_bytes(0x2C5E16, [0xEE, 0x1B, 0x18, 0x5B, 0xD3, 0x4A, 0xF4, 0x3B, ]) - rom.write_bytes(0x2C8217, [0xFF, 0x1E, ]) - - # boss requirements - rom.write_bytes(0x3D000, struct.pack("HHHHH", world.boss_requirements[0], world.boss_requirements[1], - world.boss_requirements[2], world.boss_requirements[3], - world.boss_requirements[4])) - rom.write_bytes(0x3D00A, struct.pack("H", world.required_heart_stars if world.options.goal_speed == 1 else 0xFFFF)) - rom.write_byte(0x3D00C, world.options.goal_speed.value) - rom.write_byte(0x3D00E, world.options.open_world.value) - rom.write_byte(0x3D010, world.options.death_link.value) - rom.write_byte(0x3D012, world.options.goal.value) - rom.write_byte(0x3D014, world.options.stage_shuffle.value) - rom.write_byte(0x3D016, world.options.ow_boss_requirement.value) - rom.write_byte(0x3D018, world.options.consumables.value) - rom.write_byte(0x3D01A, world.options.starsanity.value) - rom.write_byte(0x3D01C, world.options.gifting.value if world.multiworld.players > 1 else 0) - rom.write_byte(0x3D01E, world.options.strict_bosses.value) - # don't write gifting for solo game, since there's no one to send anything to - - for level in world.player_levels: - for i in range(len(world.player_levels[level])): - rom.write_bytes(0x3F002E + ((level - 1) * 14) + (i * 2), - struct.pack("H", level_pointers[world.player_levels[level][i]])) - rom.write_bytes(0x3D020 + (level - 1) * 14 + (i * 2), - struct.pack("H", world.player_levels[level][i] & 0x00FFFF)) - if (i == 0) or (i > 0 and i % 6 != 0): - rom.write_bytes(0x3D080 + (level - 1) * 12 + (i * 2), - struct.pack("H", (world.player_levels[level][i] & 0x00FFFF) % 6)) - - for i in range(6): - if world.boss_butch_bosses[i]: - rom.write_bytes(0x3F0000 + (level_pointers[0x770200 + i]), struct.pack("I", bb_bosses[0x770200 + i])) - - # copy ability shuffle - if world.options.copy_ability_randomization.value > 0: - for enemy in world.copy_abilities: - if enemy in miniboss_remap: - rom.write_bytes(0xB417E + (miniboss_remap[enemy] << 1), - struct.pack("H", ability_remap[world.copy_abilities[enemy]])) - else: - rom.write_bytes(0xB3CAC + (enemy_remap[enemy] << 1), - struct.pack("H", ability_remap[world.copy_abilities[enemy]])) - # following only needs done on non-door rando - # incredibly lucky this follows the same order (including 5E == star block) - rom.write_byte(0x2F77EA, 0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1)) - rom.write_byte(0x2F7811, 0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1)) - rom.write_byte(0x2F9BC4, 0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1)) - rom.write_byte(0x2F9BEB, 0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1)) - rom.write_byte(0x2FAC06, 0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1)) - rom.write_byte(0x2FAC2D, 0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1)) - rom.write_byte(0x2F9E7B, 0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1)) - rom.write_byte(0x2F9EA2, 0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1)) - rom.write_byte(0x2FA951, 0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1)) - rom.write_byte(0x2FA978, 0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1)) - rom.write_byte(0x2FA132, 0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1)) - rom.write_byte(0x2FA159, 0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1)) - rom.write_byte(0x2FA3E8, 0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1)) - rom.write_byte(0x2FA40F, 0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1)) - rom.write_byte(0x2F90E2, 0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1)) - rom.write_byte(0x2F9109, 0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1)) - - if world.options.copy_ability_randomization == 2: - for enemy in enemy_remap: - # we just won't include it for minibosses - rom.write_bytes(0xB3E40 + (enemy_remap[enemy] << 1), struct.pack("h", world.random.randint(-1, 2))) - - # write jumping goal - rom.write_bytes(0x94F8, struct.pack("H", world.options.jumping_target)) - rom.write_bytes(0x944E, struct.pack("H", world.options.jumping_target)) - - from Utils import __version__ - rom.name = bytearray( - f'KDL3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21] - rom.name.extend([0] * (21 - len(rom.name))) - rom.write_bytes(0x3C000, rom.name) - rom.write_byte(0x3C020, world.options.game_language.value) - - # handle palette - if world.options.kirby_flavor_preset.value != 0: - for addr in kirby_target_palettes: - target = kirby_target_palettes[addr] - palette = get_kirby_palette(world) - rom.write_bytes(addr, get_palette_bytes(palette, target[0], target[1], target[2])) - - if world.options.gooey_flavor_preset.value != 0: - for addr in gooey_target_palettes: - target = gooey_target_palettes[addr] - palette = get_gooey_palette(world) - rom.write_bytes(addr, get_palette_bytes(palette, target[0], target[1], target[2])) - - -def get_base_rom_bytes() -> bytes: - rom_file: str = get_base_rom_path() - base_rom_bytes: Optional[bytes] = getattr(get_base_rom_bytes, "base_rom_bytes", None) - if not base_rom_bytes: - base_rom_bytes = bytes(Utils.read_snes_rom(open(rom_file, "rb"))) - - basemd5 = hashlib.md5() - basemd5.update(base_rom_bytes) - if basemd5.hexdigest() not in {KDL3UHASH, KDL3JHASH}: - raise Exception("Supplied Base Rom does not match known MD5 for US or JP release. " - "Get the correct game and version, then dump it") - get_base_rom_bytes.base_rom_bytes = base_rom_bytes - return base_rom_bytes - - -def get_base_rom_path(file_name: str = "") -> str: - options: settings.Settings = settings.get_settings() - if not file_name: - file_name = options["kdl3_options"]["rom_file"] - if not os.path.exists(file_name): - file_name = Utils.user_path(file_name) - return file_name diff --git a/worlds/kdl3/Room.py b/worlds/kdl3/Room.py deleted file mode 100644 index 256955b9..00000000 --- a/worlds/kdl3/Room.py +++ /dev/null @@ -1,95 +0,0 @@ -import struct -import typing -from BaseClasses import Region, ItemClassification - -if typing.TYPE_CHECKING: - from .Rom import RomData - -animal_map = { - "Rick Spawn": 0, - "Kine Spawn": 1, - "Coo Spawn": 2, - "Nago Spawn": 3, - "ChuChu Spawn": 4, - "Pitch Spawn": 5 -} - - -class KDL3Room(Region): - pointer: int = 0 - level: int = 0 - stage: int = 0 - room: int = 0 - music: int = 0 - default_exits: typing.List[typing.Dict[str, typing.Union[int, typing.List[str]]]] - animal_pointers: typing.List[int] - enemies: typing.List[str] - entity_load: typing.List[typing.List[int]] - consumables: typing.List[typing.Dict[str, typing.Union[int, str]]] - - def __init__(self, name, player, multiworld, hint, level, stage, room, pointer, music, default_exits, - animal_pointers, enemies, entity_load, consumables, consumable_pointer): - super().__init__(name, player, multiworld, hint) - self.level = level - self.stage = stage - self.room = room - self.pointer = pointer - self.music = music - self.default_exits = default_exits - self.animal_pointers = animal_pointers - self.enemies = enemies - self.entity_load = entity_load - self.consumables = consumables - self.consumable_pointer = consumable_pointer - - def patch(self, rom: "RomData"): - rom.write_byte(self.pointer + 2, self.music) - animals = [x.item.name for x in self.locations if "Animal" in x.name] - if len(animals) > 0: - for current_animal, address in zip(animals, self.animal_pointers): - rom.write_byte(self.pointer + address + 7, animal_map[current_animal]) - if self.multiworld.worlds[self.player].options.consumables: - load_len = len(self.entity_load) - for consumable in self.consumables: - location = next(x for x in self.locations if x.name == consumable["name"]) - assert location.item - is_progression = location.item.classification & ItemClassification.progression - if load_len == 8: - # edge case, there is exactly 1 room with 8 entities and only 1 consumable among them - if not (any(x in self.entity_load for x in [[0, 22], [1, 22]]) - and any(x in self.entity_load for x in [[2, 22], [3, 22]])): - replacement_target = self.entity_load.index( - next(x for x in self.entity_load if x in [[0, 22], [1, 22], [2, 22], [3, 22]])) - if is_progression: - vtype = 0 - else: - vtype = 2 - rom.write_byte(self.pointer + 88 + (replacement_target * 2), vtype) - self.entity_load[replacement_target] = [vtype, 22] - else: - if is_progression: - # we need to see if 1-ups are in our load list - if any(x not in self.entity_load for x in [[0, 22], [1, 22]]): - self.entity_load.append([0, 22]) - else: - if any(x not in self.entity_load for x in [[2, 22], [3, 22]]): - # edge case: if (1, 22) is in, we need to load (3, 22) instead - if [1, 22] in self.entity_load: - self.entity_load.append([3, 22]) - else: - self.entity_load.append([2, 22]) - if load_len < len(self.entity_load): - rom.write_bytes(self.pointer + 88 + (load_len * 2), bytes(self.entity_load[load_len])) - rom.write_bytes(self.pointer + 104 + (load_len * 2), - bytes(struct.pack("H", self.consumable_pointer))) - if is_progression: - if [1, 22] in self.entity_load: - vtype = 1 - else: - vtype = 0 - else: - if [3, 22] in self.entity_load: - vtype = 3 - else: - vtype = 2 - rom.write_byte(self.pointer + consumable["pointer"] + 7, vtype) diff --git a/worlds/kdl3/__init__.py b/worlds/kdl3/__init__.py index 8c9f3cc4..12f56a02 100644 --- a/worlds/kdl3/__init__.py +++ b/worlds/kdl3/__init__.py @@ -1,25 +1,25 @@ import logging -import typing -from BaseClasses import Tutorial, ItemClassification, MultiWorld +from BaseClasses import Tutorial, ItemClassification, MultiWorld, CollectionState, Item from Fill import fill_restrictive from Options import PerGameCommonOptions from worlds.AutoWorld import World, WebWorld -from .Items import item_table, item_names, copy_ability_table, animal_friend_table, filler_item_weights, KDL3Item, \ - trap_item_table, copy_ability_access_table, star_item_weights, total_filler_weights -from .Locations import location_table, KDL3Location, level_consumables, consumable_locations, star_locations -from .Names.AnimalFriendSpawns import animal_friend_spawns -from .Names.EnemyAbilities import vanilla_enemies, enemy_mapping, enemy_restrictive -from .Regions import create_levels, default_levels -from .Options import KDL3Options -from .Presets import kdl3_options_presets -from .Names import LocationName -from .Room import KDL3Room -from .Rules import set_rules -from .Rom import KDL3DeltaPatch, get_base_rom_path, RomData, patch_rom, KDL3JHASH, KDL3UHASH -from .Client import KDL3SNIClient +from .items import item_table, item_names, copy_ability_table, animal_friend_table, filler_item_weights, KDL3Item, \ + trap_item_table, copy_ability_access_table, star_item_weights, total_filler_weights, animal_friend_spawn_table,\ + lookup_item_to_id +from .locations import location_table, KDL3Location, level_consumables, consumable_locations, star_locations +from .names.animal_friend_spawns import animal_friend_spawns, problematic_sets +from .names.enemy_abilities import vanilla_enemies, enemy_mapping, enemy_restrictive +from .regions import create_levels, default_levels +from .options import KDL3Options, kdl3_option_groups +from .presets import kdl3_options_presets +from .names import location_name +from .room import KDL3Room +from .rules import set_rules +from .rom import KDL3ProcedurePatch, get_base_rom_path, patch_rom, KDL3JHASH, KDL3UHASH +from .client import KDL3SNIClient -from typing import Dict, TextIO, Optional, List +from typing import Dict, TextIO, Optional, List, Any, Mapping, ClassVar, Type import os import math import threading @@ -53,6 +53,7 @@ class KDL3WebWorld(WebWorld): ) ] options_presets = kdl3_options_presets + option_groups = kdl3_option_groups class KDL3World(World): @@ -61,35 +62,35 @@ class KDL3World(World): """ game = "Kirby's Dream Land 3" - options_dataclass: typing.ClassVar[typing.Type[PerGameCommonOptions]] = KDL3Options + options_dataclass: ClassVar[Type[PerGameCommonOptions]] = KDL3Options options: KDL3Options - item_name_to_id = {item: item_table[item].code for item in item_table} + item_name_to_id = lookup_item_to_id location_name_to_id = {location_table[location]: location for location in location_table} item_name_groups = item_names web = KDL3WebWorld() - settings: typing.ClassVar[KDL3Settings] + settings: ClassVar[KDL3Settings] def __init__(self, multiworld: MultiWorld, player: int): - self.rom_name = None + self.rom_name: bytes = bytes() self.rom_name_available_event = threading.Event() super().__init__(multiworld, player) self.copy_abilities: Dict[str, str] = vanilla_enemies.copy() self.required_heart_stars: int = 0 # we fill this during create_items - self.boss_requirements: Dict[int, int] = dict() + self.boss_requirements: List[int] = [] self.player_levels = default_levels.copy() self.stage_shuffle_enabled = False - self.boss_butch_bosses: List[Optional[bool]] = list() - self.rooms: Optional[List[KDL3Room]] = None - - @classmethod - def stage_assert_generate(cls, multiworld: MultiWorld) -> None: - rom_file: str = get_base_rom_path() - if not os.path.exists(rom_file): - raise FileNotFoundError(f"Could not find base ROM for {cls.game}: {rom_file}") + self.boss_butch_bosses: List[Optional[bool]] = [] + self.rooms: List[KDL3Room] = [] create_regions = create_levels - def create_item(self, name: str, force_non_progression=False) -> KDL3Item: + def generate_early(self) -> None: + if self.options.total_heart_stars != -1: + logger.warning(f"Kirby's Dream Land 3 ({self.player_name}): Use of \"total_heart_stars\" is deprecated. " + f"Please use \"max_heart_stars\" instead.") + self.options.max_heart_stars.value = self.options.total_heart_stars.value + + def create_item(self, name: str, force_non_progression: bool = False) -> KDL3Item: item = item_table[name] classification = ItemClassification.filler if item.progression and not force_non_progression: @@ -99,7 +100,7 @@ class KDL3World(World): classification = ItemClassification.trap return KDL3Item(name, classification, item.code, self.player) - def get_filler_item_name(self, include_stars=True) -> str: + def get_filler_item_name(self, include_stars: bool = True) -> str: if include_stars: return self.random.choices(list(total_filler_weights.keys()), weights=list(total_filler_weights.values()))[0] @@ -112,8 +113,8 @@ class KDL3World(World): self.options.slow_trap_weight.value, self.options.ability_trap_weight.value])[0] - def get_restrictive_copy_ability_placement(self, copy_ability: str, enemies_to_set: typing.List[str], - level: int, stage: int): + def get_restrictive_copy_ability_placement(self, copy_ability: str, enemies_to_set: List[str], + level: int, stage: int) -> Optional[str]: valid_rooms = [room for room in self.rooms if (room.level < level) or (room.level == level and room.stage < stage)] # leave out the stage in question to avoid edge valid_enemies = set() @@ -124,6 +125,10 @@ class KDL3World(World): return None # a valid enemy got placed by a more restrictive placement return self.random.choice(sorted([enemy for enemy in valid_enemies if enemy not in placed_enemies])) + def get_pre_fill_items(self) -> List[Item]: + return [self.create_item(item) + for item in [*copy_ability_access_table.keys(), *animal_friend_spawn_table.keys()]] + def pre_fill(self) -> None: if self.options.copy_ability_randomization: # randomize copy abilities @@ -207,10 +212,32 @@ class KDL3World(World): # If Kine is ever the last animal friend placed, he will cause fill errors on closed world animal_pool.sort() locations = [self.multiworld.get_location(spawn, self.player) for spawn in spawns] - items = [self.create_item(animal) for animal in animal_pool] - allstate = self.multiworld.get_all_state(False) + items: List[Item] = [self.create_item(animal) for animal in animal_pool] + allstate = CollectionState(self.multiworld) + for item in [*copy_ability_table, *animal_friend_table, *["Heart Star" for _ in range(99)]]: + self.collect(allstate, self.create_item(item)) self.random.shuffle(locations) fill_restrictive(self.multiworld, allstate, locations, items, True, True) + + # Need to ensure all of these are unique items, and replace them if they aren't + for spawns in problematic_sets: + placed = [self.get_location(spawn).item for spawn in spawns] + placed_names = set([item.name for item in placed]) + if len(placed_names) != len(placed): + # have a duplicate + animals = [] + for spawn in spawns: + spawn_location = self.get_location(spawn) + if spawn_location.item.name not in animals: + animals.append(spawn_location.item.name) + else: + new_animal = self.random.choice([x for x in ["Rick Spawn", "Coo Spawn", "Kine Spawn", + "ChuChu Spawn", "Nago Spawn", "Pitch Spawn"] + if x not in placed_names and x not in animals]) + spawn_location.item = None + spawn_location.place_locked_item(self.create_item(new_animal)) + animals.append(new_animal) + # logically, this should be sound pre-ER. May need to adjust around it with ER in the future else: animal_friends = animal_friend_spawns.copy() for animal in animal_friends: @@ -225,21 +252,20 @@ class KDL3World(World): remaining_items = len(location_table) - len(itempool) if not self.options.consumables: remaining_items -= len(consumable_locations) - remaining_items -= len(star_locations) - if self.options.starsanity: - # star fill, keep consumable pool locked to consumable and fill 767 stars specifically - star_items = list(star_item_weights.keys()) - star_weights = list(star_item_weights.values()) - itempool.extend([self.create_item(item) for item in self.random.choices(star_items, weights=star_weights, - k=767)]) - total_heart_stars = self.options.total_heart_stars + if not self.options.starsanity: + remaining_items -= len(star_locations) + max_heart_stars = self.options.max_heart_stars.value + if max_heart_stars > remaining_items: + max_heart_stars = remaining_items # ensure at least 1 heart star required per world - required_heart_stars = max(int(total_heart_stars * required_percentage), 5) - filler_items = total_heart_stars - required_heart_stars - filler_amount = math.floor(filler_items * (self.options.filler_percentage / 100.0)) - trap_amount = math.floor(filler_amount * (self.options.trap_percentage / 100.0)) - filler_amount -= trap_amount - non_required_heart_stars = filler_items - filler_amount - trap_amount + required_heart_stars = min(max(int(max_heart_stars * required_percentage), 5), 99) + filler_items = remaining_items - required_heart_stars + converted_heart_stars = math.floor((max_heart_stars - required_heart_stars) * (self.options.filler_percentage / 100.0)) + non_required_heart_stars = max_heart_stars - converted_heart_stars - required_heart_stars + filler_items -= non_required_heart_stars + trap_amount = math.floor(filler_items * (self.options.trap_percentage / 100.0)) + + filler_items -= trap_amount self.required_heart_stars = required_heart_stars # handle boss requirements here requirements = [required_heart_stars] @@ -261,8 +287,8 @@ class KDL3World(World): requirements.insert(i - 1, quotient * i) self.boss_requirements = requirements itempool.extend([self.create_item("Heart Star") for _ in range(required_heart_stars)]) - itempool.extend([self.create_item(self.get_filler_item_name(False)) - for _ in range(filler_amount + (remaining_items - total_heart_stars))]) + itempool.extend([self.create_item(self.get_filler_item_name(bool(self.options.starsanity.value))) + for _ in range(filler_items)]) itempool.extend([self.create_item(self.get_trap_item_name()) for _ in range(trap_amount)]) itempool.extend([self.create_item("Heart Star", True) for _ in range(non_required_heart_stars)]) @@ -273,15 +299,15 @@ class KDL3World(World): self.multiworld.get_location(location_table[self.player_levels[level][stage]] .replace("Complete", "Stage Completion"), self.player) \ .place_locked_item(KDL3Item( - f"{LocationName.level_names_inverse[level]} - Stage Completion", + f"{location_name.level_names_inverse[level]} - Stage Completion", ItemClassification.progression, None, self.player)) set_rules = set_rules def generate_basic(self) -> None: self.stage_shuffle_enabled = self.options.stage_shuffle > 0 - goal = self.options.goal - goal_location = self.multiworld.get_location(LocationName.goals[goal], self.player) + goal = self.options.goal.value + goal_location = self.multiworld.get_location(location_name.goals[goal], self.player) goal_location.place_locked_item(KDL3Item("Love-Love Rod", ItemClassification.progression, None, self.player)) for level in range(1, 6): self.multiworld.get_location(f"Level {level} Boss - Defeated", self.player) \ @@ -300,60 +326,65 @@ class KDL3World(World): else: self.boss_butch_bosses = [False for _ in range(6)] - def generate_output(self, output_directory: str): - rom_path = "" + def generate_output(self, output_directory: str) -> None: try: - rom = RomData(get_base_rom_path()) - patch_rom(self, rom) + patch = KDL3ProcedurePatch() + patch_rom(self, patch) - rom_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc") - rom.write_to_file(rom_path) - self.rom_name = rom.name + self.rom_name = patch.name - patch = KDL3DeltaPatch(os.path.splitext(rom_path)[0] + KDL3DeltaPatch.patch_file_ending, player=self.player, - player_name=self.multiworld.player_name[self.player], patched_path=rom_path) - patch.write() + patch.write(os.path.join(output_directory, + f"{self.multiworld.get_out_file_name_base(self.player)}{patch.patch_file_ending}")) except Exception: raise finally: self.rom_name_available_event.set() # make sure threading continues and errors are collected - if os.path.exists(rom_path): - os.unlink(rom_path) - def modify_multidata(self, multidata: dict): + def modify_multidata(self, multidata: Dict[str, Any]) -> None: # wait for self.rom_name to be available. self.rom_name_available_event.wait() + assert isinstance(self.rom_name, bytes) rom_name = getattr(self, "rom_name", None) # we skip in case of error, so that the original error in the output thread is the one that gets raised if rom_name: - new_name = base64.b64encode(bytes(self.rom_name)).decode() + new_name = base64.b64encode(self.rom_name).decode() multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]] + def fill_slot_data(self) -> Mapping[str, Any]: + # UT support + return {"player_levels": self.player_levels} + + def interpret_slot_data(self, slot_data: Mapping[str, Any]): + # UT support + player_levels = {int(key): value for key, value in slot_data["player_levels"].items()} + return {"player_levels": player_levels} + def write_spoiler(self, spoiler_handle: TextIO) -> None: if self.stage_shuffle_enabled: spoiler_handle.write(f"\nLevel Layout ({self.multiworld.get_player_name(self.player)}):\n") - for level in LocationName.level_names: - for stage, i in zip(self.player_levels[LocationName.level_names[level]], range(1, 7)): + for level in location_name.level_names: + for stage, i in zip(self.player_levels[location_name.level_names[level]], range(1, 7)): spoiler_handle.write(f"{level} {i}: {location_table[stage].replace(' - Complete', '')}\n") if self.options.animal_randomization: spoiler_handle.write(f"\nAnimal Friends ({self.multiworld.get_player_name(self.player)}):\n") - for level in self.player_levels: + for lvl in self.player_levels: for stage in range(6): - rooms = [room for room in self.rooms if room.level == level and room.stage == stage] + rooms = [room for room in self.rooms if room.level == lvl and room.stage == stage] animals = [] for room in rooms: animals.extend([location.item.name.replace(" Spawn", "") - for location in room.locations if "Animal" in location.name]) - spoiler_handle.write(f"{location_table[self.player_levels[level][stage]].replace(' - Complete','')}" + for location in room.locations if "Animal" in location.name + and location.item is not None]) + spoiler_handle.write(f"{location_table[self.player_levels[lvl][stage]].replace(' - Complete','')}" f": {', '.join(animals)}\n") if self.options.copy_ability_randomization: spoiler_handle.write(f"\nCopy Abilities ({self.multiworld.get_player_name(self.player)}):\n") for enemy in self.copy_abilities: spoiler_handle.write(f"{enemy}: {self.copy_abilities[enemy].replace('No Ability', 'None').replace(' Ability', '')}\n") - def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): + def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]) -> None: if self.stage_shuffle_enabled: - regions = {LocationName.level_names[level]: level for level in LocationName.level_names} + regions = {location_name.level_names[level]: level for level in location_name.level_names} level_hint_data = {} for level in regions: for stage in range(7): @@ -361,6 +392,6 @@ class KDL3World(World): self.player).name.replace(" - Complete", "") stage_regions = [room for room in self.rooms if stage_name in room.name] for region in stage_regions: - for location in [location for location in region.locations if location.address]: + for location in [location for location in list(region.get_locations()) if location.address]: level_hint_data[location.address] = f"{regions[level]} {stage + 1 if stage < 6 else 'Boss'}" hint_data[self.player] = level_hint_data diff --git a/worlds/kdl3/Aesthetics.py b/worlds/kdl3/aesthetics.py similarity index 91% rename from worlds/kdl3/Aesthetics.py rename to worlds/kdl3/aesthetics.py index 8c736390..8b798ff9 100644 --- a/worlds/kdl3/Aesthetics.py +++ b/worlds/kdl3/aesthetics.py @@ -1,5 +1,9 @@ import struct -from .Options import KirbyFlavorPreset, GooeyFlavorPreset +from .options import KirbyFlavorPreset, GooeyFlavorPreset +from typing import TYPE_CHECKING, Optional, Dict, List, Tuple + +if TYPE_CHECKING: + from . import KDL3World kirby_flavor_presets = { 1: { @@ -223,6 +227,23 @@ kirby_flavor_presets = { "14": "E6E6FA", "15": "976FBD", }, + 14: { + "1": "373B3E", + "2": "98d5d3", + "3": "1aa5ab", + "4": "168f95", + "5": "4f5559", + "6": "1dbac2", + "7": "137a7f", + "8": "093a3c", + "9": "86cecb", + "10": "a0afbc", + "11": "62bfbb", + "12": "50b8b4", + "13": "bec8d1", + "14": "bce4e2", + "15": "91a2b1", + } } gooey_flavor_presets = { @@ -398,21 +419,21 @@ gooey_target_palettes = { } -def get_kirby_palette(world): +def get_kirby_palette(world: "KDL3World") -> Optional[Dict[str, str]]: palette = world.options.kirby_flavor_preset.value if palette == KirbyFlavorPreset.option_custom: return world.options.kirby_flavor.value return kirby_flavor_presets.get(palette, None) -def get_gooey_palette(world): +def get_gooey_palette(world: "KDL3World") -> Optional[Dict[str, str]]: palette = world.options.gooey_flavor_preset.value if palette == GooeyFlavorPreset.option_custom: return world.options.gooey_flavor.value return gooey_flavor_presets.get(palette, None) -def rgb888_to_bgr555(red, green, blue) -> bytes: +def rgb888_to_bgr555(red: int, green: int, blue: int) -> bytes: red = red >> 3 green = green >> 3 blue = blue >> 3 @@ -420,15 +441,15 @@ def rgb888_to_bgr555(red, green, blue) -> bytes: return struct.pack("H", outcol) -def get_palette_bytes(palette, target, offset, factor): +def get_palette_bytes(palette: Dict[str, str], target: List[str], offset: int, factor: float) -> bytes: output_data = bytearray() for color in target: hexcol = palette[color] if hexcol.startswith("#"): hexcol = hexcol.replace("#", "") colint = int(hexcol, 16) - col = ((colint & 0xFF0000) >> 16, (colint & 0xFF00) >> 8, colint & 0xFF) + col: Tuple[int, ...] = ((colint & 0xFF0000) >> 16, (colint & 0xFF00) >> 8, colint & 0xFF) col = tuple(int(int(factor*x) + offset) for x in col) byte_data = rgb888_to_bgr555(col[0], col[1], col[2]) output_data.extend(bytearray(byte_data)) - return output_data + return bytes(output_data) diff --git a/worlds/kdl3/Client.py b/worlds/kdl3/client.py similarity index 90% rename from worlds/kdl3/Client.py rename to worlds/kdl3/client.py index 1ca21d55..97bf68cb 100644 --- a/worlds/kdl3/Client.py +++ b/worlds/kdl3/client.py @@ -11,13 +11,13 @@ from MultiServer import mark_raw from NetUtils import ClientStatus, color from Utils import async_start from worlds.AutoSNIClient import SNIClient -from .Locations import boss_locations -from .Gifting import kdl3_gifting_options, kdl3_trap_gifts, kdl3_gifts, update_object, pop_object, initialize_giftboxes -from .ClientAddrs import consumable_addrs, star_addrs +from .locations import boss_locations +from .gifting import kdl3_gifting_options, kdl3_trap_gifts, kdl3_gifts, update_object, pop_object, initialize_giftboxes +from .client_addrs import consumable_addrs, star_addrs from typing import TYPE_CHECKING if TYPE_CHECKING: - from SNIClient import SNIClientCommandProcessor + from SNIClient import SNIClientCommandProcessor, SNIContext snes_logger = logging.getLogger("SNES") @@ -81,17 +81,16 @@ deathlink_messages = defaultdict(lambda: " was defeated.", { @mark_raw -def cmd_gift(self: "SNIClientCommandProcessor"): +def cmd_gift(self: "SNIClientCommandProcessor") -> None: """Toggles gifting for the current game.""" - if not getattr(self.ctx, "gifting", None): - self.ctx.gifting = True - else: - self.ctx.gifting = not self.ctx.gifting - self.output(f"Gifting set to {self.ctx.gifting}") + handler = self.ctx.client_handler + assert isinstance(handler, KDL3SNIClient) + handler.gifting = not handler.gifting + self.output(f"Gifting set to {handler.gifting}") async_start(update_object(self.ctx, f"Giftboxes;{self.ctx.team}", { f"{self.ctx.slot}": { - "IsOpen": self.ctx.gifting, + "IsOpen": handler.gifting, **kdl3_gifting_options } })) @@ -100,16 +99,17 @@ def cmd_gift(self: "SNIClientCommandProcessor"): class KDL3SNIClient(SNIClient): game = "Kirby's Dream Land 3" patch_suffix = ".apkdl3" - levels = None - consumables = None - stars = None - item_queue: typing.List = [] - initialize_gifting = False + levels: typing.Dict[int, typing.List[int]] = {} + consumables: typing.Optional[bool] = None + stars: typing.Optional[bool] = None + item_queue: typing.List[int] = [] + initialize_gifting: bool = False + gifting: bool = False giftbox_key: str = "" motherbox_key: str = "" client_random: random.Random = random.Random() - async def deathlink_kill_player(self, ctx) -> None: + async def deathlink_kill_player(self, ctx: "SNIContext") -> None: from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read game_state = await snes_read(ctx, KDL3_GAME_STATE, 1) if game_state[0] == 0xFF: @@ -131,7 +131,7 @@ class KDL3SNIClient(SNIClient): ctx.death_state = DeathState.dead ctx.last_death_link = time.time() - async def validate_rom(self, ctx) -> bool: + async def validate_rom(self, ctx: "SNIContext") -> bool: from SNIClient import snes_read rom_name = await snes_read(ctx, KDL3_ROMNAME, 0x15) if rom_name is None or rom_name == bytes([0] * 0x15) or rom_name[:4] != b"KDL3": @@ -141,7 +141,7 @@ class KDL3SNIClient(SNIClient): ctx.game = self.game ctx.rom = rom_name - ctx.items_handling = 0b111 # always remote items + ctx.items_handling = 0b101 # default local items with remote start inventory ctx.allow_collect = True if "gift" not in ctx.command_processor.commands: ctx.command_processor.commands["gift"] = cmd_gift @@ -149,9 +149,10 @@ class KDL3SNIClient(SNIClient): death_link = await snes_read(ctx, KDL3_DEATH_LINK_ADDR, 1) if death_link: await ctx.update_death_link(bool(death_link[0] & 0b1)) + ctx.items_handling |= (death_link[0] & 0b10) # set local items if enabled return True - async def pop_item(self, ctx, in_stage): + async def pop_item(self, ctx: "SNIContext", in_stage: bool) -> None: from SNIClient import snes_buffered_write, snes_read if len(self.item_queue) > 0: item = self.item_queue.pop() @@ -168,8 +169,8 @@ class KDL3SNIClient(SNIClient): else: self.item_queue.append(item) # no more slots, get it next go around - async def pop_gift(self, ctx): - if ctx.stored_data[self.giftbox_key]: + async def pop_gift(self, ctx: "SNIContext") -> None: + if self.giftbox_key in ctx.stored_data and ctx.stored_data[self.giftbox_key]: from SNIClient import snes_read, snes_buffered_write key, gift = ctx.stored_data[self.giftbox_key].popitem() await pop_object(ctx, self.giftbox_key, key) @@ -214,7 +215,7 @@ class KDL3SNIClient(SNIClient): quality = min(10, quality * 2) else: # it's not really edible, but he'll eat it anyway - quality = self.client_random.choices(range(0, 2), {0: 75, 1: 25})[0] + quality = self.client_random.choices(range(0, 2), [75, 25])[0] kirby_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1) gooey_hp = await snes_read(ctx, KDL3_KIRBY_HP + 2, 1) snes_buffered_write(ctx, KDL3_SOUND_FX, bytes([0x26])) @@ -224,7 +225,8 @@ class KDL3SNIClient(SNIClient): else: snes_buffered_write(ctx, KDL3_KIRBY_HP, struct.pack("H", min(kirby_hp[0] + quality, 10))) - async def pick_gift_recipient(self, ctx, gift): + async def pick_gift_recipient(self, ctx: "SNIContext", gift: int) -> None: + assert ctx.slot if gift != 4: gift_base = kdl3_gifts[gift] else: @@ -238,7 +240,7 @@ class KDL3SNIClient(SNIClient): if desire > most_applicable: most_applicable = desire most_applicable_slot = int(slot) - elif most_applicable_slot == ctx.slot and info["AcceptsAnyGift"]: + elif most_applicable_slot != ctx.slot and most_applicable == -1 and info["AcceptsAnyGift"]: # only send to ourselves if no one else will take it most_applicable_slot = int(slot) # print(most_applicable, most_applicable_slot) @@ -257,7 +259,7 @@ class KDL3SNIClient(SNIClient): item_uuid: item, }) - async def game_watcher(self, ctx) -> None: + async def game_watcher(self, ctx: "SNIContext") -> None: try: from SNIClient import snes_buffered_write, snes_flush_writes, snes_read rom = await snes_read(ctx, KDL3_ROMNAME, 0x15) @@ -278,11 +280,12 @@ class KDL3SNIClient(SNIClient): await initialize_giftboxes(ctx, self.giftbox_key, self.motherbox_key, bool(enable_gifting[0])) self.initialize_gifting = True # can't check debug anymore, without going and copying the value. might be important later. - if self.levels is None: + if not self.levels: self.levels = dict() for i in range(5): level_data = await snes_read(ctx, KDL3_LEVEL_ADDR + (14 * i), 14) - self.levels[i] = unpack("HHHHHHH", level_data) + self.levels[i] = [int.from_bytes(level_data[idx:idx+1], "little") + for idx in range(0, len(level_data), 2)] self.levels[5] = [0x0205, # Hyper Zone 0, # MG-5, can't send from here 0x0300, # Boss Butch @@ -371,7 +374,7 @@ class KDL3SNIClient(SNIClient): stages_raw = await snes_read(ctx, KDL3_COMPLETED_STAGES, 60) stages = struct.unpack("HHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", stages_raw) for i in range(30): - loc_id = 0x770000 + i + 1 + loc_id = 0x770000 + i if stages[i] == 1 and loc_id not in ctx.checked_locations: new_checks.append(loc_id) elif loc_id in ctx.checked_locations: @@ -381,8 +384,8 @@ class KDL3SNIClient(SNIClient): heart_stars = await snes_read(ctx, KDL3_HEART_STARS, 35) for i in range(5): start_ind = i * 7 - for j in range(1, 7): - level_ind = start_ind + j - 1 + for j in range(6): + level_ind = start_ind + j loc_id = 0x770100 + (6 * i) + j if heart_stars[level_ind] and loc_id not in ctx.checked_locations: new_checks.append(loc_id) @@ -401,6 +404,9 @@ class KDL3SNIClient(SNIClient): if star not in ctx.checked_locations and stars[star_addrs[star]] == 0x01: new_checks.append(star) + if not game_state: + return + if game_state[0] != 0xFF: await self.pop_gift(ctx) await self.pop_item(ctx, game_state[0] != 0xFF) @@ -408,7 +414,7 @@ class KDL3SNIClient(SNIClient): # boss status boss_flag_bytes = await snes_read(ctx, KDL3_BOSS_STATUS, 2) - boss_flag = unpack("H", boss_flag_bytes)[0] + boss_flag = int.from_bytes(boss_flag_bytes, "little") for bitmask, boss in zip(range(1, 11, 2), boss_locations.keys()): if boss_flag & (1 << bitmask) > 0 and boss not in ctx.checked_locations: new_checks.append(boss) diff --git a/worlds/kdl3/ClientAddrs.py b/worlds/kdl3/client_addrs.py similarity index 100% rename from worlds/kdl3/ClientAddrs.py rename to worlds/kdl3/client_addrs.py diff --git a/worlds/kdl3/Compression.py b/worlds/kdl3/compression.py similarity index 100% rename from worlds/kdl3/Compression.py rename to worlds/kdl3/compression.py diff --git a/worlds/kdl3/data/kdl3_basepatch.bsdiff4 b/worlds/kdl3/data/kdl3_basepatch.bsdiff4 index cd002121cd38dcb319ba2148ced46c9592c3905b..3b6b338d5a92ccec33693c644e73d13b15dbce46 100644 GIT binary patch literal 2646 zcmYM0dpOgJAIHBl&6Kej!zPWGSx$zL4oA0;#E^`}G z@=Gp-3TKo{ooTC7L@L*Gq;yi^*YEc`&vV{?z5n>UKcDCGJg-0AbdHC&rzeH{2Kc{X zH2yXK^?!yQ9O-0F+e_f=jTypO06^2~>i5L)jaPT-!TP8~5bprsJJ2K&m9<6(4@f}` zD%gPs#A4Yen`uNDjAu-n0k~086>S5|$GxE~mD}}?7IDIl4BykQJlEt}6XDl0XT8RELEq%`#bdEK+LHA#HGWeP9E<)u zECB4mR%{!8&#GRlk7$E(ssH@w_mH*`0JK+ES65 zp@E6|s252Q>pLLr2(f}gDCaX6NIfznGADBcI)&xZAnzv{WrtiK{1w=hX(J&cNtU*h7t_FblQN0)u4hU!@}JkY2Yn zW5a5{jF&sxdrVLRDgy&?pHQ0Aq}}4?91l%Er@a|$J{h?AJEw)&B&qn&JTaPB_cql> zt8IO7&e=0NGBjL`g&DP@;cf;#z`~IdZYbK>CGJ6B@w`111dAQ%mNnSgdhfB^DgV!1 zs?G;jsvR0(oBVC3%GVrT^viXL67Q!@hcFl(UT+Q@+Jn+6(;IE+HrrG35FI46&1Mc4 zpZ!e>TpG}-mMz6^((#GLHT;=UC$zPHRg^csLEszPe^?s3W5KOzDM5mDi5t6<{7!jD znATy$mhWjT!P_G#{#XO#ah@{ND$@&NFdYzV&g9=XeFxgAU>Gv93Nk29FnPX>`!=R9 z+fxurxovB1YC-2f5GMQ}Qh!wAt(?pTK7ZoKLq?>74T*oz?GO5knxXqMz z@EUPIlL~CMN|@kyXEjJ(t@J&6HZp9B5_n=7*%2WZSE{h)1i z^smKlNlVKQCC7Nw_*MQ`(#O>?!k5*RuS=^*ZA#7WBaw?QtG3*pd;N#atQuRW@2}4- z;Sd(LXFZ0Ma31l|3^hNkz}-teM|aM7Q^?Qa_{sCAi&!j7Aa#}k6j)j^tpaOZ12}D` z*|f`F`*D!kGjQsJ2f{urRi(h8$w0x1HZM8c?8guizs=aD~=pDuCV z$S^OHE+XVor4k^6k${q4@FNcc`5-`n%;iV7oe+M`GWB5DTgrVdg`eE_ zU#{^mK?Wp82CAq^0LT6Ty-o=`c>dH?suhNk+tn-Z4mVD&8$8Brt$~A}FWkOz@%VFn+`jcOEv4QV_d_}_nk<`slcqooyk!P!&2)P%Ke^#D zKs2w;yuf$JSkPz|yaCqTfajf&CPrCQ<(^B2WN5}&R%#PV%D(o)F9j3XU7Div3}2(` zcIFS)loZ#{A9yx#>;G-{?lNs;uCR!iYX0~XG5uFtJHTe(Ad~+*Ix6R?xkmWeKgqYD)%X;5R2k*%~<#dPK z=?a$|Wu{u%xqi5)Th}n;do8{_*XQt1MH84ijR@n4ZgO1<(Dl%%Zo0uMsntmX2;S6G z8`Vw!D0+ zx!`ez;#SmEpoO>VYAUo(C>PzqVdnx9n&a!lq=fbhlbJL5givj#2x_}}AnRa5MEb3_ zsGo1mjy2lVm6`|(>q^l6G;)y^#9Gq^j>tSkZ%fS>30OO0JMCxRqYa#QcRE{8+szc$Z)=&dkoW^q?=@5$^ddWPD&{$Ub4B;ZY;V#QXI+%7K)qLgA5paUnt;t7c2}+4 z*MBUvpjsxrejuPC^BvDiu>{j40J=}gA>qr}sDoS2qlV(g7ud@KJ?7Eqr#5YSnGzqO z@0i%A2Zu3>Yj?wb&OM3Q@0f1BU@f3~BXaQ9qk~&W5_6-&h7r=oJ9L{ZW&XQfq|Vpp zH1E3L(bv^Zt*{SB)mR3$63}o9H)1bipyc$yHjVo{F=2hZ|H-3Nt*xI2?YhF;M?H!w z_NmH0R_l%B?;ws=M@B|J$!_Q<-EQa`RiEwGTWr1Y*A~;*fh!k|qjU*|x#jmKZZ1B5 zjeL=Qjy^Wx#zJUsg&~ieaWOI^{;Q@lt|}nwr2b!Amo^4A?f9CsE@)>#y6Dr>uDhIt z2_5$6OY*35T~y$)m91>E{)p>)vxy|c4n zCKdgCkQ?huXfcGd}N6;FvkjXAL;gnmys4Y_&g}csYI|(;6OK z_)NZMq<3svP+R|tWY#Wr<-(J;YR6`ivf9t)=9Lwu_y|7@x-?lxY?#AT8lH`fxOr~- zAwg2shUSE4rojqLdMY=6l;q?WU>!T!rKAP*T3!cEWYw9siltk0O;XHP{_4E<(w$3s zIV7X9P?$@}7eraQVqv`Y^20_guoMnD@;GV2``wws$7~iU%#_3K0OguOa!Mn{snaLS z*fXe-UX6=H%X#0@%Pz*ffZrW{c6VoQ;iTDHfg&3#(!ob0HiXZvT3NBYwm501xcTwS zX6KP>o8YAjmMR?uCF0!icgxn~bu}v+$}jB?Tm_+0%m~OpqL9; zgG*x3sO6egnoELZ;?km(d8cKxS*|&Vnx(hy+;i@|&xiLs=lSxU=fm^!Vt5Dm`hvt+ z;4gK-|6>5szm3DJ=B#sdog|1`*Kw8fF$CWo*=8AqXpfU|e7&|Bs0sy8YKnsBc zm>6V=A%Fq+ZVMIWpnBYELjd0cMUxQeD198#!~;>sqeG-BXGAWHu3hEfr(Gi1$doc| zpSI_x$dgUvB4++zM!@a*%k=bM@y~y}wYo-Jl|H#%BY8AW5K*Uca|8T~XpJ#MyNU_DdEA+EuYv#eq;K#4(i54p{zcC}udOe=f6&mZ$@2AF zTgYkrb4gR9UR`T`m2>Jx6>Jx;;lZ`GhaJL%2^I=;){!{t+%N}W1TIL#o#SEp?51b{ z4uH7UC_r4HYgL{H1)k|bOc*UrKy73!f{fwd9={T{??rD#5`WRdh%BpDDY@*+b>vp< z`b7jCagl)?1rQQ*1cw76D!_i%8Vc?5W2CIg$avAoRU9x^G3` zP@e*TXi@K%|5z*qvyf}y?+hvq1uH;6g(9BI&qe5nlBDApl^F;m#G5BTxMnExL?AH{ zkx$D70HsucrczylVH1i(6o4oM5MRRo*G5Faxhre41I{LKZ|ulRHaLBJJn=Km%pPu9 z1=p$IOgbUXqbmSeFIxLYT_IZ1H#{}Ng%vo4He9H@uD&(& zVwc4me!yOg)QwLv4rht&nj)A@Hc|Xgd&+Z16y&Xxh&|+!dyxM}e0n=P`*T|JGC1HC z(y}L4-?x8odsO?tCC0|889cbB{(c5Pj!#KWEtRqkwb@So5&kgf4ECRlEQW%ziqg-x z%RDJh$5b!y$Uw23ayUAFEg#bYMIyjQ33n{F<1Jcd*b+OVJZA1ddZ8T5gS zxyy_z9k*)bvzxA7G&J+S4DHX(LHo7dv)TTc=Nnn2GvBkHYVlr}7E1SQ>6lL#83ymI z?Q4zK{I(aBk8LCwq}!}vTn|5I?dn^o%YIN~XyxTn5Rw!-Y`$yKSKLxaXmamL{wSU0 zdVLolmu>40eVQ>DR!09dGUz5}w4rpROMiPjWyh79sGyxJ`EuRgLG31o*-$NBX$fuG za;Y(+FX_|MYH8o<@*S7!Jo?4C*mt=ve8zQ=o`*<$3>rt9(Rc7C-*xPm>&wpS)5(v^ z1-EfJ9eQ~`h6N6M>V6(vu3l^WNp8=s9T>4|d2f8+=?k>P4tbxujzqmN>bE#A6zRDU z3HaE{TbmM0g!E=Bt7M4Uv^LvbujKrVLd!7fh2RyLZm}cl+j7H|cIo8YpWoCHQ#ER%;T6_oW#6i%%V-;ZL4Psb{7J-u=k5C zg?rB^;k1y){<{JrnH9!Gnxw8LkpZjUrEKfsM!e&;No4ahLu)_o`Lgzavn!QbZfhRS z4MiE8vNWHrhNSI7$oz&Xhty0C4A#QOw@Y|Nf--d0;Pt69oMQbzQ}X=Q;2njhin-7$ zUMGhp@PTf#0$_J)x7Y*@z&w8)Ijn1Cg?L!ubEhLb_DIWm!7bZLyg#lip7=lLIu_qX z10e2S$(8=<&%-OH52*c1niEMUMi2HJdA8nzTV_8?ozKZB>VaNrIL@d5&Fe4{BVP#$ zNPy8KaiDb#9R)z@=zpz#gEy#fVf14eTk7gqJG~Kw`e*CS&A-B%1OOlaK%|L^agvZt zp&p}*!C2M;F|N=&Q31pH=93h_Xx-PVLSR%X6_gQN^uA76%x1CkFV{?1*Gx=Fl0?8F zpn^>}1B);Wph~pB1CW~2lXy!g?Pd6*Z{y@tWQ)IeCrLR^%~*P#3K!Lrz!0o4)P>*W zu0+$J>pQLy5lBb3U|vbvO_v|c5m!4c_bJi*punIMPrB>%n-ug;_|#^-dJpy)YMNkt z5JI}=U3&m88bDl+o45+T!xCSdjsfvw`D}y4tO7DK*n7c(HTemyyMNr|qNV!km;l zD=Z8G%0l9t1$+K+>H7Fwx8T%ZRoC1Y-G7(*y*0~ZhL)-6R5U`o(>T}cAKQT zRtbg8OXaHZ5nM!DZPkiiP<_LNxAaR#b*!V>Q*U5nx^~mf4!^kFc%+l}dF9*RG!jBb zvb85+_N+c=y0rLu=!q$ALgMamYM*$;r{)Lw2CCO_BMrBuRQ5u?dAL&#EkX$&KO;a4 zqWV9SHwD|yciRCG;9=O}?eCBAz&T_%>TCmJq#`CHzNk0+)=`2D3~IYYg#R#32Q zdGj(IQ*ak^;KMv`c2ct3Q~UTG@knc+R)9ef(oshGCTk~_x- R2S2ZGPW5pM7JXgjzX0o>8Uz3U diff --git a/worlds/kdl3/Gifting.py b/worlds/kdl3/gifting.py similarity index 90% rename from worlds/kdl3/Gifting.py rename to worlds/kdl3/gifting.py index 8ccba7ec..e1626091 100644 --- a/worlds/kdl3/Gifting.py +++ b/worlds/kdl3/gifting.py @@ -1,8 +1,11 @@ # Small subfile to handle gifting info such as desired traits and giftbox management import typing +if typing.TYPE_CHECKING: + from SNIClient import SNIContext -async def update_object(ctx, key: str, value: typing.Dict): + +async def update_object(ctx: "SNIContext", key: str, value: typing.Dict[str, typing.Any]) -> None: await ctx.send_msgs([ { "cmd": "Set", @@ -16,7 +19,7 @@ async def update_object(ctx, key: str, value: typing.Dict): ]) -async def pop_object(ctx, key: str, value: str): +async def pop_object(ctx: "SNIContext", key: str, value: str) -> None: await ctx.send_msgs([ { "cmd": "Set", @@ -30,14 +33,14 @@ async def pop_object(ctx, key: str, value: str): ]) -async def initialize_giftboxes(ctx, giftbox_key: str, motherbox_key: str, is_open: bool): +async def initialize_giftboxes(ctx: "SNIContext", giftbox_key: str, motherbox_key: str, is_open: bool) -> None: ctx.set_notify(motherbox_key, giftbox_key) await update_object(ctx, f"Giftboxes;{ctx.team}", {f"{ctx.slot}": - { - "IsOpen": is_open, - **kdl3_gifting_options - }}) - ctx.gifting = is_open + { + "IsOpen": is_open, + **kdl3_gifting_options + }}) + ctx.client_handler.gifting = is_open kdl3_gifting_options = { diff --git a/worlds/kdl3/Items.py b/worlds/kdl3/items.py similarity index 95% rename from worlds/kdl3/Items.py rename to worlds/kdl3/items.py index 66c7f8fe..72687a60 100644 --- a/worlds/kdl3/Items.py +++ b/worlds/kdl3/items.py @@ -77,9 +77,9 @@ filler_item_weights = { } star_item_weights = { - "Little Star": 4, - "Medium Star": 2, - "Big Star": 1 + "Little Star": 16, + "Medium Star": 8, + "Big Star": 4 } total_filler_weights = { @@ -102,4 +102,4 @@ item_names = { "Animal Friend": set(animal_friend_table), } -lookup_name_to_id: typing.Dict[str, int] = {item_name: data.code for item_name, data in item_table.items() if data.code} +lookup_item_to_id: typing.Dict[str, int] = {item_name: data.code for item_name, data in item_table.items() if data.code} diff --git a/worlds/kdl3/locations.py b/worlds/kdl3/locations.py new file mode 100644 index 00000000..4fa1bfad --- /dev/null +++ b/worlds/kdl3/locations.py @@ -0,0 +1,940 @@ +import typing +from BaseClasses import Location, Region +from .names import location_name + +if typing.TYPE_CHECKING: + from .room import KDL3Room + + +class KDL3Location(Location): + game: str = "Kirby's Dream Land 3" + room: typing.Optional["KDL3Room"] = None + + def __init__(self, player: int, name: str, address: typing.Optional[int], parent: typing.Union[Region, None]): + super().__init__(player, name, address, parent) + if not address: + self.show_in_spoiler = False + + +stage_locations = { + 0x770000: location_name.grass_land_1, + 0x770001: location_name.grass_land_2, + 0x770002: location_name.grass_land_3, + 0x770003: location_name.grass_land_4, + 0x770004: location_name.grass_land_5, + 0x770005: location_name.grass_land_6, + 0x770006: location_name.ripple_field_1, + 0x770007: location_name.ripple_field_2, + 0x770008: location_name.ripple_field_3, + 0x770009: location_name.ripple_field_4, + 0x77000A: location_name.ripple_field_5, + 0x77000B: location_name.ripple_field_6, + 0x77000C: location_name.sand_canyon_1, + 0x77000D: location_name.sand_canyon_2, + 0x77000E: location_name.sand_canyon_3, + 0x77000F: location_name.sand_canyon_4, + 0x770010: location_name.sand_canyon_5, + 0x770011: location_name.sand_canyon_6, + 0x770012: location_name.cloudy_park_1, + 0x770013: location_name.cloudy_park_2, + 0x770014: location_name.cloudy_park_3, + 0x770015: location_name.cloudy_park_4, + 0x770016: location_name.cloudy_park_5, + 0x770017: location_name.cloudy_park_6, + 0x770018: location_name.iceberg_1, + 0x770019: location_name.iceberg_2, + 0x77001A: location_name.iceberg_3, + 0x77001B: location_name.iceberg_4, + 0x77001C: location_name.iceberg_5, + 0x77001D: location_name.iceberg_6, +} + +heart_star_locations = { + 0x770100: location_name.grass_land_tulip, + 0x770101: location_name.grass_land_muchi, + 0x770102: location_name.grass_land_pitcherman, + 0x770103: location_name.grass_land_chao, + 0x770104: location_name.grass_land_mine, + 0x770105: location_name.grass_land_pierre, + 0x770106: location_name.ripple_field_kamuribana, + 0x770107: location_name.ripple_field_bakasa, + 0x770108: location_name.ripple_field_elieel, + 0x770109: location_name.ripple_field_toad, + 0x77010A: location_name.ripple_field_mama_pitch, + 0x77010B: location_name.ripple_field_hb002, + 0x77010C: location_name.sand_canyon_mushrooms, + 0x77010D: location_name.sand_canyon_auntie, + 0x77010E: location_name.sand_canyon_caramello, + 0x77010F: location_name.sand_canyon_hikari, + 0x770110: location_name.sand_canyon_nyupun, + 0x770111: location_name.sand_canyon_rob, + 0x770112: location_name.cloudy_park_hibanamodoki, + 0x770113: location_name.cloudy_park_piyokeko, + 0x770114: location_name.cloudy_park_mrball, + 0x770115: location_name.cloudy_park_mikarin, + 0x770116: location_name.cloudy_park_pick, + 0x770117: location_name.cloudy_park_hb007, + 0x770118: location_name.iceberg_kogoesou, + 0x770119: location_name.iceberg_samus, + 0x77011A: location_name.iceberg_kawasaki, + 0x77011B: location_name.iceberg_name, + 0x77011C: location_name.iceberg_shiro, + 0x77011D: location_name.iceberg_angel, +} + +boss_locations = { + 0x770200: location_name.grass_land_whispy, + 0x770201: location_name.ripple_field_acro, + 0x770202: location_name.sand_canyon_poncon, + 0x770203: location_name.cloudy_park_ado, + 0x770204: location_name.iceberg_dedede, +} + +consumable_locations = { + 0x770300: location_name.grass_land_1_u1, + 0x770301: location_name.grass_land_1_m1, + 0x770302: location_name.grass_land_2_u1, + 0x770303: location_name.grass_land_3_u1, + 0x770304: location_name.grass_land_3_m1, + 0x770305: location_name.grass_land_4_m1, + 0x770306: location_name.grass_land_4_u1, + 0x770307: location_name.grass_land_4_m2, + 0x770308: location_name.grass_land_4_m3, + 0x770309: location_name.grass_land_6_u1, + 0x77030A: location_name.grass_land_6_u2, + 0x77030B: location_name.ripple_field_2_u1, + 0x77030C: location_name.ripple_field_2_m1, + 0x77030D: location_name.ripple_field_3_m1, + 0x77030E: location_name.ripple_field_3_u1, + 0x77030F: location_name.ripple_field_4_m2, + 0x770310: location_name.ripple_field_4_u1, + 0x770311: location_name.ripple_field_4_m1, + 0x770312: location_name.ripple_field_5_u1, + 0x770313: location_name.ripple_field_5_m2, + 0x770314: location_name.ripple_field_5_m1, + 0x770315: location_name.sand_canyon_1_u1, + 0x770316: location_name.sand_canyon_2_u1, + 0x770317: location_name.sand_canyon_2_m1, + 0x770318: location_name.sand_canyon_4_m1, + 0x770319: location_name.sand_canyon_4_u1, + 0x77031A: location_name.sand_canyon_4_m2, + 0x77031B: location_name.sand_canyon_5_u1, + 0x77031C: location_name.sand_canyon_5_u3, + 0x77031D: location_name.sand_canyon_5_m1, + 0x77031E: location_name.sand_canyon_5_u4, + 0x77031F: location_name.sand_canyon_5_u2, + 0x770320: location_name.cloudy_park_1_m1, + 0x770321: location_name.cloudy_park_1_u1, + 0x770322: location_name.cloudy_park_4_u1, + 0x770323: location_name.cloudy_park_4_m1, + 0x770324: location_name.cloudy_park_5_m1, + 0x770325: location_name.cloudy_park_6_u1, + 0x770326: location_name.iceberg_3_m1, + 0x770327: location_name.iceberg_5_u1, + 0x770328: location_name.iceberg_5_u2, + 0x770329: location_name.iceberg_5_u3, + 0x77032A: location_name.iceberg_6_m1, + 0x77032B: location_name.iceberg_6_u1, +} + +level_consumables = { + 1: [0, 1], + 2: [2], + 3: [3, 4], + 4: [5, 6, 7, 8], + 6: [9, 10], + 8: [11, 12], + 9: [13, 14], + 10: [15, 16, 17], + 11: [18, 19, 20], + 13: [21], + 14: [22, 23], + 16: [24, 25, 26], + 17: [27, 28, 29, 30, 31], + 19: [32, 33], + 22: [34, 35], + 23: [36], + 24: [37], + 27: [38], + 29: [39, 40, 41], + 30: [42, 43], +} + +star_locations = { + 0x770401: location_name.grass_land_1_s1, + 0x770402: location_name.grass_land_1_s2, + 0x770403: location_name.grass_land_1_s3, + 0x770404: location_name.grass_land_1_s4, + 0x770405: location_name.grass_land_1_s5, + 0x770406: location_name.grass_land_1_s6, + 0x770407: location_name.grass_land_1_s7, + 0x770408: location_name.grass_land_1_s8, + 0x770409: location_name.grass_land_1_s9, + 0x77040a: location_name.grass_land_1_s10, + 0x77040b: location_name.grass_land_1_s11, + 0x77040c: location_name.grass_land_1_s12, + 0x77040d: location_name.grass_land_1_s13, + 0x77040e: location_name.grass_land_1_s14, + 0x77040f: location_name.grass_land_1_s15, + 0x770410: location_name.grass_land_1_s16, + 0x770411: location_name.grass_land_1_s17, + 0x770412: location_name.grass_land_1_s18, + 0x770413: location_name.grass_land_1_s19, + 0x770414: location_name.grass_land_1_s20, + 0x770415: location_name.grass_land_1_s21, + 0x770416: location_name.grass_land_1_s22, + 0x770417: location_name.grass_land_1_s23, + 0x770418: location_name.grass_land_2_s1, + 0x770419: location_name.grass_land_2_s2, + 0x77041a: location_name.grass_land_2_s3, + 0x77041b: location_name.grass_land_2_s4, + 0x77041c: location_name.grass_land_2_s5, + 0x77041d: location_name.grass_land_2_s6, + 0x77041e: location_name.grass_land_2_s7, + 0x77041f: location_name.grass_land_2_s8, + 0x770420: location_name.grass_land_2_s9, + 0x770421: location_name.grass_land_2_s10, + 0x770422: location_name.grass_land_2_s11, + 0x770423: location_name.grass_land_2_s12, + 0x770424: location_name.grass_land_2_s13, + 0x770425: location_name.grass_land_2_s14, + 0x770426: location_name.grass_land_2_s15, + 0x770427: location_name.grass_land_2_s16, + 0x770428: location_name.grass_land_2_s17, + 0x770429: location_name.grass_land_2_s18, + 0x77042a: location_name.grass_land_2_s19, + 0x77042b: location_name.grass_land_2_s20, + 0x77042c: location_name.grass_land_2_s21, + 0x77042d: location_name.grass_land_3_s1, + 0x77042e: location_name.grass_land_3_s2, + 0x77042f: location_name.grass_land_3_s3, + 0x770430: location_name.grass_land_3_s4, + 0x770431: location_name.grass_land_3_s5, + 0x770432: location_name.grass_land_3_s6, + 0x770433: location_name.grass_land_3_s7, + 0x770434: location_name.grass_land_3_s8, + 0x770435: location_name.grass_land_3_s9, + 0x770436: location_name.grass_land_3_s10, + 0x770437: location_name.grass_land_3_s11, + 0x770438: location_name.grass_land_3_s12, + 0x770439: location_name.grass_land_3_s13, + 0x77043a: location_name.grass_land_3_s14, + 0x77043b: location_name.grass_land_3_s15, + 0x77043c: location_name.grass_land_3_s16, + 0x77043d: location_name.grass_land_3_s17, + 0x77043e: location_name.grass_land_3_s18, + 0x77043f: location_name.grass_land_3_s19, + 0x770440: location_name.grass_land_3_s20, + 0x770441: location_name.grass_land_3_s21, + 0x770442: location_name.grass_land_3_s22, + 0x770443: location_name.grass_land_3_s23, + 0x770444: location_name.grass_land_3_s24, + 0x770445: location_name.grass_land_3_s25, + 0x770446: location_name.grass_land_3_s26, + 0x770447: location_name.grass_land_3_s27, + 0x770448: location_name.grass_land_3_s28, + 0x770449: location_name.grass_land_3_s29, + 0x77044a: location_name.grass_land_3_s30, + 0x77044b: location_name.grass_land_3_s31, + 0x77044c: location_name.grass_land_4_s1, + 0x77044d: location_name.grass_land_4_s2, + 0x77044e: location_name.grass_land_4_s3, + 0x77044f: location_name.grass_land_4_s4, + 0x770450: location_name.grass_land_4_s5, + 0x770451: location_name.grass_land_4_s6, + 0x770452: location_name.grass_land_4_s7, + 0x770453: location_name.grass_land_4_s8, + 0x770454: location_name.grass_land_4_s9, + 0x770455: location_name.grass_land_4_s10, + 0x770456: location_name.grass_land_4_s11, + 0x770457: location_name.grass_land_4_s12, + 0x770458: location_name.grass_land_4_s13, + 0x770459: location_name.grass_land_4_s14, + 0x77045a: location_name.grass_land_4_s15, + 0x77045b: location_name.grass_land_4_s16, + 0x77045c: location_name.grass_land_4_s17, + 0x77045d: location_name.grass_land_4_s18, + 0x77045e: location_name.grass_land_4_s19, + 0x77045f: location_name.grass_land_4_s20, + 0x770460: location_name.grass_land_4_s21, + 0x770461: location_name.grass_land_4_s22, + 0x770462: location_name.grass_land_4_s23, + 0x770463: location_name.grass_land_4_s24, + 0x770464: location_name.grass_land_4_s25, + 0x770465: location_name.grass_land_4_s26, + 0x770466: location_name.grass_land_4_s27, + 0x770467: location_name.grass_land_4_s28, + 0x770468: location_name.grass_land_4_s29, + 0x770469: location_name.grass_land_4_s30, + 0x77046a: location_name.grass_land_4_s31, + 0x77046b: location_name.grass_land_4_s32, + 0x77046c: location_name.grass_land_4_s33, + 0x77046d: location_name.grass_land_4_s34, + 0x77046e: location_name.grass_land_4_s35, + 0x77046f: location_name.grass_land_4_s36, + 0x770470: location_name.grass_land_4_s37, + 0x770471: location_name.grass_land_5_s1, + 0x770472: location_name.grass_land_5_s2, + 0x770473: location_name.grass_land_5_s3, + 0x770474: location_name.grass_land_5_s4, + 0x770475: location_name.grass_land_5_s5, + 0x770476: location_name.grass_land_5_s6, + 0x770477: location_name.grass_land_5_s7, + 0x770478: location_name.grass_land_5_s8, + 0x770479: location_name.grass_land_5_s9, + 0x77047a: location_name.grass_land_5_s10, + 0x77047b: location_name.grass_land_5_s11, + 0x77047c: location_name.grass_land_5_s12, + 0x77047d: location_name.grass_land_5_s13, + 0x77047e: location_name.grass_land_5_s14, + 0x77047f: location_name.grass_land_5_s15, + 0x770480: location_name.grass_land_5_s16, + 0x770481: location_name.grass_land_5_s17, + 0x770482: location_name.grass_land_5_s18, + 0x770483: location_name.grass_land_5_s19, + 0x770484: location_name.grass_land_5_s20, + 0x770485: location_name.grass_land_5_s21, + 0x770486: location_name.grass_land_5_s22, + 0x770487: location_name.grass_land_5_s23, + 0x770488: location_name.grass_land_5_s24, + 0x770489: location_name.grass_land_5_s25, + 0x77048a: location_name.grass_land_5_s26, + 0x77048b: location_name.grass_land_5_s27, + 0x77048c: location_name.grass_land_5_s28, + 0x77048d: location_name.grass_land_5_s29, + 0x77048e: location_name.grass_land_6_s1, + 0x77048f: location_name.grass_land_6_s2, + 0x770490: location_name.grass_land_6_s3, + 0x770491: location_name.grass_land_6_s4, + 0x770492: location_name.grass_land_6_s5, + 0x770493: location_name.grass_land_6_s6, + 0x770494: location_name.grass_land_6_s7, + 0x770495: location_name.grass_land_6_s8, + 0x770496: location_name.grass_land_6_s9, + 0x770497: location_name.grass_land_6_s10, + 0x770498: location_name.grass_land_6_s11, + 0x770499: location_name.grass_land_6_s12, + 0x77049a: location_name.grass_land_6_s13, + 0x77049b: location_name.grass_land_6_s14, + 0x77049c: location_name.grass_land_6_s15, + 0x77049d: location_name.grass_land_6_s16, + 0x77049e: location_name.grass_land_6_s17, + 0x77049f: location_name.grass_land_6_s18, + 0x7704a0: location_name.grass_land_6_s19, + 0x7704a1: location_name.grass_land_6_s20, + 0x7704a2: location_name.grass_land_6_s21, + 0x7704a3: location_name.grass_land_6_s22, + 0x7704a4: location_name.grass_land_6_s23, + 0x7704a5: location_name.grass_land_6_s24, + 0x7704a6: location_name.grass_land_6_s25, + 0x7704a7: location_name.grass_land_6_s26, + 0x7704a8: location_name.grass_land_6_s27, + 0x7704a9: location_name.grass_land_6_s28, + 0x7704aa: location_name.grass_land_6_s29, + 0x7704ab: location_name.ripple_field_1_s1, + 0x7704ac: location_name.ripple_field_1_s2, + 0x7704ad: location_name.ripple_field_1_s3, + 0x7704ae: location_name.ripple_field_1_s4, + 0x7704af: location_name.ripple_field_1_s5, + 0x7704b0: location_name.ripple_field_1_s6, + 0x7704b1: location_name.ripple_field_1_s7, + 0x7704b2: location_name.ripple_field_1_s8, + 0x7704b3: location_name.ripple_field_1_s9, + 0x7704b4: location_name.ripple_field_1_s10, + 0x7704b5: location_name.ripple_field_1_s11, + 0x7704b6: location_name.ripple_field_1_s12, + 0x7704b7: location_name.ripple_field_1_s13, + 0x7704b8: location_name.ripple_field_1_s14, + 0x7704b9: location_name.ripple_field_1_s15, + 0x7704ba: location_name.ripple_field_1_s16, + 0x7704bb: location_name.ripple_field_1_s17, + 0x7704bc: location_name.ripple_field_1_s18, + 0x7704bd: location_name.ripple_field_1_s19, + 0x7704be: location_name.ripple_field_2_s1, + 0x7704bf: location_name.ripple_field_2_s2, + 0x7704c0: location_name.ripple_field_2_s3, + 0x7704c1: location_name.ripple_field_2_s4, + 0x7704c2: location_name.ripple_field_2_s5, + 0x7704c3: location_name.ripple_field_2_s6, + 0x7704c4: location_name.ripple_field_2_s7, + 0x7704c5: location_name.ripple_field_2_s8, + 0x7704c6: location_name.ripple_field_2_s9, + 0x7704c7: location_name.ripple_field_2_s10, + 0x7704c8: location_name.ripple_field_2_s11, + 0x7704c9: location_name.ripple_field_2_s12, + 0x7704ca: location_name.ripple_field_2_s13, + 0x7704cb: location_name.ripple_field_2_s14, + 0x7704cc: location_name.ripple_field_2_s15, + 0x7704cd: location_name.ripple_field_2_s16, + 0x7704ce: location_name.ripple_field_2_s17, + 0x7704cf: location_name.ripple_field_3_s1, + 0x7704d0: location_name.ripple_field_3_s2, + 0x7704d1: location_name.ripple_field_3_s3, + 0x7704d2: location_name.ripple_field_3_s4, + 0x7704d3: location_name.ripple_field_3_s5, + 0x7704d4: location_name.ripple_field_3_s6, + 0x7704d5: location_name.ripple_field_3_s7, + 0x7704d6: location_name.ripple_field_3_s8, + 0x7704d7: location_name.ripple_field_3_s9, + 0x7704d8: location_name.ripple_field_3_s10, + 0x7704d9: location_name.ripple_field_3_s11, + 0x7704da: location_name.ripple_field_3_s12, + 0x7704db: location_name.ripple_field_3_s13, + 0x7704dc: location_name.ripple_field_3_s14, + 0x7704dd: location_name.ripple_field_3_s15, + 0x7704de: location_name.ripple_field_3_s16, + 0x7704df: location_name.ripple_field_3_s17, + 0x7704e0: location_name.ripple_field_3_s18, + 0x7704e1: location_name.ripple_field_3_s19, + 0x7704e2: location_name.ripple_field_3_s20, + 0x7704e3: location_name.ripple_field_3_s21, + 0x7704e4: location_name.ripple_field_4_s1, + 0x7704e5: location_name.ripple_field_4_s2, + 0x7704e6: location_name.ripple_field_4_s3, + 0x7704e7: location_name.ripple_field_4_s4, + 0x7704e8: location_name.ripple_field_4_s5, + 0x7704e9: location_name.ripple_field_4_s6, + 0x7704ea: location_name.ripple_field_4_s7, + 0x7704eb: location_name.ripple_field_4_s8, + 0x7704ec: location_name.ripple_field_4_s9, + 0x7704ed: location_name.ripple_field_4_s10, + 0x7704ee: location_name.ripple_field_4_s11, + 0x7704ef: location_name.ripple_field_4_s12, + 0x7704f0: location_name.ripple_field_4_s13, + 0x7704f1: location_name.ripple_field_4_s14, + 0x7704f2: location_name.ripple_field_4_s15, + 0x7704f3: location_name.ripple_field_4_s16, + 0x7704f4: location_name.ripple_field_4_s17, + 0x7704f5: location_name.ripple_field_4_s18, + 0x7704f6: location_name.ripple_field_4_s19, + 0x7704f7: location_name.ripple_field_4_s20, + 0x7704f8: location_name.ripple_field_4_s21, + 0x7704f9: location_name.ripple_field_4_s22, + 0x7704fa: location_name.ripple_field_4_s23, + 0x7704fb: location_name.ripple_field_4_s24, + 0x7704fc: location_name.ripple_field_4_s25, + 0x7704fd: location_name.ripple_field_4_s26, + 0x7704fe: location_name.ripple_field_4_s27, + 0x7704ff: location_name.ripple_field_4_s28, + 0x770500: location_name.ripple_field_4_s29, + 0x770501: location_name.ripple_field_4_s30, + 0x770502: location_name.ripple_field_4_s31, + 0x770503: location_name.ripple_field_4_s32, + 0x770504: location_name.ripple_field_4_s33, + 0x770505: location_name.ripple_field_4_s34, + 0x770506: location_name.ripple_field_4_s35, + 0x770507: location_name.ripple_field_4_s36, + 0x770508: location_name.ripple_field_4_s37, + 0x770509: location_name.ripple_field_4_s38, + 0x77050a: location_name.ripple_field_4_s39, + 0x77050b: location_name.ripple_field_4_s40, + 0x77050c: location_name.ripple_field_4_s41, + 0x77050d: location_name.ripple_field_4_s42, + 0x77050e: location_name.ripple_field_4_s43, + 0x77050f: location_name.ripple_field_4_s44, + 0x770510: location_name.ripple_field_4_s45, + 0x770511: location_name.ripple_field_4_s46, + 0x770512: location_name.ripple_field_4_s47, + 0x770513: location_name.ripple_field_4_s48, + 0x770514: location_name.ripple_field_4_s49, + 0x770515: location_name.ripple_field_4_s50, + 0x770516: location_name.ripple_field_4_s51, + 0x770517: location_name.ripple_field_5_s1, + 0x770518: location_name.ripple_field_5_s2, + 0x770519: location_name.ripple_field_5_s3, + 0x77051a: location_name.ripple_field_5_s4, + 0x77051b: location_name.ripple_field_5_s5, + 0x77051c: location_name.ripple_field_5_s6, + 0x77051d: location_name.ripple_field_5_s7, + 0x77051e: location_name.ripple_field_5_s8, + 0x77051f: location_name.ripple_field_5_s9, + 0x770520: location_name.ripple_field_5_s10, + 0x770521: location_name.ripple_field_5_s11, + 0x770522: location_name.ripple_field_5_s12, + 0x770523: location_name.ripple_field_5_s13, + 0x770524: location_name.ripple_field_5_s14, + 0x770525: location_name.ripple_field_5_s15, + 0x770526: location_name.ripple_field_5_s16, + 0x770527: location_name.ripple_field_5_s17, + 0x770528: location_name.ripple_field_5_s18, + 0x770529: location_name.ripple_field_5_s19, + 0x77052a: location_name.ripple_field_5_s20, + 0x77052b: location_name.ripple_field_5_s21, + 0x77052c: location_name.ripple_field_5_s22, + 0x77052d: location_name.ripple_field_5_s23, + 0x77052e: location_name.ripple_field_5_s24, + 0x77052f: location_name.ripple_field_5_s25, + 0x770530: location_name.ripple_field_5_s26, + 0x770531: location_name.ripple_field_5_s27, + 0x770532: location_name.ripple_field_5_s28, + 0x770533: location_name.ripple_field_5_s29, + 0x770534: location_name.ripple_field_5_s30, + 0x770535: location_name.ripple_field_5_s31, + 0x770536: location_name.ripple_field_5_s32, + 0x770537: location_name.ripple_field_5_s33, + 0x770538: location_name.ripple_field_5_s34, + 0x770539: location_name.ripple_field_5_s35, + 0x77053a: location_name.ripple_field_5_s36, + 0x77053b: location_name.ripple_field_5_s37, + 0x77053c: location_name.ripple_field_5_s38, + 0x77053d: location_name.ripple_field_5_s39, + 0x77053e: location_name.ripple_field_5_s40, + 0x77053f: location_name.ripple_field_5_s41, + 0x770540: location_name.ripple_field_5_s42, + 0x770541: location_name.ripple_field_5_s43, + 0x770542: location_name.ripple_field_5_s44, + 0x770543: location_name.ripple_field_5_s45, + 0x770544: location_name.ripple_field_5_s46, + 0x770545: location_name.ripple_field_5_s47, + 0x770546: location_name.ripple_field_5_s48, + 0x770547: location_name.ripple_field_5_s49, + 0x770548: location_name.ripple_field_5_s50, + 0x770549: location_name.ripple_field_5_s51, + 0x77054a: location_name.ripple_field_6_s1, + 0x77054b: location_name.ripple_field_6_s2, + 0x77054c: location_name.ripple_field_6_s3, + 0x77054d: location_name.ripple_field_6_s4, + 0x77054e: location_name.ripple_field_6_s5, + 0x77054f: location_name.ripple_field_6_s6, + 0x770550: location_name.ripple_field_6_s7, + 0x770551: location_name.ripple_field_6_s8, + 0x770552: location_name.ripple_field_6_s9, + 0x770553: location_name.ripple_field_6_s10, + 0x770554: location_name.ripple_field_6_s11, + 0x770555: location_name.ripple_field_6_s12, + 0x770556: location_name.ripple_field_6_s13, + 0x770557: location_name.ripple_field_6_s14, + 0x770558: location_name.ripple_field_6_s15, + 0x770559: location_name.ripple_field_6_s16, + 0x77055a: location_name.ripple_field_6_s17, + 0x77055b: location_name.ripple_field_6_s18, + 0x77055c: location_name.ripple_field_6_s19, + 0x77055d: location_name.ripple_field_6_s20, + 0x77055e: location_name.ripple_field_6_s21, + 0x77055f: location_name.ripple_field_6_s22, + 0x770560: location_name.ripple_field_6_s23, + 0x770561: location_name.sand_canyon_1_s1, + 0x770562: location_name.sand_canyon_1_s2, + 0x770563: location_name.sand_canyon_1_s3, + 0x770564: location_name.sand_canyon_1_s4, + 0x770565: location_name.sand_canyon_1_s5, + 0x770566: location_name.sand_canyon_1_s6, + 0x770567: location_name.sand_canyon_1_s7, + 0x770568: location_name.sand_canyon_1_s8, + 0x770569: location_name.sand_canyon_1_s9, + 0x77056a: location_name.sand_canyon_1_s10, + 0x77056b: location_name.sand_canyon_1_s11, + 0x77056c: location_name.sand_canyon_1_s12, + 0x77056d: location_name.sand_canyon_1_s13, + 0x77056e: location_name.sand_canyon_1_s14, + 0x77056f: location_name.sand_canyon_1_s15, + 0x770570: location_name.sand_canyon_1_s16, + 0x770571: location_name.sand_canyon_1_s17, + 0x770572: location_name.sand_canyon_1_s18, + 0x770573: location_name.sand_canyon_1_s19, + 0x770574: location_name.sand_canyon_1_s20, + 0x770575: location_name.sand_canyon_1_s21, + 0x770576: location_name.sand_canyon_1_s22, + 0x770577: location_name.sand_canyon_2_s1, + 0x770578: location_name.sand_canyon_2_s2, + 0x770579: location_name.sand_canyon_2_s3, + 0x77057a: location_name.sand_canyon_2_s4, + 0x77057b: location_name.sand_canyon_2_s5, + 0x77057c: location_name.sand_canyon_2_s6, + 0x77057d: location_name.sand_canyon_2_s7, + 0x77057e: location_name.sand_canyon_2_s8, + 0x77057f: location_name.sand_canyon_2_s9, + 0x770580: location_name.sand_canyon_2_s10, + 0x770581: location_name.sand_canyon_2_s11, + 0x770582: location_name.sand_canyon_2_s12, + 0x770583: location_name.sand_canyon_2_s13, + 0x770584: location_name.sand_canyon_2_s14, + 0x770585: location_name.sand_canyon_2_s15, + 0x770586: location_name.sand_canyon_2_s16, + 0x770587: location_name.sand_canyon_2_s17, + 0x770588: location_name.sand_canyon_2_s18, + 0x770589: location_name.sand_canyon_2_s19, + 0x77058a: location_name.sand_canyon_2_s20, + 0x77058b: location_name.sand_canyon_2_s21, + 0x77058c: location_name.sand_canyon_2_s22, + 0x77058d: location_name.sand_canyon_2_s23, + 0x77058e: location_name.sand_canyon_2_s24, + 0x77058f: location_name.sand_canyon_2_s25, + 0x770590: location_name.sand_canyon_2_s26, + 0x770591: location_name.sand_canyon_2_s27, + 0x770592: location_name.sand_canyon_2_s28, + 0x770593: location_name.sand_canyon_2_s29, + 0x770594: location_name.sand_canyon_2_s30, + 0x770595: location_name.sand_canyon_2_s31, + 0x770596: location_name.sand_canyon_2_s32, + 0x770597: location_name.sand_canyon_2_s33, + 0x770598: location_name.sand_canyon_2_s34, + 0x770599: location_name.sand_canyon_2_s35, + 0x77059a: location_name.sand_canyon_2_s36, + 0x77059b: location_name.sand_canyon_2_s37, + 0x77059c: location_name.sand_canyon_2_s38, + 0x77059d: location_name.sand_canyon_2_s39, + 0x77059e: location_name.sand_canyon_2_s40, + 0x77059f: location_name.sand_canyon_2_s41, + 0x7705a0: location_name.sand_canyon_2_s42, + 0x7705a1: location_name.sand_canyon_2_s43, + 0x7705a2: location_name.sand_canyon_2_s44, + 0x7705a3: location_name.sand_canyon_2_s45, + 0x7705a4: location_name.sand_canyon_2_s46, + 0x7705a5: location_name.sand_canyon_2_s47, + 0x7705a6: location_name.sand_canyon_2_s48, + 0x7705a7: location_name.sand_canyon_3_s1, + 0x7705a8: location_name.sand_canyon_3_s2, + 0x7705a9: location_name.sand_canyon_3_s3, + 0x7705aa: location_name.sand_canyon_3_s4, + 0x7705ab: location_name.sand_canyon_3_s5, + 0x7705ac: location_name.sand_canyon_3_s6, + 0x7705ad: location_name.sand_canyon_3_s7, + 0x7705ae: location_name.sand_canyon_3_s8, + 0x7705af: location_name.sand_canyon_3_s9, + 0x7705b0: location_name.sand_canyon_3_s10, + 0x7705b1: location_name.sand_canyon_4_s1, + 0x7705b2: location_name.sand_canyon_4_s2, + 0x7705b3: location_name.sand_canyon_4_s3, + 0x7705b4: location_name.sand_canyon_4_s4, + 0x7705b5: location_name.sand_canyon_4_s5, + 0x7705b6: location_name.sand_canyon_4_s6, + 0x7705b7: location_name.sand_canyon_4_s7, + 0x7705b8: location_name.sand_canyon_4_s8, + 0x7705b9: location_name.sand_canyon_4_s9, + 0x7705ba: location_name.sand_canyon_4_s10, + 0x7705bb: location_name.sand_canyon_4_s11, + 0x7705bc: location_name.sand_canyon_4_s12, + 0x7705bd: location_name.sand_canyon_4_s13, + 0x7705be: location_name.sand_canyon_4_s14, + 0x7705bf: location_name.sand_canyon_4_s15, + 0x7705c0: location_name.sand_canyon_4_s16, + 0x7705c1: location_name.sand_canyon_4_s17, + 0x7705c2: location_name.sand_canyon_4_s18, + 0x7705c3: location_name.sand_canyon_4_s19, + 0x7705c4: location_name.sand_canyon_4_s20, + 0x7705c5: location_name.sand_canyon_4_s21, + 0x7705c6: location_name.sand_canyon_4_s22, + 0x7705c7: location_name.sand_canyon_4_s23, + 0x7705c8: location_name.sand_canyon_5_s1, + 0x7705c9: location_name.sand_canyon_5_s2, + 0x7705ca: location_name.sand_canyon_5_s3, + 0x7705cb: location_name.sand_canyon_5_s4, + 0x7705cc: location_name.sand_canyon_5_s5, + 0x7705cd: location_name.sand_canyon_5_s6, + 0x7705ce: location_name.sand_canyon_5_s7, + 0x7705cf: location_name.sand_canyon_5_s8, + 0x7705d0: location_name.sand_canyon_5_s9, + 0x7705d1: location_name.sand_canyon_5_s10, + 0x7705d2: location_name.sand_canyon_5_s11, + 0x7705d3: location_name.sand_canyon_5_s12, + 0x7705d4: location_name.sand_canyon_5_s13, + 0x7705d5: location_name.sand_canyon_5_s14, + 0x7705d6: location_name.sand_canyon_5_s15, + 0x7705d7: location_name.sand_canyon_5_s16, + 0x7705d8: location_name.sand_canyon_5_s17, + 0x7705d9: location_name.sand_canyon_5_s18, + 0x7705da: location_name.sand_canyon_5_s19, + 0x7705db: location_name.sand_canyon_5_s20, + 0x7705dc: location_name.sand_canyon_5_s21, + 0x7705dd: location_name.sand_canyon_5_s22, + 0x7705de: location_name.sand_canyon_5_s23, + 0x7705df: location_name.sand_canyon_5_s24, + 0x7705e0: location_name.sand_canyon_5_s25, + 0x7705e1: location_name.sand_canyon_5_s26, + 0x7705e2: location_name.sand_canyon_5_s27, + 0x7705e3: location_name.sand_canyon_5_s28, + 0x7705e4: location_name.sand_canyon_5_s29, + 0x7705e5: location_name.sand_canyon_5_s30, + 0x7705e6: location_name.sand_canyon_5_s31, + 0x7705e7: location_name.sand_canyon_5_s32, + 0x7705e8: location_name.sand_canyon_5_s33, + 0x7705e9: location_name.sand_canyon_5_s34, + 0x7705ea: location_name.sand_canyon_5_s35, + 0x7705eb: location_name.sand_canyon_5_s36, + 0x7705ec: location_name.sand_canyon_5_s37, + 0x7705ed: location_name.sand_canyon_5_s38, + 0x7705ee: location_name.sand_canyon_5_s39, + 0x7705ef: location_name.sand_canyon_5_s40, + 0x7705f0: location_name.cloudy_park_1_s1, + 0x7705f1: location_name.cloudy_park_1_s2, + 0x7705f2: location_name.cloudy_park_1_s3, + 0x7705f3: location_name.cloudy_park_1_s4, + 0x7705f4: location_name.cloudy_park_1_s5, + 0x7705f5: location_name.cloudy_park_1_s6, + 0x7705f6: location_name.cloudy_park_1_s7, + 0x7705f7: location_name.cloudy_park_1_s8, + 0x7705f8: location_name.cloudy_park_1_s9, + 0x7705f9: location_name.cloudy_park_1_s10, + 0x7705fa: location_name.cloudy_park_1_s11, + 0x7705fb: location_name.cloudy_park_1_s12, + 0x7705fc: location_name.cloudy_park_1_s13, + 0x7705fd: location_name.cloudy_park_1_s14, + 0x7705fe: location_name.cloudy_park_1_s15, + 0x7705ff: location_name.cloudy_park_1_s16, + 0x770600: location_name.cloudy_park_1_s17, + 0x770601: location_name.cloudy_park_1_s18, + 0x770602: location_name.cloudy_park_1_s19, + 0x770603: location_name.cloudy_park_1_s20, + 0x770604: location_name.cloudy_park_1_s21, + 0x770605: location_name.cloudy_park_1_s22, + 0x770606: location_name.cloudy_park_1_s23, + 0x770607: location_name.cloudy_park_2_s1, + 0x770608: location_name.cloudy_park_2_s2, + 0x770609: location_name.cloudy_park_2_s3, + 0x77060a: location_name.cloudy_park_2_s4, + 0x77060b: location_name.cloudy_park_2_s5, + 0x77060c: location_name.cloudy_park_2_s6, + 0x77060d: location_name.cloudy_park_2_s7, + 0x77060e: location_name.cloudy_park_2_s8, + 0x77060f: location_name.cloudy_park_2_s9, + 0x770610: location_name.cloudy_park_2_s10, + 0x770611: location_name.cloudy_park_2_s11, + 0x770612: location_name.cloudy_park_2_s12, + 0x770613: location_name.cloudy_park_2_s13, + 0x770614: location_name.cloudy_park_2_s14, + 0x770615: location_name.cloudy_park_2_s15, + 0x770616: location_name.cloudy_park_2_s16, + 0x770617: location_name.cloudy_park_2_s17, + 0x770618: location_name.cloudy_park_2_s18, + 0x770619: location_name.cloudy_park_2_s19, + 0x77061a: location_name.cloudy_park_2_s20, + 0x77061b: location_name.cloudy_park_2_s21, + 0x77061c: location_name.cloudy_park_2_s22, + 0x77061d: location_name.cloudy_park_2_s23, + 0x77061e: location_name.cloudy_park_2_s24, + 0x77061f: location_name.cloudy_park_2_s25, + 0x770620: location_name.cloudy_park_2_s26, + 0x770621: location_name.cloudy_park_2_s27, + 0x770622: location_name.cloudy_park_2_s28, + 0x770623: location_name.cloudy_park_2_s29, + 0x770624: location_name.cloudy_park_2_s30, + 0x770625: location_name.cloudy_park_2_s31, + 0x770626: location_name.cloudy_park_2_s32, + 0x770627: location_name.cloudy_park_2_s33, + 0x770628: location_name.cloudy_park_2_s34, + 0x770629: location_name.cloudy_park_2_s35, + 0x77062a: location_name.cloudy_park_2_s36, + 0x77062b: location_name.cloudy_park_2_s37, + 0x77062c: location_name.cloudy_park_2_s38, + 0x77062d: location_name.cloudy_park_2_s39, + 0x77062e: location_name.cloudy_park_2_s40, + 0x77062f: location_name.cloudy_park_2_s41, + 0x770630: location_name.cloudy_park_2_s42, + 0x770631: location_name.cloudy_park_2_s43, + 0x770632: location_name.cloudy_park_2_s44, + 0x770633: location_name.cloudy_park_2_s45, + 0x770634: location_name.cloudy_park_2_s46, + 0x770635: location_name.cloudy_park_2_s47, + 0x770636: location_name.cloudy_park_2_s48, + 0x770637: location_name.cloudy_park_2_s49, + 0x770638: location_name.cloudy_park_2_s50, + 0x770639: location_name.cloudy_park_2_s51, + 0x77063a: location_name.cloudy_park_2_s52, + 0x77063b: location_name.cloudy_park_2_s53, + 0x77063c: location_name.cloudy_park_2_s54, + 0x77063d: location_name.cloudy_park_3_s1, + 0x77063e: location_name.cloudy_park_3_s2, + 0x77063f: location_name.cloudy_park_3_s3, + 0x770640: location_name.cloudy_park_3_s4, + 0x770641: location_name.cloudy_park_3_s5, + 0x770642: location_name.cloudy_park_3_s6, + 0x770643: location_name.cloudy_park_3_s7, + 0x770644: location_name.cloudy_park_3_s8, + 0x770645: location_name.cloudy_park_3_s9, + 0x770646: location_name.cloudy_park_3_s10, + 0x770647: location_name.cloudy_park_3_s11, + 0x770648: location_name.cloudy_park_3_s12, + 0x770649: location_name.cloudy_park_3_s13, + 0x77064a: location_name.cloudy_park_3_s14, + 0x77064b: location_name.cloudy_park_3_s15, + 0x77064c: location_name.cloudy_park_3_s16, + 0x77064d: location_name.cloudy_park_3_s17, + 0x77064e: location_name.cloudy_park_3_s18, + 0x77064f: location_name.cloudy_park_3_s19, + 0x770650: location_name.cloudy_park_3_s20, + 0x770651: location_name.cloudy_park_3_s21, + 0x770652: location_name.cloudy_park_3_s22, + 0x770653: location_name.cloudy_park_4_s1, + 0x770654: location_name.cloudy_park_4_s2, + 0x770655: location_name.cloudy_park_4_s3, + 0x770656: location_name.cloudy_park_4_s4, + 0x770657: location_name.cloudy_park_4_s5, + 0x770658: location_name.cloudy_park_4_s6, + 0x770659: location_name.cloudy_park_4_s7, + 0x77065a: location_name.cloudy_park_4_s8, + 0x77065b: location_name.cloudy_park_4_s9, + 0x77065c: location_name.cloudy_park_4_s10, + 0x77065d: location_name.cloudy_park_4_s11, + 0x77065e: location_name.cloudy_park_4_s12, + 0x77065f: location_name.cloudy_park_4_s13, + 0x770660: location_name.cloudy_park_4_s14, + 0x770661: location_name.cloudy_park_4_s15, + 0x770662: location_name.cloudy_park_4_s16, + 0x770663: location_name.cloudy_park_4_s17, + 0x770664: location_name.cloudy_park_4_s18, + 0x770665: location_name.cloudy_park_4_s19, + 0x770666: location_name.cloudy_park_4_s20, + 0x770667: location_name.cloudy_park_4_s21, + 0x770668: location_name.cloudy_park_4_s22, + 0x770669: location_name.cloudy_park_4_s23, + 0x77066a: location_name.cloudy_park_4_s24, + 0x77066b: location_name.cloudy_park_4_s25, + 0x77066c: location_name.cloudy_park_4_s26, + 0x77066d: location_name.cloudy_park_4_s27, + 0x77066e: location_name.cloudy_park_4_s28, + 0x77066f: location_name.cloudy_park_4_s29, + 0x770670: location_name.cloudy_park_4_s30, + 0x770671: location_name.cloudy_park_4_s31, + 0x770672: location_name.cloudy_park_4_s32, + 0x770673: location_name.cloudy_park_4_s33, + 0x770674: location_name.cloudy_park_4_s34, + 0x770675: location_name.cloudy_park_4_s35, + 0x770676: location_name.cloudy_park_4_s36, + 0x770677: location_name.cloudy_park_4_s37, + 0x770678: location_name.cloudy_park_4_s38, + 0x770679: location_name.cloudy_park_4_s39, + 0x77067a: location_name.cloudy_park_4_s40, + 0x77067b: location_name.cloudy_park_4_s41, + 0x77067c: location_name.cloudy_park_4_s42, + 0x77067d: location_name.cloudy_park_4_s43, + 0x77067e: location_name.cloudy_park_4_s44, + 0x77067f: location_name.cloudy_park_4_s45, + 0x770680: location_name.cloudy_park_4_s46, + 0x770681: location_name.cloudy_park_4_s47, + 0x770682: location_name.cloudy_park_4_s48, + 0x770683: location_name.cloudy_park_4_s49, + 0x770684: location_name.cloudy_park_4_s50, + 0x770685: location_name.cloudy_park_5_s1, + 0x770686: location_name.cloudy_park_5_s2, + 0x770687: location_name.cloudy_park_5_s3, + 0x770688: location_name.cloudy_park_5_s4, + 0x770689: location_name.cloudy_park_5_s5, + 0x77068a: location_name.cloudy_park_5_s6, + 0x77068b: location_name.cloudy_park_6_s1, + 0x77068c: location_name.cloudy_park_6_s2, + 0x77068d: location_name.cloudy_park_6_s3, + 0x77068e: location_name.cloudy_park_6_s4, + 0x77068f: location_name.cloudy_park_6_s5, + 0x770690: location_name.cloudy_park_6_s6, + 0x770691: location_name.cloudy_park_6_s7, + 0x770692: location_name.cloudy_park_6_s8, + 0x770693: location_name.cloudy_park_6_s9, + 0x770694: location_name.cloudy_park_6_s10, + 0x770695: location_name.cloudy_park_6_s11, + 0x770696: location_name.cloudy_park_6_s12, + 0x770697: location_name.cloudy_park_6_s13, + 0x770698: location_name.cloudy_park_6_s14, + 0x770699: location_name.cloudy_park_6_s15, + 0x77069a: location_name.cloudy_park_6_s16, + 0x77069b: location_name.cloudy_park_6_s17, + 0x77069c: location_name.cloudy_park_6_s18, + 0x77069d: location_name.cloudy_park_6_s19, + 0x77069e: location_name.cloudy_park_6_s20, + 0x77069f: location_name.cloudy_park_6_s21, + 0x7706a0: location_name.cloudy_park_6_s22, + 0x7706a1: location_name.cloudy_park_6_s23, + 0x7706a2: location_name.cloudy_park_6_s24, + 0x7706a3: location_name.cloudy_park_6_s25, + 0x7706a4: location_name.cloudy_park_6_s26, + 0x7706a5: location_name.cloudy_park_6_s27, + 0x7706a6: location_name.cloudy_park_6_s28, + 0x7706a7: location_name.cloudy_park_6_s29, + 0x7706a8: location_name.cloudy_park_6_s30, + 0x7706a9: location_name.cloudy_park_6_s31, + 0x7706aa: location_name.cloudy_park_6_s32, + 0x7706ab: location_name.cloudy_park_6_s33, + 0x7706ac: location_name.iceberg_1_s1, + 0x7706ad: location_name.iceberg_1_s2, + 0x7706ae: location_name.iceberg_1_s3, + 0x7706af: location_name.iceberg_1_s4, + 0x7706b0: location_name.iceberg_1_s5, + 0x7706b1: location_name.iceberg_1_s6, + 0x7706b2: location_name.iceberg_2_s1, + 0x7706b3: location_name.iceberg_2_s2, + 0x7706b4: location_name.iceberg_2_s3, + 0x7706b5: location_name.iceberg_2_s4, + 0x7706b6: location_name.iceberg_2_s5, + 0x7706b7: location_name.iceberg_2_s6, + 0x7706b8: location_name.iceberg_2_s7, + 0x7706b9: location_name.iceberg_2_s8, + 0x7706ba: location_name.iceberg_2_s9, + 0x7706bb: location_name.iceberg_2_s10, + 0x7706bc: location_name.iceberg_2_s11, + 0x7706bd: location_name.iceberg_2_s12, + 0x7706be: location_name.iceberg_2_s13, + 0x7706bf: location_name.iceberg_2_s14, + 0x7706c0: location_name.iceberg_2_s15, + 0x7706c1: location_name.iceberg_2_s16, + 0x7706c2: location_name.iceberg_2_s17, + 0x7706c3: location_name.iceberg_2_s18, + 0x7706c4: location_name.iceberg_2_s19, + 0x7706c5: location_name.iceberg_3_s1, + 0x7706c6: location_name.iceberg_3_s2, + 0x7706c7: location_name.iceberg_3_s3, + 0x7706c8: location_name.iceberg_3_s4, + 0x7706c9: location_name.iceberg_3_s5, + 0x7706ca: location_name.iceberg_3_s6, + 0x7706cb: location_name.iceberg_3_s7, + 0x7706cc: location_name.iceberg_3_s8, + 0x7706cd: location_name.iceberg_3_s9, + 0x7706ce: location_name.iceberg_3_s10, + 0x7706cf: location_name.iceberg_3_s11, + 0x7706d0: location_name.iceberg_3_s12, + 0x7706d1: location_name.iceberg_3_s13, + 0x7706d2: location_name.iceberg_3_s14, + 0x7706d3: location_name.iceberg_3_s15, + 0x7706d4: location_name.iceberg_3_s16, + 0x7706d5: location_name.iceberg_3_s17, + 0x7706d6: location_name.iceberg_3_s18, + 0x7706d7: location_name.iceberg_3_s19, + 0x7706d8: location_name.iceberg_3_s20, + 0x7706d9: location_name.iceberg_3_s21, + 0x7706da: location_name.iceberg_4_s1, + 0x7706db: location_name.iceberg_4_s2, + 0x7706dc: location_name.iceberg_4_s3, + 0x7706dd: location_name.iceberg_5_s1, + 0x7706de: location_name.iceberg_5_s2, + 0x7706df: location_name.iceberg_5_s3, + 0x7706e0: location_name.iceberg_5_s4, + 0x7706e1: location_name.iceberg_5_s5, + 0x7706e2: location_name.iceberg_5_s6, + 0x7706e3: location_name.iceberg_5_s7, + 0x7706e4: location_name.iceberg_5_s8, + 0x7706e5: location_name.iceberg_5_s9, + 0x7706e6: location_name.iceberg_5_s10, + 0x7706e7: location_name.iceberg_5_s11, + 0x7706e8: location_name.iceberg_5_s12, + 0x7706e9: location_name.iceberg_5_s13, + 0x7706ea: location_name.iceberg_5_s14, + 0x7706eb: location_name.iceberg_5_s15, + 0x7706ec: location_name.iceberg_5_s16, + 0x7706ed: location_name.iceberg_5_s17, + 0x7706ee: location_name.iceberg_5_s18, + 0x7706ef: location_name.iceberg_5_s19, + 0x7706f0: location_name.iceberg_5_s20, + 0x7706f1: location_name.iceberg_5_s21, + 0x7706f2: location_name.iceberg_5_s22, + 0x7706f3: location_name.iceberg_5_s23, + 0x7706f4: location_name.iceberg_5_s24, + 0x7706f5: location_name.iceberg_5_s25, + 0x7706f6: location_name.iceberg_5_s26, + 0x7706f7: location_name.iceberg_5_s27, + 0x7706f8: location_name.iceberg_5_s28, + 0x7706f9: location_name.iceberg_5_s29, + 0x7706fa: location_name.iceberg_5_s30, + 0x7706fb: location_name.iceberg_5_s31, + 0x7706fc: location_name.iceberg_5_s32, + 0x7706fd: location_name.iceberg_5_s33, + 0x7706fe: location_name.iceberg_5_s34, + 0x7706ff: location_name.iceberg_6_s1, + +} + +location_table = { + **stage_locations, + **heart_star_locations, + **boss_locations, + **consumable_locations, + **star_locations +} diff --git a/worlds/kdl3/Names/__init__.py b/worlds/kdl3/names/__init__.py similarity index 100% rename from worlds/kdl3/Names/__init__.py rename to worlds/kdl3/names/__init__.py diff --git a/worlds/kdl3/Names/AnimalFriendSpawns.py b/worlds/kdl3/names/animal_friend_spawns.py similarity index 95% rename from worlds/kdl3/Names/AnimalFriendSpawns.py rename to worlds/kdl3/names/animal_friend_spawns.py index 4520cf14..5c1ba396 100644 --- a/worlds/kdl3/Names/AnimalFriendSpawns.py +++ b/worlds/kdl3/names/animal_friend_spawns.py @@ -1,3 +1,5 @@ +from typing import List + grass_land_1_a1 = "Grass Land 1 - Animal 1" # Nago grass_land_1_a2 = "Grass Land 1 - Animal 2" # Rick grass_land_2_a1 = "Grass Land 2 - Animal 1" # ChuChu @@ -197,3 +199,12 @@ animal_friend_spawns = { iceberg_6_a5: "ChuChu Spawn", iceberg_6_a6: "Nago Spawn", } + +problematic_sets: List[List[str]] = [ + # Animal groups that must be guaranteed unique. Potential for softlocks on future-ER if not. + [ripple_field_4_a1, ripple_field_4_a2, ripple_field_4_a3], + [sand_canyon_3_a1, sand_canyon_3_a2, sand_canyon_3_a3], + [cloudy_park_6_a1, cloudy_park_6_a2, cloudy_park_6_a3], + [iceberg_6_a1, iceberg_6_a2, iceberg_6_a3], + [iceberg_6_a4, iceberg_6_a5, iceberg_6_a6] +] diff --git a/worlds/kdl3/Names/EnemyAbilities.py b/worlds/kdl3/names/enemy_abilities.py similarity index 99% rename from worlds/kdl3/Names/EnemyAbilities.py rename to worlds/kdl3/names/enemy_abilities.py index 016e3033..ace15054 100644 --- a/worlds/kdl3/Names/EnemyAbilities.py +++ b/worlds/kdl3/names/enemy_abilities.py @@ -809,7 +809,7 @@ vanilla_enemies = {'Waddle Dee': 'No Ability', enemy_restrictive: List[Tuple[List[str], List[str]]] = [ # abilities, enemies, set_all (False to set any) - (["Burning Ability", "Stone Ability"], ["Rocky", "Sparky", "Babut", "Squishy", ]), # Ribbon Field 5 - 7 + (["Stone Ability"], ["Rocky", "Sparky", "Babut", "Squishy", ]), # Ribbon Field 5 - 7 # Sand Canyon 6 (["Parasol Ability", "Cutter Ability"], ['Bukiset (Parasol)', 'Bukiset (Cutter)']), (["Spark Ability", "Clean Ability"], ['Bukiset (Spark)', 'Bukiset (Clean)']), diff --git a/worlds/kdl3/Names/LocationName.py b/worlds/kdl3/names/location_name.py similarity index 100% rename from worlds/kdl3/Names/LocationName.py rename to worlds/kdl3/names/location_name.py diff --git a/worlds/kdl3/Options.py b/worlds/kdl3/options.py similarity index 82% rename from worlds/kdl3/Options.py rename to worlds/kdl3/options.py index e0a4f12f..b9163794 100644 --- a/worlds/kdl3/Options.py +++ b/worlds/kdl3/options.py @@ -1,13 +1,21 @@ import random from dataclasses import dataclass +from typing import List -from Options import DeathLink, Choice, Toggle, OptionDict, Range, PlandoBosses, DefaultOnToggle, \ - PerGameCommonOptions, PlandoConnections -from .Names import LocationName +from Options import DeathLinkMixin, Choice, Toggle, OptionDict, Range, PlandoBosses, DefaultOnToggle, \ + PerGameCommonOptions, Visibility, NamedRange, OptionGroup, PlandoConnections +from .names import location_name + + +class RemoteItems(DefaultOnToggle): + """ + Enables receiving items from your own world, primarily for co-op play. + """ + display_name = "Remote Items" class KDL3PlandoConnections(PlandoConnections): - entrances = exits = {f"{i} {j}" for i in LocationName.level_names for j in range(1, 7)} + entrances = exits = {f"{i} {j}" for i in location_name.level_names for j in range(1, 7)} class Goal(Choice): @@ -30,6 +38,7 @@ class Goal(Choice): return cls.name_lookup[value].upper() return super().get_option_name(value) + class GoalSpeed(Choice): """ Normal: the goal is unlocked after purifying the five bosses @@ -40,13 +49,14 @@ class GoalSpeed(Choice): option_fast = 1 -class TotalHeartStars(Range): +class MaxHeartStars(Range): """ Maximum number of heart stars to include in the pool of items. + If fewer available locations exist in the pool than this number, the number of available locations will be used instead. """ display_name = "Max Heart Stars" range_start = 5 # set to 5 so strict bosses does not degrade - range_end = 50 # 30 default locations + 30 stage clears + 5 bosses - 14 progression items = 51, so round down + range_end = 99 # previously set to 50, set to highest it can be should there be less locations than heart stars default = 30 @@ -84,9 +94,9 @@ class BossShuffle(PlandoBosses): Singularity: All (non-Zero) bosses will be replaced with a single boss Supports plando placement. """ - bosses = frozenset(LocationName.boss_names.keys()) + bosses = frozenset(location_name.boss_names.keys()) - locations = frozenset(LocationName.level_names.keys()) + locations = frozenset(location_name.level_names.keys()) duplicate_bosses = True @@ -278,7 +288,8 @@ class KirbyFlavorPreset(Choice): option_orange = 11 option_lime = 12 option_lavender = 13 - option_custom = 14 + option_miku = 14 + option_custom = 15 default = 0 @classmethod @@ -296,6 +307,7 @@ class KirbyFlavor(OptionDict): A custom color for Kirby. To use a custom color, set the preset to Custom and then define a dict of keys from "1" to "15", with their values being an HTML hex color. """ + display_name = "Custom Kirby Flavor" default = { "1": "B01810", "2": "F0E0E8", @@ -313,6 +325,7 @@ class KirbyFlavor(OptionDict): "14": "F8F8F8", "15": "B03830", } + visibility = Visibility.template | Visibility.spoiler # likely never supported on guis class GooeyFlavorPreset(Choice): @@ -352,6 +365,7 @@ class GooeyFlavor(OptionDict): A custom color for Gooey. To use a custom color, set the preset to Custom and then define a dict of keys from "1" to "15", with their values being an HTML hex color. """ + display_name = "Custom Gooey Flavor" default = { "1": "000808", "2": "102838", @@ -363,6 +377,7 @@ class GooeyFlavor(OptionDict): "8": "D0C0C0", "9": "F8F8F8", } + visibility = Visibility.template | Visibility.spoiler # likely never supported on guis class MusicShuffle(Choice): @@ -402,14 +417,27 @@ class Gifting(Toggle): display_name = "Gifting" +class TotalHeartStars(NamedRange): + """ + Deprecated. Use max_heart_stars instead. Supported for only one version. + """ + default = -1 + range_start = 5 + range_end = 99 + special_range_names = { + "default": -1 + } + visibility = Visibility.none + + @dataclass -class KDL3Options(PerGameCommonOptions): +class KDL3Options(PerGameCommonOptions, DeathLinkMixin): + remote_items: RemoteItems plando_connections: KDL3PlandoConnections - death_link: DeathLink game_language: GameLanguage goal: Goal goal_speed: GoalSpeed - total_heart_stars: TotalHeartStars + max_heart_stars: MaxHeartStars heart_stars_required: HeartStarsRequired filler_percentage: FillerPercentage trap_percentage: TrapPercentage @@ -435,3 +463,17 @@ class KDL3Options(PerGameCommonOptions): gooey_flavor: GooeyFlavor music_shuffle: MusicShuffle virtual_console: VirtualConsoleChanges + + total_heart_stars: TotalHeartStars # remove in 2 versions + + +kdl3_option_groups: List[OptionGroup] = [ + OptionGroup("Goal Options", [Goal, GoalSpeed, MaxHeartStars, HeartStarsRequired, JumpingTarget, ]), + OptionGroup("World Options", [RemoteItems, StrictBosses, OpenWorld, OpenWorldBossRequirement, ConsumableChecks, + StarChecks, FillerPercentage, TrapPercentage, GooeyTrapPercentage, + SlowTrapPercentage, AbilityTrapPercentage, LevelShuffle, BossShuffle, + AnimalRandomization, CopyAbilityRandomization, BossRequirementRandom, + Gifting, ]), + OptionGroup("Cosmetic Options", [GameLanguage, BossShuffleAllowBB, KirbyFlavorPreset, KirbyFlavor, + GooeyFlavorPreset, GooeyFlavor, MusicShuffle, VirtualConsoleChanges, ]), +] diff --git a/worlds/kdl3/Presets.py b/worlds/kdl3/presets.py similarity index 98% rename from worlds/kdl3/Presets.py rename to worlds/kdl3/presets.py index d3a7146d..491ad9dc 100644 --- a/worlds/kdl3/Presets.py +++ b/worlds/kdl3/presets.py @@ -25,6 +25,7 @@ all_random = { "ow_boss_requirement": "random", "boss_requirement_random": "random", "consumables": "random", + "starsanity": "random", "kirby_flavor_preset": "random", "gooey_flavor_preset": "random", "music_shuffle": "random", diff --git a/worlds/kdl3/Regions.py b/worlds/kdl3/regions.py similarity index 66% rename from worlds/kdl3/Regions.py rename to worlds/kdl3/regions.py index 407dcf96..c47e5dee 100644 --- a/worlds/kdl3/Regions.py +++ b/worlds/kdl3/regions.py @@ -1,60 +1,62 @@ import orjson import os from pkgutil import get_data +from copy import deepcopy -from typing import TYPE_CHECKING, List, Dict, Optional, Union -from BaseClasses import Region +from typing import TYPE_CHECKING, List, Dict, Optional, Union, Callable +from BaseClasses import Region, CollectionState from worlds.generic.Rules import add_item_rule -from .Locations import KDL3Location -from .Names import LocationName -from .Options import BossShuffle -from .Room import KDL3Room +from .locations import KDL3Location +from .names import location_name +from .options import BossShuffle +from .room import KDL3Room if TYPE_CHECKING: from . import KDL3World default_levels = { - 1: [0x770001, 0x770002, 0x770003, 0x770004, 0x770005, 0x770006, 0x770200], - 2: [0x770007, 0x770008, 0x770009, 0x77000A, 0x77000B, 0x77000C, 0x770201], - 3: [0x77000D, 0x77000E, 0x77000F, 0x770010, 0x770011, 0x770012, 0x770202], - 4: [0x770013, 0x770014, 0x770015, 0x770016, 0x770017, 0x770018, 0x770203], - 5: [0x770019, 0x77001A, 0x77001B, 0x77001C, 0x77001D, 0x77001E, 0x770204], + 1: [0x770000, 0x770001, 0x770002, 0x770003, 0x770004, 0x770005, 0x770200], + 2: [0x770006, 0x770007, 0x770008, 0x770009, 0x77000A, 0x77000B, 0x770201], + 3: [0x77000C, 0x77000D, 0x77000E, 0x77000F, 0x770010, 0x770011, 0x770202], + 4: [0x770012, 0x770013, 0x770014, 0x770015, 0x770016, 0x770017, 0x770203], + 5: [0x770018, 0x770019, 0x77001A, 0x77001B, 0x77001C, 0x77001D, 0x770204], } first_stage_blacklist = { # We want to confirm that the first stage can be completed without any items - 0x77000B, # 2-5 needs Kine - 0x770011, # 3-5 needs Cutter - 0x77001C, # 5-4 needs Burning + 0x77000A, # 2-5 needs Kine + 0x770010, # 3-5 needs Cutter + 0x77001B, # 5-4 needs Burning } first_world_limit = { # We need to limit the number of very restrictive stages in level 1 on solo gens *first_stage_blacklist, # all three of the blacklist stages need 2+ items for both checks + 0x770006, 0x770007, - 0x770008, - 0x770013, - 0x77001E, + 0x770012, + 0x77001D, } def generate_valid_level(world: "KDL3World", level: int, stage: int, - possible_stages: List[int], placed_stages: List[int]): + possible_stages: List[int], placed_stages: List[Optional[int]]) -> int: new_stage = world.random.choice(possible_stages) if level == 1: if stage == 0 and new_stage in first_stage_blacklist: + possible_stages.remove(new_stage) return generate_valid_level(world, level, stage, possible_stages, placed_stages) elif (not (world.multiworld.players > 1 or world.options.consumables or world.options.starsanity) and - new_stage in first_world_limit and - sum(p_stage in first_world_limit for p_stage in placed_stages) + new_stage in first_world_limit and + sum(p_stage in first_world_limit for p_stage in placed_stages) >= (2 if world.options.open_world else 1)): return generate_valid_level(world, level, stage, possible_stages, placed_stages) return new_stage -def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]): - level_names = {LocationName.level_names[level]: level for level in LocationName.level_names} +def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]) -> None: + level_names = {location_name.level_names[level]: level for level in location_name.level_names} room_data = orjson.loads(get_data(__name__, os.path.join("data", "Rooms.json"))) rooms: Dict[str, KDL3Room] = dict() for room_entry in room_data: @@ -63,7 +65,7 @@ def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]): room_entry["default_exits"], room_entry["animal_pointers"], room_entry["enemies"], room_entry["entity_load"], room_entry["consumables"], room_entry["consumables_pointer"]) room.add_locations({location: world.location_name_to_id[location] if location in world.location_name_to_id else - None for location in room_entry["locations"] + None for location in room_entry["locations"] if (not any(x in location for x in ["1-Up", "Maxim"]) or world.options.consumables.value) and ("Star" not in location or world.options.starsanity.value)}, @@ -83,8 +85,8 @@ def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]): if room.stage == 7: first_rooms[0x770200 + room.level - 1] = room else: - first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage] = room - exits = dict() + first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage - 1] = room + exits: Dict[str, Callable[[CollectionState], bool]] = dict() for def_exit in room.default_exits: target = f"{level_names[room.level]} {room.stage} - {def_exit['room']}" access_rule = tuple(def_exit["access_rule"]) @@ -115,50 +117,54 @@ def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]): if world.options.open_world: level_regions[level].add_exits([first_rooms[0x770200 + level - 1].name]) else: - world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][5]], world.player)\ + world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][5]], world.player) \ .parent_region.add_exits([first_rooms[0x770200 + level - 1].name]) -def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_pattern: bool) -> dict: - levels: Dict[int, List[Optional[int]]] = { - 1: [None] * 7, - 2: [None] * 7, - 3: [None] * 7, - 4: [None] * 7, - 5: [None] * 7, - } +def generate_valid_levels(world: "KDL3World", shuffle_mode: int) -> Dict[int, List[int]]: + if shuffle_mode: + levels: Dict[int, List[Optional[int]]] = { + 1: [None] * 7, + 2: [None] * 7, + 3: [None] * 7, + 4: [None] * 7, + 5: [None] * 7, + } - possible_stages = [default_levels[level][stage] for level in default_levels for stage in range(6)] - if world.options.plando_connections: - for connection in world.options.plando_connections: - try: - entrance_world, entrance_stage = connection.entrance.rsplit(" ", 1) - stage_world, stage_stage = connection.exit.rsplit(" ", 1) - new_stage = default_levels[LocationName.level_names[stage_world.strip()]][int(stage_stage) - 1] - levels[LocationName.level_names[entrance_world.strip()]][int(entrance_stage) - 1] = new_stage - possible_stages.remove(new_stage) + possible_stages = [default_levels[level][stage] for level in default_levels for stage in range(6)] + if world.options.plando_connections: + for connection in world.options.plando_connections: + try: + entrance_world, entrance_stage = connection.entrance.rsplit(" ", 1) + stage_world, stage_stage = connection.exit.rsplit(" ", 1) + new_stage = default_levels[location_name.level_names[stage_world.strip()]][int(stage_stage) - 1] + levels[location_name.level_names[entrance_world.strip()]][int(entrance_stage) - 1] = new_stage + possible_stages.remove(new_stage) - except Exception: - raise Exception( - f"Invalid connection: {connection.entrance} =>" - f" {connection.exit} for player {world.player} ({world.player_name})") + except Exception: + raise Exception( + f"Invalid connection: {connection.entrance} =>" + f" {connection.exit} for player {world.player} ({world.player_name})") - for level in range(1, 6): - for stage in range(6): - # Randomize bosses separately - try: + for level in range(1, 6): + for stage in range(6): + # Randomize bosses separately if levels[level][stage] is None: stage_candidates = [candidate for candidate in possible_stages - if (enforce_world and candidate in default_levels[level]) - or (enforce_pattern and ((candidate - 1) & 0x00FFFF) % 6 == stage) - or (enforce_pattern == enforce_world) + if (shuffle_mode == 1 and candidate in default_levels[level]) + or (shuffle_mode == 2 and (candidate & 0x00FFFF) % 6 == stage) + or (shuffle_mode == 3) ] + if not stage_candidates: + raise Exception( + f"Failed to find valid stage for {level}-{stage}. Remaining Stages:{possible_stages}") new_stage = generate_valid_level(world, level, stage, stage_candidates, levels[level]) possible_stages.remove(new_stage) levels[level][stage] = new_stage - except Exception: - raise Exception(f"Failed to find valid stage for {level}-{stage}. Remaining Stages:{possible_stages}") - + else: + levels = deepcopy(default_levels) + for level in levels: + levels[level][6] = None # now handle bosses boss_shuffle: Union[int, str] = world.options.boss_shuffle.value plando_bosses = [] @@ -168,17 +174,17 @@ def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_patte boss_shuffle = BossShuffle.options[options.pop()] for option in options: if "-" in option: - loc, boss = option.split("-") + loc, plando_boss = option.split("-") loc = loc.title() - boss = boss.title() - levels[LocationName.level_names[loc]][6] = LocationName.boss_names[boss] - plando_bosses.append(LocationName.boss_names[boss]) + plando_boss = plando_boss.title() + levels[location_name.level_names[loc]][6] = location_name.boss_names[plando_boss] + plando_bosses.append(location_name.boss_names[plando_boss]) else: option = option.title() for level in levels: if levels[level][6] is None: - levels[level][6] = LocationName.boss_names[option] - plando_bosses.append(LocationName.boss_names[option]) + levels[level][6] = location_name.boss_names[option] + plando_bosses.append(location_name.boss_names[option]) if boss_shuffle > 0: if boss_shuffle == BossShuffle.option_full: @@ -223,15 +229,14 @@ def create_levels(world: "KDL3World") -> None: 5: level5, } level_shuffle = world.options.stage_shuffle.value - if level_shuffle != 0: - world.player_levels = generate_valid_levels( - world, - level_shuffle == 1, - level_shuffle == 2) + if hasattr(world.multiworld, "re_gen_passthrough"): + world.player_levels = getattr(world.multiworld, "re_gen_passthrough")["Kirby's Dream Land 3"]["player_levels"] + else: + world.player_levels = generate_valid_levels(world, level_shuffle) generate_rooms(world, levels) - level6.add_locations({LocationName.goals[world.options.goal]: None}, KDL3Location) + level6.add_locations({location_name.goals[world.options.goal.value]: None}, KDL3Location) menu.connect(level1, "Start Game") level1.connect(level2, "To Level 2") diff --git a/worlds/kdl3/rom.py b/worlds/kdl3/rom.py new file mode 100644 index 00000000..3dd10ce1 --- /dev/null +++ b/worlds/kdl3/rom.py @@ -0,0 +1,602 @@ +import typing +from pkgutil import get_data + +import Utils +from typing import Optional, TYPE_CHECKING, Tuple, Dict, List +import hashlib +import os +import struct + +import settings +from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension +from .aesthetics import get_palette_bytes, kirby_target_palettes, get_kirby_palette, gooey_target_palettes, \ + get_gooey_palette +from .compression import hal_decompress +import bsdiff4 + +if TYPE_CHECKING: + from . import KDL3World + +KDL3UHASH = "201e7658f6194458a3869dde36bf8ec2" +KDL3JHASH = "b2f2d004ea640c3db66df958fce122b2" + +level_pointers = { + 0x770000: 0x0084, + 0x770001: 0x009C, + 0x770002: 0x00B8, + 0x770003: 0x00D8, + 0x770004: 0x0104, + 0x770005: 0x0124, + 0x770006: 0x014C, + 0x770007: 0x0170, + 0x770008: 0x0190, + 0x770009: 0x01B0, + 0x77000A: 0x01E8, + 0x77000B: 0x0218, + 0x77000C: 0x024C, + 0x77000D: 0x0270, + 0x77000E: 0x02A0, + 0x77000F: 0x02C4, + 0x770010: 0x02EC, + 0x770011: 0x0314, + 0x770012: 0x03CC, + 0x770013: 0x0404, + 0x770014: 0x042C, + 0x770015: 0x044C, + 0x770016: 0x0478, + 0x770017: 0x049C, + 0x770018: 0x04E4, + 0x770019: 0x0504, + 0x77001A: 0x0530, + 0x77001B: 0x0554, + 0x77001C: 0x05A8, + 0x77001D: 0x0640, + 0x770200: 0x0148, + 0x770201: 0x0248, + 0x770202: 0x03C8, + 0x770203: 0x04E0, + 0x770204: 0x06A4, + 0x770205: 0x06A8, +} + +bb_bosses = { + 0x770200: 0xED85F1, + 0x770201: 0xF01360, + 0x770202: 0xEDA3DF, + 0x770203: 0xEDC2B9, + 0x770204: 0xED7C3F, + 0x770205: 0xEC29D2, +} + +level_sprites = { + 0x19B2C6: 1827, + 0x1A195C: 1584, + 0x19F6F3: 1679, + 0x19DC8B: 1717, + 0x197900: 1872 +} + +stage_tiles = { + 0: [ + 0, 1, 2, + 16, 17, 18, + 32, 33, 34, + 48, 49, 50 + ], + 1: [ + 3, 4, 5, + 19, 20, 21, + 35, 36, 37, + 51, 52, 53 + ], + 2: [ + 6, 7, 8, + 22, 23, 24, + 38, 39, 40, + 54, 55, 56 + ], + 3: [ + 9, 10, 11, + 25, 26, 27, + 41, 42, 43, + 57, 58, 59, + ], + 4: [ + 12, 13, 64, + 28, 29, 65, + 44, 45, 66, + 60, 61, 67 + ], + 5: [ + 14, 15, 68, + 30, 31, 69, + 46, 47, 70, + 62, 63, 71 + ] +} + +heart_star_address = 0x2D0000 +heart_star_size = 456 +consumable_address = 0x2F91DD +consumable_size = 698 + +stage_palettes = [0x60964, 0x60B64, 0x60D64, 0x60F64, 0x61164] + +music_choices = [ + 2, # Boss 1 + 3, # Boss 2 (Unused) + 4, # Boss 3 (Miniboss) + 7, # Dedede + 9, # Event 2 (used once) + 10, # Field 1 + 11, # Field 2 + 12, # Field 3 + 13, # Field 4 + 14, # Field 5 + 15, # Field 6 + 16, # Field 7 + 17, # Field 8 + 18, # Field 9 + 19, # Field 10 + 20, # Field 11 + 21, # Field 12 (Gourmet Race) + 23, # Dark Matter in the Hyper Zone + 24, # Zero + 25, # Level 1 + 26, # Level 2 + 27, # Level 4 + 28, # Level 3 + 29, # Heart Star Failed + 30, # Level 5 + 31, # Minigame + 38, # Animal Friend 1 + 39, # Animal Friend 2 + 40, # Animal Friend 3 +] +# extra room pointers we don't want to track other than for music +room_music = { + 3079990: 23, # Zero + 2983409: 2, # BB Whispy + 3150688: 2, # BB Acro + 2991071: 2, # BB PonCon + 2998969: 2, # BB Ado + 2980927: 7, # BB Dedede + 2894290: 23 # BB Zero +} + +enemy_remap = { + "Waddle Dee": 0, + "Bronto Burt": 2, + "Rocky": 3, + "Bobo": 5, + "Chilly": 6, + "Poppy Bros Jr.": 7, + "Sparky": 8, + "Polof": 9, + "Broom Hatter": 11, + "Cappy": 12, + "Bouncy": 13, + "Nruff": 15, + "Glunk": 16, + "Togezo": 18, + "Kabu": 19, + "Mony": 20, + "Blipper": 21, + "Squishy": 22, + "Gabon": 24, + "Oro": 25, + "Galbo": 26, + "Sir Kibble": 27, + "Nidoo": 28, + "Kany": 29, + "Sasuke": 30, + "Yaban": 32, + "Boten": 33, + "Coconut": 34, + "Doka": 35, + "Icicle": 36, + "Pteran": 39, + "Loud": 40, + "Como": 41, + "Klinko": 42, + "Babut": 43, + "Wappa": 44, + "Mariel": 45, + "Tick": 48, + "Apolo": 49, + "Popon Ball": 50, + "KeKe": 51, + "Magoo": 53, + "Raft Waddle Dee": 57, + "Madoo": 58, + "Corori": 60, + "Kapar": 67, + "Batamon": 68, + "Peran": 72, + "Bobin": 73, + "Mopoo": 74, + "Gansan": 75, + "Bukiset (Burning)": 76, + "Bukiset (Stone)": 77, + "Bukiset (Ice)": 78, + "Bukiset (Needle)": 79, + "Bukiset (Clean)": 80, + "Bukiset (Parasol)": 81, + "Bukiset (Spark)": 82, + "Bukiset (Cutter)": 83, + "Waddle Dee Drawing": 84, + "Bronto Burt Drawing": 85, + "Bouncy Drawing": 86, + "Kabu (Dekabu)": 87, + "Wapod": 88, + "Propeller": 89, + "Dogon": 90, + "Joe": 91 +} + +miniboss_remap = { + "Captain Stitch": 0, + "Yuki": 1, + "Blocky": 2, + "Jumper Shoot": 3, + "Boboo": 4, + "Haboki": 5 +} + +ability_remap = { + "No Ability": 0, + "Burning Ability": 1, + "Stone Ability": 2, + "Ice Ability": 3, + "Needle Ability": 4, + "Clean Ability": 5, + "Parasol Ability": 6, + "Spark Ability": 7, + "Cutter Ability": 8, +} + + +class RomData: + def __init__(self, file: bytes, name: typing.Optional[str] = None): + self.file = bytearray(file) + self.name = name + + def read_byte(self, offset: int) -> int: + return self.file[offset] + + def read_bytes(self, offset: int, length: int) -> bytearray: + return self.file[offset:offset + length] + + def write_byte(self, offset: int, value: int) -> None: + self.file[offset] = value + + def write_bytes(self, offset: int, values: typing.Sequence[int]) -> None: + self.file[offset:offset + len(values)] = values + + def get_bytes(self) -> bytes: + return bytes(self.file) + + +def handle_level_sprites(stages: List[Tuple[int, ...]], sprites: List[bytearray], palettes: List[List[bytearray]]) \ + -> Tuple[List[bytearray], List[bytearray]]: + palette_by_level = list() + for palette in palettes: + palette_by_level.extend(palette[10:16]) + out_palettes = list() + for i in range(5): + for j in range(6): + palettes[i][10 + j] = palette_by_level[stages[i][j]] + out_palettes.append(bytearray([x for palette in palettes[i] for x in palette])) + tiles_by_level = list() + for spritesheet in sprites: + decompressed = hal_decompress(spritesheet) + tiles = [decompressed[i:i + 32] for i in range(0, 2304, 32)] + tiles_by_level.extend([[tiles[x] for x in stage_tiles[stage]] for stage in stage_tiles]) + out_sprites = list() + for world in range(5): + levels = [stages[world][x] for x in range(6)] + world_tiles: typing.List[bytes] = [bytes() for _ in range(72)] + for i in range(6): + for x in range(12): + world_tiles[stage_tiles[i][x]] = tiles_by_level[levels[i]][x] + out_sprites.append(bytearray()) + for tile in world_tiles: + out_sprites[world].extend(tile) + # insert our fake compression + out_sprites[world][0:0] = [0xe3, 0xff] + out_sprites[world][1026:1026] = [0xe3, 0xff] + out_sprites[world][2052:2052] = [0xe0, 0xff] + out_sprites[world].append(0xff) + return out_sprites, out_palettes + + +def write_heart_star_sprites(rom: RomData) -> None: + compressed = rom.read_bytes(heart_star_address, heart_star_size) + decompressed = hal_decompress(compressed) + patch = get_data(__name__, os.path.join("data", "APHeartStar.bsdiff4")) + patched = bytearray(bsdiff4.patch(decompressed, patch)) + rom.write_bytes(0x1AF7DF, patched) + patched[0:0] = [0xE3, 0xFF] + patched.append(0xFF) + rom.write_bytes(0x1CD000, patched) + rom.write_bytes(0x3F0EBF, [0x00, 0xD0, 0x39]) + + +def write_consumable_sprites(rom: RomData, consumables: bool, stars: bool) -> None: + compressed = rom.read_bytes(consumable_address, consumable_size) + decompressed = hal_decompress(compressed) + patched = bytearray(decompressed) + if consumables: + patch = get_data(__name__, os.path.join("data", "APConsumable.bsdiff4")) + patched = bytearray(bsdiff4.patch(bytes(patched), patch)) + if stars: + patch = get_data(__name__, os.path.join("data", "APStars.bsdiff4")) + patched = bytearray(bsdiff4.patch(bytes(patched), patch)) + patched[0:0] = [0xE3, 0xFF] + patched.append(0xFF) + rom.write_bytes(0x1CD500, patched) + rom.write_bytes(0x3F0DAE, [0x00, 0xD5, 0x39]) + + +class KDL3PatchExtensions(APPatchExtension): + game = "Kirby's Dream Land 3" + + @staticmethod + def apply_post_patch(_: APProcedurePatch, rom: bytes) -> bytes: + rom_data = RomData(rom) + write_heart_star_sprites(rom_data) + if rom_data.read_bytes(0x3D014, 1)[0] > 0: + stages = [struct.unpack("HHHHHHH", rom_data.read_bytes(0x3D020 + x * 14, 14)) for x in range(5)] + palettes = [rom_data.read_bytes(full_pal, 512) for full_pal in stage_palettes] + read_palettes = [[palette[i:i + 32] for i in range(0, 512, 32)] for palette in palettes] + sprites = [rom_data.read_bytes(offset, level_sprites[offset]) for offset in level_sprites] + sprites, palettes = handle_level_sprites(stages, sprites, read_palettes) + for addr, palette in zip(stage_palettes, palettes): + rom_data.write_bytes(addr, palette) + for addr, level_sprite in zip([0x1CA000, 0x1CA920, 0x1CB230, 0x1CBB40, 0x1CC450], sprites): + rom_data.write_bytes(addr, level_sprite) + rom_data.write_bytes(0x460A, [0x00, 0xA0, 0x39, 0x20, 0xA9, 0x39, 0x30, 0xB2, 0x39, 0x40, 0xBB, 0x39, + 0x50, 0xC4, 0x39]) + write_consumable_sprites(rom_data, rom_data.read_byte(0x3D018) > 0, rom_data.read_byte(0x3D01A) > 0) + return rom_data.get_bytes() + + +class KDL3ProcedurePatch(APProcedurePatch, APTokenMixin): + hash = [KDL3UHASH, KDL3JHASH] + game = "Kirby's Dream Land 3" + patch_file_ending = ".apkdl3" + procedure = [ + ("apply_bsdiff4", ["kdl3_basepatch.bsdiff4"]), + ("apply_tokens", ["token_patch.bin"]), + ("apply_post_patch", []), + ("calc_snes_crc", []) + ] + name: bytes # used to pass to __init__ + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + +def patch_rom(world: "KDL3World", patch: KDL3ProcedurePatch) -> None: + patch.write_file("kdl3_basepatch.bsdiff4", + get_data(__name__, os.path.join("data", "kdl3_basepatch.bsdiff4"))) + + # Write open world patch + if world.options.open_world: + patch.write_token(APTokenTypes.WRITE, 0x143C7, bytes([0xAD, 0xC1, 0x5A, 0xCD, 0xC1, 0x5A, ])) + # changes the stage flag function to compare $5AC1 to $5AC1, + # always running the "new stage" function + # This has further checks present for bosses already, so we just + # need to handle regular stages + # write check for boss to be unlocked + + if world.options.consumables: + # reroute maxim tomatoes to use the 1-UP function, then null out the function + patch.write_token(APTokenTypes.WRITE, 0x3002F, bytes([0x37, 0x00])) + patch.write_token(APTokenTypes.WRITE, 0x30037, bytes([0xA9, 0x26, 0x00, # LDA #$0026 + 0x22, 0x27, 0xD9, 0x00, # JSL $00D927 + 0xA4, 0xD2, # LDY $D2 + 0x6B, # RTL + 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, + 0xEA, # NOP #10 + ])) + + # stars handling is built into the rom, so no changes there + + rooms = world.rooms + if world.options.music_shuffle > 0: + if world.options.music_shuffle == 1: + shuffled_music = music_choices.copy() + world.random.shuffle(shuffled_music) + music_map = dict(zip(music_choices, shuffled_music)) + # Avoid putting star twinkle in the pool + music_map[5] = world.random.choice(music_choices) + # Heart Star music doesn't work on regular stages + music_map[8] = world.random.choice(music_choices) + for room in rooms: + room.music = music_map[room.music] + for room_ptr in room_music: + patch.write_token(APTokenTypes.WRITE, room_ptr + 2, bytes([music_map[room_music[room_ptr]]])) + for i, old_music in zip(range(5), [25, 26, 28, 27, 30]): + # level themes + patch.write_token(APTokenTypes.WRITE, 0x133F2 + i, bytes([music_map[old_music]])) + # Zero + patch.write_token(APTokenTypes.WRITE, 0x9AE79, music_map[0x18].to_bytes(1, "little")) + # Heart Star success and fail + patch.write_token(APTokenTypes.WRITE, 0x4A388, music_map[0x08].to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x4A38D, music_map[0x1D].to_bytes(1, "little")) + elif world.options.music_shuffle == 2: + for room in rooms: + room.music = world.random.choice(music_choices) + for room_ptr in room_music: + patch.write_token(APTokenTypes.WRITE, room_ptr + 2, + world.random.choice(music_choices).to_bytes(1, "little")) + for i in range(5): + # level themes + patch.write_token(APTokenTypes.WRITE, 0x133F2 + i, + world.random.choice(music_choices).to_bytes(1, "little")) + # Zero + patch.write_token(APTokenTypes.WRITE, 0x9AE79, world.random.choice(music_choices).to_bytes(1, "little")) + # Heart Star success and fail + patch.write_token(APTokenTypes.WRITE, 0x4A388, world.random.choice(music_choices).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x4A38D, world.random.choice(music_choices).to_bytes(1, "little")) + + for room in rooms: + room.patch(patch, bool(world.options.consumables.value), not bool(world.options.remote_items.value)) + + if world.options.virtual_console in [1, 3]: + # Flash Reduction + patch.write_token(APTokenTypes.WRITE, 0x9AE68, b"\x10") + patch.write_token(APTokenTypes.WRITE, 0x9AE8E, bytes([0x08, 0x00, 0x22, 0x5D, 0xF7, 0x00, 0xA2, 0x08, ])) + patch.write_token(APTokenTypes.WRITE, 0x9AEA1, b"\x08") + patch.write_token(APTokenTypes.WRITE, 0x9AEC9, b"\x01") + patch.write_token(APTokenTypes.WRITE, 0x9AED2, bytes([0xA9, 0x1F])) + patch.write_token(APTokenTypes.WRITE, 0x9AEE1, b"\x08") + + if world.options.virtual_console in [2, 3]: + # Hyper Zone BB colors + patch.write_token(APTokenTypes.WRITE, 0x2C5E16, bytes([0xEE, 0x1B, 0x18, 0x5B, 0xD3, 0x4A, 0xF4, 0x3B, ])) + patch.write_token(APTokenTypes.WRITE, 0x2C8217, bytes([0xFF, 0x1E, ])) + + # boss requirements + patch.write_token(APTokenTypes.WRITE, 0x3D000, + struct.pack("HHHHH", world.boss_requirements[0], world.boss_requirements[1], + world.boss_requirements[2], world.boss_requirements[3], + world.boss_requirements[4])) + patch.write_token(APTokenTypes.WRITE, 0x3D00A, + struct.pack("H", world.required_heart_stars if world.options.goal_speed == 1 else 0xFFFF)) + patch.write_token(APTokenTypes.WRITE, 0x3D00C, world.options.goal_speed.value.to_bytes(2, "little")) + patch.write_token(APTokenTypes.WRITE, 0x3D00E, world.options.open_world.value.to_bytes(2, "little")) + patch.write_token(APTokenTypes.WRITE, 0x3D010, ((world.options.remote_items.value << 1) + + world.options.death_link.value).to_bytes(2, "little")) + patch.write_token(APTokenTypes.WRITE, 0x3D012, world.options.goal.value.to_bytes(2, "little")) + patch.write_token(APTokenTypes.WRITE, 0x3D014, world.options.stage_shuffle.value.to_bytes(2, "little")) + patch.write_token(APTokenTypes.WRITE, 0x3D016, world.options.ow_boss_requirement.value.to_bytes(2, "little")) + patch.write_token(APTokenTypes.WRITE, 0x3D018, world.options.consumables.value.to_bytes(2, "little")) + patch.write_token(APTokenTypes.WRITE, 0x3D01A, world.options.starsanity.value.to_bytes(2, "little")) + patch.write_token(APTokenTypes.WRITE, 0x3D01C, world.options.gifting.value.to_bytes(2, "little") + if world.multiworld.players > 1 else bytes([0, 0])) + patch.write_token(APTokenTypes.WRITE, 0x3D01E, world.options.strict_bosses.value.to_bytes(2, "little")) + # don't write gifting for solo game, since there's no one to send anything to + + for level in world.player_levels: + for i in range(len(world.player_levels[level])): + patch.write_token(APTokenTypes.WRITE, 0x3F002E + ((level - 1) * 14) + (i * 2), + struct.pack("H", level_pointers[world.player_levels[level][i]])) + patch.write_token(APTokenTypes.WRITE, 0x3D020 + (level - 1) * 14 + (i * 2), + struct.pack("H", world.player_levels[level][i] & 0x00FFFF)) + if (i == 0) or (i > 0 and i % 6 != 0): + patch.write_token(APTokenTypes.WRITE, 0x3D080 + (level - 1) * 12 + (i * 2), + struct.pack("H", (world.player_levels[level][i] & 0x00FFFF) % 6)) + + for i in range(6): + if world.boss_butch_bosses[i]: + patch.write_token(APTokenTypes.WRITE, 0x3F0000 + (level_pointers[0x770200 + i]), + struct.pack("I", bb_bosses[0x770200 + i])) + + # copy ability shuffle + if world.options.copy_ability_randomization.value > 0: + for enemy in world.copy_abilities: + if enemy in miniboss_remap: + patch.write_token(APTokenTypes.WRITE, 0xB417E + (miniboss_remap[enemy] << 1), + struct.pack("H", ability_remap[world.copy_abilities[enemy]])) + else: + patch.write_token(APTokenTypes.WRITE, 0xB3CAC + (enemy_remap[enemy] << 1), + struct.pack("H", ability_remap[world.copy_abilities[enemy]])) + # following only needs done on non-door rando + # incredibly lucky this follows the same order (including 5E == star block) + patch.write_token(APTokenTypes.WRITE, 0x2F77EA, + (0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2F7811, + (0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2F9BC4, + (0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2F9BEB, + (0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2FAC06, + (0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2FAC2D, + (0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2F9E7B, + (0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2F9EA2, + (0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2FA951, + (0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2FA978, + (0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2FA132, + (0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2FA159, + (0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2FA3E8, + (0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2FA40F, + (0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2F90E2, + (0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1)).to_bytes(1, "little")) + patch.write_token(APTokenTypes.WRITE, 0x2F9109, + (0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1)).to_bytes(1, "little")) + + if world.options.copy_ability_randomization == 2: + for enemy in enemy_remap: + # we just won't include it for minibosses + patch.write_token(APTokenTypes.WRITE, 0xB3E40 + (enemy_remap[enemy] << 1), + struct.pack("h", world.random.randint(-1, 2))) + + # write jumping goal + patch.write_token(APTokenTypes.WRITE, 0x94F8, struct.pack("H", world.options.jumping_target)) + patch.write_token(APTokenTypes.WRITE, 0x944E, struct.pack("H", world.options.jumping_target)) + + from Utils import __version__ + patch_name = bytearray( + f'KDL3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21] + patch_name.extend([0] * (21 - len(patch_name))) + patch.name = bytes(patch_name) + patch.write_token(APTokenTypes.WRITE, 0x3C000, patch.name) + patch.write_token(APTokenTypes.WRITE, 0x3C020, world.options.game_language.value.to_bytes(1, "little")) + + patch.write_token(APTokenTypes.COPY, 0x7FC0, (21, 0x3C000)) + patch.write_token(APTokenTypes.COPY, 0x7FD9, (1, 0x3C020)) + + # handle palette + if world.options.kirby_flavor_preset.value != 0: + for addr in kirby_target_palettes: + target = kirby_target_palettes[addr] + palette = get_kirby_palette(world) + if palette is not None: + patch.write_token(APTokenTypes.WRITE, addr, get_palette_bytes(palette, target[0], target[1], target[2])) + + if world.options.gooey_flavor_preset.value != 0: + for addr in gooey_target_palettes: + target = gooey_target_palettes[addr] + palette = get_gooey_palette(world) + if palette is not None: + patch.write_token(APTokenTypes.WRITE, addr, get_palette_bytes(palette, target[0], target[1], target[2])) + + patch.write_file("token_patch.bin", patch.get_token_binary()) + + +def get_base_rom_bytes() -> bytes: + rom_file: str = get_base_rom_path() + base_rom_bytes: Optional[bytes] = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + base_rom_bytes = bytes(Utils.read_snes_rom(open(rom_file, "rb"))) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if basemd5.hexdigest() not in {KDL3UHASH, KDL3JHASH}: + raise Exception("Supplied Base Rom does not match known MD5 for US or JP release. " + "Get the correct game and version, then dump it") + get_base_rom_bytes.base_rom_bytes = base_rom_bytes + return base_rom_bytes + + +def get_base_rom_path(file_name: str = "") -> str: + options: settings.Settings = settings.get_settings() + if not file_name: + file_name = options["kdl3_options"]["rom_file"] + if not os.path.exists(file_name): + file_name = Utils.user_path(file_name) + return file_name diff --git a/worlds/kdl3/room.py b/worlds/kdl3/room.py new file mode 100644 index 00000000..bcc1c7a7 --- /dev/null +++ b/worlds/kdl3/room.py @@ -0,0 +1,133 @@ +import struct +from typing import Optional, Dict, TYPE_CHECKING, List, Union +from BaseClasses import Region, ItemClassification, MultiWorld +from worlds.Files import APTokenTypes +from .client_addrs import consumable_addrs, star_addrs + +if TYPE_CHECKING: + from .rom import KDL3ProcedurePatch + +animal_map = { + "Rick Spawn": 0, + "Kine Spawn": 1, + "Coo Spawn": 2, + "Nago Spawn": 3, + "ChuChu Spawn": 4, + "Pitch Spawn": 5 +} + + +class KDL3Room(Region): + pointer: int = 0 + level: int = 0 + stage: int = 0 + room: int = 0 + music: int = 0 + default_exits: List[Dict[str, Union[int, List[str]]]] + animal_pointers: List[int] + enemies: List[str] + entity_load: List[List[int]] + consumables: List[Dict[str, Union[int, str]]] + + def __init__(self, name: str, player: int, multiworld: MultiWorld, hint: Optional[str], level: int, + stage: int, room: int, pointer: int, music: int, + default_exits: List[Dict[str, List[str]]], + animal_pointers: List[int], enemies: List[str], + entity_load: List[List[int]], + consumables: List[Dict[str, Union[int, str]]], consumable_pointer: int) -> None: + super().__init__(name, player, multiworld, hint) + self.level = level + self.stage = stage + self.room = room + self.pointer = pointer + self.music = music + self.default_exits = default_exits + self.animal_pointers = animal_pointers + self.enemies = enemies + self.entity_load = entity_load + self.consumables = consumables + self.consumable_pointer = consumable_pointer + + def patch(self, patch: "KDL3ProcedurePatch", consumables: bool, local_items: bool) -> None: + patch.write_token(APTokenTypes.WRITE, self.pointer + 2, self.music.to_bytes(1, "little")) + animals = [x.item.name for x in self.locations if "Animal" in x.name and x.item] + if len(animals) > 0: + for current_animal, address in zip(animals, self.animal_pointers): + patch.write_token(APTokenTypes.WRITE, self.pointer + address + 7, + animal_map[current_animal].to_bytes(1, "little")) + if local_items: + for location in self.get_locations(): + if location.item is None or location.item.player != self.player: + continue + item = location.item.code + if item is None: + continue + item_idx = item & 0x00000F + location_idx = location.address & 0xFFFF + if location_idx & 0xF00 in (0x300, 0x400, 0x500, 0x600): + # consumable or star, need remapped + location_base = location_idx & 0xF00 + if location_base == 0x300: + # consumable + location_idx = consumable_addrs[location_idx & 0xFF] | 0x1000 + else: + # star + location_idx = star_addrs[location.address] | 0x2000 + if item & 0x000070 == 0: + patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x10])) + elif item & 0x000010 > 0: + patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x20])) + elif item & 0x000020 > 0: + patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x40])) + elif item & 0x000040 > 0: + patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x80])) + + if consumables: + load_len = len(self.entity_load) + for consumable in self.consumables: + location = next(x for x in self.locations if x.name == consumable["name"]) + assert location.item is not None + is_progression = location.item.classification & ItemClassification.progression + if load_len == 8: + # edge case, there is exactly 1 room with 8 entities and only 1 consumable among them + if not (any(x in self.entity_load for x in [[0, 22], [1, 22]]) + and any(x in self.entity_load for x in [[2, 22], [3, 22]])): + replacement_target = self.entity_load.index( + next(x for x in self.entity_load if x in [[0, 22], [1, 22], [2, 22], [3, 22]])) + if is_progression: + vtype = 0 + else: + vtype = 2 + patch.write_token(APTokenTypes.WRITE, self.pointer + 88 + (replacement_target * 2), + vtype.to_bytes(1, "little")) + self.entity_load[replacement_target] = [vtype, 22] + else: + if is_progression: + # we need to see if 1-ups are in our load list + if any(x not in self.entity_load for x in [[0, 22], [1, 22]]): + self.entity_load.append([0, 22]) + else: + if any(x not in self.entity_load for x in [[2, 22], [3, 22]]): + # edge case: if (1, 22) is in, we need to load (3, 22) instead + if [1, 22] in self.entity_load: + self.entity_load.append([3, 22]) + else: + self.entity_load.append([2, 22]) + if load_len < len(self.entity_load): + patch.write_token(APTokenTypes.WRITE, self.pointer + 88 + (load_len * 2), + bytes(self.entity_load[load_len])) + patch.write_token(APTokenTypes.WRITE, self.pointer + 104 + (load_len * 2), + bytes(struct.pack("H", self.consumable_pointer))) + if is_progression: + if [1, 22] in self.entity_load: + vtype = 1 + else: + vtype = 0 + else: + if [3, 22] in self.entity_load: + vtype = 3 + else: + vtype = 2 + assert isinstance(consumable["pointer"], int) + patch.write_token(APTokenTypes.WRITE, self.pointer + consumable["pointer"] + 7, + vtype.to_bytes(1, "little")) diff --git a/worlds/kdl3/Rules.py b/worlds/kdl3/rules.py similarity index 70% rename from worlds/kdl3/Rules.py rename to worlds/kdl3/rules.py index 6a85ef84..a08e9925 100644 --- a/worlds/kdl3/Rules.py +++ b/worlds/kdl3/rules.py @@ -1,7 +1,7 @@ from worlds.generic.Rules import set_rule, add_rule -from .Names import LocationName, EnemyAbilities -from .Locations import location_table -from .Options import GoalSpeed +from .names import location_name, enemy_abilities, animal_friend_spawns +from .locations import location_table +from .options import GoalSpeed import typing if typing.TYPE_CHECKING: @@ -10,9 +10,9 @@ if typing.TYPE_CHECKING: def can_reach_boss(state: "CollectionState", player: int, level: int, open_world: int, - ow_boss_req: int, player_levels: typing.Dict[int, typing.Dict[int, int]]): + ow_boss_req: int, player_levels: typing.Dict[int, typing.List[int]]) -> bool: if open_world: - return state.has(f"{LocationName.level_names_inverse[level]} - Stage Completion", player, ow_boss_req) + return state.has(f"{location_name.level_names_inverse[level]} - Stage Completion", player, ow_boss_req) else: return state.can_reach(location_table[player_levels[level][5]], "Location", player) @@ -86,11 +86,11 @@ ability_map: typing.Dict[str, typing.Callable[["CollectionState", int], bool]] = } -def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]): +def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]) -> bool: # check animal requirements if not (can_reach_coo(state, player) and can_reach_kine(state, player)): return False - for abilities, bukisets in EnemyAbilities.enemy_restrictive[1:5]: + for abilities, bukisets in enemy_abilities.enemy_restrictive[1:5]: iterator = iter(x for x in bukisets if copy_abilities[x] in abilities) target_bukiset = next(iterator, None) can_reach = False @@ -103,7 +103,7 @@ def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typi return can_reach_parasol(state, player) and can_reach_stone(state, player) -def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]): +def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]) -> bool: can_reach = True for enemy in {"Sparky", "Blocky", "Jumper Shoot", "Yuki", "Sir Kibble", "Haboki", "Boboo", "Captain Stitch"}: can_reach = can_reach & ability_map[copy_abilities[enemy]](state, player) @@ -112,114 +112,114 @@ def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: t def set_rules(world: "KDL3World") -> None: # Level 1 - set_rule(world.multiworld.get_location(LocationName.grass_land_muchi, world.player), + set_rule(world.multiworld.get_location(location_name.grass_land_muchi, world.player), lambda state: can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.grass_land_chao, world.player), + set_rule(world.multiworld.get_location(location_name.grass_land_chao, world.player), lambda state: can_reach_stone(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.grass_land_mine, world.player), + set_rule(world.multiworld.get_location(location_name.grass_land_mine, world.player), lambda state: can_reach_kine(state, world.player)) # Level 2 - set_rule(world.multiworld.get_location(LocationName.ripple_field_5, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_5, world.player), lambda state: can_reach_kine(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.ripple_field_kamuribana, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_kamuribana, world.player), lambda state: can_reach_pitch(state, world.player) and can_reach_clean(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.ripple_field_bakasa, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_bakasa, world.player), lambda state: can_reach_kine(state, world.player) and can_reach_parasol(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.ripple_field_toad, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_toad, world.player), lambda state: can_reach_needle(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.ripple_field_mama_pitch, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_mama_pitch, world.player), lambda state: (can_reach_pitch(state, world.player) and can_reach_kine(state, world.player) and can_reach_burning(state, world.player) and can_reach_stone(state, world.player))) # Level 3 - set_rule(world.multiworld.get_location(LocationName.sand_canyon_5, world.player), + set_rule(world.multiworld.get_location(location_name.sand_canyon_5, world.player), lambda state: can_reach_cutter(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.sand_canyon_auntie, world.player), + set_rule(world.multiworld.get_location(location_name.sand_canyon_auntie, world.player), lambda state: can_reach_clean(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.sand_canyon_nyupun, world.player), + set_rule(world.multiworld.get_location(location_name.sand_canyon_nyupun, world.player), lambda state: can_reach_chuchu(state, world.player) and can_reach_cutter(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.sand_canyon_rob, world.player), + set_rule(world.multiworld.get_location(location_name.sand_canyon_rob, world.player), lambda state: can_assemble_rob(state, world.player, world.copy_abilities) ) # Level 4 - set_rule(world.multiworld.get_location(LocationName.cloudy_park_hibanamodoki, world.player), + set_rule(world.multiworld.get_location(location_name.cloudy_park_hibanamodoki, world.player), lambda state: can_reach_coo(state, world.player) and can_reach_clean(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.cloudy_park_piyokeko, world.player), + set_rule(world.multiworld.get_location(location_name.cloudy_park_piyokeko, world.player), lambda state: can_reach_needle(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.cloudy_park_mikarin, world.player), + set_rule(world.multiworld.get_location(location_name.cloudy_park_mikarin, world.player), lambda state: can_reach_coo(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.cloudy_park_pick, world.player), + set_rule(world.multiworld.get_location(location_name.cloudy_park_pick, world.player), lambda state: can_reach_rick(state, world.player)) # Level 5 - set_rule(world.multiworld.get_location(LocationName.iceberg_4, world.player), + set_rule(world.multiworld.get_location(location_name.iceberg_4, world.player), lambda state: can_reach_burning(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.iceberg_kogoesou, world.player), + set_rule(world.multiworld.get_location(location_name.iceberg_kogoesou, world.player), lambda state: can_reach_burning(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.iceberg_samus, world.player), + set_rule(world.multiworld.get_location(location_name.iceberg_samus, world.player), lambda state: can_reach_ice(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.iceberg_name, world.player), + set_rule(world.multiworld.get_location(location_name.iceberg_name, world.player), lambda state: (can_reach_coo(state, world.player) and can_reach_burning(state, world.player) and can_reach_chuchu(state, world.player))) # ChuChu is guaranteed here, but we use this for consistency - set_rule(world.multiworld.get_location(LocationName.iceberg_shiro, world.player), + set_rule(world.multiworld.get_location(location_name.iceberg_shiro, world.player), lambda state: can_reach_nago(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.iceberg_angel, world.player), + set_rule(world.multiworld.get_location(location_name.iceberg_angel, world.player), lambda state: can_fix_angel_wings(state, world.player, world.copy_abilities)) # Consumables if world.options.consumables: - set_rule(world.multiworld.get_location(LocationName.grass_land_1_u1, world.player), + set_rule(world.multiworld.get_location(location_name.grass_land_1_u1, world.player), lambda state: can_reach_parasol(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.grass_land_1_m1, world.player), + set_rule(world.multiworld.get_location(location_name.grass_land_1_m1, world.player), lambda state: can_reach_spark(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.grass_land_2_u1, world.player), + set_rule(world.multiworld.get_location(location_name.grass_land_2_u1, world.player), lambda state: can_reach_needle(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.ripple_field_2_u1, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_2_u1, world.player), lambda state: can_reach_kine(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.ripple_field_2_m1, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_2_m1, world.player), lambda state: can_reach_kine(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.ripple_field_3_u1, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_3_u1, world.player), lambda state: can_reach_cutter(state, world.player) or can_reach_spark(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.ripple_field_4_u1, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_4_u1, world.player), lambda state: can_reach_stone(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.ripple_field_4_m2, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_4_m2, world.player), lambda state: can_reach_stone(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.ripple_field_5_m1, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_5_m1, world.player), lambda state: can_reach_kine(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.ripple_field_5_u1, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_5_u1, world.player), lambda state: (can_reach_kine(state, world.player) and can_reach_burning(state, world.player) and can_reach_stone(state, world.player))) - set_rule(world.multiworld.get_location(LocationName.ripple_field_5_m2, world.player), + set_rule(world.multiworld.get_location(location_name.ripple_field_5_m2, world.player), lambda state: (can_reach_kine(state, world.player) and can_reach_burning(state, world.player) and can_reach_stone(state, world.player))) - set_rule(world.multiworld.get_location(LocationName.sand_canyon_4_u1, world.player), + set_rule(world.multiworld.get_location(location_name.sand_canyon_4_u1, world.player), lambda state: can_reach_clean(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.sand_canyon_4_m2, world.player), + set_rule(world.multiworld.get_location(location_name.sand_canyon_4_m2, world.player), lambda state: can_reach_needle(state, world.player)) - set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u2, world.player), + set_rule(world.multiworld.get_location(location_name.sand_canyon_5_u2, world.player), lambda state: can_reach_ice(state, world.player) and (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) or can_reach_nago(state, world.player))) - set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u3, world.player), + set_rule(world.multiworld.get_location(location_name.sand_canyon_5_u3, world.player), lambda state: can_reach_ice(state, world.player) and (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) or can_reach_nago(state, world.player))) - set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u4, world.player), + set_rule(world.multiworld.get_location(location_name.sand_canyon_5_u4, world.player), lambda state: can_reach_ice(state, world.player) and (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) or can_reach_nago(state, world.player))) - set_rule(world.multiworld.get_location(LocationName.cloudy_park_6_u1, world.player), + set_rule(world.multiworld.get_location(location_name.cloudy_park_6_u1, world.player), lambda state: can_reach_cutter(state, world.player)) if world.options.starsanity: @@ -274,50 +274,57 @@ def set_rules(world: "KDL3World") -> None: # copy ability access edge cases # Kirby cannot eat enemies fully submerged in water. Vast majority of cases, the enemy can be brought to the surface # and eaten by inhaling while falling on top of them - set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_2_E3, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_2_E3, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_3_E6, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_3_E6, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) # Ripple Field 4 E5, E7, and E8 are doable, but too strict to leave in logic - set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E5, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_4_E5, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E7, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_4_E7, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E8, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_4_E8, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E1, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_5_E1, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E2, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_5_E2, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E3, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_5_E3, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E4, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Ripple_Field_5_E4, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E7, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Sand_Canyon_4_E7, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E8, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Sand_Canyon_4_E8, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E9, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Sand_Canyon_4_E9, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) - set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E10, world.player), + set_rule(world.multiworld.get_location(enemy_abilities.Sand_Canyon_4_E10, world.player), lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) + # animal friend rules + set_rule(world.multiworld.get_location(animal_friend_spawns.iceberg_4_a2, world.player), + lambda state: can_reach_coo(state, world.player) and can_reach_burning(state, world.player)) + set_rule(world.multiworld.get_location(animal_friend_spawns.iceberg_4_a3, world.player), + lambda state: can_reach_chuchu(state, world.player) and can_reach_coo(state, world.player) + and can_reach_burning(state, world.player)) + for boss_flag, purification, i in zip(["Level 1 Boss - Purified", "Level 2 Boss - Purified", "Level 3 Boss - Purified", "Level 4 Boss - Purified", "Level 5 Boss - Purified"], - [LocationName.grass_land_whispy, LocationName.ripple_field_acro, - LocationName.sand_canyon_poncon, LocationName.cloudy_park_ado, - LocationName.iceberg_dedede], + [location_name.grass_land_whispy, location_name.ripple_field_acro, + location_name.sand_canyon_poncon, location_name.cloudy_park_ado, + location_name.iceberg_dedede], range(1, 6)): set_rule(world.multiworld.get_location(boss_flag, world.player), - lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1]) - and can_reach_boss(state, world.player, i, + lambda state, x=i: (state.has("Heart Star", world.player, world.boss_requirements[x - 1]) + and can_reach_boss(state, world.player, x, world.options.open_world.value, world.options.ow_boss_requirement.value, world.player_levels))) set_rule(world.multiworld.get_location(purification, world.player), - lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1]) - and can_reach_boss(state, world.player, i, + lambda state, x=i: (state.has("Heart Star", world.player, world.boss_requirements[x - 1]) + and can_reach_boss(state, world.player, x, world.options.open_world.value, world.options.ow_boss_requirement.value, world.player_levels))) @@ -327,12 +334,12 @@ def set_rules(world: "KDL3World") -> None: for level in range(2, 6): set_rule(world.multiworld.get_entrance(f"To Level {level}", world.player), - lambda state, i=level: state.has(f"Level {i - 1} Boss Defeated", world.player)) + lambda state, x=level: state.has(f"Level {x - 1} Boss Defeated", world.player)) if world.options.strict_bosses: for level in range(2, 6): add_rule(world.multiworld.get_entrance(f"To Level {level}", world.player), - lambda state, i=level: state.has(f"Level {i - 1} Boss Purified", world.player)) + lambda state, x=level: state.has(f"Level {x - 1} Boss Purified", world.player)) if world.options.goal_speed == GoalSpeed.option_normal: add_rule(world.multiworld.get_entrance("To Level 6", world.player), diff --git a/worlds/kdl3/data/APPauseIcons.dat b/worlds/kdl3/src/APPauseIcons.dat similarity index 100% rename from worlds/kdl3/data/APPauseIcons.dat rename to worlds/kdl3/src/APPauseIcons.dat diff --git a/worlds/kdl3/src/kdl3_basepatch.asm b/worlds/kdl3/src/kdl3_basepatch.asm index e419d063..95c85f03 100644 --- a/worlds/kdl3/src/kdl3_basepatch.asm +++ b/worlds/kdl3/src/kdl3_basepatch.asm @@ -58,6 +58,10 @@ org $01AFC8 org $01B013 SEC ; Remove Dedede Bad Ending +org $01B050 + JSL HookBossPurify + NOP + org $02B7B0 ; Zero unlock LDA $80A0 CMP #$0001 @@ -160,7 +164,6 @@ CopyAbilityAnimalOverride: STA $39DF, X RTL -org $079A00 HeartStarCheck: TXA CMP #$0000 ; is this level 1 @@ -201,7 +204,6 @@ HeartStarCheck: SEC RTL -org $079A80 OpenWorldUnlock: PHX LDX $900E ; Are we on open world? @@ -224,7 +226,6 @@ OpenWorldUnlock: PLX RTL -org $079B00 MainLoopHook: STA $D4 INC $3524 @@ -239,16 +240,18 @@ MainLoopHook: BEQ .Return ; return if we are LDA $5541 ; gooey status BPL .Slowness ; gooey is already spawned + LDA $39D1 ; is kirby alive? + BEQ .Slowness ; branch if he isn't + ; maybe BMI here too? LDA $8080 CMP #$0000 ; did we get a gooey trap BEQ .Slowness ; branch if we did not JSL GooeySpawn - STZ $8080 + DEC $8080 .Slowness: LDA $8082 ; slowness BEQ .Eject ; are we under the effects of a slowness trap - DEC - STA $8082 ; dec by 1 each frame + DEC $8082 ; dec by 1 each frame .Eject: PHX PHY @@ -258,14 +261,13 @@ MainLoopHook: BEQ .PullVars ; branch if we haven't received eject LDA #$2000 ; select button press STA $60C1 ; write to controller mirror - STZ $8084 + DEC $8084 .PullVars: PLY PLX .Return: RTL -org $079B80 HeartStarGraphicFix: LDA #$0000 PHX @@ -288,7 +290,7 @@ HeartStarGraphicFix: ASL TAX LDA $07D080, X ; table of original stage number - CMP #$0003 ; is the current stage a minigame stage? + CMP #$0002 ; is the current stage a minigame stage? BEQ .ReturnTrue ; branch if so CLC BRA .Return @@ -299,7 +301,6 @@ HeartStarGraphicFix: PLX RTL -org $079BF0 ParseItemQueue: ; Local item queue parsing NOP @@ -336,8 +337,6 @@ ParseItemQueue: AND #$000F ASL TAY - LDA $8080,Y - BNE .LoopCheck JSL .ApplyNegative RTL .ApplyAbility: @@ -418,35 +417,73 @@ ParseItemQueue: CPY #$0005 BCS .PlayNone LDA $8080,Y - BNE .Return + CPY #$0002 + BNE .Increment + CLC LDA #$0384 + ADC $8080, Y + BVC .PlayNegative + LDA #$FFFF + .PlayNegative: STA $8080,Y LDA #$00A7 BRA .PlaySFXLong + .Increment: + INC + STA $8080, Y + BRA .PlayNegative .PlayNone: LDA #$0000 BRA .PlaySFXLong -org $079D00 AnimalFriendSpawn: PHA CPX #$0002 ; is this an animal friend? BNE .Return XBA PHA + PHX + PHA + LDX #$0000 + .CheckSpawned: + LDA $05CA, X + BNE .Continue + LDA #$0002 + CMP $074A, X + BNE .ContinueCheck + PLA + PHA + XBA + CMP $07CA, X + BEQ .AlreadySpawned + .ContinueCheck: + INX + INX + BRA .CheckSpawned + .Continue: + PLA + PLX ASL TAY PLA INC CMP $8000, Y ; do we have this animal friend BEQ .Return ; we have this animal friend + .False: INX .Return: PLY LDA #$9999 RTL + .AlreadySpawned: + PLA + PLX + ASL + TAY + PLA + BRA .False + -org $079E00 WriteBWRAM: LDY #$6001 ;starting addr LDA #$1FFE ;bytes to write @@ -479,7 +516,6 @@ WriteBWRAM: .Return: RTL -org $079E80 ConsumableSet: PHA PHX @@ -507,7 +543,6 @@ ConsumableSet: ASL TAX LDA $07D020, X ; current stage - DEC ASL #6 TAX PLA @@ -519,8 +554,16 @@ ConsumableSet: BRA .LoopHead ; return to loop head .ApplyCheck: LDA $A000, X ; consumables index + PHA ORA #$0001 STA $A000, X + PLA + AND #$00FF + BNE .Return + TXA + ORA #$1000 + JSL ApplyLocalCheck + .Return: PLY PLX PLA @@ -528,7 +571,6 @@ ConsumableSet: AND #$00FF RTL -org $079F00 NormalGoalSet: PHX LDA $07D012 @@ -549,7 +591,6 @@ NormalGoalSet: STA $5AC1 ; cutscene RTL -org $079F80 FinalIcebergFix: PHX PHY @@ -572,7 +613,7 @@ FinalIcebergFix: ASL TAX LDA $07D020, X - CMP #$001E + CMP #$001D BEQ .ReturnTrue CLC BRA .Return @@ -583,7 +624,6 @@ FinalIcebergFix: PLX RTL -org $07A000 StrictBosses: PHX LDA $901E ; Do we have strict bosses enabled? @@ -610,7 +650,6 @@ StrictBosses: LDA $53CD RTL -org $07A030 NintenHalken: LDX #$0005 .Halken: @@ -628,7 +667,6 @@ NintenHalken: LDA #$0001 RTL -org $07A080 StageCompleteSet: PHX LDA $5AC1 ; completed stage cutscene @@ -656,9 +694,17 @@ StageCompleteSet: ASL TAX LDA $9020, X ; load the stage we completed - DEC ASL TAX + PHX + LDA $8200, X + AND #$00FF + BNE .ApplyClear + TXA + LSR + JSL ApplyLocalCheck + .ApplyClear: + PLX LDA #$0001 ORA $8200, X STA $8200, X @@ -668,7 +714,6 @@ StageCompleteSet: CMP $53CB RTL -org $07A100 OpenWorldBossUnlock: PHX PHY @@ -699,7 +744,6 @@ OpenWorldBossUnlock: .LoopStage: PLX LDY $9020, X ; get stage id - DEY INX INX PHA @@ -732,7 +776,6 @@ OpenWorldBossUnlock: PLX RTL -org $07A180 GooeySpawn: PHY PHX @@ -768,7 +811,6 @@ GooeySpawn: PLY RTL -org $07A200 SpeedTrap: PHX LDX $8082 ; do we have slowness @@ -780,7 +822,6 @@ SpeedTrap: EOR #$FFFF RTL -org $07A280 HeartStarVisual: CPX #$0000 BEQ .SkipInx @@ -844,7 +885,6 @@ HeartStarVisual: .Return: RTL -org $07A300 LoadFont: JSL $00D29F ; play sfx PHX @@ -915,7 +955,6 @@ LoadFont: PLX RTL -org $07A380 HeartStarVisual2: LDA #$2C80 STA $0000, Y @@ -1029,14 +1068,12 @@ HeartStarVisual2: STA $0000, Y RTL -org $07A480 HeartStarSelectFix: PHX TXA ASL TAX LDA $9020, X - DEC TAX .LoopHead: CMP #$0006 @@ -1051,15 +1088,31 @@ HeartStarSelectFix: AND #$00FF RTL -org $07A500 HeartStarCutsceneFix: TAX LDA $53D3 DEC STA $5AC3 + LDA $53A7, X + AND #$00FF + BNE .Return + PHX + TXA + .Loop: + CMP #$0007 + BCC .Continue + SEC + SBC #$0007 + DEX + BRA .Loop + .Continue: + TXA + ORA #$0100 + JSL ApplyLocalCheck + PLX + .Return RTL -org $07A510 GiftGiving: CMP #$0008 .This: @@ -1075,7 +1128,6 @@ GiftGiving: PLX JML $CABC18 -org $07A550 PauseMenu: JSL $00D29F PHX @@ -1136,7 +1188,6 @@ PauseMenu: PLX RTL -org $07A600 StarsSet: PHA PHX @@ -1166,7 +1217,6 @@ StarsSet: ASL TAX LDA $07D020, X - DEC ASL ASL ASL @@ -1183,8 +1233,15 @@ StarsSet: BRA .2LoopHead .2LoopEnd: LDA $B000, X + PHA ORA #$0001 STA $B000, X + PLA + AND #$00FF + BNE .Return + TXA + ORA #$2000 + JSL ApplyLocalCheck .Return: PLY PLX @@ -1199,6 +1256,48 @@ StarsSet: STA $39D7 BRA .Return +ApplyLocalCheck: +; args: A-address of check following $08B000 + TAX + LDA $09B000, X + AND #$00FF + TAY + LDX #$0000 + .Loop: + LDA $C000, X + BEQ .Apply + INX + INX + CPX #$0010 + BCC .Loop + BRA .Return ; this is dangerous, could lose a check here + .Apply: + TYA + STA $C000, X + .Return: + RTL + +HookBossPurify: + ORA $B0 + STA $53D5 + LDA $B0 + LDX #$0000 + LSR + .Loop: + BIT #$0001 + BNE .Apply + LSR + LSR + INX + CPX #$0005 + BCS .Return + BRA .Loop + .Apply: + TXA + ORA #$0200 + JSL ApplyLocalCheck + .Return: + RTL org $07C000 db "KDL3_BASEPATCH_ARCHI" @@ -1234,4 +1333,7 @@ org $07E040 db $3A, $01 db $3B, $05 db $3C, $05 - db $3D, $05 \ No newline at end of file + db $3D, $05 + +org $07F000 +incbin "APPauseIcons.dat" \ No newline at end of file diff --git a/worlds/kdl3/test/__init__.py b/worlds/kdl3/test/__init__.py index 4d3f4d70..92f1d726 100644 --- a/worlds/kdl3/test/__init__.py +++ b/worlds/kdl3/test/__init__.py @@ -6,6 +6,8 @@ from test.bases import WorldTestBase from test.general import gen_steps from worlds import AutoWorld from worlds.AutoWorld import call_all +# mypy: ignore-errors +# This is a copy of core code, and I'm not smart enough to solve the errors in here class KDL3TestBase(WorldTestBase): diff --git a/worlds/kdl3/test/test_goal.py b/worlds/kdl3/test/test_goal.py index ce53642a..2c6ae614 100644 --- a/worlds/kdl3/test/test_goal.py +++ b/worlds/kdl3/test/test_goal.py @@ -5,12 +5,12 @@ class TestFastGoal(KDL3TestBase): options = { "open_world": False, "goal_speed": "fast", - "total_heart_stars": 30, + "max_heart_stars": 30, "heart_stars_required": 50, "filler_percentage": 0, } - def test_goal(self): + def test_goal(self) -> None: self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") self.collect(heart_stars[0:14]) @@ -30,12 +30,12 @@ class TestNormalGoal(KDL3TestBase): options = { "open_world": False, "goal_speed": "normal", - "total_heart_stars": 30, + "max_heart_stars": 30, "heart_stars_required": 50, "filler_percentage": 0, } - def test_goal(self): + def test_goal(self) -> None: self.assertBeatable(False) heart_stars = self.get_items_by_name("Heart Star") self.collect(heart_stars[0:14]) @@ -51,14 +51,14 @@ class TestNormalGoal(KDL3TestBase): self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) self.assertBeatable(True) - def test_kine(self): + def test_kine(self) -> None: self.collect_by_name(["Cutter", "Burning", "Heart Star"]) self.assertBeatable(False) - def test_cutter(self): + def test_cutter(self) -> None: self.collect_by_name(["Kine", "Burning", "Heart Star"]) self.assertBeatable(False) - def test_burning(self): + def test_burning(self) -> None: self.collect_by_name(["Cutter", "Kine", "Heart Star"]) self.assertBeatable(False) diff --git a/worlds/kdl3/test/test_locations.py b/worlds/kdl3/test/test_locations.py index bde9abc4..024f1b11 100644 --- a/worlds/kdl3/test/test_locations.py +++ b/worlds/kdl3/test/test_locations.py @@ -1,6 +1,6 @@ from . import KDL3TestBase +from ..names import location_name from Options import PlandoConnection -from ..Names import LocationName import typing @@ -12,31 +12,31 @@ class TestLocations(KDL3TestBase): # these ensure we can always reach all stages physically } - def test_simple_heart_stars(self): - self.run_location_test(LocationName.grass_land_muchi, ["ChuChu"]) - self.run_location_test(LocationName.grass_land_chao, ["Stone"]) - self.run_location_test(LocationName.grass_land_mine, ["Kine"]) - self.run_location_test(LocationName.ripple_field_kamuribana, ["Pitch", "Clean"]) - self.run_location_test(LocationName.ripple_field_bakasa, ["Kine", "Parasol"]) - self.run_location_test(LocationName.ripple_field_toad, ["Needle"]) - self.run_location_test(LocationName.ripple_field_mama_pitch, ["Pitch", "Kine", "Burning", "Stone"]) - self.run_location_test(LocationName.sand_canyon_auntie, ["Clean"]) - self.run_location_test(LocationName.sand_canyon_nyupun, ["ChuChu", "Cutter"]) - self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Ice"]) - self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Ice"]), - self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Needle"]), - self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Needle"]), - self.run_location_test(LocationName.cloudy_park_hibanamodoki, ["Coo", "Clean"]) - self.run_location_test(LocationName.cloudy_park_piyokeko, ["Needle"]) - self.run_location_test(LocationName.cloudy_park_mikarin, ["Coo"]) - self.run_location_test(LocationName.cloudy_park_pick, ["Rick"]) - self.run_location_test(LocationName.iceberg_kogoesou, ["Burning"]) - self.run_location_test(LocationName.iceberg_samus, ["Ice"]) - self.run_location_test(LocationName.iceberg_name, ["Burning", "Coo", "ChuChu"]) - self.run_location_test(LocationName.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean", + def test_simple_heart_stars(self) -> None: + self.run_location_test(location_name.grass_land_muchi, ["ChuChu"]) + self.run_location_test(location_name.grass_land_chao, ["Stone"]) + self.run_location_test(location_name.grass_land_mine, ["Kine"]) + self.run_location_test(location_name.ripple_field_kamuribana, ["Pitch", "Clean"]) + self.run_location_test(location_name.ripple_field_bakasa, ["Kine", "Parasol"]) + self.run_location_test(location_name.ripple_field_toad, ["Needle"]) + self.run_location_test(location_name.ripple_field_mama_pitch, ["Pitch", "Kine", "Burning", "Stone"]) + self.run_location_test(location_name.sand_canyon_auntie, ["Clean"]) + self.run_location_test(location_name.sand_canyon_nyupun, ["ChuChu", "Cutter"]) + self.run_location_test(location_name.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Ice"]) + self.run_location_test(location_name.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Ice"]) + self.run_location_test(location_name.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Needle"]) + self.run_location_test(location_name.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Needle"]) + self.run_location_test(location_name.cloudy_park_hibanamodoki, ["Coo", "Clean"]) + self.run_location_test(location_name.cloudy_park_piyokeko, ["Needle"]) + self.run_location_test(location_name.cloudy_park_mikarin, ["Coo"]) + self.run_location_test(location_name.cloudy_park_pick, ["Rick"]) + self.run_location_test(location_name.iceberg_kogoesou, ["Burning"]) + self.run_location_test(location_name.iceberg_samus, ["Ice"]) + self.run_location_test(location_name.iceberg_name, ["Burning", "Coo", "ChuChu"]) + self.run_location_test(location_name.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean", "Stone", "Ice"]) - def run_location_test(self, location: str, itempool: typing.List[str]): + def run_location_test(self, location: str, itempool: typing.List[str]) -> None: items = itempool.copy() while len(itempool) > 0: self.assertFalse(self.can_reach_location(location), str(self.multiworld.seed)) @@ -57,7 +57,7 @@ class TestShiro(KDL3TestBase): "plando_options": "connections" } - def test_shiro(self): + def test_shiro(self) -> None: self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) self.collect_by_name("Nago") self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) diff --git a/worlds/kdl3/test/test_shuffles.py b/worlds/kdl3/test/test_shuffles.py index d676b641..3ba376d0 100644 --- a/worlds/kdl3/test/test_shuffles.py +++ b/worlds/kdl3/test/test_shuffles.py @@ -1,47 +1,61 @@ -from typing import List, Tuple +from typing import List, Tuple, Optional from . import KDL3TestBase -from ..Room import KDL3Room +from ..room import KDL3Room +from ..names import animal_friend_spawns class TestCopyAbilityShuffle(KDL3TestBase): options = { "open_world": False, "goal_speed": "normal", - "total_heart_stars": 30, + "max_heart_stars": 30, "heart_stars_required": 50, "filler_percentage": 0, "copy_ability_randomization": "enabled", } - def test_goal(self): - self.assertBeatable(False) - heart_stars = self.get_items_by_name("Heart Star") - self.collect(heart_stars[0:14]) - self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) - self.assertBeatable(False) - self.collect(heart_stars[14:15]) - self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) - self.assertBeatable(False) - self.collect_by_name(["Burning", "Cutter", "Kine"]) - self.assertBeatable(True) - self.remove([self.get_item_by_name("Love-Love Rod")]) - self.collect(heart_stars) - self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) - self.assertBeatable(True) + def test_goal(self) -> None: + try: + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect_by_name(["Burning", "Cutter", "Kine"]) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect(heart_stars) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) + self.assertBeatable(True) + except AssertionError as ex: + # if assert beatable fails, this will catch and print the seed + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_kine(self): - self.collect_by_name(["Cutter", "Burning", "Heart Star"]) - self.assertBeatable(False) + def test_kine(self) -> None: + try: + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_cutter(self): - self.collect_by_name(["Kine", "Burning", "Heart Star"]) - self.assertBeatable(False) + def test_cutter(self) -> None: + try: + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_burning(self): - self.collect_by_name(["Cutter", "Kine", "Heart Star"]) - self.assertBeatable(False) + def test_burning(self) -> None: + try: + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_cutter_and_burning_reachable(self): + def test_cutter_and_burning_reachable(self) -> None: rooms = self.multiworld.worlds[1].rooms copy_abilities = self.multiworld.worlds[1].copy_abilities sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1) @@ -63,7 +77,7 @@ class TestCopyAbilityShuffle(KDL3TestBase): else: self.fail("Could not reach Burning Ability before Iceberg 4!") - def test_valid_abilities_for_ROB(self): + def test_valid_abilities_for_ROB(self) -> None: # there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach # first we need to identify our bukiset requirements @@ -74,13 +88,13 @@ class TestCopyAbilityShuffle(KDL3TestBase): ({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}), ] copy_abilities = self.multiworld.worlds[1].copy_abilities - required_abilities: List[Tuple[str]] = [] + required_abilities: List[List[str]] = [] for abilities, bukisets in groups: potential_abilities: List[str] = list() for bukiset in bukisets: if copy_abilities[bukiset] in abilities: potential_abilities.append(copy_abilities[bukiset]) - required_abilities.append(tuple(potential_abilities)) + required_abilities.append(potential_abilities) collected_abilities = list() for group in required_abilities: self.assertFalse(len(group) == 0, str(self.multiworld.seed)) @@ -103,91 +117,147 @@ class TestAnimalShuffle(KDL3TestBase): options = { "open_world": False, "goal_speed": "normal", - "total_heart_stars": 30, + "max_heart_stars": 30, "heart_stars_required": 50, "filler_percentage": 0, "animal_randomization": "full", } - def test_goal(self): - self.assertBeatable(False) - heart_stars = self.get_items_by_name("Heart Star") - self.collect(heart_stars[0:14]) - self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) - self.assertBeatable(False) - self.collect(heart_stars[14:15]) - self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) - self.assertBeatable(False) - self.collect_by_name(["Burning", "Cutter", "Kine"]) - self.assertBeatable(True) - self.remove([self.get_item_by_name("Love-Love Rod")]) - self.collect(heart_stars) - self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) - self.assertBeatable(True) + def test_goal(self) -> None: + try: + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect_by_name(["Burning", "Cutter", "Kine"]) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect(heart_stars) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) + self.assertBeatable(True) + except AssertionError as ex: + # if assert beatable fails, this will catch and print the seed + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_kine(self): - self.collect_by_name(["Cutter", "Burning", "Heart Star"]) - self.assertBeatable(False) + def test_kine(self) -> None: + try: + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_cutter(self): - self.collect_by_name(["Kine", "Burning", "Heart Star"]) - self.assertBeatable(False) + def test_cutter(self) -> None: + try: + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_burning(self): - self.collect_by_name(["Cutter", "Kine", "Heart Star"]) - self.assertBeatable(False) + def test_burning(self) -> None: + try: + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_locked_animals(self): - self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn") - self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn") - self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"}) + def test_locked_animals(self) -> None: + ripple_field_5 = self.multiworld.get_location("Ripple Field 5 - Animal 2", 1) + self.assertTrue(ripple_field_5.item is not None and ripple_field_5.item.name == "Pitch Spawn", + f"Multiworld did not place Pitch, Seed: {self.multiworld.seed}") + iceberg_4 = self.multiworld.get_location("Iceberg 4 - Animal 1", 1) + self.assertTrue(iceberg_4.item is not None and iceberg_4.item.name == "ChuChu Spawn", + f"Multiworld did not place ChuChu, Seed: {self.multiworld.seed}") + sand_canyon_6 = self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1) + self.assertTrue(sand_canyon_6.item is not None and sand_canyon_6.item.name in + {"Kine Spawn", "Coo Spawn"}, f"Multiworld did not place Coo/Kine, Seed: {self.multiworld.seed}") + + def test_problematic(self) -> None: + for spawns in animal_friend_spawns.problematic_sets: + placed = [self.multiworld.get_location(spawn, 1).item for spawn in spawns] + placed_names = set([item.name for item in placed]) + self.assertEqual(len(placed), len(placed_names), + f"Duplicate animal placed in problematic locations:" + f" {[spawn.location for spawn in placed]}, " + f"Seed: {self.multiworld.seed}") class TestAllShuffle(KDL3TestBase): options = { "open_world": False, "goal_speed": "normal", - "total_heart_stars": 30, + "max_heart_stars": 30, "heart_stars_required": 50, "filler_percentage": 0, "animal_randomization": "full", "copy_ability_randomization": "enabled", } - def test_goal(self): - self.assertBeatable(False) - heart_stars = self.get_items_by_name("Heart Star") - self.collect(heart_stars[0:14]) - self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) - self.assertBeatable(False) - self.collect(heart_stars[14:15]) - self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) - self.assertBeatable(False) - self.collect_by_name(["Burning", "Cutter", "Kine"]) - self.assertBeatable(True) - self.remove([self.get_item_by_name("Love-Love Rod")]) - self.collect(heart_stars) - self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) - self.assertBeatable(True) + def test_goal(self) -> None: + try: + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect_by_name(["Burning", "Cutter", "Kine"]) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect(heart_stars) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) + self.assertBeatable(True) + except AssertionError as ex: + # if assert beatable fails, this will catch and print the seed + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_kine(self): - self.collect_by_name(["Cutter", "Burning", "Heart Star"]) - self.assertBeatable(False) + def test_kine(self) -> None: + try: + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_cutter(self): - self.collect_by_name(["Kine", "Burning", "Heart Star"]) - self.assertBeatable(False) + def test_cutter(self) -> None: + try: + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_burning(self): - self.collect_by_name(["Cutter", "Kine", "Heart Star"]) - self.assertBeatable(False) + def test_burning(self) -> None: + try: + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + except AssertionError as ex: + raise AssertionError(f"Test failed, Seed:{self.multiworld.seed}") from ex - def test_locked_animals(self): - self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn") - self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn") - self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"}) + def test_locked_animals(self) -> None: + ripple_field_5 = self.multiworld.get_location("Ripple Field 5 - Animal 2", 1) + self.assertTrue(ripple_field_5.item is not None and ripple_field_5.item.name == "Pitch Spawn", + f"Multiworld did not place Pitch, Seed: {self.multiworld.seed}") + iceberg_4 = self.multiworld.get_location("Iceberg 4 - Animal 1", 1) + self.assertTrue(iceberg_4.item is not None and iceberg_4.item.name == "ChuChu Spawn", + f"Multiworld did not place ChuChu, Seed: {self.multiworld.seed}") + sand_canyon_6 = self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1) + self.assertTrue(sand_canyon_6.item is not None and sand_canyon_6.item.name in + {"Kine Spawn", "Coo Spawn"}, f"Multiworld did not place Coo/Kine, Seed: {self.multiworld.seed}") - def test_cutter_and_burning_reachable(self): + def test_problematic(self) -> None: + for spawns in animal_friend_spawns.problematic_sets: + placed = [self.multiworld.get_location(spawn, 1).item for spawn in spawns] + placed_names = set([item.name for item in placed]) + self.assertEqual(len(placed), len(placed_names), + f"Duplicate animal placed in problematic locations:" + f" {[spawn.location for spawn in placed]}, " + f"Seed: {self.multiworld.seed}") + + def test_cutter_and_burning_reachable(self) -> None: rooms = self.multiworld.worlds[1].rooms copy_abilities = self.multiworld.worlds[1].copy_abilities sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1) @@ -209,7 +279,7 @@ class TestAllShuffle(KDL3TestBase): else: self.fail("Could not reach Burning Ability before Iceberg 4!") - def test_valid_abilities_for_ROB(self): + def test_valid_abilities_for_ROB(self) -> None: # there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach # first we need to identify our bukiset requirements @@ -220,13 +290,13 @@ class TestAllShuffle(KDL3TestBase): ({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}), ] copy_abilities = self.multiworld.worlds[1].copy_abilities - required_abilities: List[Tuple[str]] = [] + required_abilities: List[List[str]] = [] for abilities, bukisets in groups: potential_abilities: List[str] = list() for bukiset in bukisets: if copy_abilities[bukiset] in abilities: potential_abilities.append(copy_abilities[bukiset]) - required_abilities.append(tuple(potential_abilities)) + required_abilities.append(potential_abilities) collected_abilities = list() for group in required_abilities: self.assertFalse(len(group) == 0, str(self.multiworld.seed)) @@ -242,4 +312,4 @@ class TestAllShuffle(KDL3TestBase): self.collect_by_name(["Cutter"]) self.assertTrue(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), - ''.join(str(self.multiworld.seed)).join(collected_abilities)) + f"Seed: {self.multiworld.seed}, Collected: {collected_abilities}")