Generation improvements

This commit is contained in:
aerinon
2020-12-01 15:00:53 -07:00
parent 64b539706c
commit 32e7544775
9 changed files with 53 additions and 40 deletions

View File

@@ -1499,6 +1499,7 @@ class Portal(object):
self.boss_exit_idx = boss_exit_idx
self.default = True
self.destination = False
self.dependent = None
self.deadEnd = False
self.light_world = False

2
CLI.py
View File

@@ -2,8 +2,6 @@ import argparse
import copy
import json
import os
import logging
import random
import textwrap
import shlex
import sys

View File

@@ -462,7 +462,12 @@ def analyze_portals(world, player):
if len(possible_portals) == 1:
world.get_portal(possible_portals[0], player).destination = True
elif len(possible_portals) > 1:
world.get_portal(random.choice(possible_portals), player).destination = True
dest_portal = random.choice(possible_portals)
access_portal = world.get_portal(dest_portal, player)
access_portal.destination = True
for other_portal in possible_portals:
if other_portal != dest_portal:
world.get_portal(dest_portal, player).dependent = access_portal
def connect_portal(portal, world, player):
@@ -560,7 +565,7 @@ def create_dungeon_entrances(world, player):
for key, portal_list in dungeon_portals.items():
if key in dungeon_drops.keys():
entrance_map[key].extend(dungeon_drops[key])
if key in split_portals.keys() and world.intensity[player] >= 3:
if key in split_portals.keys():
dead_ends = []
destinations = []
the_rest = []
@@ -586,10 +591,12 @@ def create_dungeon_entrances(world, player):
p_entrance = portal.door.entrance
r_name = p_entrance.parent_region.name
split_map[key][choice].append(r_name)
originating[key][choice][p_entrance.connected_region.name] = None
entrance_region = find_entrance_region(portal)
originating[key][choice][entrance_region.name] = None
dest_choices = [x for x in choices if len(split_map[key][x]) > 0]
for portal in destinations:
restricted = portal.door.entrance.connected_region.name in world.inaccessible_regions[player]
entrance_region = find_entrance_region(portal)
restricted = entrance_region.name in world.inaccessible_regions[player]
if restricted:
filtered_choices = [x for x in choices if any(y not in world.inaccessible_regions[player] for y in originating[key][x].keys())]
else:
@@ -604,15 +611,16 @@ def create_dungeon_entrances(world, player):
portal = world.get_portal(portal_name, player)
r_name = portal.door.entrance.parent_region.name
entrance_map[key].append(r_name)
if key in split_portals.keys():
for split_key in split_portals[key]:
if split_key not in split_map[key]:
split_map[key][split_key] = []
if world.intensity[player] < 3:
split_map[key][split_portal_defaults[key][r_name]].append(r_name)
return entrance_map, split_map
def find_entrance_region(portal):
for entrance in portal.door.entrance.connected_region.entrances:
if entrance.parent_region.type != RegionType.Dungeon:
return entrance.parent_region
return None
# def unpair_all_doors(world, player):
# for paired_door in world.paired_doors[player]:
# paired_door.pair = False

View File

@@ -2750,14 +2750,14 @@ def split_dungeon_builder(builder, split_list, builder_info):
builder.split_dungeon_map[name].valid_proposal = proposal
return builder.split_dungeon_map # we made this earlier in gen, just use it
attempts, comb_w_replace, merge_attempt = 0, None, False
attempts, comb_w_replace, merge_attempt, merge_limit = 0, None, 0, len(split_list) - 1
while attempts < 5: # does not solve coin flips 3% of the time
try:
candidate_sectors = dict.fromkeys(builder.sectors)
global_pole = GlobalPolarity(candidate_sectors)
dungeon_map, sub_builder, merge_keys = {}, None, []
if merge_attempt:
if merge_attempt > 0:
candidates = []
for name, split_entrances in split_list.items():
if len(split_entrances) > 1:
@@ -2770,12 +2770,13 @@ def split_dungeon_builder(builder, split_list, builder_info):
p = next(x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == r_name)
if not p.deadEnd:
candidates.append(name)
merge_keys = random.sample(candidates, 2) if len(candidates) >= 2 else []
merge_keys = random.sample(candidates, merge_attempt+1) if len(candidates) >= merge_attempt+1 else []
for name, split_entrances in split_list.items():
key = builder.name + ' ' + name
if merge_keys and name in merge_keys:
other_key = builder.name + ' ' + [x for x in merge_keys if x != name][0]
if other_key in dungeon_map:
other_keys = [builder.name + ' ' + x for x in merge_keys if x != name]
other_key = next((x for x in other_keys if x in dungeon_map), None)
if other_key:
key = other_key
sub_builder = dungeon_map[other_key]
sub_builder.all_entrances.extend(split_entrances)
@@ -2791,8 +2792,8 @@ def split_dungeon_builder(builder, split_list, builder_info):
attempts += 5 # all the combinations were tried already, no use repeating
else:
attempts += 1
if attempts >= 5 and not merge_attempt:
merge_attempt, attempts = True, 0
if attempts >= 5 and merge_attempt < merge_limit:
merge_attempt, attempts = merge_attempt + 1, 0
raise GenerationException('Unable to resolve in 5 attempts')

View File

@@ -1,12 +1,12 @@
import collections
from BaseClasses import RegionType
from Regions import create_lw_region, create_dw_region, create_cave_region, create_dungeon_region
from Regions import create_lw_region, create_dw_region, create_cave_region, create_dungeon_region, create_menu_region
def create_inverted_regions(world, player):
world.regions += [
create_dw_region(player, 'Menu', None, ['Links House S&Q', 'Dark Sanctuary S&Q', 'Old Man S&Q', 'Castle Ledge S&Q']),
create_menu_region(player, 'Menu', None, ['Links House S&Q', 'Dark Sanctuary S&Q', 'Old Man S&Q', 'Castle Ledge S&Q']),
create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest', 'Bombos Tablet'],
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam',
'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',

15
Main.py
View File

@@ -25,7 +25,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties, fill_prizes, fill_specific_items
from Utils import output_path, parse_player_names
__version__ = '0.2.0.16u'
__version__ = '0.2.0.17u'
class EnemizerError(RuntimeError):
pass
@@ -354,6 +354,7 @@ def main(args, seed=None, fish=None):
return world
def copy_world(world):
# ToDo: Not good yet
ret = World(world.players, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
@@ -443,15 +444,19 @@ def copy_world(world):
# fill locations
for location in world.get_locations():
new_location = ret.get_location(location.name, location.player)
if location.item is not None:
item = Item(location.item.name, location.item.advancement, location.item.priority, location.item.type, player = location.item.player)
ret.get_location(location.name, location.player).item = item
item.location = ret.get_location(location.name, location.player)
new_location.item = item
item.location = new_location
item.world = ret
if location.event:
ret.get_location(location.name, location.player).event = True
new_location.event = True
if location.locked:
ret.get_location(location.name, location.player).locked = True
new_location.locked = True
# these need to be modified properly by set_rules
new_location.access_rule = lambda state: True
new_location.item_rule = lambda state: True
# copy remaining itempool. No item in itempool should have an assigned location
for item in world.itempool:

View File

@@ -92,6 +92,8 @@ testing to verify logic is all good.
# Bug Fixes
* 2.0.17u
* Generation improvements
* 2.0.16u
* Prevent HUD from showing key counter when in the overworld. (Aga 2 doesn't always clear the dungeon indicator)
* Fixed key logic regarding certain isolated "important" locations

View File

@@ -564,7 +564,7 @@ def inverted_rules(world, player):
set_rule(world.get_entrance('Bomb Hut Outer Bushes', player), lambda state: state.has_Pearl(player))
set_rule(world.get_entrance('North Fairy Cave Drop', player), lambda state: state.has_Pearl(player))
set_rule(world.get_entrance('Lost Woods Hideout Drop', player), lambda state: state.has_Pearl(player))
set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player) and (state.can_reach('Potion Shop Area', 'Region', player))) # new inverted region, need pearl for bushes or access to potion shop door/waterfall fairy
set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player) and (state.can_reach('Potion Shop Area', 'Region', player))) # new inverted region, need pearl for bushes or access to potion shop door/waterfall fairy
set_rule(world.get_entrance('Desert Palace Entrance (North) Rocks', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player))
set_rule(world.get_entrance('Desert Ledge Return Rocks', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) # should we decide to place something that is not a dungeon end up there at some point
set_rule(world.get_entrance('Checkerboard Cave', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player))
@@ -1348,7 +1348,7 @@ def set_bunny_rules(world, player):
# Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing.
bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave',
'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)']
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree',
bunny_accessible_locations = ['Link\'s House', 'Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree',
'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid',
'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins']
@@ -1432,7 +1432,7 @@ def set_inverted_bunny_rules(world, player):
# Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing.
bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave',
'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)', 'The Sky']
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree',
bunny_accessible_locations = ['Link\'s House', 'Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree',
'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid',
'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins',
'Bombos Tablet', 'Ether Tablet', 'Purple Chest']

View File

@@ -1,7 +1,5 @@
import subprocess
import sys
import traceback
import io
import multiprocessing
import concurrent.futures
import argparse
@@ -42,6 +40,7 @@ def main(args=None):
task.success = False
task.name = testname
task.mode = mode[0]
task.cmd = basecommand + " " + command + mode[1]
task_mapping.append(task)
test("Vanilla ", "--shuffle vanilla")
@@ -61,14 +60,12 @@ def main(args=None):
try:
result = task.result()
if result.returncode:
raise Exception(result.stderr)
except:
error = io.StringIO()
traceback.print_exc(file=error)
errors.append([task.name + task.mode, error.getvalue()])
else:
alive += 1
task.success = True
errors.append([task.name + task.mode, task.cmd, result.stderr])
else:
alive += 1
task.success = True
except Exception as e:
raise e
progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}{task.mode}")
@@ -129,7 +126,8 @@ if __name__ == "__main__":
with open(f"{dr[0]}{(f'-{tense}' if dr[0] in ['basic', 'crossed'] else '')}-errors.txt", 'w') as stream:
for error in errors:
stream.write(error[0] + "\n")
stream.write(error[1] + "\n\n")
stream.write(error[1] + "\n")
stream.write(error[2] + "\n\n")
with open("success.txt", "w") as stream:
stream.write(str.join("\n", successes))