Hello, I've been meaning to make this guide for ages but because of life reasons, I just didn't have the time. But recently, I have found more time and decided to get on with this. In Sonic 2, there are multiple speed issue bugs with Sonic and Tails, but for now, let's look at it with Sonic's point of view. It's uncommon, but not exactly rare, and when Super Sonic is enabled, it becomes more apparent. But sometimes, Sonic's top speed, acceleration and deceleration can be set wrong. The most common place for the bugs to occur is around this area of ARZ1 due to where the speed shoes are placed. Like said, it can be uncommon, but with people's hacks putting speedshoes underwater and other places, the bugs can happen more often. There are several speed issues: If Super Sonic and you hit speedshoes, his top speed goes a little bit higher which is fine, but his acceleration and deceleration decreases. Technically, it's applying normal Sonic's speedshoes speed. If Normal Sonic and you get speedshoes, if you go underwater, speedshoes's speed are lost. Even if you get out of the water, Sonic's speed is normal. If Super Sonic and you get speedshoes, if you go underwater, speedshoes's speed are lost. Even if you get out of the water, Super Sonic's speed is normal. If underwater and you get speedshoes, Sonic's "out-of-water" speedshoes speed apply, making you go way too fast underwater. Getting out of water or even back in and the speedshoes speeds are lost, even with speedshoes applied. If Super and underwater and you get speedshoes, Normal Sonic's "out-of-water" speedshoes speed apply, making you go way too fast underwater. Getting out of water or even back in and the speedshoes speeds are lost, even with speedshoes applied, but applies Super Sonic's speed settings. If underwater and you get speedshoes, bug above applies but if you never leave the water, once speedshoes wear off, Sonic's normal speed applies, even though you're still underwater, making you still too fast. If Super and underwater and you get speedshoes, bug above the above applies but if you never leave the water, once speedshoes wear off, Super Sonic's speed applies, even though you're still underwater, making you still too fast. If underwater and then you transform into Super Sonic, Super Sonic's "out-of-water" speed applies, making you way too fast. If you have speedshoes and then you transform into Super Sonic, Super Sonic's speed applies, making you lose speedshoes speed, making you that bit slower than you should be. If underwater when you have speedshoes and then you transform into Super Sonic, Super Sonic's "out-of-water" speed applies, and you lose your speedshoes speed. If underwater when you have speedshoes and you're Super Sonic and you transform back to normal Sonic, normal Sonic's "underwater" speed applies, making you lose your speedshoes. A lot, eh? The main issue is that the game never checks to see if you have the speedshoes applied or not. Or when becoming Super and etc, it's not checking if underwater, etc, etc. It would have to do a few checks itself to get it right and I suspect Sonic Team didn't have time to do it as it wasn't particularly important. But we're going to fix these ourselves, and it's easier than you think. Please note: I am using Xenowhirls disassembly, although this shouldn't be hard to follow if you're using the HG disassembly. Step 1 - Back-up your disassembly Like all guides you're about to follow, make a back-up before making any changes! I am also not held responsible for any troubles this may bring to your hack. Step 2 - Creating new speeds First of all, we need to make some new speed settings. Here are some of Sonic's speeds settings listed in Sonic 2 currently: Code (ASM): ; NORMAL move.w #$600,(Sonic_top_speed).w move.w #$C,(Sonic_acceleration).w move.w #$80,(Sonic_deceleration).w ; NORMAL SPEED SHOES move.w #$C00,(Sonic_top_speed).w move.w #$18,(Sonic_acceleration).w move.w #$80,(Sonic_deceleration).w ; SUPER SONIC move.w #$A00,(Sonic_top_speed).w move.w #$30,(Sonic_acceleration).w move.w #$100,(Sonic_deceleration).w ; NORMAL UNDERWATER move.w #$300,(Sonic_top_speed).w move.w #6,(Sonic_acceleration).w move.w #$40,(Sonic_deceleration).w ; SUPER SONIC UNDERWATER move.w #$500,(Sonic_top_speed).w move.w #$18,(Sonic_acceleration).w move.w #$80,(Sonic_deceleration).w As you can see, the game doesn't have speed settings for "Normal underwater with speedshoes", "Super Sonic with speedshoes" or "Super Sonic underwater with speedshoes". No wonder why all these bugs exist. Now, we have to come up with these speeds, which I have already done with some maths involved. To get "Normal underwater with speedshoes", I thought, how does normal Sonic get speedshoes speed? Well, everything is doubled except his deceleration, which stays the same. So I did the same and got this new speed setting. Code (ASM): ; NORMAL UNDERWATER SPEED SHOES move.w #$600,(Sonic_top_speed).w move.w #$C,(Sonic_acceleration).w move.w #$40,(Sonic_deceleration).w It's almost identical to Sonic's normal speed, except his deceleration is slower. To get "Super Sonic with speedshoes", I left the top speed setting it already does alone, but made it not decrease Super Sonic's acceleration and deceleration, making him that tiny bit faster: Code (ASM): ; SUPER SONIC SPEED SHOES move.w #$C00,(Sonic_top_speed).w move.w #$30,(Sonic_acceleration).w move.w #$100,(Sonic_deceleration).w To get "Super Sonic underwater with speedshoes", I followed the same method on how "Normal" does it, and got this: Code (ASM): ; SUPER SONIC UNDERWATER SPEED SHOES move.w #$A00,(Sonic_top_speed).w move.w #$30,(Sonic_acceleration).w move.w #$80,(Sonic_deceleration).w You do not need to add any of the ASM above into your hack as yet. Step 3 - Creating a new table Now we've got all our speeds, we need to convert it into a table. Which I have already done on your behalf: Code (ASM): ; =========================================================================== ; ---------------------------------------------------------------------------- ; Speed Settings Array ; This array defines what speeds the character should be set to ; ---------------------------------------------------------------------------- ; blank top_speed acceleration deceleration ; # ; Comment Speedsettings: dc.w $0, $600, $C, $80 ; $00 ; Normal dc.w $0, $C00, $18, $80 ; $08 ; Normal Speedshoes dc.w $0, $300, $6, $40 ; $16 ; Normal Underwater dc.w $0, $600, $C, $40 ; $24 ; Normal Underwater Speedshoes dc.w $0, $A00, $30, $100 ; $32 ; Super dc.w $0, $C00, $30, $100 ; $40 ; Super Speedshoes dc.w $0, $500, $18, $80 ; $48 ; Super Underwater dc.w $0, $A00, $30, $80 ; $56 ; Super Underwater Speedshoes ; =========================================================================== This will be our new table. We'll insert this into the ASM file shortly. First, let's go onto the next step. Step 4 - Creating a new subroutine to apply speeds So, we need to create a new subroutine to grab the right speed under Sonic's conditions. Here is what I come up with: Code (ASM): ; =========================================================================== ; --------------------------------------------------------------------------- ; Subroutine to collect the right speed setting for a character ; a0 must be character ; a1 will be the result and have the correct speed settings ; a2 is characters' speed ; --------------------------------------------------------------------------- ; ||||||||||||||| S U B R O U T I N E ||||||||||||||||||||||||||||||||||||||| ApplySpeedSettings: moveq #0,d0 ; Quickly clear d0 tst.w speedshoes_time(a0) ; Does character have speedshoes? beq.s + ; If not, branch addq.b #1,d0 ; Quickly add 1 to d0 + btst #6,status(a0) ; Is the character underwater? beq.s + ; If not, branch addq.b #2,d0 ; Quickly add 2 to d0 + cmpa.w #MainCharacter,a0 ; Is it Tails currently following this code? bne.s + ; If so, branch and ignore next question tst.b (Super_Sonic_flag).w ; Is the character Super? beq.s + ; If not, branch addq.b #4,d0 ; Quickly add 4 to d0 + add.b d0,d0 ; Multiply itself add.b d0,d0 ; And again add.b d0,d0 ; And again lea Speedsettings(pc,d0.w),a1 ; Load correct speed settings into a1 addq.l #2,a1 ; Increment a1 by 2 quickly move.l (a1)+,(a2)+ ; Set character's new top speed and acceleration move.w (a1),(a2) ; Set character's deceleration rts ; Finish subroutine ; =========================================================================== What this will do is ask questions like if you're underwater, have speedshoes or are Super and depending on the answers, it will add bytes to d0. Once finished with the questions, whatever d0 is, it will multiply itself 3 times. With the new result in d0 after that multiplication, it will then grab data from the table with its starting position being whatever d0 is. For example, say you're normal Sonic and underwater. After the questions, d0 should be 2. Multiply itself 3 times, d0 becomes 16. If you then look for 16 on the table, you'll see that it does indeed equal "Normal Underwater". So it got the right speed setting. So, here is our new code and table so far: Code (ASM): ; =========================================================================== ; --------------------------------------------------------------------------- ; Subroutine to collect the right speed setting for a character ; a0 must be character ; a1 will be the result and have the correct speed settings ; a2 is characters' speed ; --------------------------------------------------------------------------- ; ||||||||||||||| S U B R O U T I N E ||||||||||||||||||||||||||||||||||||||| ApplySpeedSettings: moveq #0,d0 ; Quickly clear d0 tst.w speedshoes_time(a0) ; Does character have speedshoes? beq.s + ; If not, branch addq.b #1,d0 ; Quickly add 1 to d0 + btst #6,status(a0) ; Is the character underwater? beq.s + ; If not, branch addq.b #2,d0 ; Quickly add 2 to d0 + cmpa.w #MainCharacter,a0 ; Is it Tails currently following this code? bne.s + ; If so, branch and ignore next question tst.b (Super_Sonic_flag).w ; Is the character Super? beq.s + ; If not, branch addq.b #4,d0 ; Quickly add 4 to d0 + add.b d0,d0 ; Multiply itself add.b d0,d0 ; And again add.b d0,d0 ; And again lea Speedsettings(pc,d0.w),a1 ; Load correct speed settings into a1 addq.l #2,a1 ; Increment a1 by 2 quickly move.l (a1)+,(a2)+ ; Set character's new top speed and acceleration move.w (a1),(a2) ; Set character's deceleration rts ; Finish subroutine ; =========================================================================== ; ---------------------------------------------------------------------------- ; Speed Settings Array ; This array defines what speeds the character should be set to ; ---------------------------------------------------------------------------- ; blank top_speed acceleration deceleration ; # ; Comment Speedsettings: dc.w $0, $600, $C, $80 ; $00 ; Normal dc.w $0, $C00, $18, $80 ; $08 ; Normal Speedshoes dc.w $0, $300, $6, $40 ; $16 ; Normal Underwater dc.w $0, $600, $C, $40 ; $24 ; Normal Underwater Speedshoes dc.w $0, $A00, $30, $100 ; $32 ; Super dc.w $0, $C00, $30, $100 ; $40 ; Super Speedshoes dc.w $0, $500, $18, $80 ; $48 ; Super Underwater dc.w $0, $A00, $30, $80 ; $56 ; Super Underwater Speedshoes ; =========================================================================== Step 5 - Placing your new code in your ASM file Thanks to step 4, we've got the majority of the code sorted. You now want to place that new code in your ASM file. My suggestion is to put it between Sonic and Tails object, seeming as they're both the ones that are going to use it more often. So, search for "obj02:" and place it just above. Now, if you've put the code in between Sonic and Tails like I said above, the rest of my guide should be fine, although if you do decide to insert the code elsewhere, you may get some “branch out of range” errors, which you must fix yourself. Depending on how heavy you've edited Sonic and/or Tails code, you might get these errors anyway, but they're so simple to fix. Step 6 - Replacing some old code in certain places Now we have our new code, we need to make some jumps to it. So, let's go. First, go to "Obj01_Init:". You only need to do this bit if you have checkpoints starting underwater, otherwise, you can skip this bit of the step. You MUST follow the rest of this step from when you see "************" onwards. Anyway, under the label "Obj01_Init:", find and delete these lines: Code (ASM): move.w #$600,(Sonic_top_speed).w ; set Sonic's top speed move.w #$C,(Sonic_acceleration).w ; set Sonic's acceleration move.w #$80,(Sonic_deceleration).w ; set Sonic's deceleration and replace with this Code (ASM): lea (Sonic_top_speed).w,a2 ; Load Sonic_top_speed into a2 bsr.w ApplySpeedSettings ; Fetch Speed settings "************" Under "Obj01_ChkShoes:" find and delete these lines: Code (ASM): move.w #$600,(Sonic_top_speed).w move.w #$C,(Sonic_acceleration).w move.w #$80,(Sonic_deceleration).w tst.b (Super_Sonic_flag).w beq.s Obj01_RmvSpeed move.w #$A00,(Sonic_top_speed).w move.w #$30,(Sonic_acceleration).w move.w #$100,(Sonic_deceleration).w and replace with this Code (ASM): lea (Sonic_top_speed).w,a2 ; Load Sonic_top_speed into a2 bsr.w ApplySpeedSettings ; Fetch Speed settings Next, under "Obj01_InWater:" find these lines and delete: Code (ASM): move.w #$300,(Sonic_top_speed).w move.w #6,(Sonic_acceleration).w move.w #$40,(Sonic_deceleration).w tst.b (Super_Sonic_flag).w beq.s + move.w #$500,(Sonic_top_speed).w move.w #$18,(Sonic_acceleration).w move.w #$80,(Sonic_deceleration).w + and replace with: Code (ASM): lea (Sonic_top_speed).w,a2 ; Load Sonic_top_speed into a2 bsr.w ApplySpeedSettings ; Fetch Speed settings Next, under "Obj01_OutWater:" find these lines and delete: Code (ASM): move.w #$600,(Sonic_top_speed).w move.w #$C,(Sonic_acceleration).w move.w #$80,(Sonic_deceleration).w tst.b (Super_Sonic_flag).w beq.s + move.w #$A00,(Sonic_top_speed).w move.w #$30,(Sonic_acceleration).w move.w #$100,(Sonic_deceleration).w + and replace with: Code (ASM): lea (Sonic_top_speed).w,a2 ; Load Sonic_top_speed into a2 bsr.w ApplySpeedSettings ; Fetch Speed settings Next, go to "Sonic_CheckGoSuper:" and find and delete these lines: Code (ASM): move.w #$A00,(Sonic_top_speed).w move.w #$30,(Sonic_acceleration).w move.w #$100,(Sonic_deceleration).w and replace with: Code (ASM): lea (Sonic_top_speed).w,a2 ; Load Sonic_top_speed into a2 bsr.w ApplySpeedSettings ; Fetch Speed settings Next, go to "Sonic_RevertToNormal:" and find and delete these lines: Code (ASM): move.w #$600,(Sonic_top_speed).w move.w #$C,(Sonic_acceleration).w move.w #$80,(Sonic_deceleration).w btst #6,status(a0) ; Check if underwater, return if not beq.s return_1AC3C move.w #$300,(Sonic_top_speed).w move.w #6,(Sonic_acceleration).w move.w #$40,(Sonic_deceleration).w and replace with this slightly different code: Code (ASM): lea (Sonic_top_speed).w,a2 ; Load Sonic_top_speed into a2 bra.w ApplySpeedSettings ; Fetch Speed settings and return Now onto Tails. Seeming as Tails uses the exact same speeds, we can use the exact same table. So, go to "Obj02_Init:". You only need to do this bit if you have checkpoints starting underwater, otherwise, you can skip this bit of the step. You MUST follow the rest of this step from when you see "~~~~~~~~~~~~" onwards. Anyway, under the label "Obj02_Init:", find and delete these lines: Code (ASM): move.w #$600,(Tails_top_speed).w ; set Tails' top speed move.w #$C,(Tails_acceleration).w ; set Tails' acceleration move.w #$80,(Tails_deceleration).w ; set Tails' deceleration and replace with this slightly different code: Code (ASM): lea (Tails_top_speed).w,a2 ; Load Tails_top_speed into a2 bsr.w ApplySpeedSettings ; Fetch Speed settings "~~~~~~~~~~~~" Next, go to "Obj02_ChkShoes:" and find and delete these lines: Code (ASM): move.w #$600,(Tails_top_speed).w move.w #$C,(Tails_acceleration).w move.w #$80,(Tails_deceleration).w and replace with: Code (ASM): lea (Tails_top_speed).w,a2 ; Load Tails_top_speed into a2 bsr.w ApplySpeedSettings ; Fetch Speed settings Next, find "Obj02_InWater:" and find and delete these lines: Code (ASM): move.w #$300,(Tails_top_speed).w move.w #6,(Tails_acceleration).w move.w #$40,(Tails_deceleration).w and replace with: Code (ASM): lea (Tails_top_speed).w,a2 ; Load Tails_top_speed into a2 bsr.w ApplySpeedSettings ; Fetch Speed settings Next, under "Obj02_OutWater:", find and delete: Code (ASM): move.w #$600,(Tails_top_speed).w move.w #$C,(Tails_acceleration).w move.w #$80,(Tails_deceleration).w and replace with: Code (ASM): lea (Tails_top_speed).w,a2 ; Load Tails_top_speed into a2 bsr.w ApplySpeedSettings ; Fetch Speed settings That's Tails. Let's edit the speed shoes object code. Locate label "super_shoes:" and find and delete these lines: Code (ASM): move.w #$C00,(Sonic_top_speed).w move.w #$18,(Sonic_acceleration).w move.w #$80,(Sonic_deceleration).w and replace with this new code: Code (ASM): movem.l a0-a2,-(sp) ; Move a0, a1 and a2 onto stack lea (MainCharacter).w,a0 ; Load Sonic to a0 lea (Sonic_top_speed).w,a2 ; Load Sonic_top_speed into a2 jsr ApplySpeedSettings ; Fetch Speed settings movem.l (sp)+,a0-a2 ; Move a0, a1 and a2 from stack Then finally, go to "loc_12A10" and delete these lines: Code (ASM): move.w #$C00,(Tails_top_speed).w move.w #$18,(Tails_acceleration).w move.w #$80,(Tails_deceleration).w and replace with this slightly different new code: Code (ASM): movem.l a0-a2,-(sp) ; Move a0, a1 and a2 onto stack lea (MainCharacter).w,a0 ; Load Tails to a0 lea (Tails_top_speed).w,a2 ; Load Tails_top_speed into a2 jsr ApplySpeedSettings ; Fetch Speed settings movem.l (sp)+,a0-a2 ; Move a0, a1 and a2 from stack All done. Please note, do not edit under the label "loc_3AA22:". This is the only speed settings that can be left alone. This is when Sonic jumps onto the Tornado at the end of WFZ. This code applies to Super Sonic too (so Super Sonic goes slower) but this is to stop Super Sonic over-shooting the Tornado and falling to his death. The speed only applies when Sonic is about to jump onto the plane. Be warned! If you have put in other places where you've made the characters change their speed, you'll want to edit them too! Step 7 - Fixing another bug With the above fixes applied, a new bug has been introduced. The bug? If you're Super Sonic and you die whilst Super, the "Super_Sonic_flag" will still remain set. This means, once the level restarts, the speed settings will fetch speeds with Super Sonic in mind, because of the flag. So if you go underwater or get sppedshoes, it will still think you're Super and take that into account. And if you followed the two steps for when Sonic and Tails get created, then the Super speeds will apply then too. Also whilst Super Sonic, if you pause the game and press A to quit, the flag will remain set. You could select Tails in options and Tails will have Super speeds. Anyway, let's fix this. Find "Level_ClrRam:" and directly underneath it before the ClearRAM macros, put this: Code (ASM): clr.b (Super_Sonic_flag).w There we go, all bugs should be fixed. Now, when you enter/exit water, enter/exit speedshoes and/or enter/exit Super, the speeds should all be set correctly! As per usual, here is a ROM with the bugs fixed. Sonic 1? Excluding Super Sonic speeds, these bugs technically exist in Sonic 1 too. Although because there isn't any speedshoes in LZ or SBZ3 (unless I'm mistaken but I don't think there is any), these bugs are never seen. But again, if you've made edits to these levels and added speedshoes about especially speedshoes in water, you'll notice these bugs. You can follow this guide as a guideline for a fix. Sonic 3 and Knuckles? To my knowledge, these bugs are fixed already. But I could be mistaken. If anyone wants to add this to the wiki, feel free. Cheers, redhotsonic EDIT: Fixed a bug that MoDule pointed out. ROM download also updated containing this new fix.
I've been sitting on something like this for a while now, too, but never got around to posting it. In fact, our approaches are very similar. A few things, though: First of all, thanks. Your version has an optimization that I didn't think of (updating two stats with a long operation). Next, you can get away with a smaller data table without the blank words. Just multiply d0 with 6 instead of 8. Adding to that, I think you can save a few cycles by multiplying the bit-values themselves with 6, instead of doing it at the end. You'll need to change the second and third addq.w to addi.w, but the cycles those two changes add (4 each) are mitigated by not having to do three add.w's at the end (12 cycles, saving 4 cycles total). Admittedly, not much, and this isn't exactly a routine where optimization is very important, but you'll at least save some ROM space while you're at it. Finally, I think you're going to have to make the routine character-specific, or else Tails is going to use the wrong values when Sonic turns super.
Thanks for the tip, but it's not much point changing it. My maths isn't too great and that's why I came up with the longword routine. But you can change it this way by all means in your hack ...shit. Didn't think of that. Never crossed my mind. Anyway, simple fix. Under the label "ApplySpeedSettings:", just before "tst.b (Super_Sonic_flag).w", add this: Code (ASM): cmpa.w #MainCharacter,a0 ; Is it Tails currently following this code? bne.s + ; If so, branch and ignore next question So you end up with this: Code (ASM): ApplySpeedSettings: moveq #0,d0 ; Quickly clear d0 tst.w speedshoes_time(a0) ; Does character have speedshoes? beq.s + ; If not, branch addq.b #1,d0 ; Quickly add 1 to d0 + btst #6,status(a0) ; Is the character underwater? beq.s + ; If not, branch addq.b #2,d0 ; Quickly add 2 to d0 + cmpa.w #MainCharacter,a0 ; Is it Tails currently following this code? bne.s + ; If so, branch and ignore next question tst.b (Super_Sonic_flag).w ; Is the character Super? beq.s + ; If not, branch addq.b #4,d0 ; Quickly add 4 to d0 + add.b d0,d0 ; Multiply itself add.b d0,d0 ; And again add.b d0,d0 ; And again lea Speedsettings(pc,d0.w),a1 ; Load correct speed settings into a1 addq.l #2,a1 ; Increment a1 by 2 quickly move.l (a1)+,(a2)+ ; Set character's new top speed and acceleration move.w (a1),(a2) ; Set character's deceleration rts ; Finish subroutine Fixed in first post. Thanks for pointing that bug out.
THIS must be in the SCHG. Won't have any time for the next couple of days, though I WILL get to it when I can. I'll also put in steps myself for Sonic's 1 and 3... on that note, I don't believe this bug was ever fixed for S3K either... I'll have to look.
Indeed, it is not fixed in S3K. Current, unreleased S3C fixes this using MoDule's earlier suggestion from here, which accounts for all the water stuff, but not the fact that speed shoes slow Super down; I fixed that much earlier by making speed shoes a no-op if you're transformed (fucker is glitchy enough, don't want him going any faster!) The main positive of RHS's approach here over that one, it seems to me, is efficiency; the way I'm doing it halves the speeds on the fly if underwater every time they're referenced, which isn't too expensive an operation, but has to happen a hell of a lot. On the other hand, it is more elegant code-wise and fixes a bunch of other stuff for free, such as Tails respawning at the wrong speed, ending up with the wrong speed through debugging out of water (mostly fixed in S3K anyway, but badly done, so you can end up with Super stuck at normal speeds), anything where the in/out of water functions get missed for some reason. On that basis I'll probably stick with that version, but if I get on an optimisation kick at some point I might think about using this method instead. I'm just lucky, Sonic 3 is already pretty fast...
There's a couple other approaches that might be worth looking into. I've been toying with these for a while, but never got around to implementing them: 1. The first one works a lot like RHS's solution above, except instead of calculating the bit values each time, we do it only once whenever something changes. Basically, we'd be saving the index into the speed stats table somewhere in RAM. In S2 we've still got a few free OST variables for Sonic and Tails, so that would work beautifully. Here's my idea in a little more detail: We'd add a word variable stat_flags to Sonic and Tails's OST definition. Then, let's say Sonic gets a speed shoes power up. What we do now is call a routine Sonic_GiveShoes: Code (ASM): Sonic_GiveShoes: move.b stat_flags(a0),d0 bset #1+3,d0 ; set speed shoes bit bne.s + ; branch, if Sonic already has speed shoes move.b d0,stat_flags(a0) ; update flags ext.w d0 lea Sonic_SpeedStats(pc,d0.w),a1 move.l (a1)+,(Sonic_top_speed).w move.w (a1),(Sonic_deceleration).w + rts ; =========================================================================== ; Sonic's speed stats data ; top_speed acceleration deceleration blank Sonic_SpeedStats: dc.w $600, $C, $80, 0 ; 000 ; normal dc.w $300, $6, $40, 0 ; 001 ; under water dc.w $C00, $18, $80, 0 ; 010 ; speed shoes dc.w $500, $8, $40, 0 ; 011 ; under water & speed shoes ; Super Sonic's speed stats data dc.w $A00, $30, $100, 0 ; 100 ; normal dc.w $500, $18, $80, 0 ; 101 ; under water dc.w $C00, $30, $100, 0 ; 110 ; speed shoes dc.w $600, $18, $80, 0 ; 111 ; under water & speed shoes ; =========================================================================== We're back to using blank table entries, but now we're only calculating what we have to (and you could use those blank table entries for something like jump height). To remove a stat, simply do a bclr. Which bits to set or clear is determined as follows: I've arranged the table so that bit 0 corresponds to being under water, bit 1 to having speed shoes, and bit 2 to being super. Since I know in advance that I always need to multiply the bit values with 8, I do so by adding 3 to the bit number (because 3 = log2(8)). You could take this one step further by using a single, non-powerup-specific routine that takes d1 as the bit to be set/cleared. 2. Building upon the previous approach, if we sacrifice a little more RAM we can save the table address itself. So we'd use stat_addr instead of stat_flags. To change status here, we'd add or subtract 8, 16, or 32 to the saved table address. Here's some usage examples: Code (ASM): ; changing stats addi.l #(1<<0)*8,stat_addr(a0) ; character is under water subi.l #(1<<1)*8,stat_addr(a0) ; remove speed shoes addi.l #(1<<2)*8,stat_addr(a0) ; make Sonic super ; reading stats movea.l stat_addr(a0),a1 ; load character's stats This method is a little more dangerous, since you could end up addressing data outside the table if you're not careful. 3. Lastly, I've got an idea about how you could optimize the approach (mine, weeee) Tiddles linked to: have two sets of stats in RAM, one normal, and one for under water. Then, when it's time to move your character around, instead of reading the values and then shifting them, we just read the under water set of values. This is even easier in S3K, since all we have to do there is add 6 to a4. Actually, now that I think about it, the optimal solution could be a combination between 2 and 3. Save the entire stat table in RAM and use stat_addr to load a character's movement stats when they are needed. With this, you're still reading your stats from RAM (faster than reading from ROM), and you only need to use a single word per character to keep track of your stats. But again, this isn't the best place to look for optimizations. A character's stats don't get changed very often per frame. It's better to optimize things that happen multiple times per frame.
Now why the hell did I not think of that? Would have saved the "addq.l #2,a1" but it's only 8 cycles saved, and like you mentioned, it's not like it has to do this every frame. Meh, can't be arsed to edit the guide; I'm sure people will spot this for themselves =P Also, that's a nice way of doing it, Module, but I wanted mine to be a bit more friendly for beginners (that's why I've gone overboard on explaining), and tried to fix these bugs without the use of creating more SST's and RAM addresses (not to mention I've practically used all my SST's in S2R and don't want to extend the tables). No rush. EDIT: I put that it saves 4 cycles accidently when it's 8. Whoops!
Okay, this is perfect, I've been bothered by the speed issues, and I've been meaning to do something. However, my something was make a bunch of constants for every speed-affecting situation, so this table is much more proper. Very nice work! Since Sonic is a bit faster than Tails in my hack, I had to make two separate tables, but I feel it's cleaner that way since each character is then a bit more self-contained. Not to mention that it's easier on the CPU to just grab data off of a table than redefine the same number multiple times. Thanks for the guide!
I copy/pasted the first post onto the wiki... I know, its about goddamned time. Sorry for the delay guys. I kinda rushed it too... some of the above mentioned optimizations could be added to this if deemed really necessary... Up to the rest of you guys.
Bump. Found a bug (sorta). So, what if you merge this guide with this guide? You overwrite a1 and cause Tails to do some random nonsense such as falling through the ground whenever he respawns. Such nonsense convinced me to rebase my hack for the umpteenth time, only to find the cause halfway through rebasing. Just my luck. For anyone reading, if you're applying this to loc_1BC68, backup a1 like so: Code (ASM): move.l a1,-(sp) ; Backup a1 lea (Tails_top_speed).w,a2 ; Load Tails_top_speed into a2 bsr.w ApplySpeedSettings ; Fetch Speed settings move.l (sp)+,a1 ; Restore a1
Is that so? Thanks for that little bit of information. Surprised I never spotted that myself. And only just spotted you posting in this topic =P
Hi. When I was working on a hack of mine and implementing this fix regarding speed, I tested it and noticed a bug in 2P mode. Perhaps not many people care about 2P mode, but I thought this might be worth mentioning. Look at this snippet of code where the speed table is used for Tails when getting the Speed Shoes: [68k] ;loc_12A10: super_shoes_Tails: movem.l a0-a2,-(sp) lea (MainCharacter).w,a0 lea (Tails_top_speed).w,a2 jsr ApplySpeedSettings movem.l (sp)+,a0-a2 [/68k] This only works when you are using Tails only, thus when he's the main character. When in 2P mode, Tails can break the monitor, but the speed values are not applied for him because he's not the main character, but the "sidekick" (using the disassembly terminology). What I did to fix it was this: [68k] ;loc_12A10: super_shoes_Tails: movem.l a0-a2,-(sp) lea (MainCharacter).w,a0 tst.w (Player_mode).w ; Test which characters we're using bne.s + ; If we're not using Sonic and Tails together, branch lea (Sidekick).w,a0 ; otherwise load sidekick in a0 (for Tails obviously) + lea (Tails_top_speed).w,a2 jsr ApplySpeedSettings movem.l (sp)+,a0-a2 [/68k] Simple enough. We use the MainCharacter RAM section when we're using Tails only, and we load the Sidekick RAM section in a0 when we're using both Sonic and Tails (for 2P not 1P mode). Since we added a + label, the bra.s instruction above the label won't branch correctly. So change [68k] movem.l a0-a2,-(sp) ; Move a0, a1 and a2 onto stack lea (MainCharacter).w,a0 ; Load Sonic to a0 lea (Sonic_top_speed).w,a2 ; Load Sonic_top_speed into a2 jsr ApplySpeedSettings ; Fetch Speed settings movem.l (sp)+,a0-a2 ; Move a0, a1 and a2 from stack bra.s + ; --------------------------------------------------------------------------- ;loc_12A10: super_shoes_Tails: movem.l a0-a2,-(sp) ; Move a0, a1 and a2 onto stack [/68k] to this: [68k] movem.l a0-a2,-(sp) ; Move a0, a1 and a2 onto stack lea (MainCharacter).w,a0 ; Load Sonic to a0 lea (Sonic_top_speed).w,a2 ; Load Sonic_top_speed into a2 jsr ApplySpeedSettings ; Fetch Speed settings movem.l (sp)+,a0-a2 ; Move a0, a1 and a2 from stack bra.s ++ ; <-- added a + to make it branch to the correct location ; --------------------------------------------------------------------------- ;loc_12A10: super_shoes_Tails: movem.l a0-a2,-(sp) ; Move a0, a1 and a2 onto stack [/68k] I tested it and everything should be OK. I hope it's clear enough. Since it's my first post here, I don't know how I should explain stuff, if I forgot to include something or if it's not detailed enough, for an asm edit that is.
Just goes to show how many people care for the 2 player mode if this has only been discovered a year and a half later. Anyway, I haven't had time to try out your fix, but looks simple enough that I don't see it not working. Thanks for the fix, I can't be assed to edit my first post but no doubt people will see your fix