Merge in unstable
This commit is contained in:
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@@ -26,8 +26,8 @@ jobs:
|
|||||||
# os & python versions
|
# os & python versions
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os-name: [ ubuntu-latest, ubuntu-16.04, macOS-latest, windows-latest ]
|
os-name: [ ubuntu-latest, ubuntu-18.04, macOS-latest, windows-latest ]
|
||||||
python-version: [ 3.7 ]
|
python-version: [ 3.8 ]
|
||||||
# needs: [ install-test ]
|
# needs: [ install-test ]
|
||||||
steps:
|
steps:
|
||||||
# checkout commit
|
# checkout commit
|
||||||
@@ -57,11 +57,11 @@ jobs:
|
|||||||
# run build-gui.py
|
# run build-gui.py
|
||||||
- name: Build GUI
|
- name: Build GUI
|
||||||
run: |
|
run: |
|
||||||
python ./build-gui.py
|
python ./source/meta/build-gui.py
|
||||||
# run build-dr.py
|
# run build-dr.py
|
||||||
- name: Build DungeonRandomizer
|
- name: Build DungeonRandomizer
|
||||||
run: |
|
run: |
|
||||||
python ./build-dr.py
|
python ./source/meta/build-dr.py
|
||||||
# prepare binary artifacts for later step
|
# prepare binary artifacts for later step
|
||||||
- name: Prepare Binary Artifacts
|
- name: Prepare Binary Artifacts
|
||||||
env:
|
env:
|
||||||
@@ -88,8 +88,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# install/release on not xenial
|
# install/release on not xenial
|
||||||
os-name: [ ubuntu-latest, macOS-latest, windows-latest ]
|
os-name: [ ubuntu-latest, ubuntu-18.04, macOS-latest, windows-latest ]
|
||||||
python-version: [ 3.7 ]
|
python-version: [ 3.8 ]
|
||||||
|
|
||||||
needs: [ install-build ]
|
needs: [ install-build ]
|
||||||
steps:
|
steps:
|
||||||
@@ -150,9 +150,9 @@ jobs:
|
|||||||
# os & python versions
|
# os & python versions
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# release only on bionic
|
# release only on focal/bionic
|
||||||
os-name: [ ubuntu-latest ]
|
os-name: [ ubuntu-latest ]
|
||||||
python-version: [ 3.7 ]
|
python-version: [ 3.8 ]
|
||||||
|
|
||||||
needs: [ install-prepare-release ]
|
needs: [ install-prepare-release ]
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -154,8 +154,9 @@ class World(object):
|
|||||||
self._door_cache[(door.name, door.player)] = door
|
self._door_cache[(door.name, door.player)] = door
|
||||||
|
|
||||||
def remove_door(self, door, player):
|
def remove_door(self, door, player):
|
||||||
if (door, player) in self._door_cache.keys():
|
if (door.name, player) in self._door_cache.keys():
|
||||||
del self._door_cache[(door, player)]
|
del self._door_cache[(door.name, player)]
|
||||||
|
if door in self.doors:
|
||||||
self.doors.remove(door)
|
self.doors.remove(door)
|
||||||
|
|
||||||
def get_regions(self, player=None):
|
def get_regions(self, player=None):
|
||||||
@@ -1220,6 +1221,7 @@ class Door(object):
|
|||||||
# self.connected = False # combine with Dest?
|
# self.connected = False # combine with Dest?
|
||||||
self.dest = None
|
self.dest = None
|
||||||
self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed)
|
self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed)
|
||||||
|
self.blocked_orig = False
|
||||||
self.stonewall = False # Indicate that the door cannot be enter until exited (Desert Torches, PoD Eye Statue)
|
self.stonewall = False # Indicate that the door cannot be enter until exited (Desert Torches, PoD Eye Statue)
|
||||||
self.smallKey = False # There's a small key door on this side
|
self.smallKey = False # There's a small key door on this side
|
||||||
self.bigKey = False # There's a big key door on this side
|
self.bigKey = False # There's a big key door on this side
|
||||||
@@ -1231,7 +1233,7 @@ class Door(object):
|
|||||||
self.dead = False
|
self.dead = False
|
||||||
|
|
||||||
self.entrance = entrance
|
self.entrance = entrance
|
||||||
if entrance is not None:
|
if entrance is not None and not entrance.door:
|
||||||
entrance.door = self
|
entrance.door = self
|
||||||
|
|
||||||
def getAddress(self):
|
def getAddress(self):
|
||||||
@@ -1317,7 +1319,7 @@ class Door(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def no_exit(self):
|
def no_exit(self):
|
||||||
self.blocked = True
|
self.blocked = self.blocked_orig = True
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def no_entrance(self):
|
def no_entrance(self):
|
||||||
|
|||||||
@@ -8,17 +8,43 @@ from typing import DefaultDict, Dict, List
|
|||||||
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo
|
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo
|
||||||
|
from Doors import reset_portals
|
||||||
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
|
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
|
||||||
from Dungeons import dungeon_bigs, dungeon_keys, dungeon_hints
|
from Dungeons import dungeon_bigs, dungeon_keys, dungeon_hints
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from RoomData import DoorKind, PairedDoor
|
from RoomData import DoorKind, PairedDoor, reset_rooms
|
||||||
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances
|
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances
|
||||||
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
|
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
|
||||||
from DungeonGenerator import dungeon_portals, dungeon_drops
|
from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException
|
||||||
from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic, build_key_layout, validate_key_layout
|
from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic, build_key_layout, validate_key_layout
|
||||||
|
|
||||||
|
|
||||||
def link_doors(world, player):
|
def link_doors(world, player):
|
||||||
|
attempt, valid = 1, False
|
||||||
|
while not valid:
|
||||||
|
try:
|
||||||
|
link_doors_main(world, player)
|
||||||
|
valid = True
|
||||||
|
except GenerationException as e:
|
||||||
|
logging.getLogger('').debug(f'Irreconcilable generation. {str(e)} Starting a new attempt.')
|
||||||
|
attempt += 1
|
||||||
|
if attempt > 10:
|
||||||
|
raise Exception('Could not create world in 10 attempts. Generation algorithms need more work', e)
|
||||||
|
for door in world.doors:
|
||||||
|
if door.player == player:
|
||||||
|
door.dest = None
|
||||||
|
door.entranceFlag = False
|
||||||
|
ent = door.entrance
|
||||||
|
if door.type != DoorType.Logical and ent.connected_region is not None:
|
||||||
|
ent.connected_region.entrances = [x for x in ent.connected_region.entrances if x != ent]
|
||||||
|
ent.connected_region = None
|
||||||
|
for portal in world.dungeon_portals[player]:
|
||||||
|
disconnect_portal(portal, world, player)
|
||||||
|
reset_portals(world, player)
|
||||||
|
reset_rooms(world, player)
|
||||||
|
|
||||||
|
|
||||||
|
def link_doors_main(world, player):
|
||||||
|
|
||||||
# Drop-down connections & push blocks
|
# Drop-down connections & push blocks
|
||||||
for exitName, regionName in logical_connections:
|
for exitName, regionName in logical_connections:
|
||||||
@@ -45,6 +71,7 @@ def link_doors(world, player):
|
|||||||
mirror_route = world.get_entrance('Sanctuary Mirror Route', player)
|
mirror_route = world.get_entrance('Sanctuary Mirror Route', player)
|
||||||
mr_door = mirror_route.door
|
mr_door = mirror_route.door
|
||||||
sanctuary = mirror_route.parent_region
|
sanctuary = mirror_route.parent_region
|
||||||
|
if mirror_route in sanctuary.exits:
|
||||||
sanctuary.exits.remove(mirror_route)
|
sanctuary.exits.remove(mirror_route)
|
||||||
world.remove_entrance(mirror_route, player)
|
world.remove_entrance(mirror_route, player)
|
||||||
world.remove_door(mr_door, player)
|
world.remove_door(mr_door, player)
|
||||||
@@ -388,6 +415,9 @@ def choose_portals(world, player):
|
|||||||
possible_portals = outstanding_portals if not info.sole_entrance else [x for x in outstanding_portals if x != info.sole_entrance]
|
possible_portals = outstanding_portals if not info.sole_entrance else [x for x in outstanding_portals if x != info.sole_entrance]
|
||||||
choice, portal = assign_portal(candidates, possible_portals, world, player)
|
choice, portal = assign_portal(candidates, possible_portals, world, player)
|
||||||
if choice.deadEnd:
|
if choice.deadEnd:
|
||||||
|
if choice.passage:
|
||||||
|
portal.destination = True
|
||||||
|
else:
|
||||||
portal.deadEnd = True
|
portal.deadEnd = True
|
||||||
clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_list, outstanding_portals)
|
clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_list, outstanding_portals)
|
||||||
the_rest = info.total - len(portal_assignment[dungeon])
|
the_rest = info.total - len(portal_assignment[dungeon])
|
||||||
@@ -477,7 +507,6 @@ def connect_portal(portal, world, player):
|
|||||||
ent, ext, entrance_name = portal_map[portal.name]
|
ent, ext, entrance_name = portal_map[portal.name]
|
||||||
if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']:
|
if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']:
|
||||||
ext = 'Inverted ' + ext
|
ext = 'Inverted ' + ext
|
||||||
# ent = 'Inverted ' + ent
|
|
||||||
portal_entrance = world.get_entrance(portal.door.entrance.name, player) # ensures I get the right one for copying
|
portal_entrance = world.get_entrance(portal.door.entrance.name, player) # ensures I get the right one for copying
|
||||||
target_exit = world.get_entrance(ext, player)
|
target_exit = world.get_entrance(ext, player)
|
||||||
portal_entrance.connected_region = target_exit.parent_region
|
portal_entrance.connected_region = target_exit.parent_region
|
||||||
@@ -491,22 +520,18 @@ def connect_portal(portal, world, player):
|
|||||||
portal_entrance.parent_region.entrances.append(edit_entrance)
|
portal_entrance.parent_region.entrances.append(edit_entrance)
|
||||||
|
|
||||||
|
|
||||||
# todo: remove this?
|
def disconnect_portal(portal, world, player):
|
||||||
def connect_portal_copy(portal, world, player):
|
|
||||||
ent, ext, entrance_name = portal_map[portal.name]
|
ent, ext, entrance_name = portal_map[portal.name]
|
||||||
if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']:
|
portal_entrance = world.get_entrance(portal.door.entrance.name, player)
|
||||||
ext = 'Inverted ' + ext
|
# portal_region = world.get_region(portal.name + ' Portal', player)
|
||||||
portal_entrance = world.get_entrance(portal.door.entrance.name, player) # ensures I get the right one for copying
|
|
||||||
target_exit = world.get_entrance(ext, player)
|
|
||||||
portal_entrance.connected_region = target_exit.parent_region
|
|
||||||
portal_region = world.get_region(portal.name + ' Portal', player)
|
|
||||||
portal_region.entrances.append(portal_entrance)
|
|
||||||
edit_entrance = world.get_entrance(entrance_name, player)
|
edit_entrance = world.get_entrance(entrance_name, player)
|
||||||
edit_entrance.connected_region = portal_entrance.parent_region
|
|
||||||
chosen_door = world.get_door(portal_entrance.name, player)
|
chosen_door = world.get_door(portal_entrance.name, player)
|
||||||
chosen_door.blocked = False
|
|
||||||
connect_door_only(world, chosen_door, portal_region, player)
|
# reverse work
|
||||||
portal_entrance.parent_region.entrances.append(edit_entrance)
|
if edit_entrance in portal_entrance.parent_region.entrances:
|
||||||
|
portal_entrance.parent_region.entrances.remove(edit_entrance)
|
||||||
|
chosen_door.blocked = chosen_door.blocked_orig
|
||||||
|
chosen_door.entranceFlag = False
|
||||||
|
|
||||||
|
|
||||||
def find_portal_candidates(door_list, dungeon, need_passage=False, dead_end_allowed=False, crossed=False, bk_shuffle=False):
|
def find_portal_candidates(door_list, dungeon, need_passage=False, dead_end_allowed=False, crossed=False, bk_shuffle=False):
|
||||||
@@ -710,10 +735,11 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
|
|||||||
continue
|
continue
|
||||||
origin_list = list(builder.entrance_list)
|
origin_list = list(builder.entrance_list)
|
||||||
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
|
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
|
||||||
|
split_dungeon = treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player)
|
||||||
if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player):
|
if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player):
|
||||||
if last_key == builder.name or loops > 1000:
|
if last_key == builder.name or loops > 1000:
|
||||||
origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin'
|
origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin'
|
||||||
raise Exception('Infinite loop detected for "%s" located at %s' % (builder.name, origin_name))
|
raise GenerationException(f'Infinite loop detected for "{builder.name}" located at {origin_name}')
|
||||||
sector_queue.append(builder)
|
sector_queue.append(builder)
|
||||||
last_key = builder.name
|
last_key = builder.name
|
||||||
loops += 1
|
loops += 1
|
||||||
@@ -857,6 +883,22 @@ def aga_tower_enabled(enabled):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player):
|
||||||
|
# what about ER dungeons? - find an example? (bad key doors 0 keys not valid)
|
||||||
|
if split_dungeon and name in multiple_portal_map:
|
||||||
|
possible_entrances = []
|
||||||
|
for portal_name in multiple_portal_map[name]:
|
||||||
|
portal = world.get_portal(portal_name, player)
|
||||||
|
portal_entrance = world.get_entrance(portal_map[portal_name][0], player)
|
||||||
|
if not portal.destination and portal_entrance.parent_region.name not in world.inaccessible_regions[player]:
|
||||||
|
possible_entrances.append(portal)
|
||||||
|
if len(possible_entrances) == 1:
|
||||||
|
single_portal = possible_entrances[0]
|
||||||
|
if single_portal.door.entrance.parent_region.name in origin_list and len(origin_list) == 1:
|
||||||
|
return False
|
||||||
|
return split_dungeon
|
||||||
|
|
||||||
|
|
||||||
# goals:
|
# goals:
|
||||||
# 1. have enough chests to be interesting (2 more than dungeon items)
|
# 1. have enough chests to be interesting (2 more than dungeon items)
|
||||||
# 2. have a balanced amount of regions added (check)
|
# 2. have a balanced amount of regions added (check)
|
||||||
@@ -2828,6 +2870,14 @@ portal_map = {
|
|||||||
'Ganons Tower': ('Ganons Tower', 'Ganons Tower Exit', 'Enter Ganons Tower'),
|
'Ganons Tower': ('Ganons Tower', 'Ganons Tower Exit', 'Enter Ganons Tower'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
multiple_portal_map = {
|
||||||
|
'Hyrule Castle': ['Sanctuary', 'Hyrule Castle West', 'Hyrule Castle South', 'Hyrule Castle East'],
|
||||||
|
'Desert Palace': ['Desert West', 'Desert South', 'Desert East', 'Desert Back'],
|
||||||
|
'Skull Woods': ['Skull 1', 'Skull 2 West', 'Skull 2 East', 'Skull 3'],
|
||||||
|
'Turtle Rock': ['Turtle Rock Lazy Eyes', 'Turtle Rock Eye Bridge', 'Turtle Rock Chest', 'Turtle Rock Main'],
|
||||||
|
}
|
||||||
|
|
||||||
split_portals = {
|
split_portals = {
|
||||||
'Desert Palace': ['Back', 'Main'],
|
'Desert Palace': ['Back', 'Main'],
|
||||||
'Skull Woods': ['1', '2', '3']
|
'Skull Woods': ['1', '2', '3']
|
||||||
|
|||||||
67
Doors.py
67
Doors.py
@@ -1271,36 +1271,11 @@ def create_doors(world, player):
|
|||||||
|
|
||||||
assign_entrances(world, player)
|
assign_entrances(world, player)
|
||||||
|
|
||||||
dungeon_portals = [
|
create_portals(world, player)
|
||||||
create_portal(player, 'Sanctuary', world.get_door('Sanctuary S', player), 0x02, 0x02),
|
|
||||||
create_portal(player, 'Hyrule Castle West', world.get_door('Hyrule Castle West Lobby S', player), 0x03, 0x04),
|
|
||||||
create_portal(player, 'Hyrule Castle South', world.get_door('Hyrule Castle Lobby S', player), 0x04, 0x06),
|
|
||||||
create_portal(player, 'Hyrule Castle East', world.get_door('Hyrule Castle East Lobby S', player), 0x05, 0x08),
|
|
||||||
create_portal(player, 'Eastern', world.get_door('Eastern Lobby S', player), 0x08, 0x12, 0),
|
|
||||||
create_portal(player, 'Desert South', world.get_door('Desert Main Lobby S', player), 0x09, 0x14),
|
|
||||||
create_portal(player, 'Desert East', world.get_door('Desert East Lobby S', player), 0x0a, 0x16),
|
|
||||||
create_portal(player, 'Desert West', world.get_door('Desert West S', player), 0x0b, 0x18),
|
|
||||||
create_portal(player, 'Desert Back', world.get_door('Desert Back Lobby S', player), 0x0c, 0x1a, 1),
|
|
||||||
create_portal(player, 'Turtle Rock Lazy Eyes', world.get_door('TR Lazy Eyes SE', player), 0x15, 0x2c),
|
|
||||||
create_portal(player, 'Turtle Rock Eye Bridge', world.get_door('TR Eye Bridge SW', player), 0x18, 0x32),
|
|
||||||
create_portal(player, 'Turtle Rock Chest', world.get_door('TR Big Chest Entrance SE', player), 0x19, 0x34),
|
|
||||||
create_portal(player, 'Agahnims Tower', world.get_door('Tower Lobby S', player), 0x24, 0x4a),
|
|
||||||
create_portal(player, 'Swamp', world.get_door('Swamp Lobby S', player), 0x25, 0x4c, 4),
|
|
||||||
create_portal(player, 'Palace of Darkness', world.get_door('PoD Lobby S', player), 0x26, 0x4e, 5),
|
|
||||||
create_portal(player, 'Mire', world.get_door('Mire Lobby S', player), 0x27, 0x50, 7),
|
|
||||||
create_portal(player, 'Skull 2 West', world.get_door('Skull 2 West Lobby S', player), 0x28, 0x52),
|
|
||||||
create_portal(player, 'Skull 2 East', world.get_door('Skull 2 East Lobby SW', player), 0x29, 0x54),
|
|
||||||
create_portal(player, 'Skull 1', world.get_door('Skull 1 Lobby S', player), 0x2a, 0x56),
|
|
||||||
create_portal(player, 'Skull 3', world.get_door('Skull 3 Lobby SW', player), 0x2b, 0x58, 6),
|
|
||||||
create_portal(player, 'Ice', world.get_door('Ice Lobby SE', player), 0x2d, 0x5c, 8),
|
|
||||||
create_portal(player, 'Hera', world.get_door('Hera Lobby S', player), 0x33, 0x5a, 2),
|
|
||||||
create_portal(player, 'Thieves Town', world.get_door('Thieves Lobby S', player), 0x34, 0x6a, 10),
|
|
||||||
create_portal(player, 'Turtle Rock Main', world.get_door('TR Main Lobby SE', player), 0x35, 0x68, 9),
|
|
||||||
create_portal(player, 'Ganons Tower', world.get_door('GT Lobby S', player), 0x37, 0x70),
|
|
||||||
]
|
|
||||||
world.dungeon_portals[player] += dungeon_portals
|
|
||||||
|
|
||||||
|
# static portal flags
|
||||||
world.get_door('Sanctuary S', player).dead_end(allowPassage=True)
|
world.get_door('Sanctuary S', player).dead_end(allowPassage=True)
|
||||||
|
world.get_door('Eastern Hint Tile Blocked Path SE', player).passage = False
|
||||||
world.get_door('TR Big Chest Entrance SE', player).passage = False
|
world.get_door('TR Big Chest Entrance SE', player).passage = False
|
||||||
world.get_door('Sewers Secret Room Key Door S', player).dungeonLink = 'Hyrule Castle'
|
world.get_door('Sewers Secret Room Key Door S', player).dungeonLink = 'Hyrule Castle'
|
||||||
world.get_door('Desert Cannonball S', player).dead_end()
|
world.get_door('Desert Cannonball S', player).dead_end()
|
||||||
@@ -1336,6 +1311,42 @@ def create_doors(world, player):
|
|||||||
world.get_door('Ice Conveyor SW', player).dungeonLink = 'linkIceFalls2'
|
world.get_door('Ice Conveyor SW', player).dungeonLink = 'linkIceFalls2'
|
||||||
|
|
||||||
|
|
||||||
|
def create_portals(world, player):
|
||||||
|
dungeon_portals = [
|
||||||
|
create_portal(player, 'Sanctuary', world.get_door('Sanctuary S', player), 0x02, 0x02),
|
||||||
|
create_portal(player, 'Hyrule Castle West', world.get_door('Hyrule Castle West Lobby S', player), 0x03, 0x04),
|
||||||
|
create_portal(player, 'Hyrule Castle South', world.get_door('Hyrule Castle Lobby S', player), 0x04, 0x06),
|
||||||
|
create_portal(player, 'Hyrule Castle East', world.get_door('Hyrule Castle East Lobby S', player), 0x05, 0x08),
|
||||||
|
create_portal(player, 'Eastern', world.get_door('Eastern Lobby S', player), 0x08, 0x12, 0),
|
||||||
|
create_portal(player, 'Desert South', world.get_door('Desert Main Lobby S', player), 0x09, 0x14),
|
||||||
|
create_portal(player, 'Desert East', world.get_door('Desert East Lobby S', player), 0x0a, 0x16),
|
||||||
|
create_portal(player, 'Desert West', world.get_door('Desert West S', player), 0x0b, 0x18),
|
||||||
|
create_portal(player, 'Desert Back', world.get_door('Desert Back Lobby S', player), 0x0c, 0x1a, 1),
|
||||||
|
create_portal(player, 'Turtle Rock Lazy Eyes', world.get_door('TR Lazy Eyes SE', player), 0x15, 0x2c),
|
||||||
|
create_portal(player, 'Turtle Rock Eye Bridge', world.get_door('TR Eye Bridge SW', player), 0x18, 0x32),
|
||||||
|
create_portal(player, 'Turtle Rock Chest', world.get_door('TR Big Chest Entrance SE', player), 0x19, 0x34),
|
||||||
|
create_portal(player, 'Agahnims Tower', world.get_door('Tower Lobby S', player), 0x24, 0x4a),
|
||||||
|
create_portal(player, 'Swamp', world.get_door('Swamp Lobby S', player), 0x25, 0x4c, 4),
|
||||||
|
create_portal(player, 'Palace of Darkness', world.get_door('PoD Lobby S', player), 0x26, 0x4e, 5),
|
||||||
|
create_portal(player, 'Mire', world.get_door('Mire Lobby S', player), 0x27, 0x50, 7),
|
||||||
|
create_portal(player, 'Skull 2 West', world.get_door('Skull 2 West Lobby S', player), 0x28, 0x52),
|
||||||
|
create_portal(player, 'Skull 2 East', world.get_door('Skull 2 East Lobby SW', player), 0x29, 0x54),
|
||||||
|
create_portal(player, 'Skull 1', world.get_door('Skull 1 Lobby S', player), 0x2a, 0x56),
|
||||||
|
create_portal(player, 'Skull 3', world.get_door('Skull 3 Lobby SW', player), 0x2b, 0x58, 6),
|
||||||
|
create_portal(player, 'Ice', world.get_door('Ice Lobby SE', player), 0x2d, 0x5c, 8),
|
||||||
|
create_portal(player, 'Hera', world.get_door('Hera Lobby S', player), 0x33, 0x5a, 2),
|
||||||
|
create_portal(player, 'Thieves Town', world.get_door('Thieves Lobby S', player), 0x34, 0x6a, 10),
|
||||||
|
create_portal(player, 'Turtle Rock Main', world.get_door('TR Main Lobby SE', player), 0x35, 0x68, 9),
|
||||||
|
create_portal(player, 'Ganons Tower', world.get_door('GT Lobby S', player), 0x37, 0x70),
|
||||||
|
]
|
||||||
|
world.dungeon_portals[player] += dungeon_portals
|
||||||
|
|
||||||
|
|
||||||
|
def reset_portals(world, player):
|
||||||
|
world.dungeon_portals[player].clear()
|
||||||
|
world._portal_cache.clear()
|
||||||
|
create_portals(world, player)
|
||||||
|
|
||||||
def create_paired_doors(world, player):
|
def create_paired_doors(world, player):
|
||||||
world.paired_doors[player] = [
|
world.paired_doors[player] = [
|
||||||
PairedDoor('Sewers Secret Room Key Door S', 'Sewers Key Rat Key Door N', True),
|
PairedDoor('Sewers Secret Room Key Door S', 'Sewers Key Rat Key Door N', True),
|
||||||
|
|||||||
@@ -1225,15 +1225,15 @@ def simple_dungeon_builder(name, sector_list):
|
|||||||
def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
||||||
dungeon_entrances=None, split_dungeon_entrances=None):
|
dungeon_entrances=None, split_dungeon_entrances=None):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
|
logger.info('Shuffling Dungeon Sectors')
|
||||||
|
|
||||||
if dungeon_entrances is None:
|
if dungeon_entrances is None:
|
||||||
dungeon_entrances = default_dungeon_entrances
|
dungeon_entrances = default_dungeon_entrances
|
||||||
if split_dungeon_entrances is None:
|
if split_dungeon_entrances is None:
|
||||||
split_dungeon_entrances = split_region_starts
|
split_dungeon_entrances = split_region_starts
|
||||||
define_sector_features(all_sectors)
|
define_sector_features(all_sectors)
|
||||||
finished, dungeon_map = False, {}
|
finished, dungeon_map, attempts = False, {}, 0
|
||||||
while not finished:
|
while not finished:
|
||||||
logger.info('Shuffling Dungeon Sectors')
|
|
||||||
candidate_sectors = dict.fromkeys(all_sectors)
|
candidate_sectors = dict.fromkeys(all_sectors)
|
||||||
global_pole = GlobalPolarity(candidate_sectors)
|
global_pole = GlobalPolarity(candidate_sectors)
|
||||||
|
|
||||||
@@ -1248,6 +1248,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
|||||||
for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda
|
for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda
|
||||||
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon,
|
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon,
|
||||||
candidate_sectors, global_pole)
|
candidate_sectors, global_pole)
|
||||||
|
standard_stair_check(dungeon_map, current_dungeon, candidate_sectors, global_pole)
|
||||||
entrances_map, potentials, connections = connections_tuple
|
entrances_map, potentials, connections = connections_tuple
|
||||||
accessible_sectors, reverse_d_map = set(), {}
|
accessible_sectors, reverse_d_map = set(), {}
|
||||||
for key in dungeon_entrances.keys():
|
for key in dungeon_entrances.keys():
|
||||||
@@ -1324,11 +1325,27 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
|||||||
assign_the_rest(dungeon_map, neutral_sectors, global_pole, builder_info)
|
assign_the_rest(dungeon_map, neutral_sectors, global_pole, builder_info)
|
||||||
dungeon_map.update(complete_dungeons)
|
dungeon_map.update(complete_dungeons)
|
||||||
finished = True
|
finished = True
|
||||||
except NeutralizingException:
|
except (NeutralizingException, GenerationException) as e:
|
||||||
pass
|
attempts += 1
|
||||||
|
logger.debug(f'Attempt {attempts} failed with {str(e)}')
|
||||||
|
if attempts >= 10:
|
||||||
|
raise Exception('Could not find a valid seed quickly, something is likely horribly wrong.', e)
|
||||||
return dungeon_map
|
return dungeon_map
|
||||||
|
|
||||||
|
|
||||||
|
def standard_stair_check(dungeon_map, dungeon, candidate_sectors, global_pole):
|
||||||
|
# this is because there must be at least one non-dead stairway in hc to get out
|
||||||
|
# this check may not be necessary
|
||||||
|
filtered_sectors = [x for x in candidate_sectors if any(y for y in x.outstanding_doors if not y.dead and y.type == DoorType.SpiralStairs)]
|
||||||
|
valid = False
|
||||||
|
while not valid:
|
||||||
|
chosen_sector = random.choice(filtered_sectors)
|
||||||
|
filtered_sectors.remove(chosen_sector)
|
||||||
|
valid = global_pole.is_valid_choice(dungeon_map, dungeon, [chosen_sector])
|
||||||
|
if valid:
|
||||||
|
assign_sector(chosen_sector, dungeon, candidate_sectors, global_pole)
|
||||||
|
|
||||||
|
|
||||||
def identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map, connections, dungeon_entrances, split_dungeon_entrances):
|
def identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map, connections, dungeon_entrances, split_dungeon_entrances):
|
||||||
accessible_overworld, found_connections, explored = set(), set(), False
|
accessible_overworld, found_connections, explored = set(), set(), False
|
||||||
|
|
||||||
@@ -1578,6 +1595,8 @@ def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barrier
|
|||||||
if len(crystal_switches) == 0:
|
if len(crystal_switches) == 0:
|
||||||
raise GenerationException('No crystal switches to assign')
|
raise GenerationException('No crystal switches to assign')
|
||||||
sector_list = list(crystal_switches)
|
sector_list = list(crystal_switches)
|
||||||
|
if len(population) > len(sector_list):
|
||||||
|
raise GenerationException('Not enough crystal switch sectors for those needed')
|
||||||
choices = random.sample(sector_list, k=len(population))
|
choices = random.sample(sector_list, k=len(population))
|
||||||
for i, choice in enumerate(choices):
|
for i, choice in enumerate(choices):
|
||||||
builder = dungeon_map[population[i]]
|
builder = dungeon_map[population[i]]
|
||||||
@@ -1588,7 +1607,7 @@ def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barrier
|
|||||||
def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_sectors, crystal_barriers, global_pole):
|
def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_sectors, crystal_barriers, global_pole):
|
||||||
invalid_builders = []
|
invalid_builders = []
|
||||||
for name, builder in dungeon_map.items():
|
for name, builder in dungeon_map.items():
|
||||||
if builder.c_switch_present and not builder.c_locked:
|
if builder.c_switch_present and builder.c_switch_required and not builder.c_locked:
|
||||||
invalid_builders.append(builder)
|
invalid_builders.append(builder)
|
||||||
while len(invalid_builders) > 0:
|
while len(invalid_builders) > 0:
|
||||||
valid_builders = []
|
valid_builders = []
|
||||||
@@ -1597,7 +1616,7 @@ def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_s
|
|||||||
reachable_crystals = defaultdict()
|
reachable_crystals = defaultdict()
|
||||||
for sector in builder.sectors:
|
for sector in builder.sectors:
|
||||||
if sector.equations is None:
|
if sector.equations is None:
|
||||||
sector.equations = calc_sector_equations(sector, builder)
|
sector.equations = calc_sector_equations(sector)
|
||||||
if sector.is_entrance_sector() and not sector.destination_entrance:
|
if sector.is_entrance_sector() and not sector.destination_entrance:
|
||||||
need_switch = True
|
need_switch = True
|
||||||
for region in sector.get_start_regions():
|
for region in sector.get_start_regions():
|
||||||
@@ -1631,7 +1650,7 @@ def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_s
|
|||||||
valid, sector, which_list = False, None, None
|
valid, sector, which_list = False, None, None
|
||||||
while not valid:
|
while not valid:
|
||||||
if len(candidates) <= 0:
|
if len(candidates) <= 0:
|
||||||
raise GenerationException(f'need to provide more sophisticatedted crystal connection for {entrance_sector}')
|
raise GenerationException(f'need to provide more sophisticated crystal connection for {entrance_sector}')
|
||||||
sector, which_list = random.choice(list(candidates.items()))
|
sector, which_list = random.choice(list(candidates.items()))
|
||||||
del candidates[sector]
|
del candidates[sector]
|
||||||
valid = global_pole.is_valid_choice(dungeon_map, builder, [sector])
|
valid = global_pole.is_valid_choice(dungeon_map, builder, [sector])
|
||||||
@@ -1690,7 +1709,7 @@ def find_pol_cand_for_c_switch(access, reachable_crystals, polarized_candidates)
|
|||||||
|
|
||||||
def pol_cand_matches_access_reach(sector, access, reachable_crystals):
|
def pol_cand_matches_access_reach(sector, access, reachable_crystals):
|
||||||
if sector.equations is None:
|
if sector.equations is None:
|
||||||
sector.equations = calc_sector_equations(sector, None)
|
sector.equations = calc_sector_equations(sector)
|
||||||
for eq in sector.equations:
|
for eq in sector.equations:
|
||||||
key, cost_door = eq.cost
|
key, cost_door = eq.cost
|
||||||
if key in access.keys() and access[key]:
|
if key in access.keys() and access[key]:
|
||||||
@@ -1712,7 +1731,7 @@ def find_crystal_cand(access, crystal_switches):
|
|||||||
|
|
||||||
def crystal_cand_matches_access(sector, access):
|
def crystal_cand_matches_access(sector, access):
|
||||||
if sector.equations is None:
|
if sector.equations is None:
|
||||||
sector.equations = calc_sector_equations(sector, None)
|
sector.equations = calc_sector_equations(sector)
|
||||||
for eq in sector.equations:
|
for eq in sector.equations:
|
||||||
key, cost_door = eq.cost
|
key, cost_door = eq.cost
|
||||||
if key in access.keys() and access[key] and eq.c_switch and len(sector.outstanding_doors) > 1:
|
if key in access.keys() and access[key] and eq.c_switch and len(sector.outstanding_doors) > 1:
|
||||||
@@ -1984,6 +2003,9 @@ def polarity_step_3(dungeon_map, polarized_sectors, global_pole):
|
|||||||
sample_target = 100 if combos > 10 else combos * 2
|
sample_target = 100 if combos > 10 else combos * 2
|
||||||
while best_choices is None or samples < sample_target:
|
while best_choices is None or samples < sample_target:
|
||||||
samples += 1
|
samples += 1
|
||||||
|
if len(odd_candidates) < len(odd_builders):
|
||||||
|
raise GenerationException(f'Unable to fix dungeon parity - not enough candidates.'
|
||||||
|
f' Ref: {next(iter(odd_builders)).name}')
|
||||||
choices = random.sample(odd_candidates, k=len(odd_builders))
|
choices = random.sample(odd_candidates, k=len(odd_builders))
|
||||||
valid = global_pole.is_valid_multi_choice(dungeon_map, odd_builders, choices)
|
valid = global_pole.is_valid_multi_choice(dungeon_map, odd_builders, choices)
|
||||||
charge = calc_total_charge(dungeon_map, odd_builders, choices)
|
charge = calc_total_charge(dungeon_map, odd_builders, choices)
|
||||||
@@ -3649,14 +3671,14 @@ def copy_door_equations(builder, sector_list):
|
|||||||
for sector in builder.sectors + sector_list:
|
for sector in builder.sectors + sector_list:
|
||||||
if sector.equations is None:
|
if sector.equations is None:
|
||||||
# todo: sort equations?
|
# todo: sort equations?
|
||||||
sector.equations = calc_sector_equations(sector, builder)
|
sector.equations = calc_sector_equations(sector)
|
||||||
curr_list = equations[sector] = []
|
curr_list = equations[sector] = []
|
||||||
for equation in sector.equations:
|
for equation in sector.equations:
|
||||||
curr_list.append(equation.copy())
|
curr_list.append(equation.copy())
|
||||||
return equations
|
return equations
|
||||||
|
|
||||||
|
|
||||||
def calc_sector_equations(sector, builder):
|
def calc_sector_equations(sector):
|
||||||
equations = []
|
equations = []
|
||||||
is_entrance = sector.is_entrance_sector() and not sector.destination_entrance
|
is_entrance = sector.is_entrance_sector() and not sector.destination_entrance
|
||||||
if is_entrance:
|
if is_entrance:
|
||||||
@@ -3686,6 +3708,8 @@ def calc_door_equation(door, sector, look_for_entrance):
|
|||||||
eq.benefit[hook_from_door(door)].append(door)
|
eq.benefit[hook_from_door(door)].append(door)
|
||||||
eq.required = True
|
eq.required = True
|
||||||
eq.c_switch = door.crystal == CrystalBarrier.Either
|
eq.c_switch = door.crystal == CrystalBarrier.Either
|
||||||
|
# exceptions for long entrances ???
|
||||||
|
# if door.name in ['PoD Dark Alley']:
|
||||||
eq.entrance_flag = True
|
eq.entrance_flag = True
|
||||||
return eq, flag
|
return eq, flag
|
||||||
eq = DoorEquation(door)
|
eq = DoorEquation(door)
|
||||||
|
|||||||
@@ -3181,7 +3181,7 @@ default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'),
|
|||||||
('Sanctuary Grave', 'Sewer Drop'),
|
('Sanctuary Grave', 'Sewer Drop'),
|
||||||
('Sanctuary Exit', 'Light World'),
|
('Sanctuary Exit', 'Light World'),
|
||||||
|
|
||||||
('Old Man Cave (West)', 'Old Man Cave'),
|
('Old Man Cave (West)', 'Old Man Cave Ledge'),
|
||||||
('Old Man Cave (East)', 'Old Man Cave'),
|
('Old Man Cave (East)', 'Old Man Cave'),
|
||||||
('Old Man Cave Exit (West)', 'Light World'),
|
('Old Man Cave Exit (West)', 'Light World'),
|
||||||
('Old Man Cave Exit (East)', 'Death Mountain'),
|
('Old Man Cave Exit (East)', 'Death Mountain'),
|
||||||
@@ -3327,7 +3327,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'
|
|||||||
('Two Brothers House (West)', 'Two Brothers House'),
|
('Two Brothers House (West)', 'Two Brothers House'),
|
||||||
('Two Brothers House Exit (East)', 'Light World'),
|
('Two Brothers House Exit (East)', 'Light World'),
|
||||||
('Two Brothers House Exit (West)', 'Maze Race Ledge'),
|
('Two Brothers House Exit (West)', 'Maze Race Ledge'),
|
||||||
('Sanctuary', 'Sanctuary'),
|
('Sanctuary', 'Sanctuary Portal'),
|
||||||
('Sanctuary Grave', 'Sewer Drop'),
|
('Sanctuary Grave', 'Sewer Drop'),
|
||||||
('Sanctuary Exit', 'Light World'),
|
('Sanctuary Exit', 'Light World'),
|
||||||
('Old Man House (Bottom)', 'Old Man House'),
|
('Old Man House (Bottom)', 'Old Man House'),
|
||||||
@@ -3398,7 +3398,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'
|
|||||||
('Old Man Cave Exit (West)', 'West Dark World'),
|
('Old Man Cave Exit (West)', 'West Dark World'),
|
||||||
('Old Man Cave Exit (East)', 'Dark Death Mountain'),
|
('Old Man Cave Exit (East)', 'Dark Death Mountain'),
|
||||||
('Dark Death Mountain Fairy', 'Old Man Cave'),
|
('Dark Death Mountain Fairy', 'Old Man Cave'),
|
||||||
('Bumper Cave (Bottom)', 'Old Man Cave'),
|
('Bumper Cave (Bottom)', 'Old Man Cave Ledge'),
|
||||||
('Bumper Cave (Top)', 'Dark Death Mountain Healer Fairy'),
|
('Bumper Cave (Top)', 'Dark Death Mountain Healer Fairy'),
|
||||||
('Bumper Cave Exit (Top)', 'Death Mountain Return Ledge'),
|
('Bumper Cave Exit (Top)', 'Death Mountain Return Ledge'),
|
||||||
('Bumper Cave Exit (Bottom)', 'Light World'),
|
('Bumper Cave Exit (Bottom)', 'Light World'),
|
||||||
|
|||||||
1
Gui.py
1
Gui.py
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -335,8 +335,9 @@ def adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_coun
|
|||||||
test_set = None
|
test_set = None
|
||||||
needed = rule.needed_keys_w_bk
|
needed = rule.needed_keys_w_bk
|
||||||
if needed > 0:
|
if needed > 0:
|
||||||
accessible_loc.update(key_counter.other_locations)
|
all_accessible = set(accessible_loc)
|
||||||
blocked_loc = key_layout.all_locations-accessible_loc
|
all_accessible.update(key_counter.other_locations)
|
||||||
|
blocked_loc = key_layout.all_locations-all_accessible
|
||||||
for location in blocked_loc:
|
for location in blocked_loc:
|
||||||
if location not in key_logic.location_rules.keys():
|
if location not in key_logic.location_rules.keys():
|
||||||
loc_rule = LocationRule()
|
loc_rule = LocationRule()
|
||||||
@@ -373,11 +374,16 @@ def refine_placement_rules(key_layout, max_ctr):
|
|||||||
rule.needed_keys_w_bk -= len(key_onlys)
|
rule.needed_keys_w_bk -= len(key_onlys)
|
||||||
if rule.needed_keys_w_bk == 0:
|
if rule.needed_keys_w_bk == 0:
|
||||||
rules_to_remove.append(rule)
|
rules_to_remove.append(rule)
|
||||||
if rule.bk_relevant and len(rule.check_locations_w_bk) == rule.needed_keys_w_bk + 1:
|
# todo: evaluate this usage
|
||||||
new_restricted = set(max_ctr.free_locations) - rule.check_locations_w_bk
|
# if rule.bk_relevant and len(rule.check_locations_w_bk) == rule.needed_keys_w_bk + 1:
|
||||||
if len(new_restricted - key_logic.bk_restricted) > 0:
|
# new_restricted = set(max_ctr.free_locations) - rule.check_locations_w_bk
|
||||||
key_logic.bk_restricted.update(new_restricted) # bk must be in one of the check_locations
|
# if len(new_restricted | key_logic.bk_restricted) < len(key_layout.all_chest_locations):
|
||||||
changed = True
|
# if len(new_restricted - key_logic.bk_restricted) > 0:
|
||||||
|
# key_logic.bk_restricted.update(new_restricted) # bk must be in one of the check_locations
|
||||||
|
# changed = True
|
||||||
|
# else:
|
||||||
|
# rules_to_remove.append(rule)
|
||||||
|
# changed = True
|
||||||
if rule.needed_keys_w_bk > key_layout.max_chests or len(rule.check_locations_w_bk) < rule.needed_keys_w_bk:
|
if rule.needed_keys_w_bk > key_layout.max_chests or len(rule.check_locations_w_bk) < rule.needed_keys_w_bk:
|
||||||
logging.getLogger('').warning('Invalid rule - what went wrong here??')
|
logging.getLogger('').warning('Invalid rule - what went wrong here??')
|
||||||
rules_to_remove.append(rule)
|
rules_to_remove.append(rule)
|
||||||
@@ -501,6 +507,8 @@ def find_bk_locked_sections(key_layout, world, player):
|
|||||||
key_layout.all_chest_locations.update(counter.free_locations)
|
key_layout.all_chest_locations.update(counter.free_locations)
|
||||||
key_layout.item_locations.update(counter.free_locations)
|
key_layout.item_locations.update(counter.free_locations)
|
||||||
key_layout.item_locations.update(counter.key_only_locations)
|
key_layout.item_locations.update(counter.key_only_locations)
|
||||||
|
key_layout.all_locations.update(key_layout.item_locations)
|
||||||
|
key_layout.all_locations.update(counter.other_locations)
|
||||||
if counter.big_key_opened and counter.important_location:
|
if counter.big_key_opened and counter.important_location:
|
||||||
big_chest_allowed_big_key = False
|
big_chest_allowed_big_key = False
|
||||||
if not counter.big_key_opened:
|
if not counter.big_key_opened:
|
||||||
|
|||||||
16
Main.py
16
Main.py
@@ -17,7 +17,7 @@ from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
|||||||
from EntranceShuffle import link_entrances, link_inverted_entrances
|
from EntranceShuffle import link_entrances, link_inverted_entrances
|
||||||
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
||||||
from Doors import create_doors
|
from Doors import create_doors
|
||||||
from DoorShuffle import link_doors, connect_portal_copy
|
from DoorShuffle import link_doors, connect_portal
|
||||||
from RoomData import create_rooms
|
from RoomData import create_rooms
|
||||||
from Rules import set_rules
|
from Rules import set_rules
|
||||||
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
||||||
@@ -25,8 +25,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
|
|||||||
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
|
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
|
||||||
from Utils import output_path, parse_player_names
|
from Utils import output_path, parse_player_names
|
||||||
|
|
||||||
__version__ = '0.2.1.0-u'
|
__version__ = '0.3.1.0-u'
|
||||||
|
|
||||||
|
|
||||||
class EnemizerError(RuntimeError):
|
class EnemizerError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
@@ -116,7 +115,7 @@ def main(args, seed=None, fish=None):
|
|||||||
create_dungeons(world, player)
|
create_dungeons(world, player)
|
||||||
adjust_locations(world, player)
|
adjust_locations(world, player)
|
||||||
|
|
||||||
if any(world.potshuffle):
|
if any(world.potshuffle.values()):
|
||||||
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
|
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
if world.potshuffle[player]:
|
if world.potshuffle[player]:
|
||||||
@@ -232,9 +231,6 @@ def main(args, seed=None, fish=None):
|
|||||||
if use_enemizer:
|
if use_enemizer:
|
||||||
base_patch = LocalRom(args.rom) # update base2current.json
|
base_patch = LocalRom(args.rom) # update base2current.json
|
||||||
|
|
||||||
if use_enemizer:
|
|
||||||
base_patch = LocalRom(args.rom) # update base2current.json
|
|
||||||
|
|
||||||
rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom)
|
rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom)
|
||||||
|
|
||||||
if use_enemizer and (args.enemizercli or not args.jsonout):
|
if use_enemizer and (args.enemizercli or not args.jsonout):
|
||||||
@@ -448,9 +444,7 @@ def copy_world(world):
|
|||||||
copied_region.is_light_world = region.is_light_world
|
copied_region.is_light_world = region.is_light_world
|
||||||
copied_region.is_dark_world = region.is_dark_world
|
copied_region.is_dark_world = region.is_dark_world
|
||||||
copied_region.dungeon = region.dungeon
|
copied_region.dungeon = region.dungeon
|
||||||
copied_region.locations = [copy.copy(location) for location in region.locations]
|
copied_region.locations = [ret.get_location(location.name, location.player) for location in region.locations]
|
||||||
for location in copied_region.locations:
|
|
||||||
location.parent_region = copied_region
|
|
||||||
for entrance in region.entrances:
|
for entrance in region.entrances:
|
||||||
ret.get_entrance(entrance.name, entrance.player).connect(copied_region)
|
ret.get_entrance(entrance.name, entrance.player).connect(copied_region)
|
||||||
|
|
||||||
@@ -494,7 +488,7 @@ def copy_world(world):
|
|||||||
ret.dungeon_portals = world.dungeon_portals
|
ret.dungeon_portals = world.dungeon_portals
|
||||||
for player, portals in world.dungeon_portals.items():
|
for player, portals in world.dungeon_portals.items():
|
||||||
for portal in portals:
|
for portal in portals:
|
||||||
connect_portal_copy(portal, ret, player)
|
connect_portal(portal, ret, player)
|
||||||
ret.sanc_portal = world.sanc_portal
|
ret.sanc_portal = world.sanc_portal
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
|
|||||||
35
README.md
35
README.md
@@ -60,9 +60,10 @@ Key drop location are added to the pool. The keys normally found there are added
|
|||||||
### Mixed Travel (--mixed_travel value)
|
### Mixed Travel (--mixed_travel value)
|
||||||
|
|
||||||
Due to Hammerjump, Hovering in PoD Arena, and the Mire Big Key Chest bomb jump two sections of a supertile that are
|
Due to Hammerjump, Hovering in PoD Arena, and the Mire Big Key Chest bomb jump two sections of a supertile that are
|
||||||
otherwise unconnected logically can be reach using these glitches. To prevent the player from unintentionally
|
otherwise unconnected logically can be reached using these glitches. To prevent the player from unintentionally changing
|
||||||
|
dungeons while doing these tricks, you may use one of the following options.
|
||||||
|
|
||||||
#### Prevent
|
#### Prevent (default)
|
||||||
|
|
||||||
Rails are added the 3 spots to prevent this tricks. This setting is recommend for those learning crossed dungeon mode to
|
Rails are added the 3 spots to prevent this tricks. This setting is recommend for those learning crossed dungeon mode to
|
||||||
learn what is dangerous and what is not. No logic seeds ignore this setting.
|
learn what is dangerous and what is not. No logic seeds ignore this setting.
|
||||||
@@ -73,17 +74,17 @@ The rooms are left alone and it is up to the discretion of the player whether to
|
|||||||
|
|
||||||
#### Force
|
#### Force
|
||||||
|
|
||||||
The two disjointed sections are forced to be in the same dungeon but never logically required to complete that game.
|
The two disjointed sections are forced to be in the same dungeon but the glitches are never logically required to complete that game.
|
||||||
|
|
||||||
### Standardize Palettes (--standardize_palettes)
|
### Standardize Palettes (--standardize_palettes)
|
||||||
No effect if door shuffle is not on crossed
|
No effect if door shuffle is not on crossed
|
||||||
|
|
||||||
#### Standardize
|
#### Standardize (default)
|
||||||
Rooms in the same dungeon have their palettes changed to match. Hyrule Castle is split between Sewer and HC palette.
|
Rooms in the same dungeon have their palettes changed to match. Hyrule Castle is split between Sewer and HC palette.
|
||||||
Rooms adjacent to sanctuary get their coloring to match sanc.
|
Rooms adjacent to sanctuary get their coloring to match the Sanctuary's original palette.
|
||||||
|
|
||||||
#### Original
|
#### Original
|
||||||
Room keep their original palettes.
|
Rooms/supertiles keep their original palettes.
|
||||||
|
|
||||||
|
|
||||||
## Map/Compass/Small Key/Big Key shuffle (aka Keysanity)
|
## Map/Compass/Small Key/Big Key shuffle (aka Keysanity)
|
||||||
@@ -121,13 +122,31 @@ Use to batch generate multiple seeds with same settings. If a seed number is pro
|
|||||||
Show the help message and exit.
|
Show the help message and exit.
|
||||||
|
|
||||||
```
|
```
|
||||||
--door_shuffle
|
--door_shuffle <mode>
|
||||||
```
|
```
|
||||||
|
|
||||||
For specifying the door shuffle you want as above. (default: basic)
|
For specifying the door shuffle you want as above. (default: basic)
|
||||||
|
|
||||||
```
|
```
|
||||||
--intensity
|
--intensity <number>
|
||||||
```
|
```
|
||||||
|
|
||||||
For specifying the door shuffle intensity level you want as above. (default: 2)
|
For specifying the door shuffle intensity level you want as above. (default: 2)
|
||||||
|
|
||||||
|
```
|
||||||
|
--keydropshuffle
|
||||||
|
```
|
||||||
|
|
||||||
|
Include mobs and pots drop in the item pool. (default: not enabled)
|
||||||
|
|
||||||
|
```
|
||||||
|
--mixed_travel <mode>
|
||||||
|
```
|
||||||
|
|
||||||
|
How to handle certain glitches in crossed dungeon mode. (default: prevent)
|
||||||
|
|
||||||
|
```
|
||||||
|
--standardize_palettes (mode)
|
||||||
|
```
|
||||||
|
|
||||||
|
Whether to standardize dungeon palettes in crossed dungeon mode. (default: standardize)
|
||||||
168
RELEASENOTES.md
168
RELEASENOTES.md
@@ -1,173 +1,21 @@
|
|||||||
# New Features
|
# New Features
|
||||||
|
|
||||||
## Lobby shuffle added as Intensity level 3
|
|
||||||
|
|
||||||
* Standard notes:
|
|
||||||
* The sanctuary is vanilla, and will be missing the exit door until Zelda is rescued
|
|
||||||
* In entrance shuffle the hyrule castle left and right exit door will be missing until Zelda is rescued. This
|
|
||||||
replaces the rails that used to block those lobby exits
|
|
||||||
* In non-entrance shuffle, Agahnims tower can be in logic if you have cape and/or Master sword, but you are never
|
|
||||||
required to beat Agahnim 1 until Zelda is rescued.
|
|
||||||
* Open notes:
|
|
||||||
* The Sanctuary is limited to be in a LW dungeon unless you have ER Crossed or higher enabled
|
|
||||||
* Mirroring from the Sanctuary to the new "Sanctuary" lobby is now in logic, as is exiting there.
|
|
||||||
* In ER crossed or higher, if the Sanctuary is in the Dark World, Link starts as Bunny there until the Moon Pearl
|
|
||||||
is found. Nothing inside that dungeon is in logic until the Moon Pearl is found. (Unless it is a multi-entrance
|
|
||||||
dungeon that you can access from some LW entrance)
|
|
||||||
* Lobby list is found in the spoiler
|
|
||||||
* Exits for Multi-entrance dungeons after beating bosses now makes more sense. Generally you'll exit from a entrance
|
|
||||||
from which the boss can logically be reached. If there are multiple, ones that do not lead to regions only accessible
|
|
||||||
by connector are preferred. The exit is randomly chosen if there's no obvious preference. However, In certain poor
|
|
||||||
cases like Skull Woods in ER, sometimes an exit is chosen not because you can reach the boss from there, but to
|
|
||||||
prevent a potential forced S&Q.
|
|
||||||
* Palette changes:
|
|
||||||
* Certain doors/transition no longer have an effect on the palette choice (dead ends mostly or just bridges)
|
|
||||||
* Sanctuary palette used on the adjacent rooms to Sanctuary (Sanctuary stays the dungeon color for now)
|
|
||||||
* Sewer palette comes back for part of Hyrule Castle for areas "near" the sewer dropdown
|
|
||||||
* There is a setting to keep original palettes (--standardize_palettes original)
|
|
||||||
* Known issues:
|
|
||||||
* Palettes aren't perfect
|
|
||||||
* Some ugly colors
|
|
||||||
* Invisible floors can be see in many palettes
|
|
||||||
|
|
||||||
## Shopsanity
|
## Shopsanity
|
||||||
|
|
||||||
--shopsanity added. This adds 29 shop locations (9 more in retro) to the general and location pool.
|
--shopsanity added. This adds 29 shop locations (9 more in retro) to the general and location pool.
|
||||||
|
|
||||||
** **Todo** **: add more info here.
|
** **Todo** **: add more info here.
|
||||||
|
|
||||||
## Key Drop Shuffle
|
|
||||||
|
|
||||||
--keydropshuffle added. This add 33 new locations to the game where keys are found under pots
|
|
||||||
and where enemies drop keys. This includes 32 small key location and the ball and chain guard who normally drop the HC
|
|
||||||
Big Key.
|
|
||||||
|
|
||||||
* Overall location count updated
|
|
||||||
* Setting mentioned in spoiler
|
|
||||||
* Minor change: if a key is Universal or for that dungeon, then if will use the old mechanics of picking up the key without
|
|
||||||
an entire pose and should be obtainable with the hookshot or boomerang as before
|
|
||||||
|
|
||||||
## --mixed_travel setting
|
|
||||||
* Due to Hammerjump, Hovering in PoD Arena, and the Mire Big Key Chest bomb jump two sections of a supertile that are
|
|
||||||
otherwise unconnected logically can be reach using these glitches. To prevent the player from unintentionally
|
|
||||||
* prevent: Rails are added the 3 spots to prevent this tricks. This setting is recommend for those learning
|
|
||||||
crossed dungeon mode to learn what is dangerous and what is not. No logic seeds ignore this setting.
|
|
||||||
* allow: The rooms are left alone and it is up to the discretion of the player whether to use these tricks or not.
|
|
||||||
* force: The two disjointed sections are forced to be in the same dungeon but never logically required to complete that game.
|
|
||||||
|
|
||||||
## Keysanity menu redesign
|
|
||||||
|
|
||||||
Redesign of Keysanity Menu complete for crossed dungeon and moved out of experimental.
|
|
||||||
* First screen about Big Keys and Small Keys
|
|
||||||
* 1st Column: The map is required for information about the Big Key
|
|
||||||
* If you don't have the map, it'll be blank until you obtain the Big Key
|
|
||||||
* If have the map:
|
|
||||||
* 0 indicates there is no Big Key for that dungeon
|
|
||||||
* A red symbol indicates the Ball N Chain guard has the big key for that dungeon (does not apply in
|
|
||||||
--keydropshuffle)
|
|
||||||
* Blank if there a big key but you haven't found it yet
|
|
||||||
* 2nd Column displays the current number of keys for that dungeon. Suppressed in retro (always blank)
|
|
||||||
* 3rd Column only display if you have the map. It shows the number of keys left to collect for that dungeon. If
|
|
||||||
--keydropshuffle is off, this does not count key drops. If on, it does.
|
|
||||||
* (Note: the key columns can display up to 36 using the letters A-Z after 9)
|
|
||||||
* Second screen about Maps / Compass
|
|
||||||
* 1st Column: indicate if you have foudn the map of not for that dungeon
|
|
||||||
* 2nd and 3rd Column: You must have the compass to see these columns. A two-digit display that show you how
|
|
||||||
many chests are left in the dungeon. If -keydropshuffle is off, this does not count key drop. If on, it does.
|
|
||||||
|
|
||||||
## Potshuffle by compiling
|
|
||||||
|
|
||||||
Same flag as before but uses python logic written by compiling instead of the enemizer logic-less version. Needs some
|
|
||||||
testing to verify logic is all good.
|
|
||||||
|
|
||||||
## Other features
|
|
||||||
|
|
||||||
### Spoiler log improvements
|
|
||||||
|
|
||||||
* In crossed mode, the new dungeon is listed along with the location designated by a '@' sign
|
|
||||||
* Random gt crystals and ganon crystal are noted in the settings for better reproduction of seeds
|
|
||||||
|
|
||||||
### Experimental features
|
|
||||||
|
|
||||||
* Only the item counter is currently experimental
|
|
||||||
* Item counter is suppressed in Triforce Hunt
|
|
||||||
|
|
||||||
|
|
||||||
#### Temporary debug features
|
|
||||||
|
|
||||||
* Removed the red square in the upper right corner of the hud if the castle gate is closed
|
|
||||||
|
|
||||||
# Bug Fixes
|
# Bug Fixes
|
||||||
|
|
||||||
* 2.0.20u
|
* 0.3.0.1-u
|
||||||
* Problem with Desert Wall not being pre-opened in intensity 3 fixed
|
* Problem with lobbies on re-rolls corrected
|
||||||
* 2.0.19u
|
* Potential playthrough problem addressed
|
||||||
* Generation improvement
|
* 0.3.0.0-u
|
||||||
* Possible fix for shop vram corruption
|
* Generation improvements. Basic >95% success. Crossed >80% success.
|
||||||
* The Cane of Byrna does not count as a chest key anymore
|
* Possible increased generation times as certain generation problem tries a partial re-roll
|
||||||
* 2.0.18u
|
|
||||||
* Generation improvements
|
|
||||||
* Bombs/Dash doors more consistent with the amount in vanilla.
|
|
||||||
* 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
|
|
||||||
* Fixed a problem with keydropshuffle thinking certain progression items are keys
|
|
||||||
* A couple of inverted rules fixed
|
|
||||||
* A more accurate count of which locations are blocked by teh big key in Ganon's Tower
|
|
||||||
* Updated base rom to 31.0.7 (includes potential hera basement cage fix)
|
|
||||||
* 2.0.15u
|
|
||||||
* Allow Aga Tower lobby door as a a paired keydoor (typo)
|
|
||||||
* Fix portal check for multi-entrance dungeons
|
|
||||||
* 2.0.14u
|
|
||||||
* Removal of key doors no longer messes up certain lobbies
|
|
||||||
* Fixed ER entrances when Desert Back is a connector
|
|
||||||
* 2.0.13u
|
|
||||||
* Minor portal re-work for certain logic and spoiler information
|
|
||||||
* Repaired certain exits wrongly affected by Sanctuary placement (ER crossed + intensity 3)
|
|
||||||
* Fix for inverted ER + intensity 3
|
|
||||||
* Fix for current small keys missing on keysanity menu
|
|
||||||
* Logic added for cases where you can flood Swamp Trench 1 before finding flippers and lock yourself out of getting
|
|
||||||
something behind the trench that leads to the flippers
|
|
||||||
* 2.0.12u
|
|
||||||
* Another fix for animated tiles (fairy fountains)
|
|
||||||
* GT Big Key stat fixed on credits
|
|
||||||
* Any denomination of rupee 20 or below can be removed to make room for Crossed Dungeon's extra dungeon items. This
|
|
||||||
helps retro generate more often.
|
|
||||||
* Fix for TR Lobbies in intensity 3 and ER shuffles that was causing a hardlock
|
|
||||||
* Standard ER logic revised for lobby shuffle and rain state considerations.
|
|
||||||
* 2.0.11u
|
|
||||||
* Fix output path setting in settings.json
|
|
||||||
* Fix trock entrances when intensity <= 2
|
|
||||||
* 2.0.10u
|
|
||||||
* Fix POD, TR, GT and SKULL 3 entrances if sanc ends up in that dungeon in crossed ER+
|
|
||||||
* TR Lobbies that need a bomb and can be entered before bombing should be pre-opened
|
|
||||||
* Animated tiles are loaded correctly in lobbies
|
|
||||||
* If a wallmaster grabs you and the lobby is dark, the lamp turns on now
|
|
||||||
* Certain key rules no longer override item requirements (e.g. Somaria behind TR Hub)
|
|
||||||
* Old Man Cave is correctly one way in the graph
|
|
||||||
* Some key logic fixes
|
|
||||||
* 2.0.9-u
|
|
||||||
* /missing command in MultiClient fixed
|
|
||||||
* 2.0.8-u
|
|
||||||
* Player sprite disappears after picking up a key drop in keydropshuffle
|
|
||||||
* Sewers and Hyrule Castle compass problems
|
|
||||||
* Double count of the Hera Basement Cage item (both overall and compass)
|
|
||||||
* Unnecessary/inconsistent rug cutoff
|
|
||||||
* TR Crystal Maze thought you get through backwards without Somaria
|
|
||||||
* Ensure Thieves Attic Window area can always be reached
|
|
||||||
* Fixed where HC big key was not counted
|
|
||||||
* Prior fixes
|
|
||||||
* Fixed a situation where logic did not account properly for Big Key doors in standard Hyrule Castle
|
|
||||||
* Fixed a problem ER shuffle generation that did not account for lobbies moving around
|
|
||||||
* Fixed a problem with camera unlock (GT Mimics and Mire Minibridge)
|
|
||||||
* Fixed a problem with bad-pseudo layer at PoD map Balcony (unable to hit switch with Bomb)
|
|
||||||
* Fixed a problem with the Ganon hint when hints are turned off
|
|
||||||
|
|
||||||
# Known Issues
|
# Known Issues
|
||||||
|
|
||||||
* Multiworld = /missing command not working
|
* Potential keylocks in multi-entrance dungeons
|
||||||
* Potenial keylocks in multi-entrance dungeons
|
* Incorrect vanilla key logic for Mire
|
||||||
* Incorrect vanilla keylogic for Mire
|
|
||||||
* ER - Potential for Skull Woods West to be completely inaccessible in non-beatable logic
|
|
||||||
15
Rom.py
15
Rom.py
@@ -284,14 +284,23 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, random_sprite_
|
|||||||
with open(options_path, 'w') as f:
|
with open(options_path, 'w') as f:
|
||||||
json.dump(options, f)
|
json.dump(options, f)
|
||||||
|
|
||||||
subprocess.check_call([os.path.abspath(enemizercli),
|
try:
|
||||||
|
subprocess.run([os.path.abspath(enemizercli),
|
||||||
'--rom', baserom_path,
|
'--rom', baserom_path,
|
||||||
'--seed', str(world.rom_seeds[player]),
|
'--seed', str(world.rom_seeds[player]),
|
||||||
'--base', basepatch_path,
|
'--base', basepatch_path,
|
||||||
'--randomizer', randopatch_path,
|
'--randomizer', randopatch_path,
|
||||||
'--enemizer', options_path,
|
'--enemizer', options_path,
|
||||||
'--output', enemizer_output_path],
|
'--output', enemizer_output_path],
|
||||||
cwd=os.path.dirname(enemizercli), stdout=subprocess.DEVNULL)
|
cwd=os.path.dirname(enemizercli),
|
||||||
|
check=True,
|
||||||
|
capture_output=True)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
from Main import EnemizerError
|
||||||
|
enemizerMsg = world.fish.translate("cli","cli","Enemizer returned exit code: ") + str(e.returncode) + "\n"
|
||||||
|
enemizerMsg += world.fish.translate("cli","cli","enemizer.nothing.applied")
|
||||||
|
logging.error(f'Enemizer error output: {e.stderr.decode("utf-8")}\n')
|
||||||
|
raise EnemizerError(enemizerMsg)
|
||||||
|
|
||||||
with open(enemizer_basepatch_path, 'r') as f:
|
with open(enemizer_basepatch_path, 'r') as f:
|
||||||
for patch in json.load(f):
|
for patch in json.load(f):
|
||||||
@@ -690,7 +699,7 @@ def patch_rom(world, rom, player, team, enemized):
|
|||||||
for name, pair in boss_indicator.items():
|
for name, pair in boss_indicator.items():
|
||||||
dungeon_id, boss_door = pair
|
dungeon_id, boss_door = pair
|
||||||
opposite_door = world.get_door(boss_door, player).dest
|
opposite_door = world.get_door(boss_door, player).dest
|
||||||
if opposite_door and opposite_door.roomIndex > -1:
|
if opposite_door and isinstance(opposite_door, Door) and opposite_door.roomIndex > -1:
|
||||||
dungeon_name = opposite_door.entrance.parent_region.dungeon.name
|
dungeon_name = opposite_door.entrance.parent_region.dungeon.name
|
||||||
dungeon_id = boss_indicator[dungeon_name][0]
|
dungeon_id = boss_indicator[dungeon_name][0]
|
||||||
rom.write_byte(0x13f000+dungeon_id, opposite_door.roomIndex)
|
rom.write_byte(0x13f000+dungeon_id, opposite_door.roomIndex)
|
||||||
|
|||||||
@@ -254,6 +254,12 @@ def create_rooms(world, player):
|
|||||||
world.get_room(0xc0, player).change(0, DoorKind.Normal) # fix this kill room if enemizer is on
|
world.get_room(0xc0, player).change(0, DoorKind.Normal) # fix this kill room if enemizer is on
|
||||||
|
|
||||||
|
|
||||||
|
def reset_rooms(world, player):
|
||||||
|
world.rooms = [x for x in world.rooms if x.player != player]
|
||||||
|
world._room_cache.clear()
|
||||||
|
create_rooms(world, player)
|
||||||
|
|
||||||
|
|
||||||
class Room(object):
|
class Room(object):
|
||||||
def __init__(self, player, index, address):
|
def __init__(self, player, index, address):
|
||||||
self.player = player
|
self.player = player
|
||||||
|
|||||||
17
Utils.py
17
Utils.py
@@ -36,6 +36,9 @@ def is_bundled():
|
|||||||
return getattr(sys, 'frozen', False)
|
return getattr(sys, 'frozen', False)
|
||||||
|
|
||||||
def local_path(path):
|
def local_path(path):
|
||||||
|
# just do stuff here and bail
|
||||||
|
return os.path.join(".", path)
|
||||||
|
|
||||||
if local_path.cached_path is not None:
|
if local_path.cached_path is not None:
|
||||||
return os.path.join(local_path.cached_path, path)
|
return os.path.join(local_path.cached_path, path)
|
||||||
|
|
||||||
@@ -51,6 +54,9 @@ def local_path(path):
|
|||||||
local_path.cached_path = None
|
local_path.cached_path = None
|
||||||
|
|
||||||
def output_path(path):
|
def output_path(path):
|
||||||
|
# just do stuff here and bail
|
||||||
|
return os.path.join(".", path)
|
||||||
|
|
||||||
if output_path.cached_path is not None:
|
if output_path.cached_path is not None:
|
||||||
return os.path.join(output_path.cached_path, path)
|
return os.path.join(output_path.cached_path, path)
|
||||||
|
|
||||||
@@ -61,15 +67,7 @@ def output_path(path):
|
|||||||
# has been packaged, so cannot use CWD for output.
|
# has been packaged, so cannot use CWD for output.
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
#windows
|
#windows
|
||||||
import ctypes.wintypes
|
documents = os.path.join(os.path.expanduser("~"),"Documents")
|
||||||
CSIDL_PERSONAL = 5 # My Documents
|
|
||||||
SHGFP_TYPE_CURRENT = 0 # Get current, not default value
|
|
||||||
|
|
||||||
buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
|
|
||||||
ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf)
|
|
||||||
|
|
||||||
documents = buf.value
|
|
||||||
|
|
||||||
elif sys.platform == 'darwin':
|
elif sys.platform == 'darwin':
|
||||||
from AppKit import NSSearchPathForDirectoriesInDomains # pylint: disable=import-error
|
from AppKit import NSSearchPathForDirectoriesInDomains # pylint: disable=import-error
|
||||||
# http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
|
# http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
|
||||||
@@ -655,4 +653,3 @@ if __name__ == '__main__':
|
|||||||
# room_palette_data(old_rom=sys.argv[1])
|
# room_palette_data(old_rom=sys.argv[1])
|
||||||
# extract_data_from_us_rom(sys.argv[1])
|
# extract_data_from_us_rom(sys.argv[1])
|
||||||
extract_data_from_jp_rom(sys.argv[1])
|
extract_data_from_jp_rom(sys.argv[1])
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user