First pass on multiworld for enemy drops. Fixed a few graphical enemizer issues.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
2
Rom.py
@@ -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.
@@ -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))
|
||||
|
||||
|
||||
@@ -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]),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"]]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user