Skip to content

Commit

Permalink
v1.0.0 files
Browse files Browse the repository at this point in the history
  • Loading branch information
TheLX5 committed May 14, 2024
1 parent 68ff861 commit 8f4c786
Show file tree
Hide file tree
Showing 9 changed files with 1,119 additions and 265 deletions.
276 changes: 202 additions & 74 deletions worlds/mmx3/Client.py

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions worlds/mmx3/Items.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class MMX3Item(Item):
junk_table = {
ItemName.small_hp: ItemData(0xBD0030, False),
ItemName.large_hp: ItemData(0xBD0031, False),
ItemName.small_weapon: ItemData(0xBD0032, False),
ItemName.large_weapon: ItemData(0xBD0033, False),
ItemName.life: ItemData(0xBD0034, False),
}

Expand Down
10 changes: 6 additions & 4 deletions worlds/mmx3/Names/ItemName.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
stage_doppler_lab = "Dr. Doppler's Lab Access Codes"

# Third Armor
third_armor_helmet = "Progressive Third Armor Helmet"
third_armor_body = "Progressive Third Armor Body"
third_armor_arms = "Progressive Third Armor Arms"
third_armor_legs = "Progressive Third Armor Legs"
third_armor_helmet = "Helmet Upgrade"
third_armor_body = "Body Upgrade"
third_armor_arms = "Arms Upgrade"
third_armor_legs = "Legs Upgrade"

# Weapons
parasitic_bomb = "Parasitic Bomb"
Expand Down Expand Up @@ -45,3 +45,5 @@
small_hp = "Small HP Refill"
large_hp = "Large HP Refill"
life = "1-Up"
small_weapon = "Small Weapon Energy Refill"
large_weapon = "Large Weapon Energy Refill"
98 changes: 95 additions & 3 deletions worlds/mmx3/Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ class LogicZSaber(Choice):
class EnergyLink(DefaultOnToggle):
"""
Enable EnergyLink support.
EnergyLink works as a big Sub Tank/HP pool where players can request HP manually or automatically when
they lose HP. You make use of this feature by typing /pool, /heal <amount> or /autoheal in the client.
EnergyLink in MMX3 works as a big HP and Weapon Energy pool that the players can use to request HP
or Weapon Energy whenever they need to.
You make use of this feature by typing /pool, /heal <amount>, /refill <amount> or /autoheal in the client.
"""
display_name = "Energy Link"

Expand All @@ -34,6 +35,81 @@ class StartingLifeCount(Range):
range_end = 9
default = 2

class StartingHP(Range):
"""
How much HP X will have at the start of the game.
Note: Going over 32 HP may cause visual bugs in either gameplay or the pause menu.
The max HP is capped at 56.
"""
display_name = "Starting HP"
range_start = 1
range_end = 32
default = 16

class HeartTankEffectiveness(Range):
"""
How many units of HP each Heart tank will provide to the user.
Note: Going over 32 HP may cause visual bugs in either gameplay or the pause menu.
The max HP is capped at 56.
"""
display_name = "Heart Tank Effectiveness"
range_start = 1
range_end = 8
default = 2

class BossWeaknessRando(Choice):
"""
Every main boss will have its weakness randomized.
vanilla: Bosses retain their original weaknesses
shuffled: Bosses have their weaknesses shuffled
chaotic_double: Bosses will have two random weaknesses under the chaotic set
chaotic_single: Bosses will have one random weakness under the chaotic set
The chaotic set makes every weapon charge level a separate weakness instead of keeping
them together, meaning that a boss can be weak to Charged Frost Shield but not its
uncharged version.
"""
display_name = "Boss Weakness Randomization"
option_vanilla = 0
option_shuffled = 1
option_chaotic_double = 2
option_chaotic_single = 3
default = 0

class BossWeaknessStrictness(Choice):
"""
How strict boss weaknesses will be.
not_strict: Allow every weapon to deal damage to the bosses
weakness_and_buster: Only allow the weakness and buster to deal damage to the bosses
weakness_and_upgraded_buster: Only allow the weakness and buster charge levels 3 & 4 to deal damage to the bosses
only_weakness: Only the weakness will deal damage to the bosses
Z-Saber damage output will be cut to 50%/37.5%/25% of its original damage according to the strictness setting.
"""
display_name = "Boss Weakness Strictness"
option_not_strict = 0
option_weakness_and_buster = 1
option_weakness_and_upgraded_buster = 2
option_only_weakness = 3
default = 0

class BossRandomizedHP(Choice):
"""
Wheter to randomize the boss' hp or not.
off: Bosses' HP will not be randomized
weak: Bosses will have [1,32] HP
regular: Bosses will have [16,48] HP
strong: Bosses will have [32,64] HP
chaotic: Bosses will have [1,64] HP
"""
display_name = "Boss Randomize HP"
option_off = 0
option_weak = 1
option_regular = 2
option_strong = 3
option_chaotic = 4
default = 0

class JammedBuster(Toggle):
"""
Jams X's buster making it only able to shoot lemons.
Expand All @@ -50,6 +126,8 @@ class DisableChargeFreeze(DefaultOnToggle):
class LogicBossWeakness(DefaultOnToggle):
"""
Every main boss will logically expect you to have its weakness.
This option will be forced if the Boss Weakness Strictness setting is set to require only the weakness or
the upgraded buster option.
"""
display_name = "Boss Weakness Logic"

Expand Down Expand Up @@ -98,6 +176,14 @@ class Lab3BossRematchCount(Range):
range_end = 8
default = 8

class LabsBundleUnlock(Toggle):
"""
Whether to unlock Dr. Doppler's Lab 1-3 levels as a group or not.
Unlocking level 4 requires getting all Lab levels cleared.
"""
display_name = "Doppler Lab Levels Bundle Unlock"


class DopplerOpen(Choice):
"""
Under what conditions will Dr. Doppler's lab open.
Expand Down Expand Up @@ -257,14 +343,20 @@ class MMX3Options(PerGameCommonOptions):
death_link: DeathLink
energy_link: EnergyLink
starting_life_count: StartingLifeCount
starting_hp: StartingHP
heart_tank_effectiveness: HeartTankEffectiveness
boss_weakness_rando: BossWeaknessRando
boss_weakness_strictness: BossWeaknessStrictness
boss_randomize_hp: BossRandomizedHP
pickupsanity: PickupSanity
jammed_buster: JammedBuster
disable_charge_freeze: DisableChargeFreeze
pickupsanity: PickupSanity
logic_boss_weakness: LogicBossWeakness
logic_vile_required: LogicRequireVileDefeatForDoppler
logic_z_saber: LogicZSaber
doppler_lab_2_boss: Lab2Boss
doppler_lab_3_boss_rematch_count: Lab3BossRematchCount
doppler_all_labs: LabsBundleUnlock
doppler_open: DopplerOpen
doppler_medal_count: DopplerMedalCount
doppler_weapon_count: DopplerWeaponCount
Expand Down
124 changes: 113 additions & 11 deletions worlds/mmx3/Rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import Utils
import hashlib
import os
from typing import Optional, TYPE_CHECKING
from typing import Optional
from pkgutil import get_data

from worlds.AutoWorld import World
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes

from .Weaknesses import boss_weakness_data

HASH_US = 'cfe8c11f0dce19e4fa5f3fd75775e47c'
HASH_LEGACY = 'ff683b75e75e9b59f0c713c7512a016b'

Expand Down Expand Up @@ -51,13 +53,61 @@
}

refill_rom_data = {
0xBD0030: ["small hp refill"],
0xBD0031: ["large hp refill"],
0xBD0034: ["1up"],
#0xBD0032: ["small weapon refill"],
#0xBD0033: ["large weapon refill"]
0xBD0030: ["hp refill", 2],
0xBD0031: ["hp refill", 8],
0xBD0034: ["1up", 0],
0xBD0032: ["weapon refill", 2],
0xBD0033: ["weapon refill", 8]
}

boss_weakness_offsets = {
"Blast Hornet": 0x03674B,
"Blizzard Buffalo": 0x036771,
"Gravity Beetle": 0x036797,
"Toxic Seahorse": 0x0367BD,
"Volt Catfish": 0x0367E3,
"Crush Crawfish": 0x036809,
"Tunnel Rhino": 0x03682F,
"Neon Tiger": 0x036855,
"Bit": 0x03687B,
"Byte": 0x0368A1,
"Vile": 0x0368C7,
"Vile Goliath": 0x0368ED,
"Doppler": 0x036913,
"Sigma": 0x036939,
"Kaiser Sigma": 0x03695F,
"Godkarmachine": 0x037F00,
"Press Disposer": 0x037FC8,
"Worm Seeker-R": 0x03668D,
"Shurikein": 0x037F28,
"Hotareeca": 0x037F50,
"Volt Kurageil": 0x037F78,
"Hell Crusher": 0x037FA0,
}

boss_hp_caps_offsets = {
"Maoh": 0x016985,
"Blast Hornet": 0x1C9DC2,
"Blizzard Buffalo": 0x01C9CB,
"Gravity Beetle": 0x09F3C3,
"Toxic Seahorse": 0x09E612,
"Volt Catfish": 0x09EBC0,
"Crush Crawfish": 0x01D1B2,
"Tunnel Rhino": 0x1FE765,
"Neon Tiger": 0x09DE11,
"Bit": 0x0390F2,
"Byte": 0x1E4614,
"Vile": 0x02AC3E,
"Vile Kangaroo": 0x03958F,
"Vile Goliath": 0x02A4EA,
"Doppler": 0x09D737,
"Sigma": 0x0294F2,
"Kaiser Sigma": 0x029B1F,
"Godkarmachine": 0x028F60,
"Press Disposer": 0x09C6B9,
}


class MMX3ProcedurePatch(APProcedurePatch, APTokenMixin):
hash = [HASH_US, HASH_LEGACY]
game = "Mega Man X3"
Expand All @@ -79,6 +129,42 @@ def write_byte(self, offset, value):
def write_bytes(self, offset, value: typing.Iterable[int]):
self.write_token(APTokenTypes.WRITE, offset, bytes(value))

def adjust_boss_damage_table(world: World, patch: MMX3ProcedurePatch):
for boss, data in world.boss_weakness_data.items():
try:
offset = boss_weakness_offsets[boss]
if boss == "Worm Seeker-R":
for x in range(len(data)):
if x == 0x02 or x == 0x04 or x == 0x05:
data[x] = 0x7F
else:
data[x] = data[x]*3 if data[x] < 0x80 else data[x]
except:
continue
patch.write_bytes(offset, bytearray(data))

# Adjust Charged Triad Thunder lag (lasts 90 less frames)
patch.write_byte(0x1FD2D1, 0x14)

def adjust_boss_hp(world: World, patch: MMX3ProcedurePatch):
option = world.options.boss_randomize_hp
if option == "weak":
ranges = [1,32]
elif option == "regular":
ranges = [16,48]
elif option == "strong":
ranges = [32,64]
elif option == "chaotic":
ranges = [1,64]

for boss, offset in boss_hp_caps_offsets.items():
if boss == "Blast Hornet":
patch.write_byte(offset, world.random.randint(ranges[0], 32))
else:
patch.write_byte(offset, world.random.randint(ranges[0], ranges[1]))



def patch_rom(world: World, patch: MMX3ProcedurePatch):
from Utils import __version__

Expand Down Expand Up @@ -116,11 +202,27 @@ def patch_rom(world: World, patch: MMX3ProcedurePatch):
patch.write_bytes(0x0FF84, bytearray([0xFF for _ in range(0x007C)]))
patch.write_bytes(0x1FA80, bytearray([0xFF for _ in range(0x0580)]))

if world.options.boss_weakness_rando != "vanilla":
adjust_boss_damage_table(world, patch)

if world.options.boss_randomize_hp != "off":
adjust_boss_hp(world, patch)

# Edit the ROM header
patch.name = bytearray(f'MMX3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21]
patch.name.extend([0] * (21 - len(patch.name)))
patch.write_bytes(0x7FC0, patch.name)

# Setup starting HP
patch.write_byte(0x007487, world.options.starting_hp.value)
patch.write_byte(0x0019B6, world.options.starting_hp.value)
patch.write_byte(0x0021CC, (world.options.starting_hp.value + (world.options.heart_tank_effectiveness.value * 8)) | 0x80)

# Setup starting life count
patch.write_byte(0x0019B1, world.options.starting_life_count.value)
patch.write_byte(0x0072C3, world.options.starting_life_count.value)
patch.write_byte(0x0021BE, world.options.starting_life_count.value)

# Write options to the ROM
patch.write_byte(0x17FFE0, world.options.doppler_open.value)
patch.write_byte(0x17FFE1, world.options.doppler_medal_count.value)
Expand Down Expand Up @@ -170,11 +272,11 @@ def patch_rom(world: World, patch: MMX3ProcedurePatch):
patch.write_byte(0x17FFF8, world.options.death_link.value)

patch.write_byte(0x17FFF9, world.options.jammed_buster.value)

# Setup starting life count
patch.write_byte(0x0019B1, world.options.starting_life_count.value)
patch.write_byte(0x0072C3, world.options.starting_life_count.value)
patch.write_byte(0x0021BE, world.options.starting_life_count.value)
patch.write_byte(0x17FFFA, world.options.boss_weakness_rando.value)
patch.write_byte(0x17FFFB, world.options.boss_weakness_strictness.value)
patch.write_byte(0x17FFFC, world.options.starting_hp.value)
patch.write_byte(0x17FFFD, world.options.heart_tank_effectiveness.value)
patch.write_byte(0x17FFFE, world.options.doppler_all_labs.value)

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

Expand Down
Loading

0 comments on commit 8f4c786

Please sign in to comment.