forked from ArchipelagoMW/Archipelago
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Final Fantasy Mystic Quest: Implement new game (ArchipelagoMW#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.
- Loading branch information
Showing
16 changed files
with
8,072 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
|
||
from NetUtils import ClientStatus, color | ||
from worlds.AutoSNIClient import SNIClient | ||
from .Regions import offset | ||
import logging | ||
|
||
snes_logger = logging.getLogger("SNES") | ||
|
||
ROM_NAME = (0x7FC0, 0x7FD4 + 1 - 0x7FC0) | ||
|
||
READ_DATA_START = 0xF50EA8 | ||
READ_DATA_END = 0xF50FE7 + 1 | ||
|
||
GAME_FLAGS = (0xF50EA8, 64) | ||
COMPLETED_GAME = (0xF50F22, 1) | ||
BATTLEFIELD_DATA = (0xF50FD4, 20) | ||
|
||
RECEIVED_DATA = (0xE01FF0, 3) | ||
|
||
ITEM_CODE_START = 0x420000 | ||
|
||
IN_GAME_FLAG = (4 * 8) + 2 | ||
|
||
NPC_CHECKS = { | ||
4325676: ((6 * 8) + 4, False), # Old Man Level Forest | ||
4325677: ((3 * 8) + 6, True), # Kaeli Level Forest | ||
4325678: ((25 * 8) + 1, True), # Tristam | ||
4325680: ((26 * 8) + 0, True), # Aquaria Vendor Girl | ||
4325681: ((29 * 8) + 2, True), # Phoebe Wintry Cave | ||
4325682: ((25 * 8) + 6, False), # Mysterious Man (Life Temple) | ||
4325683: ((29 * 8) + 3, True), # Reuben Mine | ||
4325684: ((29 * 8) + 7, True), # Spencer | ||
4325685: ((29 * 8) + 6, False), # Venus Chest | ||
4325686: ((29 * 8) + 1, True), # Fireburg Tristam | ||
4325687: ((26 * 8) + 1, True), # Fireburg Vendor Girl | ||
4325688: ((14 * 8) + 4, True), # MegaGrenade Dude | ||
4325689: ((29 * 8) + 5, False), # Tristam's Chest | ||
4325690: ((29 * 8) + 4, True), # Arion | ||
4325691: ((29 * 8) + 0, True), # Windia Kaeli | ||
4325692: ((26 * 8) + 2, True), # Windia Vendor Girl | ||
|
||
} | ||
|
||
|
||
def get_flag(data, flag): | ||
byte = int(flag / 8) | ||
bit = int(0x80 / (2 ** (flag % 8))) | ||
return (data[byte] & bit) > 0 | ||
|
||
|
||
class FFMQClient(SNIClient): | ||
game = "Final Fantasy Mystic Quest" | ||
|
||
async def validate_rom(self, ctx): | ||
from SNIClient import snes_read | ||
rom_name = await snes_read(ctx, *ROM_NAME) | ||
if rom_name is None: | ||
return False | ||
if rom_name[:2] != b"MQ": | ||
return False | ||
|
||
ctx.rom = rom_name | ||
ctx.game = self.game | ||
ctx.items_handling = 0b001 | ||
return True | ||
|
||
async def game_watcher(self, ctx): | ||
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read | ||
|
||
check_1 = await snes_read(ctx, 0xF53749, 1) | ||
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 == b'\x00' or check_2 == b'\x00': | ||
return | ||
|
||
def get_range(data_range): | ||
return data[data_range[0] - READ_DATA_START:data_range[0] + data_range[1] - READ_DATA_START] | ||
completed_game = get_range(COMPLETED_GAME) | ||
battlefield_data = get_range(BATTLEFIELD_DATA) | ||
game_flags = get_range(GAME_FLAGS) | ||
|
||
if game_flags is None: | ||
return | ||
if not get_flag(game_flags, IN_GAME_FLAG): | ||
return | ||
|
||
if not ctx.finished_game: | ||
if completed_game[0] & 0x80 and game_flags[30] & 0x18: | ||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) | ||
ctx.finished_game = True | ||
|
||
old_locations_checked = ctx.locations_checked.copy() | ||
|
||
for container in range(256): | ||
if get_flag(game_flags, (0x20 * 8) + container): | ||
ctx.locations_checked.add(offset["Chest"] + container) | ||
|
||
for location, data in NPC_CHECKS.items(): | ||
if get_flag(game_flags, data[0]) is data[1]: | ||
ctx.locations_checked.add(location) | ||
|
||
for battlefield in range(20): | ||
if battlefield_data[battlefield] == 0: | ||
ctx.locations_checked.add(offset["BattlefieldItem"] + battlefield + 1) | ||
|
||
if old_locations_checked != ctx.locations_checked: | ||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": ctx.locations_checked}]) | ||
|
||
if received[0] == 0: | ||
received_index = int.from_bytes(received[1:], "big") | ||
if received_index < len(ctx.items_received): | ||
item = ctx.items_received[received_index] | ||
received_index += 1 | ||
code = (item.item - ITEM_CODE_START) + 1 | ||
if code > 256: | ||
code -= 256 | ||
snes_buffered_write(ctx, RECEIVED_DATA[0], bytes([code, *received_index.to_bytes(2, "big")])) | ||
await snes_flush_writes(ctx) |
Oops, something went wrong.