From 028ebea682bd30b9f94d6e208c53eef9253de758 Mon Sep 17 00:00:00 2001 From: TheLX5 Date: Fri, 8 Nov 2024 23:19:48 -0700 Subject: [PATCH] initial version --- worlds/dkc2/Client.py | 125 ++++++++++++++++--- worlds/dkc2/Items.py | 60 +++++++++- worlds/dkc2/Levels.py | 11 +- worlds/dkc2/Locations.py | 4 +- worlds/dkc2/Names/EventName.py | 2 + worlds/dkc2/Names/ItemName.py | 33 +++-- worlds/dkc2/Options.py | 117 +++++++++++++++--- worlds/dkc2/Regions.py | 8 +- worlds/dkc2/Rom.py | 153 ++++++++++++++++++++++-- worlds/dkc2/Rules.py | 135 ++++++++++++++++----- worlds/dkc2/__init__.py | 103 +++++++++++----- worlds/dkc2/data/dkc2_basepatch.bsdiff4 | Bin 1055 -> 2857 bytes 12 files changed, 623 insertions(+), 128 deletions(-) diff --git a/worlds/dkc2/Client.py b/worlds/dkc2/Client.py index 009c81c36105..de2bb92cec9e 100644 --- a/worlds/dkc2/Client.py +++ b/worlds/dkc2/Client.py @@ -14,17 +14,27 @@ WRAM_SIZE = 0x20000 SRAM_START = 0xE00000 -STARTING_ID = 0xBE0800 +STARTING_ID = 0xBF0000 + +DKC2_SETTINGS = ROM_START + 0x3DFF80 DKC2_MISC_FLAGS = WRAM_START + 0x08D2 DKC2_GAME_FLAGS = WRAM_START + 0x59B2 +DKC2_EFFECT_BUFFER = WRAM_START + 0x0619 +DKC2_SOUND_BUFFER = WRAM_START + 0x0622 +DKC2_SPC_NEXT_INDEX = WRAM_START + 0x0634 +DKC2_SPC_INDEX = WRAM_START + 0x0632 +DKC2_SPC_CHANNEL_BUSY = WRAM_START + 0x0621 + DKC2_SRAM = SRAM_START + 0x800 DKC2_RECV_INDEX = DKC2_SRAM + 0x020 DKC2_INIT_FLAG = DKC2_SRAM + 0x022 +DKC2_GAME_TIME = WRAM_START + 0x00D5 DKC2_IN_LEVEL = WRAM_START + 0x01FF DKC2_CURRENT_LEVEL = WRAM_START + 0x00D3 +DKC2_CURRENT_MODE = WRAM_START + 0x00D0 DKC2_CRANKY_FLAGS = WRAM_START + 0x08D2 DKC2_WRINKLY_FLAGS = WRAM_START + 0x08E0 @@ -37,7 +47,7 @@ DKC2_DK_COIN_FLAGS = WRAM_START + 0x59D2 DKC2_STAGE_FLAGS = WRAM_START + 0x59F2 -DKC2_ROMHASH_START = 0x7FC0 +DKC2_ROMHASH_START = 0xFFC0 ROMHASH_SIZE = 0x15 class DKC2SNIClient(SNIClient): @@ -83,24 +93,29 @@ async def validate_rom(self, ctx): async def game_watcher(self, ctx): from SNIClient import snes_buffered_write, snes_flush_writes, snes_read - in_level = await snes_read(ctx, DKC2_IN_LEVEL, 0x01) - if in_level[0] != 0x80: - self.game_state = False + setting_data = await snes_read(ctx, DKC2_SETTINGS, 0x40) + general_data = await snes_read(ctx, WRAM_START + 0x00D0, 0x0F) + game_flags = await snes_read(ctx, DKC2_GAME_FLAGS, 0x60) + misc_flags = await snes_read(ctx, DKC2_MISC_FLAGS, 0x80) + + if general_data is None or game_flags is None or misc_flags is None or setting_data is None: return - + + loaded_save = int.from_bytes(general_data[0x05:0x07], "little") + if loaded_save == 0: + return + validation = int.from_bytes(await snes_read(ctx, DKC2_INIT_FLAG, 0x2), "little") if validation != 0xDEAD: snes_logger.info(f'ROM not properly validated.') self.game_state = False return - + from .Levels import location_id_to_level_id from worlds import AutoWorldRegister new_checks = [] current_level = int.from_bytes(await snes_read(ctx, DKC2_CURRENT_LEVEL, 0x01)) - game_flags = await snes_read(ctx, DKC2_GAME_FLAGS, 0x60) - misc_flags = await snes_read(ctx, DKC2_MISC_FLAGS, 0x80) kong_flags = misc_flags[0x30] stage_flags = game_flags[0x40:0x60] bonus_flags = list(game_flags[0x00:0x20]) @@ -132,7 +147,32 @@ async def game_watcher(self, ctx): level_data = int.from_bytes(bonus_flags[level_offset:level_offset+2], "little") if level_data & level_bit: new_checks.append(loc_id) - + + # Check goals + goal_check = 0 + selected_goal = setting_data[0x01] + + level_num = 0x61 + level_offset = (level_num >> 3) & 0x1E + level_bit = 1 << (level_num & 0x0F) + level_data = int.from_bytes(bonus_flags[level_offset:level_offset+2], "little") + if level_data & level_bit: + goal_check |= 1 + + level_num = 0x6B + level_offset = (level_num >> 3) & 0x1E + level_bit = 1 << (level_num & 0x0F) + level_data = int.from_bytes(dk_coin_flags[level_offset:level_offset+2], "little") + if level_data & level_bit: + goal_check |= 2 + + if goal_check & selected_goal == selected_goal: + if not ctx.finished_game: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.finished_game = True + return + + # Receive items rom = await snes_read(ctx, DKC2_ROMHASH_START, ROMHASH_SIZE) if rom != ctx.rom: ctx.rom = None @@ -151,7 +191,8 @@ async def game_watcher(self, ctx): # Add a small failsafe in case we get a None. Other SNI games do this... return - recv_index = recv_count[0] | (recv_count[1] << 8) + from .Rom import unlock_data, currency_data, trap_data + recv_index = int.from_bytes(recv_count, "little") if recv_index < len(ctx.items_received): item = ctx.items_received[recv_index] @@ -162,11 +203,67 @@ async def game_watcher(self, ctx): color(ctx.player_names[item.player], 'yellow'), ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received))) + sfx = 0 + # Give kongs + if item.item in {STARTING_ID + 0x0010, STARTING_ID + 0x0011}: + offset = unlock_data[item.item][0] + sfx = unlock_data[item.item][1] + count = await snes_read(ctx, DKC2_SRAM + offset, 0x02) + if count is None: + recv_index -= 1 + return + count = int.from_bytes(count, "little") + if item.item == STARTING_ID + 0x0010: + count |= 0x0001 + else: + count |= 0x0002 + count &= 0x00FF + snes_buffered_write(ctx, DKC2_SRAM + offset, bytes([count])) + + # Give items + elif item.item in unlock_data: + offset = unlock_data[item.item][0] + sfx = unlock_data[item.item][1] + snes_buffered_write(ctx, DKC2_SRAM + offset, bytearray([0x01])) + + # Give currency-like items + elif item.item in currency_data: + offset = currency_data[item.item][0] + if offset & 0x8000 == 0x8000: + addr = DKC2_SRAM + (offset & 0x7FFF) + else: + addr = WRAM_START + offset + sfx = currency_data[item.item][1] + currency = await snes_read(ctx, addr, 0x02) + if currency is None: + recv_index -= 1 + return + currency = int.from_bytes(currency, "little") + 1 + currency &= 0x00FF + snes_buffered_write(ctx, addr, bytes([currency])) + + # Give traps + elif item.item in trap_data: + print ("wea") + offset = trap_data[item.item][0] + sfx = trap_data[item.item][1] + traps = await snes_read(ctx, DKC2_SRAM + offset, 0x02) + if traps is None: + recv_index -= 1 + return + traps = int.from_bytes(traps, "little") + 1 + traps &= 0x00FF + snes_buffered_write(ctx, DKC2_SRAM + offset, bytes([traps])) + + if sfx: + snes_buffered_write(ctx, DKC2_SOUND_BUFFER, bytearray([sfx, 0x05])) + snes_buffered_write(ctx, DKC2_EFFECT_BUFFER + 0x05, bytearray([sfx])) + snes_buffered_write(ctx, DKC2_SPC_INDEX, bytearray([0x00])) + snes_buffered_write(ctx, DKC2_SPC_NEXT_INDEX, bytearray([0x00])) + snes_buffered_write(ctx, DKC2_RECV_INDEX, bytes([recv_index])) + await snes_flush_writes(ctx) - - if item.item in {0x0}: - pass # Handle collected locations i = 0 diff --git a/worlds/dkc2/Items.py b/worlds/dkc2/Items.py index 6d34c718f0df..d8518a4948e9 100644 --- a/worlds/dkc2/Items.py +++ b/worlds/dkc2/Items.py @@ -30,6 +30,20 @@ class DKC2Item(Item): ItemName.the_flying_krock: ItemData(STARTING_ID + 0x0007, True), } +mcguffin_table = { + ItemName.lost_world_rock: ItemData(STARTING_ID + 0x0008, True), + ItemName.dk_coin: ItemData(STARTING_ID + 0x0009, False), + ItemName.kremkoins: ItemData(STARTING_ID + 0x000A, False), +} + +lost_world_table = { + ItemName.lost_world_cauldron: ItemData(STARTING_ID + 0x000B, True), + ItemName.lost_world_quay: ItemData(STARTING_ID + 0x000C, True), + ItemName.lost_world_kremland: ItemData(STARTING_ID + 0x000D, True), + ItemName.lost_world_gulch: ItemData(STARTING_ID + 0x000E, True), + ItemName.lost_world_keep: ItemData(STARTING_ID + 0x000F, True), +} + progression_table = { ItemName.diddy: ItemData(STARTING_ID + 0x0010, True), ItemName.dixie: ItemData(STARTING_ID + 0x0011, True), @@ -60,19 +74,63 @@ class DKC2Item(Item): ItemName.red_balloon: ItemData(STARTING_ID + 0x0031, False), } +trap_table = { + ItemName.freeze_trap: ItemData(STARTING_ID + 0x0040, False, True), + ItemName.reverse_trap: ItemData(STARTING_ID + 0x0041, False, True), +} + item_groups = { "Worlds": { + ItemName.gangplank_galleon, + ItemName.crocodile_cauldron, + ItemName.krem_quay, + ItemName.krazy_kremland, + ItemName.gloomy_gulch, + ItemName.krools_keep, + ItemName.the_flying_krock, + }, + "Lost World": { + ItemName.lost_world_cauldron, + ItemName.lost_world_quay, + ItemName.lost_world_kremland, + ItemName.lost_world_gulch, + ItemName.lost_world_keep, }, "Abilities": { + ItemName.carry, + ItemName.climb, + ItemName.cling, + ItemName.cartwheel, + ItemName.swim, + ItemName.team_attack, + ItemName.helicopter_spin, + }, + "Animals": { + ItemName.rambi, + ItemName.squawks, + ItemName.enguarde, + ItemName.squitter, + ItemName.rattly, + ItemName.clapper, + ItemName.glimmer, + ItemName.skull_kart, }, - "Animal Buddies": { + "Barrels": { + ItemName.barrel_kannons, + ItemName.barrel_exclamation, + ItemName.barrel_kong, + ItemName.barrel_warp, + ItemName.barrel_control, } } item_table = { **event_table, + **mcguffin_table, **worlds_table, + **lost_world_table, **progression_table, + **trap_table, **junk_table, } diff --git a/worlds/dkc2/Levels.py b/worlds/dkc2/Levels.py index 0468803509b9..41bd80555921 100644 --- a/worlds/dkc2/Levels.py +++ b/worlds/dkc2/Levels.py @@ -43,7 +43,7 @@ LocationName.clappers_cavern_clear: [0x00, 0x8F], LocationName.chain_link_chamber_clear: [0x00, 0x6D], LocationName.toxic_tower_clear: [0x00, 0x6E], - LocationName.stronghold_showdown_clear: [0x00, 0xB9], + LocationName.stronghold_showdown_clear: [0x03, 0xB9], LocationName.screechs_sprint_clear: [0x00, 0x2F], LocationName.jungle_jinx_clear: [0x00, 0x99], LocationName.black_ice_battle_clear: [0x00, 0x96], @@ -202,7 +202,7 @@ } level_list = [ - [RegionName.pirate_panic_level, 0x03], + #[RegionName.pirate_panic_level, 0x03], [RegionName.mainbrace_mayhem_level, 0x0C], [RegionName.gangplank_galley_level, 0x04], [RegionName.lockjaws_locker_level, 0x15], @@ -304,7 +304,7 @@ } level_rom_data = { - #RegionName.pirate_panic_level: [0x34DD6F+9, 0x34DD7E], + RegionName.pirate_panic_level: [0x34DD6F+9, 0x34DD7E], RegionName.mainbrace_mayhem_level: [0x34DD9C+9, 0x34DDB3], RegionName.gangplank_galley_level: [0x34D24D+9, 0x34D260], RegionName.lockjaws_locker_level: [0x34D2BA+9, 0x34D2CD], @@ -360,16 +360,17 @@ def generate_level_list(world: World): if world.options.shuffle_levels: world.random.shuffle(shuffled_level_list) world.random.shuffle(shuffled_boss_list) + #shuffled_level_list.insert(0, [RegionName.pirate_panic_level, 0x03]) for map_level, level in level_connections.items(): selected_level = shuffled_level_list.pop(0) world.level_connections[map_level] = selected_level[0] - world.rom_connections[level] = selected_level[1] + world.rom_connections[level] = selected_level for map_boss, boss in boss_connections.items(): selected_boss = shuffled_boss_list.pop(0) world.level_connections[map_boss] = selected_boss[0] - world.rom_connections[boss] = selected_boss[1] + world.rom_connections[boss] = selected_boss # Place locked levels world.level_connections[RegionName.pirate_panic_map] = RegionName.pirate_panic_level diff --git a/worlds/dkc2/Locations.py b/worlds/dkc2/Locations.py index 1f800ce274c1..b004db38a5d2 100644 --- a/worlds/dkc2/Locations.py +++ b/worlds/dkc2/Locations.py @@ -53,13 +53,13 @@ def __init__(self, player: int, name: str = '', address: int = None, parent=None LocationName.toxic_tower_clear: STARTING_ID + 0x0026, LocationName.stronghold_showdown_clear: STARTING_ID + 0x0027, LocationName.screechs_sprint_clear: STARTING_ID + 0x0028, - LocationName.k_rool_duel_clear: STARTING_ID + 0x0029, + #LocationName.k_rool_duel_clear: STARTING_ID + 0x0029, LocationName.jungle_jinx_clear: STARTING_ID + 0x002A, LocationName.black_ice_battle_clear: STARTING_ID + 0x002B, LocationName.klobber_karnage_clear: STARTING_ID + 0x002C, LocationName.fiery_furnace_clear: STARTING_ID + 0x002D, LocationName.animal_antics_clear: STARTING_ID + 0x002E, - LocationName.krocodile_core_clear: STARTING_ID + 0x002F, + #LocationName.krocodile_core_clear: STARTING_ID + 0x002F, } stage_kong = { diff --git a/worlds/dkc2/Names/EventName.py b/worlds/dkc2/Names/EventName.py index e69de29bb2d1..90c74fe5137b 100644 --- a/worlds/dkc2/Names/EventName.py +++ b/worlds/dkc2/Names/EventName.py @@ -0,0 +1,2 @@ +k_rool_duel_clear = "Defeated K. Rool at The Flying Krock" +krocodile_core_clear = "Defeated K. Rool at Krocodile Kore" \ No newline at end of file diff --git a/worlds/dkc2/Names/ItemName.py b/worlds/dkc2/Names/ItemName.py index f3c450ba401e..388ada310da0 100644 --- a/worlds/dkc2/Names/ItemName.py +++ b/worlds/dkc2/Names/ItemName.py @@ -6,6 +6,14 @@ gloomy_gulch = "Gloomy Gulch Access" krools_keep = "K. Rool's Keep Access" the_flying_krock = "The Flying Krock Access" +lost_world_cauldron = "Lost World Access (Crocodile Cauldron)" +lost_world_quay = "Lost World Access (Krem Quay)" +lost_world_kremland = "Lost World Access (Krazy Kremland)" +lost_world_gulch = "Lost World Access (Gloomy Gulch)" +lost_world_keep = "Lost World Access (K. Rool's Keep)" + +# Collectibles +lost_world_rock = "Lost World Rock" # Playable Characters diddy = "Diddy" @@ -15,7 +23,7 @@ carry = "Carry" climb = "Climb" cling = "Cling" -cartwheel = "Cartwheel/Spin Attack" +cartwheel = "Cartwheel" swim = "Swim" team_attack = "Team Attack" helicopter_spin = "Helicopter Spin" @@ -28,6 +36,7 @@ rattly = "Rattly" clapper = "Clapper" glimmer = "Glimmer" +skull_kart = "Skull Kart" # Barrels barrel_kannons = "Barrel Kannons" @@ -35,31 +44,17 @@ barrel_kong = "Kong Barrerls" barrel_warp = "Warp Barrels" barrel_control = "Controllable Barrels" -skull_kart = "Skull Kart" # Victory victory = "Donkey Kong" -# Currency (Progression) +# Currency (Hintables) kremkoins = "Kremkoins" dk_coin = "DK Coin" -# Hints -kollege_w1_hint = "Kong Kollege Hint (Gangplank Galleon)" -kollege_w2_hint = "Kong Kollege Hint (Crocodile Cauldron)" -kollege_w3_hint = "Kong Kollege Hint (Krem Quay)" -kollege_w4_hint = "Kong Kollege Hint (Krazy Kremland)" -kollege_w5_hint = "Kong Kollege Hint (Gloomy Gulch)" -kollege_w6_hint = "Kong Kollege Hint (K. Rool's Keep)" -kollege_w7_hint = "Kong Kollege Hint (The Flying Krock)" - -museum_w1_hint = "Monkey Museum Hint (Gangplank Galleon)" -museum_w2_hint = "Monkey Museum Hint (Crocodile Cauldron)" -museum_w3_hint = "Monkey Museum Hint (Krem Quay)" -museum_w4_hint = "Monkey Museum Hint (Krazy Kremland)" -museum_w5_hint = "Monkey Museum Hint (Gloomy Gulch)" -museum_w6_hint = "Monkey Museum Hint (K. Rool's Keep)" -museum_w8_hint = "Monkey Museum Hint (Lost World)" +# Traps +freeze_trap = "Freeze Trap" +reverse_trap = "Reverse Trap" # Junk red_balloon = "1-Up" diff --git a/worlds/dkc2/Options.py b/worlds/dkc2/Options.py index 8feb9dafb389..239657080fa2 100644 --- a/worlds/dkc2/Options.py +++ b/worlds/dkc2/Options.py @@ -1,7 +1,8 @@ from dataclasses import dataclass -import typing -from Options import OptionGroup, Choice, Range, Toggle, DefaultOnToggle, OptionSet, OptionDict, DeathLink, PerGameCommonOptions, StartInventoryPool +from .Items import item_groups + +from Options import OptionGroup, Choice, Range, Toggle, DefaultOnToggle, OptionSet, PerGameCommonOptions, StartInventoryPool class StartingLifeCount(Range): """ @@ -15,36 +16,113 @@ class StartingLifeCount(Range): class StartingKong(Choice): """ - kual kong kieres + Which Kongs will be available at the start """ display_name = "Starting Kong" - option_diddy = 0 - option_dixie = 1 - option_both = 2 - default = 0 + option_diddy = 1 + option_dixie = 2 + option_both = 3 + default = 1 class Logic(Choice): """ - kual dificultad kieres + Logic difficulty. + - **Strict**: Ensures everything is reachable as the original devs intended + - **Loose**: Reaching locations may require some advanced knowledge about the game's mechanics + - **Expert**: Locations expects players to be extremely good at the game with minimal amount of abilities """ - display_name = "Logic Setting" + display_name = "Logic Difficulty" option_strict = 0 option_loose = 1 option_expert = 2 default = 1 -class KONGSanity(Toggle): +class ShuffleLevels(Toggle): """ - aƱade kong como checks + Shuffles levels and bosses around """ - display_name = "KONG-Sanity" + display_name = "Shuffle Levels" +class Goal(Choice): + """ + Which goal will be used to mark the game as completed + - **Flying Krock:** Duel with K. Rool at the Flying Krock + - **Lost World:** Duel with K. Rool at Lost World + - **Kompletionist:** Duel with K. Rool at both Flying Krock and Lost World + """ + display_name = "Barrel Kannons Shuffle" + option_flying_krock = 1 + option_lost_world = 2 + option_kompletionist = 3 + default = 1 -class ShuffleLevels(Toggle): +class LostWorldRocks(Range): """ - mueve niveles + How many rocks are required to be found to be able to duel K. Rool at Lost World's Kore """ - display_name = "Shuffle Levels" + display_name = "Lost World Rocks" + range_start = 0 + range_end = 10 + default = 5 + +class AbilityShuffle(OptionSet): + """ + Which abilities will be added as items in the item pool + If an ability is not present in the list they will be treated as unlocked from the start + """ + display_name = "Ability Shuffle" + default = {ability for ability in item_groups["Abilities"]} + valid_keys = {ability for ability in item_groups["Abilities"]} + +class AnimalShuffle(OptionSet): + """ + Which animal buddies will be added as items in the item pool + If an animal buddy is not present in the list they will be treated as unlocked from the start + """ + display_name = "Animal Buddies Shuffle" + default = {ability for ability in item_groups["Animals"]} + valid_keys = {ability for ability in item_groups["Animals"]} + +class BarrelShuffle(OptionSet): + """ + Which kind of barrels will be added as items in the item pool + If a barrel is not present in the list they will be treated as unlocked from the start + """ + display_name = "Barrel Kannons Shuffle" + default = {ability for ability in item_groups["Barrels"]} + valid_keys = {ability for ability in item_groups["Barrels"]} + +class TrapFillPercentage(Range): + """ + Replace a percentage of junk items in the item pool with random traps + """ + display_name = "Trap Fill Percentage" + range_start = 0 + range_end = 100 + default = 0 + +class BaseTrapWeight(Choice): + """ + Base Class for Trap Weights + """ + option_none = 0 + option_low = 1 + option_medium = 2 + option_high = 4 + default = 2 + +class FreezeTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which freezes the controllable kong + """ + display_name = "Freeze Trap Weight" + + +class ReverseTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which reverses the player's controls + """ + display_name = "Reverse Trap Weight" @dataclass @@ -52,6 +130,13 @@ class DKC2Options(PerGameCommonOptions): start_inventory_from_pool: StartInventoryPool starting_life_count: StartingLifeCount starting_kong: StartingKong + goal: Goal + lost_world_rocks: LostWorldRocks logic: Logic - kongsanity: KONGSanity shuffle_levels: ShuffleLevels + shuffle_abilities: AbilityShuffle + shuffle_animals: AnimalShuffle + shuffle_barrels: BarrelShuffle + trap_fill_percentage: TrapFillPercentage + freeze_trap_weight: FreezeTrapWeight + reverse_trap_weight: ReverseTrapWeight diff --git a/worlds/dkc2/Regions.py b/worlds/dkc2/Regions.py index 9a22990cd093..7e01f4ab61a7 100644 --- a/worlds/dkc2/Regions.py +++ b/worlds/dkc2/Regions.py @@ -289,13 +289,14 @@ def create_regions(multiworld: MultiWorld, player: int, world: World, active_loc add_location_to_region(multiworld, player, active_locations, RegionName.toxic_tower_level, LocationName.toxic_tower_clear) add_location_to_region(multiworld, player, active_locations, RegionName.stronghold_showdown_level, LocationName.stronghold_showdown_clear) add_location_to_region(multiworld, player, active_locations, RegionName.screechs_sprint_level, LocationName.screechs_sprint_clear) - add_location_to_region(multiworld, player, active_locations, RegionName.k_rool_duel_level, LocationName.k_rool_duel_clear) add_location_to_region(multiworld, player, active_locations, RegionName.jungle_jinx_level, LocationName.jungle_jinx_clear) add_location_to_region(multiworld, player, active_locations, RegionName.black_ice_battle_level, LocationName.black_ice_battle_clear) add_location_to_region(multiworld, player, active_locations, RegionName.klobber_karnage_level, LocationName.klobber_karnage_clear) add_location_to_region(multiworld, player, active_locations, RegionName.fiery_furnace_level, LocationName.fiery_furnace_clear) add_location_to_region(multiworld, player, active_locations, RegionName.animal_antics_level, LocationName.animal_antics_clear) - add_location_to_region(multiworld, player, active_locations, RegionName.krocodile_core_level, LocationName.krocodile_core_clear) + + add_event_to_region(multiworld, player, RegionName.k_rool_duel_level, LocationName.k_rool_duel_clear, EventName.k_rool_duel_clear) + add_event_to_region(multiworld, player, RegionName.krocodile_core_level, LocationName.krocodile_core_clear, EventName.krocodile_core_clear) # KONG add_location_to_region(multiworld, player, active_locations, RegionName.pirate_panic_level, LocationName.pirate_panic_kong) @@ -514,6 +515,7 @@ def connect_regions(world: World): connect(world, RegionName.the_flying_krock, RegionName.screechs_sprint_map) connect(world, RegionName.the_flying_krock, RegionName.k_rool_duel_map) + connect(world, RegionName.k_rool_duel_map, RegionName.k_rool_duel_level) connect(world, RegionName.lost_world_cauldron, RegionName.jungle_jinx_map) connect(world, RegionName.lost_world_quay, RegionName.black_ice_battle_map) @@ -526,11 +528,11 @@ def connect_regions(world: World): connect(world, RegionName.lost_world_kremland, RegionName.krocodile_core_map) connect(world, RegionName.lost_world_gulch, RegionName.krocodile_core_map) connect(world, RegionName.lost_world_keep, RegionName.krocodile_core_map) + connect(world, RegionName.krocodile_core_map, RegionName.krocodile_core_level) for map_level, level in world.level_connections.items(): connect(world, map_level, level) - connect(world, RegionName.k_rool_duel_map, RegionName.k_rool_duel_level) def create_region(multiworld: MultiWorld, player: int, active_locations, name: str, locations=None): diff --git a/worlds/dkc2/Rom.py b/worlds/dkc2/Rom.py index 6644c5893fc1..f0ed88d53cc7 100644 --- a/worlds/dkc2/Rom.py +++ b/worlds/dkc2/Rom.py @@ -1,18 +1,125 @@ import typing -import bsdiff4 import Utils import hashlib import os -from pkgutil import get_data +import json -from worlds.AutoWorld import World -from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from . import DKC2World + +from .Names import ItemName +from .Items import item_groups + +from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension HASH_US = '98458530599b9dff8a7414a7f20b777a' HASH_US_REV_1 = 'd323e6bb4ccc85fd7b416f58350bc1a2' STARTING_ID = 0xBF0000 +rom_start_inventory = { + ItemName.carry: 0x3DFF83, + ItemName.climb: 0x3DFF84, + ItemName.cling: 0x3DFF85, + ItemName.cartwheel: 0x3DFF86, + ItemName.swim: 0x3DFF87, + ItemName.team_attack: 0x3DFF88, + ItemName.helicopter_spin: 0x3DFF89, + ItemName.rambi: 0x3DFF8A, + ItemName.squawks: 0x3DFF8B, + ItemName.enguarde: 0x3DFF8C, + ItemName.squitter: 0x3DFF8D, + ItemName.rattly: 0x3DFF8E, + ItemName.clapper: 0x3DFF8F, + ItemName.glimmer: 0x3DFF90, + ItemName.skull_kart: 0x3DFF91, + ItemName.barrel_kannons: 0x3DFF92, + ItemName.barrel_exclamation: 0x3DFF93, + ItemName.barrel_kong: 0x3DFF93, + ItemName.barrel_warp: 0x3DFF94, + ItemName.barrel_control: 0x3DFF95, +} + +unlock_data = { + STARTING_ID + 0x0001: [0x28, 0x56], # Galleon + STARTING_ID + 0x0002: [0x29, 0x56], # Cauldron + STARTING_ID + 0x0003: [0x2A, 0x56], # Quay + STARTING_ID + 0x0004: [0x2B, 0x56], # Kremland + STARTING_ID + 0x0005: [0x2C, 0x56], # Gulch + STARTING_ID + 0x0006: [0x2D, 0x56], # Keep + STARTING_ID + 0x0007: [0x2E, 0x56], # Flying Krock + STARTING_ID + 0x000B: [0x30, 0x56], # Lost World (Cauldron) + STARTING_ID + 0x000C: [0x31, 0x56], # Lost World (Quay) + STARTING_ID + 0x000D: [0x32, 0x56], # Lost World (C) + STARTING_ID + 0x000E: [0x33, 0x56], # Lost World (Cauldron) + STARTING_ID + 0x000F: [0x34, 0x56], # Lost World (Cauldron) + STARTING_ID + 0x0010: [0x0E, 0x05], # Diddy + STARTING_ID + 0x0011: [0x0E, 0x05], # Dixie + STARTING_ID + 0x0012: [0x00, 0x36], # Carry + STARTING_ID + 0x0013: [0x02, 0x36], # Climb + STARTING_ID + 0x0014: [0x03, 0x36], # Cling + STARTING_ID + 0x0015: [0x01, 0x36], # Cartwheel + STARTING_ID + 0x0016: [0x05, 0x36], # Swim + STARTING_ID + 0x0017: [0x06, 0x05], # Team Attack + STARTING_ID + 0x0018: [0x04, 0x36], # Helicopter Spin + STARTING_ID + 0x0019: [0x07, 0x35], # Rambi + STARTING_ID + 0x001A: [0x08, 0x35], # Squawks + STARTING_ID + 0x001B: [0x09, 0x35], # Enaguarde + STARTING_ID + 0x001C: [0x0A, 0x35], # Squitter + STARTING_ID + 0x001D: [0x0B, 0x35], # Rattly + STARTING_ID + 0x001E: [0x0C, 0x35], # Clapper + STARTING_ID + 0x001F: [0x0D, 0x35], # Glimmer + STARTING_ID + 0x0020: [0x0F, 0x4B], # Barrel Kannons + STARTING_ID + 0x0021: [0x12, 0x4B], # Barrel Exclamation + STARTING_ID + 0x0022: [0x10, 0x4B], # Barrel Kong + STARTING_ID + 0x0023: [0x13, 0x4B], # Barrel Warp + STARTING_ID + 0x0024: [0x11, 0x4B], # Barrel Control + STARTING_ID + 0x0025: [0x14, 0x35], # Skull Kart +} + +currency_data = { + STARTING_ID + 0x0008: [0x802F, 0x36], # Lost World Rock + STARTING_ID + 0x0009: [0x08CE, 0x56], # DK Coin + STARTING_ID + 0x000A: [0x08CC, 0x36], # Kremkoin + STARTING_ID + 0x0030: [0x08CA, 0x2D], # Banana Coin + STARTING_ID + 0x0031: [0x08BE, 0x2C], # 1-Up +} + +trap_data = { + STARTING_ID + 0x0040: [0x40, 0x00], # Freeze Trap + STARTING_ID + 0x0041: [0x42, 0x00], # Reverse Trap +} + + +class DKC2PatchExtension(APPatchExtension): + game = "Donkey Kong Country 2" + + @staticmethod + def shuffle_levels(caller: APProcedurePatch, rom: bytes) -> bytes: + unshuffled_rom = bytearray(rom) + rom = bytearray(rom) + rom_connections = json.loads(caller.get_file("levels.json").decode("UTF-8")) + + from .Levels import level_rom_data, boss_rom_data + dkc2_level_rom_data = dict(level_rom_data, **boss_rom_data) + + + for level, selected_level in rom_connections.items(): + addr = dkc2_level_rom_data[level][0] + rom[addr] = selected_level[1] + + source_ptr = dkc2_level_rom_data[selected_level[0]][1] + name_size = unshuffled_rom[source_ptr] + name = unshuffled_rom[source_ptr + 1:source_ptr + name_size + 2] + + destination_ptr = dkc2_level_rom_data[level][1] + rom[destination_ptr] = name_size + rom[destination_ptr + 1:destination_ptr + name_size + 2] = bytearray(name) + + return bytes(rom) + class DKC2ProcedurePatch(APProcedurePatch, APTokenMixin): hash = [HASH_US, HASH_US_REV_1] game = "Donkey Kong Country 2" @@ -22,6 +129,7 @@ class DKC2ProcedurePatch(APProcedurePatch, APTokenMixin): procedure = [ ("apply_tokens", ["token_patch.bin"]), ("apply_bsdiff4", ["dkc2_basepatch.bsdiff4"]), + ("shuffle_levels", []), ] @classmethod @@ -34,13 +142,44 @@ def write_byte(self, offset, value): def write_bytes(self, offset, value: typing.Iterable[int]): self.write_token(APTokenTypes.WRITE, offset, bytes(value)) - -def patch_rom(world: World, patch: DKC2ProcedurePatch): +def patch_rom(world: "DKC2World", patch: DKC2ProcedurePatch): # Edit the ROM header from Utils import __version__ patch.name = bytearray(f'DKC2{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21] patch.name.extend([0] * (21 - len(patch.name))) - patch.write_bytes(0x7FC0, patch.name) + patch.write_bytes(0xFFC0, patch.name) + + # Set goal + patch.write_byte(0x3DFF81, world.options.goal.value) + patch.write_byte(0x3DFF82, world.options.lost_world_rocks.value) + + # Set starting lives + patch.write_byte(0x008FA1, world.options.starting_life_count.value) + + # Write starting inventory + patch.write_byte(0x3DFF80, world.options.starting_kong.value) + + for item in item_groups["Abilities"]: + addr = rom_start_inventory[item] + if item in world.options.shuffle_abilities.value: + patch.write_byte(addr, 0x00) + else: + patch.write_byte(addr, 0x01) + for item in item_groups["Animals"]: + addr = rom_start_inventory[item] + if item in world.options.shuffle_animals.value: + patch.write_byte(addr, 0x00) + else: + patch.write_byte(addr, 0x01) + for item in item_groups["Barrels"]: + addr = rom_start_inventory[item] + if item in world.options.shuffle_barrels.value: + patch.write_byte(addr, 0x00) + else: + patch.write_byte(addr, 0x01) + + # Save shuffled levels data + patch.write_file("levels.json", json.dumps(world.rom_connections).encode("UTF-8")) patch.write_file("token_patch.bin", patch.get_token_binary()) diff --git a/worlds/dkc2/Rules.py b/worlds/dkc2/Rules.py index c42fb0fe8419..11c77fa61701 100644 --- a/worlds/dkc2/Rules.py +++ b/worlds/dkc2/Rules.py @@ -5,7 +5,7 @@ from .Names import LocationName, ItemName, RegionName, EventName -from worlds.generic.Rules import CollectionRule, add_rule +from worlds.generic.Rules import CollectionRule from BaseClasses import CollectionState class DKC2Rules: @@ -34,6 +34,26 @@ def __init__(self, world: "DKC2World") -> None: self.can_access_keep, f"{RegionName.crocodile_isle} -> {RegionName.the_flying_krock}": self.can_access_krock, + f"{RegionName.crocodile_cauldron} -> {RegionName.lost_world_cauldron}": + self.can_access_lost_world_cauldron, + f"{RegionName.krem_quay} -> {RegionName.lost_world_quay}": + self.can_access_lost_world_quay, + f"{RegionName.krazy_kremland} -> {RegionName.lost_world_kremland}": + self.can_access_lost_world_kremland, + f"{RegionName.gloomy_gulch} -> {RegionName.lost_world_gulch}": + self.can_access_lost_world_gulch, + f"{RegionName.krools_keep} -> {RegionName.lost_world_keep}": + self.can_access_lost_world_keep, + f"{RegionName.lost_world_cauldron} -> {RegionName.krocodile_core_map}": + self.can_access_kore, + f"{RegionName.lost_world_quay} -> {RegionName.krocodile_core_map}": + self.can_access_kore, + f"{RegionName.lost_world_kremland} -> {RegionName.krocodile_core_map}": + self.can_access_kore, + f"{RegionName.lost_world_gulch} -> {RegionName.krocodile_core_map}": + self.can_access_kore, + f"{RegionName.lost_world_keep} -> {RegionName.krocodile_core_map}": + self.can_access_kore, } def can_access_galleon(self, state: CollectionState) -> bool: @@ -56,6 +76,24 @@ def can_access_keep(self, state: CollectionState) -> bool: def can_access_krock(self, state: CollectionState) -> bool: return state.has(ItemName.the_flying_krock, self.player) + + def can_access_lost_world_cauldron(self, state: CollectionState) -> bool: + return state.has(ItemName.lost_world_cauldron, self.player) + + def can_access_lost_world_quay(self, state: CollectionState) -> bool: + return state.has(ItemName.lost_world_quay, self.player) + + def can_access_lost_world_kremland(self, state: CollectionState) -> bool: + return state.has(ItemName.lost_world_kremland, self.player) + + def can_access_lost_world_gulch(self, state: CollectionState) -> bool: + return state.has(ItemName.lost_world_gulch, self.player) + + def can_access_lost_world_keep(self, state: CollectionState) -> bool: + return state.has(ItemName.lost_world_keep, self.player) + + def can_access_kore(self, state: CollectionState) -> bool: + return state.has(ItemName.lost_world_rock, self.player, self.world.options.lost_world_rocks.value) def has_diddy(self, state: CollectionState) -> bool: return state.has(ItemName.diddy, self.player) @@ -138,8 +176,18 @@ def set_dkc2_rules(self) -> None: for loc in multiworld.get_locations(self.player): if loc.name in self.location_rules: loc.access_rule = self.location_rules[loc.name] - - multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.victory, self.player) + + if self.world.options.goal.value == 0x01: + multiworld.completion_condition[self.player] = lambda state: state.has(EventName.k_rool_duel_clear, self.player) + + elif self.world.options.goal.value == 0x02: + multiworld.completion_condition[self.player] = lambda state: state.has(EventName.krocodile_core_clear, self.player) + + else: + multiworld.completion_condition[self.player] = lambda state: ( + state.has(EventName.k_rool_duel_clear, self.player) and + state.has(EventName.krocodile_core_clear, self.player) + ) class DKC2StrictRules(DKC2Rules): @@ -181,7 +229,7 @@ def __init__(self, world: "DKC2World") -> None: LocationName.lockjaws_locker_clear: self.can_swim, LocationName.lockjaws_locker_kong: - lambda state: self.can_swim(state) and self.has_enguarde(state), + self.can_swim, LocationName.lockjaws_locker_dk_coin: lambda state: self.can_swim(state) and self.has_enguarde(state), LocationName.lockjaws_locker_bonus_1: @@ -240,7 +288,7 @@ def __init__(self, world: "DKC2World") -> None: LocationName.kannons_klaim_kong: lambda state: self.can_carry(state) and self.has_kannons(state), LocationName.kannons_klaim_dk_coin: - lambda state: self.can_hover(state) and self.has_kannons(state), + lambda state: self.can_hover(state), LocationName.kannons_klaim_bonus_1: lambda state: self.can_use_diddy_barrels(state) and self.can_use_dixie_barrels(state) and self.can_hover(state), @@ -427,7 +475,7 @@ def __init__(self, world: "DKC2World") -> None: lambda state: self.can_climb(state) and self.can_cling(state) and self.has_invincibility(state) and self.can_hover(state), LocationName.mudhole_marsh_bonus_1: - lambda state: self.can_climb(state) and self.can_cling(state) and self.has_invincibility(state) and + lambda state: self.can_cling(state) and self.has_invincibility(state) and self.can_carry(state) and self.can_team_attack(state), LocationName.mudhole_marsh_bonus_2: lambda state: self.can_climb(state) and self.can_cling(state) and self.has_invincibility(state) and @@ -757,7 +805,7 @@ def __init__(self, world: "DKC2World") -> None: LocationName.kannons_klaim_kong: lambda state: self.can_carry(state) and self.has_kannons(state), LocationName.kannons_klaim_dk_coin: - lambda state: self.can_hover(state) and self.has_kannons(state), + lambda state: self.can_hover(state) and self.can_cartwheel(state), LocationName.kannons_klaim_bonus_1: lambda state: self.can_use_diddy_barrels(state) and self.can_use_dixie_barrels(state) and self.can_hover(state), @@ -780,7 +828,7 @@ def __init__(self, world: "DKC2World") -> None: self.has_enguarde(state) and self.can_carry(state), LocationName.red_hot_ride_clear: - self.can_carry, + self.true, LocationName.red_hot_ride_kong: self.can_carry, LocationName.red_hot_ride_dk_coin: @@ -940,7 +988,7 @@ def __init__(self, world: "DKC2World") -> None: LocationName.mudhole_marsh_dk_coin: lambda state: self.can_climb(state) and self.can_cling(state) and self.can_hover(state), LocationName.mudhole_marsh_bonus_1: - lambda state: self.can_climb(state) and self.can_cling(state) and self.can_carry(state) and + lambda state: self.can_cling(state) and self.can_carry(state) and self.can_team_attack(state), LocationName.mudhole_marsh_bonus_2: lambda state: self.can_climb(state) and self.can_cling(state) and self.can_carry(state), @@ -1068,9 +1116,9 @@ def __init__(self, world: "DKC2World") -> None: self.has_squawks(state), LocationName.castle_crush_clear: - lambda state: self.has_rambi(state) and self.can_cartwheel(state), + lambda state: self.can_cartwheel(state) or (self.has_rambi(state) and self.can_carry(state)), LocationName.castle_crush_kong: - lambda state: self.can_cartwheel(state) and self.can_carry(state), + lambda state: self.can_cartwheel(state) or (self.has_rambi(state) and self.can_carry(state)), LocationName.castle_crush_dk_coin: lambda state: self.can_cartwheel(state) and self.has_squawks(state), LocationName.castle_crush_bonus_1: @@ -1362,9 +1410,9 @@ def __init__(self, world: "DKC2World") -> None: LocationName.red_hot_ride_kong: - self.can_carry, + lambda state: self.can_carry(state) or (self.has_diddy(state) and self.has_dixie(state)), LocationName.red_hot_ride_dk_coin: - self.can_carry, + lambda state: self.can_carry(state) or (self.has_diddy(state) and self.has_dixie(state)), LocationName.red_hot_ride_bonus_1: lambda state: self.can_carry(state) or self.has_rambi(state), @@ -1613,7 +1661,13 @@ def __init__(self, world: "DKC2World") -> None: LocationName.web_woods_clear: self.has_squitter, LocationName.web_woods_kong: - self.has_squitter, + lambda state: ( + self.can_team_attack(state) or + ( + self.has_squitter(state) and + self.can_cartwheel(state) + ) + ), LocationName.web_woods_dk_coin: lambda state: self.has_squitter(state) and self.has_kannons(state), LocationName.web_woods_bonus_1: @@ -1632,10 +1686,9 @@ def __init__(self, world: "DKC2World") -> None: ) ), LocationName.arctic_abyss_kong: - lambda state: self.can_swim(state) or ( - self.has_enguarde(state) and ( - self.can_cartwheel(state) or self.can_hover(state) - ) + lambda state: ( + (self.can_swim(state) or self.has_enguarde(state)) and + (self.can_cartwheel(state or self.can_hover(state))) ), LocationName.arctic_abyss_dk_coin: lambda state: self.can_swim(state) or ( @@ -1671,15 +1724,24 @@ def __init__(self, world: "DKC2World") -> None: lambda state: self.can_carry(state) and self.can_cling(state) and self.has_squawks(state), LocationName.castle_crush_clear: - self.can_carry, + lambda state: self.can_cartwheel(state) or (self.has_dixie(state) and self.has_diddy(state)), LocationName.castle_crush_kong: - self.can_carry, + lambda state: self.can_cartwheel(state) or (self.has_dixie(state) and self.has_diddy(state)), LocationName.castle_crush_dk_coin: - lambda state: self.can_carry(state) and self.has_squawks(state), + lambda state: self.has_squawks(state) and ( + self.can_cartwheel(state) or (self.has_dixie(state) and self.has_diddy(state)) + ), LocationName.castle_crush_bonus_1: - self.has_rambi, + lambda state: self.has_rambi(state) and ( + (self.has_diddy(state) and self.has_dixie) or + self.can_carry(state) + ), LocationName.castle_crush_bonus_2: - lambda state: self.can_carry(state) and self.has_squawks(state), + lambda state: self.can_carry(state) and self.has_squawks(state) and ( + (self.has_diddy(state) and self.has_dixie) or + self.can_cartwheel(state) or + self.has_rambi(state) + ), LocationName.clappers_cavern_clear: lambda state: self.has_clapper(state) and self.has_kannons(state) and (( @@ -1717,13 +1779,15 @@ def __init__(self, world: "DKC2World") -> None: self.can_cling(state) or self.has_kannons(state) ), LocationName.chain_link_chamber_bonus_1: - lambda state: self.can_climb(state) and self.can_cling(state) or self.can_carry(state), + lambda state: self.can_climb(state) and self.can_cling(state) and self.can_carry(state), LocationName.chain_link_chamber_bonus_2: lambda state: self.can_climb(state) and self.has_controllable_barrels(state) and ( self.can_cling(state) or self.has_kannons(state) ) and ( - self.can_cartwheel(state) or self.can_team_attack(state) + self.can_cartwheel(state) or self.can_team_attack(state) or ( + self.has_dixie(state) and self.has_diddy(state) + ) ), LocationName.toxic_tower_clear: @@ -1797,11 +1861,26 @@ def __init__(self, world: "DKC2World") -> None: ), LocationName.fiery_furnace_clear: - self.has_controllable_barrels, + lambda state: ( + self.has_controllable_barrels(state) and ( + self.can_cartwheel(state) or + (self.has_diddy(state) and self.has_dixie(state)) + ) + ), LocationName.fiery_furnace_kong: - self.has_controllable_barrels, + lambda state: ( + self.has_controllable_barrels(state) and ( + self.can_cartwheel(state) or + (self.has_diddy(state) and self.has_dixie(state)) + ) + ), LocationName.fiery_furnace_dk_coin: - self.has_controllable_barrels, + lambda state: ( + self.has_controllable_barrels(state) and ( + self.can_cartwheel(state) or + (self.has_diddy(state) and self.has_dixie(state)) + ) + ), LocationName.animal_antics_clear: lambda state: self.can_swim(state) and self.has_kannons(state) and diff --git a/worlds/dkc2/__init__.py b/worlds/dkc2/__init__.py index ac7ed378e0e0..e4b1dafd5ac4 100644 --- a/worlds/dkc2/__init__.py +++ b/worlds/dkc2/__init__.py @@ -78,13 +78,15 @@ def create_regions(self) -> None: connect_regions(self) - total_required_locations = 192 + total_required_locations = 191 # Set starting kong if self.options.starting_kong == StartingKong.option_diddy: self.multiworld.push_precollected(self.create_item(ItemName.diddy)) + itempool += [self.create_item(ItemName.dixie)] elif self.options.starting_kong == StartingKong.option_dixie: self.multiworld.push_precollected(self.create_item(ItemName.dixie)) + itempool += [self.create_item(ItemName.diddy)] elif self.options.starting_kong == StartingKong.option_both: self.multiworld.push_precollected(self.create_item(ItemName.diddy)) self.multiworld.push_precollected(self.create_item(ItemName.dixie)) @@ -98,55 +100,65 @@ def create_regions(self) -> None: itempool += [self.create_item(ItemName.gloomy_gulch)] itempool += [self.create_item(ItemName.krools_keep)] itempool += [self.create_item(ItemName.the_flying_krock)] - - itempool += [self.create_item(ItemName.diddy)] - itempool += [self.create_item(ItemName.dixie)] - itempool += [self.create_item(ItemName.carry)] - itempool += [self.create_item(ItemName.climb)] - itempool += [self.create_item(ItemName.cling)] - itempool += [self.create_item(ItemName.cartwheel)] - itempool += [self.create_item(ItemName.swim)] - itempool += [self.create_item(ItemName.team_attack)] - itempool += [self.create_item(ItemName.helicopter_spin)] - - itempool += [self.create_item(ItemName.rambi)] - itempool += [self.create_item(ItemName.squawks)] - itempool += [self.create_item(ItemName.enguarde)] - itempool += [self.create_item(ItemName.squitter)] - itempool += [self.create_item(ItemName.rattly)] - itempool += [self.create_item(ItemName.clapper)] - itempool += [self.create_item(ItemName.glimmer)] - - itempool += [self.create_item(ItemName.barrel_kannons)] - itempool += [self.create_item(ItemName.barrel_exclamation)] - itempool += [self.create_item(ItemName.barrel_kong)] - itempool += [self.create_item(ItemName.barrel_warp)] - itempool += [self.create_item(ItemName.barrel_control)] + itempool += [self.create_item(ItemName.lost_world_cauldron)] + itempool += [self.create_item(ItemName.lost_world_quay)] + itempool += [self.create_item(ItemName.lost_world_kremland)] + itempool += [self.create_item(ItemName.lost_world_gulch)] + itempool += [self.create_item(ItemName.lost_world_keep)] + + for item in item_groups["Abilities"]: + if item in self.options.shuffle_abilities.value: + itempool += [self.create_item(item)] + else: + self.multiworld.push_precollected(self.create_item(item)) + + for item in item_groups["Animals"]: + if item in self.options.shuffle_animals.value: + itempool += [self.create_item(item)] + else: + self.multiworld.push_precollected(self.create_item(item)) + + for item in item_groups["Barrels"]: + if item in self.options.shuffle_barrels.value: + itempool += [self.create_item(item)] + else: + self.multiworld.push_precollected(self.create_item(item)) + + for _ in range(self.options.lost_world_rocks.value): + itempool.append(self.create_item(ItemName.lost_world_rock)) + + # Add trap items into the pool + junk_count = total_required_locations - len(itempool) + trap_weights = [] + trap_weights += ([ItemName.freeze_trap] * self.options.freeze_trap_weight.value) + trap_weights += ([ItemName.reverse_trap] * self.options.reverse_trap_weight.value) + trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.options.trap_fill_percentage.value / 100.0)) + junk_count -= trap_count + + trap_pool = [] + for _ in range(trap_count): + trap_item = self.random.choice(trap_weights) + trap_pool.append(self.create_item(trap_item)) - itempool += [self.create_item(ItemName.skull_kart)] + itempool += trap_pool # Add junk items into the pool - junk_count = total_required_locations - len(itempool) - junk_weights = [] junk_weights += ([ItemName.red_balloon] * 40) junk_weights += ([ItemName.banana_coin] * 20) junk_pool = [] - for i in range(junk_count): + for _ in range(junk_count): junk_item = self.random.choice(junk_weights) junk_pool.append(self.create_item(junk_item)) itempool += junk_pool - # Set victory item - self.multiworld.get_location(LocationName.k_rool_duel_clear, self.player).place_locked_item(self.create_item(ItemName.victory)) - # Finish self.multiworld.itempool += itempool - def create_item(self, name: str, force_classification=False) -> Item: + def create_item(self, name: str, force_classification=False) -> DKC2Item: data = item_table[name] if force_classification: @@ -163,6 +175,10 @@ def create_item(self, name: str, force_classification=False) -> Item: return created_item + def interpret_slot_data(self, slot_data): + return slot_data + + def set_rules(self): logic = self.options.logic if logic == Logic.option_strict: @@ -178,6 +194,14 @@ def set_rules(self): def fill_slot_data(self): slot_data = {} + slot_data["level_connections"] = self.level_connections + slot_data["boss_connections"] = self.boss_connections + slot_data["goal"] = self.options.goal.value + slot_data["starting_kong"] = self.options.starting_kong.value + slot_data["lost_world_rocks"] = self.options.lost_world_rocks.value + slot_data["shuffled_abilities"] = self.options.shuffle_abilities.value + slot_data["shuffled_animals"] = self.options.shuffle_animals.value + slot_data["shuffled_barrels"] = self.options.shuffle_barrels.value return slot_data @@ -187,6 +211,19 @@ def generate_early(self): self.rom_connections = dict() generate_level_list(self) + # Handle Universal Tracker support, doesn't do anything during regular generation + if hasattr(self.multiworld, "re_gen_passthrough"): + if "Donkey Kong Country 2" in self.multiworld.re_gen_passthrough: + passthrough = self.multiworld.re_gen_passthrough["Donkey Kong Country 2"] + self.level_connections = passthrough["level_connections"] + self.boss_connections = passthrough["boss_connections"] + self.options.goal.value = passthrough["goal"] + self.options.starting_kong.value = passthrough["starting_kong"] + self.options.lost_world_rocks.value = passthrough["lost_world_rocks"] + self.options.shuffle_abilities.value = passthrough["shuffled_abilities"] + self.options.shuffle_animals.value = passthrough["shuffled_animals"] + self.options.shuffle_barrels.value = passthrough["shuffled_barrels"] + def write_spoiler(self, spoiler_handle: typing.TextIO) -> None: pass diff --git a/worlds/dkc2/data/dkc2_basepatch.bsdiff4 b/worlds/dkc2/data/dkc2_basepatch.bsdiff4 index abd57bc92e075ec73cdbac91369a2546bc6359a1..1aaae615d93f143b0741e098c282700d07e91a9c 100644 GIT binary patch literal 2857 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 literal 1055 zcmV+)1mOEZQ$$HdMl>)r000000002I0{{R30000006+i$0000&T4*^jL0KkKS$D10 z&Hw-dR64)M-ys(=<@!O*8-i00xGQ8Vv(Y4GjUH4H^IpfCE4P z27^X`4K`Aq;Ce5NK~TX}u;@DGFY^f4jYAJ~^b!Rd&#`)%K#InY0flJ-0=0=PTS7@K zt3pXCh9n7GF%>N!LP=OOfJ?N{Bn7)d010dW0VIg;h@@a7AOJ5yW4Y->HUdZh1ANdn zsdRX_yPX<%tC@I_hKMSfTE-LuJLJTtR8X5@#SJPKc5Vbt)v?m}MNX*%&bkV`aDm-Em0UR*MT~}; zYuQRtl_DEUWT`I=4j{vjL!lYT3u-7Lio@+LR*{H(SR45e30Ux|aEWpy=gDAzoc&yj zH~UG&;R0h0Ggms<7;p^7_Avc`oTxgDMZEpO!yK{*JOhH=I>X0+P@`)qYzknh1O;-6 zW5j)9fkEy#4|gpoeD-ze{;XT6DqA4%>%NgAsCy7W3c}(96O)Td@+i3XF=us0045iuMhwL