Links Awakening: Implement New Game (#1334)
Adds Link's Awakening: DX. Fully imports and forks LADXR, with permission - https://github.com/daid/LADXR
This commit is contained in:
24
worlds/ladx/LADXR/mapgen/locations/base.py
Normal file
24
worlds/ladx/LADXR/mapgen/locations/base.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from ...roomEditor import RoomEditor
|
||||
from ..map import RoomInfo
|
||||
|
||||
|
||||
class LocationBase:
|
||||
MAX_COUNT = 9999
|
||||
|
||||
def __init__(self, room: RoomInfo, x, y):
|
||||
self.room = room
|
||||
self.x = x
|
||||
self.y = y
|
||||
room.locations.append(self)
|
||||
|
||||
def prepare(self, rom):
|
||||
pass
|
||||
|
||||
def update_room(self, rom, re: RoomEditor):
|
||||
pass
|
||||
|
||||
def connect_logic(self, logic_location):
|
||||
raise NotImplementedError(self.__class__)
|
||||
|
||||
def get_item_pool(self):
|
||||
raise NotImplementedError(self.__class__)
|
||||
73
worlds/ladx/LADXR/mapgen/locations/chest.py
Normal file
73
worlds/ladx/LADXR/mapgen/locations/chest.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from .base import LocationBase
|
||||
from ..tileset import solid_tiles, open_tiles, walkable_tiles
|
||||
from ...roomEditor import RoomEditor
|
||||
from ...locations.all import HeartPiece, Chest as ChestLocation
|
||||
import random
|
||||
|
||||
|
||||
class Chest(LocationBase):
|
||||
def __init__(self, room, x, y):
|
||||
super().__init__(room, x, y)
|
||||
room.tiles[x + y * 10] = 0xA0
|
||||
|
||||
def connect_logic(self, logic_location):
|
||||
logic_location.add(ChestLocation(self.room.x + self.room.y * 16))
|
||||
|
||||
def get_item_pool(self):
|
||||
return {None: 1}
|
||||
|
||||
@staticmethod
|
||||
def check_possible(room, reachable_map):
|
||||
# Check if we can potentially place a chest here, and what the best spot would be.
|
||||
options = []
|
||||
for y in range(1, 6):
|
||||
for x in range(1, 9):
|
||||
if room.tiles[x + y * 10 - 10] not in solid_tiles: # Chest needs to be against a "wall" at the top
|
||||
continue
|
||||
if room.tiles[x + y * 10] not in walkable_tiles or room.tiles[x + y * 10 + 10] not in walkable_tiles:
|
||||
continue
|
||||
if room.tiles[x - 1 + y * 10] not in solid_tiles and room.tiles[x - 1 + y * 10 + 10] not in open_tiles:
|
||||
continue
|
||||
if room.tiles[x + 1 + y * 10] not in solid_tiles and room.tiles[x + 1 + y * 10 + 10] not in open_tiles:
|
||||
continue
|
||||
idx = room.x * 10 + x + (room.y * 8 + y) * reachable_map.w
|
||||
if reachable_map.area[idx] == -1:
|
||||
continue
|
||||
options.append((reachable_map.distance[idx], x, y))
|
||||
if not options:
|
||||
return None
|
||||
options.sort(reverse=True)
|
||||
options = [(x, y) for d, x, y in options if d > options[0][0] - 4]
|
||||
return random.choice(options)
|
||||
|
||||
|
||||
class FloorItem(LocationBase):
|
||||
def __init__(self, room, x, y):
|
||||
super().__init__(room, x, y)
|
||||
|
||||
def update_room(self, rom, re: RoomEditor):
|
||||
re.entities.append((self.x, self.y, 0x35))
|
||||
|
||||
def connect_logic(self, logic_location):
|
||||
logic_location.add(HeartPiece(self.room.x + self.room.y * 16))
|
||||
|
||||
def get_item_pool(self):
|
||||
return {None: 1}
|
||||
|
||||
@staticmethod
|
||||
def check_possible(room, reachable_map):
|
||||
# Check if we can potentially place a floor item here, and what the best spot would be.
|
||||
options = []
|
||||
for y in range(1, 7):
|
||||
for x in range(1, 9):
|
||||
if room.tiles[x + y * 10] not in walkable_tiles:
|
||||
continue
|
||||
idx = room.x * 10 + x + (room.y * 8 + y) * reachable_map.w
|
||||
if reachable_map.area[idx] == -1:
|
||||
continue
|
||||
options.append((reachable_map.distance[idx], x, y))
|
||||
if not options:
|
||||
return None
|
||||
options.sort(reverse=True)
|
||||
options = [(x, y) for d, x, y in options if d > options[0][0] - 4]
|
||||
return random.choice(options)
|
||||
107
worlds/ladx/LADXR/mapgen/locations/entrance.py
Normal file
107
worlds/ladx/LADXR/mapgen/locations/entrance.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from ...locations.items import BOMB
|
||||
from .base import LocationBase
|
||||
from ...roomEditor import RoomEditor, Object, ObjectWarp
|
||||
from ...entranceInfo import ENTRANCE_INFO
|
||||
from ...assembler import ASM
|
||||
from .entrance_info import INFO
|
||||
|
||||
|
||||
class Entrance(LocationBase):
|
||||
def __init__(self, room, x, y, entrance_name):
|
||||
super().__init__(room, x, y)
|
||||
self.entrance_name = entrance_name
|
||||
self.entrance_info = ENTRANCE_INFO[entrance_name]
|
||||
self.source_warp = None
|
||||
self.target_warp_idx = None
|
||||
|
||||
self.inside_logic = None
|
||||
|
||||
def prepare(self, rom):
|
||||
info = self.entrance_info
|
||||
re = RoomEditor(rom, info.alt_room if info.alt_room is not None else info.room)
|
||||
self.source_warp = re.getWarps()[info.index if info.index not in (None, "all") else 0]
|
||||
re = RoomEditor(rom, self.source_warp.room)
|
||||
for idx, warp in enumerate(re.getWarps()):
|
||||
if warp.room == info.room or warp.room == info.alt_room:
|
||||
self.target_warp_idx = idx
|
||||
|
||||
def update_room(self, rom, re: RoomEditor):
|
||||
re.objects.append(self.source_warp)
|
||||
|
||||
target = RoomEditor(rom, self.source_warp.room)
|
||||
warp = target.getWarps()[self.target_warp_idx]
|
||||
warp.room = self.room.x | (self.room.y << 4)
|
||||
warp.target_x = self.x * 16 + 8
|
||||
warp.target_y = self.y * 16 + 18
|
||||
target.store(rom)
|
||||
|
||||
def prepare_logic(self, configuration_options, world_setup, requirements_settings):
|
||||
if self.entrance_name in INFO and INFO[self.entrance_name].logic is not None:
|
||||
self.inside_logic = INFO[self.entrance_name].logic(configuration_options, world_setup, requirements_settings)
|
||||
|
||||
def connect_logic(self, logic_location):
|
||||
if self.entrance_name not in INFO:
|
||||
raise RuntimeError(f"WARNING: Logic connection to entrance unmapped! {self.entrance_name}")
|
||||
if self.inside_logic:
|
||||
req = None
|
||||
if self.room.tiles[self.x + self.y * 10] == 0xBA:
|
||||
req = BOMB
|
||||
logic_location.connect(self.inside_logic, req)
|
||||
if INFO[self.entrance_name].exits:
|
||||
return [(name, logic(logic_location)) for name, logic in INFO[self.entrance_name].exits]
|
||||
return None
|
||||
|
||||
def get_item_pool(self):
|
||||
if self.entrance_name not in INFO:
|
||||
return {}
|
||||
return INFO[self.entrance_name].items or {}
|
||||
|
||||
|
||||
class DummyEntrance(LocationBase):
|
||||
def __init__(self, room, x, y):
|
||||
super().__init__(room, x, y)
|
||||
|
||||
def update_room(self, rom, re: RoomEditor):
|
||||
re.objects.append(ObjectWarp(0x01, 0x10, 0x2A3, 0x50, 0x7C))
|
||||
|
||||
def connect_logic(self, logic_location):
|
||||
return
|
||||
|
||||
def get_item_pool(self):
|
||||
return {}
|
||||
|
||||
|
||||
class EggEntrance(LocationBase):
|
||||
def __init__(self, room, x, y):
|
||||
super().__init__(room, x, y)
|
||||
|
||||
def update_room(self, rom, re: RoomEditor):
|
||||
# Setup the warps
|
||||
re.objects.insert(0, Object(5, 3, 0xE1)) # Hide an entrance tile under the tile where the egg will open.
|
||||
re.objects.append(ObjectWarp(0x01, 0x08, 0x270, 0x50, 0x7C))
|
||||
re.entities.append((0, 0, 0xDE)) # egg song event
|
||||
|
||||
egg_inside = RoomEditor(rom, 0x270)
|
||||
egg_inside.getWarps()[0].room = self.room.x
|
||||
egg_inside.store(rom)
|
||||
|
||||
# Fix the alt room layout
|
||||
alt = RoomEditor(rom, "Alt06")
|
||||
tiles = re.getTileArray()
|
||||
tiles[25] = 0xC1
|
||||
tiles[35] = 0xCB
|
||||
alt.buildObjectList(tiles, reduce_size=True)
|
||||
alt.store(rom)
|
||||
|
||||
# Patch which room shows as Alt06
|
||||
rom.patch(0x00, 0x31F1, ASM("cp $06"), ASM(f"cp ${self.room.x:02x}"))
|
||||
rom.patch(0x00, 0x31F5, ASM("ld a, [$D806]"), ASM(f"ld a, [${0xD800 + self.room.x:04x}]"))
|
||||
rom.patch(0x20, 0x2DE6, ASM("cp $06"), ASM(f"cp ${self.room.x:02x}"))
|
||||
rom.patch(0x20, 0x2DEA, ASM("ld a, [$D806]"), ASM(f"ld a, [${0xD800 + self.room.x:04x}]"))
|
||||
rom.patch(0x19, 0x0D1A, ASM("ld hl, $D806"), ASM(f"ld hl, ${0xD800 + self.room.x:04x}"))
|
||||
|
||||
def connect_logic(self, logic_location):
|
||||
return
|
||||
|
||||
def get_item_pool(self):
|
||||
return {}
|
||||
341
worlds/ladx/LADXR/mapgen/locations/entrance_info.py
Normal file
341
worlds/ladx/LADXR/mapgen/locations/entrance_info.py
Normal file
@@ -0,0 +1,341 @@
|
||||
from ...locations.birdKey import BirdKey
|
||||
from ...locations.chest import Chest
|
||||
from ...locations.faceKey import FaceKey
|
||||
from ...locations.goldLeaf import GoldLeaf
|
||||
from ...locations.heartPiece import HeartPiece
|
||||
from ...locations.madBatter import MadBatter
|
||||
from ...locations.song import Song
|
||||
from ...locations.startItem import StartItem
|
||||
from ...locations.tradeSequence import TradeSequenceItem
|
||||
from ...locations.seashell import Seashell
|
||||
from ...locations.shop import ShopItem
|
||||
from ...locations.droppedKey import DroppedKey
|
||||
from ...locations.witch import Witch
|
||||
from ...logic import *
|
||||
from ...logic.dungeon1 import Dungeon1
|
||||
from ...logic.dungeon2 import Dungeon2
|
||||
from ...logic.dungeon3 import Dungeon3
|
||||
from ...logic.dungeon4 import Dungeon4
|
||||
from ...logic.dungeon5 import Dungeon5
|
||||
from ...logic.dungeon6 import Dungeon6
|
||||
from ...logic.dungeon7 import Dungeon7
|
||||
from ...logic.dungeon8 import Dungeon8
|
||||
from ...logic.dungeonColor import DungeonColor
|
||||
|
||||
|
||||
def one_way(loc, req=None):
|
||||
res = Location()
|
||||
loc.connect(res, req, one_way=True)
|
||||
return res
|
||||
|
||||
|
||||
class EntranceInfo:
|
||||
def __init__(self, *, items=None, logic=None, exits=None):
|
||||
self.items = items
|
||||
self.logic = logic
|
||||
self.exits = exits
|
||||
|
||||
|
||||
INFO = {
|
||||
"start_house": EntranceInfo(items={None: 1}, logic=lambda c, w, r: Location().add(StartItem())),
|
||||
"d0": EntranceInfo(
|
||||
items={None: 2, KEY9: 3, MAP9: 1, COMPASS9: 1, STONE_BEAK9: 1, NIGHTMARE_KEY9: 1},
|
||||
logic=lambda c, w, r: DungeonColor(c, w, r).entrance
|
||||
),
|
||||
"d1": EntranceInfo(
|
||||
items={None: 3, KEY1: 3, MAP1: 1, COMPASS1: 1, STONE_BEAK1: 1, NIGHTMARE_KEY1: 1, HEART_CONTAINER: 1, INSTRUMENT1: 1},
|
||||
logic=lambda c, w, r: Dungeon1(c, w, r).entrance
|
||||
),
|
||||
"d2": EntranceInfo(
|
||||
items={None: 3, KEY2: 5, MAP2: 1, COMPASS2: 1, STONE_BEAK2: 1, NIGHTMARE_KEY2: 1, HEART_CONTAINER: 1, INSTRUMENT2: 1},
|
||||
logic=lambda c, w, r: Dungeon2(c, w, r).entrance
|
||||
),
|
||||
"d3": EntranceInfo(
|
||||
items={None: 4, KEY3: 9, MAP3: 1, COMPASS3: 1, STONE_BEAK3: 1, NIGHTMARE_KEY3: 1, HEART_CONTAINER: 1, INSTRUMENT3: 1},
|
||||
logic=lambda c, w, r: Dungeon3(c, w, r).entrance
|
||||
),
|
||||
"d4": EntranceInfo(
|
||||
items={None: 4, KEY4: 5, MAP4: 1, COMPASS4: 1, STONE_BEAK4: 1, NIGHTMARE_KEY4: 1, HEART_CONTAINER: 1, INSTRUMENT4: 1},
|
||||
logic=lambda c, w, r: Dungeon4(c, w, r).entrance
|
||||
),
|
||||
"d5": EntranceInfo(
|
||||
items={None: 5, KEY5: 3, MAP5: 1, COMPASS5: 1, STONE_BEAK5: 1, NIGHTMARE_KEY5: 1, HEART_CONTAINER: 1, INSTRUMENT5: 1},
|
||||
logic=lambda c, w, r: Dungeon5(c, w, r).entrance
|
||||
),
|
||||
"d6": EntranceInfo(
|
||||
items={None: 6, KEY6: 3, MAP6: 1, COMPASS6: 1, STONE_BEAK6: 1, NIGHTMARE_KEY6: 1, HEART_CONTAINER: 1, INSTRUMENT6: 1},
|
||||
logic=lambda c, w, r: Dungeon6(c, w, r, raft_game_chest=False).entrance
|
||||
),
|
||||
"d7": EntranceInfo(
|
||||
items={None: 4, KEY7: 3, MAP7: 1, COMPASS7: 1, STONE_BEAK7: 1, NIGHTMARE_KEY7: 1, HEART_CONTAINER: 1, INSTRUMENT7: 1},
|
||||
logic=lambda c, w, r: Dungeon7(c, w, r).entrance
|
||||
),
|
||||
"d8": EntranceInfo(
|
||||
items={None: 6, KEY8: 7, MAP8: 1, COMPASS8: 1, STONE_BEAK8: 1, NIGHTMARE_KEY8: 1, HEART_CONTAINER: 1, INSTRUMENT8: 1},
|
||||
logic=lambda c, w, r: Dungeon8(c, w, r, back_entrance_heartpiece=False).entrance
|
||||
),
|
||||
|
||||
"writes_cave_left": EntranceInfo(
|
||||
items={None: 2},
|
||||
logic=lambda c, w, r: Location().connect(
|
||||
Location().add(Chest(0x2AE)), OR(FEATHER, ROOSTER, HOOKSHOT)
|
||||
).connect(
|
||||
Location().add(Chest(0x2AF)), POWER_BRACELET
|
||||
),
|
||||
exits=[("writes_cave_right", lambda loc: loc)],
|
||||
),
|
||||
"writes_cave_right": EntranceInfo(),
|
||||
|
||||
"castle_main_entrance": EntranceInfo(
|
||||
items={None: 2},
|
||||
logic=lambda c, w, r: Location().connect(
|
||||
Location().add(GoldLeaf(0x2D2)), r.attack_hookshot_powder # in the castle, kill enemies
|
||||
).connect(
|
||||
Location().add(GoldLeaf(0x2C5)), AND(BOMB, r.attack_hookshot_powder) # in the castle, bomb wall to show enemy
|
||||
),
|
||||
exits=[("castle_upper_left", lambda loc: loc)],
|
||||
),
|
||||
"castle_upper_left": EntranceInfo(),
|
||||
|
||||
"castle_upper_right": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(GoldLeaf(0x2C6)), AND(POWER_BRACELET, r.attack_hookshot)),
|
||||
),
|
||||
|
||||
"right_taltal_connector1": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[("right_taltal_connector2", lambda loc: loc)],
|
||||
),
|
||||
"right_taltal_connector2": EntranceInfo(),
|
||||
|
||||
"fire_cave_entrance": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[("fire_cave_exit", lambda loc: Location().connect(loc, COUNT(SHIELD, 2)))],
|
||||
),
|
||||
"fire_cave_exit": EntranceInfo(),
|
||||
|
||||
"graveyard_cave_left": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(HeartPiece(0x2DF)), OR(AND(BOMB, OR(HOOKSHOT, PEGASUS_BOOTS), FEATHER), ROOSTER)),
|
||||
exits=[("graveyard_cave_right", lambda loc: Location().connect(loc, OR(FEATHER, ROOSTER)))],
|
||||
),
|
||||
"graveyard_cave_right": EntranceInfo(),
|
||||
|
||||
"raft_return_enter": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[("raft_return_exit", one_way)],
|
||||
),
|
||||
"raft_return_exit": EntranceInfo(),
|
||||
|
||||
"prairie_right_cave_top": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[("prairie_right_cave_bottom", lambda loc: loc), ("prairie_right_cave_high", lambda loc: Location().connect(loc, AND(BOMB, OR(FEATHER, ROOSTER))))],
|
||||
),
|
||||
"prairie_right_cave_bottom": EntranceInfo(),
|
||||
"prairie_right_cave_high": EntranceInfo(),
|
||||
|
||||
"armos_maze_cave": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().add(Chest(0x2FC)),
|
||||
),
|
||||
"right_taltal_connector3": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[("right_taltal_connector4", lambda loc: one_way(loc, AND(OR(FEATHER, ROOSTER), HOOKSHOT)))],
|
||||
),
|
||||
"right_taltal_connector4": EntranceInfo(),
|
||||
|
||||
"obstacle_cave_entrance": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(Chest(0x2BB)), AND(SWORD, OR(HOOKSHOT, ROOSTER))),
|
||||
exits=[
|
||||
("obstacle_cave_outside_chest", lambda loc: Location().connect(loc, SWORD)),
|
||||
("obstacle_cave_exit", lambda loc: Location().connect(loc, AND(SWORD, OR(PEGASUS_BOOTS, ROOSTER))))
|
||||
],
|
||||
),
|
||||
"obstacle_cave_outside_chest": EntranceInfo(),
|
||||
"obstacle_cave_exit": EntranceInfo(),
|
||||
|
||||
"d6_connector_entrance": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[("d6_connector_exit", lambda loc: Location().connect(loc, OR(AND(HOOKSHOT, OR(FLIPPERS, AND(FEATHER, PEGASUS_BOOTS))), ROOSTER)))],
|
||||
),
|
||||
"d6_connector_exit": EntranceInfo(),
|
||||
|
||||
"multichest_left": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[
|
||||
("multichest_right", lambda loc: loc),
|
||||
("multichest_top", lambda loc: Location().connect(loc, BOMB)),
|
||||
],
|
||||
),
|
||||
"multichest_right": EntranceInfo(),
|
||||
"multichest_top": EntranceInfo(),
|
||||
|
||||
"prairie_madbatter_connector_entrance": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[("prairie_madbatter_connector_exit", lambda loc: Location().connect(loc, FLIPPERS))],
|
||||
),
|
||||
"prairie_madbatter_connector_exit": EntranceInfo(),
|
||||
|
||||
"papahl_house_left": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[("papahl_house_right", lambda loc: loc)],
|
||||
),
|
||||
"papahl_house_right": EntranceInfo(),
|
||||
|
||||
"prairie_to_animal_connector": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[("animal_to_prairie_connector", lambda loc: Location().connect(loc, PEGASUS_BOOTS))],
|
||||
),
|
||||
"animal_to_prairie_connector": EntranceInfo(),
|
||||
|
||||
"castle_secret_entrance": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[("castle_secret_exit", lambda loc: Location().connect(loc, FEATHER))],
|
||||
),
|
||||
"castle_secret_exit": EntranceInfo(),
|
||||
|
||||
"papahl_entrance": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().add(Chest(0x28A)),
|
||||
exits=[("papahl_exit", lambda loc: loc)],
|
||||
),
|
||||
"papahl_exit": EntranceInfo(),
|
||||
|
||||
"right_taltal_connector5": EntranceInfo(
|
||||
logic=lambda c, w, r: Location(),
|
||||
exits=[("right_taltal_connector6", lambda loc: loc)],
|
||||
),
|
||||
"right_taltal_connector6": EntranceInfo(),
|
||||
|
||||
"toadstool_entrance": EntranceInfo(
|
||||
items={None: 2},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(Chest(0x2BD)), SWORD).connect( # chest in forest cave on route to mushroom
|
||||
Location().add(HeartPiece(0x2AB), POWER_BRACELET)), # piece of heart in the forest cave on route to the mushroom
|
||||
exits=[("right_taltal_connector6", lambda loc: loc)],
|
||||
),
|
||||
"toadstool_exit": EntranceInfo(),
|
||||
|
||||
"richard_house": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(Chest(0x2C8)), AND(COUNT(GOLD_LEAF, 5), OR(FEATHER, HOOKSHOT, ROOSTER))),
|
||||
exits=[("richard_maze", lambda loc: Location().connect(loc, COUNT(GOLD_LEAF, 5)))],
|
||||
),
|
||||
"richard_maze": EntranceInfo(),
|
||||
|
||||
"left_to_right_taltalentrance": EntranceInfo(
|
||||
exits=[("left_taltal_entrance", lambda loc: one_way(loc, OR(HOOKSHOT, ROOSTER)))],
|
||||
),
|
||||
"left_taltal_entrance": EntranceInfo(),
|
||||
|
||||
"boomerang_cave": EntranceInfo(), # TODO boomerang gift
|
||||
"trendy_shop": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(TradeSequenceItem(0x2A0, TRADING_ITEM_YOSHI_DOLL)), FOUND("RUPEES", 50))
|
||||
),
|
||||
"moblin_cave": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(Chest(0x2E2)), AND(r.attack_hookshot_powder, r.miniboss_requirements[w.miniboss_mapping["moblin_cave"]]))
|
||||
),
|
||||
"prairie_madbatter": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(MadBatter(0x1E0)), MAGIC_POWDER)
|
||||
),
|
||||
"ulrira": EntranceInfo(),
|
||||
"rooster_house": EntranceInfo(),
|
||||
"animal_house2": EntranceInfo(),
|
||||
"animal_house4": EntranceInfo(),
|
||||
"armos_fairy": EntranceInfo(),
|
||||
"right_fairy": EntranceInfo(),
|
||||
"photo_house": EntranceInfo(),
|
||||
|
||||
"bird_cave": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(BirdKey()), OR(AND(FEATHER, COUNT(POWER_BRACELET, 2)), ROOSTER))
|
||||
),
|
||||
"mamu": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(Song(0x2FB)), AND(OCARINA, COUNT("RUPEES", 300)))
|
||||
),
|
||||
"armos_temple": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(FaceKey()), r.miniboss_requirements[w.miniboss_mapping["armos_temple"]])
|
||||
),
|
||||
"animal_house1": EntranceInfo(),
|
||||
"madambowwow": EntranceInfo(),
|
||||
"library": EntranceInfo(),
|
||||
"kennel": EntranceInfo(
|
||||
items={None: 1, TRADING_ITEM_RIBBON: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(Seashell(0x2B2)), SHOVEL).connect(Location().add(TradeSequenceItem(0x2B2, TRADING_ITEM_DOG_FOOD)), TRADING_ITEM_RIBBON)
|
||||
),
|
||||
"dream_hut": EntranceInfo(
|
||||
items={None: 2},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(Chest(0x2BF)), OR(SWORD, BOOMERANG, HOOKSHOT, FEATHER)).connect(Location().add(Chest(0x2BE)), AND(OR(SWORD, BOOMERANG, HOOKSHOT, FEATHER), PEGASUS_BOOTS))
|
||||
),
|
||||
"hookshot_cave": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(Chest(0x2B3)), OR(HOOKSHOT, ROOSTER))
|
||||
),
|
||||
"madbatter_taltal": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(MadBatter(0x1E2)), MAGIC_POWDER)
|
||||
),
|
||||
"forest_madbatter": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(MadBatter(0x1E1)), MAGIC_POWDER)
|
||||
),
|
||||
"banana_seller": EntranceInfo(
|
||||
items={TRADING_ITEM_DOG_FOOD: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(TradeSequenceItem(0x2FE, TRADING_ITEM_BANANAS)), TRADING_ITEM_DOG_FOOD)
|
||||
),
|
||||
"shop": EntranceInfo(
|
||||
items={None: 2},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(ShopItem(0)), COUNT("RUPEES", 200)).connect(Location().add(ShopItem(1)), COUNT("RUPEES", 980))
|
||||
),
|
||||
"ghost_house": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(Seashell(0x1E3)), POWER_BRACELET)
|
||||
),
|
||||
"writes_house": EntranceInfo(
|
||||
items={TRADING_ITEM_LETTER: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(TradeSequenceItem(0x2A8, TRADING_ITEM_BROOM)), TRADING_ITEM_LETTER)
|
||||
),
|
||||
"animal_house3": EntranceInfo(
|
||||
items={TRADING_ITEM_HIBISCUS: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(TradeSequenceItem(0x2D9, TRADING_ITEM_LETTER)), TRADING_ITEM_HIBISCUS)
|
||||
),
|
||||
"animal_house5": EntranceInfo(
|
||||
items={TRADING_ITEM_HONEYCOMB: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(TradeSequenceItem(0x2D7, TRADING_ITEM_PINEAPPLE)), TRADING_ITEM_HONEYCOMB)
|
||||
),
|
||||
"crazy_tracy": EntranceInfo(
|
||||
items={"MEDICINE2": 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(KeyLocation("MEDICINE2")), FOUND("RUPEES", 50))
|
||||
),
|
||||
"rooster_grave": EntranceInfo(
|
||||
logic=lambda c, w, r: Location().connect(Location().add(DroppedKey(0x1E4)), AND(OCARINA, SONG3))
|
||||
),
|
||||
"desert_cave": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(HeartPiece(0x1E8)), BOMB)
|
||||
),
|
||||
"witch": EntranceInfo(
|
||||
items={TOADSTOOL: 1},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(Witch()), TOADSTOOL)
|
||||
),
|
||||
"prairie_left_cave1": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().add(Chest(0x2CD))
|
||||
),
|
||||
"prairie_left_cave2": EntranceInfo(
|
||||
items={None: 2},
|
||||
logic=lambda c, w, r: Location().connect(Location().add(Chest(0x2F4)), PEGASUS_BOOTS).connect(Location().add(HeartPiece(0x2E5)), AND(BOMB, PEGASUS_BOOTS))
|
||||
),
|
||||
"castle_jump_cave": EntranceInfo(
|
||||
items={None: 1},
|
||||
logic=lambda c, w, r: Location().add(Chest(0x1FD))
|
||||
),
|
||||
"raft_house": EntranceInfo(),
|
||||
"prairie_left_fairy": EntranceInfo(),
|
||||
"seashell_mansion": EntranceInfo(), # TODO: Not sure if we can guarantee enough shells
|
||||
}
|
||||
172
worlds/ladx/LADXR/mapgen/locations/seashell.py
Normal file
172
worlds/ladx/LADXR/mapgen/locations/seashell.py
Normal file
@@ -0,0 +1,172 @@
|
||||
from ..logic import Location, PEGASUS_BOOTS, SHOVEL
|
||||
from .base import LocationBase
|
||||
from ..tileset import solid_tiles, open_tiles, walkable_tiles
|
||||
from ...roomEditor import RoomEditor
|
||||
from ...assembler import ASM
|
||||
from ...locations.all import Seashell
|
||||
import random
|
||||
|
||||
|
||||
class HiddenSeashell(LocationBase):
|
||||
def __init__(self, room, x, y):
|
||||
super().__init__(room, x, y)
|
||||
if room.tiles[x + y * 10] not in (0x20, 0x5C):
|
||||
if random.randint(0, 1):
|
||||
room.tiles[x + y * 10] = 0x20 # rock
|
||||
else:
|
||||
room.tiles[x + y * 10] = 0x5C # bush
|
||||
|
||||
def update_room(self, rom, re: RoomEditor):
|
||||
re.entities.append((self.x, self.y, 0x3D))
|
||||
|
||||
def connect_logic(self, logic_location):
|
||||
logic_location.add(Seashell(self.room.x + self.room.y * 16))
|
||||
|
||||
def get_item_pool(self):
|
||||
return {None: 1}
|
||||
|
||||
@staticmethod
|
||||
def check_possible(room, reachable_map):
|
||||
# Check if we can potentially place a hidden seashell here
|
||||
# First see if we have a nice bush or rock to hide under
|
||||
options = []
|
||||
for y in range(1, 7):
|
||||
for x in range(1, 9):
|
||||
if room.tiles[x + y * 10] not in {0x20, 0x5C}:
|
||||
continue
|
||||
idx = room.x * 10 + x + (room.y * 8 + y) * reachable_map.w
|
||||
if reachable_map.area[idx] == -1:
|
||||
continue
|
||||
options.append((reachable_map.distance[idx], x, y))
|
||||
if not options:
|
||||
# No existing bush, we can always add one. So find a nice spot
|
||||
for y in range(1, 7):
|
||||
for x in range(1, 9):
|
||||
if room.tiles[x + y * 10] not in walkable_tiles:
|
||||
continue
|
||||
if room.tiles[x + y * 10] == 0x1E: # ocean edge
|
||||
continue
|
||||
idx = room.x * 10 + x + (room.y * 8 + y) * reachable_map.w
|
||||
if reachable_map.area[idx] == -1:
|
||||
continue
|
||||
options.append((reachable_map.distance[idx], x, y))
|
||||
if not options:
|
||||
return None
|
||||
options.sort(reverse=True)
|
||||
options = [(x, y) for d, x, y in options if d > options[0][0] - 4]
|
||||
return random.choice(options)
|
||||
|
||||
|
||||
class DigSeashell(LocationBase):
|
||||
MAX_COUNT = 6
|
||||
|
||||
def __init__(self, room, x, y):
|
||||
super().__init__(room, x, y)
|
||||
if room.tileset_id == "beach":
|
||||
room.tiles[x + y * 10] = 0x08
|
||||
for ox, oy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||
if room.tiles[x + ox + (y + oy) * 10] != 0x1E:
|
||||
room.tiles[x + ox + (y + oy) * 10] = 0x24
|
||||
else:
|
||||
room.tiles[x + y * 10] = 0x04
|
||||
for ox, oy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||
room.tiles[x + ox + (y + oy) * 10] = 0x0A
|
||||
|
||||
def update_room(self, rom, re: RoomEditor):
|
||||
re.entities.append((self.x, self.y, 0x3D))
|
||||
if rom.banks[0x03][0x2210] == 0xFF:
|
||||
rom.patch(0x03, 0x220F, ASM("cp $FF"), ASM(f"cp ${self.room.x | (self.room.y << 4):02x}"))
|
||||
elif rom.banks[0x03][0x2214] == 0xFF:
|
||||
rom.patch(0x03, 0x2213, ASM("cp $FF"), ASM(f"cp ${self.room.x | (self.room.y << 4):02x}"))
|
||||
elif rom.banks[0x03][0x2218] == 0xFF:
|
||||
rom.patch(0x03, 0x2217, ASM("cp $FF"), ASM(f"cp ${self.room.x | (self.room.y << 4):02x}"))
|
||||
elif rom.banks[0x03][0x221C] == 0xFF:
|
||||
rom.patch(0x03, 0x221B, ASM("cp $FF"), ASM(f"cp ${self.room.x | (self.room.y << 4):02x}"))
|
||||
elif rom.banks[0x03][0x2220] == 0xFF:
|
||||
rom.patch(0x03, 0x221F, ASM("cp $FF"), ASM(f"cp ${self.room.x | (self.room.y << 4):02x}"))
|
||||
elif rom.banks[0x03][0x2224] == 0xFF:
|
||||
rom.patch(0x03, 0x2223, ASM("cp $FF"), ASM(f"cp ${self.room.x | (self.room.y << 4):02x}"))
|
||||
|
||||
def connect_logic(self, logic_location):
|
||||
logic_location.connect(Location().add(Seashell(self.room.x + self.room.y * 16)), SHOVEL)
|
||||
|
||||
def get_item_pool(self):
|
||||
return {None: 1}
|
||||
|
||||
@staticmethod
|
||||
def check_possible(room, reachable_map):
|
||||
options = []
|
||||
for y in range(1, 7):
|
||||
for x in range(1, 9):
|
||||
if room.tiles[x + y * 10] not in walkable_tiles:
|
||||
continue
|
||||
if room.tiles[x - 1 + y * 10] not in walkable_tiles:
|
||||
continue
|
||||
if room.tiles[x + 1 + y * 10] not in walkable_tiles:
|
||||
continue
|
||||
if room.tiles[x + (y - 1) * 10] not in walkable_tiles:
|
||||
continue
|
||||
if room.tiles[x + (y + 1) * 10] not in walkable_tiles:
|
||||
continue
|
||||
idx = room.x * 10 + x + (room.y * 8 + y) * reachable_map.w
|
||||
if reachable_map.area[idx] == -1:
|
||||
continue
|
||||
options.append((x, y))
|
||||
if not options:
|
||||
return None
|
||||
return random.choice(options)
|
||||
|
||||
|
||||
class BonkSeashell(LocationBase):
|
||||
MAX_COUNT = 2
|
||||
|
||||
def __init__(self, room, x, y):
|
||||
super().__init__(room, x, y)
|
||||
self.tree_x = x
|
||||
self.tree_y = y
|
||||
for offsetx, offsety in [(-1, 0), (-1, 1), (2, 0), (2, 1), (0, -1), (1, -1), (0, 2), (1, 2)]:
|
||||
if room.tiles[x + offsetx + (y + offsety) * 10] in walkable_tiles:
|
||||
self.x += offsetx
|
||||
self.y += offsety
|
||||
break
|
||||
|
||||
def update_room(self, rom, re: RoomEditor):
|
||||
re.entities.append((self.tree_x, self.tree_y, 0x3D))
|
||||
if rom.banks[0x03][0x0F04] == 0xFF:
|
||||
rom.patch(0x03, 0x0F03, ASM("cp $FF"), ASM(f"cp ${self.room.x|(self.room.y<<4):02x}"))
|
||||
elif rom.banks[0x03][0x0F08] == 0xFF:
|
||||
rom.patch(0x03, 0x0F07, ASM("cp $FF"), ASM(f"cp ${self.room.x|(self.room.y<<4):02x}"))
|
||||
else:
|
||||
raise RuntimeError("To many bonk seashells")
|
||||
|
||||
def connect_logic(self, logic_location):
|
||||
logic_location.connect(Location().add(Seashell(self.room.x + self.room.y * 16)), PEGASUS_BOOTS)
|
||||
|
||||
def get_item_pool(self):
|
||||
return {None: 1}
|
||||
|
||||
@staticmethod
|
||||
def check_possible(room, reachable_map):
|
||||
# Check if we can potentially place a hidden seashell here
|
||||
# Find potential trees
|
||||
options = []
|
||||
for y in range(1, 6):
|
||||
for x in range(1, 8):
|
||||
if room.tiles[x + y * 10] != 0x25:
|
||||
continue
|
||||
if room.tiles[x + y * 10 + 1] != 0x26:
|
||||
continue
|
||||
if room.tiles[x + y * 10 + 10] != 0x27:
|
||||
continue
|
||||
if room.tiles[x + y * 10 + 11] != 0x28:
|
||||
continue
|
||||
idx = room.x * 10 + x + (room.y * 8 + y) * reachable_map.w
|
||||
top_reachable = reachable_map.area[idx - reachable_map.w] != -1 or reachable_map.area[idx - reachable_map.w + 1] != -1
|
||||
bottom_reachable = reachable_map.area[idx + reachable_map.w * 2] != -1 or reachable_map.area[idx + reachable_map.w * 2 + 1] != -1
|
||||
left_reachable = reachable_map.area[idx - 1] != -1 or reachable_map.area[idx + reachable_map.w - 1] != -1
|
||||
right_reachable = reachable_map.area[idx + 2] != -1 or reachable_map.area[idx + reachable_map.w + 2] != -1
|
||||
if (top_reachable and bottom_reachable) or (left_reachable and right_reachable):
|
||||
options.append((x, y))
|
||||
if not options:
|
||||
return None
|
||||
return random.choice(options)
|
||||
Reference in New Issue
Block a user