Skip to content

Commit

Permalink
final v1.0.0 files
Browse files Browse the repository at this point in the history
  • Loading branch information
TheLX5 committed Aug 7, 2024
1 parent df2fa3b commit 5ba8d0f
Show file tree
Hide file tree
Showing 11 changed files with 1,001 additions and 664 deletions.
1,180 changes: 666 additions & 514 deletions worlds/mmx2/Client.py

Large diffs are not rendered by default.

32 changes: 16 additions & 16 deletions worlds/mmx2/Names/LocationName.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,22 @@
flame_stag_clear = "Flame Stag - Clear"
flame_stag_heart_tank = "Flame Stag - Heart Tank"
flame_stag_sub_tank = "Flame Stag - Sub Tank"
flame_stag_1up_1 = "Flame Stage - 1-Up Pickup 1"
flame_stag_hp_1 = "Flame Stage - HP Pickup 1"
flame_stag_energy_1 = "Flame Stage - Weapon Energy Pickup 1"
flame_stag_hp_2 = "Flame Stage - HP Pickup 2"
flame_stag_energy_2 = "Flame Stage - Weapon Energy Pickup 2"
flame_stag_hp_3 = "Flame Stage - HP Pickup 3"
flame_stag_hp_4 = "Flame Stage - HP Pickup 4"
flame_stag_1up_2 = "Flame Stage - 1-Up Pickup 2"
flame_stag_hp_5 = "Flame Stage - HP Pickup 5"
flame_stag_energy_3 = "Flame Stage - Weapon Energy Pickup 3"
flame_stag_hp_6 = "Flame Stage - HP Pickup 6"
flame_stag_hp_7 = "Flame Stage - HP Pickup 7"
flame_stag_energy_4 = "Flame Stage - Weapon Energy Pickup 4"
flame_stag_hp_8 = "Flame Stage - HP Pickup 8"
flame_stag_1up_3 = "Flame Stage - 1-Up Pickup 3"
flame_stag_hp_9 = "Flame Stage - HP Pickup 9"
flame_stag_1up_1 = "Flame Stag - 1-Up Pickup 1"
flame_stag_hp_1 = "Flame Stag - HP Pickup 1"
flame_stag_energy_1 = "Flame Stag - Weapon Energy Pickup 1"
flame_stag_hp_2 = "Flame Stag - HP Pickup 2"
flame_stag_energy_2 = "Flame Stag - Weapon Energy Pickup 2"
flame_stag_hp_3 = "Flame Stag - HP Pickup 3"
flame_stag_hp_4 = "Flame Stag - HP Pickup 4"
flame_stag_1up_2 = "Flame Stag - 1-Up Pickup 2"
flame_stag_hp_5 = "Flame Stag - HP Pickup 5"
flame_stag_energy_3 = "Flame Stag - Weapon Energy Pickup 3"
flame_stag_hp_6 = "Flame Stag - HP Pickup 6"
flame_stag_hp_7 = "Flame Stag - HP Pickup 7"
flame_stag_energy_4 = "Flame Stag - Weapon Energy Pickup 4"
flame_stag_hp_8 = "Flame Stag - HP Pickup 8"
flame_stag_1up_3 = "Flame Stag - 1-Up Pickup 3"
flame_stag_hp_9 = "Flame Stag - HP Pickup 9"

morph_moth_boss = "Defeated Morph Moth"
morph_moth_clear = "Morph Moth - Clear"
Expand Down
6 changes: 3 additions & 3 deletions worlds/mmx2/Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class EnergyLink(DefaultOnToggle):
EnergyLink in MMX2 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.
You make use of this feature by typing /heal <amount> or /refill <amount> in the client.
"""
display_name = "Energy Link"

Expand All @@ -26,7 +26,7 @@ class StartingLifeCount(Range):
"""
display_name = "Starting Life Count"
range_start = 0
range_end = 9
range_end = 99
default = 2

class StartingHP(Range):
Expand Down Expand Up @@ -149,7 +149,7 @@ class BaseBossRematchCount(Range):
"""
How many boss rematches are needed in the fourth X-Hunter's Base stage.
"""
display_name = "Doppler Lab 3 Rematch count"
display_name = "X-Hunter Base 4 Rematch count"
range_start = 0
range_end = 8
default = 8
Expand Down
4 changes: 3 additions & 1 deletion worlds/mmx2/Regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,11 +457,13 @@ def connect_regions(world: World):
connect(world, RegionName.x_hunter_stage, RegionName.x_hunter_stage_1)
connect(world, RegionName.x_hunter_stage, RegionName.x_hunter_stage_2)
connect(world, RegionName.x_hunter_stage, RegionName.x_hunter_stage_3)
connect(world, RegionName.x_hunter_stage, RegionName.x_hunter_stage_4)
else:
connect(world, RegionName.x_hunter_stage, RegionName.x_hunter_stage_1)
connect(world, RegionName.x_hunter_stage_1_boss, RegionName.x_hunter_stage_2)
connect(world, RegionName.x_hunter_stage_2_boss, RegionName.x_hunter_stage_3)
connect(world, RegionName.x_hunter_stage_3_boss, RegionName.x_hunter_stage_4)
connect(world, RegionName.x_hunter_stage_3_boss, RegionName.x_hunter_stage_4)

connect(world, RegionName.x_hunter_stage_4_voice, RegionName.x_hunter_stage_5)


Expand Down
4 changes: 3 additions & 1 deletion worlds/mmx2/Rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ def adjust_boss_damage_table(world: World, patch: MMX2ProcedurePatch):
if boss == "Serges Tank":
for x in range(len(data)):
data[x] = data[x]*3 if data[x] < 0x80 else data[x]
elif boss == "Wheel Gator":
patch.write_bytes(0x37669, bytearray(data))

patch.write_bytes(offset, bytearray(data))

Expand Down Expand Up @@ -249,7 +251,7 @@ def patch_rom(world: World, patch: MMX2ProcedurePatch):
patch.write_byte(action_offsets[action], button_values[button])

# Starting HP
patch.write_byte(0x01D6A, 0xFF)
patch.write_byte(0x01D6A, 0x7F)

# Write options to the ROM
value = 0
Expand Down
57 changes: 41 additions & 16 deletions worlds/mmx2/Rules.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from worlds.generic.Rules import add_rule, set_rule
from BaseClasses import CollectionState

from . import MMX2World
from .Names import LocationName, ItemName, RegionName, EventName
Expand Down Expand Up @@ -148,7 +149,7 @@ def set_rules(world: MMX2World):

# X-Hunter Base rules
if world.options.base_all_levels.value:
set_rule(multiworld.get_entrance(f"{RegionName.x_hunter_stage_4_voice} -> {RegionName.x_hunter_stage_5}", player),
set_rule(multiworld.get_entrance(f"{RegionName.x_hunter_stage} -> {RegionName.x_hunter_stage_4}", player),
lambda state: (
state.has(EventName.x_hunter_stage_1_clear, player) and
state.has(EventName.x_hunter_stage_2_clear, player) and
Expand All @@ -161,6 +162,7 @@ def set_rules(world: MMX2World):
lambda state: state.has(EventName.x_hunter_stage_2_clear, player))
set_rule(multiworld.get_entrance(f"{RegionName.x_hunter_stage_3_boss} -> {RegionName.x_hunter_stage_4}", player),
lambda state: state.has(EventName.x_hunter_stage_3_clear, player))

set_rule(multiworld.get_entrance(f"{RegionName.x_hunter_stage_4_voice} -> {RegionName.x_hunter_stage_5}", player),
lambda state: state.has(EventName.x_hunter_stage_4_clear, player))

Expand Down Expand Up @@ -278,36 +280,51 @@ def set_rules(world: MMX2World):
add_pickupsanity_logic(world)


def check_weaknesses(state: CollectionState, player: int, rulesets: list) -> bool:
states = list()
for i in range(len(rulesets)):
valid = state.has_all_counts(rulesets[i], player)
states.append(valid)
return any(states)


def add_boss_weakness_logic(world: MMX2World):
player = world.player
multiworld = world.multiworld
jammed_buster = world.options.jammed_buster.value

if world.options.base_boss_rematch_count.value == 0:
for boss in mavericks:
bosses[boss].pop()
bosses[boss].pop()

boss_dict = {}
for boss, regions in bosses.items():
boss_dict[boss] = regions.copy()
if boss in mavericks and world.options.base_boss_rematch_count.value == 0:
boss_dict[boss].pop()
boss_dict[boss].pop()

for boss, regions in boss_dict.items():
weaknesses = world.boss_weaknesses[boss]
rulesets = list()
for weakness in weaknesses:
if weakness[0] is None:
continue
rulesets = None
break
weakness = weakness[0]
ruleset = dict()
if "Check Charge" in weakness[0]:
ruleset[ItemName.arms] = jammed_buster + int(weakness[0][-1:]) - 1
else:
ruleset[weakness[0]] = 1
if len(weakness) != 1:
ruleset[weakness[1]] = 1
rulesets.append(ruleset)

if rulesets is not None:
for region in regions:
ruleset = {}
if "Check Charge" in weakness[0]:
ruleset[ItemName.arms] = jammed_buster + int(weakness[0][-1:]) - 1
else:
ruleset[weakness[0]] = 1
if len(weakness) != 1:
ruleset[weakness[1]] = 1
if "->" in region:
add_rule(multiworld.get_entrance(region, player),
lambda state, ruleset=ruleset: state.has_all_counts(ruleset, player))
lambda state, rulesets=rulesets: check_weaknesses(state, player, rulesets))
else:
add_rule(multiworld.get_location(region, player),
lambda state, ruleset=ruleset: state.has_all_counts(ruleset, player))
lambda state, rulesets=rulesets: check_weaknesses(state, player, rulesets))


def add_pickupsanity_logic(world: MMX2World):
Expand All @@ -329,6 +346,14 @@ def add_pickupsanity_logic(world: MMX2World):
state.has(ItemName.bubble_splash, player)
)
))

# Morph Moth
add_rule(multiworld.get_location(LocationName.morph_moth_1up_1, player),
lambda state: state.has(ItemName.crystal_hunter, player))

# Flame Stag
add_rule(multiworld.get_location(LocationName.flame_stag_1up_3, player),
lambda state: state.has(ItemName.legs, player))

# Overdrive Ostrich
add_rule(multiworld.get_location(LocationName.overdrive_ostrich_hp_1, player),
Expand Down
40 changes: 34 additions & 6 deletions worlds/mmx2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,28 @@ class MMX2Web(WebWorld):
"setup/en",
["lx5"]
)

setup_es = Tutorial(
"Guía de configuración de Multiworld",
"Guía para jugar Mega Man X2 en Archipelago",
"Spanish",
"setup_es.md",
"setup/es",
["lx5"]
)

tutorials = [setup_en, setup_es]

tutorials = [setup_en]

option_groups = mmx2_option_groups


class MMX2World(World):
"""
Mega Man X2 WIP
Mega Man X2, released in 1994 for the SNES, is the second game in Capcom's "Mega Man X" series.
Players control Mega Man X, a Maverick Hunter, as he battles a new group of Mavericks and the X-Hunters,
who have taken parts of his ally Zero. The game features classic run-and-gun gameplay with challenging levels,
boss battles that grant new weapons, and the use of the Cx4 chip for enhanced graphics.
"""
game = "Mega Man X2"
web = MMX2Web()
Expand All @@ -57,7 +70,7 @@ class MMX2World(World):
options_dataclass = MMX2Options
options: MMX2Options

required_client_version = (0, 4, 6)
required_client_version = (0, 5, 0)

item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = all_locations
Expand Down Expand Up @@ -214,8 +227,13 @@ def create_item(self, name: str, force_classification=False) -> Item:

def set_rules(self):
from .Rules import set_rules
if hasattr(self.multiworld, "generation_is_fake"):
if hasattr(self.multiworld, "re_gen_passthrough"):
if "Mega Man X2" in self.multiworld.re_gen_passthrough:
slot_data = self.multiworld.re_gen_passthrough["Mega Man X2"]
self.boss_weaknesses = slot_data["weakness_rules"]
set_rules(self)


def fill_slot_data(self):
slot_data = {}
Expand Down Expand Up @@ -248,9 +266,11 @@ def fill_slot_data(self):
slot_data["base_sub_tank_count"] = self.options.base_sub_tank_count.value
slot_data["base_all_levels"] = self.options.base_all_levels.value

# Write boss weaknesses to slot_data
# Write boss weaknesses to slot_data (and for UT)
slot_data["boss_weaknesses"] = {}
slot_data["weakness_rules"] = {}
for boss, entries in self.boss_weaknesses.items():
slot_data["weakness_rules"][boss] = entries.copy()
slot_data["boss_weaknesses"][boss] = []
for entry in entries:
slot_data["boss_weaknesses"][boss].append(entry[1])
Expand All @@ -259,11 +279,19 @@ def fill_slot_data(self):


def generate_early(self):
self.boss_weaknesses = {}
# Generate weaknesses
self.boss_weakness_data = {}
self.boss_weaknesses = {}
handle_weaknesses(self)


def interpret_slot_data(self, slot_data):
local_weaknesses = dict()
for boss, entries in slot_data["weakness_rules"].items():
local_weaknesses[boss] = entries.copy()
return {"weakness_rules": local_weaknesses}


def write_spoiler(self, spoiler_handle: typing.TextIO) -> None:
spoiler_handle.write(f"\nMega Man X2 boss weaknesses for {self.multiworld.player_name[self.player]}:\n")

Expand Down
Binary file modified worlds/mmx2/data/mmx2_basepatch.bsdiff4
Binary file not shown.
77 changes: 75 additions & 2 deletions worlds/mmx2/docs/en_Mega Man X2.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,76 @@
# Mega Man X3
# Mega Man X2

WIP
## Where is the options page?

The [player options page for this game](../player-options) contains all the options you need to configure and export a
config file.

## What does randomization do to this game?

Access to each Maverick Stage, weapons obtained from Mavericks and upgrades obtained from Dr. Light's capsules
are randomized in the multiworld. The requirements for entering X-Hunter Base can be randomized to require different
amount of items (Medals from Mavericks, Weapon count, Upgrade count, Heart Tank and Sub Tank count).

The game will be marked as completed when Sigma Virus is defeated.

## What Mega Man X2 items can appear in other players' worlds?
- Maverick Access Codes
- Maverick Weapons
- Armor Upgrades (Helmet/Arms/Body/Legs)
- Heart Tanks
- Sub Tanks
- 1-Ups
- HP Refill

## What is considered a location check in Mega Man X2?
- Defeating a Boss Enemy
- Using a Dr. Light Capsule
- Collecting a Heart Tank or a Sub Tank item
- Optionally, collecting a Pickup Item (1-Up/HP/Weapon) present within stages

## When the player receives an item, what happens?
A sound effect will play based on the type of item received, and the effects of the item will be immediately applied,
such as unlocking the use of a weapon mid-stage. If the effects of the item cannot be fully applied (such as receiving
a HP refill while at full health), the remaining are withheld until they can be applied.

## Quality of Life
The implementation features several enhancements to the original game's systems which attempt to make Mega Man X2 a
much smoother experience.
- **Checkpoint Selector:** Allows you to travel to any previously visited checkpoint in the game by selecting a
checkpoint at the stage select screen. Switch between different checkpoints with `L` or `R`.
- **Enhanced Helmet:** By getting the Helmet Upgrade item, the Checkpoint Selector will allow you to travel to any
checkpoint regardless if you have visited them or not.
- **X-Hunter Base Selector:** You can switch which X-Hunter Base level you will travel to by pressing `SELECT` at the
stage select screen.
- **X-Hunter Selector:** You can decide which X-Hunter you will face by pressing `SELECT` at the stage select screen.

## What is EnergyLink?
EnergyLink is an energy storage supported by certain games that is shared across all worlds in a multiworld. In Mega Man
X2, when enabled, deposits a certain amount of Energy to the EnergyLink pool. Only a quarter of the collected Energy is
successfully sent to the EnergyLink pool.

Energy from the EnergyLink pool can be transmuted into HP and Weapon Energy with the same conversion rate.
The transmutation can happen within the game itself and the client. In the client, you use `/heal <amount>` to request
a heal by `<amount>` or use a `/refill <amount>` to request a weapon refill. In the game, you press `SELECT` on the item
you want to request a refill of during the pause menu screen (X.Buster and G. Crush. will provide HP).

Weapon refills will be applied to either the current weapon, the current selected weapon on the pause menu or will be
filled from top to bottom according to the pause menu's order if none of them are selected or being used.

## Boss weakness plando
You can enforce a singular weakness into a boss with this option, ignoring weaknesses generated by the world in case
weaknesses are shuffled. The format is the following:
```yaml
boss_weakness_plando:
Wheel Gator: Lemon (Dash)
Crystal Snail: Magnet Mine
```
This will force `Wheel Gator` to receive increased damage from the basic shot performed when dashing and will force
`Crystal Snail` to receive increased damage from Magnet Mine.

## Unique Local Commands
- `/resync` Deletes the current saved data in the server which will force every item to be given again. Only has
effect during the title screen.
- `/heal <amount>` Only present with EnergyLink. Request a HP refill using EnergyLink's pool.
- `/refill <amount>` Only present with EnergyLink. Request a Weapon Energy refill using EnergyLink's pool.
- `/trade <amount>` Exchanges HP for Weapon Energy. The conversion rate is 1:1.
Loading

0 comments on commit 5ba8d0f

Please sign in to comment.