From 106397b13c6211633bfa5324517ef61c4bcf2b9e Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 29 Dec 2022 19:39:13 -0700 Subject: [PATCH 1/7] Fixed door position typo --- Doors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doors.py b/Doors.py index 32ffd9ae..6627a345 100644 --- a/Doors.py +++ b/Doors.py @@ -775,7 +775,7 @@ def create_doors(world, player): create_door(player, 'Ice Freezors Hole', Hole), create_door(player, 'Ice Freezors Bomb Hole', Hole), # combine these two? -- they have to lead to the same spot create_door(player, 'Ice Freezors Ledge Hole', Hole), - create_door(player, 'Ice Freezors Ledge ES', Intr).dir(Ea, 0x7e, Bot, High).pos(2), + create_door(player, 'Ice Freezors Ledge ES', Intr).dir(Ea, 0x7e, Bot, High).pos(1), create_door(player, 'Ice Tall Hint WS', Intr).dir(We, 0x7e, Bot, High).pos(1), create_door(player, 'Ice Tall Hint EN', Nrml).dir(Ea, 0x7e, Top, High).pos(2), create_door(player, 'Ice Tall Hint SE', Nrml).dir(So, 0x7e, Right, High).small_key().pos(0).portal(X, 0x02), From c2da20207bec1c5b1a6f50da40b8ae7519fa4f80 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 3 Jan 2023 16:10:32 -0700 Subject: [PATCH 2/7] Fix for experimental inverted + vanilla ER --- source/overworld/EntranceShuffle2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index b9bea9cd..460125b0 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -66,6 +66,8 @@ def link_entrances_new(world, player): default_map['Old Man Cave (East)'] = 'Death Mountain Return Cave Exit (West)' one_way_map['Bumper Cave (Top)'] = 'Dark Death Mountain Healer Fairy' del default_map['Bumper Cave (Top)'] + del one_way_map['Big Bomb Shop'] + one_way_map['Inverted Big Bomb Shop'] = 'Inverted Big Bomb Shop' avail_pool.default_map = default_map avail_pool.one_way_map = one_way_map From 8839926a9734de8204c0c93ae4bf24c6de56c1d1 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 6 Jan 2023 13:56:18 -0700 Subject: [PATCH 3/7] Race flag on GUI Async Doors League presets Take into account starting inventory with default item pool --- ItemList.py | 17 ++++++++++ Main.py | 14 ++------ RELEASENOTES.md | 11 +++++-- Rom.py | 4 +-- data/base2current.bps | Bin 93478 -> 93506 bytes .../async_doors_league/S3_BombBag.yaml | 23 ++++++++++++++ docs/presets/async_doors_league/S3_Main.yaml | 22 +++++++++++++ .../async_doors_league/S3_PotteryLottery.yaml | 22 +++++++++++++ .../async_doors_league/S3_Standard.yaml | 30 ++++++++++++++++++ resources/app/gui/lang/en.json | 1 + .../gui/randomize/generation/checkboxes.json | 3 +- source/classes/CustomSettings.py | 5 ++- source/classes/constants.py | 1 + 13 files changed, 134 insertions(+), 19 deletions(-) create mode 100644 docs/presets/async_doors_league/S3_BombBag.yaml create mode 100644 docs/presets/async_doors_league/S3_Main.yaml create mode 100644 docs/presets/async_doors_league/S3_PotteryLottery.yaml create mode 100644 docs/presets/async_doors_league/S3_Standard.yaml diff --git a/ItemList.py b/ItemList.py index 1b4b6087..7080b330 100644 --- a/ItemList.py +++ b/ItemList.py @@ -962,9 +962,26 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt pool.extend(['Small Key (Universal)']) else: pool.extend(['Small Key (Universal)']) + modify_pool_for_start_inventory(pool, world, player) return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) +def modify_pool_for_start_inventory(pool, world, player): + for item in world.precollected_items: + if item.player == player: + pool.remove(item.name) + if item.dungeon: + d = world.get_dungeon(item.dungeon, item.player) + match = next((i for i in d.all_items if i.name == item.name), None) + if match: + if match.map or match.compass: + d.dungeon_items.remove(match) + elif match.smallkey: + d.small_keys.remove(match) + elif match.bigkey: + d.big_key.remove(match) + + def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer, goal, mode, swords, bombbag, customitemarray): if isinstance(customitemarray,dict) and 1 in customitemarray: customitemarray = customitemarray[1] diff --git a/Main.py b/Main.py index cfb7e5e5..820cf079 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -__version__ = '1.2.0.2-u' +__version__ = '1.2.0.3-u' from source.classes.BabelFish import BabelFish @@ -150,7 +150,7 @@ def main(args, seed=None, fish=None): world.player_names[player].append(name) logger.info('') world.settings = CustomSettings() - world.settings.create_from_world(world) + world.settings.create_from_world(world, args.race) outfilebase = f'DR_{args.outputname if args.outputname else world.seed}' @@ -196,16 +196,6 @@ def main(args, seed=None, fish=None): item = ItemFactory(inv_item.strip(), p) if item: world.push_precollected(item) - if item.dungeon: - d = world.get_dungeon(item.dungeon, item.player) - match = next((i for i in d.all_items if i.name == item.name), None) - if match: - if match.map or match.compass: - d.dungeon_items.remove(match) - elif match.smallkey: - d.small_keys.remove(match) - elif match.bigkey: - d.big_key.remove(match) if args.print_custom_yaml: world.settings.record_info(world) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 794af2d6..21f4a884 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,14 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.2.0.3-u + * Starting inventory taken into account with default item pool. (Custom pools must do this themselves) + * Fast ROM update + * Fix for restricted boss item counting maps & compasses as vital + * Bug fix for vanilla ER + inverted + experimental +* 1.2.0.2-u + * Fixed a bug with certain trap doors missing + * Added a hint reference for district hints * 1.2.0.1-u * Added new ganonhunt and completionist goals * Fixed the issue when defeating Agahnim and standing in the doorway can cause door state to linger. @@ -116,9 +124,6 @@ These are now independent of retro mode and have three options: None, Random, an * Fix for vanilla Doors + Standard + ER * Added a limit per dungeon on small key doors to ensure reasonable generation * Fixed many small bugs -* 1.2.0.2-u - * Fixed a bug with certain trap doors missing - * Added a hint reference for district hints # Known Issues diff --git a/Rom.py b/Rom.py index 03ab0283..d210ae01 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'f204143853a58e55a5fbc4c5bc87045e' +RANDOMIZERBASEHASH = '1e1094919d6a9dd151f8bc15c40b35f7' class JsonRom(object): @@ -2226,7 +2226,7 @@ def write_strings(rom, world, player, team): for loc_name in district.locations: location_item = world.get_location(loc_name, player).item if location_item.advancement: - if 'Heart Container' in location_item.name: + if 'Heart Container' in location_item.name or location_item.compass or location_item.map: continue itm_type = 'useful' if useful_item_for_hint(location_item, world) else 'vital' hint_type = 'path' diff --git a/data/base2current.bps b/data/base2current.bps index 41a996a35b4b37b1f6e70916a66e816e618ce471..271b2c2e5bd56abf7e11615aee052723186d40f8 100644 GIT binary patch delta 1286 zcmWlXe@s(X6vulXEk8LnngSbc!M@G2_P&m7$V}iV0?9buLaL{sQhR z1?+>@AH0<}$}Ck|E^R={*jAUh7CM+VnMq@GOSa66GUCcRIjSCjT+ zU`y(44!(h~sNDvK5veCGScA8Dtk7k^vEPiByYM-S6-N7_IRUn}b2K}^np3A*5gmLxAgta!*s&oX7?`~SdS;KH9}S)iu;D~} zZ?HMQdOt=?nO)4-N*1E5)XU#y)aC-jg+V;StU>Hy)GdTMyB|189L9y{LzBLLR_05p z%AdTY_0B{+A~RjvMz5KkvS`Q1Ee)7usPFVShbNL2915^;S=H2(`HDM|L`YL1i()G7jM z>yE|E)Ra^aXRu^MMo?8XsUVQ=d)7z*CW9#iQ`hg&fSqtYR47UQ*o(a+mhy6$6kq{T z;B$ce-%+52(qOc7RuqxCo#9;(v1bEHcRijI#lgeiqjoIRN z9xk5j_6@Wazg*A28Uq%T$zT&HSCI_#Ny~7Uil~R2LD3U@^P)5^=PpXUj-{Ro1_^U% zphj%=R3KrC6uhN8aCXF;FK`a^VIY)&em|0eU%~F1N_e-!1zoQY-1G~Y^Xc5H?Fg9ea;1* z;<+=<$dffzbYFPOY8_DG#Fj8KBN9@redDDCI z@A~FdsJ`e{r|>Ba?kG9CGK;mAL-kdz@1vGEp*9aVBnVF5NB$Riz+0)fP;YULwU%3s Xw*=O%=LFvoa5Qzu#0RqXZ?F6xQ7rS9&6T(33HvR5` zJ_m{&8@I#TLuU^=T`_31qmq-S;aWTLgR-o9HuHDA9CpF;bx8agbOR)o(vZX&I+a8> zox22ihh%lj!PSp?c%kn^@5GdxfK4qm`Pb)JY>ieVPSF{}m!U|@ zLa{AYso=AGo5L(t3U0W!7R6#WT<`_qS9YkfqRTx?+n$S;wSo(7BGz)nc4EDxC^jHx z!jE>i7$d6I4bNiIPicMyt?PHeUMq_IV(Y0x$DA%0ZRw1eYF#jy6#sQWGXX`W%OSmv zDAxcqd_h?U2y)f0#c^~ROCX*~ctm2Arj$ijy)dVVOa&J;q2US5mbhwJ;`?!&)@Fek z=6um6yD-C}6oj-EiwkqXS4X5pJ3E9`o&t-o%VQCWJyq7OLnBOhO5PQ$I&Tp+5V1RN zGFs30L>dc5hZ)<$KsGo`xV{26NRLiln2bK)mR_5j(E1gHOGa;fP=7AkPdT_9HQd!P z=R8L`c56U9ZQ+g_YyUN(3HFUJvbJz+EPjY6$bq9nPDV6Y#Dlu|fDt{q18IyrBO2Ti zVtm2!XVsZ0&#Nc%-VHJ0T>W9P%o+VfCE0m<%IhR?ryO2Lpj*2+{u+Do3Q6)s-hh^% zq;OP6Fmv~_asqZVIE~=u^*bD3h6f(#^hE#d-@8aH`Q`RKxl(}J ztVo)z2HB+DPBpM7W}`42G4^!^sVC@W7bCOwF2>iBtxm>sYNaO{uuU0WASXy07_h=n zymg?h+joU(H&y`2#J>#GGvH^i-lm7QTXPK>+&sV2#e0V$TYJ{mB;ocV?}>Nrit`5_ z@96dF2auLHs2>262Tz!ErNIga4e25zlusnAOi$@!z3T+a$Q4x(N zm$!&3a$2RuET~LF3qd5IV@Y1aH6y>8hf;Izpb{ggp9S*_NSd|VBOMr2Wg^PpaX}UI;NCA6D&s!;AA3 Date: Fri, 6 Jan 2023 15:01:24 -0700 Subject: [PATCH 4/7] Fix for randomization replication issue --- source/overworld/EntranceShuffle2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 460125b0..0a9e206a 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -275,7 +275,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def): # OM Cave entrance in lw/dw if cross_world off if 'Old Man Cave Exit (West)' in rem_exits: world_limiter = DW_Entrances if avail.inverted else LW_Entrances - om_cave_options = [x for x in rem_entrances if x in world_limiter and bonk_fairy_exception(x)] + om_cave_options = sorted([x for x in rem_entrances if x in world_limiter and bonk_fairy_exception(x)]) om_cave_choice = random.choice(om_cave_options) if not avail.coupled: connect_exit('Old Man Cave Exit (West)', om_cave_choice, avail) From 839baee9a4a07139458a7a2c97b097ba520b0bda Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 6 Jan 2023 15:11:38 -0700 Subject: [PATCH 5/7] Minor fix for preset --- docs/presets/async_doors_league/S3_Standard.yaml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/presets/async_doors_league/S3_Standard.yaml b/docs/presets/async_doors_league/S3_Standard.yaml index 0b6e116c..050db332 100644 --- a/docs/presets/async_doors_league/S3_Standard.yaml +++ b/docs/presets/async_doors_league/S3_Standard.yaml @@ -22,9 +22,4 @@ settings: hints: true msu_resume: true collection_rate: true - quickswap: true -start_inventory: - 1: - - Progressive Armor - - Pegasus Boots - - Boss Heart Container \ No newline at end of file + quickswap: true \ No newline at end of file From 4d1b5f58c0537b15c6ea5a79942b765cf92dd571 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 6 Jan 2023 16:57:52 -0700 Subject: [PATCH 6/7] Minor naming convention fixed --- DoorShuffle.py | 2 +- Doors.py | 2 +- Regions.py | 2 +- Rules.py | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index adf2607b..95bfd4c7 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -4247,7 +4247,7 @@ default_door_connections = [ ('Eastern Map Valley SW', 'Eastern Dark Square NW'), ('Eastern Attic Start WS', 'Eastern False Switches ES'), ('Eastern Cannonball Hell WS', 'Eastern Single Eyegore ES'), - ('Desert Compass NW', 'Desert Cannonball S'), + ('Desert Compass NE', 'Desert Cannonball S'), ('Desert Beamos Hall NE', 'Desert Tiles 2 SE'), ('PoD Middle Cage N', 'PoD Pit Room S'), ('PoD Pit Room NW', 'PoD Arena Main SW'), diff --git a/Doors.py b/Doors.py index 6627a345..7c9e5ca6 100644 --- a/Doors.py +++ b/Doors.py @@ -207,7 +207,7 @@ def create_doors(world, player): create_door(player, 'Desert East Wing ES', Intr).dir(Ea, 0x85, Bot, High).pos(3), create_door(player, 'Desert East Wing Key Door EN', Intr).dir(Ea, 0x85, Top, High).small_key().pos(1), create_door(player, 'Desert Compass Key Door WN', Intr).dir(We, 0x85, Top, High).small_key().pos(1), - create_door(player, 'Desert Compass NW', Nrml).dir(No, 0x85, Right, High).trap(0x4).pos(0), + create_door(player, 'Desert Compass NE', Nrml).dir(No, 0x85, Right, High).trap(0x4).pos(0), create_door(player, 'Desert Cannonball S', Nrml).dir(So, 0x75, Right, High).pos(1).portal(X, 0x02), create_door(player, 'Desert Arrow Pot Corner S Edge', Open).dir(So, 0x75, None, High).edge(6, Z, 0x20), create_door(player, 'Desert Arrow Pot Corner W Edge', Open).dir(We, 0x75, None, High).edge(2, Z, 0x20), diff --git a/Regions.py b/Regions.py index 3c307e40..6be2bbf3 100644 --- a/Regions.py +++ b/Regions.py @@ -338,7 +338,7 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Desert Dead End', 'Desert Palace', None, ['Desert Dead End Edge']), create_dungeon_region(player, 'Desert East Lobby', 'Desert Palace', None, ['Desert East Lobby WS', 'Desert East Lobby S']), create_dungeon_region(player, 'Desert East Wing', 'Desert Palace', None, ['Desert East Wing ES', 'Desert East Wing Key Door EN', 'Desert East Wing W Edge', 'Desert East Wing N Edge']), - create_dungeon_region(player, 'Desert Compass Room', 'Desert Palace', ['Desert Palace - Compass Chest'], ['Desert Compass Key Door WN', 'Desert Compass NW']), + create_dungeon_region(player, 'Desert Compass Room', 'Desert Palace', ['Desert Palace - Compass Chest'], ['Desert Compass Key Door WN', 'Desert Compass NE']), create_dungeon_region(player, 'Desert Cannonball', 'Desert Palace', ['Desert Palace - Big Key Chest'], ['Desert Cannonball S']), create_dungeon_region(player, 'Desert Arrow Pot Corner', 'Desert Palace', None, ['Desert Arrow Pot Corner S Edge', 'Desert Arrow Pot Corner W Edge', 'Desert Arrow Pot Corner NW']), create_dungeon_region(player, 'Desert Trap Room', 'Desert Palace', None, ['Desert Trap Room SW']), diff --git a/Rules.py b/Rules.py index 0190aaa4..38ff5666 100644 --- a/Rules.py +++ b/Rules.py @@ -649,7 +649,7 @@ def bomb_rules(world, player): ('Hyrule Dungeon Armory S', True), # One green guard ('Hyrule Dungeon Armory ES', True), # One green guard ('Hyrule Dungeon Armory Boomerang WS', True), # One blue guard - ('Desert Compass NW', True), # Three popos + ('Desert Compass NE', True), # Three popos ('Desert Four Statues NW', True), # Four popos ('Desert Four Statues ES', True), # Four popos ('Hera Beetles WS', False), # Three blue beetles and only two pots, and bombs don't work. @@ -1219,7 +1219,7 @@ std_kill_rooms = { 'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard 'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard 'Eastern Stalfos Spawn': ['Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW'], # Can use pots - 'Desert Compass Room': ['Desert Compass NW'], # Three popos + 'Desert Compass Room': ['Desert Compass NE'], # Three popos 'Desert Four Statues': ['Desert Four Statues NW', 'Desert Four Statues ES'], # Four popos 'Hera Beetles': ['Hera Beetles WS'], # Three blue beetles and only two pots, and bombs don't work. 'Tower Gold Knights': ['Tower Gold Knights SW', 'Tower Gold Knights EN'], # Two ball and chain @@ -1994,7 +1994,7 @@ bunny_impassible_doors = { 'Eastern Map Balcony Hook Path', 'Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW', 'Eastern Darkness S', 'Eastern Darkness NE', 'Eastern Darkness Up Stairs', 'Eastern Attic Start WS', 'Eastern Single Eyegore NE', 'Eastern Duo Eyegores NE', 'Desert Main Lobby Left Path', - 'Desert Main Lobby Right Path', 'Desert Left Alcove Path', 'Desert Right Alcove Path', 'Desert Compass NW', + 'Desert Main Lobby Right Path', 'Desert Left Alcove Path', 'Desert Right Alcove Path', 'Desert Compass NE', 'Desert West Lobby NW', 'Desert Back Lobby NW', 'Desert Four Statues NW', 'Desert Four Statues ES', 'Desert Beamos Hall WS', 'Desert Beamos Hall NE', 'Desert Wall Slide NW', 'Hera Lobby to Front Barrier - Blue', 'Hera Front to Lobby Barrier - Blue', 'Hera Front to Down Stairs Barrier - Blue', From 22dfeeeccad75b9a4a7d4f6f795413d90e69ad02 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 12 Jan 2023 15:32:49 -0700 Subject: [PATCH 7/7] Starting inventory updates Logic fix for skull woods star tile logic Standard logic improvement --- BaseClasses.py | 3 + DoorShuffle.py | 2 +- ItemList.py | 113 ++++++++++++++++-- Main.py | 2 +- RELEASENOTES.md | 6 +- Rom.py | 16 ++- Rules.py | 22 +++- .../async_doors_league/S3_BombBag.yaml | 39 +++--- docs/presets/async_doors_league/S3_Main.yaml | 37 +++--- .../async_doors_league/S3_PotteryLottery.yaml | 37 +++--- source/dungeon/DungeonStitcher.py | 2 + test/customizer/test_stuff.yaml | 28 +++++ 12 files changed, 225 insertions(+), 82 deletions(-) create mode 100644 test/customizer/test_stuff.yaml diff --git a/BaseClasses.py b/BaseClasses.py index f7fd2cc9..91404ebb 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2287,6 +2287,9 @@ class Item(object): def __unicode__(self): return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})' + def __eq__(self, other): + return self.name == other.name and self.player == other.player + # have 6 address that need to be filled class Crystal(Item): diff --git a/DoorShuffle.py b/DoorShuffle.py index 95bfd4c7..d4d94905 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -2107,7 +2107,7 @@ def find_trappable_candidates(builder, world, player): if ext.door and ext.door.type in [DoorType.Interior, DoorType.Normal]] for d in filtered_doors: # I only support the first 3 due to the trapFlag right now - if 0 <= d.doorListPos < 3 and not d.entranceFlag: + if 0 <= d.doorListPos < 3 and not d.entranceFlag and d.name != 'Skull Small Hall WS': room = world.get_room(d.roomIndex, player) kind = room.kind(d) if d.type == DoorType.Interior: diff --git a/ItemList.py b/ItemList.py index 7080b330..4bbd0f32 100644 --- a/ItemList.py +++ b/ItemList.py @@ -286,6 +286,7 @@ def generate_itempool(world, player): for _ in range(0, amt): pool.append('Rupees (20)') + start_inventory = list(world.precollected_items) for item in precollected_items: world.push_precollected(ItemFactory(item, player)) @@ -435,6 +436,22 @@ def generate_itempool(world, player): if world.pottery[player] not in ['none', 'keys'] and not skip_pool_adjustments: add_pot_contents(world, player) + # modfiy based on start inventory, if any + modify_pool_for_start_inventory(start_inventory, world, player) + + # increase pool if not enough items + ttl_locations = sum(1 for x in world.get_unfilled_locations(player) if '- Prize' not in x.name) + pool_size = count_player_dungeon_item_pool(world, player) + pool_size += sum(1 for x in world.itempool if x.player == player) + + if pool_size < ttl_locations: + retro_bow = world.bow_mode[player].startswith('retro') + amount_to_add = ttl_locations - pool_size + filler_additions = random.choices(list(filler_items.keys()), filler_items.values(), k=amount_to_add) + for item in filler_additions: + item_name = 'Rupees (5)' if retro_bow and item == 'Arrows (10)' else item + world.itempool.append(ItemFactory(item_name, player)) + take_any_locations = [ 'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut', @@ -962,14 +979,60 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt pool.extend(['Small Key (Universal)']) else: pool.extend(['Small Key (Universal)']) - modify_pool_for_start_inventory(pool, world, player) return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) -def modify_pool_for_start_inventory(pool, world, player): - for item in world.precollected_items: +item_alternates = { + # Bows + 'Progressive Bow (Alt)': ('Progressive Bow', 1), + 'Bow': ('Progressive Bow', 1), + 'Silver Arrows': ('Progressive Bow', 2), + # Gloves + 'Power Glove': ('Progressive Glove', 1), + 'Titans Mitts': ('Progressive Glove', 2), + # Swords + 'Sword and Shield': ('Progressive Sword', 1), # could find a way to also remove a shield, but mostly not impactful + 'Fighter Sword': ('Progressive Sword', 1), + 'Master Sword': ('Progressive Sword', 2), + 'Tempered Sword': ('Progressive Sword', 3), + 'Golden Sword': ('Progressive Sword', 4), + # Shields + 'Blue Shield': ('Progressive Shield', 1), + 'Red Shield': ('Progressive Shield', 2), + 'Mirror Shield': ('Progressive Shield', 3), + # Armors + 'Blue Mail': ('Progressive Armor', 1), + 'Red Mail': ('Progressive Armor', 2), + + 'Magic Upgrade (1/4)': ('Magic Upgrade (1/2)', 2), + 'Ocarina': ('Ocarina (Activated)', 1), + 'Ocarina (Activated)': ('Ocarina', 1), + 'Boss Heart Container': ('Sanctuary Heart Container', 1), + 'Sanctuary Heart Container': ('Boss Heart Container', 1), + 'Power Star': ('Triforce Piece', 1) +} + + +def modify_pool_for_start_inventory(start_inventory, world, player): + # skips custom item pools - these shouldn't be adjusted + if (world.customizer and world.customizer.get_item_pool()) or world.custom: + return + for item in start_inventory: if item.player == player: - pool.remove(item.name) + if item in world.itempool: + world.itempool.remove(item) + elif item.name in item_alternates: + alt = item_alternates[item.name] + i = alt[1] + while i > 0: + alt_item = ItemFactory([alt[0]], player)[0] + if alt_item in world.itempool: + world.itempool.remove(alt_item) + i = i-1 + elif 'Bottle' in item.name: + bottle_item = next((x for x in world.itempool if 'Bottle' in item.name and x.player == player), None) + if bottle_item is not None: + world.itempool.remove(bottle_item) if item.dungeon: d = world.get_dungeon(item.dungeon, item.player) match = next((i for i in d.all_items if i.name == item.name), None) @@ -1085,6 +1148,22 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer # print("Placing " + str(nothings) + " Nothings") pool.extend(['Nothing'] * nothings) + start_inventory = [x for x in world.precollected_items if x.player == player] + if not start_inventory: + if world.logic[player] in ['owglitches', 'nologic']: + precollected_items.append('Pegasus Boots') + if 'Pegasus Boots' in pool: + pool.remove('Pegasus Boots') + pool.append('Rupees (20)') + if world.swords[player] == 'assured': + precollected_items.append('Progressive Sword') + if 'Progressive Sword' in pool: + pool.remove('Progressive Sword') + pool.append('Rupees (50)') + elif 'Fighter Sword' in pool: + pool.remove('Fighter Sword') + pool.append('Rupees (50)') + return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) @@ -1206,12 +1285,20 @@ def make_customizer_pool(world, player): if pieces < t: pool.extend(['Triforce Piece'] * (t - pieces)) - ttl_locations = sum(1 for x in world.get_unfilled_locations(player) if '- Prize' not in x.name) - pool_size = len(get_player_dungeon_item_pool(world, player)) + len(pool) - - if pool_size < ttl_locations: - amount_to_add = ttl_locations - pool_size - pool.extend(random.choices(list(filler_items.keys()), filler_items.values(), k=amount_to_add)) + if not world.customizer.get_start_inventory(): + if world.logic[player] in ['owglitches', 'nologic']: + precollected_items.append('Pegasus Boots') + if 'Pegasus Boots' in pool: + pool.remove('Pegasus Boots') + pool.append('Rupees (20)') + if world.swords[player] == 'assured': + precollected_items.append('Progressive Sword') + if 'Progressive Sword' in pool: + pool.remove('Progressive Sword') + pool.append('Rupees (50)') + elif 'Fighter Sword' in pool: + pool.remove('Fighter Sword') + pool.append('Rupees (50)') return pool, placed_items, precollected_items, clock_mode, 1 @@ -1227,9 +1314,9 @@ filler_items = { } -def get_player_dungeon_item_pool(world, player): - return [item for dungeon in world.dungeons for item in dungeon.all_items - if dungeon.player == player and item.location is None] +def count_player_dungeon_item_pool(world, player): + return sum(1 for dungeon in world.dungeons for item in dungeon.all_items + if dungeon.player == player and item.location is None and is_dungeon_item(item.name, world, player)) # location pool doesn't support larger values at this time diff --git a/Main.py b/Main.py index 820cf079..1dc01f14 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -__version__ = '1.2.0.3-u' +__version__ = '1.2.0.4-u' from source.classes.BabelFish import BabelFish diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 21f4a884..54b2edb0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -108,7 +108,11 @@ These are now independent of retro mode and have three options: None, Random, an * Bonk Fairy (Dark) # Bug Fixes and Notes - +* 1.2.0.4-u + * Starting inventory fixes if item not present in the item pool. + * Support for Assured sword setting and OWG Boots when using a custom item pool. (Customizer or GUI) + * Logic fix for the skull woods star tile that lets you into the X pot room. Now accounts for small key or big key door there blocking the way from the star tile. A trap door is not allowed there. + * Standard logic improvement that requires a path from Zelda to the start so that you cannot get softlocked by rescuing Zelda. Standard mirror scroll change may need to be reverted if impossible seed are still generated. * 1.2.0.3-u * Starting inventory taken into account with default item pool. (Custom pools must do this themselves) * Fast ROM update diff --git a/Rom.py b/Rom.py index d210ae01..b0121eea 100644 --- a/Rom.py +++ b/Rom.py @@ -2221,7 +2221,7 @@ def write_strings(rom, world, player, team): hint_candidates = [] for name, district in world.districts[player].items(): hint_type = 'foolish' - choice_set = set() + choices = [] item_count, item_type = 0, 'useful' for loc_name in district.locations: location_item = world.get_location(loc_name, player).item @@ -2231,34 +2231,32 @@ def write_strings(rom, world, player, team): itm_type = 'useful' if useful_item_for_hint(location_item, world) else 'vital' hint_type = 'path' if item_type == itm_type: - choice_set.add(location_item) + choices.append(location_item) item_count += 1 elif itm_type == 'vital': item_type = 'vital' item_count = 1 - choice_set.clear() - choice_set.add(location_item) + choices.clear() + choices.append(location_item) if hint_type == 'foolish': if district.dungeons and world.shuffle[player] != 'vanilla': - choice_set.update(district.dungeons) + choices.extend(district.dungeons) hint_type = 'dungeon_path' elif district.access_points and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: - choice_set.update([x.hint_text for x in district.access_points]) + choices.extend([x.hint_text for x in district.access_points]) hint_type = 'connector' if hint_type == 'foolish': hint_candidates.append((hint_type, f'{name} is a foolish choice')) elif hint_type == 'dungeon_path': - choices = sorted(list(choice_set)) dungeon_choice = random.choice(choices) # prefer required dungeons... hint_candidates.append((hint_type, f'{name} is on the path to {dungeon_choice}')) elif hint_type == 'connector': - choices = sorted(list(choice_set)) access_point = random.choice(choices) # prefer required access... hint_candidates.append((hint_type, f'{name} can reach {access_point}')) elif hint_type == 'path': if item_count == 1: - the_item = text_for_item(next(iter(choice_set)), world, player, team) + the_item = text_for_item(next(iter(choices)), world, player, team) hint_candidates.append((hint_type, f'{name} conceals only {the_item}')) else: hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items')) diff --git a/Rules.py b/Rules.py index 38ff5666..4bf9cd83 100644 --- a/Rules.py +++ b/Rules.py @@ -124,6 +124,10 @@ def or_rule(rule1, rule2): return lambda state: rule1(state) or rule2(state) +def and_rule(rule1, rule2): + return lambda state: rule1(state) and rule2(state) + + def add_lamp_requirement(spot, player): add_rule(spot, lambda state: state.has('Lamp', player, state.world.lamps_needed_for_dark_rooms)) @@ -277,8 +281,22 @@ def global_rules(world, player): set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player)) - set_rule(world.get_entrance('Skull 2 West Lobby Pits', player), lambda state: state.has_Boots(player) or state.has('Hidden Pits', player)) - set_rule(world.get_entrance('Skull 2 West Lobby Ledge Pits', player), lambda state: state.has('Hidden Pits', player)) + + hidden_pits_door = world.get_door('Skull Small Hall WS', player) + + def hidden_pits_rule(state): + return state.has('Hidden Pits', player) + + if hidden_pits_door.bigKey: + key_logic = world.key_logic[player][hidden_pits_door.entrance.parent_region.dungeon.name] + hidden_pits_rule = and_rule(hidden_pits_rule, create_rule(key_logic.bk_name, player)) + elif hidden_pits_door.smallKey: + d_name = hidden_pits_door.entrance.parent_region.dungeon.name + hidden_pits_rule = and_rule(hidden_pits_rule, eval_small_key_door('Skull Small Hall WS', d_name, player)) + + set_rule(world.get_entrance('Skull 2 West Lobby Pits', player), lambda state: state.has_Boots(player) + or hidden_pits_rule(state)) + set_rule(world.get_entrance('Skull 2 West Lobby Ledge Pits', player), hidden_pits_rule) set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player)) diff --git a/docs/presets/async_doors_league/S3_BombBag.yaml b/docs/presets/async_doors_league/S3_BombBag.yaml index e1f831f6..9edc1803 100644 --- a/docs/presets/async_doors_league/S3_BombBag.yaml +++ b/docs/presets/async_doors_league/S3_BombBag.yaml @@ -2,22 +2,23 @@ meta: players: 1 race: true settings: - shopsanity: true - pseudoboots: true - goal: crystals - crystals_gt: random - bombbag: true - shuffle: crossed - shufflelinks: true - keysanity: true - door_shuffle: crossed - intensity: 3 - door_type_mode: big - pottery: keys - dropshuffle: true - experimental: true - dungeon_counters: 'on' - hints: true - msu_resume: true - collection_rate: true - quickswap: true + 1: + shopsanity: true + pseudoboots: true + goal: crystals + crystals_gt: random + bombbag: true + shuffle: crossed + shufflelinks: true + keysanity: true + door_shuffle: crossed + intensity: 3 + door_type_mode: big + pottery: keys + dropshuffle: true + experimental: true + dungeon_counters: 'on' + hints: true + msu_resume: true + collection_rate: true + quickswap: true diff --git a/docs/presets/async_doors_league/S3_Main.yaml b/docs/presets/async_doors_league/S3_Main.yaml index e8d8f282..564d19d4 100644 --- a/docs/presets/async_doors_league/S3_Main.yaml +++ b/docs/presets/async_doors_league/S3_Main.yaml @@ -2,21 +2,22 @@ meta: players: 1 race: true settings: - shopsanity: true - pseudoboots: true - goal: crystals - crystals_gt: random - shuffle: crossed - shufflelinks: true - keysanity: true - door_shuffle: crossed - intensity: 3 - door_type_mode: big - pottery: keys - dropshuffle: true - experimental: true - dungeon_counters: 'on' - hints: true - msu_resume: true - collection_rate: true - quickswap: true + 1: + shopsanity: true + pseudoboots: true + goal: crystals + crystals_gt: random + shuffle: crossed + shufflelinks: true + keysanity: true + door_shuffle: crossed + intensity: 3 + door_type_mode: big + pottery: keys + dropshuffle: true + experimental: true + dungeon_counters: 'on' + hints: true + msu_resume: true + collection_rate: true + quickswap: true diff --git a/docs/presets/async_doors_league/S3_PotteryLottery.yaml b/docs/presets/async_doors_league/S3_PotteryLottery.yaml index d65e2387..baae256d 100644 --- a/docs/presets/async_doors_league/S3_PotteryLottery.yaml +++ b/docs/presets/async_doors_league/S3_PotteryLottery.yaml @@ -2,21 +2,22 @@ meta: players: 1 race: true settings: - shopsanity: true - pseudoboots: true - goal: crystals - crystals_gt: random - shuffle: crossed - shufflelinks: true - keysanity: true - door_shuffle: crossed - intensity: 3 - door_type_mode: big - pottery: lottery - dropshuffle: true - experimental: true - dungeon_counters: 'on' - hints: true - msu_resume: true - collection_rate: true - quickswap: true + 1: + shopsanity: true + pseudoboots: true + goal: crystals + crystals_gt: random + shuffle: crossed + shufflelinks: true + keysanity: true + door_shuffle: crossed + intensity: 3 + door_type_mode: big + pottery: lottery + dropshuffle: true + experimental: true + dungeon_counters: 'on' + hints: true + msu_resume: true + collection_rate: true + quickswap: true diff --git a/source/dungeon/DungeonStitcher.py b/source/dungeon/DungeonStitcher.py index 205941b5..d0b21d89 100644 --- a/source/dungeon/DungeonStitcher.py +++ b/source/dungeon/DungeonStitcher.py @@ -319,6 +319,8 @@ def determine_paths_for_dungeon(world, player, all_regions, name): if world.mode[player] == 'standard' and name == 'Hyrule Castle Dungeon': paths.append('Hyrule Dungeon Cellblock') paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room')) + entrance = next(x for x in world.dungeon_portals[player] if x.name == 'Hyrule Castle South') + paths.append(('Hyrule Dungeon Cellblock', entrance.door.entrance.parent_region.name)) if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town': paths.append('Thieves Attic Window') elif 'Thieves Attic Window' in all_r_names: diff --git a/test/customizer/test_stuff.yaml b/test/customizer/test_stuff.yaml new file mode 100644 index 00000000..552d5490 --- /dev/null +++ b/test/customizer/test_stuff.yaml @@ -0,0 +1,28 @@ +meta: + players: 1 + race: true +settings: + 1: + shopsanity: true + pseudoboots: true + goal: crystals + crystals_gt: random + keysanity: true + door_shuffle: crossed + intensity: 3 + door_type_mode: big + pottery: keys + dropshuffle: true + experimental: true + dungeon_counters: 'on' + hints: true + msu_resume: true + collection_rate: true + quickswap: true +start_inventory: + 1: + - Pegasus Boots + - Ocarina (Activated) + - Magic Mirror + - Boss Heart Container + - Blue Mail \ No newline at end of file