Skip to content

Commit

Permalink
Merge branch 'main' into smw-main
Browse files Browse the repository at this point in the history
  • Loading branch information
PoryGone committed Dec 20, 2022
2 parents 406e188 + 4cfc73b commit a84e36c
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 104 deletions.
154 changes: 77 additions & 77 deletions worlds/lufia2ac/Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,81 @@ class CrowdedFloorChance(Range):
default = 16


class DefaultCapsule(Choice):
"""Preselect the active capsule monster.
(Only has an effect if shuffle_capsule_monsters is set to false.)
Supported values: jelze, flash, gusto, zeppy, darbi, sully, blaze
Default value: jelze
"""

display_name = "Default capsule monster"
option_jelze = 0x00
option_flash = 0x01
option_gusto = 0x02
option_zeppy = 0x03
option_darbi = 0x04
option_sully = 0x05
option_blaze = 0x06
default = option_jelze


class DefaultParty(RandomGroupsChoice, TextChoice):
"""Preselect the party lineup.
(Only has an effect if shuffle_party_members is set to false.)
Supported values:
Can be set to any valid combination of up to 4 party member initials, e.g.:
M — Maxim
DGMA — Dekar, Guy, Maxim, and Arty
MSTL — Maxim, Selan, Tia, and Lexis
random-2p — a random 2-person party
random-3p — a random 3-person party
random-4p — a random 4-person party
Default value: M
"""

display_name = "Default party lineup"
default = "M"

random_groups = {
"random-2p": ["M" + "".join(p) for p in combinations("ADGLST", 1)],
"random-3p": ["M" + "".join(p) for p in combinations("ADGLST", 2)],
"random-4p": ["M" + "".join(p) for p in combinations("ADGLST", 3)],
}
vars().update({f"option_{party}": party for party in (*random_groups, "M", *chain(*random_groups.values()))})
_valid_sorted_parties: List[List[str]] = [sorted(party) for party in ("M", *chain(*random_groups.values()))]
_members_to_bytes: bytes = bytes.maketrans(b"MSGATDL", bytes(range(7)))

def verify(self, *args, **kwargs) -> None:
if str(self.value).lower() in self.random_groups:
return
if sorted(str(self.value).upper()) in self._valid_sorted_parties:
return
raise ValueError(f"Could not find option '{self.value}' for '{self.__class__.__name__}', known options are:\n"
f"{', '.join(self.random_groups)}, {', '.join(('M', *chain(*self.random_groups.values())))} "
"as well as all permutations of these.")

@staticmethod
def _flip(i: int) -> int:
return {4: 5, 5: 4}.get(i, i)

@property
def event_script(self) -> bytes:
return bytes((*(b for i in bytes(self) if i != 0 for b in (0x2B, i, 0x2E, i + 0x65, 0x1A, self._flip(i) + 1)),
0x1E, 0x0B, len(self) - 1, 0x1C, 0x86, 0x03, *(0x00,) * (6 * (4 - len(self)))))

@property
def roster(self) -> bytes:
return bytes((len(self), *bytes(self), *(0xFF,) * (4 - len(self))))

def __bytes__(self) -> bytes:
return str(self.value).upper().encode("ASCII").translate(self._members_to_bytes)

def __len__(self) -> int:
return len(str(self.value))


class FinalFloor(Range):
"""The final floor, where the boss resides.
Expand Down Expand Up @@ -439,81 +514,6 @@ def unlock(self) -> int:
return 0b00000000 if self.value else 0b11111100


class StartingCapsule(Choice):
"""The capsule monster you start the game with.
Only has an effect if shuffle_capsule_monsters is set to false.
Supported values: jelze, flash, gusto, zeppy, darbi, sully, blaze
Default value: jelze
"""

display_name = "Starting capsule monster"
option_jelze = 0x00
option_flash = 0x01
option_gusto = 0x02
option_zeppy = 0x03
option_darbi = 0x04
option_sully = 0x05
option_blaze = 0x06
default = option_jelze


class StartingParty(RandomGroupsChoice, TextChoice):
"""The party you start the game with.
Only has an effect if shuffle_party_members is set to false.
Supported values:
Can be set to any valid combination of up to 4 party member initials, e.g.:
M — start with Maxim
DGMA — start with Dekar, Guy, Maxim, and Arty
MSTL — start with Maxim, Selan, Tia, and Lexis
random-2p — a random 2-person party
random-3p — a random 3-person party
random-4p — a random 4-person party
Default value: M
"""

display_name = "Starting party"
default = "M"

random_groups = {
"random-2p": ["M" + "".join(p) for p in combinations("ADGLST", 1)],
"random-3p": ["M" + "".join(p) for p in combinations("ADGLST", 2)],
"random-4p": ["M" + "".join(p) for p in combinations("ADGLST", 3)],
}
vars().update({f"option_{party}": party for party in (*random_groups, "M", *chain(*random_groups.values()))})
_valid_sorted_parties: List[List[str]] = [sorted(party) for party in ("M", *chain(*random_groups.values()))]
_members_to_bytes: bytes = bytes.maketrans(b"MSGATDL", bytes(range(7)))

def verify(self, *args, **kwargs) -> None:
if str(self.value).lower() in self.random_groups:
return
if sorted(str(self.value).upper()) in self._valid_sorted_parties:
return
raise ValueError(f"Could not find option '{self.value}' for '{self.__class__.__name__}', known options are:\n"
f"{', '.join(self.random_groups)}, {', '.join(('M', *chain(*self.random_groups.values())))} "
"as well as all permutations of these.")

@staticmethod
def _flip(i: int) -> int:
return {4: 5, 5: 4}.get(i, i)

@property
def event_script(self) -> bytes:
return bytes((*(b for i in bytes(self) if i != 0 for b in (0x2B, i, 0x2E, i + 0x65, 0x1A, self._flip(i) + 1)),
0x1E, 0x0B, len(self) - 1, 0x1C, 0x86, 0x03, *(0x00,) * (6 * (4 - len(self)))))

@property
def roster(self) -> bytes:
return bytes((len(self), *bytes(self), *(0xFF,) * (4 - len(self))))

def __bytes__(self) -> bytes:
return str(self.value).upper().encode("ASCII").translate(self._members_to_bytes)

def __len__(self) -> int:
return len(str(self.value))


l2ac_option_definitions: Dict[str, type(Option)] = {
"blue_chest_chance": BlueChestChance,
"blue_chest_count": BlueChestCount,
Expand All @@ -523,6 +523,8 @@ def __len__(self) -> int:
"capsule_starting_level": CapsuleStartingLevel,
"crowded_floor_chance": CrowdedFloorChance,
"death_link": DeathLink,
"default_capsule": DefaultCapsule,
"default_party": DefaultParty,
"final_floor": FinalFloor,
"gear_variety_after_b9": GearVarietyAfterB9,
"goal": Goal,
Expand All @@ -535,6 +537,4 @@ def __len__(self) -> int:
"run_speed": RunSpeed,
"shuffle_capsule_monsters": ShuffleCapsuleMonsters,
"shuffle_party_members": ShufflePartyMembers,
"starting_capsule": StartingCapsule,
"starting_party": StartingParty,
}
20 changes: 10 additions & 10 deletions worlds/lufia2ac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
from .Client import L2ACSNIClient # noqa: F401
from .Items import ItemData, ItemType, l2ac_item_name_to_id, l2ac_item_table, L2ACItem, start_id as items_start_id
from .Locations import l2ac_location_name_to_id, L2ACLocation
from .Options import Boss, CapsuleStartingForm, CapsuleStartingLevel, Goal, l2ac_option_definitions, MasterHp, \
PartyStartingLevel, ShuffleCapsuleMonsters, ShufflePartyMembers, StartingParty
from .Options import Boss, CapsuleStartingForm, CapsuleStartingLevel, DefaultParty, Goal, l2ac_option_definitions, \
MasterHp, PartyStartingLevel, ShuffleCapsuleMonsters, ShufflePartyMembers
from .Rom import get_base_rom_bytes, get_base_rom_path, L2ACDeltaPatch
from .basepatch import apply_basepatch

Expand Down Expand Up @@ -64,6 +64,8 @@ class L2ACWorld(World):
capsule_starting_level: Optional[CapsuleStartingLevel]
crowded_floor_chance: Optional[int]
death_link: Optional[int]
default_capsule: Optional[int]
default_party: Optional[DefaultParty]
final_floor: Optional[int]
gear_variety_after_b9: Optional[int]
goal: Optional[int]
Expand All @@ -76,8 +78,6 @@ class L2ACWorld(World):
run_speed: Optional[int]
shuffle_capsule_monsters: Optional[ShuffleCapsuleMonsters]
shuffle_party_members: Optional[ShufflePartyMembers]
starting_capsule: Optional[int]
starting_party: Optional[StartingParty]

@classmethod
def stage_assert_generate(cls, _multiworld: MultiWorld) -> None:
Expand All @@ -103,6 +103,8 @@ def generate_early(self) -> None:
self.capsule_starting_level = self.multiworld.capsule_starting_level[self.player]
self.crowded_floor_chance = self.multiworld.crowded_floor_chance[self.player].value
self.death_link = self.multiworld.death_link[self.player].value
self.default_capsule = self.multiworld.default_capsule[self.player].value
self.default_party = self.multiworld.default_party[self.player]
self.final_floor = self.multiworld.final_floor[self.player].value
self.gear_variety_after_b9 = self.multiworld.gear_variety_after_b9[self.player].value
self.goal = self.multiworld.goal[self.player].value
Expand All @@ -115,8 +117,6 @@ def generate_early(self) -> None:
self.run_speed = self.multiworld.run_speed[self.player].value
self.shuffle_capsule_monsters = self.multiworld.shuffle_capsule_monsters[self.player]
self.shuffle_party_members = self.multiworld.shuffle_party_members[self.player]
self.starting_capsule = self.multiworld.starting_capsule[self.player].value
self.starting_party = self.multiworld.starting_party[self.player]

if self.capsule_starting_level.value == CapsuleStartingLevel.special_range_names["party_starting_level"]:
self.capsule_starting_level.value = self.party_starting_level.value
Expand All @@ -125,7 +125,7 @@ def generate_early(self) -> None:
if self.master_hp == MasterHp.special_range_names["scale"]:
self.master_hp = MasterHp.scale(self.final_floor)
if self.shuffle_party_members:
self.starting_party.value = StartingParty.default
self.default_party.value = DefaultParty.default

def create_regions(self) -> None:
menu = Region("Menu", RegionType.Generic, "Menu", self.player, self.multiworld)
Expand Down Expand Up @@ -234,21 +234,21 @@ def generate_output(self, output_directory: str) -> None:
rom_bytearray[0x019E82:0x019E82 + 1] = self.final_floor.to_bytes(1, "little")
rom_bytearray[0x01FC75:0x01FC75 + 1] = self.run_speed.to_bytes(1, "little")
rom_bytearray[0x01FC81:0x01FC81 + 1] = self.run_speed.to_bytes(1, "little")
rom_bytearray[0x02B2A1:0x02B2A1 + 5] = self.starting_party.roster
rom_bytearray[0x02B2A1:0x02B2A1 + 5] = self.default_party.roster
for offset in range(0x02B395, 0x02B452, 0x1B):
rom_bytearray[offset:offset + 1] = self.party_starting_level.value.to_bytes(1, "little")
for offset in range(0x02B39A, 0x02B457, 0x1B):
rom_bytearray[offset:offset + 3] = self.party_starting_level.xp.to_bytes(3, "little")
rom_bytearray[0x05699E:0x05699E + 147] = self.get_goal_text_bytes()
rom_bytearray[0x056AA3:0x056AA3 + 24] = self.starting_party.event_script
rom_bytearray[0x056AA3:0x056AA3 + 24] = self.default_party.event_script
rom_bytearray[0x072742:0x072742 + 1] = self.boss.value.to_bytes(1, "little")
rom_bytearray[0x072748:0x072748 + 1] = self.boss.flag.to_bytes(1, "little")
rom_bytearray[0x09D59B:0x09D59B + 256] = self.get_node_connection_table()
rom_bytearray[0x0B4F02:0x0B4F02 + 2] = self.master_hp.to_bytes(2, "little")
rom_bytearray[0x280010:0x280010 + 2] = self.blue_chest_count.to_bytes(2, "little")
rom_bytearray[0x280012:0x280012 + 3] = self.capsule_starting_level.xp.to_bytes(3, "little")
rom_bytearray[0x280015:0x280015 + 1] = self.initial_floor.to_bytes(1, "little")
rom_bytearray[0x280016:0x280016 + 1] = self.starting_capsule.to_bytes(1, "little")
rom_bytearray[0x280016:0x280016 + 1] = self.default_capsule.to_bytes(1, "little")
rom_bytearray[0x280017:0x280017 + 1] = self.iris_treasures_required.to_bytes(1, "little")
rom_bytearray[0x280018:0x280018 + 1] = self.shuffle_party_members.unlock.to_bytes(1, "little")
rom_bytearray[0x280019:0x280019 + 1] = self.shuffle_capsule_monsters.unlock.to_bytes(1, "little")
Expand Down
3 changes: 3 additions & 0 deletions worlds/lufia2ac/basepatch/basepatch.asm
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,9 @@ SpecialItemUse:
ADC.w #$FD2E
STA $09B7 ; set pointer to L2SASM join script
SEP #$20
LDA $07A9 ; load EV register $11 (party counter)
CMP.b #$03
BPL + ; abort if party full
LDA.b #$8E
STA $09B9
PHK
Expand Down
Binary file modified worlds/lufia2ac/basepatch/basepatch.bsdiff4
Binary file not shown.
6 changes: 3 additions & 3 deletions worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ Your Party Leader will hold up the item they received when not in a fight or in
Retrieve a (customizable) number of iris treasures from the cave; 4) Retrieve the iris treasures *and* defeat the boss
- You can also randomize the goal; The blue-haired NPC in front of the cafe can tell you about the selected objective
- Customize (or randomize) the chances of encountering blue chests, healing tiles, iris treasures, etc.
- Customize (or randomize) your starting party members and/or party level
- Customize (or randomize) your starting capsule monster and/or capsule monster level as well as form
- Customize (or randomize) the initial and/or final floor numbers
- Customize (or randomize) the default party lineup and capsule monster
- Customize (or randomize) the party starting level as well as capsule monster level and form
- Customize (or randomize) the initial and final floor numbers
- Customize (or randomize) the boss that resides on the final floor
- Customize start inventory, i.e., begin every run with certain items or spells of your choice
- Option to shuffle your party members and/or capsule monsters into the multiworld, meaning that someone will have to
Expand Down
6 changes: 3 additions & 3 deletions worlds/oot/Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,11 +899,11 @@ class HintDistribution(Choice):
display_name = "Hint Distribution"
option_balanced = 0
option_ddr = 1
option_league = 2
option_mw3 = 3
# option_league = 2
# option_mw3 = 3
option_scrubs = 4
option_strong = 5
option_tournament = 6
# option_tournament = 6
option_useless = 7
option_very_strong = 8
option_async = 9
Expand Down
2 changes: 1 addition & 1 deletion worlds/oot/Patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def patch_rom(world, rom):
# Load Triforce model into a file
triforce_obj_file = File({ 'Name': 'object_gi_triforce' })
triforce_obj_file.copy(rom)
with open(data_path('triforce.zobj'), 'rb') as stream:
with open(data_path('Triforce.zobj'), 'rb') as stream:
obj_data = stream.read()
rom.write_bytes(triforce_obj_file.start, obj_data)
triforce_obj_file.end = triforce_obj_file.start + len(obj_data)
Expand Down
17 changes: 10 additions & 7 deletions worlds/oot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -943,13 +943,16 @@ def generate_output(self, output_directory: str):

outfile_name = self.multiworld.get_out_file_name_base(self.player)
rom = Rom(file=get_options()['oot_options']['rom_file'])
if self.hints != 'none':
buildWorldGossipHints(self)
# try:
patch_rom(self, rom)
# except Exception as e:
# print(e)
patch_cosmetics(self, rom)
try:
if self.hints != 'none':
buildWorldGossipHints(self)
patch_rom(self, rom)
patch_cosmetics(self, rom)
except Exception as e:
logger.error(e)
raise e
finally:
self.collectible_flags_available.set()
rom.update_header()
patch_data = create_patch_file(rom)
rom.restore()
Expand Down
10 changes: 10 additions & 0 deletions worlds/pokemon_rb/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,13 @@ def pokemon_rb_fossil_checks(self, count, player):
return (self.can_reach('Mt Moon 1F - Southwest Item', 'Location', player) and
self.can_reach('Cinnabar Island - Lab Scientist', 'Location', player) and len(
[item for item in ["Dome Fossil", "Helix Fossil", "Old Amber"] if self.has(item, player)]) >= count)

def pokemon_rb_cinnabar_gym(self, player):
# ensures higher level Pokémon are obtainable before Cinnabar Gym is in logic
return ((self.multiworld.old_man[player] != "vanilla") or (not self.multiworld.extra_key_items[player]) or
self.has("Mansion Key", player) or self.has("Oak's Parcel", player) or self.pokemon_rb_can_surf(player))

def pokemon_rb_dojo(self, player):
# ensures higher level Pokémon are obtainable before Fighting Dojo is in logic
return (self.pokemon_rb_can_pass_guards(player) or self.has("Oak's Parcel", player) or
self.pokemon_rb_can_surf(player))
2 changes: 1 addition & 1 deletion worlds/pokemon_rb/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ class OaksAidRt15(Range):
class ExpModifier(SpecialRange):
"""Modifier for EXP gained. When specifying a number, exp is multiplied by this amount and divided by 16."""
display_name = "Exp Modifier"
range_start = 1
range_start = 0
range_end = 255
default = 16
special_range_names = {
Expand Down
4 changes: 2 additions & 2 deletions worlds/pokemon_rb/regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def create_regions(multiworld: MultiWorld, player: int):
connect(multiworld, player, "Pokemon Tower 6F", "Pokemon Tower 7F", lambda state: state.has("Silph Scope", player))
connect(multiworld, player, "Cerulean City", "Route 5")
connect(multiworld, player, "Route 5", "Saffron City", lambda state: state.pokemon_rb_can_pass_guards(player))
connect(multiworld, player, "Saffron City", "Fighting Dojo", one_way=True)
connect(multiworld, player, "Saffron City", "Fighting Dojo", lambda state: state.pokemon_rb_dojo(player), one_way=True)
connect(multiworld, player, "Route 5", "Underground Tunnel North-South")
connect(multiworld, player, "Route 6", "Underground Tunnel North-South")
connect(multiworld, player, "Route 6", "Saffron City", lambda state: state.pokemon_rb_can_pass_guards(player))
Expand Down Expand Up @@ -248,7 +248,7 @@ def create_regions(multiworld: MultiWorld, player: int):
connect(multiworld, player, "Viridian City", "Viridian City North", lambda state: state.has("Oak's Parcel", player) or state.multiworld.old_man[player].value == 2 or state.pokemon_rb_can_cut(player))
connect(multiworld, player, "Route 3", "Mt Moon 1F", one_way=True)
connect(multiworld, player, "Route 11", "Route 11 East", lambda state: state.pokemon_rb_can_strength(player))
connect(multiworld, player, "Cinnabar Island", "Cinnabar Gym", lambda state: state.has("Secret Key", player), one_way=True)
connect(multiworld, player, "Cinnabar Island", "Cinnabar Gym", lambda state: state.has("Secret Key", player) and state.pokemon_rb_cinnabar_gym(player), one_way=True)
connect(multiworld, player, "Cinnabar Island", "Pokemon Mansion 1F", lambda state: state.has("Mansion Key", player) or not state.multiworld.extra_key_items[player].value, one_way=True)
connect(multiworld, player, "Seafoam Islands 1F", "Seafoam Islands B1F", one_way=True)
connect(multiworld, player, "Seafoam Islands B1F", "Seafoam Islands B2F", one_way=True)
Expand Down

0 comments on commit a84e36c

Please sign in to comment.