Files
Grinch-AP/worlds/ffmq/Items.py
Alchav f54f8622bb Final Fantasy Mystic Quest: Implement new game (#1909)
FFMQR by @wildham0 
Uses an API created by wildham for Map Shuffle, Crest Shuffle and Battlefield Reward Shuffle, using a similar method of obtaining data from an external website to Super Metroid's Varia Preset option.
Generates a .apmq file which the user must bring to the FFMQR website https://www.ffmqrando.net/Archipelago to patch their rom. It is not an actual patch file but contains item placement and options data for the FFMQR website to generate a patched rom with for AP.
Some of the AP options may seem unusual, using Choice instead of Range where it may seem more appropriate, but these are options that are passed to FFMQR and I can only be as flexible as it is.

@wildham0 deserves the bulk of the credit for not only creating FFMQR in the first place but all the ASM work on the rom needed to make this possible, work on FFMQR to allow patching with the .apmq files, and creating the API that meant I did not have to recreate his map shuffle from scratch.
2023-11-26 17:17:59 +01:00

297 lines
17 KiB
Python

from BaseClasses import ItemClassification, Item
fillers = {"Cure Potion": 61, "Heal Potion": 52, "Refresher": 17, "Seed": 2, "Bomb Refill": 19,
"Projectile Refill": 50}
class ItemData:
def __init__(self, item_id, classification, groups=(), data_name=None):
self.groups = groups
self.classification = classification
self.id = None
if item_id is not None:
self.id = item_id + 0x420000
self.data_name = data_name
item_table = {
"Elixir": ItemData(0, ItemClassification.progression, ["Key Items"]),
"Tree Wither": ItemData(1, ItemClassification.progression, ["Key Items"]),
"Wakewater": ItemData(2, ItemClassification.progression, ["Key Items"]),
"Venus Key": ItemData(3, ItemClassification.progression, ["Key Items"]),
"Multi Key": ItemData(4, ItemClassification.progression, ["Key Items"]),
"Mask": ItemData(5, ItemClassification.progression, ["Key Items"]),
"Magic Mirror": ItemData(6, ItemClassification.progression, ["Key Items"]),
"Thunder Rock": ItemData(7, ItemClassification.progression, ["Key Items"]),
"Captain's Cap": ItemData(8, ItemClassification.progression_skip_balancing, ["Key Items"]),
"Libra Crest": ItemData(9, ItemClassification.progression, ["Key Items"]),
"Gemini Crest": ItemData(10, ItemClassification.progression, ["Key Items"]),
"Mobius Crest": ItemData(11, ItemClassification.progression, ["Key Items"]),
"Sand Coin": ItemData(12, ItemClassification.progression, ["Key Items", "Coins"]),
"River Coin": ItemData(13, ItemClassification.progression, ["Key Items", "Coins"]),
"Sun Coin": ItemData(14, ItemClassification.progression, ["Key Items", "Coins"]),
"Sky Coin": ItemData(15, ItemClassification.progression_skip_balancing, ["Key Items", "Coins"]),
"Sky Fragment": ItemData(15 + 256, ItemClassification.progression_skip_balancing, ["Key Items"]),
"Cure Potion": ItemData(16, ItemClassification.filler, ["Consumables"]),
"Heal Potion": ItemData(17, ItemClassification.filler, ["Consumables"]),
"Seed": ItemData(18, ItemClassification.filler, ["Consumables"]),
"Refresher": ItemData(19, ItemClassification.filler, ["Consumables"]),
"Exit Book": ItemData(20, ItemClassification.useful, ["Spells"]),
"Cure Book": ItemData(21, ItemClassification.useful, ["Spells"]),
"Heal Book": ItemData(22, ItemClassification.useful, ["Spells"]),
"Life Book": ItemData(23, ItemClassification.useful, ["Spells"]),
"Quake Book": ItemData(24, ItemClassification.useful, ["Spells"]),
"Blizzard Book": ItemData(25, ItemClassification.useful, ["Spells"]),
"Fire Book": ItemData(26, ItemClassification.useful, ["Spells"]),
"Aero Book": ItemData(27, ItemClassification.useful, ["Spells"]),
"Thunder Seal": ItemData(28, ItemClassification.useful, ["Spells"]),
"White Seal": ItemData(29, ItemClassification.useful, ["Spells"]),
"Meteor Seal": ItemData(30, ItemClassification.useful, ["Spells"]),
"Flare Seal": ItemData(31, ItemClassification.useful, ["Spells"]),
"Progressive Sword": ItemData(32 + 256, ItemClassification.progression, ["Weapons", "Swords"]),
"Steel Sword": ItemData(32, ItemClassification.progression, ["Weapons", "Swords"]),
"Knight Sword": ItemData(33, ItemClassification.progression_skip_balancing, ["Weapons", "Swords"]),
"Excalibur": ItemData(34, ItemClassification.progression_skip_balancing, ["Weapons", "Swords"]),
"Progressive Axe": ItemData(35 + 256, ItemClassification.progression, ["Weapons", "Axes"]),
"Axe": ItemData(35, ItemClassification.progression, ["Weapons", "Axes"]),
"Battle Axe": ItemData(36, ItemClassification.progression_skip_balancing, ["Weapons", "Axes"]),
"Giant's Axe": ItemData(37, ItemClassification.progression_skip_balancing, ["Weapons", "Axes"]),
"Progressive Claw": ItemData(38 + 256, ItemClassification.progression, ["Weapons", "Axes"]),
"Cat Claw": ItemData(38, ItemClassification.progression, ["Weapons", "Claws"]),
"Charm Claw": ItemData(39, ItemClassification.progression_skip_balancing, ["Weapons", "Claws"]),
"Dragon Claw": ItemData(40, ItemClassification.progression, ["Weapons", "Claws"]),
"Progressive Bomb": ItemData(41 + 256, ItemClassification.progression, ["Weapons", "Bombs"]),
"Bomb": ItemData(41, ItemClassification.progression, ["Weapons", "Bombs"]),
"Jumbo Bomb": ItemData(42, ItemClassification.progression_skip_balancing, ["Weapons", "Bombs"]),
"Mega Grenade": ItemData(43, ItemClassification.progression, ["Weapons", "Bombs"]),
# Ally-only equipment does nothing when received, no reason to put them in the datapackage
#"Morning Star": ItemData(44, ItemClassification.progression, ["Weapons"]),
#"Bow Of Grace": ItemData(45, ItemClassification.progression, ["Weapons"]),
#"Ninja Star": ItemData(46, ItemClassification.progression, ["Weapons"]),
"Progressive Helm": ItemData(47 + 256, ItemClassification.useful, ["Helms"]),
"Steel Helm": ItemData(47, ItemClassification.useful, ["Helms"]),
"Moon Helm": ItemData(48, ItemClassification.useful, ["Helms"]),
"Apollo Helm": ItemData(49, ItemClassification.useful, ["Helms"]),
"Progressive Armor": ItemData(50 + 256, ItemClassification.useful, ["Armors"]),
"Steel Armor": ItemData(50, ItemClassification.useful, ["Armors"]),
"Noble Armor": ItemData(51, ItemClassification.useful, ["Armors"]),
"Gaia's Armor": ItemData(52, ItemClassification.useful, ["Armors"]),
#"Replica Armor": ItemData(53, ItemClassification.progression, ["Armors"]),
#"Mystic Robes": ItemData(54, ItemClassification.progression, ["Armors"]),
#"Flame Armor": ItemData(55, ItemClassification.progression, ["Armors"]),
#"Black Robe": ItemData(56, ItemClassification.progression, ["Armors"]),
"Progressive Shield": ItemData(57 + 256, ItemClassification.useful, ["Shields"]),
"Steel Shield": ItemData(57, ItemClassification.useful, ["Shields"]),
"Venus Shield": ItemData(58, ItemClassification.useful, ["Shields"]),
"Aegis Shield": ItemData(59, ItemClassification.useful, ["Shields"]),
#"Ether Shield": ItemData(60, ItemClassification.progression, ["Shields"]),
"Progressive Accessory": ItemData(61 + 256, ItemClassification.useful, ["Accessories"]),
"Charm": ItemData(61, ItemClassification.useful, ["Accessories"]),
"Magic Ring": ItemData(62, ItemClassification.useful, ["Accessories"]),
"Cupid Locket": ItemData(63, ItemClassification.useful, ["Accessories"]),
# these are understood by FFMQR and I could place these if I want, but it's easier to just let FFMQR
# place them. I want an option to make shuffle battlefield rewards NOT color-code the battlefields,
# and then I would make the non-item reward battlefields into AP checks and these would be put into those as
# the item for AP. But there is no such option right now.
# "54 XP": ItemData(96, ItemClassification.filler, data_name="Xp54"),
# "99 XP": ItemData(97, ItemClassification.filler, data_name="Xp99"),
# "540 XP": ItemData(98, ItemClassification.filler, data_name="Xp540"),
# "744 XP": ItemData(99, ItemClassification.filler, data_name="Xp744"),
# "816 XP": ItemData(100, ItemClassification.filler, data_name="Xp816"),
# "1068 XP": ItemData(101, ItemClassification.filler, data_name="Xp1068"),
# "1200 XP": ItemData(102, ItemClassification.filler, data_name="Xp1200"),
# "2700 XP": ItemData(103, ItemClassification.filler, data_name="Xp2700"),
# "2808 XP": ItemData(104, ItemClassification.filler, data_name="Xp2808"),
# "150 Gp": ItemData(105, ItemClassification.filler, data_name="Gp150"),
# "300 Gp": ItemData(106, ItemClassification.filler, data_name="Gp300"),
# "600 Gp": ItemData(107, ItemClassification.filler, data_name="Gp600"),
# "900 Gp": ItemData(108, ItemClassification.filler, data_name="Gp900"),
# "1200 Gp": ItemData(109, ItemClassification.filler, data_name="Gp1200"),
"Bomb Refill": ItemData(221, ItemClassification.filler, ["Refills"]),
"Projectile Refill": ItemData(222, ItemClassification.filler, ["Refills"]),
#"None": ItemData(255, ItemClassification.progression, []),
"Kaeli 1": ItemData(None, ItemClassification.progression),
"Kaeli 2": ItemData(None, ItemClassification.progression),
"Tristam": ItemData(None, ItemClassification.progression),
"Phoebe 1": ItemData(None, ItemClassification.progression),
"Reuben 1": ItemData(None, ItemClassification.progression),
"Reuben Dad Saved": ItemData(None, ItemClassification.progression),
"Otto": ItemData(None, ItemClassification.progression),
"Captain Mac": ItemData(None, ItemClassification.progression),
"Ship Steering Wheel": ItemData(None, ItemClassification.progression),
"Minotaur": ItemData(None, ItemClassification.progression),
"Flamerus Rex": ItemData(None, ItemClassification.progression),
"Phanquid": ItemData(None, ItemClassification.progression),
"Freezer Crab": ItemData(None, ItemClassification.progression),
"Ice Golem": ItemData(None, ItemClassification.progression),
"Jinn": ItemData(None, ItemClassification.progression),
"Medusa": ItemData(None, ItemClassification.progression),
"Dualhead Hydra": ItemData(None, ItemClassification.progression),
"Gidrah": ItemData(None, ItemClassification.progression),
"Dullahan": ItemData(None, ItemClassification.progression),
"Pazuzu": ItemData(None, ItemClassification.progression),
"Aquaria Plaza": ItemData(None, ItemClassification.progression),
"Summer Aquaria": ItemData(None, ItemClassification.progression),
"Reuben Mine": ItemData(None, ItemClassification.progression),
"Alive Forest": ItemData(None, ItemClassification.progression),
"Rainbow Bridge": ItemData(None, ItemClassification.progression),
"Collapse Spencer's Cave": ItemData(None, ItemClassification.progression),
"Ship Liberated": ItemData(None, ItemClassification.progression),
"Ship Loaned": ItemData(None, ItemClassification.progression),
"Ship Dock Access": ItemData(None, ItemClassification.progression),
"Stone Golem": ItemData(None, ItemClassification.progression),
"Twinhead Wyvern": ItemData(None, ItemClassification.progression),
"Zuh": ItemData(None, ItemClassification.progression),
"Libra Temple Crest Tile": ItemData(None, ItemClassification.progression),
"Life Temple Crest Tile": ItemData(None, ItemClassification.progression),
"Aquaria Vendor Crest Tile": ItemData(None, ItemClassification.progression),
"Fireburg Vendor Crest Tile": ItemData(None, ItemClassification.progression),
"Fireburg Grenademan Crest Tile": ItemData(None, ItemClassification.progression),
"Sealed Temple Crest Tile": ItemData(None, ItemClassification.progression),
"Wintry Temple Crest Tile": ItemData(None, ItemClassification.progression),
"Kaidge Temple Crest Tile": ItemData(None, ItemClassification.progression),
"Light Temple Crest Tile": ItemData(None, ItemClassification.progression),
"Windia Kids Crest Tile": ItemData(None, ItemClassification.progression),
"Windia Dock Crest Tile": ItemData(None, ItemClassification.progression),
"Ship Dock Crest Tile": ItemData(None, ItemClassification.progression),
"Alive Forest Libra Crest Tile": ItemData(None, ItemClassification.progression),
"Alive Forest Gemini Crest Tile": ItemData(None, ItemClassification.progression),
"Alive Forest Mobius Crest Tile": ItemData(None, ItemClassification.progression),
"Wood House Libra Crest Tile": ItemData(None, ItemClassification.progression),
"Wood House Gemini Crest Tile": ItemData(None, ItemClassification.progression),
"Wood House Mobius Crest Tile": ItemData(None, ItemClassification.progression),
"Barrel Pushed": ItemData(None, ItemClassification.progression),
"Long Spine Bombed": ItemData(None, ItemClassification.progression),
"Short Spine Bombed": ItemData(None, ItemClassification.progression),
"Skull 1 Bombed": ItemData(None, ItemClassification.progression),
"Skull 2 Bombed": ItemData(None, ItemClassification.progression),
"Ice Pyramid 1F Statue": ItemData(None, ItemClassification.progression),
"Ice Pyramid 3F Statue": ItemData(None, ItemClassification.progression),
"Ice Pyramid 4F Statue": ItemData(None, ItemClassification.progression),
"Ice Pyramid 5F Statue": ItemData(None, ItemClassification.progression),
"Spencer Cave Libra Block Bombed": ItemData(None, ItemClassification.progression),
"Lava Dome Plate": ItemData(None, ItemClassification.progression),
"Pazuzu 2F Lock": ItemData(None, ItemClassification.progression),
"Pazuzu 4F Lock": ItemData(None, ItemClassification.progression),
"Pazuzu 6F Lock": ItemData(None, ItemClassification.progression),
"Pazuzu 1F": ItemData(None, ItemClassification.progression),
"Pazuzu 2F": ItemData(None, ItemClassification.progression),
"Pazuzu 3F": ItemData(None, ItemClassification.progression),
"Pazuzu 4F": ItemData(None, ItemClassification.progression),
"Pazuzu 5F": ItemData(None, ItemClassification.progression),
"Pazuzu 6F": ItemData(None, ItemClassification.progression),
"Dark King": ItemData(None, ItemClassification.progression),
#"Barred": ItemData(None, ItemClassification.progression),
}
prog_map = {
"Swords": "Progressive Sword",
"Axes": "Progressive Axe",
"Claws": "Progressive Claw",
"Bombs": "Progressive Bomb",
"Shields": "Progressive Shield",
"Armors": "Progressive Armor",
"Helms": "Progressive Helm",
"Accessories": "Progressive Accessory",
}
def yaml_item(text):
if text == "CaptainCap":
return "Captain's Cap"
elif text == "WakeWater":
return "Wakewater"
return "".join(
[(" " + c if (c.isupper() or c.isnumeric()) and not (text[i - 1].isnumeric() and c == "F") else c) for
i, c in enumerate(text)]).strip()
item_groups = {}
for item, data in item_table.items():
for group in data.groups:
item_groups[group] = item_groups.get(group, []) + [item]
def create_items(self) -> None:
items = []
starting_weapon = self.multiworld.starting_weapon[self.player].current_key.title().replace("_", " ")
if self.multiworld.progressive_gear[self.player]:
for item_group in prog_map:
if starting_weapon in self.item_name_groups[item_group]:
starting_weapon = prog_map[item_group]
break
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":
self.multiworld.push_precollected(self.create_item("Sky Coin"))
precollected_item_names = {item.name for item in self.multiworld.precollected_items[self.player]}
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:
return
if self.multiworld.progressive_gear[self.player]:
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":
for _ in range(40):
items.append(self.create_item("Sky Fragment"))
return
elif self.multiworld.sky_coin_mode[self.player] == "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"):
i.classification = ItemClassification.useful
if (self.multiworld.logic[self.player] == "expert" and self.multiworld.map_shuffle[self.player] == "none" and
item_name == "Exit Book"):
i.classification = ItemClassification.progression
items.append(i)
for item_group in ("Key Items", "Spells", "Armors", "Helms", "Shields", "Accessories", "Weapons"):
for item in self.item_name_groups[item_group]:
add_item(item)
if self.multiworld.brown_boxes[self.player] == "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":
self.multiworld.random.shuffle(filler_items)
filler_items = filler_items[39:]
items += filler_items
self.multiworld.itempool += items
if len(self.multiworld.player_ids) > 1:
early_choices = ["Sand Coin", "River Coin"]
early_item = self.multiworld.random.choice(early_choices)
self.multiworld.early_items[self.player][early_item] = 1
class FFMQItem(Item):
game = "Final Fantasy Mystic Quest"
type = None
def __init__(self, name, player: int = None):
item_data = item_table[name]
super(FFMQItem, self).__init__(
name,
item_data.classification,
item_data.id, player
)