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 <beauxq@yahoo.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
This commit is contained in:
Silvris 2024-08-31 06:15:00 -05:00 committed by GitHub
parent b1be597451
commit 920cffda2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 2420 additions and 2056 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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 = {

View File

@ -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}

940
worlds/kdl3/locations.py Normal file
View File

@ -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
}

View File

@ -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]
]

View File

@ -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)']),

View File

@ -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, ]),
]

View File

@ -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",

View File

@ -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")

602
worlds/kdl3/rom.py Normal file
View File

@ -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

133
worlds/kdl3/room.py Normal file
View File

@ -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"))

View File

@ -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),

View File

@ -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
db $3D, $05
org $07F000
incbin "APPauseIcons.dat"

View File

@ -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):

View File

@ -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)

View File

@ -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))

View File

@ -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}")