Skip to content

Commit 80d7ac4

Browse files
authored
KDL3: RC1 Fixes and Enhancement (#3022)
* 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
1 parent 7731171 commit 80d7ac4

File tree

6 files changed

+66
-52
lines changed

6 files changed

+66
-52
lines changed

worlds/kdl3/Client.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@
3636
KDL3_GIFTING_FLAG = SRAM_1_START + 0x901C
3737
KDL3_LEVEL_ADDR = SRAM_1_START + 0x9020
3838
KDL3_IS_DEMO = SRAM_1_START + 0x5AD5
39-
KDL3_GAME_STATE = SRAM_1_START + 0x36D0
4039
KDL3_GAME_SAVE = SRAM_1_START + 0x3617
40+
KDL3_CURRENT_WORLD = SRAM_1_START + 0x363F
41+
KDL3_CURRENT_LEVEL = SRAM_1_START + 0x3641
42+
KDL3_GAME_STATE = SRAM_1_START + 0x36D0
4143
KDL3_LIFE_COUNT = SRAM_1_START + 0x39CF
4244
KDL3_KIRBY_HP = SRAM_1_START + 0x39D1
4345
KDL3_BOSS_HP = SRAM_1_START + 0x39D5
@@ -46,8 +48,6 @@
4648
KDL3_HEART_STARS = SRAM_1_START + 0x53A7
4749
KDL3_WORLD_UNLOCK = SRAM_1_START + 0x53CB
4850
KDL3_LEVEL_UNLOCK = SRAM_1_START + 0x53CD
49-
KDL3_CURRENT_WORLD = SRAM_1_START + 0x53CF
50-
KDL3_CURRENT_LEVEL = SRAM_1_START + 0x53D3
5151
KDL3_BOSS_STATUS = SRAM_1_START + 0x53D5
5252
KDL3_INVINCIBILITY_TIMER = SRAM_1_START + 0x54B1
5353
KDL3_MG5_STATUS = SRAM_1_START + 0x5EE4
@@ -74,7 +74,9 @@
7474
0x0202: " was out-numbered by Pon & Con.",
7575
0x0203: " was defeated by Ado's powerful paintings.",
7676
0x0204: " was clobbered by King Dedede.",
77-
0x0205: " lost their battle against Dark Matter."
77+
0x0205: " lost their battle against Dark Matter.",
78+
0x0300: " couldn't overcome the Boss Butch.",
79+
0x0400: " is bad at jumping.",
7880
})
7981

8082

@@ -281,6 +283,11 @@ async def game_watcher(self, ctx) -> None:
281283
for i in range(5):
282284
level_data = await snes_read(ctx, KDL3_LEVEL_ADDR + (14 * i), 14)
283285
self.levels[i] = unpack("HHHHHHH", level_data)
286+
self.levels[5] = [0x0205, # Hyper Zone
287+
0, # MG-5, can't send from here
288+
0x0300, # Boss Butch
289+
0x0400, # Jumping
290+
0, 0, 0]
284291

285292
if self.consumables is None:
286293
consumables = await snes_read(ctx, KDL3_CONSUMABLE_FLAG, 1)
@@ -314,7 +321,7 @@ async def game_watcher(self, ctx) -> None:
314321
current_world = struct.unpack("H", await snes_read(ctx, KDL3_CURRENT_WORLD, 2))[0]
315322
current_level = struct.unpack("H", await snes_read(ctx, KDL3_CURRENT_LEVEL, 2))[0]
316323
currently_dead = current_hp[0] == 0x00
317-
message = deathlink_messages[self.levels[current_world][current_level - 1]]
324+
message = deathlink_messages[self.levels[current_world][current_level]]
318325
await ctx.handle_deathlink_state(currently_dead, f"{ctx.player_names[ctx.slot]}{message}")
319326

320327
recv_count = await snes_read(ctx, KDL3_RECV_COUNT, 2)

worlds/kdl3/Regions.py

+45-40
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,30 @@
2828
0x77001C, # 5-4 needs Burning
2929
}
3030

31+
first_world_limit = {
32+
# We need to limit the number of very restrictive stages in level 1 on solo gens
33+
*first_stage_blacklist, # all three of the blacklist stages need 2+ items for both checks
34+
0x770007,
35+
0x770008,
36+
0x770013,
37+
0x77001E,
3138

32-
def generate_valid_level(level, stage, possible_stages, slot_random):
33-
new_stage = slot_random.choice(possible_stages)
34-
if level == 1 and stage == 0 and new_stage in first_stage_blacklist:
35-
return generate_valid_level(level, stage, possible_stages, slot_random)
36-
else:
37-
return new_stage
39+
}
40+
41+
42+
def generate_valid_level(world: "KDL3World", level, stage, possible_stages, placed_stages):
43+
new_stage = world.random.choice(possible_stages)
44+
if level == 1:
45+
if stage == 0 and new_stage in first_stage_blacklist:
46+
return generate_valid_level(world, level, stage, possible_stages, placed_stages)
47+
elif not (world.multiworld.players > 1 or world.options.consumables or world.options.starsanity) and \
48+
new_stage in first_world_limit and \
49+
sum(p_stage in first_world_limit for p_stage in placed_stages) >= 2:
50+
return generate_valid_level(world, level, stage, possible_stages, placed_stages)
51+
return new_stage
3852

3953

40-
def generate_rooms(world: "KDL3World", door_shuffle: bool, level_regions: typing.Dict[int, Region]):
54+
def generate_rooms(world: "KDL3World", level_regions: typing.Dict[int, Region]):
4155
level_names = {LocationName.level_names[level]: level for level in LocationName.level_names}
4256
room_data = orjson.loads(get_data(__name__, os.path.join("data", "Rooms.json")))
4357
rooms: typing.Dict[str, KDL3Room] = dict()
@@ -49,8 +63,8 @@ def generate_rooms(world: "KDL3World", door_shuffle: bool, level_regions: typing
4963
room.add_locations({location: world.location_name_to_id[location] if location in world.location_name_to_id else
5064
None for location in room_entry["locations"]
5165
if (not any(x in location for x in ["1-Up", "Maxim"]) or
52-
world.options.consumables.value) and ("Star" not in location
53-
or world.options.starsanity.value)},
66+
world.options.consumables.value) and ("Star" not in location
67+
or world.options.starsanity.value)},
5468
KDL3Location)
5569
rooms[room.name] = room
5670
for location in room.locations:
@@ -62,33 +76,25 @@ def generate_rooms(world: "KDL3World", door_shuffle: bool, level_regions: typing
6276
world.multiworld.regions.extend(world.rooms)
6377

6478
first_rooms: typing.Dict[int, KDL3Room] = dict()
65-
if door_shuffle:
66-
# first, we need to generate the notable edge cases
67-
# 5-6 is the first, being the most restrictive
68-
# half of its rooms are required to be vanilla, but can be in different orders
69-
# the room before it *must* contain the copy ability required to unlock the room's goal
70-
71-
raise NotImplementedError()
72-
else:
73-
for name, room in rooms.items():
74-
if room.room == 0:
75-
if room.stage == 7:
76-
first_rooms[0x770200 + room.level - 1] = room
77-
else:
78-
first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage] = room
79-
exits = dict()
80-
for def_exit in room.default_exits:
81-
target = f"{level_names[room.level]} {room.stage} - {def_exit['room']}"
82-
access_rule = tuple(def_exit["access_rule"])
83-
exits[target] = lambda state, rule=access_rule: state.has_all(rule, world.player)
84-
room.add_exits(
85-
exits.keys(),
86-
exits
87-
)
88-
if world.options.open_world:
89-
if any("Complete" in location.name for location in room.locations):
90-
room.add_locations({f"{level_names[room.level]} {room.stage} - Stage Completion": None},
91-
KDL3Location)
79+
for name, room in rooms.items():
80+
if room.room == 0:
81+
if room.stage == 7:
82+
first_rooms[0x770200 + room.level - 1] = room
83+
else:
84+
first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage] = room
85+
exits = dict()
86+
for def_exit in room.default_exits:
87+
target = f"{level_names[room.level]} {room.stage} - {def_exit['room']}"
88+
access_rule = tuple(def_exit["access_rule"])
89+
exits[target] = lambda state, rule=access_rule: state.has_all(rule, world.player)
90+
room.add_exits(
91+
exits.keys(),
92+
exits
93+
)
94+
if world.options.open_world:
95+
if any("Complete" in location.name for location in room.locations):
96+
room.add_locations({f"{level_names[room.level]} {room.stage} - Stage Completion": None},
97+
KDL3Location)
9298

9399
for level in world.player_levels:
94100
for stage in range(6):
@@ -102,7 +108,7 @@ def generate_rooms(world: "KDL3World", door_shuffle: bool, level_regions: typing
102108
if world.options.open_world or stage == 0:
103109
level_regions[level].add_exits([first_rooms[proper_stage].name])
104110
else:
105-
world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][stage-1]],
111+
world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][stage - 1]],
106112
world.player).parent_region.add_exits([first_rooms[proper_stage].name])
107113
level_regions[level].add_exits([first_rooms[0x770200 + level - 1].name])
108114

@@ -141,8 +147,7 @@ def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_patte
141147
or (enforce_pattern and ((candidate - 1) & 0x00FFFF) % 6 == stage)
142148
or (enforce_pattern == enforce_world)
143149
]
144-
new_stage = generate_valid_level(level, stage, stage_candidates,
145-
world.random)
150+
new_stage = generate_valid_level(world, level, stage, stage_candidates, levels[level])
146151
possible_stages.remove(new_stage)
147152
levels[level][stage] = new_stage
148153
except Exception:
@@ -218,7 +223,7 @@ def create_levels(world: "KDL3World") -> None:
218223
level_shuffle == 1,
219224
level_shuffle == 2)
220225

221-
generate_rooms(world, False, levels)
226+
generate_rooms(world, levels)
222227

223228
level6.add_locations({LocationName.goals[world.options.goal]: None}, KDL3Location)
224229

worlds/kdl3/Rules.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def set_rules(world: "KDL3World") -> None:
264264
for r in [range(1, 31), range(44, 51)]:
265265
for i in r:
266266
set_rule(world.multiworld.get_location(f"Cloudy Park 4 - Star {i}", world.player),
267-
lambda state: can_reach_clean(state, world.player))
267+
lambda state: can_reach_coo(state, world.player))
268268
for i in [18, *list(range(20, 25))]:
269269
set_rule(world.multiworld.get_location(f"Cloudy Park 6 - Star {i}", world.player),
270270
lambda state: can_reach_ice(state, world.player))

worlds/kdl3/docs/en_Kirby's Dream Land 3.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Kirby's Dream Land 3
22

3-
## Where is the settings page?
3+
## Where is the options page?
44

5-
The [player settings page for this game](../player-settings) contains all the options you need to configure and export a
5+
The [player options page for this game](../player-options) contains all the options you need to configure and export a
66
config file.
77

88
## What does randomization do to this game?
@@ -15,6 +15,7 @@ as Heart Stars, 1-Ups, and Invincibility Candy will be shuffled into the pool fo
1515
- Purifying a boss after acquiring a certain number of Heart Stars
1616
(indicated by their portrait flashing in the level select)
1717
- If enabled, 1-Ups and Maxim Tomatoes
18+
- If enabled, every single Star Piece within a stage
1819

1920
## When the player receives an item, what happens?
2021
A sound effect will play, and Kirby will immediately receive the effects of that item, such as being able to receive Copy Abilities from enemies that

worlds/kdl3/docs/setup_en.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en)
4343

4444
### Where do I get a config file?
4545

46-
The [Player Settings](/games/Kirby's%20Dream%20Land%203/player-settings) page on the website allows you to configure
47-
your personal settings and export a config file from them.
46+
The [Player Options](/games/Kirby's%20Dream%20Land%203/player-options) page on the website allows you to configure
47+
your personal options and export a config file from them.
4848

4949
### Verifying your config file
5050

@@ -53,7 +53,7 @@ If you would like to validate your config file to make sure it works, you may do
5353

5454
## Generating a Single-Player Game
5555

56-
1. Navigate to the [Player Settings](/games/Kirby's%20Dream%20Land%203/player-settings) page, configure your options,
56+
1. Navigate to the [Player Options](/games/Kirby's%20Dream%20Land%203/player-options) page, configure your options,
5757
and click the "Generate Game" button.
5858
2. You will be presented with a "Seed Info" page.
5959
3. Click the "Create New Room" link.

worlds/kdl3/test/test_locations.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def test_simple_heart_stars(self):
3333
self.run_location_test(LocationName.iceberg_kogoesou, ["Burning"])
3434
self.run_location_test(LocationName.iceberg_samus, ["Ice"])
3535
self.run_location_test(LocationName.iceberg_name, ["Burning", "Coo", "ChuChu"])
36-
self.run_location_test(LocationName.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean", "Stone", "Ice"])
36+
self.run_location_test(LocationName.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean",
37+
"Stone", "Ice"])
3738

3839
def run_location_test(self, location: str, itempool: typing.List[str]):
3940
items = itempool.copy()

0 commit comments

Comments
 (0)