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.boss_exit_idx = boss_exit_idx
self.default = True self.default = True
self.destination = False self.destination = False
self.dependent = None
self.deadEnd = False self.deadEnd = False
self.light_world = False self.light_world = False

2
CLI.py
View File

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

View File

@@ -462,7 +462,12 @@ def analyze_portals(world, player):
if len(possible_portals) == 1: if len(possible_portals) == 1:
world.get_portal(possible_portals[0], player).destination = True world.get_portal(possible_portals[0], player).destination = True
elif len(possible_portals) > 1: 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): def connect_portal(portal, world, player):
@@ -560,7 +565,7 @@ def create_dungeon_entrances(world, player):
for key, portal_list in dungeon_portals.items(): for key, portal_list in dungeon_portals.items():
if key in dungeon_drops.keys(): if key in dungeon_drops.keys():
entrance_map[key].extend(dungeon_drops[key]) 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 = [] dead_ends = []
destinations = [] destinations = []
the_rest = [] the_rest = []
@@ -586,10 +591,12 @@ def create_dungeon_entrances(world, player):
p_entrance = portal.door.entrance p_entrance = portal.door.entrance
r_name = p_entrance.parent_region.name r_name = p_entrance.parent_region.name
split_map[key][choice].append(r_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] dest_choices = [x for x in choices if len(split_map[key][x]) > 0]
for portal in destinations: 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: 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())] filtered_choices = [x for x in choices if any(y not in world.inaccessible_regions[player] for y in originating[key][x].keys())]
else: else:
@@ -604,15 +611,16 @@ def create_dungeon_entrances(world, player):
portal = world.get_portal(portal_name, player) portal = world.get_portal(portal_name, player)
r_name = portal.door.entrance.parent_region.name r_name = portal.door.entrance.parent_region.name
entrance_map[key].append(r_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 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): # def unpair_all_doors(world, player):
# for paired_door in world.paired_doors[player]: # for paired_door in world.paired_doors[player]:
# paired_door.pair = False # 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 builder.split_dungeon_map[name].valid_proposal = proposal
return builder.split_dungeon_map # we made this earlier in gen, just use it 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 while attempts < 5: # does not solve coin flips 3% of the time
try: try:
candidate_sectors = dict.fromkeys(builder.sectors) candidate_sectors = dict.fromkeys(builder.sectors)
global_pole = GlobalPolarity(candidate_sectors) global_pole = GlobalPolarity(candidate_sectors)
dungeon_map, sub_builder, merge_keys = {}, None, [] dungeon_map, sub_builder, merge_keys = {}, None, []
if merge_attempt: if merge_attempt > 0:
candidates = [] candidates = []
for name, split_entrances in split_list.items(): for name, split_entrances in split_list.items():
if len(split_entrances) > 1: 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) p = next(x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == r_name)
if not p.deadEnd: if not p.deadEnd:
candidates.append(name) 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(): for name, split_entrances in split_list.items():
key = builder.name + ' ' + name key = builder.name + ' ' + name
if merge_keys and name in merge_keys: if merge_keys and name in merge_keys:
other_key = builder.name + ' ' + [x for x in merge_keys if x != name][0] other_keys = [builder.name + ' ' + x for x in merge_keys if x != name]
if other_key in dungeon_map: other_key = next((x for x in other_keys if x in dungeon_map), None)
if other_key:
key = other_key key = other_key
sub_builder = dungeon_map[other_key] sub_builder = dungeon_map[other_key]
sub_builder.all_entrances.extend(split_entrances) 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 attempts += 5 # all the combinations were tried already, no use repeating
else: else:
attempts += 1 attempts += 1
if attempts >= 5 and not merge_attempt: if attempts >= 5 and merge_attempt < merge_limit:
merge_attempt, attempts = True, 0 merge_attempt, attempts = merge_attempt + 1, 0
raise GenerationException('Unable to resolve in 5 attempts') raise GenerationException('Unable to resolve in 5 attempts')

View File

@@ -1,12 +1,12 @@
import collections import collections
from BaseClasses import RegionType 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): def create_inverted_regions(world, player):
world.regions += [ 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'], 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', ["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', '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 ItemList import generate_itempool, difficulties, fill_prizes, fill_specific_items
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.2.0.16u' __version__ = '0.2.0.17u'
class EnemizerError(RuntimeError): class EnemizerError(RuntimeError):
pass pass
@@ -354,6 +354,7 @@ def main(args, seed=None, fish=None):
return world return world
def copy_world(world): def copy_world(world):
# ToDo: Not good yet # ToDo: Not good yet
ret = World(world.players, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, ret = World(world.players, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
@@ -443,15 +444,19 @@ def copy_world(world):
# fill locations # fill locations
for location in world.get_locations(): for location in world.get_locations():
new_location = ret.get_location(location.name, location.player)
if location.item is not None: if location.item is not None:
item = Item(location.item.name, location.item.advancement, location.item.priority, location.item.type, player = location.item.player) 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 new_location.item = item
item.location = ret.get_location(location.name, location.player) item.location = new_location
item.world = ret item.world = ret
if location.event: if location.event:
ret.get_location(location.name, location.player).event = True new_location.event = True
if location.locked: 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 # copy remaining itempool. No item in itempool should have an assigned location
for item in world.itempool: for item in world.itempool:

View File

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

View File

@@ -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. # 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', bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave',
'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] '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', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid',
'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins'] '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. # 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', bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave',
'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)', 'The Sky'] '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', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid',
'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins',
'Bombos Tablet', 'Ether Tablet', 'Purple Chest'] 'Bombos Tablet', 'Ether Tablet', 'Purple Chest']

View File

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