First pass on multiworld for enemy drops. Fixed a few graphical enemizer issues.

This commit is contained in:
aerinon
2023-06-26 10:29:37 -06:00
parent 503be6aa91
commit 627168e771
12 changed files with 97 additions and 38 deletions

View File

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

View File

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

2
Rom.py
View File

@@ -40,7 +40,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '79a57594c59b3fa2e9cf344fc7437bf9'
RANDOMIZERBASEHASH = '563fe28515c5dd9f64270ca475d1c2d3'
class JsonRom(object):

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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