News:

11 March 2016 - Forum Rules

Main Menu

Mega Man V gameplay hack, ASM advice needed

Started by Ness, March 22, 2017, 11:43:34 AM

Previous topic - Next topic

Ness

Hi,

I'm working on a gameplay hack for Mega Man V and I need advice from MM and ASM experts.

I'm trying to make a 'weapon' that can deflect some enemy bullets, and those bullets should be able to hit enemies after deflection. Think of Protoman's shield in MM9/10, same thing.

I've tried to turn enemy bullets into the object ID and attributes of buster shot upon deflection.

However that doesn't work because the EFF8 routine which determines contact between MM's shots and enemies, only checks indexes 1 to 3, and the deflected shots which I turn into buster shots remain in their original address indexes (8 to 17). For instance for object ID: ram addresses 301->303 is for MM's shots, 308->317 for enemies and their shots.

AFAIK there is no way to change the address indexes upon deflection (?).

So I tried to change the EFF8 (shots->enemies hit detection) routine so that it checks all object ID addresses rather than just the first 3, so I changed LDY 03 to LDY 17 (at ram address F010, rom address 3F020). That did work and the deflected shots would hurt enemies, however, on top of being a solution that should increases lag, I also got glitches like enemies disappearing on spawn.

I couldn't figure out the enemy disappearing thing. It looks like SOME enemy types (and some only...) would basically detect that they are in contact with themselves. What I don't understand is why it happens to some enemies only, and why it happens at all since onl MM's shots are programmed to dmg enemies (although it looks like it skips HP altogether and jumps directly to the 'delete enemy' part).

BTW - I also thought of a completly different solution: upon deflection the enemy shot would disappear and I'd make a buster shot appear instead (rather than giving the enemy shots buster shot attributes); however I'm afraid that this would have impact on other things such as the "3 shots per screen" limit, meaning that the player couldn't fire 3 times if there is a deflected shot on screen...

So, does anyone have an idea, any clue, on how to achieve this?

Thank you

RhysOwens101


Disch

QuoteAFAIK there is no way to change the address indexes upon deflection (?).

I don't see why there wouldn't be.

Assuming the projectile data is just in an array somewhere, you'd just have to move the projectile's information to a different "slot" in the array once you deflect it.

The easiest solution to this is probably to reserve slots 0-2 for player bullets (as they are now), reserve 3-5 for deflected bullets, and change the EFF8 routine to check indexes 0-5 instead of 0-2.

Ness

#3
Quote from: RhysOwens101 on March 22, 2017, 02:09:54 PM
What's the hack gonna be called?
Sorry it's a secret-ish project for now. I'd rather not talk too much about it until I know I can pull it off.

Quote from: Disch on March 22, 2017, 10:03:49 PM
I don't see why there wouldn't be.

Assuming the projectile data is just in an array somewhere, you'd just have to move the projectile's information to a different "slot" in the array once you deflect it.

The easiest solution to this is probably to reserve slots 0-2 for player bullets (as they are now), reserve 3-5 for deflected bullets, and change the EFF8 routine to check indexes 0-5 instead of 0-2.

Thanks for your answer. That's an interesting idea. I'm new at learning 6502 Assembly, and I learn in an empirical kind of way.
For reserving the slots, the game has routines to reserve slots 08 to 17, so I guess I can try to copy paste and alter that routine.
I tried to search for ways to change the addresses or indexes but all I found was doing it manually for the addresses, something like this:
LDA "object ID (automatically 08->17)"
JSR "Reserve slots 04->05 routine"
STA "object ID (now automatically 04-05 ?)"

I'll keep digging, but how would you do it?
I think changing object ID address should be enough for the hit detection routine to detect the deflected shots.
However, I am wondering if the shots would still be functionnal if its object ID is in a different index than the rest of its attributes. For instance by default if object id is 300 + 9, the X coordinate (low) will be 330 +9 ; and I'm wondering if it would work if only the object ID would be changed or if I should change to change all addresses indexes (coordinates, speed, hit judgment, enemy handler)

So is there any way to change the index itself for all in one go or would I have to manually change all addresses ?
I guess I'll try your solution sees what happens.

Disch

QuoteI'll keep digging, but how would you do it?

For space to be reserved, I'd assume you just have to prevent something from spawning there.  The game already doesn't spawn stuff at 00-02 because that's reserved for player bullets, right?  So when it spawns stuff that means it looks for open slots starting at index 03.  You just have to find/change that 03 to a 06.

This is all speculation as I have no idea how the game is coded... but this where I would start.

QuoteHowever, I am wondering if the shots would still be functionnal if its object ID is in a different index than the rest of its attributes.

There might be two different IDs here.  In my mind, the ID in question is defined by the index.  Player bullets have IDs of 0-2 because that's always where their data is.  The ID is the value used to index the table.

Maybe there's another ID in the table, but from your description I highly doubt that second ID has anything to do with how the game does collision detection, which is what you're interested in.

So everything will need to be moved.  Whatever secondary ID the projectile has, it's X/Y position, it's speed, any graphical data, etc.  All of it.  That has to all be moved to a new position in the object array in order to give it a new ID so it'll be treated as hostile to enemies.


QuoteSo is there any way to change the index itself for all in one go or would I have to manually change all addresses ?

This can be done in a short loop:


LDA #srcptr_lo
STA $10
LDA #srcptr_hi
STA $11       ; $10 points to where the object data is now
LDA #dstptr_lo
STA $12
LDA #dstptr_hi
STA $13       ; $12 points to where you want to move the object data to

LDY #$10-1     ; if you want to move $10 bytes of data, initialize to $10-1 = $F
loop:
  LDA ($10),Y
  STA ($12),Y  ; copy the bytes over
  DEY
  BPL loop


; at this point, you'll want to "erase" the projectile from it's original position
;  which can probably be done by zeroing it's secondary ID (but that's a guess)
LDY #whatever_index_is_the_secondary_ID
LDA #0
STA ($10),Y  ; erase original projectile

Ness

Thanks a lot for your hindsight.
I have no idea what you refer to with "srcptr_lo/hi" and "dstptr_lo/hi"

Here is the routine the game uses to reserve slots for enemies and their projectiles for the X register:



So it reserves slots 08 to 17.

Slot 01 is for Mega Man himself. Slots 02 to 07, according to documentation, are for MM's shots, however I've checked them all and all weapons use slots 1 to 3, except for Charge Kick (slot 5) and R coil and R Jet (slot 4).
As far as I can tell, slots 6 and 7 aren't used, so I'd like to use those for deflected bullets.

So what I did is copy/pasted that routine above but changed LDX06 to LDX 06 and CPX18 to CPX08.

Then, upon deflection here is what I did:



JSR F15F is the game's original routine to reserve slots 08->17(pic above)
JSR BB65 is my custom routine to reserve slots 05->06
So I did this for all the attributes of the object (object id, x/y coordinates low and high, speed, enemy handler, etc etc)

However this doesn't work at all. It looks like both routines work against each others? If we were to trust what the debugger shows, it doesn't load the original index.

Disch

QuoteI have no idea what you refer to with "srcptr_lo/hi" and "dstptr_lo/hi"

Sorry.  Pseudocode.  I'm speculating/guessing a lot here as to the internals of how the game is arranging this data.

Quote from: Ness on March 23, 2017, 01:29:34 PM
Here is the routine the game uses to reserve slots for enemies and their projectiles for the X register:

I don't know if I'd call that "reserving" a range.

It looks like it's checking whether any of those slots are currently available.  If any slot is open, C is cleared (and the available slot is in X) -- if all are occupied, C is set.


QuoteHowever this doesn't work at all. It looks like both routines work against each others? If we were to trust what the debugger shows, it doesn't load the original index.

Well... your code has some problems.

Let's look at the first few lines:


LDA $0300,X     ; What is 'X' at this point?  I assume it's the slot that the projectile
                ;   is currently in.  In which case, this code would be getting the object ID
                ;   of the projectile.  Is that correct?
               
JSR $BB65       ; This looks for an available slot to move the projectile to.  Which is fine...
                ;   but this routine trashes A.. so you've effectively lost your object ID.
                ; It also changes X, so you've lost the slot ID of the projectile you're interested in

STA $0300,X     ; Again, since A was trashed by the above JSR, you're writing garbage here.
                ;   also.. what if both slots 6 and 7 were occupied?  You have to check the C flag
                ;   to make sure there's space available.
               
JSR $F15F       ; ??? why are you looking for available space in slots 8+?



So yeah -- this code is not going to work.  You'll want something like this:


;;  I assume X is the slot index of the projectile you want move/deflect

TXA         ; move that slot index to Y
TAY         ; Y is now the "source" slot index

JSR $BB65   ; Look for a free slot to move this projectile to.
            ; X is now the "destination" slot index.
           
BCS no_slots_available  ; UNLESS C is set, in which case there are no free slots to move the
            ; projectile to.  So skip the copy
           
; Now that we have source index (Y), and destination index (X),
;   start copying the information over
LDA $0300,Y
STA $0300,X
LDA $0330,Y
STA $0330,X
    ; ... etc ...
    ; do this for all the data that needs to be copied
   
; Once you've moved the projectile, you need to erase it from its original slot
no_slots_available: ; <- also, have our above failure case here.  If there are no slots available
                    ;  we'll end up just destroying the projectile instead of deflecting it.
LDA #0
STA $0300,Y     ; zero the object ID (this seems to be all you need to free the slot)

Ness

#7
The reason I was switching between F15F and BB65 was because I didn't have a full understanding of what they were doing. I thought that I had to switch back & forth so that X corresponds to the right slots every time. My problem was that I was thinking solely in terms of X, while your solution uses X and Y.

Anyway - I gave your solution a try and it works. Thanks a lot! Now I have a much better understanding of what I was doing wrong and of how things work. Like I said, up until now I was mostly solely thinking in terms of "X". I'm not a programmer, I learn by basically looking at the code and trying to guess, with the help of documentation.

However for some reasons the game freezes if I try to deflect more than 1 bullet, even though there is still another free slot to use and even though I set the deletion on BCS. I'm trying to figure it out.

Edit: got it, fixed the freezing. Something was wrong in my BB65 routine. Thx again!

Disch

Quote from: Ness on March 24, 2017, 07:30:27 AM
Anyway - I gave your solution a try and it works. Thanks a lot!

Yay.  Glad to hear it.   :beer:

Good luck with the rest of your hack.  It sounds neat.

Ness

#9
Yeah it works nicely, there is just one thing I can't figure out.

Sometimes a deflected shot which hits an enemy will not be destroyed on impact, but it seems to happen only in some rare occasions (only on some screens and only when there are more than 2 enemies, but it doesn't happen every time there are more than 2 enemies...).

It doesn't seem to have to do with whatever index the bullets or enemies are in, nor with the fact that my deflected shot code isn't in the "main" bank.
I checked the hit detection routine, the deletion routines, and the code that call them and nothing caught my eye...

I think I'll leave it like this for now and come back to it later, and I know it's a longshot in the dark here, but just in case would you a clue or an idea about that?

Disch

Quote from: Ness on March 24, 2017, 12:25:58 PM
I think I'll leave it like this for now and come back to it later, and I know it's a longshot in the dark here, but just in case would you a clue or an idea about that?

Nah, I have no idea.  I'd have to see the code that's handling the collision.

Ness

#11
NVM, I fixed it. Thanks.

Edit: or not, it just seems less likely to happen, goddamn.

Edit2: Now I have  ;D

Ness

#12
Hey,

Now I'm looking for the "hit box", or "hit magnitude" of Mega Man.

When I look at documentation, or when I look through code of the game, enemies, their projectiles and MM's weapons do have a "hit box", but I can't find it for MM.

All I find regarding hit detection with MM are comparisons with MM's X/Y coordinates, however it does not make sense that this is all there is to it considering that during a slide MM's hit magnitude is shorter and I see no exception regarding sliding in that code comparing X/Y coordinates... So there should be a "hitbox" setting for MM but I see no sign of it.

Does anyone know about this? Any clue or idea? If you know the answer but regarding another NES MM game instead of 5, please shoot, chances are it'd be similar in 5.

Edit: nvm, the hit detection routine does have an exception if "move = slide" and in that case reduces the Y hit judgment of the object trying to hit MM by 7.

So it does look like this is all there is to it. Not "hitbox" for MM, only coordinate comparison with objects' hitboxes.

Ness

Hey,

Following up on what was said earlier, I've been making tons of progress, but here is a new issue I can't figure out.

So to make the shield that deflects bullets, what I do is take the player's X-Y coordinate (low), add/substract to it, and check if the projectile is in that zone. (A similar method is used in the original game for enemies which have certain parts of their body deflect player's shots)

The problem being that if the player's X coordinate is between F1 and FF (which can happen in the middle of a screen during scrolls), and I add say 0F, the result will go over and back to 00+, and the hit detection won't work.

I'd like to understand how the game goes around this originally, I believe it uses the "coordinate high" which represents room number, but I'm not sure how.
So here is the hit detection routine when being hit by an object in a similar case, when the X low coordinate of the player is FB in room 0 and the projectile is 04 in room 1.
I'm sure the answer is there somehow but I can't figure it out.



Some help to understand this:
0528=Display flag
390=Y coordinate (high), representing room number
408=Type bite (hit judgement, size, etc)
330= X coordinate (low)
348= X coordinate (high), representing room number
378= Y coordinate (low)
F0F1 onward as well and F0B1 onward are table for projectile size
For the part at the end, 558 is anim number and 0030 current move

I'm sure this is a common issue so please let me know if you know how to go around this...

pianohombre

I don't get why people go to all this hard work to add features that were default to later games. If they created MM6 just to show off that feature is it really worth it to add the feature to MM5?
"Programming in itself is beauty,
whether or not the operating system actually functions." - Steve Wozniak

Ness

Whatever.

Anyway - not to waste anyone's time: someone's explained to me what EOR, ORA and EOR#FF ADC#01 did, and I also found a solution to the issue I was having.

Turns out that, unlike for X/Y coordinates low, it's not possible to add/substract anything to "player room number", even for the shortest time before setting it back to normal, without glitching the player position. So I found a solution by setting room number to an unused address which I then use to make the operations needed.

pianohombre

Are you using FCEUX as a disassembler? That's pretty convenient it spits out the value of the registers. On Asmdev when working on snes games it doesn't show those values I have to set breakpoints using an emulator/debugger.
"Programming in itself is beauty,
whether or not the operating system actually functions." - Steve Wozniak