From e67ff4d5dca12ba7d6f1ad960d5da719c8bcea27 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 7 Dec 2022 15:57:51 -0700 Subject: [PATCH] Completionist fixes --- BaseClasses.py | 9 +++++++-- DoorShuffle.py | 6 +++--- DungeonGenerator.py | 2 +- Main.py | 5 ++++- Rom.py | 6 +++--- Rules.py | 3 ++- data/base2current.bps | Bin 93489 -> 93478 bytes test/stats/EntranceShuffleStats.py | 6 ++++-- 8 files changed, 24 insertions(+), 13 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 6cbc146e..358a40e0 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -470,7 +470,10 @@ class World(object): if self.has_beaten_game(state): return True - prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event) and location not in state.locations_checked] + prog_locations = [location for location in self.get_locations() if location.item is not None + and (location.item.advancement or location.event + or self.goal[location.player] == 'completionist') + and location not in state.locations_checked] while prog_locations: sphere = [] @@ -1038,8 +1041,10 @@ class CollectionState(object): return self.prog_items[item, player] def everything(self, player): + all_locations = self.world.get_filled_locations(player) + all_locations.remove(self.world.get_location('Ganon', player)) return (len([x for x in self.locations_checked if x.player == player]) - >= len(self.world.get_filled_locations(player))) + >= len(all_locations)) def has_crystals(self, count, player): crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'] diff --git a/DoorShuffle.py b/DoorShuffle.py index 44685e5d..31e5d13b 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -6,7 +6,7 @@ from enum import unique, Flag from typing import DefaultDict, Dict, List from itertools import chain -from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys +from BaseClasses import RegionType, Region, Door, DoorType, Sector, CrystalBarrier, DungeonInfo, dungeon_keys from BaseClasses import PotFlags, LocationType, Direction from Doors import reset_portals from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts @@ -15,12 +15,12 @@ from Items import ItemFactory from RoomData import DoorKind, PairedDoor, reset_rooms from source.dungeon.DungeonStitcher import GenerationException, generate_dungeon from source.dungeon.DungeonStitcher import ExplorationState as ExplorationState2 -from DungeonGenerator import ExplorationState, convert_regions, pre_validate, determine_required_paths, drop_entrances +from DungeonGenerator import ExplorationState, convert_regions, determine_required_paths, drop_entrances from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances from DungeonGenerator import dungeon_portals, dungeon_drops, connect_doors, count_reserved_locations from DungeonGenerator import valid_region_to_explore from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock -from KeyDoorShuffle import validate_bk_layout, check_bk_special +from KeyDoorShuffle import validate_bk_layout from Utils import ncr, kth_combination diff --git a/DungeonGenerator.py b/DungeonGenerator.py index 6a21817b..af0ef5c0 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -12,7 +12,7 @@ from typing import List from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys, Sector from BaseClasses import Hook, hook_from_door, Door from Regions import dungeon_events, flooded_keys_reverse -from Dungeons import dungeon_regions, split_region_starts +from Dungeons import split_region_starts from RoomData import DoorKind from source.dungeon.DungeonStitcher import generate_dungeon_find_proposal diff --git a/Main.py b/Main.py index 48ba4c81..2897f839 100644 --- a/Main.py +++ b/Main.py @@ -617,7 +617,8 @@ def create_playthrough(world): world = copy_world(world) # get locations containing progress items - prog_locations = [location for location in world.get_filled_locations() if location.item.advancement] + prog_locations = [location for location in world.get_filled_locations() if location.item.advancement + or world.goal[location.player] == 'completionist'] optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile'] state_cache = [None] collection_spheres = [] @@ -654,6 +655,8 @@ def create_playthrough(world): for num, sphere in reversed(list(enumerate(collection_spheres))): to_delete = set() for location in sphere: + if world.goal[location.player] == 'completionist': + continue # every location for that player is required # we remove the item at location and check if game is still beatable logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player) old_item = location.item diff --git a/Rom.py b/Rom.py index 1f1dfed8..f5a529fd 100644 --- a/Rom.py +++ b/Rom.py @@ -15,7 +15,7 @@ try: except ImportError: raise Exception('Could not load BPS module') -from BaseClasses import ShopType, Region, Location, Door, DoorType, RegionType, LocationType, Item +from BaseClasses import ShopType, Region, Location, Door, DoorType, RegionType, LocationType from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals from Dungeons import dungeon_music_addresses, dungeon_table from Regions import location_table, shop_to_location_table, retro_shops @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'fb7f9a0d501ba9ecd0a31066f9a0a973' +RANDOMIZERBASEHASH = '54cfc4c5e85c80fb2958cb458d36ad14' class JsonRom(object): @@ -1344,7 +1344,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): elif world.goal[player] in ['ganonhunt']: rom.write_byte(0x18003E, 0x05) # make ganon invincible until all triforce pieces collected elif world.goal[player] in ['completionist']: - rom.write_byte(0x18003E, 0x09) # make ganon invincible until everything is collected + rom.write_byte(0x18003E, 0x0a) # make ganon invincible until everything is collected else: rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected diff --git a/Rules.py b/Rules.py index 3f4e6b4d..0190aaa4 100644 --- a/Rules.py +++ b/Rules.py @@ -2075,7 +2075,8 @@ def add_key_logic_rules(world, player): for chest in d_logic.bk_chests: big_chest = world.get_location(chest.name, player) add_rule(big_chest, create_rule(d_logic.bk_name, player)) - if len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1: + if (len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1 + and world.accessibility[player] != 'locations'): set_always_allow(big_chest, allow_big_key_in_big_chest(d_logic.bk_name, player)) if world.keyshuffle[player] == 'universal': for d_name, layout in world.key_layout[player].items(): diff --git a/data/base2current.bps b/data/base2current.bps index a3983a652d56acb5442bb49a4732d16ddd2c9109..e9709333a2475617d136d5e83f868116597e4c94 100644 GIT binary patch delta 889 zcmW-be`phT7{>GFM-$TEB{50U&Zgg9Y?E}OG_%^+s_V22mqp7qu;>^Zve~f&h55%u znfkrV>zZmwF4y!;dx&Z64O5Y#_ea*v+N!Hj2GVV~xx#RBj5!#RKPqAox?_Jm&-3Bs zd7mpQrmt2^nPF%V&-`R8ha>mY!%z?U&BN-K`mi3t>YVXM1Kd#0TCP{?*K=yj_1zob zQyuTA320WA%I#gSh--QXSgwiGqyTw_Ap`i}U)*beMpF+ad0IMZfSB3VBMNMBl~!v< zv~=4FtwyNDryF2f@8WvJOrB&5dTJn@(%toncj9d(`&`2&k`^`iy+}Nn2nyyq3$5I^q1@tLo0VbH)%|_9{j}vP}vV z>-nvP9QBeNNlyCbQgrVAbVf0dBz%8iC3?8l`i3G9w8(#fA;|vK^W>9r~8Hq?4CTijnk)kLSmi zglcJO#KwgfxeF6^@K-tgHB_LOvjYp)@M}9X!?V&uJ1`J_AW_$%Dcwv{4<(wl{Bb#| zy{5l$i#lB;m`2`@$&nWlt;L~n$0%jU-`L=Ux9qK>@ke&4cT8e-xoMbgzyT-p1zC}A z$4?U}K7&Vi{5-`+E+i*A%O`0ETRSu*2f37dES{1N;d7_9B#fIjLcas^=_!mIr8lO_ zC+FKL1aWjDWSzYz*^`P{w`_>@V$l(tO<=?Ygp_ju@2Nj?fr|@VB{1IOxpSxZbhD-x z4n``I7V-&x=7J`-CFG*=zAt!2-pWjfl`2xqUy9*X7j#=k#^mj^sMXft3#|}BSnq~9 zaAU*`zS=Vw99WC5EWDZz%#g!*%??MUSCUc)kGUZPF&uZpF2{lTiIgYErSFPt{`T|= zzJ!n6(9O0eDa-6?da$_cc+^&`5#E3DyB-~8S-9D@M{56=$No~(12!wHVjqDPIDtt5 z`{5n@fxvF&#Hk_iqt1AtFuQY0Mm8eb2tONtjen59wMN(i{n*NCJCEa=EbOihs?@2T jzH*ZkAK(}Z9ngyRSm=U=lGP6lQOo^~*SWLX*WCXB6RcMp delta 957 zcmW-dZA@Er7{&Ya-L|YRw5&|%{&a!1n`jAd9dp2DLKK*BnPGEgv00|8mKljI>YVI< z%P!DO(%xR+Dco%owrecSWOzR`!ypJu#1DpHTnzi5AcJV!MmkVhPOyo;(xZI=kX#%IYkHk8w(w4}W!bJs;j z|A>Wd9AQ)R#aKdUc+X1t&c-C)RP)zBQ3}XE%+(0ff)S=C8&W~5&eX!O?(_yV%Dz3> zSpz(&hH6~j0yXDOw5KENPpS z|3`TV88L~OcU%ROam6BiM>%qc?;Q|TZ0fvrzH(|>?W4<&iE0+)0yIxlkDTouAeZ8D zjghv`q?VhP|9fzX#iF)x^)^)45x|O~ogog7opMQq;+Ztj0x6LJd@CF5xdL$t2GzL=%>y z%L1iEq0bq8EOK2Vs!0p?>-5kq*2s_bnSwsE@EpEkfgM`1V=HBQu-5`^n8%C-%HUCU z#R6t9{>W4NtR_}Qqrc!iD-`Cb`e`Bd4mta&ncPR44O%SK{oyqWe{7I9*E?thb>Jx* z9QOJ-rV2HuqD&lLWAJR030#eg)-86@T6baZm{1dp3Ll1}LJ05KK#UR&&(_UFhI?8%3OtqplgMARvkRjqB9wt6Sd;4V85?Y9FS=eghUpdIpcCj+@r zJ$WC)b|`fe`0P|T(8ZXAXUxM~PD^eiGe=RhL!+U8P}oh=vQr7J%O2oIivzYn5$bc1x?y{>KKjDa?POiN=3h`Obp`+c diff --git a/test/stats/EntranceShuffleStats.py b/test/stats/EntranceShuffleStats.py index 2be02071..25c0cbfd 100644 --- a/test/stats/EntranceShuffleStats.py +++ b/test/stats/EntranceShuffleStats.py @@ -1,5 +1,7 @@ +import os +import sys +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) import RaceRandom as random -import logging import time from collections import Counter, defaultdict @@ -106,7 +108,7 @@ def test_loop(tests, entrance_set, exit_set, ctr, shuffle_mode, main_mode, links # seed = 635441530 random.seed(seed) world = World(1, {1: shuffle_mode}, {1: 'vanilla'}, {1: 'noglitches'}, {1: main_mode}, {}, {}, {}, - {}, {}, {}, {}, {}, True, {}, {}, [], {}) + {}, {}, {}, {}, {}, True, {}, [], {}) world.customizer = False world.shufflelinks = {1: links} if world.mode[1] != 'inverted':