Skip to content

Commit

Permalink
update 1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
TheLX5 committed Jun 15, 2024
1 parent 12b72f4 commit f9f5c19
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 32 deletions.
79 changes: 64 additions & 15 deletions worlds/mmx3/Client.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ def __init__(self):
self.energy_link_enabled = False
self.heal_request_command = None
self.weapon_refill_request_command = None
self.using_newer_client = False
self.energy_link_details = False
self.trade_request = None
self.item_queue = []

Expand Down Expand Up @@ -148,6 +150,8 @@ async def validate_rom(self, ctx):
ctx.command_processor.commands.pop("heal")
if "refill" in ctx.command_processor.commands:
ctx.command_processor.commands.pop("refill")
if "details" in ctx.command_processor.commands:
ctx.command_processor.commands.pop("details")
return False

ctx.game = self.game
Expand All @@ -164,6 +168,8 @@ async def validate_rom(self, ctx):
ctx.command_processor.commands["heal"] = cmd_heal
if "refill" not in ctx.command_processor.commands:
ctx.command_processor.commands["refill"] = cmd_refill
if "details" not in ctx.command_processor.commands:
ctx.command_processor.commands["details"] = cmd_details
if "trade" not in ctx.command_processor.commands:
ctx.command_processor.commands["trade"] = cmd_trade

Expand Down Expand Up @@ -236,7 +242,8 @@ async def handle_energy_link(self, ctx):
{"operation": "max", "value": 0}],
}])
pool = ((ctx.stored_data[f'EnergyLink{ctx.team}'] or 0) / EXCHANGE_RATE) + (energy_packet_raw / 16)
logger.info(f"Deposited {energy_packet_raw / 16:.2f} into the energy pool. Energy available: {pool:.2f}")
if self.energy_link_details:
logger.info(f"Deposited {energy_packet_raw / 16:.2f} into the energy pool. Energy available: {pool:.2f}")
snes_buffered_write(ctx, MMX3_ENERGY_LINK_PACKET, bytearray([0x00, 0x00]))
await snes_flush_writes(ctx)

Expand Down Expand Up @@ -354,12 +361,6 @@ async def handle_item_queue(self, ctx):

next_item = self.item_queue[0]
item_id = next_item[1]

if next_item[0] == "boss access":
snes_buffered_write(ctx, MMX3_SFX_FLAG, bytearray([0x01]))
snes_buffered_write(ctx, MMX3_SFX_NUMBER, bytearray([0x1D]))
self.item_queue.pop(0)
return

# Do not give items if you can't move, are in pause state, not in the correct mode or not in gameplay state
receiving_item = await snes_read(ctx, MMX3_RECEIVING_ITEM, 0x1)
Expand Down Expand Up @@ -466,6 +467,7 @@ async def handle_item_queue(self, ctx):
if bit == 0x01:
snes_buffered_write(ctx, WRAM_START + 0x09EE, bytearray([0x18]))
snes_buffered_write(ctx, MMX3_UPGRADES, bytearray([upgrades]))
snes_buffered_write(ctx, WRAM_START + 0x0F4ED, bytearray([0x80]))
elif bit == 0x02:
jam_check = await snes_read(ctx, MMX3_JAMMED_BUSTER_ACTIVE, 0x1)
charge_shot_unlocked = await snes_read(ctx, MMX3_UNLOCKED_CHARGED_SHOT, 0x1)
Expand Down Expand Up @@ -566,14 +568,20 @@ async def game_watcher(self, ctx):

# This is going to be rewritten whenever SNIClient supports on_package
energy_link = await snes_read(ctx, MMX3_ENERGY_LINK_ENABLED, 0x1)
if energy_link[0] != 0:
if self.energy_link_enabled and f'EnergyLink{ctx.team}' in ctx.stored_data:
if self.using_newer_client:
if energy_link[0] != 0:
await self.handle_energy_link(ctx)
else:
if energy_link[0] != 0:
if self.energy_link_enabled and f'EnergyLink{ctx.team}' in ctx.stored_data:
await self.handle_energy_link(ctx)

if ctx.server and ctx.server.socket.open and not self.energy_link_enabled and ctx.team is not None:
self.energy_link_enabled = True
ctx.set_notify(f"EnergyLink{ctx.team}")
logger.info(f"Initialized EnergyLink{ctx.team}")
if ctx.server and ctx.server.socket.open and not self.energy_link_enabled and ctx.team is not None:
self.energy_link_enabled = True
ctx.set_notify(f"EnergyLink{ctx.team}")
logger.info(f"Initialized EnergyLink{ctx.team}")
self.energy_link_details = True
logger.info(f"EnergyLink detailed deposit activity enabled.")

from worlds.mmx3.Rom import weapon_rom_data, ride_armor_rom_data, upgrades_rom_data, boss_access_rom_data, refill_rom_data
from worlds.mmx3.Levels import location_id_to_level_id
Expand Down Expand Up @@ -764,12 +772,14 @@ async def game_watcher(self, ctx):
snes_buffered_write(ctx, MMX3_UNLOCKED_LEVELS, boss_access)
if item.item == 0xBD000A:
snes_buffered_write(ctx, MMX3_DOPPLER_ACCESS, bytearray([0x00]))
self.add_item_to_queue("boss access", item.item)
snes_buffered_write(ctx, MMX3_SFX_FLAG, bytearray([0x01]))
snes_buffered_write(ctx, MMX3_SFX_NUMBER, bytearray([0x1D]))

elif item.item == 0xBD0019:
# Unlock vile stage
snes_buffered_write(ctx, MMX3_VILE_ACCESS, bytearray([0x00]))
self.add_item_to_queue("boss access", item.item)
snes_buffered_write(ctx, MMX3_SFX_FLAG, bytearray([0x01]))
snes_buffered_write(ctx, MMX3_SFX_NUMBER, bytearray([0x1D]))

elif item.item in refill_rom_data:
self.add_item_to_queue(refill_rom_data[item.item][0], item.item, refill_rom_data[item.item][1])
Expand Down Expand Up @@ -898,6 +908,29 @@ async def game_watcher(self, ctx):
snes_buffered_write(ctx, MMX3_COLLECTED_RIDE_CHIPS, bytearray([collected_ride_chips_data]))
await snes_flush_writes(ctx)

def on_package(self, ctx, cmd: str, args: dict):
super().on_package(ctx, cmd, args)

if cmd == "Connected":
slot_data = args.get("slot_data", None)
self.using_newer_client = True
if slot_data["energy_link"]:
ctx.set_notify(f"EnergyLink{ctx.team}")
if ctx.ui:
ctx.ui.enable_energy_link()
ctx.ui.energy_link_label.text = "Energy: Standby"
logger.info(f"Initialized EnergyLink{ctx.team}")

elif cmd == "SetReply" and args["key"].startswith("EnergyLink"):
if ctx.ui:
pool = (args["value"] or 0) / EXCHANGE_RATE
ctx.ui.energy_link_label.text = f"Energy: {pool:.2f}"

elif cmd == "Retrieved":
if f"EnergyLink{ctx.team}" in args["keys"] and args["keys"][f"EnergyLink{ctx.team}"] and ctx.ui:
pool = (args["keys"][f"EnergyLink{ctx.team}"] or 0) / EXCHANGE_RATE
ctx.ui.energy_link_label.text = f"Energy: {pool:.2f}"


def cmd_pool(self):
"""
Expand Down Expand Up @@ -1008,4 +1041,20 @@ def cmd_trade(self, amount: str = ""):
logger.info(f"Set up trade for {amount} Weapon Energy. Pause the game to process the trade.")
else:
logger.info(f"You need to specify how much Weapon Energy you will request.")

def cmd_details(self):
"""
Toggles displaying energy deposit activity into the console when EnergyLink is active.
"""
if self.ctx.game != "Mega Man X3":
logger.warning("This command can only be used while playing Mega Man X3")
if (not self.ctx.server) or self.ctx.server.socket.closed or not self.ctx.client_handler.game_state:
logger.info(f"Must be connected to server and in game.")
else:
if self.ctx.client_handler.energy_link_details:
self.ctx.client_handler.energy_link_details = False
logger.info(f"EnergyLink detailed deposit activity disabled.")
else:
self.ctx.client_handler.energy_link_details = True
logger.info(f"EnergyLink detailed deposit activity enabled.")

49 changes: 46 additions & 3 deletions worlds/mmx3/Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import typing

#from Options import OptionGroup, Choice, Range, Toggle, DefaultOnToggle, OptionSet, DeathLink, PerGameCommonOptions, StartInventoryPool
from Options import Choice, Range, Toggle, DefaultOnToggle, OptionSet, DeathLink, PerGameCommonOptions, StartInventoryPool
from Options import Choice, Range, Toggle, DefaultOnToggle, OptionDict, OptionSet, DeathLink, PerGameCommonOptions, StartInventoryPool
from schema import Schema, And, Use, Optional

from .Rom import action_buttons, action_names
from .Weaknesses import boss_weaknesses, weapons_chaotic

class EnergyLink(DefaultOnToggle):
"""
Expand Down Expand Up @@ -64,8 +68,8 @@ class BossWeaknessRando(Choice):
display_name = "Boss Weakness Randomization"
option_vanilla = 0
option_shuffled = 1
option_chaotic_double = 2
option_chaotic_single = 3
option_chaotic_double = 3
option_chaotic_single = 2
default = 0

class BossWeaknessStrictness(Choice):
Expand Down Expand Up @@ -115,6 +119,12 @@ class DisableChargeFreeze(DefaultOnToggle):
"""
display_name = "Disable Level 3 Charge freeze after shooting"

class LongJumps(Toggle):
"""
Allows X to perform longer jumps when holding down the Dash button. Only works after getting a Legs Upgrade.
"""
display_name = "Long Jumps"

class LogicBossWeakness(DefaultOnToggle):
"""
Most bosses will logically expect you to have its weakness.
Expand Down Expand Up @@ -331,6 +341,35 @@ class ByteMedalCount(Range):
range_end = 6
default = 5

class ButtonConfiguration(OptionDict):
"""
Default buttons for every action.
"""
display_name = "Button Configuration"
schema = Schema({action_name: And(str, Use(str.upper), lambda s: s in action_buttons) for action_name in action_names})
default = {
"SHOT": "Y",
"JUMP": "B",
"DASH": "A",
"SELECT_L": "L",
"SELECT_R": "R",
"MENU": "START"
}

class PlandoWeaknesses(OptionDict):
"""
Forces bosses to have a specific weakness. Uses the names that appear on the chaotic weakness set.
Format:
Boss Name: Weakness Name
"""
display_name = "Button Configuration"
schema = Schema({
Optional(boss_name):
And(str, lambda weapon: weapon in weapons_chaotic.keys()) for boss_name in boss_weaknesses.keys()
})
default = {}

mmx3_option_groups = [
"""
OptionGroup("Gameplay Options", [
Expand All @@ -339,6 +378,7 @@ class ByteMedalCount(Range):
HeartTankEffectiveness,
JammedBuster,
DisableChargeFreeze,
LongJumps,
]),
OptionGroup("Boss Weakness Options", [
BossWeaknessRando,
Expand Down Expand Up @@ -378,16 +418,19 @@ class MMX3Options(PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool
death_link: DeathLink
energy_link: EnergyLink
button_configuration: ButtonConfiguration
starting_life_count: StartingLifeCount
starting_hp: StartingHP
heart_tank_effectiveness: HeartTankEffectiveness
boss_weakness_rando: BossWeaknessRando
boss_weakness_strictness: BossWeaknessStrictness
boss_weakness_plando: PlandoWeaknesses
boss_randomize_hp: BossRandomizedHP
pickupsanity: PickupSanity
jammed_buster: JammedBuster
zsaber_in_pool: ZSaberInPool
disable_charge_freeze: DisableChargeFreeze
long_jumps: LongJumps
doppler_open: DopplerOpen
doppler_medal_count: DopplerMedalCount
doppler_weapon_count: DopplerWeaponCount
Expand Down
32 changes: 30 additions & 2 deletions worlds/mmx3/Rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from worlds.AutoWorld import World
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes

from .Weaknesses import boss_weakness_data
action_names = ("SHOT", "JUMP", "DASH", "SELECT_L", "SELECT_R", "MENU")
action_buttons = ("Y", "B", "A", "L", "R", "X", "START", "SELECT")

HASH_US = 'cfe8c11f0dce19e4fa5f3fd75775e47c'
HASH_LEGACY = 'ff683b75e75e9b59f0c713c7512a016b'
Expand Down Expand Up @@ -224,6 +225,29 @@ def patch_rom(world: World, patch: MMX3ProcedurePatch):
patch.name.extend([0] * (21 - len(patch.name)))
patch.write_bytes(0x7FC0, patch.name)

# Remap buttons
button_values = {
"A": 0x20,
"B": 0x80,
"X": 0x10,
"Y": 0x40,
"L": 0x08,
"R": 0x04,
"START": 0x01,
"SELECT": 0x02,
}
action_offsets = {
"SHOT": 0x360E3,
"JUMP": 0x360E4,
"DASH": 0x360E5,
"SELECT_L": 0x360E6,
"SELECT_R": 0x360E7,
"MENU": 0x360E8,
}
button_config = world.options.button_configuration.value
for action, button in button_config.items():
patch.write_byte(action_offsets[action], button_values[button])

# Setup starting HP
patch.write_byte(0x007487, world.options.starting_hp.value)
patch.write_byte(0x0019B6, world.options.starting_hp.value)
Expand Down Expand Up @@ -281,7 +305,6 @@ def patch_rom(world: World, patch: MMX3ProcedurePatch):
patch.write_byte(0x17FFE8, value)

#patch.write_byte(0x17FFF1, world.options.doppler_lab_1_boss.value)
patch.write_byte(0x17FFF1, 0x00)
patch.write_byte(0x17FFF2, world.options.doppler_lab_2_boss.value)
patch.write_byte(0x17FFF3, world.options.doppler_lab_3_boss_rematch_count.value)
patch.write_byte(0x17FFF4, world.options.bit_medal_count.value)
Expand All @@ -303,6 +326,11 @@ def patch_rom(world: World, patch: MMX3ProcedurePatch):
patch.write_byte(0x17FFFD, world.options.heart_tank_effectiveness.value)
patch.write_byte(0x17FFFE, world.options.doppler_all_labs.value)

value = 0
if world.options.long_jumps.value:
value |= 0x01
patch.write_byte(0x17FFF1, value)

patch.write_file("token_patch.bin", patch.get_token_binary())


Expand Down
29 changes: 19 additions & 10 deletions worlds/mmx3/Weaknesses.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@
def handle_weaknesses(world):
shuffle_type = world.options.boss_weakness_rando.value
strictness_type = world.options.boss_weakness_strictness.value
boss_weakness_plando = world.options.boss_weakness_plando.value

if shuffle_type != "vanilla":
weapon_list = weapons.keys()
Expand Down Expand Up @@ -561,6 +562,21 @@ def handle_weaknesses(world):
damage_table[0x0C] = 0x03
damage_table[0x15] = 0x03

if boss in boss_weakness_plando.keys():
if shuffle_type != "vanilla":
chosen_weapon = boss_weakness_plando[boss]
if chosen_weapon not in boss_excluded_weapons[boss]:
data = weapons_chaotic[chosen_weapon].copy()
for entry in data:
world.boss_weaknesses[boss].append(entry)
damage = entry[2]
damage_table[entry[1]] = damage
world.boss_weakness_data[boss] = damage_table.copy()
continue

print (f"[{world.multiworld.player_name[world.player]}] Weakness plando failed for {boss}, contains an excluded weapon. Choosing an alternate weapon...")

if shuffle_type != "vanilla":
copied_weapon_list = weapon_list.copy()
for weapon in boss_excluded_weapons[boss]:
if weapon in copied_weapon_list:
Expand All @@ -574,23 +590,16 @@ def handle_weaknesses(world):
damage = entry[2]
damage_table[entry[1]] = damage

elif shuffle_type == 2:
for _ in range(2):
elif shuffle_type >= 2:
for _ in range(shuffle_type - 1):
chosen_weapon = world.random.choice(copied_weapon_list)
data = weapons_chaotic[chosen_weapon].copy()
copied_weapon_list.remove(chosen_weapon)
for entry in data:
world.boss_weaknesses[boss].append(entry)
damage = entry[2]
damage_table[entry[1]] = damage

elif shuffle_type == 3:
chosen_weapon = world.random.choice(copied_weapon_list)
data = weapons_chaotic[chosen_weapon].copy()
for entry in data:
world.boss_weaknesses[boss].append(entry)
damage = entry[2]
damage_table[entry[1]] = damage
world.boss_weakness_data[boss] = damage_table.copy()

else:
for entry in boss_weaknesses[boss]:
Expand Down
3 changes: 1 addition & 2 deletions worlds/mmx3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ def fill_slot_data(self):
slot_data = {}

# Write options to slot_data
slot_data["energy_link"] = self.options.energy_link.value
slot_data["boss_weakness_rando"] = self.options.boss_weakness_rando.value
slot_data["boss_weakness_strictness"] = self.options.boss_weakness_strictness.value
slot_data["pickupsanity"] = self.options.pickupsanity.value
Expand Down Expand Up @@ -357,8 +358,6 @@ def generate_early(self):
self.boss_weaknesses = {}
self.boss_weakness_data = {}
handle_weaknesses(self)
early_stage = self.random.choice(list(item_groups["Access Codes"]))
self.multiworld.local_early_items[self.player][early_stage] = 1


def write_spoiler(self, spoiler_handle: typing.TextIO) -> None:
Expand Down
Binary file modified worlds/mmx3/data/mmx3_basepatch.bsdiff4
Binary file not shown.

0 comments on commit f9f5c19

Please sign in to comment.