Merge branch 'main' into tunc-combat-logic

This commit is contained in:
Scipio Wright
2024-07-24 09:52:37 -04:00
committed by GitHub
30 changed files with 472 additions and 6670 deletions

2
.gitignore vendored
View File

@@ -150,7 +150,7 @@ venv/
ENV/
env.bak/
venv.bak/
.code-workspace
*.code-workspace
shell.nix
# Spyder project settings

View File

@@ -8,11 +8,15 @@ from .Locations import DLCQuestLocation, location_table
from .Options import DLCQuestOptions
from .Regions import create_regions
from .Rules import set_rules
from .presets import dlcq_options_presets
from .option_groups import dlcq_option_groups
client_version = 0
class DLCqwebworld(WebWorld):
options_presets = dlcq_options_presets
option_groups = dlcq_option_groups
setup_en = Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Archipelago DLCQuest game on your computer.",

View File

@@ -0,0 +1,27 @@
from typing import List
from Options import ProgressionBalancing, Accessibility, OptionGroup
from .Options import (Campaign, ItemShuffle, TimeIsMoney, EndingChoice, PermanentCoins, DoubleJumpGlitch, CoinSanity,
CoinSanityRange, DeathLink)
dlcq_option_groups: List[OptionGroup] = [
OptionGroup("General", [
Campaign,
ItemShuffle,
CoinSanity,
]),
OptionGroup("Customization", [
EndingChoice,
PermanentCoins,
CoinSanityRange,
]),
OptionGroup("Tedious and Grind", [
TimeIsMoney,
DoubleJumpGlitch,
]),
OptionGroup("Advanced Options", [
DeathLink,
ProgressionBalancing,
Accessibility,
]),
]

View File

@@ -0,0 +1,68 @@
from typing import Any, Dict
from .Options import DoubleJumpGlitch, CoinSanity, CoinSanityRange, PermanentCoins, TimeIsMoney, EndingChoice, Campaign, ItemShuffle
all_random_settings = {
DoubleJumpGlitch.internal_name: "random",
CoinSanity.internal_name: "random",
CoinSanityRange.internal_name: "random",
PermanentCoins.internal_name: "random",
TimeIsMoney.internal_name: "random",
EndingChoice.internal_name: "random",
Campaign.internal_name: "random",
ItemShuffle.internal_name: "random",
"death_link": "random",
}
main_campaign_settings = {
DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_none,
CoinSanity.internal_name: CoinSanity.option_coin,
CoinSanityRange.internal_name: 30,
PermanentCoins.internal_name: PermanentCoins.option_false,
TimeIsMoney.internal_name: TimeIsMoney.option_required,
EndingChoice.internal_name: EndingChoice.option_true,
Campaign.internal_name: Campaign.option_basic,
ItemShuffle.internal_name: ItemShuffle.option_shuffled,
}
lfod_campaign_settings = {
DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_none,
CoinSanity.internal_name: CoinSanity.option_coin,
CoinSanityRange.internal_name: 30,
PermanentCoins.internal_name: PermanentCoins.option_false,
TimeIsMoney.internal_name: TimeIsMoney.option_required,
EndingChoice.internal_name: EndingChoice.option_true,
Campaign.internal_name: Campaign.option_live_freemium_or_die,
ItemShuffle.internal_name: ItemShuffle.option_shuffled,
}
easy_settings = {
DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_none,
CoinSanity.internal_name: CoinSanity.option_none,
CoinSanityRange.internal_name: 40,
PermanentCoins.internal_name: PermanentCoins.option_true,
TimeIsMoney.internal_name: TimeIsMoney.option_required,
EndingChoice.internal_name: EndingChoice.option_true,
Campaign.internal_name: Campaign.option_both,
ItemShuffle.internal_name: ItemShuffle.option_shuffled,
}
hard_settings = {
DoubleJumpGlitch.internal_name: DoubleJumpGlitch.option_simple,
CoinSanity.internal_name: CoinSanity.option_coin,
CoinSanityRange.internal_name: 30,
PermanentCoins.internal_name: PermanentCoins.option_false,
TimeIsMoney.internal_name: TimeIsMoney.option_optional,
EndingChoice.internal_name: EndingChoice.option_true,
Campaign.internal_name: Campaign.option_both,
ItemShuffle.internal_name: ItemShuffle.option_shuffled,
}
dlcq_options_presets: Dict[str, Dict[str, Any]] = {
"All random": all_random_settings,
"Main campaign": main_campaign_settings,
"LFOD campaign": lfod_campaign_settings,
"Both easy": easy_settings,
"Both hard": hard_settings,
}

View File

@@ -71,7 +71,7 @@ class FFMQClient(SNIClient):
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)
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
def get_range(data_range):

View File

@@ -222,10 +222,10 @@ for item, data in item_table.items():
def create_items(self) -> None:
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("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"))
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):
if item_name in ["Steel Armor", "Sky Fragment"] or "Progressive" in item_name:
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
if self.multiworld.progressive_gear[self.player]:
if self.options.progressive_gear:
for item_group in prog_map:
if item_name in self.item_name_groups[item_group]:
item_name = prog_map[item_group]
break
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):
items.append(self.create_item("Sky Fragment"))
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())
return
if item_name in precollected_item_names:
items.append(self.create_filler())
return
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
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"):
i.classification = ItemClassification.progression
items.append(i)
@@ -263,11 +263,11 @@ def create_items(self) -> None:
for item in self.item_name_groups[item_group]:
add_item(item)
if self.multiworld.brown_boxes[self.player] == "include":
if self.options.brown_boxes == "include":
filler_items = []
for item, count in fillers.items():
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)
filler_items = filler_items[39:]
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):
@@ -321,36 +322,36 @@ class KaelisMomFightsMinotaur(Toggle):
default = 0
option_definitions = {
"logic": Logic,
"brown_boxes": BrownBoxes,
"sky_coin_mode": SkyCoinMode,
"shattered_sky_coin_quantity": ShatteredSkyCoinQuantity,
"starting_weapon": StartingWeapon,
"progressive_gear": ProgressiveGear,
"leveling_curve": LevelingCurve,
"starting_companion": StartingCompanion,
"available_companions": AvailableCompanions,
"companions_locations": CompanionsLocations,
"kaelis_mom_fight_minotaur": KaelisMomFightsMinotaur,
"companion_leveling_type": CompanionLevelingType,
"companion_spellbook_type": CompanionSpellbookType,
"enemies_density": EnemiesDensity,
"enemies_scaling_lower": EnemiesScalingLower,
"enemies_scaling_upper": EnemiesScalingUpper,
"bosses_scaling_lower": BossesScalingLower,
"bosses_scaling_upper": BossesScalingUpper,
"enemizer_attacks": EnemizerAttacks,
"enemizer_groups": EnemizerGroups,
"shuffle_res_weak_types": ShuffleResWeakType,
"shuffle_enemies_position": ShuffleEnemiesPositions,
"progressive_formations": ProgressiveFormations,
"doom_castle_mode": DoomCastle,
"doom_castle_shortcut": DoomCastleShortcut,
"tweak_frustrating_dungeons": TweakFrustratingDungeons,
"map_shuffle": MapShuffle,
"crest_shuffle": CrestShuffle,
"shuffle_battlefield_rewards": ShuffleBattlefieldRewards,
"map_shuffle_seed": MapShuffleSeed,
"battlefields_battles_quantities": BattlefieldsBattlesQuantities,
}
@dataclass
class FFMQOptions(PerGameCommonOptions):
logic: Logic
brown_boxes: BrownBoxes
sky_coin_mode: SkyCoinMode
shattered_sky_coin_quantity: ShatteredSkyCoinQuantity
starting_weapon: StartingWeapon
progressive_gear: ProgressiveGear
leveling_curve: LevelingCurve
starting_companion: StartingCompanion
available_companions: AvailableCompanions
companions_locations: CompanionsLocations
kaelis_mom_fight_minotaur: KaelisMomFightsMinotaur
companion_leveling_type: CompanionLevelingType
companion_spellbook_type: CompanionSpellbookType
enemies_density: EnemiesDensity
enemies_scaling_lower: EnemiesScalingLower
enemies_scaling_upper: EnemiesScalingUpper
bosses_scaling_lower: BossesScalingLower
bosses_scaling_upper: BossesScalingUpper
enemizer_attacks: EnemizerAttacks
enemizer_groups: EnemizerGroups
shuffle_res_weak_types: ShuffleResWeakType
shuffle_enemies_position: ShuffleEnemiesPositions
progressive_formations: ProgressiveFormations
doom_castle_mode: DoomCastle
doom_castle_shortcut: DoomCastleShortcut
tweak_frustrating_dungeons: TweakFrustratingDungeons
map_shuffle: MapShuffle
crest_shuffle: CrestShuffle
shuffle_battlefield_rewards: ShuffleBattlefieldRewards
map_shuffle_seed: MapShuffleSeed
battlefields_battles_quantities: BattlefieldsBattlesQuantities

View File

@@ -1,13 +1,13 @@
import yaml
import os
import zipfile
import Utils
from copy import deepcopy
from .Regions import object_id_table
from Utils import __version__
from worlds.Files import APPatch
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):
@@ -21,7 +21,7 @@ def generate_output(self, output_directory):
item_name = "".join(item_name.split(" "))
else:
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"
else:
item_name = "APItemFiller"
@@ -46,60 +46,60 @@ def generate_output(self, output_directory):
options = deepcopy(settings_template)
options["name"] = self.multiworld.player_name[self.player]
option_writes = {
"enemies_density": cc(self.multiworld.enemies_density[self.player]),
"enemies_density": cc(self.options.enemies_density),
"chests_shuffle": "Include",
"shuffle_boxes_content": self.multiworld.brown_boxes[self.player] == "shuffle",
"shuffle_boxes_content": self.options.brown_boxes == "shuffle",
"npcs_shuffle": "Include",
"battlefields_shuffle": "Include",
"logic_options": cc(self.multiworld.logic[self.player]),
"shuffle_enemies_position": tf(self.multiworld.shuffle_enemies_position[self.player]),
"enemies_scaling_lower": cc(self.multiworld.enemies_scaling_lower[self.player]),
"enemies_scaling_upper": cc(self.multiworld.enemies_scaling_upper[self.player]),
"bosses_scaling_lower": cc(self.multiworld.bosses_scaling_lower[self.player]),
"bosses_scaling_upper": cc(self.multiworld.bosses_scaling_upper[self.player]),
"enemizer_attacks": cc(self.multiworld.enemizer_attacks[self.player]),
"leveling_curve": cc(self.multiworld.leveling_curve[self.player]),
"battles_quantity": cc(self.multiworld.battlefields_battles_quantities[self.player]) if
self.multiworld.battlefields_battles_quantities[self.player].value < 5 else
"logic_options": cc(self.options.logic),
"shuffle_enemies_position": tf(self.options.shuffle_enemies_position),
"enemies_scaling_lower": cc(self.options.enemies_scaling_lower),
"enemies_scaling_upper": cc(self.options.enemies_scaling_upper),
"bosses_scaling_lower": cc(self.options.bosses_scaling_lower),
"bosses_scaling_upper": cc(self.options.bosses_scaling_upper),
"enemizer_attacks": cc(self.options.enemizer_attacks),
"leveling_curve": cc(self.options.leveling_curve),
"battles_quantity": cc(self.options.battlefields_battles_quantities) if
self.options.battlefields_battles_quantities.value < 5 else
"RandomLow" if
self.multiworld.battlefields_battles_quantities[self.player].value == 5 else
self.options.battlefields_battles_quantities.value == 5 else
"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,
"progressive_gear": tf(self.multiworld.progressive_gear[self.player]),
"tweaked_dungeons": tf(self.multiworld.tweak_frustrating_dungeons[self.player]),
"doom_castle_mode": cc(self.multiworld.doom_castle_mode[self.player]),
"doom_castle_shortcut": tf(self.multiworld.doom_castle_shortcut[self.player]),
"sky_coin_mode": cc(self.multiworld.sky_coin_mode[self.player]),
"sky_coin_fragments_qty": cc(self.multiworld.shattered_sky_coin_quantity[self.player]),
"progressive_gear": tf(self.options.progressive_gear),
"tweaked_dungeons": tf(self.options.tweak_frustrating_dungeons),
"doom_castle_mode": cc(self.options.doom_castle_mode),
"doom_castle_shortcut": tf(self.options.doom_castle_shortcut),
"sky_coin_mode": cc(self.options.sky_coin_mode),
"sky_coin_fragments_qty": cc(self.options.shattered_sky_coin_quantity),
"enable_spoilers": False,
"progressive_formations": cc(self.multiworld.progressive_formations[self.player]),
"map_shuffling": cc(self.multiworld.map_shuffle[self.player]),
"crest_shuffle": tf(self.multiworld.crest_shuffle[self.player]),
"enemizer_groups": cc(self.multiworld.enemizer_groups[self.player]),
"shuffle_res_weak_type": tf(self.multiworld.shuffle_res_weak_types[self.player]),
"companion_leveling_type": cc(self.multiworld.companion_leveling_type[self.player]),
"companion_spellbook_type": cc(self.multiworld.companion_spellbook_type[self.player]),
"starting_companion": cc(self.multiworld.starting_companion[self.player]),
"progressive_formations": cc(self.options.progressive_formations),
"map_shuffling": cc(self.options.map_shuffle),
"crest_shuffle": tf(self.options.crest_shuffle),
"enemizer_groups": cc(self.options.enemizer_groups),
"shuffle_res_weak_type": tf(self.options.shuffle_res_weak_types),
"companion_leveling_type": cc(self.options.companion_leveling_type),
"companion_spellbook_type": cc(self.options.companion_spellbook_type),
"starting_companion": cc(self.options.starting_companion),
"available_companions": ["Zero", "One", "Two",
"Three", "Four"][self.multiworld.available_companions[self.player].value],
"companions_locations": cc(self.multiworld.companions_locations[self.player]),
"kaelis_mom_fight_minotaur": tf(self.multiworld.kaelis_mom_fight_minotaur[self.player]),
"Three", "Four"][self.options.available_companions.value],
"companions_locations": cc(self.options.companions_locations),
"kaelis_mom_fight_minotaur": tf(self.options.kaelis_mom_fight_minotaur),
}
for option, data in option_writes.items():
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,
'utf8')
self.rom_name_available_event.set()
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]]
if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin":
if self.options.sky_coin_mode == "shattered_sky_coin":
starting_items.append("SkyCoin")
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 worlds.generic.Rules import add_rule
from .data.rooms import rooms, entrances
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 yaml.load(pkgutil.get_data(__name__, "data/entrances.yaml"), yaml.Loader)}
entrance_names = {entrance["id"]: entrance["name"] for entrance in entrances}
object_id_table = {}
object_type_table = {}
@@ -69,7 +67,7 @@ def create_regions(self):
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
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"]))
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:
spoiler = False
if link["entrance"] in crest_warps:
if self.multiworld.crest_shuffle[self.player]:
if self.options.crest_shuffle:
spoiler = True
elif self.multiworld.map_shuffle[self.player] == "everything":
elif self.options.map_shuffle == "everything":
spoiler = True
elif "Subregion" in region.name and self.multiworld.map_shuffle[self.player] not in ("dungeons",
"none"):
elif "Subregion" in region.name and self.options.map_shuffle not in ("dungeons", "none"):
spoiler = True
elif "Subregion" not in region.name and self.multiworld.map_shuffle[self.player] not in ("none",
"overworld"):
elif "Subregion" not in region.name and self.options.map_shuffle not in ("none", "overworld"):
spoiler = True
if spoiler:
@@ -111,6 +107,7 @@ def create_regions(self):
connection.connect(connect_room)
break
non_dead_end_crest_rooms = [
'Libra Temple', 'Aquaria Gemini Room', "GrenadeMan's Mobius Room", 'Fireburg Gemini Room',
'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("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"):
loc = self.multiworld.get_location(boss, self.player)
checked_regions = {loc.parent_region}
@@ -158,12 +155,12 @@ def set_rules(self) -> None:
return True
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),
["MagicMirror"])
process_rules(self.multiworld.get_entrance("Overworld - Volcano", self.player),
["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),
["Bomb"])
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),
["DragonClaw", "CaptainCap"])
if self.multiworld.logic[self.player] == "expert":
if self.multiworld.map_shuffle[self.player] == "none" and not self.multiworld.crest_shuffle[self.player]:
if self.options.logic == "expert":
if self.options.map_shuffle == "none" and not self.options.crest_shuffle:
inner_room = self.multiworld.get_region("Wintry Temple Inner Room", self.player)
connection = Entrance(self.player, "Sealed Temple Exit Trick", inner_room)
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:
entrance.access_rule = lambda state: False
if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin":
logic_coins = [16, 24, 32, 32, 38][self.multiworld.shattered_sky_coin_quantity[self.player].value]
if self.options.sky_coin_mode == "shattered_sky_coin":
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 = \
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 = \
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 = \
lambda state: state.has("Sky Coin", self.player)
@@ -213,26 +210,24 @@ def set_rules(self) -> None:
def stage_set_rules(multiworld):
# 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")
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,
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 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
else:
multiworld.get_location(location, player).access_rule = lambda state: False
else:
# 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 location in vendor_locations:
multiworld.get_location(location, player).item_rule = lambda item: not item.advancement
class FFMQLocation(Location):
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
from .Items import item_table, item_groups, create_items, FFMQItem, fillers
from .Output import generate_output
from .Options import option_definitions
from .Options import FFMQOptions
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}
location_name_to_id = location_table
option_definitions = option_definitions
options_dataclass = FFMQOptions
options: FFMQOptions
topology_present = True
@@ -67,20 +68,14 @@ class FFMQWorld(World):
super().__init__(world, player)
def generate_early(self):
if self.multiworld.sky_coin_mode[self.player] == "shattered_sky_coin":
self.multiworld.brown_boxes[self.player].value = 1
if self.multiworld.enemies_scaling_lower[self.player].value > \
self.multiworld.enemies_scaling_upper[self.player].value:
(self.multiworld.enemies_scaling_lower[self.player].value,
self.multiworld.enemies_scaling_upper[self.player].value) =\
(self.multiworld.enemies_scaling_upper[self.player].value,
self.multiworld.enemies_scaling_lower[self.player].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)
if self.options.sky_coin_mode == "shattered_sky_coin":
self.options.brown_boxes.value = 1
if self.options.enemies_scaling_lower.value > self.options.enemies_scaling_upper.value:
self.options.enemies_scaling_lower.value, self.options.enemies_scaling_upper.value = \
self.options.enemies_scaling_upper.value, self.options.enemies_scaling_lower.value
if self.options.bosses_scaling_lower.value > self.options.bosses_scaling_upper.value:
self.options.bosses_scaling_lower.value, self.options.bosses_scaling_upper.value = \
self.options.bosses_scaling_upper.value, self.options.bosses_scaling_lower.value
@classmethod
def stage_generate_early(cls, multiworld):
@@ -94,20 +89,20 @@ class FFMQWorld(World):
rooms_data = {}
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
world.multiworld.crest_shuffle[world.player]):
if world.multiworld.map_shuffle_seed[world.player].value.isdigit():
multiworld.random.seed(int(world.multiworld.map_shuffle_seed[world.player].value))
elif world.multiworld.map_shuffle_seed[world.player].value != "random":
multiworld.random.seed(int(hash(world.multiworld.map_shuffle_seed[world.player].value))
+ int(world.multiworld.seed))
if (world.options.map_shuffle or world.options.crest_shuffle or world.options.shuffle_battlefield_rewards
or world.options.companions_locations):
if world.options.map_shuffle_seed.value.isdigit():
multiworld.random.seed(int(world.options.map_shuffle_seed.value))
elif world.options.map_shuffle_seed.value != "random":
multiworld.random.seed(int(hash(world.options.map_shuffle_seed.value))
+ int(world.multiworld.seed))
seed = hex(multiworld.random.randint(0, 0xFFFFFFFF)).split("0x")[1].upper()
map_shuffle = multiworld.map_shuffle[world.player].value
crest_shuffle = multiworld.crest_shuffle[world.player].current_key
battlefield_shuffle = multiworld.shuffle_battlefield_rewards[world.player].current_key
companion_shuffle = multiworld.companions_locations[world.player].value
kaeli_mom = multiworld.kaelis_mom_fight_minotaur[world.player].current_key
map_shuffle = world.options.map_shuffle.value
crest_shuffle = world.options.crest_shuffle.current_key
battlefield_shuffle = world.options.shuffle_battlefield_rewards.current_key
companion_shuffle = world.options.companions_locations.value
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}"
@@ -175,14 +170,14 @@ class FFMQWorld(World):
def extend_hint_information(self, hint_data):
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"]
for subregion in ["Subregion Foresta", "Subregion Aquaria", "Subregion Frozen Fields", "Subregion Fireburg",
"Subregion Volcano Battlefield", "Subregion Windia", "Subregion Mac's Ship",
"Subregion Doom Castle"]:
region = self.multiworld.get_region(subregion, self.player)
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]
+ (" Region" if subregion not in
single_location_regions else ""))
@@ -202,14 +197,13 @@ class FFMQWorld(World):
for location in exit_check.connected_region.locations:
if location.address:
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
in single_location_regions else "")))
if self.multiworld.map_shuffle[self.player] != "overworld" and subregion not in \
("Subregion Mac's Ship", "Subregion Doom Castle"):
if self.options.map_shuffle != "overworld":
hint.append(overworld_spot.name.split("Overworld - ")[-1].replace("Pazuzu",
"Pazuzu's"))
hint = " - ".join(hint)
hint = " - ".join(hint).replace(" - Mac Ship", "")
if location.address in hint_data[self.player]:
hint_data[self.player][location.address] += f"/{hint}"
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

View File

@@ -116,12 +116,19 @@ class KH2Context(CommonContext):
# self.inBattle = 0x2A0EAC4 + 0x40
# self.onDeath = 0xAB9078
# PC Address anchors
self.Now = 0x0714DB8
self.Save = 0x09A70B0
# self.Now = 0x0714DB8 old address
# epic addresses
self.Now = 0x0716DF8
self.Save = 0x09A92F0
self.Journal = 0x743260
self.Shop = 0x743350
self.Slot1 = 0x2A22FD8
# self.Sys3 = 0x2A59DF0
# self.Bt10 = 0x2A74880
# self.BtlEnd = 0x2A0D3E0
self.Slot1 = 0x2A20C98
# self.Slot1 = 0x2A20C98 old address
self.kh2_game_version = None # can be egs or steam
self.chest_set = set(exclusion_table["Chests"])
self.keyblade_set = set(CheckDupingItems["Weapons"]["Keyblades"])
@@ -228,6 +235,9 @@ class KH2Context(CommonContext):
def kh2_write_int(self, address, value):
self.kh2.write_int(self.kh2.base_address + address, value)
def kh2_read_string(self, address, length):
return self.kh2.read_string(self.kh2.base_address + address, length)
def on_package(self, cmd: str, args: dict):
if cmd in {"RoomInfo"}:
self.kh2seedname = args['seed_name']
@@ -367,10 +377,26 @@ class KH2Context(CommonContext):
for weapon_location in all_weapon_slot:
all_weapon_location_id.append(self.kh2_loc_name_to_id[weapon_location])
self.all_weapon_location_id = set(all_weapon_location_id)
try:
self.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX")
logger.info("You are now auto-tracking")
self.kh2connected = True
if self.kh2_game_version is None:
if self.kh2_read_string(0x09A9830, 4) == "KH2J":
self.kh2_game_version = "STEAM"
self.Now = 0x0717008
self.Save = 0x09A9830
self.Slot1 = 0x2A23518
self.Journal = 0x7434E0
self.Shop = 0x7435D0
elif self.kh2_read_string(0x09A92F0, 4) == "KH2J":
self.kh2_game_version = "EGS"
else:
self.kh2_game_version = None
logger.info("Your game version is out of date. Please update your game via The Epic Games Store or Steam.")
if self.kh2_game_version is not None:
logger.info(f"You are now auto-tracking. {self.kh2_game_version}")
self.kh2connected = True
except Exception as e:
if self.kh2connected:
@@ -589,8 +615,8 @@ class KH2Context(CommonContext):
# if journal=-1 and shop = 5 then in shop
# if journal !=-1 and shop = 10 then journal
journal = self.kh2_read_short(0x741230)
shop = self.kh2_read_short(0x741320)
journal = self.kh2_read_short(self.Journal)
shop = self.kh2_read_short(self.Shop)
if (journal == -1 and shop == 5) or (journal != -1 and shop == 10):
# print("your in the shop")
sellable_dict = {}
@@ -599,8 +625,8 @@ class KH2Context(CommonContext):
amount = self.kh2_read_byte(self.Save + itemdata.memaddr)
sellable_dict[itemName] = amount
while (journal == -1 and shop == 5) or (journal != -1 and shop == 10):
journal = self.kh2_read_short(0x741230)
shop = self.kh2_read_short(0x741320)
journal = self.kh2_read_short(self.Journal)
shop = self.kh2_read_short(self.Shop)
await asyncio.sleep(0.5)
for item, amount in sellable_dict.items():
itemdata = self.item_name_to_data[item]
@@ -750,7 +776,7 @@ class KH2Context(CommonContext):
item_data = self.item_name_to_data[item_name]
amount_of_items = 0
amount_of_items += self.kh2_seed_save_cache["AmountInvo"]["Magic"][item_name]
if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items and self.kh2_read_byte(0x741320) in {10, 8}:
if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items and self.kh2_read_byte(self.Shop) in {10, 8}:
self.kh2_write_byte(self.Save + item_data.memaddr, amount_of_items)
for item_name in master_stat:
@@ -802,7 +828,7 @@ class KH2Context(CommonContext):
self.kh2_write_byte(self.Save + 0x2502, current_item_slots + 1)
elif self.base_item_slots + amount_of_items < 8:
self.kh2_write_byte(self.Save + 0x2502, self.base_item_slots + amount_of_items)
# if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items \
# and self.kh2_read_byte(self.Slot1 + 0x1B2) >= 5 and \
# self.kh2_read_byte(self.Save + 0x23DF) & 0x1 << 3 > 0 and self.kh2_read_byte(0x741320) in {10, 8}:
@@ -905,8 +931,23 @@ async def kh2_watcher(ctx: KH2Context):
await asyncio.sleep(15)
ctx.kh2 = pymem.Pymem(process_name="KINGDOM HEARTS II FINAL MIX")
if ctx.kh2 is not None:
logger.info("You are now auto-tracking")
ctx.kh2connected = True
if ctx.kh2_game_version is None:
if ctx.kh2_read_string(0x09A9830, 4) == "KH2J":
ctx.kh2_game_version = "STEAM"
ctx.Now = 0x0717008
ctx.Save = 0x09A9830
ctx.Slot1 = 0x2A23518
ctx.Journal = 0x7434E0
ctx.Shop = 0x7435D0
elif ctx.kh2_read_string(0x09A92F0, 4) == "KH2J":
ctx.kh2_game_version = "EGS"
else:
ctx.kh2_game_version = None
logger.info("Your game version is out of date. Please update your game via The Epic Games Store or Steam.")
if ctx.kh2_game_version is not None:
logger.info(f"You are now auto-tracking {ctx.kh2_game_version}")
ctx.kh2connected = True
except Exception as e:
if ctx.kh2connected:
ctx.kh2connected = False

View File

@@ -98,9 +98,12 @@ class LinksAwakeningWorld(World):
# Items can be grouped using their names to allow easy checking if any item
# from that group has been collected. Group names can also be used for !hint
#item_name_groups = {
# "weapons": {"sword", "lance"}
#}
item_name_groups = {
"Instruments": {
"Full Moon Cello", "Conch Horn", "Sea Lily's Bell", "Surf Harp",
"Wind Marimba", "Coral Triangle", "Organ of Evening Calm", "Thunder Drum"
},
}
prefill_dungeon_items = None

View File

@@ -3,7 +3,7 @@ Archipelago init file for Lingo
"""
from logging import warning
from BaseClasses import Item, ItemClassification, Tutorial
from BaseClasses import CollectionState, Item, ItemClassification, Tutorial
from Options import OptionError
from worlds.AutoWorld import WebWorld, World
from .datatypes import Room, RoomEntrance
@@ -68,6 +68,37 @@ class LingoWorld(World):
def create_regions(self):
create_regions(self)
if not self.options.shuffle_postgame:
state = CollectionState(self.multiworld)
state.collect(LingoItem("Prevent Victory", ItemClassification.progression, None, self.player), True)
# Note: relies on the assumption that real_items is a definitive list of real progression items in this
# world, and is not modified after being created.
for item in self.player_logic.real_items:
state.collect(self.create_item(item), True)
# Exception to the above: a forced good item is not considered a "real item", but needs to be here anyway.
if self.player_logic.forced_good_item != "":
state.collect(self.create_item(self.player_logic.forced_good_item), True)
all_locations = self.multiworld.get_locations(self.player)
state.sweep_for_events(locations=all_locations)
unreachable_locations = [location for location in all_locations
if not state.can_reach_location(location.name, self.player)]
for location in unreachable_locations:
if location.name in self.player_logic.event_loc_to_item.keys():
continue
self.player_logic.real_locations.remove(location.name)
location.parent_region.locations.remove(location)
if len(self.player_logic.real_items) > len(self.player_logic.real_locations):
raise OptionError(f"{self.player_name}'s Lingo world does not have enough locations to fit the number"
f" of required items without shuffling the postgame. Either enable postgame"
f" shuffling, or choose different options.")
def create_items(self):
pool = [self.create_item(name) for name in self.player_logic.real_items]

View File

@@ -879,6 +879,8 @@
panel: DRAWL + RUNS
- room: Owl Hallway
panel: READS + RUST
- room: Ending Area
panel: THE END
paintings:
- id: eye_painting
disable: True
@@ -2322,7 +2324,7 @@
orientation: east
- id: hi_solved_painting
orientation: west
Orange Tower Seventh Floor:
Ending Area:
entrances:
Orange Tower Sixth Floor:
room: Orange Tower
@@ -2334,6 +2336,18 @@
check: True
tag: forbid
non_counting: True
location_name: Orange Tower Seventh Floor - THE END
doors:
End:
event: True
panels:
- THE END
Orange Tower Seventh Floor:
entrances:
Ending Area:
room: Ending Area
door: End
panels:
THE MASTER:
# We will set up special rules for this in code.
id: Countdown Panels/Panel_master_master

Binary file not shown.

View File

@@ -272,8 +272,9 @@ panels:
PAINTING (4): 445081
PAINTING (5): 445082
ROOM: 445083
Orange Tower Seventh Floor:
Ending Area:
THE END: 444620
Orange Tower Seventh Floor:
THE MASTER: 444621
MASTERY: 444622
Behind A Smile:

View File

@@ -194,6 +194,11 @@ class EarlyColorHallways(Toggle):
display_name = "Early Color Hallways"
class ShufflePostgame(Toggle):
"""When off, locations that could not be reached without also reaching your victory condition are removed."""
display_name = "Shuffle Postgame"
class TrapPercentage(Range):
"""Replaces junk items with traps, at the specified rate."""
display_name = "Trap Percentage"
@@ -263,6 +268,7 @@ class LingoOptions(PerGameCommonOptions):
mastery_achievements: MasteryAchievements
level_2_requirement: Level2Requirement
early_color_hallways: EarlyColorHallways
shuffle_postgame: ShufflePostgame
trap_percentage: TrapPercentage
trap_weights: TrapWeights
puzzle_skip_percentage: PuzzleSkipPercentage

View File

@@ -19,22 +19,25 @@ class AccessRequirements:
doors: Set[RoomAndDoor]
colors: Set[str]
the_master: bool
postgame: bool
def __init__(self):
self.rooms = set()
self.doors = set()
self.colors = set()
self.the_master = False
self.postgame = False
def merge(self, other: "AccessRequirements"):
self.rooms |= other.rooms
self.doors |= other.doors
self.colors |= other.colors
self.the_master |= other.the_master
self.postgame |= other.postgame
def __str__(self):
return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors})," \
f" the_master={self.the_master}"
return f"AccessRequirements(rooms={self.rooms}, doors={self.doors}, colors={self.colors}," \
f" the_master={self.the_master}, postgame={self.postgame})"
class PlayerLocation(NamedTuple):
@@ -190,16 +193,6 @@ class LingoPlayerLogic:
if color_shuffle:
self.real_items += [name for name, item in ALL_ITEM_TABLE.items() if item.type == ItemType.COLOR]
# Create events for each achievement panel, so that we can determine when THE MASTER is accessible.
for room_name, room_data in PANELS_BY_ROOM.items():
for panel_name, panel_data in room_data.items():
if panel_data.achievement:
access_req = AccessRequirements()
access_req.merge(self.calculate_panel_requirements(room_name, panel_name, world))
access_req.rooms.add(room_name)
self.mastery_reqs.append(access_req)
# Handle the victory condition. Victory conditions other than the chosen one become regular checks, so we need
# to prevent the actual victory condition from becoming a check.
self.mastery_location = "Orange Tower Seventh Floor - THE MASTER"
@@ -207,7 +200,7 @@ class LingoPlayerLogic:
if victory_condition == VictoryCondition.option_the_end:
self.victory_condition = "Orange Tower Seventh Floor - THE END"
self.add_location("Orange Tower Seventh Floor", "The End (Solved)", None, [], world)
self.add_location("Ending Area", "The End (Solved)", None, [], world)
self.event_loc_to_item["The End (Solved)"] = "Victory"
elif victory_condition == VictoryCondition.option_the_master:
self.victory_condition = "Orange Tower Seventh Floor - THE MASTER"
@@ -231,6 +224,16 @@ class LingoPlayerLogic:
[RoomAndPanel("Pilgrim Antechamber", "PILGRIM")], world)
self.event_loc_to_item["PILGRIM (Solved)"] = "Victory"
# Create events for each achievement panel, so that we can determine when THE MASTER is accessible.
for room_name, room_data in PANELS_BY_ROOM.items():
for panel_name, panel_data in room_data.items():
if panel_data.achievement:
access_req = AccessRequirements()
access_req.merge(self.calculate_panel_requirements(room_name, panel_name, world))
access_req.rooms.add(room_name)
self.mastery_reqs.append(access_req)
# Create groups of counting panel access requirements for the LEVEL 2 check.
self.create_panel_hunt_events(world)
@@ -470,6 +473,11 @@ class LingoPlayerLogic:
if panel == "THE MASTER":
access_reqs.the_master = True
# Evil python magic (so sayeth NewSoupVi): this checks victory_condition against the panel's location name
# override if it exists, or the auto-generated location name if it's None.
if self.victory_condition == (panel_object.location_name or f"{room} - {panel}"):
access_reqs.postgame = True
self.panel_reqs[room][panel] = access_reqs
return self.panel_reqs[room][panel]

View File

@@ -62,6 +62,9 @@ def _lingo_can_satisfy_requirements(state: CollectionState, access: AccessRequir
if access.the_master and not lingo_can_use_mastery_location(state, world):
return False
if access.postgame and state.has("Prevent Victory", world.player):
return False
return True

View File

@@ -5,7 +5,8 @@ class TestMasteryWhenVictoryIsTheEnd(LingoTestBase):
options = {
"mastery_achievements": "22",
"victory_condition": "the_end",
"shuffle_colors": "true"
"shuffle_colors": "true",
"shuffle_postgame": "true",
}
def test_requirement(self):
@@ -43,7 +44,8 @@ class TestMasteryBlocksDependents(LingoTestBase):
options = {
"mastery_achievements": "24",
"shuffle_colors": "true",
"location_checks": "insanity"
"location_checks": "insanity",
"victory_condition": "level_2",
}
def test_requirement(self):

View File

@@ -0,0 +1,62 @@
from . import LingoTestBase
class TestPostgameVanillaTheEnd(LingoTestBase):
options = {
"shuffle_doors": "none",
"victory_condition": "the_end",
"shuffle_postgame": "false",
}
def test_requirement(self):
location_names = [location.name for location in self.multiworld.get_locations(self.player)]
self.assertTrue("The End (Solved)" in location_names)
self.assertTrue("Champion's Rest - YOU" in location_names)
self.assertFalse("Orange Tower Seventh Floor - THE MASTER" in location_names)
self.assertFalse("The Red - Achievement" in location_names)
class TestPostgameComplexDoorsTheEnd(LingoTestBase):
options = {
"shuffle_doors": "complex",
"victory_condition": "the_end",
"shuffle_postgame": "false",
}
def test_requirement(self):
location_names = [location.name for location in self.multiworld.get_locations(self.player)]
self.assertTrue("The End (Solved)" in location_names)
self.assertFalse("Orange Tower Seventh Floor - THE MASTER" in location_names)
self.assertTrue("The Red - Achievement" in location_names)
class TestPostgameLateColorHunt(LingoTestBase):
options = {
"shuffle_doors": "none",
"victory_condition": "the_end",
"sunwarp_access": "disabled",
"shuffle_postgame": "false",
}
def test_requirement(self):
location_names = [location.name for location in self.multiworld.get_locations(self.player)]
self.assertFalse("Champion's Rest - YOU" in location_names)
class TestPostgameVanillaTheMaster(LingoTestBase):
options = {
"shuffle_doors": "none",
"victory_condition": "the_master",
"shuffle_postgame": "false",
}
def test_requirement(self):
location_names = [location.name for location in self.multiworld.get_locations(self.player)]
self.assertTrue("Orange Tower Seventh Floor - THE END" in location_names)
self.assertTrue("Orange Tower Seventh Floor - Mastery Achievements" in location_names)
self.assertTrue("The Red - Achievement" in location_names)
self.assertFalse("Mastery Panels" in location_names)

View File

@@ -80,7 +80,7 @@ def generate_itempool(tlozworld):
location.item.classification = ItemClassification.progression
def get_pool_core(world):
random = world.multiworld.random
random = world.random
pool = []
placed_items = {}
@@ -132,14 +132,6 @@ def get_pool_core(world):
else:
pool.append(fragment)
# Level 9 junk fill
if world.options.ExpandedPool > 0:
spots = random.sample(level_locations[8], len(level_locations[8]) // 2)
for spot in spots:
junk = random.choice(list(minor_items.keys()))
placed_items[spot] = junk
minor_items[junk] -= 1
# Finish Pool
final_pool = basic_pool
if world.options.ExpandedPool:

View File

@@ -99,6 +99,14 @@ shop_locations = [
"Potion Shop Item Left", "Potion Shop Item Middle", "Potion Shop Item Right"
]
take_any_locations = [
"Take Any Item Left", "Take Any Item Middle", "Take Any Item Right"
]
sword_cave_locations = [
"Starting Sword Cave", "White Sword Pond", "Magical Sword Grave"
]
food_locations = [
"Level 7 Map", "Level 7 Boss", "Level 7 Triforce", "Level 7 Key Drop (Goriyas)",
"Level 7 Bomb Drop (Moldorms North)", "Level 7 Bomb Drop (Goriyas North)",

View File

@@ -12,7 +12,8 @@ from BaseClasses import Item, Location, Region, Entrance, MultiWorld, ItemClassi
from .ItemPool import generate_itempool, starting_weapons, dangerous_weapon_locations
from .Items import item_table, item_prices, item_game_ids
from .Locations import location_table, level_locations, major_locations, shop_locations, all_level_locations, \
standard_level_locations, shop_price_location_ids, secret_money_ids, location_ids, food_locations
standard_level_locations, shop_price_location_ids, secret_money_ids, location_ids, food_locations, \
take_any_locations, sword_cave_locations
from .Options import TlozOptions
from .Rom import TLoZDeltaPatch, get_base_rom_path, first_quest_dungeon_items_early, first_quest_dungeon_items_late
from .Rules import set_rules
@@ -87,6 +88,21 @@ class TLoZWorld(World):
}
}
location_name_groups = {
"Shops": set(shop_locations),
"Take Any": set(take_any_locations),
"Sword Caves": set(sword_cave_locations),
"Level 1": set(level_locations[0]),
"Level 2": set(level_locations[1]),
"Level 3": set(level_locations[2]),
"Level 4": set(level_locations[3]),
"Level 5": set(level_locations[4]),
"Level 6": set(level_locations[5]),
"Level 7": set(level_locations[6]),
"Level 8": set(level_locations[7]),
"Level 9": set(level_locations[8])
}
for k, v in item_name_to_id.items():
item_name_to_id[k] = v + base_id
@@ -307,7 +323,7 @@ class TLoZWorld(World):
def get_filler_item_name(self) -> str:
if self.filler_items is None:
self.filler_items = [item for item in item_table if item_table[item].classification == ItemClassification.filler]
return self.multiworld.random.choice(self.filler_items)
return self.random.choice(self.filler_items)
def fill_slot_data(self) -> Dict[str, Any]:
if self.options.ExpandedPool:

View File

@@ -805,7 +805,7 @@ Swamp Rotating Bridge (Swamp) - Swamp Between Bridges Far - 0x181F5 - Swamp Near
159334 - 0x036CE (Rotating Bridge CW EP) - 0x181F5 - True
Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underwater - 0x18482 - Swamp Long Bridge - 0xFFD00 & 0xFFD02 - The Ocean - 0x09DB8:
158903 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True
159803 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True
158328 - 0x09DB8 (Boat Spawn) - True - Boat
158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Rotated Shapers
158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers
@@ -1088,7 +1088,7 @@ Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB
158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry
Elevator (Mountain Bottom Floor):
158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True
158530 - 0x3D9A6 (Elevator Door Close Left) - True - True
158531 - 0x3D9A7 (Elevator Door Close Right) - True - True
158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True
158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True

View File

@@ -805,7 +805,7 @@ Swamp Rotating Bridge (Swamp) - Swamp Between Bridges Far - 0x181F5 - Swamp Near
159334 - 0x036CE (Rotating Bridge CW EP) - 0x181F5 - True
Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underwater - 0x18482 - Swamp Long Bridge - 0xFFD00 & 0xFFD02 - The Ocean - 0x09DB8:
158903 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True
159803 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True
158328 - 0x09DB8 (Boat Spawn) - True - Boat
158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Shapers & Dots & Full Dots
158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers & Shapers & Dots & Full Dots
@@ -1088,7 +1088,7 @@ Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB
158529 - 0x339BB (Left Pillar 4) - 0x03859 - Symmetry & Black/White Squares & Stars & Stars + Same Colored Symbol & Triangles & Colored Dots
Elevator (Mountain Bottom Floor):
158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True
158530 - 0x3D9A6 (Elevator Door Close Left) - True - True
158531 - 0x3D9A7 (Elevator Door Close Right) - True - True
158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True
158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True

View File

@@ -805,7 +805,7 @@ Swamp Rotating Bridge (Swamp) - Swamp Between Bridges Far - 0x181F5 - Swamp Near
159334 - 0x036CE (Rotating Bridge CW EP) - 0x181F5 - True
Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underwater - 0x18482 - Swamp Long Bridge - 0xFFD00 & 0xFFD02 - The Ocean - 0x09DB8:
158903 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True
159803 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True
158328 - 0x09DB8 (Boat Spawn) - True - Boat
158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Rotated Shapers
158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers
@@ -1088,7 +1088,7 @@ Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB
158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry
Elevator (Mountain Bottom Floor):
158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True
158530 - 0x3D9A6 (Elevator Door Close Left) - True - True
158531 - 0x3D9A7 (Elevator Door Close Right) - True - True
158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True
158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True