diff --git a/MultiClient.py b/MultiClient.py index fbde673d..559f6f63 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -8,10 +8,12 @@ import shlex import urllib.parse import websockets -from BaseClasses import PotItem, PotFlags +from BaseClasses import PotItem, PotFlags, LocationType import Items import Regions import PotShuffle +import source.dungeon.EnemyList as EnemyList +import source.rom.DataTables as DataTables class ReceivedItem: @@ -66,6 +68,9 @@ class Context: self.lookup_name_to_id = {} self.lookup_id_to_name = {} + self.pottery_locations_enabled = None + self.uw_sprite_locations_enabled = None + def color_code(*args): codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43, @@ -96,6 +101,9 @@ SHOP_SRAM_START = WRAM_START + 0x0164B8 # 2 bytes? ITEM_SRAM_SIZE = 0x250 SHOP_SRAM_LEN = 0x29 # 41 tracked items +POT_LOCATION_TABLE = 0x142A60 +UW_SPRITE_LOCATION_TABLE = 0x142CB0 + RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte @@ -826,12 +834,16 @@ def get_location_name_from_address(ctx, address): def filter_location(ctx, location): - if (not ctx.key_drop_mode and location in PotShuffle.key_drop_data - and PotShuffle.key_drop_data[location][0] == 'Drop'): - return True - if (not ctx.pottery_mode and location in PotShuffle.key_drop_data - and PotShuffle.key_drop_data[location][0] == 'Pot'): - return True + if location in location_table_pot_items: + tile_idx, mask = location_table_pot_items[location] + tracking_data = ctx.pottery_locations_enabled + tile_pots = tracking_data[tile_idx] | (tracking_data[tile_idx+1] << 8) + return (mask & tile_pots) == 0 + if location in location_table_sprite_items: + tile_idx, mask = location_table_sprite_items[location] + tracking_data = ctx.uw_sprite_locations_enabled + tile_sprites = tracking_data[tile_idx] | (tracking_data[tile_idx+1] << 8) + return (mask & tile_sprites) == 0 if not ctx.shop_mode and location in Regions.flat_normal_shops: return True if not ctx.retro_mode and location in Regions.flat_retro_shops: @@ -842,13 +854,6 @@ def filter_location(ctx, location): def init_lookups(ctx): ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()} ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()} - for location, datum in PotShuffle.key_drop_data.items(): - type = datum[0] - if type == 'Drop': - location_id, super_tile, sprite_index = datum[1] - location_table_sprite_items[location] = (2 * super_tile, 0x8000 >> sprite_index) - ctx.lookup_name_to_id[location] = location_id - ctx.lookup_id_to_name[location_id] = location for super_tile, pot_list in PotShuffle.vanilla_pots.items(): for pot_index, pot in enumerate(pot_list): if pot.item != PotItem.Hole: @@ -862,6 +867,25 @@ def init_lookups(ctx): location_id = Regions.pot_address(pot_index, super_tile) ctx.lookup_name_to_id[loc_name] = location_id ctx.lookup_id_to_name[location_id] = loc_name + logging.info('Init Lookups') + uw_table = DataTables.get_uw_enemy_table() + key_drop_data = {(v[1][1], v[1][2]): k for k, v in PotShuffle.key_drop_data.items() if v[1] == 'Drop'} + for super_tile, enemy_list in uw_table.room_map.items(): + index_adj = 0 + for index, sprite in enumerate(enemy_list): + if sprite.sub_type == 0x07: # overlord + index_adj += 1 + continue + if (super_tile, index) in key_drop_data: + loc_name = key_drop_data[(super_tile, index)] + else: + loc_name = f'{sprite.region} Enemy #{index+1}' + if index < index_adj: + logging.info(f'Problem at {hex(super_tile)} {loc_name}') + location_table_sprite_items[loc_name] = (2 * super_tile, 0x8000 >> (index-index_adj)) + location_id = EnemyList.drop_address(index, super_tile) + ctx.lookup_name_to_id[loc_name] = location_id + ctx.lookup_id_to_name[location_id] = loc_name async def track_locations(ctx : Context, roomid, roomdata): @@ -983,6 +1007,7 @@ async def game_watcher(ctx : Context): if not ctx.rom: rom = await snes_read(ctx, ROMNAME_START, ROMNAME_SIZE) + logging.info(f'Rom name: {rom} @ {hex(ROMNAME_START)}') if rom is None or rom == bytes([0] * ROMNAME_SIZE): continue @@ -996,6 +1021,12 @@ async def game_watcher(ctx : Context): logging.warning("ROM change detected, please reconnect to the multiworld server") await disconnect(ctx) + if ctx.pottery_locations_enabled is None: + ctx.pottery_locations_enabled = await snes_read(ctx, POT_LOCATION_TABLE, 0x250) + + if ctx.uw_sprite_locations_enabled is None: + ctx.uw_sprite_locations_enabled = await snes_read(ctx, UW_SPRITE_LOCATION_TABLE, 0x250) + gamemode = await snes_read(ctx, WRAM_START + 0x10, 1) if gamemode is None or gamemode[0] not in INGAME_MODES: continue diff --git a/MultiServer.py b/MultiServer.py index a9f9e9e2..7040ce27 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -16,6 +16,8 @@ import Items import Regions import PotShuffle from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address +import source.dungeon.EnemyList as EnemyList +import source.rom.DataTables as DataTables class Client: def __init__(self, socket): @@ -355,12 +357,6 @@ async def console(ctx : Context): def init_lookups(ctx): ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()} ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()} - for location, datum in PotShuffle.key_drop_data.items(): - type = datum[0] - if type == 'Drop': - location_id = datum[1][0] - ctx.lookup_name_to_id[location] = location_id - ctx.lookup_id_to_name[location_id] = location for super_tile, pot_list in PotShuffle.vanilla_pots.items(): for pot_index, pot in enumerate(pot_list): if pot.item != PotItem.Hole: @@ -373,6 +369,17 @@ def init_lookups(ctx): location_id = Regions.pot_address(pot_index, super_tile) ctx.lookup_name_to_id[loc_name] = location_id ctx.lookup_id_to_name[location_id] = loc_name + uw_table = DataTables.get_uw_enemy_table() + key_drop_data = {(v[1][1], v[1][2]): k for k, v in PotShuffle.key_drop_data.items() if v[1] == 'Drop'} + for super_tile, enemy_list in uw_table.room_map.items(): + for index, sprite in enumerate(enemy_list): + if (super_tile, index) in key_drop_data: + loc_name = key_drop_data[(super_tile, index)] + else: + loc_name = f'{sprite.region} Enemy #{index+1}' + location_id = EnemyList.drop_address(index, super_tile) + ctx.lookup_name_to_id[loc_name] = location_id + ctx.lookup_id_to_name[location_id] = loc_name async def main(): diff --git a/Rom.py b/Rom.py index f3fdfa59..cbeb9725 100644 --- a/Rom.py +++ b/Rom.py @@ -40,7 +40,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '79a57594c59b3fa2e9cf344fc7437bf9' +RANDOMIZERBASEHASH = '563fe28515c5dd9f64270ca475d1c2d3' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 55b2a98f..214584d3 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/source/dungeon/EnemyList.py b/source/dungeon/EnemyList.py index 64beb1d0..fdde1426 100644 --- a/source/dungeon/EnemyList.py +++ b/source/dungeon/EnemyList.py @@ -2107,16 +2107,24 @@ class EnemyTable: data_pointer += 2 for room in range(0, 0x128): if room in self.room_map: + tracking_mask = 0x00 data_address = pc_to_snes(data_pointer) & 0xFFFF rom.write_bytes(pointer_address + room * 2, int16_as_bytes(data_address)) rom.write_byte(data_pointer, 0x01 if room in layered_oam_rooms else 0x00) list_offset = 1 - for sprite in self.room_map[room]: + idx_adj = 0 + for idx, sprite in enumerate(self.room_map[room]): data = sprite.sprite_data() rom.write_bytes(data_pointer + list_offset, data) list_offset += len(data) + if sprite.sub_type == 0x07: # overlord + idx_adj += 1 + continue + if sprite.location is not None: + tracking_mask |= 1 << (15 - idx + idx_adj) rom.write_byte(data_pointer + list_offset, 0xff) data_pointer += list_offset + 1 + rom.write_bytes(snes_to_pc(0x28ACB0) + room * 2, int16_as_bytes(tracking_mask)) else: rom.write_bytes(pointer_address + room * 2, int16_as_bytes(empty_pointer)) diff --git a/source/dungeon/RoomList.py b/source/dungeon/RoomList.py index ecb2d8df..951f5b33 100644 --- a/source/dungeon/RoomList.py +++ b/source/dungeon/RoomList.py @@ -46,9 +46,10 @@ class Room: def find_all_pots(self): pots = [] - pots.extend([x for x in self.layer1 if x.data[2] == 0xFA]) - pots.extend([x for x in self.layer2 if x.data[2] == 0xFA]) - pots.extend([x for x in self.layer3 if x.data[2] == 0xFA]) + pots.extend([x for x in self.layer1 if x.data[2] in {0xFA, 0xFB} and not x.dummy]) + pots.extend([x for x in self.layer2 if x.data[2] in {0xFA, 0xFB} and not x.dummy]) + if self.layer3: + pots.extend([x for x in self.layer3 if x.data[2] in {0xFA, 0xFB} and not x.dummy]) return pots @@ -514,12 +515,12 @@ Room0127 = Room([0xE1, 0x00], RoomObject(0x0AB61E, [0x43, 0xCB, 0xFA]), RoomObject(0x0AB621, [0x4B, 0xCB, 0xFA]), RoomObject(0x0AB624, [0xBF, 0x94, 0xF9]), - RoomObject(0x0AB627, [0xB3, 0xB3, 0xFA]), - RoomObject(0x0AB62A, [0xCB, 0xB3, 0xFA]), + RoomObject(0x0AB627, [0xB3, 0xB3, 0xFA], True), + RoomObject(0x0AB62A, [0xCB, 0xB3, 0xFA], True), RoomObject(0x0AB62D, [0xAD, 0xC8, 0xDF]), RoomObject(0x0AB630, [0xC4, 0xC8, 0xDF]), - RoomObject(0x0AB633, [0xB3, 0xE3, 0xFA]), - RoomObject(0x0AB636, [0xCB, 0xE3, 0xFA]), + RoomObject(0x0AB633, [0xB3, 0xE3, 0xFA], True), + RoomObject(0x0AB636, [0xCB, 0xE3, 0xFA], True), RoomObject(0x0AB639, [0x81, 0x93, 0xC0]), RoomObject(0x0AB63C, [0x81, 0xD2, 0xC0]), RoomObject(0x0AB63F, [0xE1, 0x93, 0xC0]), diff --git a/source/dungeon/RoomObject.py b/source/dungeon/RoomObject.py index 4a890021..a541c926 100644 --- a/source/dungeon/RoomObject.py +++ b/source/dungeon/RoomObject.py @@ -8,9 +8,10 @@ Shuffled_Pot = (0xFB, 0, 0) # formerly weird pot, or black diagonal thing class RoomObject: - def __init__(self, address, data): + def __init__(self, address, data, dummy=False): self.address = address self.data = data + self.dummy = dummy # some room objects are dummies, unreachable def change_type(self, new_type): type_id, datum_a, datum_b = new_type diff --git a/source/enemizer/Enemizer.py b/source/enemizer/Enemizer.py index 00aba1a8..f14893ec 100644 --- a/source/enemizer/Enemizer.py +++ b/source/enemizer/Enemizer.py @@ -75,7 +75,7 @@ def get_possible_sheets(room_id, data_tables, specific, all_sheets, uw_sheets): req = requirements[key] if isinstance(req, dict): req = req[room_id] - if req.static or not req.can_randomize: + if req.static or not req.can_randomize or sprite.static: if req.groups: match_all_room_groups.intersection_update(req.groups) if not match_all_room_groups: @@ -284,9 +284,6 @@ def randomize_underworld_rooms(data_tables, world, player, custom_uw): if room_id in {0, 1, 3, 6, 7, 0xd, 0x14, 0x1c, 0x20, 0x29, 0x30, 0x33, 0x4d, 0x5a, 0x90, 0xa4, 0xac, 0xc8, 0xde}: continue - if room_id not in data_tables.uw_enemy_table.room_map: - continue - # sprite_reqs = data_tables.sprite_requirements current_sprites = data_tables.uw_enemy_table.room_map[room_id] sprite_limit = sum(sprite_limiter[x.kind] if x.kind in sprite_limiter else 1 for x in current_sprites) randomizeable_sprites = get_randomize_able_sprites(room_id, data_tables) diff --git a/source/enemizer/SpriteSheets.py b/source/enemizer/SpriteSheets.py index 610f1696..b132dddd 100644 --- a/source/enemizer/SpriteSheets.py +++ b/source/enemizer/SpriteSheets.py @@ -189,7 +189,7 @@ def init_sprite_requirements(): SpriteRequirement(EnemySprite.Ropa).sub_group(0, 0x16), SpriteRequirement(EnemySprite.RedBari).sub_group(0, 0x1f), SpriteRequirement(EnemySprite.BlueBari).sub_group(0, 0x1f), - SpriteRequirement(EnemySprite.TalkingTree).affix().sub_group(0, 0x15), + SpriteRequirement(EnemySprite.TalkingTree).affix().sub_group(3, [0x15, 0x1B]), SpriteRequirement(EnemySprite.HardhatBeetle).sub_group(1, 0x1e), SpriteRequirement(EnemySprite.Deadrock).sub_group(3, 0x10).exclude({0x7f, 0x10c}), SpriteRequirement(EnemySprite.DarkWorldHintNpc).affix(), # no groups? @@ -700,6 +700,8 @@ def setup_required_overworld_groups(sheets): [None, 73, 19, None], # allow for green knife guard [22, None, 23, None], # increase odds for snapdragon [70, 73, None, None], # guards group (ballnchain, redbush, redjav, cannon, bomb, bluesain + [None, None, None, 0x15], # an option for talking trees + [None, None, None, 0x1B], # an option for talking trees ] for group in free_sheet_reqs: diff --git a/source/enemizer/enemy_deny.yaml b/source/enemizer/enemy_deny.yaml index 13bf6763..7e07e370 100644 --- a/source/enemizer/enemy_deny.yaml +++ b/source/enemizer/enemy_deny.yaml @@ -126,6 +126,7 @@ UwGeneralDeny: - [ 0x005f, 1, [ "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Ice Palace - Bari University - Blue Bari 2" - [ 0x0060, 0, [ "RollerVerticalUp", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Hyrule Castle - West - Blue Guard" - [ 0x0062, 0, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Hyrule Castle - East - Blue Guard" + - [ 0x0064, 2, [ "Bumper" , "Beamos" ] ] #"Thieves' Town - Attic Hall Left - Keese 2" - [ 0x0064, 4, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Attic Hall Left - Rat 1" - [ 0x0065, 0, [ "RollerVerticalUp", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Attic Window - Rat 1" - [ 0x0065, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Attic Window - Rat 2" @@ -224,6 +225,7 @@ UwGeneralDeny: - [ 0x009e, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Red Bari 3" - [ 0x00a0, 1, [ "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Boss Antechamber - Antifairy" - [ 0x00a1, 2, [ "Statue", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Fish Room - Spark (Clockwise) 2" + - [ 0x00a5, 2, [ "BigSpike" ] ] #"GT Wizzrobes 1 - Wizzrobe 3" - [ 0x00a5, 10, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Laser Bridge - Red Spear Guard" - [ 0x00a8, 1, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Eastern Palace - West Wing - Top - Stalfos 2" - [ 0x00a8, 3, [ "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Eastern Palace - West Wing - Top - Stalfos 4" @@ -309,6 +311,7 @@ UwGeneralDeny: - [ 0x00f1, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 5" - [ 0x00f1, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 6" OwGeneralDeny: + - [0x1e, 3, ["Beamos"]] # forbid a beamos here - [0x5e, 4, ["RollerVerticalUp", "Gibo"]] # forbid that one roller for kiki pod, and the kiki eating Gibo - [0x5e, 5, ["Gibo"]] # kiki eating Gibo UwEnemyDrop: @@ -360,7 +363,7 @@ UwEnemyDrop: "BombGuard", "GreenKnifeGuard"]] - [0x00c6, 5, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", - "BombGuard", "GreenKnifeGuard"]] + "BombGuard", "GreenKnifeGuard", "Bumper"]] - [0x00c6, 6, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", "BombGuard", "GreenKnifeGuard"]] diff --git a/source/rom/DataTables.py b/source/rom/DataTables.py index 8cfb3c32..41b1f7ec 100644 --- a/source/rom/DataTables.py +++ b/source/rom/DataTables.py @@ -57,7 +57,6 @@ class DataTables: choice = SheetChoice(tuple(item['slots']), item['assignments'], item['weight']) self.sheet_choices.append(choice) - def write_to_rom(self, rom, colorize_pots=False, increase_bush_sprite_chance=False): if self.pot_secret_table.size() > 0x11c0: raise Exception('Pot table is too big for current area') @@ -175,3 +174,13 @@ def init_data_tables(world, player): data_tables.enemy_damage = {k: list(v) for k, v in world.damage_table[player].enemy_damage.items()} # todo: more denials based on enemy drops return data_tables + + +def get_uw_enemy_table(): + init_vanilla_sprites() + uw_table = EnemyTable() + for room, sprite_list in vanilla_sprites.items(): + for sprite in sprite_list: + uw_table.room_map[room].append(sprite.copy()) + return uw_table + diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index 6229fac1..c7df3bdf 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -109,7 +109,7 @@ def roll_settings(weights): ret.standardize_palettes = (get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize') - goal = get_choice('goals') + goal = get_choice_default('goals', default='ganon') if goal is not None: ret.goal = {'ganon': 'ganon', 'fast_ganon': 'crystals',