* 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>
		
			
				
	
	
		
			222 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
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):
 | 
						|
    setup_en = Tutorial(
 | 
						|
        "Multiworld Setup Guide",
 | 
						|
        "A guide to playing Final Fantasy Mystic Quest with Archipelago.",
 | 
						|
        "English",
 | 
						|
        "setup_en.md",
 | 
						|
        "setup/en",
 | 
						|
        ["Alchav"]
 | 
						|
        )
 | 
						|
    
 | 
						|
    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
 |