Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LADX: Zig's Entrance Rando PR with some cleaning and merging #3578

Closed
wants to merge 45 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
2c47b08
wip
zig-for Mar 26, 2023
356b65a
Outside the hole, technically
zig-for Mar 26, 2023
1674d40
gen
zig-for Mar 27, 2023
7c46a8c
cleanup
zig-for Mar 27, 2023
ac1f064
fix castle button
zig-for Mar 27, 2023
3e1ca6a
note some bad seeds
zig-for Mar 28, 2023
e29c02e
castle upper right isn't a connector lol
zig-for Mar 31, 2023
90b736a
Merge remote-tracking branch 'ap/main' into zig/er_2
zig-for Apr 1, 2023
8f8ac79
foobar
zig-for Apr 1, 2023
fb8eaf6
it's working
zig-for Apr 3, 2023
5b09d35
fix D7
zig-for Apr 3, 2023
ef3bcf9
connect d7
zig-for Apr 3, 2023
ba96120
allow secondary entrance type
zig-for Apr 4, 2023
20a0e63
Merge remote-tracking branch 'ap/main' into zig/er_2
zig-for Jul 29, 2023
06357fe
fix
zig-for Jul 29, 2023
db5cd2d
Merge remote-tracking branch 'ap/main' into zig/er_2
zig-for Aug 13, 2023
3f5c4dc
hooray
zig-for Aug 13, 2023
b839812
foobar
zig-for Aug 14, 2023
62e8a1e
fixed start item placer
zig-for Aug 18, 2023
9dde58e
fixed code
zig-for Aug 18, 2023
2db6455
add new ER logic, add ability to turn off tarin gift
zig-for Aug 20, 2023
bf0240c
hmm
zig-for Aug 20, 2023
82675e1
undo
zig-for Aug 20, 2023
73bae2b
wip
zig-for Aug 20, 2023
cb092ee
fixes
zig-for Aug 20, 2023
47ae932
turn off dev mode
zig-for Aug 20, 2023
b6b7438
comment
zig-for Aug 20, 2023
0c54ad3
remove prints
zig-for Aug 27, 2023
9027477
Merge branch 'main' into zig/er_2
zig-for Nov 13, 2023
3dc307e
Update Fill.py
zig-for Nov 13, 2023
af43210
Merge remote-tracking branch 'ap/main' into zig/er_2
zig-for Dec 3, 2023
3cd804b
ER
zig-for Dec 3, 2023
db5b6f1
fix warp code
zig-for Feb 6, 2024
6b5c551
Merge branch 'main' into zig/er_2
zig-for Feb 14, 2024
8cfb383
Merge remote-tracking branch 'refs/remotes/sw/main' into zig/er_2
ScipioWright Jun 22, 2024
49a9301
Mostly works, just gotta deal with memory leak issue
ScipioWright Jun 22, 2024
e358d3e
Make suggested change for loop in randomize entrances
ScipioWright Jun 22, 2024
d3bd985
newline at end of file
ScipioWright Jun 26, 2024
c544420
Newline at end of file
ScipioWright Jun 26, 2024
546640c
Merge branch 'main' into ladx-er
ScipioWright Jun 26, 2024
456d95a
Merge branch 'main' into ladx-er
ScipioWright Jul 1, 2024
337d99b
Merge branch 'main' into ladx-er
ScipioWright Jul 10, 2024
8dfcccb
Merge branch 'main' into ladx-er
ScipioWright Jul 24, 2024
ac74e92
Merge branch 'main' into ladx-er
ScipioWright Aug 4, 2024
955b3cd
Merge branch 'main' into ladx-er
ScipioWright Sep 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion worlds/ladx/Common.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
LINKS_AWAKENING = "Links Awakening DX"
BASE_ID = 10000000
BASE_ID = 10000000
10 changes: 8 additions & 2 deletions worlds/ladx/LADXR/entranceInfo.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections import defaultdict

class EntranceInfo:
def __init__(self, room, alt_room=None, *, type=None, dungeon=None, index=None, instrument_room=None, target=None):
Expand Down Expand Up @@ -48,12 +49,12 @@ def __init__(self, room, alt_room=None, *, type=None, dungeon=None, index=None,
"d2": EntranceInfo(0x24, target=0x136, dungeon=2, instrument_room=0x12A),
"moblin_cave": EntranceInfo(0x35, target=0x2f0, type="single"),
"photo_house": EntranceInfo(0x37, target=0x2b5, type="dummy"),
"mambo": EntranceInfo(0x2A, target=0x2fd, type="single"),
"mambo": EntranceInfo(0x2A, target=0x2fd, type="water"),
"d4": EntranceInfo(0x2B, "Alt2B", target=0x17a, dungeon=4, index=0, instrument_room=0x162),
# TODO
# "d4_connector": EntranceInfo(0x2B, "Alt2B", index=1),
# "d4_connector_exit": EntranceInfo(0x2D),
"heartpiece_swim_cave": EntranceInfo(0x2E, target=0x1f2, type="single"),
"heartpiece_swim_cave": EntranceInfo(0x2E, target=0x1f2, type="water"),
"raft_return_exit": EntranceInfo(0x2F, target=0x1e7, type="connector"),
"raft_house": EntranceInfo(0x3F, target=0x2b0, type="insanity"),
"raft_return_enter": EntranceInfo(0x8F, target=0x1f7, type="connector"),
Expand Down Expand Up @@ -134,3 +135,8 @@ def __init__(self, room, alt_room=None, *, type=None, dungeon=None, index=None,
"animal_cave": EntranceInfo(0xCD, target=0x2f7, type="single", index=0),
"desert_cave": EntranceInfo(0xCF, target=0x1f9, type="single"),
}

entrances_by_type = defaultdict(list)

for name, entrance in ENTRANCE_INFO.items():
entrances_by_type[entrance.type or "dungeon"].append(name)
3 changes: 3 additions & 0 deletions worlds/ladx/LADXR/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,13 @@ def generateRom(args, world: "LinksAwakeningWorld"):
assembler.const("wDropBombSpawnCount", 0xDE12)
assembler.const("wLinkSpawnDelay", 0xDE13)

assembler.const("wOverworldRoomStatus", 0xD800)

#assembler.const("HARDWARE_LINK", 1)
assembler.const("HARD_MODE", 1 if world.ladxr_settings.hardmode != "none" else 0)

patches.core.cleanup(rom)
patches.core.fixD7exit(rom)
patches.save.singleSaveSlot(rom)
patches.phone.patchPhone(rom)
patches.photographer.fixPhotographer(rom)
Expand Down
9 changes: 5 additions & 4 deletions worlds/ladx/LADXR/itempool.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .locations.items import *
from typing import Dict


DEFAULT_ITEM_POOL = {
Expand Down Expand Up @@ -165,12 +166,12 @@ def __setup(self, logic, settings):
for n in range(9):
self.remove("MAP%d" % (n + 1))
self.remove("COMPASS%d" % (n + 1))
self.add("KEY%d" % (n +1))
self.add("NIGHTMARE_KEY%d" % (n +1))
self.add("KEY%d" % (n + 1))
self.add("NIGHTMARE_KEY%d" % (n + 1))
if settings.owlstatues in ("none", "overworld"):
for n in range(9):
self.remove("STONE_BEAK%d" % (n + 1))
self.add("KEY%d" % (n +1))
self.add("KEY%d" % (n + 1))

# if settings.dungeon_items == 'keysy':
# for n in range(9):
Expand Down Expand Up @@ -274,5 +275,5 @@ def __randomizeRupees(self, options, rnd):
self.add(new_item)
self.remove(remove_item)

def toDict(self):
def toDict(self) -> Dict[str, int]:
return self.__pool.copy()
3 changes: 0 additions & 3 deletions worlds/ladx/LADXR/locations/itemInfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ def location(self):
def setLocation(self, location):
self._location = location

def getOptions(self):
return self.OPTIONS

def configure(self, options):
pass

Expand Down
11 changes: 0 additions & 11 deletions worlds/ladx/LADXR/locations/startItem.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,12 @@


class StartItem(DroppedKey):
# We need to give something here that we can use to progress.
# FEATHER
OPTIONS = [SWORD, SHIELD, POWER_BRACELET, OCARINA, BOOMERANG, MAGIC_ROD, TAIL_KEY, SHOVEL, HOOKSHOT, PEGASUS_BOOTS, MAGIC_POWDER, BOMB]
MULTIWORLD = False

def __init__(self):
super().__init__(0x2A3)
self.give_bowwow = False

def configure(self, options):
if options.bowwow != 'normal':
# When we have bowwow mode, we pretend to be a sword for logic reasons
self.OPTIONS = [SWORD]
self.give_bowwow = True
if options.randomstartlocation and options.entranceshuffle != 'none':
self.OPTIONS.append(FLIPPERS)

def patch(self, rom, option, *, multiworld=None):
assert multiworld is None

Expand Down
149 changes: 0 additions & 149 deletions worlds/ladx/LADXR/logic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,152 +133,3 @@ def __recursiveFindAll(self, location):
self.__recursiveFindAll(connection)
for connection, requirement in location.gated_connections:
self.__recursiveFindAll(connection)


class MultiworldLogic:
def __init__(self, settings, rnd=None, *, world_setups=None):
assert rnd or world_setups
self.worlds = []
self.start = Location()
self.location_list = [self.start]
self.iteminfo_list = []

for n in range(settings.multiworld):
options = settings.multiworld_settings[n]
world = None
if world_setups:
world = Logic(options, world_setup=world_setups[n])
else:
for cnt in range(1000): # Try the world setup in case entrance randomization generates unsolvable logic
world_setup = WorldSetup()
world_setup.randomize(options, rnd)
world = Logic(options, world_setup=world_setup)
if options.entranceshuffle not in ("advanced", "expert", "insanity") or len(world.iteminfo_list) == sum(itempool.ItemPool(options, rnd).toDict().values()):
break

for ii in world.iteminfo_list:
ii.world = n

req_done_set = set()
for loc in world.location_list:
loc.simple_connections = [(target, addWorldIdToRequirements(req_done_set, n, req)) for target, req in loc.simple_connections]
loc.gated_connections = [(target, addWorldIdToRequirements(req_done_set, n, req)) for target, req in loc.gated_connections]
loc.items = [MultiworldItemInfoWrapper(n, options, ii) for ii in loc.items]
self.iteminfo_list += loc.items

self.worlds.append(world)
self.start.simple_connections += world.start.simple_connections
self.start.gated_connections += world.start.gated_connections
self.start.items += world.start.items
world.start.items.clear()
self.location_list += world.location_list

self.entranceMapping = None


class MultiworldMetadataWrapper:
def __init__(self, world, metadata):
self.world = world
self.metadata = metadata

@property
def name(self):
return self.metadata.name

@property
def area(self):
return "P%d %s" % (self.world + 1, self.metadata.area)


class MultiworldItemInfoWrapper:
def __init__(self, world, configuration_options, target):
self.world = world
self.world_count = configuration_options.multiworld
self.target = target
self.dungeon_items = configuration_options.dungeon_items
self.MULTIWORLD_OPTIONS = None
self.item = None

@property
def nameId(self):
return self.target.nameId

@property
def forced_item(self):
if self.target.forced_item is None:
return None
if "_W" in self.target.forced_item:
return self.target.forced_item
return "%s_W%d" % (self.target.forced_item, self.world)

@property
def room(self):
return self.target.room

@property
def metadata(self):
return MultiworldMetadataWrapper(self.world, self.target.metadata)

@property
def MULTIWORLD(self):
return self.target.MULTIWORLD

def read(self, rom):
world = rom.banks[0x3E][0x3300 + self.target.room] if self.target.MULTIWORLD else self.world
return "%s_W%d" % (self.target.read(rom), world)

def getOptions(self):
if self.MULTIWORLD_OPTIONS is None:
options = self.target.getOptions()
if self.target.MULTIWORLD and len(options) > 1:
self.MULTIWORLD_OPTIONS = []
for n in range(self.world_count):
self.MULTIWORLD_OPTIONS += ["%s_W%d" % (t, n) for t in options if n == self.world or self.canMultiworld(t)]
else:
self.MULTIWORLD_OPTIONS = ["%s_W%d" % (t, self.world) for t in options]
return self.MULTIWORLD_OPTIONS

def patch(self, rom, option):
idx = option.rfind("_W")
world = int(option[idx+2:])
option = option[:idx]
if not self.target.MULTIWORLD:
assert self.world == world
self.target.patch(rom, option)
else:
self.target.patch(rom, option, multiworld=world)

# Return true if the item is allowed to be placed in any world, or false if it is
# world specific for this check.
def canMultiworld(self, option):
if self.dungeon_items in {'', 'smallkeys'}:
if option.startswith("MAP"):
return False
if option.startswith("COMPASS"):
return False
if option.startswith("STONE_BEAK"):
return False
if self.dungeon_items in {'', 'localkeys'}:
if option.startswith("KEY"):
return False
if self.dungeon_items in {'', 'localkeys', 'localnightmarekey', 'smallkeys'}:
if option.startswith("NIGHTMARE_KEY"):
return False
return True

@property
def location(self):
return self.target.location

def __repr__(self):
return "W%d:%s" % (self.world, repr(self.target))


def addWorldIdToRequirements(req_done_set, world, req):
if req is None:
return None
if isinstance(req, str):
return "%s_W%d" % (req, world)
if req in req_done_set:
return req
return req.copyWithModifiedItemNames(lambda item: "%s_W%d" % (item, world))
32 changes: 31 additions & 1 deletion worlds/ladx/LADXR/logic/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,33 @@
from .requirements import hasConsumableRequirement, OR
from ..locations.itemInfo import ItemInfo

from enum import Enum

class LocationType(Enum):
Unknown = 0
Overworld = 1
Dungeon = 2
Indoor = 3

class Location:
def __init__(self, name=None, dungeon=None):
def __init__(self, name=None, location_type=LocationType.Unknown, dungeon=None):
self.name = name
self.items = [] # type: typing.List[ItemInfo]
self.dungeon = dungeon
if self.dungeon != None:
assert location_type == location_type.Unknown or location_type == LocationType.Dungeon
location_type = LocationType.Dungeon

self.location_type = location_type
self.__connected_to = set()
self.simple_connections = []
self.gated_connections = []

def add(self, *item_infos):
if not self.name:
meta = item_infos[0].metadata
self.name = f"{meta.name} ({meta.area})"

for ii in item_infos:
assert isinstance(ii, ItemInfo)
ii.setLocation(self)
Expand Down Expand Up @@ -55,3 +71,17 @@ def connect(self, other, req, *, one_way=False):

def __repr__(self):
return "<%s:%s:%d:%d:%d>" % (self.__class__.__name__, self.dungeon, len(self.items), len(self.simple_connections), len(self.gated_connections))

class OverworldLocation(Location):
def __init__(self, name=None):
assert(name)
Location.__init__(self, name, location_type=LocationType.Overworld)


class IndoorLocation(Location):
def __init__(self, name):
assert(name)
Location.__init__(self, name, location_type=LocationType.Indoor)

class VirtualLocation(Location):
pass
Loading
Loading