Generation improvements
This commit is contained in:
@@ -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
2
CLI.py
@@ -2,8 +2,6 @@ import argparse
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import random
|
||||
import textwrap
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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
15
Main.py
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
6
Rules.py
6
Rules.py
@@ -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']
|
||||
|
||||
20
TestSuite.py
20
TestSuite.py
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user