Archipelago/worlds/ffmq/__init__.py

222 lines
10 KiB
Python
Raw Normal View History

import Utils
import settings
import base64
import threading
import requests
import yaml
from worlds.AutoWorld import World, WebWorld
from BaseClasses import Tutorial
from .Regions import create_regions, location_table, set_rules, stage_set_rules, rooms, non_dead_end_crest_rooms,\
non_dead_end_crest_warps
from .Items import item_table, item_groups, create_items, FFMQItem, fillers
from .Output import generate_output
from .Options import FFMQOptions
from .Client import FFMQClient
# removed until lists are supported
# class FFMQSettings(settings.Group):
# class APIUrls(list):
# """A list of API URLs to get map shuffle, crest shuffle, and battlefield reward shuffle data from."""
# api_urls: APIUrls = [
# "https://api.ffmqrando.net/",
# "http://ffmqr.jalchavware.com:5271/"
# ]
class FFMQWebWorld(WebWorld):
Docs: Add FFMQ French Setup Guide + Minor fixes to English Guide (#3590) * Add docs * Fix character * Configuration Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * ajuster Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * inclure Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * doublon Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * remplissage Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * autre Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * pouvoir Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * mappemonde Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * virgule Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * fournir Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes 2 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * snes9x Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes 3 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * options Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * lien Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * de laquelle Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * Étape de génération Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes 4 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * également Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * guillemets Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * guillemets 2 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * adresse Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * Connect Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * seed Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * Changer fichier yaml pour de configuration * Fix capitalization Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Fix capitalization 2 Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Fix typo+Add link to fr/en info page --------- Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
2024-07-31 15:40:45 +00:00
setup_en = Tutorial(
"Multiworld Setup Guide",
"A guide to playing Final Fantasy Mystic Quest with Archipelago.",
"English",
"setup_en.md",
"setup/en",
["Alchav"]
Docs: Add FFMQ French Setup Guide + Minor fixes to English Guide (#3590) * Add docs * Fix character * Configuration Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * ajuster Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * inclure Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * doublon Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * remplissage Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * autre Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * pouvoir Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * mappemonde Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * virgule Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * fournir Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes 2 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * snes9x Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes 3 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * options Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * lien Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * de laquelle Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * Étape de génération Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * apostrophes 4 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * également Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * guillemets Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * guillemets 2 Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * adresse Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * Connect Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * seed Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> * Changer fichier yaml pour de configuration * Fix capitalization Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Fix capitalization 2 Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Fix typo+Add link to fr/en info page --------- Co-authored-by: Jérémie Bolduc <16137441+Jouramie@users.noreply.github.com> Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
2024-07-31 15:40:45 +00:00
)
setup_fr = Tutorial(
setup_en.tutorial_name,
setup_en.description,
"Français",
"setup_fr.md",
"setup/fr",
["Artea"]
)
tutorials = [setup_en, setup_fr]
class FFMQWorld(World):
"""Final Fantasy: Mystic Quest is a simple, humorous RPG for the Super Nintendo. You travel across four continents,
linked in the middle of the world by the Focus Tower, which has been locked by four magical coins. Make your way to
the bottom of the Focus Tower, then straight up through the top!"""
# -Giga Otomia
game = "Final Fantasy Mystic Quest"
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
options_dataclass = FFMQOptions
options: FFMQOptions
topology_present = True
item_name_groups = item_groups
generate_output = generate_output
create_items = create_items
create_regions = create_regions
set_rules = set_rules
stage_set_rules = stage_set_rules
web = FFMQWebWorld()
# settings: FFMQSettings
def __init__(self, world, player: int):
self.rom_name_available_event = threading.Event()
self.rom_name = None
self.rooms = None
super().__init__(world, player)
def generate_early(self):
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):
# api_urls = Utils.get_options()["ffmq_options"].get("api_urls", None)
api_urls = [
"https://api.ffmqrando.net/",
"http://ffmqr.jalchavware.com:5271/"
]
rooms_data = {}
for world in multiworld.get_game_worlds("Final Fantasy Mystic Quest"):
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 = 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}"
if query in rooms_data:
world.rooms = rooms_data[query]
continue
if not api_urls:
raise Exception("No FFMQR API URLs specified in host.yaml")
errors = []
for api_url in api_urls.copy():
try:
response = requests.get(f"{api_url}GenerateRooms?{query}")
except (ConnectionError, requests.exceptions.HTTPError, requests.exceptions.ConnectionError,
requests.exceptions.RequestException) as err:
api_urls.remove(api_url)
errors.append([api_url, err])
else:
if response.ok:
world.rooms = rooms_data[query] = yaml.load(response.text, yaml.Loader)
break
else:
api_urls.remove(api_url)
errors.append([api_url, response])
else:
error_text = f"Failed to fetch map shuffle data for FFMQ player {world.player}"
for error in errors:
error_text += f"\n{error[0]} - got error {error[1].status_code} {error[1].reason} {error[1].text}"
raise Exception(error_text)
api_urls.append(api_urls.pop(0))
else:
world.rooms = rooms
def create_item(self, name: str):
return FFMQItem(name, self.player)
def collect_item(self, state, item, remove=False):
if "Progressive" in item.name:
i = item.code - 256
if state.has(self.item_id_to_name[i], self.player):
if state.has(self.item_id_to_name[i+1], self.player):
return self.item_id_to_name[i+2]
return self.item_id_to_name[i+1]
return self.item_id_to_name[i]
return item.name if item.advancement else None
def modify_multidata(self, multidata):
# wait for self.rom_name to be available.
self.rom_name_available_event.wait()
rom_name = getattr(self, "rom_name", None)
# we skip in case of error, so that the original error in the output thread is the one that gets raised
if rom_name:
new_name = base64.b64encode(bytes(self.rom_name)).decode()
payload = multidata["connect_names"][self.multiworld.player_name[self.player]]
multidata["connect_names"][new_name] = payload
def get_filler_item_name(self):
r = self.multiworld.random.randint(0, 201)
for item, count in fillers.items():
r -= count
r -= fillers[item]
if r <= 0:
return item
def extend_hint_information(self, hint_data):
hint_data[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.options.map_shuffle != "dungeons":
hint_data[self.player][location.address] = (subregion.split("Subregion ")[-1]
+ (" Region" if subregion not in
single_location_regions else ""))
for overworld_spot in region.exits:
if ("Subregion" in overworld_spot.connected_region.name or
overworld_spot.name == "Overworld - Mac Ship Doom" or "Focus Tower" in overworld_spot.name
or "Doom Castle" in overworld_spot.name or overworld_spot.name == "Overworld - Giant Tree"):
continue
exits = list(overworld_spot.connected_region.exits) + [overworld_spot]
checked_regions = set()
while exits:
exit_check = exits.pop()
if (exit_check.connected_region not in checked_regions and "Subregion" not in
exit_check.connected_region.name):
checked_regions.add(exit_check.connected_region)
exits.extend(exit_check.connected_region.exits)
for location in exit_check.connected_region.locations:
if location.address:
hint = []
if self.options.map_shuffle != "dungeons":
hint.append((subregion.split("Subregion ")[-1] + (" Region" if subregion not
in single_location_regions else "")))
if self.options.map_shuffle != "overworld":
hint.append(overworld_spot.name.split("Overworld - ")[-1].replace("Pazuzu",
"Pazuzu's"))
hint = " - ".join(hint).replace(" - Mac Ship", "")
if location.address in hint_data[self.player]:
hint_data[self.player][location.address] += f"/{hint}"
else:
hint_data[self.player][location.address] = hint