From 627168e771b839e38e89382e7a512f7f3aefcc33 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 26 Jun 2023 10:29:37 -0600 Subject: [PATCH] First pass on multiworld for enemy drops. Fixed a few graphical enemizer issues. --- MultiClient.py | 59 ++++++++++++++++++++++++-------- MultiServer.py | 19 ++++++---- Rom.py | 2 +- data/base2current.bps | Bin 99517 -> 99523 bytes source/dungeon/EnemyList.py | 10 +++++- source/dungeon/RoomList.py | 15 ++++---- source/dungeon/RoomObject.py | 3 +- source/enemizer/Enemizer.py | 5 +-- source/enemizer/SpriteSheets.py | 4 ++- source/enemizer/enemy_deny.yaml | 5 ++- source/rom/DataTables.py | 11 +++++- source/tools/MysteryUtils.py | 2 +- 12 files changed, 97 insertions(+), 38 deletions(-) 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 55b2a98f5ba9ff7ba8b1b1b8b03e1f6b46aefc6c..214584d37c75193eab4cf42ed4efeadea411a5f8 100644 GIT binary patch delta 2280 zcmW-ie^gY}7RT?tGcd!z$WKQEM`kWCKq)^}6d_rl2}&R-q=>17HHZ(h@JAwWFEZ+Y zcYb&t9BoV{xa5pV1Kpuf%gDj_B|PzIWti2n_ntuE@Z4~%K~Vr7?QEzMFFj7>vAYjvq%zZ^zy3<-~L&)otL$#ZfcbVU#Xb!bem257WI_yRoRtA*hJrKrWj4Do!XryBfL*e$0EX0dGj z{!hjZX)EpMQ_&If%*QBCT;QAeEw}U_Q)pU6G77!c8}Z`j7f*It#>S2|%6JP6W2uK4 zDL=cVLxf`znKm!#72Xm;^&CBEZlqrM*c}(duvG7L^D3$PN+q2r)N8r#vkuEx1+8rv zG#Aral^yNyih_6eV_tHQXZCa7y`Oi-9K5Mv<>@|&R=D3towHlU&a%{?9bJ?B3^&jP zZv`nHML&4Uz2;o!#y0I|sV|X-PYLWpTYd7O2Yu$V972##`l9dA4htuAm>GfEt>R(_ zS|_a$?;GKY8(69w{V0_|A^%9a&l7t2ilDs^96ZeB^z|reS?X`YoSCIchPh&Q;#1@) zOBWljb7HreT_{(UJ^js5E}{YDtv|9*&r+e^oBz^Tj?TztCeOXe-L;wfKmF~-{aeL# zZ1Fdv+@9LzOO2Xo#T##O(G6^IgFE-I9%>_4lvzvA`lZb*wTpi$D~W-Z`OWE0L16A> zPXC>;d_+XCxZb$kZHM>;OKUxXp7nE9V1Fj5rasPwn0S{5<%5Q@&%y8fffckwnm5SN z5=!T$kQ(L#3p9bC;*Y7<2x8m&xb{oJD@r=vW!jQ>pmRXv3Z=UHxH4;!rXQycYrSpH z|*J6?nrE~BqT7nvid!eO&*)hWw}Krzv~mxkz*3udtTCJ(wyF@gLaEQSg6(u0gR zQq!0*ULB&(upMqV%cR(}j1XW~rg4gsKeR-?jYx>r+%(mv*hV#ukQONjg#jCXggAb}*Ubd9K zs<@%f$?DC*>%(<;txAV?pta*w0RaWNHr04_@b=_YH8H;E0188Q#+S)LW;f;>ClPPqf#lVZYGqC)(4j= zh*M@lF&{-enlFhQ4&HKGbGuBDgx+|R1r5mds0OA`++?s%;FXU^#gD7!x&%3!(dxLcR+C9Uc?wUNFu53?Nj!aq^mdLL@|0X01?06F^gu^ft!Wa_FPKWtdzRM-Fsu4!DeaU%L?c*Q5cNGNfDR;oDtG&fpDvpbuJWR-1P%3iVoiifiXTC$E2X|oSj?s9|(K_>}Wl6z>_m%63lDDDrM zTJ#P5pN_J&&XN4qEQfO%$V7gr^9&~>g$(n>`GNojdlWhDPKVDtL<;zjA#u3lbSSit z;sjwmb1aZv>?oKGJn1oeJ#%s{mMS4j_iqNrB7v2(?!d7E@I7E>}Mfg@Y%o7BP#W*Me7LuF7ac%@e zlfT&Uo(P!jpT>^|(ZwnMJWxhebXod#Zp0@eAkp(7ZxB+y;~yd*KJ6}V8JTV?IAw`b ziK<=VzxEOJdoMSoE*r49?hHh@ewu22z2=Znol<5t+vEub(~5-m{_VTs)X`gad7Kjo z$)p#9HIeXKcz%cb13S0eetMH)vz^-TP#zCuZPEep2-@RAGs>o5sUBaAg!g7`?Xs{k z<^P-9QmqThv=S36nTZ#c>v0%9sDdb;uutd!zOK`$uwLoH=T)$PoV5u5R|P2{nJf20 z^?^jXdiz&lH0(WFNf;CN*y8pcS2I>cG_`VXp5sPOeh&wh~WY_-(ozBIV delta 2244 zcmW+%d010d7Jug@kPr+Iq+t_C9w96);I6ooiVG+TjDl8Csg-`wSgG68h2_0KRM3~j zJ9xE0$;d+k3IU#DTxzx8h#=J1#VS^(qt0kETGJ0uL~AGZpWnIneCM9;cfa57JEy(E z`(lN+X*wJ-&&U?vY_%<;S?aX;V7G9fLox+@0yD_+R#XR@6aRIU6Vp}HB@^dzZJ11( zspeC*d{vbh#BvaY95Ck{`#a~l%~J9^TwG;EH=0Y1CI>svHgW>Fz6qTqGvP6MO}-ER zMbiY)Fbk~~Y$Q#s=!PH+b|IHw34D&;7tVwie2Fj`ARis`vO+AM>aBr*p%&Z8oDnQr zS$4*?w_;=~x*$40<~5^CagKlT9d7m>W}zjEWE92<67k}ZGi-L7%Vp7K(p6WRZ8W&1 z@2#RPx7eCRc;Ub6d?-{oa}-^?WMGjzWFr`P-XpuB~*% zu`X*KU2&@g75c=&dLH{kKt%F2?x!P}kF6p3`xcz+kW>iE%+$J8o9iq~?QBIiCBMK8 zbir3a{&5H0^NsL{{+@H?m$B5BNHAE16A2ttUh?LItPiwMqLWh+RXgn-7 zHKC=_Qt=iim$#3l3ebJ292W8e(jsrT$ZrnX2_gPHTt-K`LdQ~TdpIjgt?1$MJP-Sj zP@W>*`#mT2u=x~a$kPi}^>V6xD09VuDMpqGzi)lVv!IuA&GwXgu2~+PJ>h3QW6J@L zYO1^36T9`=g>8H( zlpm6>jfZr8bxNCH_?+vU@h9^}r-&-YmF7ZEZ09G;)_DcJxW?53`wL029h@C8Np3Gn zfV$Gp!4tlGJ}r^X?c!(&rT3tay28tH^ur;7$C@PqIoH88UJ>3<(n)Sh!MNQmog#NQ zbq2wPU^v5vZkQMX=TK(($8eYD%h&nCZa(5~zmJATe0ZA+Z&V8o4+7u=2=8 zCP|+$l(&)(ztZRh4XCs87^I*LU2X_PE3co1U3_5oHTA><%Z`->A9@erjI6W83y4mV za0bOKx2mYz9;-d8!R8bwo?hKaL)?j-D-FhjUbI&66v_Lh!Bg~?zFCQ%l~>K`F9|cw zvhUw_mPxi(Fj5@7k!F&ytZ=_sqoqDNYtgJUn4{8q?Xi|j)ZRBScElJ{V^Ev2j)?z* zC|uuPa^hE%y2hwCZmcc&$XI)p*-)d~cfRIh6xAOI8E9s|+$TNSfaj_Wcr{wtUmQr} z80yshB_V~Avr6Or(QYI~Py6S{#Su*7l;<8c#+g~AaYLG;-jO;KgXRs45pAAkVpgGS zR5YOWUZtk9>1Jdb;F1*G)Z_k0MSl0u>EoZ(j4gg;PSK0&yT1yQ>d^E}caO`G+L^)H zT)!i1R|-K|CZeJjhl3M(Y~@Lc5gr_??X3)1ryv@vgklsL^>U(QL{CV;1N{T7A^~Y% zrhyKfepwpxo0DrC1)XBI%pRsoa*FLIvr0SPIt3Y$8%?C*=f$Jlf{bNo+29HYK&J<* zCO`b9{&ibGH8IOl5G zNvXjXa6qUJjTWNnH%btruik`u40`)bdwk{(oZGuA;AJJ-sbw5$zzvA{sA6C2vDvIy&@rjAt@- zyuAU^Wjl;j&#fZ}`**1DX;m$#KvH!L62~fMo0vGfJ%dK$iVRFBmM${c{T6w09XckS z*B$WRuph-NGMkH+pSn$=-r5u#2T%;D_&7k~n6r1ecp#h+L-)sRJ2Q~i_QLPd`Mcau z$U350Pk0d0>cfZKvJ+xKrhRI8?K(bgx;HthHM0zGF?6`D4HM zkN^CJL-!7~;OrQfOy@ZJ?0tbNZSf~NXgViSJ*~_P>X#Xcm4_og%bI;TSs>+ kFtrD7!#j3Cfw#!#OvkP@jz_!TN3r-5vg?DXMPsJ@9{}93eE 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',