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:
parent
b1be597451
commit
920cffda2d
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
Binary file not shown.
|
@ -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 = {
|
|
@ -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}
|
|
@ -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
|
||||
}
|
|
@ -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]
|
||||
]
|
|
@ -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)']),
|
|
@ -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, ]),
|
||||
]
|
|
@ -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",
|
|
@ -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")
|
|
@ -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
|
|
@ -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"))
|
|
@ -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),
|
|
@ -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"
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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}")
|
||||
|
|
Loading…
Reference in New Issue