Archipelago/worlds/ladx/LADXR/mapgen/__init__.py

148 lines
6.1 KiB
Python

from ..romTables import ROMWithTables
from ..roomEditor import RoomEditor, ObjectWarp
from ..patches import overworld, core
from .tileset import loadTileInfo
from .map import Map, MazeGen
from .wfc import WFCMap, ContradictionException
from .roomgen import setup_room_types
from .imagegenerator import ImageGen
from .util import xyrange
from .locations.entrance import DummyEntrance
from .locationgen import LocationGenerator
from .logic import LogicGenerator
from .enemygen import generate_enemies
from ..assembler import ASM
def store_map(rom, the_map: Map):
# Move all exceptions to room FF
# Dig seashells
rom.patch(0x03, 0x220F, ASM("cp $DA"), ASM("cp $FF"))
rom.patch(0x03, 0x2213, ASM("cp $A5"), ASM("cp $FF"))
rom.patch(0x03, 0x2217, ASM("cp $74"), ASM("cp $FF"))
rom.patch(0x03, 0x221B, ASM("cp $3A"), ASM("cp $FF"))
rom.patch(0x03, 0x221F, ASM("cp $A8"), ASM("cp $FF"))
rom.patch(0x03, 0x2223, ASM("cp $B2"), ASM("cp $FF"))
# Force tile 04 under bushes and rocks, instead of conditionally tile 3, else seashells won't spawn.
rom.patch(0x14, 0x1655, 0x1677, "", fill_nop=True)
# Bonk trees
rom.patch(0x03, 0x0F03, ASM("cp $A4"), ASM("cp $FF"))
rom.patch(0x03, 0x0F07, ASM("cp $D2"), ASM("cp $FF"))
# Stairs under rocks
rom.patch(0x14, 0x1638, ASM("cp $52"), ASM("cp $FF"))
rom.patch(0x14, 0x163C, ASM("cp $04"), ASM("cp $FF"))
# Patch D6 raft game exit, just remove the exit.
re = RoomEditor(rom, 0x1B0)
re.removeObject(7, 0)
re.store(rom)
# Patch D8 back entrance, remove the outside part
re = RoomEditor(rom, 0x23A)
re.objects = [obj for obj in re.objects if not isinstance(obj, ObjectWarp)] + [ObjectWarp(1, 7, 0x23D, 0x58, 0x10)]
re.store(rom)
re = RoomEditor(rom, 0x23D)
re.objects = [obj for obj in re.objects if not isinstance(obj, ObjectWarp)] + [ObjectWarp(1, 7, 0x23A, 0x58, 0x10)]
re.store(rom)
for room in the_map:
for location in room.locations:
location.prepare(rom)
for n in range(0x00, 0x100):
sx = n & 0x0F
sy = ((n >> 4) & 0x0F)
if sx < the_map.w and sy < the_map.h:
tiles = the_map.get(sx, sy).tiles
else:
tiles = [4] * 80
tiles[44] = 0xC6
re = RoomEditor(rom, n)
# tiles = re.getTileArray()
re.objects = []
re.entities = []
room = the_map.get(sx, sy) if sx < the_map.w and sy < the_map.h else None
tileset = the_map.tilesets[room.tileset_id] if room else None
rom.banks[0x3F][0x3F00 + n] = tileset.main_id if tileset else 0x0F
rom.banks[0x21][0x02EF + n] = tileset.palette_id if tileset and tileset.palette_id is not None else 0x03
rom.banks[0x1A][0x2476 + n] = tileset.attr_bank if tileset and tileset.attr_bank else 0x22
rom.banks[0x1A][0x1E76 + n * 2] = (tileset.attr_addr & 0xFF) if tileset and tileset.attr_addr else 0x00
rom.banks[0x1A][0x1E77 + n * 2] = (tileset.attr_addr >> 8) if tileset and tileset.attr_addr else 0x60
re.animation_id = tileset.animation_id if tileset and tileset.animation_id is not None else 0x03
re.buildObjectList(tiles)
if room:
for idx, tile_id in enumerate(tiles):
if tile_id == 0x61: # Fix issues with the well being used as chimney as well and causing wrong warps
DummyEntrance(room, idx % 10, idx // 10)
re.entities += room.entities
room.locations.sort(key=lambda loc: (loc.y, loc.x, id(loc)))
for location in room.locations:
location.update_room(rom, re)
else:
re.objects.append(ObjectWarp(0x01, 0x10, 0x2A3, 0x50, 0x7C))
re.store(rom)
rom.banks[0x21][0x00BF:0x00BF+3] = [0, 0, 0] # Patch out the "load palette on screen transition" exception code.
# Fix some tile attribute issues
def change_attr(tileset, index, a, b, c, d):
rom.banks[the_map.tilesets[tileset].attr_bank][the_map.tilesets[tileset].attr_addr - 0x4000 + index * 4 + 0] = a
rom.banks[the_map.tilesets[tileset].attr_bank][the_map.tilesets[tileset].attr_addr - 0x4000 + index * 4 + 1] = b
rom.banks[the_map.tilesets[tileset].attr_bank][the_map.tilesets[tileset].attr_addr - 0x4000 + index * 4 + 2] = c
rom.banks[the_map.tilesets[tileset].attr_bank][the_map.tilesets[tileset].attr_addr - 0x4000 + index * 4 + 3] = d
change_attr("mountains", 0x04, 6, 6, 6, 6)
change_attr("mountains", 0x27, 6, 6, 3, 3)
change_attr("mountains", 0x28, 6, 6, 3, 3)
change_attr("mountains", 0x6E, 1, 1, 1, 1)
change_attr("town", 0x59, 2, 2, 2, 2) # Roof tile wrong color
def generate(rom_filename, w, h):
rom = ROMWithTables(rom_filename)
overworld.patchOverworldTilesets(rom)
core.cleanup(rom)
tilesets = loadTileInfo(rom)
the_map = Map(w, h, tilesets)
setup_room_types(the_map)
MazeGen(the_map)
imggen = ImageGen(tilesets, the_map, rom)
imggen.enabled = False
wfcmap = WFCMap(the_map, tilesets) #, step_callback=imggen.on_step)
try:
wfcmap.initialize()
except ContradictionException as e:
print(f"Failed on setup {e.x // 10} {e.y // 8} {e.x % 10} {e.y % 8}")
imggen.on_step(wfcmap, err=(e.x, e.y))
return
imggen.on_step(wfcmap)
for x, y in xyrange(w, h):
for n in range(50):
try:
wfcmap.build(x * 10, y * 8, 10, 8)
imggen.on_step(wfcmap)
break
except ContradictionException as e:
print(f"Failed {x} {y} {e.x%10} {e.y%8} {n}")
imggen.on_step(wfcmap, err=(e.x, e.y))
wfcmap.clear()
if n == 49:
raise RuntimeError("Failed to fill chunk")
print(f"Done {x} {y}")
imggen.on_step(wfcmap)
wfcmap.store_tile_data(the_map)
LocationGenerator(the_map)
for room in the_map:
generate_enemies(room)
if imggen.enabled:
store_map(rom, the_map)
from mapexport import MapExport
MapExport(rom).export_all(w, h, dungeons=False)
rom.save("test.gbc")
return the_map