New Game: Faxanadu (#3059)

This commit is contained in:
David St-Louis 2024-11-29 16:45:36 -05:00 committed by GitHub
parent c97e4866dd
commit 6e5adc7abd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 762 additions and 0 deletions

View File

@ -76,6 +76,7 @@ Currently, the following games are supported:
* Kingdom Hearts 1
* Mega Man 2
* Yacht Dice
* Faxanadu
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled

View File

@ -63,6 +63,9 @@
# Factorio
/worlds/factorio/ @Berserker66
# Faxanadu
/worlds/faxanadu/ @Daivuk
# Final Fantasy Mystic Quest
/worlds/ffmq/ @Alchav @wildham0

58
worlds/faxanadu/Items.py Normal file
View File

@ -0,0 +1,58 @@
from BaseClasses import ItemClassification
from typing import List, Optional
class ItemDef:
def __init__(self,
id: Optional[int],
name: str,
classification: ItemClassification,
count: int,
progression_count: int,
prefill_location: Optional[str]):
self.id = id
self.name = name
self.classification = classification
self.count = count
self.progression_count = progression_count
self.prefill_location = prefill_location
items: List[ItemDef] = [
ItemDef(400000, 'Progressive Sword', ItemClassification.progression, 4, 0, None),
ItemDef(400001, 'Progressive Armor', ItemClassification.progression, 3, 0, None),
ItemDef(400002, 'Progressive Shield', ItemClassification.useful, 4, 0, None),
ItemDef(400003, 'Spring Elixir', ItemClassification.progression, 1, 0, None),
ItemDef(400004, 'Mattock', ItemClassification.progression, 1, 0, None),
ItemDef(400005, 'Unlock Wingboots', ItemClassification.progression, 1, 0, None),
ItemDef(400006, 'Key Jack', ItemClassification.progression, 1, 0, None),
ItemDef(400007, 'Key Queen', ItemClassification.progression, 1, 0, None),
ItemDef(400008, 'Key King', ItemClassification.progression, 1, 0, None),
ItemDef(400009, 'Key Joker', ItemClassification.progression, 1, 0, None),
ItemDef(400010, 'Key Ace', ItemClassification.progression, 1, 0, None),
ItemDef(400011, 'Ring of Ruby', ItemClassification.progression, 1, 0, None),
ItemDef(400012, 'Ring of Dworf', ItemClassification.progression, 1, 0, None),
ItemDef(400013, 'Demons Ring', ItemClassification.progression, 1, 0, None),
ItemDef(400014, 'Black Onyx', ItemClassification.progression, 1, 0, None),
ItemDef(None, 'Sky Spring Flow', ItemClassification.progression, 1, 0, 'Sky Spring'),
ItemDef(None, 'Tower of Fortress Spring Flow', ItemClassification.progression, 1, 0, 'Tower of Fortress Spring'),
ItemDef(None, 'Joker Spring Flow', ItemClassification.progression, 1, 0, 'Joker Spring'),
ItemDef(400015, 'Deluge', ItemClassification.progression, 1, 0, None),
ItemDef(400016, 'Thunder', ItemClassification.useful, 1, 0, None),
ItemDef(400017, 'Fire', ItemClassification.useful, 1, 0, None),
ItemDef(400018, 'Death', ItemClassification.useful, 1, 0, None),
ItemDef(400019, 'Tilte', ItemClassification.useful, 1, 0, None),
ItemDef(400020, 'Ring of Elf', ItemClassification.useful, 1, 0, None),
ItemDef(400021, 'Magical Rod', ItemClassification.useful, 1, 0, None),
ItemDef(400022, 'Pendant', ItemClassification.useful, 1, 0, None),
ItemDef(400023, 'Hourglass', ItemClassification.filler, 6, 0, None),
# We need at least 4 red potions for the Tower of Red Potion. Up to the player to save them up!
ItemDef(400024, 'Red Potion', ItemClassification.filler, 15, 4, None),
ItemDef(400025, 'Elixir', ItemClassification.filler, 4, 0, None),
ItemDef(400026, 'Glove', ItemClassification.filler, 5, 0, None),
ItemDef(400027, 'Ointment', ItemClassification.filler, 8, 0, None),
ItemDef(400028, 'Poison', ItemClassification.trap, 13, 0, None),
ItemDef(None, 'Killed Evil One', ItemClassification.progression, 1, 0, 'Evil One'),
# Placeholder item so the game knows which shop slot to prefill wingboots
ItemDef(400029, 'Wingboots', ItemClassification.useful, 0, 0, None),
]

View File

@ -0,0 +1,199 @@
from typing import List, Optional
class LocationType():
world = 1 # Just standing there in the world
hidden = 2 # Kill all monsters in the room to reveal, each "item room" counter tick.
boss_reward = 3 # Kill a boss to reveal the item
shop = 4 # Buy at a shop
give = 5 # Given by an NPC
spring = 6 # Activatable spring
boss = 7 # Entity to kill to trigger the check
class ItemType():
unknown = 0 # Or don't care
red_potion = 1
class LocationDef:
def __init__(self, id: Optional[int], name: str, region: str, type: int, original_item: int):
self.id = id
self.name = name
self.region = region
self.type = type
self.original_item = original_item
locations: List[LocationDef] = [
# Eolis
LocationDef(400100, 'Eolis Guru', 'Eolis', LocationType.give, ItemType.unknown),
LocationDef(400101, 'Eolis Key Jack', 'Eolis', LocationType.shop, ItemType.unknown),
LocationDef(400102, 'Eolis Hand Dagger', 'Eolis', LocationType.shop, ItemType.unknown),
LocationDef(400103, 'Eolis Red Potion', 'Eolis', LocationType.shop, ItemType.red_potion),
LocationDef(400104, 'Eolis Elixir', 'Eolis', LocationType.shop, ItemType.unknown),
LocationDef(400105, 'Eolis Deluge', 'Eolis', LocationType.shop, ItemType.unknown),
# Path to Apolune
LocationDef(400106, 'Path to Apolune Magic Shield', 'Path to Apolune', LocationType.shop, ItemType.unknown),
LocationDef(400107, 'Path to Apolune Death', 'Path to Apolune', LocationType.shop, ItemType.unknown),
# Apolune
LocationDef(400108, 'Apolune Small Shield', 'Apolune', LocationType.shop, ItemType.unknown),
LocationDef(400109, 'Apolune Hand Dagger', 'Apolune', LocationType.shop, ItemType.unknown),
LocationDef(400110, 'Apolune Deluge', 'Apolune', LocationType.shop, ItemType.unknown),
LocationDef(400111, 'Apolune Red Potion', 'Apolune', LocationType.shop, ItemType.red_potion),
LocationDef(400112, 'Apolune Key Jack', 'Apolune', LocationType.shop, ItemType.unknown),
# Tower of Trunk
LocationDef(400113, 'Tower of Trunk Hidden Mattock', 'Tower of Trunk', LocationType.hidden, ItemType.unknown),
LocationDef(400114, 'Tower of Trunk Hidden Hourglass', 'Tower of Trunk', LocationType.hidden, ItemType.unknown),
LocationDef(400115, 'Tower of Trunk Boss Mattock', 'Tower of Trunk', LocationType.boss_reward, ItemType.unknown),
# Path to Forepaw
LocationDef(400116, 'Path to Forepaw Hidden Red Potion', 'Path to Forepaw', LocationType.hidden, ItemType.red_potion),
LocationDef(400117, 'Path to Forepaw Glove', 'Path to Forepaw', LocationType.world, ItemType.unknown),
# Forepaw
LocationDef(400118, 'Forepaw Long Sword', 'Forepaw', LocationType.shop, ItemType.unknown),
LocationDef(400119, 'Forepaw Studded Mail', 'Forepaw', LocationType.shop, ItemType.unknown),
LocationDef(400120, 'Forepaw Small Shield', 'Forepaw', LocationType.shop, ItemType.unknown),
LocationDef(400121, 'Forepaw Red Potion', 'Forepaw', LocationType.shop, ItemType.red_potion),
LocationDef(400122, 'Forepaw Wingboots', 'Forepaw', LocationType.shop, ItemType.unknown),
LocationDef(400123, 'Forepaw Key Jack', 'Forepaw', LocationType.shop, ItemType.unknown),
LocationDef(400124, 'Forepaw Key Queen', 'Forepaw', LocationType.shop, ItemType.unknown),
# Trunk
LocationDef(400125, 'Trunk Hidden Ointment', 'Trunk', LocationType.hidden, ItemType.unknown),
LocationDef(400126, 'Trunk Hidden Red Potion', 'Trunk', LocationType.hidden, ItemType.red_potion),
LocationDef(400127, 'Trunk Red Potion', 'Trunk', LocationType.world, ItemType.red_potion),
LocationDef(None, 'Sky Spring', 'Trunk', LocationType.spring, ItemType.unknown),
# Joker Spring
LocationDef(400128, 'Joker Spring Ruby Ring', 'Joker Spring', LocationType.give, ItemType.unknown),
LocationDef(None, 'Joker Spring', 'Joker Spring', LocationType.spring, ItemType.unknown),
# Tower of Fortress
LocationDef(400129, 'Tower of Fortress Poison 1', 'Tower of Fortress', LocationType.world, ItemType.unknown),
LocationDef(400130, 'Tower of Fortress Poison 2', 'Tower of Fortress', LocationType.world, ItemType.unknown),
LocationDef(400131, 'Tower of Fortress Hidden Wingboots', 'Tower of Fortress', LocationType.world, ItemType.unknown),
LocationDef(400132, 'Tower of Fortress Ointment', 'Tower of Fortress', LocationType.world, ItemType.unknown),
LocationDef(400133, 'Tower of Fortress Boss Wingboots', 'Tower of Fortress', LocationType.boss_reward, ItemType.unknown),
LocationDef(400134, 'Tower of Fortress Elixir', 'Tower of Fortress', LocationType.world, ItemType.unknown),
LocationDef(400135, 'Tower of Fortress Guru', 'Tower of Fortress', LocationType.give, ItemType.unknown),
LocationDef(None, 'Tower of Fortress Spring', 'Tower of Fortress', LocationType.spring, ItemType.unknown),
# Path to Mascon
LocationDef(400136, 'Path to Mascon Hidden Wingboots', 'Path to Mascon', LocationType.hidden, ItemType.unknown),
# Tower of Red Potion
LocationDef(400137, 'Tower of Red Potion', 'Tower of Red Potion', LocationType.world, ItemType.red_potion),
# Mascon
LocationDef(400138, 'Mascon Large Shield', 'Mascon', LocationType.shop, ItemType.unknown),
LocationDef(400139, 'Mascon Thunder', 'Mascon', LocationType.shop, ItemType.unknown),
LocationDef(400140, 'Mascon Mattock', 'Mascon', LocationType.shop, ItemType.unknown),
LocationDef(400141, 'Mascon Red Potion', 'Mascon', LocationType.shop, ItemType.red_potion),
LocationDef(400142, 'Mascon Key Jack', 'Mascon', LocationType.shop, ItemType.unknown),
LocationDef(400143, 'Mascon Key Queen', 'Mascon', LocationType.shop, ItemType.unknown),
# Path to Victim
LocationDef(400144, 'Misty Shop Death', 'Path to Victim', LocationType.shop, ItemType.unknown),
LocationDef(400145, 'Misty Shop Hourglass', 'Path to Victim', LocationType.shop, ItemType.unknown),
LocationDef(400146, 'Misty Shop Elixir', 'Path to Victim', LocationType.shop, ItemType.unknown),
LocationDef(400147, 'Misty Shop Red Potion', 'Path to Victim', LocationType.shop, ItemType.red_potion),
LocationDef(400148, 'Misty Doctor Office', 'Path to Victim', LocationType.hidden, ItemType.unknown),
# Tower of Suffer
LocationDef(400149, 'Tower of Suffer Hidden Wingboots', 'Tower of Suffer', LocationType.hidden, ItemType.unknown),
LocationDef(400150, 'Tower of Suffer Hidden Hourglass', 'Tower of Suffer', LocationType.hidden, ItemType.unknown),
LocationDef(400151, 'Tower of Suffer Pendant', 'Tower of Suffer', LocationType.boss_reward, ItemType.unknown),
# Victim
LocationDef(400152, 'Victim Full Plate', 'Victim', LocationType.shop, ItemType.unknown),
LocationDef(400153, 'Victim Mattock', 'Victim', LocationType.shop, ItemType.unknown),
LocationDef(400154, 'Victim Red Potion', 'Victim', LocationType.shop, ItemType.red_potion),
LocationDef(400155, 'Victim Key King', 'Victim', LocationType.shop, ItemType.unknown),
LocationDef(400156, 'Victim Key Queen', 'Victim', LocationType.shop, ItemType.unknown),
LocationDef(400157, 'Victim Tavern', 'Mist', LocationType.give, ItemType.unknown),
# Mist
LocationDef(400158, 'Mist Hidden Poison 1', 'Mist', LocationType.hidden, ItemType.unknown),
LocationDef(400159, 'Mist Hidden Poison 2', 'Mist', LocationType.hidden, ItemType.unknown),
LocationDef(400160, 'Mist Hidden Wingboots', 'Mist', LocationType.hidden, ItemType.unknown),
LocationDef(400161, 'Misty Magic Hall', 'Mist', LocationType.give, ItemType.unknown),
LocationDef(400162, 'Misty House', 'Mist', LocationType.give, ItemType.unknown),
# Useless Tower
LocationDef(400163, 'Useless Tower', 'Useless Tower', LocationType.hidden, ItemType.unknown),
# Tower of Mist
LocationDef(400164, 'Tower of Mist Hidden Ointment', 'Tower of Mist', LocationType.hidden, ItemType.unknown),
LocationDef(400165, 'Tower of Mist Elixir', 'Tower of Mist', LocationType.world, ItemType.unknown),
LocationDef(400166, 'Tower of Mist Black Onyx', 'Tower of Mist', LocationType.boss_reward, ItemType.unknown),
# Path to Conflate
LocationDef(400167, 'Path to Conflate Hidden Ointment', 'Path to Conflate', LocationType.hidden, ItemType.unknown),
LocationDef(400168, 'Path to Conflate Poison', 'Path to Conflate', LocationType.hidden, ItemType.unknown),
# Helm Branch
LocationDef(400169, 'Helm Branch Hidden Glove', 'Helm Branch', LocationType.hidden, ItemType.unknown),
LocationDef(400170, 'Helm Branch Battle Helmet', 'Helm Branch', LocationType.boss_reward, ItemType.unknown),
# Conflate
LocationDef(400171, 'Conflate Giant Blade', 'Conflate', LocationType.shop, ItemType.unknown),
LocationDef(400172, 'Conflate Magic Shield', 'Conflate', LocationType.shop, ItemType.unknown),
LocationDef(400173, 'Conflate Wingboots', 'Conflate', LocationType.shop, ItemType.unknown),
LocationDef(400174, 'Conflate Red Potion', 'Conflate', LocationType.shop, ItemType.red_potion),
LocationDef(400175, 'Conflate Guru', 'Conflate', LocationType.give, ItemType.unknown),
# Branches
LocationDef(400176, 'Branches Hidden Ointment', 'Branches', LocationType.hidden, ItemType.unknown),
LocationDef(400177, 'Branches Poison', 'Branches', LocationType.world, ItemType.unknown),
LocationDef(400178, 'Branches Hidden Mattock', 'Branches', LocationType.hidden, ItemType.unknown),
LocationDef(400179, 'Branches Hidden Hourglass', 'Branches', LocationType.hidden, ItemType.unknown),
# Path to Daybreak
LocationDef(400180, 'Path to Daybreak Hidden Wingboots 1', 'Path to Daybreak', LocationType.hidden, ItemType.unknown),
LocationDef(400181, 'Path to Daybreak Magical Rod', 'Path to Daybreak', LocationType.world, ItemType.unknown),
LocationDef(400182, 'Path to Daybreak Hidden Wingboots 2', 'Path to Daybreak', LocationType.hidden, ItemType.unknown),
LocationDef(400183, 'Path to Daybreak Poison', 'Path to Daybreak', LocationType.world, ItemType.unknown),
LocationDef(400184, 'Path to Daybreak Glove', 'Path to Daybreak', LocationType.world, ItemType.unknown),
LocationDef(400185, 'Path to Daybreak Battle Suit', 'Path to Daybreak', LocationType.boss_reward, ItemType.unknown),
# Daybreak
LocationDef(400186, 'Daybreak Tilte', 'Daybreak', LocationType.shop, ItemType.unknown),
LocationDef(400187, 'Daybreak Giant Blade', 'Daybreak', LocationType.shop, ItemType.unknown),
LocationDef(400188, 'Daybreak Red Potion', 'Daybreak', LocationType.shop, ItemType.red_potion),
LocationDef(400189, 'Daybreak Key King', 'Daybreak', LocationType.shop, ItemType.unknown),
LocationDef(400190, 'Daybreak Key Queen', 'Daybreak', LocationType.shop, ItemType.unknown),
# Dartmoor Castle
LocationDef(400191, 'Dartmoor Castle Hidden Hourglass', 'Dartmoor Castle', LocationType.hidden, ItemType.unknown),
LocationDef(400192, 'Dartmoor Castle Hidden Red Potion', 'Dartmoor Castle', LocationType.hidden, ItemType.red_potion),
# Dartmoor
LocationDef(400193, 'Dartmoor Giant Blade', 'Dartmoor', LocationType.shop, ItemType.unknown),
LocationDef(400194, 'Dartmoor Red Potion', 'Dartmoor', LocationType.shop, ItemType.red_potion),
LocationDef(400195, 'Dartmoor Key King', 'Dartmoor', LocationType.shop, ItemType.unknown),
# Fraternal Castle
LocationDef(400196, 'Fraternal Castle Hidden Ointment', 'Fraternal Castle', LocationType.hidden, ItemType.unknown),
LocationDef(400197, 'Fraternal Castle Shop Hidden Ointment', 'Fraternal Castle', LocationType.hidden, ItemType.unknown),
LocationDef(400198, 'Fraternal Castle Poison 1', 'Fraternal Castle', LocationType.world, ItemType.unknown),
LocationDef(400199, 'Fraternal Castle Poison 2', 'Fraternal Castle', LocationType.world, ItemType.unknown),
LocationDef(400200, 'Fraternal Castle Poison 3', 'Fraternal Castle', LocationType.world, ItemType.unknown),
# LocationDef(400201, 'Fraternal Castle Red Potion', 'Fraternal Castle', LocationType.world, ItemType.red_potion), # This location is inaccessible. Keeping commented for context.
LocationDef(400202, 'Fraternal Castle Hidden Hourglass', 'Fraternal Castle', LocationType.hidden, ItemType.unknown),
LocationDef(400203, 'Fraternal Castle Dragon Slayer', 'Fraternal Castle', LocationType.boss_reward, ItemType.unknown),
LocationDef(400204, 'Fraternal Castle Guru', 'Fraternal Castle', LocationType.give, ItemType.unknown),
# Evil Fortress
LocationDef(400205, 'Evil Fortress Ointment', 'Evil Fortress', LocationType.world, ItemType.unknown),
LocationDef(400206, 'Evil Fortress Poison 1', 'Evil Fortress', LocationType.world, ItemType.unknown),
LocationDef(400207, 'Evil Fortress Glove', 'Evil Fortress', LocationType.world, ItemType.unknown),
LocationDef(400208, 'Evil Fortress Poison 2', 'Evil Fortress', LocationType.world, ItemType.unknown),
LocationDef(400209, 'Evil Fortress Poison 3', 'Evil Fortress', LocationType.world, ItemType.unknown),
LocationDef(400210, 'Evil Fortress Hidden Glove', 'Evil Fortress', LocationType.hidden, ItemType.unknown),
LocationDef(None, 'Evil One', 'Evil Fortress', LocationType.boss, ItemType.unknown),
]

107
worlds/faxanadu/Options.py Normal file
View File

@ -0,0 +1,107 @@
from Options import PerGameCommonOptions, Toggle, DefaultOnToggle, StartInventoryPool, Choice
from dataclasses import dataclass
class KeepShopRedPotions(Toggle):
"""
Prevents the Shop's Red Potions from being shuffled. Those locations
will have purchasable Red Potion as usual for their usual price.
"""
display_name = "Keep Shop Red Potions"
class IncludePendant(Toggle):
"""
Pendant is an item that boosts your attack power permanently when picked up.
However, due to a programming error in the original game, it has the reverse
effect. You start with the Pendant power, and lose it when picking
it up. So this item is essentially a trap.
There is a setting in the client to reverse the effect back to its original intend.
This could be used in conjunction with this option to increase or lower difficulty.
"""
display_name = "Include Pendant"
class IncludePoisons(DefaultOnToggle):
"""
Whether or not to include Poison Potions in the pool of items. Including them
effectively turn them into traps in multiplayer.
"""
display_name = "Include Poisons"
class RequireDragonSlayer(Toggle):
"""
Requires the Dragon Slayer to be available before fighting the final boss is required.
Turning this on will turn Progressive Shields into progression items.
This setting does not force you to use Dragon Slayer to kill the final boss.
Instead, it ensures that you will have the Dragon Slayer and be able to equip
it before you are expected to beat the final boss.
"""
display_name = "Require Dragon Slayer"
class RandomMusic(Toggle):
"""
All levels' music is shuffled. Except the title screen because it's finite.
This is an aesthetic option and doesn't affect gameplay.
"""
display_name = "Random Musics"
class RandomSound(Toggle):
"""
All sounds are shuffled.
This is an aesthetic option and doesn't affect gameplay.
"""
display_name = "Random Sounds"
class RandomNPC(Toggle):
"""
NPCs and their portraits are shuffled.
This is an aesthetic option and doesn't affect gameplay.
"""
display_name = "Random NPCs"
class RandomMonsters(Choice):
"""
Choose how monsters are randomized.
"Vanilla": No randomization
"Level Shuffle": Monsters are shuffled within a level
"Level Random": Monsters are picked randomly, balanced based on the ratio of the current level
"World Shuffle": Monsters are shuffled across the entire world
"World Random": Monsters are picked randomly, balanced based on the ratio of the entire world
"Chaotic": Completely random, except big vs small ratio is kept. Big are mini-bosses.
"""
display_name = "Random Monsters"
option_vanilla = 0
option_level_shuffle = 1
option_level_random = 2
option_world_shuffle = 3
option_world_random = 4
option_chaotic = 5
default = 0
class RandomRewards(Toggle):
"""
Monsters drops are shuffled.
"""
display_name = "Random Rewards"
@dataclass
class FaxanaduOptions(PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool
keep_shop_red_potions: KeepShopRedPotions
include_pendant: IncludePendant
include_poisons: IncludePoisons
require_dragon_slayer: RequireDragonSlayer
random_musics: RandomMusic
random_sounds: RandomSound
random_npcs: RandomNPC
random_monsters: RandomMonsters
random_rewards: RandomRewards

View File

@ -0,0 +1,66 @@
from BaseClasses import Region
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from . import FaxanaduWorld
def create_region(name, player, multiworld):
region = Region(name, player, multiworld)
multiworld.regions.append(region)
return region
def create_regions(faxanadu_world: "FaxanaduWorld"):
player = faxanadu_world.player
multiworld = faxanadu_world.multiworld
# Create regions
menu = create_region("Menu", player, multiworld)
eolis = create_region("Eolis", player, multiworld)
path_to_apolune = create_region("Path to Apolune", player, multiworld)
apolune = create_region("Apolune", player, multiworld)
create_region("Tower of Trunk", player, multiworld)
path_to_forepaw = create_region("Path to Forepaw", player, multiworld)
forepaw = create_region("Forepaw", player, multiworld)
trunk = create_region("Trunk", player, multiworld)
create_region("Joker Spring", player, multiworld)
create_region("Tower of Fortress", player, multiworld)
path_to_mascon = create_region("Path to Mascon", player, multiworld)
create_region("Tower of Red Potion", player, multiworld)
mascon = create_region("Mascon", player, multiworld)
path_to_victim = create_region("Path to Victim", player, multiworld)
create_region("Tower of Suffer", player, multiworld)
victim = create_region("Victim", player, multiworld)
mist = create_region("Mist", player, multiworld)
create_region("Useless Tower", player, multiworld)
create_region("Tower of Mist", player, multiworld)
path_to_conflate = create_region("Path to Conflate", player, multiworld)
create_region("Helm Branch", player, multiworld)
create_region("Conflate", player, multiworld)
branches = create_region("Branches", player, multiworld)
path_to_daybreak = create_region("Path to Daybreak", player, multiworld)
daybreak = create_region("Daybreak", player, multiworld)
dartmoor_castle = create_region("Dartmoor Castle", player, multiworld)
create_region("Dartmoor", player, multiworld)
create_region("Fraternal Castle", player, multiworld)
create_region("Evil Fortress", player, multiworld)
# Create connections
menu.add_exits(["Eolis"])
eolis.add_exits(["Path to Apolune"])
path_to_apolune.add_exits(["Apolune"])
apolune.add_exits(["Tower of Trunk", "Path to Forepaw"])
path_to_forepaw.add_exits(["Forepaw"])
forepaw.add_exits(["Trunk"])
trunk.add_exits(["Joker Spring", "Tower of Fortress", "Path to Mascon"])
path_to_mascon.add_exits(["Tower of Red Potion", "Mascon"])
mascon.add_exits(["Path to Victim"])
path_to_victim.add_exits(["Tower of Suffer", "Victim"])
victim.add_exits(["Mist"])
mist.add_exits(["Useless Tower", "Tower of Mist", "Path to Conflate"])
path_to_conflate.add_exits(["Helm Branch", "Conflate", "Branches"])
branches.add_exits(["Path to Daybreak"])
path_to_daybreak.add_exits(["Daybreak"])
daybreak.add_exits(["Dartmoor Castle"])
dartmoor_castle.add_exits(["Dartmoor", "Fraternal Castle", "Evil Fortress"])

79
worlds/faxanadu/Rules.py Normal file
View File

@ -0,0 +1,79 @@
from typing import TYPE_CHECKING
from worlds.generic.Rules import set_rule
if TYPE_CHECKING:
from . import FaxanaduWorld
def can_buy_in_eolis(state, player):
# Sword or Deluge so we can farm for gold.
# Ring of Elf so we can get 1500 from the King.
return state.has_any(["Progressive Sword", "Deluge", "Ring of Elf"], player)
def has_any_magic(state, player):
return state.has_any(["Deluge", "Thunder", "Fire", "Death", "Tilte"], player)
def set_rules(faxanadu_world: "FaxanaduWorld"):
player = faxanadu_world.player
multiworld = faxanadu_world.multiworld
# Region rules
set_rule(multiworld.get_entrance("Eolis -> Path to Apolune", player), lambda state:
state.has_all(["Key Jack", "Progressive Sword"], player)) # You can't go far with magic only
set_rule(multiworld.get_entrance("Apolune -> Tower of Trunk", player), lambda state: state.has("Key Jack", player))
set_rule(multiworld.get_entrance("Apolune -> Path to Forepaw", player), lambda state: state.has("Mattock", player))
set_rule(multiworld.get_entrance("Trunk -> Joker Spring", player), lambda state: state.has("Key Joker", player))
set_rule(multiworld.get_entrance("Trunk -> Tower of Fortress", player), lambda state: state.has("Key Jack", player))
set_rule(multiworld.get_entrance("Trunk -> Path to Mascon", player), lambda state:
state.has_all(["Key Queen", "Ring of Ruby", "Sky Spring Flow", "Tower of Fortress Spring Flow", "Joker Spring Flow"], player) and
state.has("Progressive Sword", player, 2))
set_rule(multiworld.get_entrance("Path to Mascon -> Tower of Red Potion", player), lambda state:
state.has("Key Queen", player) and
state.has("Red Potion", player, 4)) # It's impossible to go through the tower of Red Potion without at least 1-2 potions. Give them 4 for good measure.
set_rule(multiworld.get_entrance("Path to Victim -> Tower of Suffer", player), lambda state: state.has("Key Queen", player))
set_rule(multiworld.get_entrance("Path to Victim -> Victim", player), lambda state: state.has("Unlock Wingboots", player))
set_rule(multiworld.get_entrance("Mist -> Useless Tower", player), lambda state:
state.has_all(["Key King", "Unlock Wingboots"], player))
set_rule(multiworld.get_entrance("Mist -> Tower of Mist", player), lambda state: state.has("Key King", player))
set_rule(multiworld.get_entrance("Mist -> Path to Conflate", player), lambda state: state.has("Key Ace", player))
set_rule(multiworld.get_entrance("Path to Conflate -> Helm Branch", player), lambda state: state.has("Key King", player))
set_rule(multiworld.get_entrance("Path to Conflate -> Branches", player), lambda state: state.has("Key King", player))
set_rule(multiworld.get_entrance("Daybreak -> Dartmoor Castle", player), lambda state: state.has("Ring of Dworf", player))
set_rule(multiworld.get_entrance("Dartmoor Castle -> Evil Fortress", player), lambda state: state.has("Demons Ring", player))
# Location rules
set_rule(multiworld.get_location("Eolis Key Jack", player), lambda state: can_buy_in_eolis(state, player))
set_rule(multiworld.get_location("Eolis Hand Dagger", player), lambda state: can_buy_in_eolis(state, player))
set_rule(multiworld.get_location("Eolis Elixir", player), lambda state: can_buy_in_eolis(state, player))
set_rule(multiworld.get_location("Eolis Deluge", player), lambda state: can_buy_in_eolis(state, player))
set_rule(multiworld.get_location("Eolis Red Potion", player), lambda state: can_buy_in_eolis(state, player))
set_rule(multiworld.get_location("Path to Apolune Magic Shield", player), lambda state: state.has("Key King", player)) # Mid-late cost, make sure we've progressed
set_rule(multiworld.get_location("Path to Apolune Death", player), lambda state: state.has("Key Ace", player)) # Mid-late cost, make sure we've progressed
set_rule(multiworld.get_location("Tower of Trunk Hidden Mattock", player), lambda state:
# This is actually possible if the monster drop into the stairs and kill it with dagger. But it's a "pro move"
state.has("Deluge", player, 1) or
state.has("Progressive Sword", player, 2))
set_rule(multiworld.get_location("Path to Forepaw Glove", player), lambda state:
state.has_all(["Deluge", "Unlock Wingboots"], player))
set_rule(multiworld.get_location("Trunk Red Potion", player), lambda state: state.has("Unlock Wingboots", player))
set_rule(multiworld.get_location("Sky Spring", player), lambda state: state.has("Unlock Wingboots", player))
set_rule(multiworld.get_location("Tower of Fortress Spring", player), lambda state: state.has("Spring Elixir", player))
set_rule(multiworld.get_location("Tower of Fortress Guru", player), lambda state: state.has("Sky Spring Flow", player))
set_rule(multiworld.get_location("Tower of Suffer Hidden Wingboots", player), lambda state:
state.has("Deluge", player) or
state.has("Progressive Sword", player, 2))
set_rule(multiworld.get_location("Misty House", player), lambda state: state.has("Black Onyx", player))
set_rule(multiworld.get_location("Misty Doctor Office", player), lambda state: has_any_magic(state, player))
set_rule(multiworld.get_location("Conflate Guru", player), lambda state: state.has("Progressive Armor", player, 3))
set_rule(multiworld.get_location("Branches Hidden Mattock", player), lambda state: state.has("Unlock Wingboots", player))
set_rule(multiworld.get_location("Path to Daybreak Glove", player), lambda state: state.has("Unlock Wingboots", player))
set_rule(multiworld.get_location("Dartmoor Castle Hidden Hourglass", player), lambda state: state.has("Unlock Wingboots", player))
set_rule(multiworld.get_location("Dartmoor Castle Hidden Red Potion", player), lambda state: has_any_magic(state, player))
set_rule(multiworld.get_location("Fraternal Castle Guru", player), lambda state: state.has("Progressive Sword", player, 4))
set_rule(multiworld.get_location("Fraternal Castle Shop Hidden Ointment", player), lambda state: has_any_magic(state, player))
if faxanadu_world.options.require_dragon_slayer.value:
set_rule(multiworld.get_location("Evil One", player), lambda state:
state.has_all_counts({"Progressive Sword": 4, "Progressive Armor": 3, "Progressive Shield": 4}, player))

190
worlds/faxanadu/__init__.py Normal file
View File

@ -0,0 +1,190 @@
from typing import Any, Dict, List
from BaseClasses import Item, Location, Tutorial, ItemClassification, MultiWorld
from worlds.AutoWorld import WebWorld, World
from . import Items, Locations, Regions, Rules
from .Options import FaxanaduOptions
from worlds.generic.Rules import set_rule
DAXANADU_VERSION = "0.3.0"
class FaxanaduLocation(Location):
game: str = "Faxanadu"
class FaxanaduItem(Item):
game: str = "Faxanadu"
class FaxanaduWeb(WebWorld):
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Faxanadu randomizer connected to an Archipelago Multiworld",
"English",
"setup_en.md",
"setup/en",
["Daivuk"]
)]
theme = "dirt"
class FaxanaduWorld(World):
"""
Faxanadu is an action role-playing platform video game developed by Hudson Soft for the Nintendo Entertainment System
"""
options_dataclass = FaxanaduOptions
options: FaxanaduOptions
game = "Faxanadu"
web = FaxanaduWeb()
item_name_to_id = {item.name: item.id for item in Items.items if item.id is not None}
item_name_to_item = {item.name: item for item in Items.items}
location_name_to_id = {loc.name: loc.id for loc in Locations.locations if loc.id is not None}
def __init__(self, world: MultiWorld, player: int):
self.filler_ratios: Dict[str, int] = {}
super().__init__(world, player)
def create_regions(self):
Regions.create_regions(self)
# Add locations into regions
for region in self.multiworld.get_regions(self.player):
for loc in [location for location in Locations.locations if location.region == region.name]:
location = FaxanaduLocation(self.player, loc.name, loc.id, region)
# In Faxanadu, Poison hurts you when picked up. It makes no sense to sell them in shops
if loc.type == Locations.LocationType.shop:
location.item_rule = lambda item, player=self.player: not (player == item.player and item.name == "Poison")
region.locations.append(location)
def set_rules(self):
Rules.set_rules(self)
self.multiworld.completion_condition[self.player] = lambda state: state.has("Killed Evil One", self.player)
def create_item(self, name: str) -> FaxanaduItem:
item: Items.ItemDef = self.item_name_to_item[name]
return FaxanaduItem(name, item.classification, item.id, self.player)
# Returns how many red potions were prefilled into shops
def prefill_shop_red_potions(self) -> int:
red_potion_in_shop_count = 0
if self.options.keep_shop_red_potions:
red_potion_item = self.item_name_to_item["Red Potion"]
red_potion_shop_locations = [
loc
for loc in Locations.locations
if loc.type == Locations.LocationType.shop and loc.original_item == Locations.ItemType.red_potion
]
for loc in red_potion_shop_locations:
location = self.get_location(loc.name)
location.place_locked_item(FaxanaduItem(red_potion_item.name, red_potion_item.classification, red_potion_item.id, self.player))
red_potion_in_shop_count += 1
return red_potion_in_shop_count
def put_wingboot_in_shop(self, shops, region_name):
item = self.item_name_to_item["Wingboots"]
shop = shops.pop(region_name)
slot = self.random.randint(0, len(shop) - 1)
loc = shop[slot]
location = self.get_location(loc.name)
location.place_locked_item(FaxanaduItem(item.name, item.classification, item.id, self.player))
# Put a rule right away that we need to have to unlocked.
set_rule(location, lambda state: state.has("Unlock Wingboots", self.player))
# Returns how many wingboots were prefilled into shops
def prefill_shop_wingboots(self) -> int:
# Collect shops
shops: Dict[str, List[Locations.LocationDef]] = {}
for loc in Locations.locations:
if loc.type == Locations.LocationType.shop:
if self.options.keep_shop_red_potions and loc.original_item == Locations.ItemType.red_potion:
continue # Don't override our red potions
shops.setdefault(loc.region, []).append(loc)
shop_count = len(shops)
wingboots_count = round(shop_count / 2.5) # On 10 shops, we should have about 4 shops with wingboots
# At least one should be in the first 4 shops. Because we require wingboots to progress past that point.
must_have_regions = [region for i, region in enumerate(shops) if i < 4]
self.put_wingboot_in_shop(shops, self.random.choice(must_have_regions))
# Fill in the rest randomly in remaining shops
for i in range(wingboots_count - 1): # -1 because we added one already
region = self.random.choice(list(shops.keys()))
self.put_wingboot_in_shop(shops, region)
return wingboots_count
def create_items(self) -> None:
itempool: List[FaxanaduItem] = []
# Prefill red potions in shops if option is set
red_potion_in_shop_count = self.prefill_shop_red_potions()
# Prefill wingboots in shops
wingboots_in_shop_count = self.prefill_shop_wingboots()
# Create the item pool, excluding fillers.
prefilled_count = red_potion_in_shop_count + wingboots_in_shop_count
for item in Items.items:
# Ignore pendant if turned off
if item.name == "Pendant" and not self.options.include_pendant:
continue
# ignore fillers for now, we will fill them later
if item.classification in [ItemClassification.filler, ItemClassification.trap] and \
item.progression_count == 0:
continue
prefill_loc = None
if item.prefill_location:
prefill_loc = self.get_location(item.prefill_location)
# if require dragon slayer is turned on, we need progressive shields to be progression
item_classification = item.classification
if self.options.require_dragon_slayer and item.name == "Progressive Shield":
item_classification = ItemClassification.progression
if prefill_loc:
prefill_loc.place_locked_item(FaxanaduItem(item.name, item_classification, item.id, self.player))
prefilled_count += 1
else:
for i in range(item.count - item.progression_count):
itempool.append(FaxanaduItem(item.name, item_classification, item.id, self.player))
for i in range(item.progression_count):
itempool.append(FaxanaduItem(item.name, ItemClassification.progression, item.id, self.player))
# Set up filler ratios
self.filler_ratios = {
item.name: item.count
for item in Items.items
if item.classification in [ItemClassification.filler, ItemClassification.trap]
}
# If red potions are locked in shops, remove the count from the ratio.
self.filler_ratios["Red Potion"] -= red_potion_in_shop_count
# Remove poisons if not desired
if not self.options.include_poisons:
self.filler_ratios["Poison"] = 0
# Randomly add fillers to the pool with ratios based on og game occurrence counts.
filler_count = len(Locations.locations) - len(itempool) - prefilled_count
for i in range(filler_count):
itempool.append(self.create_item(self.get_filler_item_name()))
self.multiworld.itempool += itempool
def get_filler_item_name(self) -> str:
return self.random.choices(list(self.filler_ratios.keys()), weights=list(self.filler_ratios.values()))[0]
def fill_slot_data(self) -> Dict[str, Any]:
slot_data = self.options.as_dict("keep_shop_red_potions", "random_musics", "random_sounds", "random_npcs", "random_monsters", "random_rewards")
slot_data["daxanadu_version"] = DAXANADU_VERSION
return slot_data

View File

@ -0,0 +1,27 @@
# Faxanadu
## Where is the settings page?
The [player options page](../player-options) contains the options needed to configure your game session.
## What does randomization do to this game?
All game items collected in the map, shops, and boss drops are randomized.
Keys are unique. Once you get the Jack Key, you can open all Jack doors; the key stays in your inventory.
Wingboots are randomized across shops only. They are LOCKED and cannot be bought until you get the item that unlocks them.
Normal Elixirs don't revive the tower spring. A new item, Spring Elixir, is necessary. This new item is unique.
## What is the goal?
The goal is to kill the Evil One.
## What is a "check" in The Faxanadu?
Shop items, item locations in the world, boss drops, and secret items.
## What "items" can you unlock in Faxanadu?
Keys, Armors, Weapons, Potions, Shields, Magics, Poisons, Gloves, etc.

View File

@ -0,0 +1,32 @@
# Faxanadu Randomizer Setup
## Required Software
- [Daxanadu](https://github.com/Daivuk/Daxanadu/releases/)
- Faxanadu ROM, English version
## Optional Software
- [ArchipelagoTextClient](https://github.com/ArchipelagoMW/Archipelago/releases)
## Installing Daxanadu
1. Download [Daxanadu.zip](https://github.com/Daivuk/Daxanadu/releases/) and extract it.
2. Copy your rom `Faxanadu (U).nes` into the newly extracted folder.
## Joining a MultiWorld Game
1. Launch Daxanadu.exe
2. From the Main menu, go to the `ARCHIPELAGO` menu. Enter the server's address, slot name, and password. Then select `PLAY`.
3. Enjoy!
To continue a game, follow the same connection steps.
Connecting with a different seed won't erase your progress in other seeds.
## Archipelago Text Client
We recommend having Archipelago's Text Client open on the side to keep track of what items you receive and send.
Daxanadu doesn't display messages. You'll only get popups when picking them up.
## Auto-Tracking
Daxanadu has an integrated tracker that can be toggled in the options.