forked from mirror/Archipelago
* initial work on procedure patch
* more flexibility
load default procedure for version 5 patches
add args for procedure
add default extension for tokens and bsdiff
allow specifying additional required extensions for generation
* pushing current changes to go fix tloz bug
* move tokens into a separate inheritable class
* forgot the commit to remove token from ProcedurePatch
* further cleaning from bad commit
* start on docstrings
* further work on docstrings and typing
* improve docstrings
* fix incorrect docstring
* cleanup
* clean defaults and docstring
* define interface that has only the bare minimum required
for `Patch.create_rom_file`
* change to dictionary.get
* remove unnecessary if statement
* update to explicitly check for procedure, restore compatible version and manual override
* Update Files.py
* remove struct uses
* Update Rom.py
* convert KDL3 to APPP
* change class variables to instance variables
* Update worlds/Files.py
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
* Update worlds/Files.py
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
* move required_extensions to tuple
* fix missing tuple ellipsis
* fix classvar mixup
* rename tokens to _tokens. use hasattr
* type hint cleanup
* Update Files.py
* initial base for local items, need to finish
* coo not clean
* handle local items for real, appp cleanup
* actually make bosses send their locations
* fix cloudy park 4 rule, zero deathlink message
* remove redundant door_shuffle bool
when generic ER gets in, this whole function gets rewritten. So just clean it a little now.
* properly fix deathlink messages, fix fill error
* update docs
* add prefill items
* fix kine fill error
* Update Rom.py
* Update Files.py
* mypy and softlock fix
* Update Gifting.py
* mypy phase 1
* fix rare async client bug
* Update __init__.py
* typing cleanup
* fix stone softlock
because of the way Kine's Stone works, you can't clear the stone blocks before clearing the burning blocks, so we have to bring Burning from outside
* Update Rom.py
* Add option groups
* Rename to lowercase
* finish rename
* whoops broke the world
* fix animal duplication bug
* overhaul filler generation
* add Miku flavor
* Update gifting.py
* fix issues related to max_hs increase
* Update test_locations.py
* fix boss shuffle not working if level shuffle is disabled
* fix bleeding default levels
* Update options.py
* thought this would print seed
* yay bad merges
* forgot options too
* yeah lets just break generation while at it
* this is probably a problem
* cap required heart stars
* Revert "cap required heart stars"
This reverts commit 759efd3e2b.
* fix duplication removal placement, deprecated test option
* forgot that we need to account for what we place
* move location ids
* rewrite trap handling
* further stage renumber fixes
* forgot one more
* basic UT support
* fix local heart star checks
* fix pattern
---------
Co-authored-by: beauxq <beauxq@yahoo.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
134 lines
6.4 KiB
Python
134 lines
6.4 KiB
Python
import struct
|
|
from typing import Optional, Dict, TYPE_CHECKING, List, Union
|
|
from BaseClasses import Region, ItemClassification, MultiWorld
|
|
from worlds.Files import APTokenTypes
|
|
from .client_addrs import consumable_addrs, star_addrs
|
|
|
|
if TYPE_CHECKING:
|
|
from .rom import KDL3ProcedurePatch
|
|
|
|
animal_map = {
|
|
"Rick Spawn": 0,
|
|
"Kine Spawn": 1,
|
|
"Coo Spawn": 2,
|
|
"Nago Spawn": 3,
|
|
"ChuChu Spawn": 4,
|
|
"Pitch Spawn": 5
|
|
}
|
|
|
|
|
|
class KDL3Room(Region):
|
|
pointer: int = 0
|
|
level: int = 0
|
|
stage: int = 0
|
|
room: int = 0
|
|
music: int = 0
|
|
default_exits: List[Dict[str, Union[int, List[str]]]]
|
|
animal_pointers: List[int]
|
|
enemies: List[str]
|
|
entity_load: List[List[int]]
|
|
consumables: List[Dict[str, Union[int, str]]]
|
|
|
|
def __init__(self, name: str, player: int, multiworld: MultiWorld, hint: Optional[str], level: int,
|
|
stage: int, room: int, pointer: int, music: int,
|
|
default_exits: List[Dict[str, List[str]]],
|
|
animal_pointers: List[int], enemies: List[str],
|
|
entity_load: List[List[int]],
|
|
consumables: List[Dict[str, Union[int, str]]], consumable_pointer: int) -> None:
|
|
super().__init__(name, player, multiworld, hint)
|
|
self.level = level
|
|
self.stage = stage
|
|
self.room = room
|
|
self.pointer = pointer
|
|
self.music = music
|
|
self.default_exits = default_exits
|
|
self.animal_pointers = animal_pointers
|
|
self.enemies = enemies
|
|
self.entity_load = entity_load
|
|
self.consumables = consumables
|
|
self.consumable_pointer = consumable_pointer
|
|
|
|
def patch(self, patch: "KDL3ProcedurePatch", consumables: bool, local_items: bool) -> None:
|
|
patch.write_token(APTokenTypes.WRITE, self.pointer + 2, self.music.to_bytes(1, "little"))
|
|
animals = [x.item.name for x in self.locations if "Animal" in x.name and x.item]
|
|
if len(animals) > 0:
|
|
for current_animal, address in zip(animals, self.animal_pointers):
|
|
patch.write_token(APTokenTypes.WRITE, self.pointer + address + 7,
|
|
animal_map[current_animal].to_bytes(1, "little"))
|
|
if local_items:
|
|
for location in self.get_locations():
|
|
if location.item is None or location.item.player != self.player:
|
|
continue
|
|
item = location.item.code
|
|
if item is None:
|
|
continue
|
|
item_idx = item & 0x00000F
|
|
location_idx = location.address & 0xFFFF
|
|
if location_idx & 0xF00 in (0x300, 0x400, 0x500, 0x600):
|
|
# consumable or star, need remapped
|
|
location_base = location_idx & 0xF00
|
|
if location_base == 0x300:
|
|
# consumable
|
|
location_idx = consumable_addrs[location_idx & 0xFF] | 0x1000
|
|
else:
|
|
# star
|
|
location_idx = star_addrs[location.address] | 0x2000
|
|
if item & 0x000070 == 0:
|
|
patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x10]))
|
|
elif item & 0x000010 > 0:
|
|
patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x20]))
|
|
elif item & 0x000020 > 0:
|
|
patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x40]))
|
|
elif item & 0x000040 > 0:
|
|
patch.write_token(APTokenTypes.WRITE, 0x4B000 + location_idx, bytes([item_idx | 0x80]))
|
|
|
|
if consumables:
|
|
load_len = len(self.entity_load)
|
|
for consumable in self.consumables:
|
|
location = next(x for x in self.locations if x.name == consumable["name"])
|
|
assert location.item is not None
|
|
is_progression = location.item.classification & ItemClassification.progression
|
|
if load_len == 8:
|
|
# edge case, there is exactly 1 room with 8 entities and only 1 consumable among them
|
|
if not (any(x in self.entity_load for x in [[0, 22], [1, 22]])
|
|
and any(x in self.entity_load for x in [[2, 22], [3, 22]])):
|
|
replacement_target = self.entity_load.index(
|
|
next(x for x in self.entity_load if x in [[0, 22], [1, 22], [2, 22], [3, 22]]))
|
|
if is_progression:
|
|
vtype = 0
|
|
else:
|
|
vtype = 2
|
|
patch.write_token(APTokenTypes.WRITE, self.pointer + 88 + (replacement_target * 2),
|
|
vtype.to_bytes(1, "little"))
|
|
self.entity_load[replacement_target] = [vtype, 22]
|
|
else:
|
|
if is_progression:
|
|
# we need to see if 1-ups are in our load list
|
|
if any(x not in self.entity_load for x in [[0, 22], [1, 22]]):
|
|
self.entity_load.append([0, 22])
|
|
else:
|
|
if any(x not in self.entity_load for x in [[2, 22], [3, 22]]):
|
|
# edge case: if (1, 22) is in, we need to load (3, 22) instead
|
|
if [1, 22] in self.entity_load:
|
|
self.entity_load.append([3, 22])
|
|
else:
|
|
self.entity_load.append([2, 22])
|
|
if load_len < len(self.entity_load):
|
|
patch.write_token(APTokenTypes.WRITE, self.pointer + 88 + (load_len * 2),
|
|
bytes(self.entity_load[load_len]))
|
|
patch.write_token(APTokenTypes.WRITE, self.pointer + 104 + (load_len * 2),
|
|
bytes(struct.pack("H", self.consumable_pointer)))
|
|
if is_progression:
|
|
if [1, 22] in self.entity_load:
|
|
vtype = 1
|
|
else:
|
|
vtype = 0
|
|
else:
|
|
if [3, 22] in self.entity_load:
|
|
vtype = 3
|
|
else:
|
|
vtype = 2
|
|
assert isinstance(consumable["pointer"], int)
|
|
patch.write_token(APTokenTypes.WRITE, self.pointer + consumable["pointer"] + 7,
|
|
vtype.to_bytes(1, "little"))
|