Crossed Dungeon generation work
--Introduced reachable_switches --Calculate total completion in equation resolution --Prioritized one-way connections over neutral/neutral_profitable sectored but not over dead-ends --Simplified finding good complex branching candidates - just re-used equations routine --Valid multi choice for global polarity when fixing parity --Added total dungeon charge as critieria when fixing parity --Pinball used for navigation in skull 2, marked appropriately (particularly with Ice Cross in Skull 2) --Equation resolution detects used benefits with unreached_doors benefits --Greedy equation finder not longer used destination/entrance sectors of the wrong split dungeon --Required connections don't overestimate benefits --Introduced the concept of crystal switches and doors blocked by blue barriers to equations
This commit is contained in:
@@ -1076,6 +1076,12 @@ class Polarity:
|
||||
result += abs(self.vector[i])
|
||||
return result
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
|
||||
def __unicode__(self):
|
||||
return f'{self.vector}'
|
||||
|
||||
|
||||
pol_idx = {
|
||||
Direction.North: (0, 'Pos'),
|
||||
@@ -1383,11 +1389,23 @@ class Sector(object):
|
||||
self.entrance_sector = True
|
||||
return self.entrance_sector
|
||||
|
||||
def get_start_regions(self):
|
||||
if self.is_entrance_sector():
|
||||
starts = []
|
||||
for region in self.regions:
|
||||
for ent in region.entrances:
|
||||
if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] or ent.parent_region.name == 'Sewer Drop':
|
||||
starts.append(region)
|
||||
return starts
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % next(iter(self.region_set()))
|
||||
if len(self.regions) > 0:
|
||||
return f'{self.regions[0].name}'
|
||||
return f'{next(iter(self.region_set()))}'
|
||||
|
||||
|
||||
class Boss(object):
|
||||
|
||||
@@ -12,6 +12,7 @@ from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarit
|
||||
from BaseClasses import Hook, hook_from_door
|
||||
from Regions import key_only_locations, dungeon_events, flooded_keys_reverse
|
||||
from Dungeons import dungeon_regions, split_region_starts
|
||||
from RoomData import DoorKind
|
||||
|
||||
|
||||
class GraphPiece:
|
||||
@@ -22,6 +23,7 @@ class GraphPiece:
|
||||
self.hooks = {}
|
||||
self.visited_regions = set()
|
||||
self.possible_bk_locations = set()
|
||||
self.pinball_used = False
|
||||
|
||||
|
||||
# Dungeons shouldn't be generated until all entrances are appropriately accessible
|
||||
@@ -107,6 +109,13 @@ def generate_dungeon_main(builder, entrance_region_names, split_dungeon, world,
|
||||
valid = True
|
||||
if valid:
|
||||
if len(proposed_map) == len(doors_to_connect):
|
||||
if dungeon['Origin'].pinball_used:
|
||||
door = world.get_door('Skull Pinball WS', player)
|
||||
room = world.get_room(door.roomIndex, player)
|
||||
if room.doorList[door.doorListPos][1] == DoorKind.Trap:
|
||||
room.change(door.doorListPos, DoorKind.Normal)
|
||||
door.trapFlag = 0x0
|
||||
door.blocked = False
|
||||
finished = True
|
||||
continue
|
||||
prev_choices = choices_master[depth]
|
||||
@@ -554,6 +563,7 @@ def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exceptio
|
||||
graph_piece.visited_regions.update(b_state.visited_orange)
|
||||
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(o_state.bk_found))
|
||||
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(b_state.bk_found))
|
||||
graph_piece.pinball_used = o_state.pinball_used or b_state.pinball_used
|
||||
return graph_piece
|
||||
|
||||
|
||||
@@ -694,6 +704,7 @@ class ExplorationState(object):
|
||||
|
||||
self.non_door_entrances = []
|
||||
self.dungeon = dungeon
|
||||
self.pinball_used = False
|
||||
|
||||
def copy(self):
|
||||
ret = ExplorationState(dungeon=self.dungeon)
|
||||
@@ -719,6 +730,8 @@ class ExplorationState(object):
|
||||
ret.bk_found = set(self.bk_found)
|
||||
|
||||
ret.non_door_entrances = list(self.non_door_entrances)
|
||||
ret.dungeon = self.dungeon
|
||||
ret.pinball_used = self.pinball_used
|
||||
return ret
|
||||
|
||||
def next_avail_door(self):
|
||||
@@ -813,6 +826,8 @@ class ExplorationState(object):
|
||||
|
||||
def add_all_doors_check_proposed(self, region, proposed_map, valid_doors, flag, world, player, exception):
|
||||
for door in get_doors(world, region, player):
|
||||
if door.blocked and exception(door):
|
||||
self.pinball_used = True
|
||||
if self.can_traverse(door, exception):
|
||||
if door.controller is not None:
|
||||
door = door.controller
|
||||
@@ -1176,6 +1191,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge
|
||||
assign_location_sectors(dungeon_map, free_location_sectors, global_pole)
|
||||
logger.info('-Assigning Crystal Switches and Barriers')
|
||||
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
|
||||
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
|
||||
for sector in leftover:
|
||||
if sector.polarity().is_neutral():
|
||||
neutral_sectors[sector] = None
|
||||
@@ -1428,6 +1444,138 @@ def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barrier
|
||||
return crystal_switches
|
||||
|
||||
|
||||
def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_sectors, crystal_barriers, global_pole):
|
||||
invalid_builders = []
|
||||
for name, builder in dungeon_map.items():
|
||||
if builder.c_switch_present and not builder.c_locked:
|
||||
invalid_builders.append(builder)
|
||||
while len(invalid_builders) > 0:
|
||||
valid_builders = []
|
||||
for builder in invalid_builders:
|
||||
entrance_sectors = []
|
||||
reachable_crystals = defaultdict()
|
||||
for sector in builder.sectors:
|
||||
if sector.equations is None:
|
||||
sector.equations = calc_sector_equations(sector, builder)
|
||||
if sector.is_entrance_sector() and not sector.destination_entrance:
|
||||
need_switch = True
|
||||
for region in sector.get_start_regions():
|
||||
if region.crystal_switch:
|
||||
need_switch = False
|
||||
break
|
||||
any_benefit = False
|
||||
for eq in sector.equations:
|
||||
if len(eq.benefit) > 0:
|
||||
any_benefit = True
|
||||
break
|
||||
if need_switch and any_benefit:
|
||||
entrance_sectors.append(sector)
|
||||
for eq in sector.equations:
|
||||
if eq.c_switch:
|
||||
reachable_crystals[hook_from_door(eq.door)] = True
|
||||
valid_ent_sectors = []
|
||||
for entrance_sector in entrance_sectors:
|
||||
other_sectors = [x for x in builder.sectors if x != entrance_sector]
|
||||
reachable, access = is_c_switch_reachable(entrance_sector, reachable_crystals, other_sectors)
|
||||
if reachable:
|
||||
valid_ent_sectors.append(entrance_sector)
|
||||
else:
|
||||
candidates = {}
|
||||
for c in find_pol_cand_for_c_switch(access, reachable_crystals, polarized_sectors):
|
||||
candidates[c] = 'Polarized'
|
||||
for c in find_crystal_cand(access, crystal_switches):
|
||||
candidates[c] = 'Crystals'
|
||||
for c in find_pol_cand_for_c_switch(access, reachable_crystals, crystal_barriers):
|
||||
candidates[c] = 'Barriers'
|
||||
valid, sector, which_list = False, None, None
|
||||
while not valid:
|
||||
if len(candidates) <= 0:
|
||||
raise Exception(f'need to provide more sophisticatedted crystal connection for {entrance_sector}')
|
||||
sector, which_list = random.choice(list(candidates.items()))
|
||||
del candidates[sector]
|
||||
valid = global_pole.is_valid_choice(dungeon_map, builder, [sector])
|
||||
if which_list == 'Polarized':
|
||||
assign_sector(sector, builder, polarized_sectors, global_pole)
|
||||
elif which_list == 'Crystals':
|
||||
assign_sector(sector, builder, crystal_switches, global_pole)
|
||||
elif which_list == 'Barriers':
|
||||
assign_sector(sector, builder, crystal_barriers, global_pole)
|
||||
entrance_sectors = [x for x in entrance_sectors if x not in valid_ent_sectors]
|
||||
if len(entrance_sectors) == 0:
|
||||
valid_builders.append(builder)
|
||||
invalid_builders = [x for x in invalid_builders if x not in valid_builders]
|
||||
|
||||
|
||||
def is_c_switch_reachable(entrance_sector, reachable_crystals, other_sectors):
|
||||
current_access = {}
|
||||
for eq in entrance_sector.equations:
|
||||
if eq.total_cost() <= 0:
|
||||
for key, door_list in eq.benefit.items():
|
||||
for door in door_list:
|
||||
if door not in eq.blue_blocked:
|
||||
current_access[key] = True
|
||||
break
|
||||
for key, flag in current_access.items():
|
||||
if key in reachable_crystals.keys():
|
||||
return True, {}
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for sector in other_sectors:
|
||||
for eq in sector.equations:
|
||||
for key, door_list in eq.cost.items():
|
||||
if key in current_access.keys() and current_access[key]:
|
||||
for bene_key, door_list in eq.benefit.items():
|
||||
for door in door_list:
|
||||
if door not in eq.blue_blocked and bene_key not in current_access.keys():
|
||||
current_access[bene_key] = True
|
||||
changed = True
|
||||
break
|
||||
for key, flag in current_access.items():
|
||||
if key in reachable_crystals.keys():
|
||||
return True, {}
|
||||
return False, current_access
|
||||
|
||||
|
||||
def find_pol_cand_for_c_switch(access, reachable_crystals, polarized_candidates):
|
||||
candidates = []
|
||||
for sector in polarized_candidates:
|
||||
if pol_cand_matches_access_reach(sector, access, reachable_crystals):
|
||||
candidates.append(sector)
|
||||
return candidates
|
||||
|
||||
|
||||
def pol_cand_matches_access_reach(sector, access, reachable_crystals):
|
||||
if sector.equations is None:
|
||||
sector.equations = calc_sector_equations(sector, None)
|
||||
for eq in sector.equations:
|
||||
for key, door_list in eq.cost.items():
|
||||
if key in access.keys() and access[key]:
|
||||
for bene_key, door_list in eq.benefit.items():
|
||||
for door in door_list:
|
||||
if door not in eq.blue_blocked and opposite_h_type(bene_key) in reachable_crystals.keys():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def find_crystal_cand(access, crystal_switches):
|
||||
candidates = []
|
||||
for sector in crystal_switches:
|
||||
if crystal_cand_matches_access(sector, access):
|
||||
candidates.append(sector)
|
||||
return candidates
|
||||
|
||||
|
||||
def crystal_cand_matches_access(sector, access):
|
||||
if sector.equations is None:
|
||||
sector.equations = calc_sector_equations(sector, None)
|
||||
for eq in sector.equations:
|
||||
for key, door_list in eq.cost.items():
|
||||
if key in access.keys() and access[key] and eq.c_switch:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def assign_crystal_barrier_sectors(dungeon_map, crystal_barriers, global_pole):
|
||||
population = []
|
||||
for name, builder in dungeon_map.items():
|
||||
@@ -1692,20 +1840,36 @@ def polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger, fish):
|
||||
while len(odd_builders) > 0:
|
||||
if tries > 1000:
|
||||
raise Exception('Unable to fix dungeon parity. Ref: %s' % next(iter(odd_builders)).name)
|
||||
choices = random.sample(odd_candidates, k=len(odd_builders))
|
||||
best_choices = None
|
||||
best_charge = sum([x.polarity().charge() for x in dungeon_map.values()])
|
||||
samples = 0
|
||||
combos = ncr(len(odd_candidates), len(odd_builders))
|
||||
sample_target = 100 if combos > 10 else combos * 2
|
||||
while best_choices is None or samples < sample_target:
|
||||
samples += 1
|
||||
choices = random.sample(odd_candidates, k=len(odd_builders))
|
||||
valid = global_pole.is_valid_multi_choice(dungeon_map, odd_builders, choices)
|
||||
charge = calc_total_charge(dungeon_map, odd_builders, choices)
|
||||
if valid and charge < best_charge:
|
||||
best_choices = choices
|
||||
best_charge = charge
|
||||
if samples > sample_target and best_choices is None:
|
||||
best_choices = choices
|
||||
best_charge = charge
|
||||
samples = 0
|
||||
all_valid = True
|
||||
for i, candidate_list in enumerate(choices):
|
||||
for i, candidate_list in enumerate(best_choices):
|
||||
test_set = find_forced_connections(dungeon_map, candidate_list, polarized_sectors)
|
||||
builder = odd_builders[i]
|
||||
if ensure_test_set_connectedness(test_set, builder, polarized_sectors, dungeon_map, global_pole):
|
||||
all_valid &= global_pole.is_valid_choice(dungeon_map, builder, test_set) and valid_branch_only(builder, candidate_list)
|
||||
all_valid &= valid_branch_only(builder, candidate_list)
|
||||
else:
|
||||
all_valid = False
|
||||
break
|
||||
if not all_valid:
|
||||
break
|
||||
if all_valid:
|
||||
for i, candidate_list in enumerate(choices):
|
||||
for i, candidate_list in enumerate(best_choices):
|
||||
builder = odd_builders[i]
|
||||
for sector in candidate_list:
|
||||
assign_sector(sector, builder, polarized_sectors, global_pole)
|
||||
@@ -1716,6 +1880,15 @@ def polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger, fish):
|
||||
# step 3b: neutralize all builders
|
||||
builder_order = list(dungeon_map.values())
|
||||
random.shuffle(builder_order)
|
||||
def max_deviation_polarity(bldr):
|
||||
max = 0
|
||||
for x in bldr.polarity().vector:
|
||||
if abs(x) > max:
|
||||
max = abs(x)
|
||||
return max
|
||||
builder_order.sort(key=max_deviation_polarity, reverse=True)
|
||||
|
||||
|
||||
for builder in builder_order:
|
||||
# global_pole.check_odd_polarities(polarized_sectors, dungeon_map)
|
||||
logger.info('%s %s', fish.translate("cli", "cli", "balancing"), builder.name)
|
||||
@@ -1794,6 +1967,15 @@ def ensure_test_set_connectedness(test_set, builder, polarized_sectors, dungeon_
|
||||
return True
|
||||
|
||||
|
||||
def calc_total_charge(dungeon_map, builders, sector_lists):
|
||||
polarity_list = [x.polarity() for x in dungeon_map.values() if x not in builders]
|
||||
for i, sectors in enumerate(sector_lists):
|
||||
builder = builders[i]
|
||||
polarity = builder.polarity() + sum_polarity(sectors)
|
||||
polarity_list.append(polarity)
|
||||
return sum([x.charge() for x in polarity_list])
|
||||
|
||||
|
||||
class GlobalPolarity:
|
||||
|
||||
def __init__(self, candidate_sectors):
|
||||
@@ -1891,6 +2073,18 @@ class GlobalPolarity:
|
||||
proposal.consume(sector)
|
||||
return proposal._check_parity(non_neutral_polarities) and proposal._is_valid_polarities(non_neutral_polarities)
|
||||
|
||||
def is_valid_multi_choice(self, dungeon_map, builders, sector_lists):
|
||||
proposal = self.copy()
|
||||
non_neutral_polarities = [x.polarity() for x in dungeon_map.values() if not x.polarity().is_neutral()
|
||||
and x not in builders]
|
||||
for i, sectors in enumerate(sector_lists):
|
||||
builder = builders[i]
|
||||
current_polarity = builder.polarity() + sum_polarity(sectors)
|
||||
non_neutral_polarities.append(current_polarity)
|
||||
for sector in sectors:
|
||||
proposal.consume(sector)
|
||||
return proposal._check_parity(non_neutral_polarities) and proposal._is_valid_polarities(non_neutral_polarities)
|
||||
|
||||
# def check_odd_polarities(self, candidate_sectors, dungeon_map):
|
||||
# odd_candidates = [x for x in candidate_sectors if x.polarity().charge() % 2 != 0]
|
||||
# odd_map = {n: x for (n, x) in dungeon_map.items() if sum_polarity(x.sectors).charge() % 2 != 0}
|
||||
@@ -2049,15 +2243,7 @@ def weed_candidates(builder, candidates, best_charge):
|
||||
def find_branching_candidates(builder, neutral_choices):
|
||||
candidates = []
|
||||
for choice in neutral_choices:
|
||||
door_match = False
|
||||
flow_match = False
|
||||
for sector in choice:
|
||||
if sector.adj_outflow() >= 2:
|
||||
flow_match = True
|
||||
for door in sector.outstanding_doors:
|
||||
if builder.unfulfilled[hanger_from_door(door)] > 0:
|
||||
door_match = True
|
||||
if (door_match and flow_match) or len(resolve_equations(builder, choice)) == 0:
|
||||
if len(resolve_equations(builder, choice)) == 0:
|
||||
candidates.append(choice)
|
||||
return candidates
|
||||
|
||||
@@ -2327,15 +2513,12 @@ def balance_split(candidate_sectors, dungeon_map, global_pole, fish):
|
||||
logger = logging.getLogger('')
|
||||
|
||||
comb_w_replace = len(dungeon_map) ** len(candidate_sectors)
|
||||
if comb_w_replace <= 5000:
|
||||
if comb_w_replace <= 10000:
|
||||
combinations = list(itertools.product(dungeon_map.keys(), repeat=len(candidate_sectors)))
|
||||
random.shuffle(combinations)
|
||||
tries = 0
|
||||
while tries < len(combinations):
|
||||
if combinations:
|
||||
choices = combinations[tries]
|
||||
else:
|
||||
choices = random.choices(list(dungeon_map.keys()), k=len(candidate_sectors))
|
||||
choices = combinations[tries]
|
||||
main_sector_list = list(candidate_sectors)
|
||||
chosen_sectors = defaultdict(list)
|
||||
for i, choice in enumerate(choices):
|
||||
@@ -2361,6 +2544,7 @@ def balance_split(candidate_sectors, dungeon_map, global_pole, fish):
|
||||
crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors)
|
||||
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers,
|
||||
global_pole, len(crystal_barriers) > 0)
|
||||
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
|
||||
for sector in leftover:
|
||||
if sector.polarity().is_neutral():
|
||||
neutral_sectors[sector] = None
|
||||
@@ -2503,6 +2687,8 @@ class DoorEquation:
|
||||
self.benefit = defaultdict(list)
|
||||
self.required = False
|
||||
self.access_id = None
|
||||
self.c_switch = False
|
||||
self.blue_blocked = []
|
||||
|
||||
def copy(self):
|
||||
eq = DoorEquation(self.door)
|
||||
@@ -2511,6 +2697,8 @@ class DoorEquation:
|
||||
for key, doors in self.benefit.items():
|
||||
eq.benefit[key] = doors.copy()
|
||||
eq.required = self.required
|
||||
eq.c_switch = self.c_switch
|
||||
eq.blue_blocked.extend(self.blue_blocked)
|
||||
return eq
|
||||
|
||||
def total_cost(self):
|
||||
@@ -2519,6 +2707,12 @@ class DoorEquation:
|
||||
ttl += len(door_list)
|
||||
return ttl
|
||||
|
||||
def gross(self):
|
||||
ttl = 0
|
||||
for key, door_list in self.benefit.items():
|
||||
ttl += len(door_list)
|
||||
return ttl
|
||||
|
||||
def profit(self):
|
||||
ttl = 0
|
||||
for key, door_list in self.benefit.items():
|
||||
@@ -2547,6 +2741,14 @@ class DoorEquation:
|
||||
return True
|
||||
|
||||
|
||||
class DungeonAccess:
|
||||
|
||||
def __init__(self):
|
||||
self.access = defaultdict(int)
|
||||
self.blue_possible = False
|
||||
self.child_sections = []
|
||||
|
||||
|
||||
def identify_branching_issues(dungeon_map):
|
||||
unconnected_builders = {}
|
||||
for name, builder in dungeon_map.items():
|
||||
@@ -2567,11 +2769,13 @@ def resolve_equations(builder, sector_list):
|
||||
if builder.name in split_region_starts.keys():
|
||||
for name, region_list in split_region_starts[builder.name].items():
|
||||
current_access[name] = defaultdict(int)
|
||||
# current_access[name] = DungeonAccess()
|
||||
for r_name in region_list:
|
||||
sector = find_sector(r_name, builder.sectors)
|
||||
sector_split[sector] = name
|
||||
else:
|
||||
current_access[builder.name] = defaultdict(int)
|
||||
# current_access[builder.name] = DungeonAccess()
|
||||
|
||||
# resolve all that provide more access
|
||||
free_sector, eq_list, free_eq = find_free_equation(equations)
|
||||
@@ -2592,7 +2796,7 @@ def resolve_equations(builder, sector_list):
|
||||
eq, eq_list, sector = find_priority_equation(equations, access_id, access)
|
||||
elif len(valid_access) > 1:
|
||||
access_id, access = valid_access[0]
|
||||
eq, eq_list, sector = find_greedy_equation(equations, access_id, access)
|
||||
eq, eq_list, sector = find_greedy_equation(equations, access_id, access, sector_split)
|
||||
if eq:
|
||||
resolve_equation(eq, eq_list, sector, access_id, access, reached_doors, equations)
|
||||
else:
|
||||
@@ -2600,6 +2804,19 @@ def resolve_equations(builder, sector_list):
|
||||
for eq in eq_list:
|
||||
unreached_doors[hook_from_door(eq.door)].append(eq.door)
|
||||
return unreached_doors
|
||||
valid_access = next_access(current_access)
|
||||
for access_id, access in valid_access:
|
||||
access[Hook.Stairs] = access[Hook.Stairs] % 2
|
||||
ns_leftover = min(access[Hook.North], access[Hook.South])
|
||||
access[Hook.North] -= ns_leftover
|
||||
access[Hook.South] -= ns_leftover
|
||||
ew_leftover = min(access[Hook.West], access[Hook.East])
|
||||
access[Hook.East] -= ew_leftover
|
||||
access[Hook.West] -= ew_leftover
|
||||
if sum(access.values()) > 0:
|
||||
for hook, num in access.items():
|
||||
for i in range(num):
|
||||
unreached_doors[hook].append('placeholder')
|
||||
return unreached_doors
|
||||
|
||||
|
||||
@@ -2628,8 +2845,8 @@ def find_priority_equation(equations, access_id, current_access):
|
||||
for eq in eq_list:
|
||||
profit = eq.profit()
|
||||
if eq.can_cover_cost(current_access) and (eq.access_id is None or eq.access_id == access_id):
|
||||
if eq.neutral_profit() or eq.neutral():
|
||||
return eq, eq_list, sector # don't need to compare - just use it now
|
||||
# if eq.neutral_profit() or eq.neutral():
|
||||
# return eq, eq_list, sector # don't need to compare - just use it now
|
||||
if best_local_profit is None or profit > best_local_profit:
|
||||
best_local_profit = profit
|
||||
all_candidates.append((eq, eq_list, sector))
|
||||
@@ -2641,6 +2858,7 @@ def find_priority_equation(equations, access_id, current_access):
|
||||
wanted_candidates.append(eq)
|
||||
local_profit_map[sector] = best_local_profit
|
||||
filtered_candidates = filter_requirements(all_candidates, equations, required, current_access)
|
||||
filtered_candidates = [x for x in filtered_candidates if x[0].gross() > 0]
|
||||
if len(filtered_candidates) == 0:
|
||||
filtered_candidates = all_candidates # probably bad things
|
||||
if len(filtered_candidates) == 0:
|
||||
@@ -2648,6 +2866,18 @@ def find_priority_equation(equations, access_id, current_access):
|
||||
if len(filtered_candidates) == 1:
|
||||
return filtered_candidates[0]
|
||||
|
||||
neutral_candidates = [x for x in filtered_candidates if x[0].neutral_profit() or x[0].neutral()]
|
||||
if len(neutral_candidates) == 0:
|
||||
neutral_candidates = filtered_candidates
|
||||
if len(neutral_candidates) == 1:
|
||||
return neutral_candidates[0]
|
||||
|
||||
filtered_candidates = filter_requirements(neutral_candidates, equations, required, current_access)
|
||||
if len(filtered_candidates) == 0:
|
||||
filtered_candidates = neutral_candidates
|
||||
if len(filtered_candidates) == 1:
|
||||
return filtered_candidates[0]
|
||||
|
||||
triplet_candidates = []
|
||||
best_profit = None
|
||||
for eq, eq_list, sector in filtered_candidates:
|
||||
@@ -2706,13 +2936,14 @@ def find_cost_point(eq_triplet, access):
|
||||
return cost_point
|
||||
|
||||
|
||||
def find_greedy_equation(equations, access_id, current_access):
|
||||
def find_greedy_equation(equations, access_id, current_access, sector_split):
|
||||
all_candidates = []
|
||||
for sector, eq_list in equations.items():
|
||||
eq_list.sort(key=lambda eq: eq.profit(), reverse=True)
|
||||
for eq in eq_list:
|
||||
if eq.can_cover_cost(current_access) and (eq.access_id is None or eq.access_id == access_id):
|
||||
all_candidates.append((eq, eq_list, sector))
|
||||
if sector not in sector_split.keys() or sector_split[sector] == access_id:
|
||||
eq_list.sort(key=lambda eq: eq.profit(), reverse=True)
|
||||
for eq in eq_list:
|
||||
if eq.can_cover_cost(current_access) and (eq.access_id is None or eq.access_id == access_id):
|
||||
all_candidates.append((eq, eq_list, sector))
|
||||
if len(all_candidates) == 0:
|
||||
return None, None, None # can't pay for anything
|
||||
if len(all_candidates) == 1:
|
||||
@@ -2798,6 +3029,7 @@ def filter_requirements(triplet_candidates, equations, required, current_access)
|
||||
valid = True
|
||||
if not cand.required:
|
||||
potential_benefit = defaultdict(int)
|
||||
benefit_counted = set()
|
||||
potential_costs = defaultdict(int)
|
||||
for h_type, benefit in current_access.items():
|
||||
cur_cost = len(cand.cost[h_type])
|
||||
@@ -2813,7 +3045,9 @@ def filter_requirements(triplet_candidates, equations, required, current_access)
|
||||
adj_list = eq_list
|
||||
for eq in adj_list:
|
||||
for h_type, benefit_list in eq.benefit.items():
|
||||
potential_benefit[h_type] += len(benefit_list)
|
||||
total_benefit = set(benefit_list) - benefit_counted
|
||||
potential_benefit[h_type] += len(total_benefit)
|
||||
benefit_counted.update(benefit_list)
|
||||
for h_type, cost_list in eq.cost.items():
|
||||
potential_costs[h_type] += len(cost_list)
|
||||
for h_type, requirement in r_costs.items():
|
||||
@@ -2854,14 +3088,14 @@ def resolve_equation(equation, eq_list, sector, access_id, current_access, reach
|
||||
for door in door_list:
|
||||
reached_doors.add(door)
|
||||
eq_list.remove(equation)
|
||||
removing = [x for x in eq_list if x.door in reached_doors]
|
||||
for r_eq in removing:
|
||||
all_benefits_met = True
|
||||
# removing = [x for x in eq_list if x.door in reached_doors]
|
||||
for r_eq in list(eq_list):
|
||||
all_benefits_met = r_eq.door in reached_doors
|
||||
for key in Hook:
|
||||
fringe_list = [x for x in r_eq.benefit[key] if x not in reached_doors]
|
||||
r_eq.benefit[key] = fringe_list
|
||||
if len(fringe_list) > 0:
|
||||
all_benefits_met = False
|
||||
r_eq.benefit[key] = fringe_list
|
||||
if all_benefits_met:
|
||||
eq_list.remove(r_eq)
|
||||
if len(eq_list) == 0:
|
||||
@@ -2926,12 +3160,14 @@ def calc_door_equation(door, sector, look_for_entrance):
|
||||
eq.cost[hanger_from_door(door)].append(door)
|
||||
if not door.stonewall:
|
||||
start_region = door.entrance.parent_region
|
||||
visited = {start_region}
|
||||
queue = deque([start_region])
|
||||
visited = {(start_region, False)}
|
||||
queue = deque([(start_region, False)])
|
||||
found_events = set()
|
||||
event_doors = set()
|
||||
while len(queue) > 0:
|
||||
region = queue.popleft()
|
||||
region, blue_flag = queue.popleft()
|
||||
if region.crystal_switch and not blue_flag:
|
||||
eq.c_switch = True
|
||||
for loc in region.locations:
|
||||
if loc.name in dungeon_events:
|
||||
found_events.add(loc.name)
|
||||
@@ -2939,8 +3175,9 @@ def calc_door_equation(door, sector, look_for_entrance):
|
||||
if loc.name == d.req_event:
|
||||
connect = d.entrance.connected_region
|
||||
if connect is not None and connect.type == RegionType.Dungeon and connect not in visited:
|
||||
visited.add(connect)
|
||||
queue.append(connect)
|
||||
blue_barrier_flag = blue_flag or d.crystal == CrystalBarrier.Blue
|
||||
visited.add((connect, blue_barrier_flag))
|
||||
queue.append((connect, blue_barrier_flag))
|
||||
for ext in region.exits:
|
||||
d = ext.door
|
||||
if d is not None:
|
||||
@@ -2950,13 +3187,19 @@ def calc_door_equation(door, sector, look_for_entrance):
|
||||
eq_list = eq.benefit[hook_from_door(d)]
|
||||
if d not in eq_list:
|
||||
eq_list.append(d)
|
||||
if blue_flag:
|
||||
eq.blue_blocked.append(d)
|
||||
elif not blue_flag and d in eq.blue_blocked:
|
||||
eq.blue_blocked.remove(d)
|
||||
if d.req_event is not None and d.req_event not in found_events:
|
||||
event_doors.add(d)
|
||||
else:
|
||||
connect = ext.connected_region if ext.door.controller is None else d.entrance.parent_region
|
||||
if connect is not None and connect.type == RegionType.Dungeon and connect not in visited:
|
||||
visited.add(connect)
|
||||
queue.append(connect)
|
||||
if connect is not None and connect.type == RegionType.Dungeon:
|
||||
blue_barrier_flag = blue_flag or d.crystal == CrystalBarrier.Blue
|
||||
if (connect, blue_barrier_flag) not in visited:
|
||||
visited.add((connect, blue_barrier_flag))
|
||||
queue.append((connect, blue_barrier_flag))
|
||||
if len(eq.benefit) == 0:
|
||||
eq.required = True
|
||||
return eq, False
|
||||
|
||||
5
Main.py
5
Main.py
@@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
|
||||
from ItemList import generate_itempool, difficulties, fill_prizes
|
||||
from Utils import output_path, parse_player_names
|
||||
|
||||
__version__ = '0.1.0.11-u'
|
||||
__version__ = '0.1.0.12-u'
|
||||
|
||||
class EnemizerError(RuntimeError):
|
||||
pass
|
||||
@@ -287,7 +287,8 @@ def main(args, seed=None, fish=None):
|
||||
"roms": rom_names,
|
||||
"remote_items": [player for player in range(1, world.players + 1) if world.remote_items[player]],
|
||||
"locations": [((location.address, location.player), (location.item.code, location.item.player))
|
||||
for location in world.get_filled_locations() if type(location.address) is int]
|
||||
for location in world.get_filled_locations() if type(location.address) is int],
|
||||
"tags" : ["DR"]
|
||||
}).encode("utf-8"))
|
||||
if args.jsonout:
|
||||
jsonout["multidata"] = list(multidata)
|
||||
|
||||
@@ -750,7 +750,8 @@ def create_dungeon_regions(world, player):
|
||||
world.get_region('TR Crystal Maze', player).crystal_switch = True
|
||||
world.get_region('GT Crystal Conveyor', player).crystal_switch = True # INTERIOR not accessible
|
||||
world.get_region('GT Hookshot South Platform', player).crystal_switch = True
|
||||
# world.get_region('GT Double Switch Switches', player).crystal_switch = True # this is not very relevant
|
||||
# Relevant to indicate north door can access c_switch
|
||||
world.get_region('GT Double Switch Switches', player).crystal_switch = True
|
||||
world.get_region('GT Spike Crystals', player).crystal_switch = True
|
||||
world.get_region('GT Crystal Paths', player).crystal_switch = True
|
||||
world.get_region('GT Hidden Spikes', player).crystal_switch = True
|
||||
|
||||
Reference in New Issue
Block a user