Generation improvements
This commit is contained in:
@@ -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
2
CLI.py
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -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
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 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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
4
Rules.py
4
Rules.py
@@ -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']
|
||||||
|
|||||||
14
TestSuite.py
14
TestSuite.py
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user