FFMQ: Efficiency Improvement and Use New Options Methods (#2767)

* FFMQ Efficiency improvement and use new options methods

* Hard check for 0x01 game status

* Fixes

* Why were Mac's Ship entrance hints excluded?

* Two remaining per_slot_randoms purged

* reformat generate_early

* Utils.parse_yaml
This commit is contained in:
Alchav 2024-07-24 07:46:14 -04:00 committed by GitHub
parent ad5089b5a3
commit e7dbfa7fcd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 134 additions and 6618 deletions

View File

@ -71,7 +71,7 @@ class FFMQClient(SNIClient):
received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1]) received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1])
data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START) data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START)
check_2 = await snes_read(ctx, 0xF53749, 1) check_2 = await snes_read(ctx, 0xF53749, 1)
if check_1 in (b'\x00', b'\x55') or check_2 in (b'\x00', b'\x55'): if check_1 != b'01' or check_2 != b'01':
return return
def get_range(data_range): def get_range(data_range):

View File

@ -222,10 +222,10 @@ for item, data in item_table.items():
def create_items(self) -> None: def create_items(self) -> None:
items = [] items = []
starting_weapon = self.multiworld.starting_weapon[self.player].current_key.title().replace("_", " ") starting_weapon = self.options.starting_weapon.current_key.title().replace("_", " ")
self.multiworld.push_precollected(self.create_item(starting_weapon)) self.multiworld.push_precollected(self.create_item(starting_weapon))
self.multiworld.push_precollected(self.create_item("Steel Armor")) self.multiworld.push_precollected(self.create_item("Steel Armor"))
if self.multiworld.sky_coin_mode[self.player] == "start_with": if self.options.sky_coin_mode == "start_with":
self.multiworld.push_precollected(self.create_item("Sky Coin")) self.multiworld.push_precollected(self.create_item("Sky Coin"))
precollected_item_names = {item.name for item in self.multiworld.precollected_items[self.player]} precollected_item_names = {item.name for item in self.multiworld.precollected_items[self.player]}
@ -233,28 +233,28 @@ def create_items(self) -> None:
def add_item(item_name): def add_item(item_name):
if item_name in ["Steel Armor", "Sky Fragment"] or "Progressive" in item_name: if item_name in ["Steel Armor", "Sky Fragment"] or "Progressive" in item_name:
return return
if item_name.lower().replace(" ", "_") == self.multiworld.starting_weapon[self.player].current_key: if item_name.lower().replace(" ", "_") == self.options.starting_weapon.current_key:
return return
if self.multiworld.progressive_gear[self.player]: if self.options.progressive_gear:
for item_group in prog_map: for item_group in prog_map:
if item_name in self.item_name_groups[item_group]: if item_name in self.item_name_groups[item_group]:
item_name = prog_map[item_group] item_name = prog_map[item_group]
break break
if item_name == "Sky Coin": if item_name == "Sky Coin":
if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": if self.options.sky_coin_mode == "shattered_sky_coin":
for _ in range(40): for _ in range(40):
items.append(self.create_item("Sky Fragment")) items.append(self.create_item("Sky Fragment"))
return return
elif self.multiworld.sky_coin_mode[self.player] == "save_the_crystals": elif self.options.sky_coin_mode == "save_the_crystals":
items.append(self.create_filler()) items.append(self.create_filler())
return return
if item_name in precollected_item_names: if item_name in precollected_item_names:
items.append(self.create_filler()) items.append(self.create_filler())
return return
i = self.create_item(item_name) i = self.create_item(item_name)
if self.multiworld.logic[self.player] != "friendly" and item_name in ("Magic Mirror", "Mask"): if self.options.logic != "friendly" and item_name in ("Magic Mirror", "Mask"):
i.classification = ItemClassification.useful i.classification = ItemClassification.useful
if (self.multiworld.logic[self.player] == "expert" and self.multiworld.map_shuffle[self.player] == "none" and if (self.options.logic == "expert" and self.options.map_shuffle == "none" and
item_name == "Exit Book"): item_name == "Exit Book"):
i.classification = ItemClassification.progression i.classification = ItemClassification.progression
items.append(i) items.append(i)
@ -263,11 +263,11 @@ def create_items(self) -> None:
for item in self.item_name_groups[item_group]: for item in self.item_name_groups[item_group]:
add_item(item) add_item(item)
if self.multiworld.brown_boxes[self.player] == "include": if self.options.brown_boxes == "include":
filler_items = [] filler_items = []
for item, count in fillers.items(): for item, count in fillers.items():
filler_items += [self.create_item(item) for _ in range(count)] filler_items += [self.create_item(item) for _ in range(count)]
if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": if self.options.sky_coin_mode == "shattered_sky_coin":
self.multiworld.random.shuffle(filler_items) self.multiworld.random.shuffle(filler_items)
filler_items = filler_items[39:] filler_items = filler_items[39:]
items += filler_items items += filler_items

View File

@ -1,4 +1,5 @@
from Options import Choice, FreeText, Toggle, Range from Options import Choice, FreeText, Toggle, Range, PerGameCommonOptions
from dataclasses import dataclass
class Logic(Choice): class Logic(Choice):
@ -321,36 +322,36 @@ class KaelisMomFightsMinotaur(Toggle):
default = 0 default = 0
option_definitions = { @dataclass
"logic": Logic, class FFMQOptions(PerGameCommonOptions):
"brown_boxes": BrownBoxes, logic: Logic
"sky_coin_mode": SkyCoinMode, brown_boxes: BrownBoxes
"shattered_sky_coin_quantity": ShatteredSkyCoinQuantity, sky_coin_mode: SkyCoinMode
"starting_weapon": StartingWeapon, shattered_sky_coin_quantity: ShatteredSkyCoinQuantity
"progressive_gear": ProgressiveGear, starting_weapon: StartingWeapon
"leveling_curve": LevelingCurve, progressive_gear: ProgressiveGear
"starting_companion": StartingCompanion, leveling_curve: LevelingCurve
"available_companions": AvailableCompanions, starting_companion: StartingCompanion
"companions_locations": CompanionsLocations, available_companions: AvailableCompanions
"kaelis_mom_fight_minotaur": KaelisMomFightsMinotaur, companions_locations: CompanionsLocations
"companion_leveling_type": CompanionLevelingType, kaelis_mom_fight_minotaur: KaelisMomFightsMinotaur
"companion_spellbook_type": CompanionSpellbookType, companion_leveling_type: CompanionLevelingType
"enemies_density": EnemiesDensity, companion_spellbook_type: CompanionSpellbookType
"enemies_scaling_lower": EnemiesScalingLower, enemies_density: EnemiesDensity
"enemies_scaling_upper": EnemiesScalingUpper, enemies_scaling_lower: EnemiesScalingLower
"bosses_scaling_lower": BossesScalingLower, enemies_scaling_upper: EnemiesScalingUpper
"bosses_scaling_upper": BossesScalingUpper, bosses_scaling_lower: BossesScalingLower
"enemizer_attacks": EnemizerAttacks, bosses_scaling_upper: BossesScalingUpper
"enemizer_groups": EnemizerGroups, enemizer_attacks: EnemizerAttacks
"shuffle_res_weak_types": ShuffleResWeakType, enemizer_groups: EnemizerGroups
"shuffle_enemies_position": ShuffleEnemiesPositions, shuffle_res_weak_types: ShuffleResWeakType
"progressive_formations": ProgressiveFormations, shuffle_enemies_position: ShuffleEnemiesPositions
"doom_castle_mode": DoomCastle, progressive_formations: ProgressiveFormations
"doom_castle_shortcut": DoomCastleShortcut, doom_castle_mode: DoomCastle
"tweak_frustrating_dungeons": TweakFrustratingDungeons, doom_castle_shortcut: DoomCastleShortcut
"map_shuffle": MapShuffle, tweak_frustrating_dungeons: TweakFrustratingDungeons
"crest_shuffle": CrestShuffle, map_shuffle: MapShuffle
"shuffle_battlefield_rewards": ShuffleBattlefieldRewards, crest_shuffle: CrestShuffle
"map_shuffle_seed": MapShuffleSeed, shuffle_battlefield_rewards: ShuffleBattlefieldRewards
"battlefields_battles_quantities": BattlefieldsBattlesQuantities, map_shuffle_seed: MapShuffleSeed
} battlefields_battles_quantities: BattlefieldsBattlesQuantities

View File

@ -1,13 +1,13 @@
import yaml import yaml
import os import os
import zipfile import zipfile
import Utils
from copy import deepcopy from copy import deepcopy
from .Regions import object_id_table from .Regions import object_id_table
from Utils import __version__
from worlds.Files import APPatch from worlds.Files import APPatch
import pkgutil import pkgutil
settings_template = yaml.load(pkgutil.get_data(__name__, "data/settings.yaml"), yaml.Loader) settings_template = Utils.parse_yaml(pkgutil.get_data(__name__, "data/settings.yaml"))
def generate_output(self, output_directory): def generate_output(self, output_directory):
@ -21,7 +21,7 @@ def generate_output(self, output_directory):
item_name = "".join(item_name.split(" ")) item_name = "".join(item_name.split(" "))
else: else:
if item.advancement or item.useful or (item.trap and if item.advancement or item.useful or (item.trap and
self.multiworld.per_slot_randoms[self.player].randint(0, 1)): self.random.randint(0, 1)):
item_name = "APItem" item_name = "APItem"
else: else:
item_name = "APItemFiller" item_name = "APItemFiller"
@ -46,60 +46,60 @@ def generate_output(self, output_directory):
options = deepcopy(settings_template) options = deepcopy(settings_template)
options["name"] = self.multiworld.player_name[self.player] options["name"] = self.multiworld.player_name[self.player]
option_writes = { option_writes = {
"enemies_density": cc(self.multiworld.enemies_density[self.player]), "enemies_density": cc(self.options.enemies_density),
"chests_shuffle": "Include", "chests_shuffle": "Include",
"shuffle_boxes_content": self.multiworld.brown_boxes[self.player] == "shuffle", "shuffle_boxes_content": self.options.brown_boxes == "shuffle",
"npcs_shuffle": "Include", "npcs_shuffle": "Include",
"battlefields_shuffle": "Include", "battlefields_shuffle": "Include",
"logic_options": cc(self.multiworld.logic[self.player]), "logic_options": cc(self.options.logic),
"shuffle_enemies_position": tf(self.multiworld.shuffle_enemies_position[self.player]), "shuffle_enemies_position": tf(self.options.shuffle_enemies_position),
"enemies_scaling_lower": cc(self.multiworld.enemies_scaling_lower[self.player]), "enemies_scaling_lower": cc(self.options.enemies_scaling_lower),
"enemies_scaling_upper": cc(self.multiworld.enemies_scaling_upper[self.player]), "enemies_scaling_upper": cc(self.options.enemies_scaling_upper),
"bosses_scaling_lower": cc(self.multiworld.bosses_scaling_lower[self.player]), "bosses_scaling_lower": cc(self.options.bosses_scaling_lower),
"bosses_scaling_upper": cc(self.multiworld.bosses_scaling_upper[self.player]), "bosses_scaling_upper": cc(self.options.bosses_scaling_upper),
"enemizer_attacks": cc(self.multiworld.enemizer_attacks[self.player]), "enemizer_attacks": cc(self.options.enemizer_attacks),
"leveling_curve": cc(self.multiworld.leveling_curve[self.player]), "leveling_curve": cc(self.options.leveling_curve),
"battles_quantity": cc(self.multiworld.battlefields_battles_quantities[self.player]) if "battles_quantity": cc(self.options.battlefields_battles_quantities) if
self.multiworld.battlefields_battles_quantities[self.player].value < 5 else self.options.battlefields_battles_quantities.value < 5 else
"RandomLow" if "RandomLow" if
self.multiworld.battlefields_battles_quantities[self.player].value == 5 else self.options.battlefields_battles_quantities.value == 5 else
"RandomHigh", "RandomHigh",
"shuffle_battlefield_rewards": tf(self.multiworld.shuffle_battlefield_rewards[self.player]), "shuffle_battlefield_rewards": tf(self.options.shuffle_battlefield_rewards),
"random_starting_weapon": True, "random_starting_weapon": True,
"progressive_gear": tf(self.multiworld.progressive_gear[self.player]), "progressive_gear": tf(self.options.progressive_gear),
"tweaked_dungeons": tf(self.multiworld.tweak_frustrating_dungeons[self.player]), "tweaked_dungeons": tf(self.options.tweak_frustrating_dungeons),
"doom_castle_mode": cc(self.multiworld.doom_castle_mode[self.player]), "doom_castle_mode": cc(self.options.doom_castle_mode),
"doom_castle_shortcut": tf(self.multiworld.doom_castle_shortcut[self.player]), "doom_castle_shortcut": tf(self.options.doom_castle_shortcut),
"sky_coin_mode": cc(self.multiworld.sky_coin_mode[self.player]), "sky_coin_mode": cc(self.options.sky_coin_mode),
"sky_coin_fragments_qty": cc(self.multiworld.shattered_sky_coin_quantity[self.player]), "sky_coin_fragments_qty": cc(self.options.shattered_sky_coin_quantity),
"enable_spoilers": False, "enable_spoilers": False,
"progressive_formations": cc(self.multiworld.progressive_formations[self.player]), "progressive_formations": cc(self.options.progressive_formations),
"map_shuffling": cc(self.multiworld.map_shuffle[self.player]), "map_shuffling": cc(self.options.map_shuffle),
"crest_shuffle": tf(self.multiworld.crest_shuffle[self.player]), "crest_shuffle": tf(self.options.crest_shuffle),
"enemizer_groups": cc(self.multiworld.enemizer_groups[self.player]), "enemizer_groups": cc(self.options.enemizer_groups),
"shuffle_res_weak_type": tf(self.multiworld.shuffle_res_weak_types[self.player]), "shuffle_res_weak_type": tf(self.options.shuffle_res_weak_types),
"companion_leveling_type": cc(self.multiworld.companion_leveling_type[self.player]), "companion_leveling_type": cc(self.options.companion_leveling_type),
"companion_spellbook_type": cc(self.multiworld.companion_spellbook_type[self.player]), "companion_spellbook_type": cc(self.options.companion_spellbook_type),
"starting_companion": cc(self.multiworld.starting_companion[self.player]), "starting_companion": cc(self.options.starting_companion),
"available_companions": ["Zero", "One", "Two", "available_companions": ["Zero", "One", "Two",
"Three", "Four"][self.multiworld.available_companions[self.player].value], "Three", "Four"][self.options.available_companions.value],
"companions_locations": cc(self.multiworld.companions_locations[self.player]), "companions_locations": cc(self.options.companions_locations),
"kaelis_mom_fight_minotaur": tf(self.multiworld.kaelis_mom_fight_minotaur[self.player]), "kaelis_mom_fight_minotaur": tf(self.options.kaelis_mom_fight_minotaur),
} }
for option, data in option_writes.items(): for option, data in option_writes.items():
options["Final Fantasy Mystic Quest"][option][data] = 1 options["Final Fantasy Mystic Quest"][option][data] = 1
rom_name = f'MQ{__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed_name:11}'[:21] rom_name = f'MQ{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed_name:11}'[:21]
self.rom_name = bytearray(rom_name, self.rom_name = bytearray(rom_name,
'utf8') 'utf8')
self.rom_name_available_event.set() self.rom_name_available_event.set()
setup = {"version": "1.5", "name": self.multiworld.player_name[self.player], "romname": rom_name, "seed": setup = {"version": "1.5", "name": self.multiworld.player_name[self.player], "romname": rom_name, "seed":
hex(self.multiworld.per_slot_randoms[self.player].randint(0, 0xFFFFFFFF)).split("0x")[1].upper()} hex(self.random.randint(0, 0xFFFFFFFF)).split("0x")[1].upper()}
starting_items = [output_item_name(item) for item in self.multiworld.precollected_items[self.player]] starting_items = [output_item_name(item) for item in self.multiworld.precollected_items[self.player]]
if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": if self.options.sky_coin_mode == "shattered_sky_coin":
starting_items.append("SkyCoin") starting_items.append("SkyCoin")
file_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.apmq") file_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.apmq")

View File

@ -1,11 +1,9 @@
from BaseClasses import Region, MultiWorld, Entrance, Location, LocationProgressType, ItemClassification from BaseClasses import Region, MultiWorld, Entrance, Location, LocationProgressType, ItemClassification
from worlds.generic.Rules import add_rule from worlds.generic.Rules import add_rule
from .data.rooms import rooms, entrances
from .Items import item_groups, yaml_item from .Items import item_groups, yaml_item
import pkgutil
import yaml
rooms = yaml.load(pkgutil.get_data(__name__, "data/rooms.yaml"), yaml.Loader) entrance_names = {entrance["id"]: entrance["name"] for entrance in entrances}
entrance_names = {entrance["id"]: entrance["name"] for entrance in yaml.load(pkgutil.get_data(__name__, "data/entrances.yaml"), yaml.Loader)}
object_id_table = {} object_id_table = {}
object_type_table = {} object_type_table = {}
@ -69,7 +67,7 @@ def create_regions(self):
location_table else None, object["type"], object["access"], location_table else None, object["type"], object["access"],
self.create_item(yaml_item(object["on_trigger"][0])) if object["type"] == "Trigger" else None) for object in self.create_item(yaml_item(object["on_trigger"][0])) if object["type"] == "Trigger" else None) for object in
room["game_objects"] if "Hero Chest" not in object["name"] and object["type"] not in ("BattlefieldGp", room["game_objects"] if "Hero Chest" not in object["name"] and object["type"] not in ("BattlefieldGp",
"BattlefieldXp") and (object["type"] != "Box" or self.multiworld.brown_boxes[self.player] == "include") and "BattlefieldXp") and (object["type"] != "Box" or self.options.brown_boxes == "include") and
not (object["name"] == "Kaeli Companion" and not object["on_trigger"])], room["links"])) not (object["name"] == "Kaeli Companion" and not object["on_trigger"])], room["links"]))
dark_king_room = self.multiworld.get_region("Doom Castle Dark King Room", self.player) dark_king_room = self.multiworld.get_region("Doom Castle Dark King Room", self.player)
@ -91,15 +89,13 @@ def create_regions(self):
if "entrance" in link and link["entrance"] != -1: if "entrance" in link and link["entrance"] != -1:
spoiler = False spoiler = False
if link["entrance"] in crest_warps: if link["entrance"] in crest_warps:
if self.multiworld.crest_shuffle[self.player]: if self.options.crest_shuffle:
spoiler = True spoiler = True
elif self.multiworld.map_shuffle[self.player] == "everything": elif self.options.map_shuffle == "everything":
spoiler = True spoiler = True
elif "Subregion" in region.name and self.multiworld.map_shuffle[self.player] not in ("dungeons", elif "Subregion" in region.name and self.options.map_shuffle not in ("dungeons", "none"):
"none"):
spoiler = True spoiler = True
elif "Subregion" not in region.name and self.multiworld.map_shuffle[self.player] not in ("none", elif "Subregion" not in region.name and self.options.map_shuffle not in ("none", "overworld"):
"overworld"):
spoiler = True spoiler = True
if spoiler: if spoiler:
@ -111,6 +107,7 @@ def create_regions(self):
connection.connect(connect_room) connection.connect(connect_room)
break break
non_dead_end_crest_rooms = [ non_dead_end_crest_rooms = [
'Libra Temple', 'Aquaria Gemini Room', "GrenadeMan's Mobius Room", 'Fireburg Gemini Room', 'Libra Temple', 'Aquaria Gemini Room', "GrenadeMan's Mobius Room", 'Fireburg Gemini Room',
'Sealed Temple', 'Alive Forest', 'Kaidge Temple Upper Ledge', 'Sealed Temple', 'Alive Forest', 'Kaidge Temple Upper Ledge',
@ -140,7 +137,7 @@ def set_rules(self) -> None:
add_rule(self.multiworld.get_location("Gidrah", self.player), hard_boss_logic) add_rule(self.multiworld.get_location("Gidrah", self.player), hard_boss_logic)
add_rule(self.multiworld.get_location("Dullahan", self.player), hard_boss_logic) add_rule(self.multiworld.get_location("Dullahan", self.player), hard_boss_logic)
if self.multiworld.map_shuffle[self.player]: if self.options.map_shuffle:
for boss in ("Freezer Crab", "Ice Golem", "Jinn", "Medusa", "Dualhead Hydra"): for boss in ("Freezer Crab", "Ice Golem", "Jinn", "Medusa", "Dualhead Hydra"):
loc = self.multiworld.get_location(boss, self.player) loc = self.multiworld.get_location(boss, self.player)
checked_regions = {loc.parent_region} checked_regions = {loc.parent_region}
@ -158,12 +155,12 @@ def set_rules(self) -> None:
return True return True
check_foresta(loc.parent_region) check_foresta(loc.parent_region)
if self.multiworld.logic[self.player] == "friendly": if self.options.logic == "friendly":
process_rules(self.multiworld.get_entrance("Overworld - Ice Pyramid", self.player), process_rules(self.multiworld.get_entrance("Overworld - Ice Pyramid", self.player),
["MagicMirror"]) ["MagicMirror"])
process_rules(self.multiworld.get_entrance("Overworld - Volcano", self.player), process_rules(self.multiworld.get_entrance("Overworld - Volcano", self.player),
["Mask"]) ["Mask"])
if self.multiworld.map_shuffle[self.player] in ("none", "overworld"): if self.options.map_shuffle in ("none", "overworld"):
process_rules(self.multiworld.get_entrance("Overworld - Bone Dungeon", self.player), process_rules(self.multiworld.get_entrance("Overworld - Bone Dungeon", self.player),
["Bomb"]) ["Bomb"])
process_rules(self.multiworld.get_entrance("Overworld - Wintry Cave", self.player), process_rules(self.multiworld.get_entrance("Overworld - Wintry Cave", self.player),
@ -185,8 +182,8 @@ def set_rules(self) -> None:
process_rules(self.multiworld.get_entrance("Overworld - Mac Ship Doom", self.player), process_rules(self.multiworld.get_entrance("Overworld - Mac Ship Doom", self.player),
["DragonClaw", "CaptainCap"]) ["DragonClaw", "CaptainCap"])
if self.multiworld.logic[self.player] == "expert": if self.options.logic == "expert":
if self.multiworld.map_shuffle[self.player] == "none" and not self.multiworld.crest_shuffle[self.player]: if self.options.map_shuffle == "none" and not self.options.crest_shuffle:
inner_room = self.multiworld.get_region("Wintry Temple Inner Room", self.player) inner_room = self.multiworld.get_region("Wintry Temple Inner Room", self.player)
connection = Entrance(self.player, "Sealed Temple Exit Trick", inner_room) connection = Entrance(self.player, "Sealed Temple Exit Trick", inner_room)
connection.connect(self.multiworld.get_region("Wintry Temple Outer Room", self.player)) connection.connect(self.multiworld.get_region("Wintry Temple Outer Room", self.player))
@ -198,14 +195,14 @@ def set_rules(self) -> None:
if entrance.connected_region.name in non_dead_end_crest_rooms: if entrance.connected_region.name in non_dead_end_crest_rooms:
entrance.access_rule = lambda state: False entrance.access_rule = lambda state: False
if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": if self.options.sky_coin_mode == "shattered_sky_coin":
logic_coins = [16, 24, 32, 32, 38][self.multiworld.shattered_sky_coin_quantity[self.player].value] logic_coins = [16, 24, 32, 32, 38][self.options.shattered_sky_coin_quantity.value]
self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \
lambda state: state.has("Sky Fragment", self.player, logic_coins) lambda state: state.has("Sky Fragment", self.player, logic_coins)
elif self.multiworld.sky_coin_mode[self.player] == "save_the_crystals": elif self.options.sky_coin_mode == "save_the_crystals":
self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \
lambda state: state.has_all(["Flamerus Rex", "Dualhead Hydra", "Ice Golem", "Pazuzu"], self.player) lambda state: state.has_all(["Flamerus Rex", "Dualhead Hydra", "Ice Golem", "Pazuzu"], self.player)
elif self.multiworld.sky_coin_mode[self.player] in ("standard", "start_with"): elif self.options.sky_coin_mode in ("standard", "start_with"):
self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \ self.multiworld.get_entrance("Focus Tower 1F - Sky Door", self.player).access_rule = \
lambda state: state.has("Sky Coin", self.player) lambda state: state.has("Sky Coin", self.player)
@ -213,26 +210,24 @@ def set_rules(self) -> None:
def stage_set_rules(multiworld): def stage_set_rules(multiworld):
# If there's no enemies, there's no repeatable income sources # If there's no enemies, there's no repeatable income sources
no_enemies_players = [player for player in multiworld.get_game_players("Final Fantasy Mystic Quest") no_enemies_players = [player for player in multiworld.get_game_players("Final Fantasy Mystic Quest")
if multiworld.enemies_density[player] == "none"] if multiworld.worlds[player].options.enemies_density == "none"]
if (len([item for item in multiworld.itempool if item.classification in (ItemClassification.filler, if (len([item for item in multiworld.itempool if item.classification in (ItemClassification.filler,
ItemClassification.trap)]) > len([player for player in no_enemies_players if ItemClassification.trap)]) > len([player for player in no_enemies_players if
multiworld.accessibility[player] == "minimal"]) * 3): multiworld.worlds[player].options.accessibility == "minimal"]) * 3):
for player in no_enemies_players: for player in no_enemies_players:
for location in vendor_locations: for location in vendor_locations:
if multiworld.accessibility[player] == "locations": if multiworld.worlds[player].options.accessibility == "locations":
multiworld.get_location(location, player).progress_type = LocationProgressType.EXCLUDED multiworld.get_location(location, player).progress_type = LocationProgressType.EXCLUDED
else: else:
multiworld.get_location(location, player).access_rule = lambda state: False multiworld.get_location(location, player).access_rule = lambda state: False
else: else:
# There are not enough junk items to fill non-minimal players' vendors. Just set an item rule not allowing # There are not enough junk items to fill non-minimal players' vendors. Just set an item rule not allowing
# advancement items so that useful items can be placed # advancement items so that useful items can be placed.
for player in no_enemies_players: for player in no_enemies_players:
for location in vendor_locations: for location in vendor_locations:
multiworld.get_location(location, player).item_rule = lambda item: not item.advancement multiworld.get_location(location, player).item_rule = lambda item: not item.advancement
class FFMQLocation(Location): class FFMQLocation(Location):
game = "Final Fantasy Mystic Quest" game = "Final Fantasy Mystic Quest"

View File

@ -10,7 +10,7 @@ from .Regions import create_regions, location_table, set_rules, stage_set_rules,
non_dead_end_crest_warps non_dead_end_crest_warps
from .Items import item_table, item_groups, create_items, FFMQItem, fillers from .Items import item_table, item_groups, create_items, FFMQItem, fillers
from .Output import generate_output from .Output import generate_output
from .Options import option_definitions from .Options import FFMQOptions
from .Client import FFMQClient from .Client import FFMQClient
@ -45,7 +45,8 @@ class FFMQWorld(World):
item_name_to_id = {name: data.id for name, data in item_table.items() if data.id is not None} item_name_to_id = {name: data.id for name, data in item_table.items() if data.id is not None}
location_name_to_id = location_table location_name_to_id = location_table
option_definitions = option_definitions options_dataclass = FFMQOptions
options: FFMQOptions
topology_present = True topology_present = True
@ -67,20 +68,14 @@ class FFMQWorld(World):
super().__init__(world, player) super().__init__(world, player)
def generate_early(self): def generate_early(self):
if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin": if self.options.sky_coin_mode == "shattered_sky_coin":
self.multiworld.brown_boxes[self.player].value = 1 self.options.brown_boxes.value = 1
if self.multiworld.enemies_scaling_lower[self.player].value > \ if self.options.enemies_scaling_lower.value > self.options.enemies_scaling_upper.value:
self.multiworld.enemies_scaling_upper[self.player].value: self.options.enemies_scaling_lower.value, self.options.enemies_scaling_upper.value = \
(self.multiworld.enemies_scaling_lower[self.player].value, self.options.enemies_scaling_upper.value, self.options.enemies_scaling_lower.value
self.multiworld.enemies_scaling_upper[self.player].value) =\ if self.options.bosses_scaling_lower.value > self.options.bosses_scaling_upper.value:
(self.multiworld.enemies_scaling_upper[self.player].value, self.options.bosses_scaling_lower.value, self.options.bosses_scaling_upper.value = \
self.multiworld.enemies_scaling_lower[self.player].value) self.options.bosses_scaling_upper.value, self.options.bosses_scaling_lower.value
if self.multiworld.bosses_scaling_lower[self.player].value > \
self.multiworld.bosses_scaling_upper[self.player].value:
(self.multiworld.bosses_scaling_lower[self.player].value,
self.multiworld.bosses_scaling_upper[self.player].value) =\
(self.multiworld.bosses_scaling_upper[self.player].value,
self.multiworld.bosses_scaling_lower[self.player].value)
@classmethod @classmethod
def stage_generate_early(cls, multiworld): def stage_generate_early(cls, multiworld):
@ -94,20 +89,20 @@ class FFMQWorld(World):
rooms_data = {} rooms_data = {}
for world in multiworld.get_game_worlds("Final Fantasy Mystic Quest"): for world in multiworld.get_game_worlds("Final Fantasy Mystic Quest"):
if (world.multiworld.map_shuffle[world.player] or world.multiworld.crest_shuffle[world.player] or if (world.options.map_shuffle or world.options.crest_shuffle or world.options.shuffle_battlefield_rewards
world.multiworld.crest_shuffle[world.player]): or world.options.companions_locations):
if world.multiworld.map_shuffle_seed[world.player].value.isdigit(): if world.options.map_shuffle_seed.value.isdigit():
multiworld.random.seed(int(world.multiworld.map_shuffle_seed[world.player].value)) multiworld.random.seed(int(world.options.map_shuffle_seed.value))
elif world.multiworld.map_shuffle_seed[world.player].value != "random": elif world.options.map_shuffle_seed.value != "random":
multiworld.random.seed(int(hash(world.multiworld.map_shuffle_seed[world.player].value)) multiworld.random.seed(int(hash(world.options.map_shuffle_seed.value))
+ int(world.multiworld.seed)) + int(world.multiworld.seed))
seed = hex(multiworld.random.randint(0, 0xFFFFFFFF)).split("0x")[1].upper() seed = hex(multiworld.random.randint(0, 0xFFFFFFFF)).split("0x")[1].upper()
map_shuffle = multiworld.map_shuffle[world.player].value map_shuffle = world.options.map_shuffle.value
crest_shuffle = multiworld.crest_shuffle[world.player].current_key crest_shuffle = world.options.crest_shuffle.current_key
battlefield_shuffle = multiworld.shuffle_battlefield_rewards[world.player].current_key battlefield_shuffle = world.options.shuffle_battlefield_rewards.current_key
companion_shuffle = multiworld.companions_locations[world.player].value companion_shuffle = world.options.companions_locations.value
kaeli_mom = multiworld.kaelis_mom_fight_minotaur[world.player].current_key kaeli_mom = world.options.kaelis_mom_fight_minotaur.current_key
query = f"s={seed}&m={map_shuffle}&c={crest_shuffle}&b={battlefield_shuffle}&cs={companion_shuffle}&km={kaeli_mom}" query = f"s={seed}&m={map_shuffle}&c={crest_shuffle}&b={battlefield_shuffle}&cs={companion_shuffle}&km={kaeli_mom}"
@ -175,14 +170,14 @@ class FFMQWorld(World):
def extend_hint_information(self, hint_data): def extend_hint_information(self, hint_data):
hint_data[self.player] = {} hint_data[self.player] = {}
if self.multiworld.map_shuffle[self.player]: if self.options.map_shuffle:
single_location_regions = ["Subregion Volcano Battlefield", "Subregion Mac's Ship", "Subregion Doom Castle"] single_location_regions = ["Subregion Volcano Battlefield", "Subregion Mac's Ship", "Subregion Doom Castle"]
for subregion in ["Subregion Foresta", "Subregion Aquaria", "Subregion Frozen Fields", "Subregion Fireburg", for subregion in ["Subregion Foresta", "Subregion Aquaria", "Subregion Frozen Fields", "Subregion Fireburg",
"Subregion Volcano Battlefield", "Subregion Windia", "Subregion Mac's Ship", "Subregion Volcano Battlefield", "Subregion Windia", "Subregion Mac's Ship",
"Subregion Doom Castle"]: "Subregion Doom Castle"]:
region = self.multiworld.get_region(subregion, self.player) region = self.multiworld.get_region(subregion, self.player)
for location in region.locations: for location in region.locations:
if location.address and self.multiworld.map_shuffle[self.player] != "dungeons": if location.address and self.options.map_shuffle != "dungeons":
hint_data[self.player][location.address] = (subregion.split("Subregion ")[-1] hint_data[self.player][location.address] = (subregion.split("Subregion ")[-1]
+ (" Region" if subregion not in + (" Region" if subregion not in
single_location_regions else "")) single_location_regions else ""))
@ -202,14 +197,13 @@ class FFMQWorld(World):
for location in exit_check.connected_region.locations: for location in exit_check.connected_region.locations:
if location.address: if location.address:
hint = [] hint = []
if self.multiworld.map_shuffle[self.player] != "dungeons": if self.options.map_shuffle != "dungeons":
hint.append((subregion.split("Subregion ")[-1] + (" Region" if subregion not hint.append((subregion.split("Subregion ")[-1] + (" Region" if subregion not
in single_location_regions else ""))) in single_location_regions else "")))
if self.multiworld.map_shuffle[self.player] != "overworld" and subregion not in \ if self.options.map_shuffle != "overworld":
("Subregion Mac's Ship", "Subregion Doom Castle"):
hint.append(overworld_spot.name.split("Overworld - ")[-1].replace("Pazuzu", hint.append(overworld_spot.name.split("Overworld - ")[-1].replace("Pazuzu",
"Pazuzu's")) "Pazuzu's"))
hint = " - ".join(hint) hint = " - ".join(hint).replace(" - Mac Ship", "")
if location.address in hint_data[self.player]: if location.address in hint_data[self.player]:
hint_data[self.player][location.address] += f"/{hint}" hint_data[self.player][location.address] += f"/{hint}"
else: else:

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff