Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mega Man X: Implement New Game #3842

Open
wants to merge 83 commits into
base: main
Choose a base branch
from
Open

Mega Man X: Implement New Game #3842

wants to merge 83 commits into from

Conversation

TheLX5
Copy link
Contributor

@TheLX5 TheLX5 commented Aug 24, 2024

What is this fixing or adding?

Implements Mega Man X in Archipelago.

How was this tested?

Months of weekly sessions in private and public games

If this makes graphical changes, please attach screenshots.

image

TheLX5 added 30 commits April 21, 2024 19:44
@TheLX5
Copy link
Contributor Author

TheLX5 commented Aug 24, 2024

Part of the client code requires #3093 to be properly tested. It can work without it though.

@github-actions github-actions bot added the waiting-on: peer-review Issue/PR has not been reviewed by enough people yet. label Aug 24, 2024
@NewSoupVi NewSoupVi added the is: new game Pull requests for implementing new games into Archipelago. label Aug 24, 2024
Comment on lines 185 to 187
game_state = await snes_read(ctx, MMX_GAME_STATE, 0x1)
menu_state = await snes_read(ctx, MMX_MENU_STATE, 0x1)
gameplay_state = await snes_read(ctx, MMX_GAMEPLAY_STATE, 0x1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, all the code in this client is treating snes_read as if it's as fast and as cheap and as reliable as reading from your own ram.
The await should be a clue that that isn't the case.
I think it's better to treat snes_read like asking someone to go to the other side of town to get something for you.

In this example, you're asking someone to cross town to get MMX_GAME_STATE,
and then after they return, you immediate ask them to go back, to the same place they just came from, to get another small thing MMX_MENU_STATE that was sitting right next to the first thing.
The person would probably say "Why didn't you tell me to get that in the first trip?"
And then you ask them to go get a 3rd single byte that's right next to the first two.

I would recommend reworking most of the snes_read to just get a few bigger blocks of memory that have everything you need all at once.

I was surprised by how much faster it could be getting a block of memory at once instead of individual bytes one at a time.
https://discord.com/channels/731205301247803413/1022545979146252288/1239239492385247252

(This link is not meant to indicate that FF6 is a good example to follow, because there are still a lot of patterns that aren't so good. It's just to see players noticing and talking about the amount of time things take.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's even better to get some memory that you don't need to make fewer trips.
example:
If you need 101, 102, 117, and 118
it's probably better to just get all of 101-118
even though you don't need 103-116

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another reason to do this rework to read memory in fewer bigger chunks:

The more times someone has to travel across town, the more likely they are to get in an accident and not return.

$ python -m pip install --upgrade mypy
$ python -m mypy --check-untyped-defs --follow-imports=silent --allow-untyped-def --disable-error-code=assignment --disable-error-code=operator --disable-error-code=comparison-overlap --disable-error-code=attr-defined worlds/mmx/Client.py

This reports 104 errors and almost all of these are real bugs that will happen regularly.
I have seen from someone else an indication that MMX has one of the most unstable clients of all SNI clients.
From this, I can see why.

Run the same command on Super Metroid's `Client.py`, and you can see that it has only 1 error report.

I recommend that you not try to handle these mypy error reports until after the rework that I suggested, because afterwards there will be fewer error reports to handle.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh, that makes so much sense with some reports I've received before. I do assume those show up less frequently in powerful machines, as I've not seen my client(s) being unstable on my end at all.

I'm gonna rework X1's client (and the other two as well, they're pretty much the same code), I believe I can group several of those as most of the RAM reads regarding collected locations and upgrades are almost in the same block of RAM.

@TheLX5
Copy link
Contributor Author

TheLX5 commented Sep 7, 2024

image

Don't know if it's actually stable as I never faced its unstability, but seems to be working fine.

Also I got Scipio's requested changes for X3 ported to X1, I'm focusing on one game at the time and since all of them share almost the very same code, functions, etc. I can port the changes to X2 and X3 with ease after this one is given a thumbs up with the changes.

@TheLX5
Copy link
Contributor Author

TheLX5 commented Sep 11, 2024

There was some discussion that I brought up in the ap-world-dev channel regarding my usage of the hint system. Link to the conversation.

I'm using the current hint system to also display boss weaknesses in case they're shuffled via the Entrance field. This lead to me placing a location-item pair which triggers the goal when the item is obtained, which then users could plando or start_inventory into their games. ALTTP (?), SMW and DKC3 do this and just that day I realized that it was actually frowned upon so I'm gonna be changing it.

image

I did reach to the conclusion of recreating what HintMachine does for my own Weakness Hint System (tm), I haven't started worked on it, so I'd like for future reviewers to skip comments on that if possible as it's getting a rework at some point... or in case people want to place their own opinion on the aformentioned matter.

My opinion on that it's that it's on the user end if they want to screw up their yamls like that, but if it'd be a blocker for this world to be approved & merged into main, well I gotta agree with the people who use the Archipelago API more often/know it like the back of their hands :P

Comment on lines +133 to +136
itempool += [self.create_item(ItemName.stage_sigma_fortress)]

# Add weapons into the pool
itempool += [self.create_item(ItemName.electric_spark)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all of these itempool += [ would be better as itempool.append( instead.
That way it doesn't create a new list for every +=

Comment on lines +222 to +226
def create_item(self, name: str, force_classification: ItemClassification = False) -> Item:
data = item_table[name]

if force_classification:
classification = force_classification
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a dangerous interface because it doesn't allow you to force something to be filler.
I see that you may not want to force any filler right now, but someone might be surprised by a bug if something is changed in the future.

Suggested change
def create_item(self, name: str, force_classification: ItemClassification = False) -> Item:
data = item_table[name]
if force_classification:
classification = force_classification
def create_item(self, name: str, force_classification: ItemClassification = False) -> Item:
data = item_table[name]
if isinstance(force_classification, ItemClassification):
classification = force_classification

This change would allow forcing something to be filler.

Comment on lines +240 to +243
if hasattr(self.multiworld, "generation_is_fake"):
if hasattr(self.multiworld, "re_gen_passthrough"):
if "Mega Man X" in self.multiworld.re_gen_passthrough:
self.boss_weaknesses = self.multiworld.re_gen_passthrough["Mega Man X"]["weakness_rules"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most other games that do things like this for UT include a comment explaining that it's for UT, and maybe also a little explanation in the comment for why it's needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fair 👍

build_rules(self)


def fill_slot_data(self) -> Dict[int, Any]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def fill_slot_data(self) -> Dict[int, Any]:
def fill_slot_data(self) -> Dict[str, Any]:

I wonder how that got there...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops xd
I'm too new to typing and all that stuff.

Comment on lines +314 to +317
weaknesses = ""
for i in range(len(data)):
weaknesses += f"{weapon_id[data[i][1]]}, "
weaknesses = weaknesses[:-2]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
weaknesses = ""
for i in range(len(data)):
weaknesses += f"{weapon_id[data[i][1]]}, "
weaknesses = weaknesses[:-2]
weaknesses = ", ".join([weapon_id[weak[1]] for weak in data])

I think this will give the same result. You should test to make sure.

if amount:
try:
amount = int(amount)
except:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
except:
except ValueError:

https://docs.astral.sh/ruff/rules/bare-except/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
is: new game Pull requests for implementing new games into Archipelago. waiting-on: peer-review Issue/PR has not been reviewed by enough people yet.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants