Hey there, it's THE SuperEgg here. Ok kiddos, let's talk a bit about Sonic 2 proto compressions. S2NA and S2B use the same layout compression and format, which is no compression, and a layout format the same as Sonic 1's. The background and foregrounds are separate files. As far as S2B is concerned, the level art files are compressed as such: Tiles - Nemesis Blocks - uncompressed Chunks - Kosinski I don't remember S2NA off the top of my head, as I haven't touched S2NA in about 2 years, but I assume it's the same. Collisions in S2B is uncompressed. SonLVL does support S2B by extension, but I never released a public LVl file, as I've abandoned the disasm to make a custom engine with it, so I haven't bothered to advance anywhere with it. I suppose I could release a disasm will the the LVL files for it later if there is a big enough call for it. Last note to make, the "improved" disasm doesn't change file type compression for the sake of "byte perfection." It's "improved" because I either reorganized the file names, locations, split more data from the disasm, ect.
How do I port Knuckles' top boundary block (while gliding and climbing) to Sonic 1 correctly? Just asking cuz' I give it several unsuccessful tries as far my knowledge can (being the main reason why I deactivated it in my Rouge in S1 hack). While its functionality works quite fine, it suddenly activates itself on random places: And it also rarely happened with Tails' S3 flight codes (needs beta-testing since I recently modified it), so I would be grateful if someone could check them. Here's the codes: Spoiler Knuckles' boundary block while... ...gliding: Spoiler [68k] kloc_315D68: ; blocks Knuckles at the upper part of the boundary while gliding cmpi.w #$102,($FFFFFE10).w ; is level LZ3 ? beq.w kreturn_315D9A ; if yes, return cmpi.w #$501,($FFFFFE10).w ; is level SBZ2 ? beq.w kreturn_315D9A ; if yes, return move.w (Camera_Min_Y_pos).w,d0 ; $FFFFF73C cmp.w #$FF00,d0 beq.w kloc_315D88 add.w #$10,d0 cmp.w y_pos(a0),d0 ble.w kloc_315D88 asr x_vel(a0) asr inertia(a0) kloc_315D88: ; Camera_Y_pos_bias = $FFFFF73E cmpi.w #$60,(Camera_Y_pos_bias).w ; is screen in its default position? beq.s kreturn_315D9A ; if yes, branch bcc.s kloc_315D96 addq.w #4,(Camera_Y_pos_bias).w ; move screen back to default kloc_315D96: subq.w #2,(Camera_Y_pos_bias).w ; move screen back to default kreturn_315D9A: ; ... rts [/68k] ...and climbing: Spoiler [68k] kloc_315A54: ; blocks Knuckles at the upper part of the boundary while climbing moveq #1,d1 cmpi.w #$102,($FFFFFE10).w ; is level LZ3 ? beq.w kloc_315A76 ; if yes, branch cmpi.w #$501,($FFFFFE10).w ; is level SBZ2 ? beq.w kloc_315A76 ; if yes, branch move.w (Camera_Min_Y_pos).w,d0 ; $FFFFF73C cmp.w #-$100,d0 beq.w kloc_315B04 add.w #$10,d0 cmp.w y_pos(a0),d0 ble.w kloc_315B04 move.w d0,y_pos(a0) bra.w kloc_315B04 [/68k] Tails' boundary block while flying: Spoiler [68k] loc_14892: ; blocks Tails at the upper part of the boundary while flying cmpi.w #$102,($FFFFFE10).w ; is level LZ3 ? beq.w Tails_Set_Flying_Animation ; if yes, branch cmpi.w #$501,($FFFFFE10).w ; is level SBZ2 ? beq.w Tails_Set_Flying_Animation ; if yes, branch move.w (Camera_Min_Y_pos).w,d0 addi.w #$10,d0 cmp.w y_pos(a0),d0 blt.s Tails_Set_Flying_Animation tst.w y_vel(a0) bpl.s Tails_Set_Flying_Animation move.w #0,y_vel(a0) [/68k]
AsuharaMoon, check S1 ram editing page and you'll notice your Camera_Min_Y_pos isn't correct: So it should be:
I'm watching a lets play of Sonic 2 8-bit and I also tried playing it myself, has anyone looked into why sometimes you can go over 10 lives and sometimes you can't? Someone commented saying it depended on whether you picked up your 100th ring through a 10 ring TV or from a normal ring, I'm curious about this behavior. I didn't see anything mentioned on the Wiki. I've encountered some strange bugs playing Sonic 2 8 Bit and this one is curious because in Sonic 1 8-bit your life counter would go over 9 no problem and reflected it on the results screen, but in levels it would just show 9.
I thought I'd have a look into this, so I disassembled the SEGA Master System version of Sonic 2. Now, for the record, I'm not 100% familiar with the Master System games, I've played them mind, but not enough to be familiar. But judging by the code I'm seeing, it looks to me like there is no cap for the lives counter at all, only for the display to remain at 9 if it's beyond 9. The lives counter itself may go beyond 9. Observe: Code (Text): ROM:25C3 IncreaseRings: ; CODE XREF: ROM:026Cj ROM:25C3 ; sub_7378+8Cj ROM:25C3 ld a, (RingsCounter) ; load rings counter ROM:25C6 add a, 1 ; increase by 1 ROM:25C8 daa ; adjust for decimal addition (this should wrap to 0 once it surpasses 99 decimal) ROM:25C9 ld (RingsCounter), a ; update rings counter ROM:25CC or a ; check the number of rings now ROM:25CD jr nz, DisplayRings ; if it has NOT wrapped back to 0, branch (no lives updating) ROM:25CF ld hl, 0D298h ; Load lives counter address ROM:25D2 inc (hl) ; Increase lives counter ROM:25D3 call DisplayLives ; Cap it for display only and update ROM:25D6 ld a, 0A6h ; 'ª' ROM:25D8 ld (byte_DD04), a ROM:25D8 ; END OF FUNCTION CHUNK FOR sub_7378 ROM:25DB ROM:25DB ; =============== S U B R O U T I N E ======================================= ROM:25DB ROM:25DB ROM:25DB DisplayRings: ; CODE XREF: j_DisplayRingsj ROM:25DB ; sub_30+96Ap ... ROM:25DB ld a, (DemoFlag) ; is the demo running? (I "think" this is demo, the hud doesn't display during demo) ROM:25DE or a ROM:25DF ret nz ; if demo is on, branch ROM:25E0 ld a, (byte_D295) ROM:25E3 cp 6 ROM:25E5 jr nz, loc_25ED ROM:25E7 ld a, (byte_D296) ROM:25EA cp 2 ROM:25EC ret z ROM:25ED ROM:25ED loc_25ED: ; CODE XREF: DisplayRings+Aj ROM:25ED ld a, (RingsCounter) ROM:25F0 rrca ; Get upper digit but multiplied by 2 ROM:25F1 rrca ROM:25F2 rrca ROM:25F3 and 1Eh ; get only upper digit ROM:25F5 add a, 10h ROM:25F7 ld (RingDigitUpper), a ; Upper digit of rings counter ROM:25FA ld a, (RingsCounter) ROM:25FD rlca ; get lower digit by multiplied by 2 ROM:25FE and 1Eh ; get only lower digit ROM:2600 add a, 10h ROM:2602 ld (RingDigitLower), a ; Lower digit of rings counter ROM:2605 ret And the lives counter updating: Code (Text): ROM:25AC DisplayLives: ; CODE XREF: j_DisplayLivesj ROM:25AC ; sub_30+901p ... ROM:25AC ld a, (DemoFlag) ROM:25AF or a ROM:25B0 ret nz ROM:25B1 ld a, (LivesCounter) ; load lives counter ROM:25B4 cp 9 ; has it reached 9 or above? ROM:25B6 jr c, loc_25BA ; if not, branch ROM:25B8 ld a, 9 ; keep it at 9 ROM:25BA ROM:25BA loc_25BA: ; CODE XREF: DisplayLives+Aj ROM:25BA rlca ; multiply by 2 ROM:25BB and 1Eh ; get only the digit ROM:25BD add a, 10h ROM:25BF ld (LivesDigitLower), a ; update lives display counter ROM:25C2 ret Note, this is just for ring collection it appears, but the claim is there is no cap for collecting a 10 ring monitor, but that would mean that a single ring collection in the routine above would have the cap, which it does not. I'll keep looking though, but either I've misunderstood your post, or I've yet to discover the capping within the routines of the game. --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- EDIT: OK, I think I understand what you're talking about now. I've found the routine for collecting 10 rings from a monitor: Code (Text): ROM:4817 Monitor_Rings: ; CODE XREF: sub_2FA8+1854j ROM:4817 res 0, (hl) ROM:4819 ld a, (RingsCounter) ROM:481C add a, 10h ; Add 10 to rings ROM:481E daa ; Adjust for decimal ROM:481F ld (RingsCounter), a ROM:4822 ld a, 0B3h ; '¦' ROM:4824 ld (byte_DD04), a ROM:4827 jp DisplayRings This increases the ring counter by 10, and that's it. If the result ends up surpassing 100 rings or not doesn't matter, there is no change to the lives counter. It could quite easily be added in though.
I've ported over the code from Sonic Chaos that removes the lives counter cap. I think I wrote a guide and put it on the wiki too. I'm not really sure why the programmed the lives counter to be capped.
Thanks for the reply MarkeyJester! I wrote the post when it was pretty late, so that may have lead to some clarity issues.
Not really a coding question, but since I'm making a whole new set of sprites for Sonic 2 I've been wondering if this frame is used anywhere: I thought it was unused but it isn't listed as such on Sonic Retro or TCRF, but this may just be an oversight. Is there some specific situation it shows up in I don't know about or have forgotten?
That is a tweening frame for the "almost falling off" teetering animation. To expand on this: in Sonic 2, the teetering animation is split into two variants, "far" and "near", depending on how close you are to the ledge. Furthermore, the "far" animation is further split into two directions, depending on whether you're facing towards or away from the ledge. In order for the single "near" animation to look right, however, Sonic needs to be facing towards the ledge. If you happen to already be facing the ledge, then the animation simply plays normally, but if you happen to be facing away from the ledge, then the game displays that frame for a couple of moments as it flips the Sonic object towards the ledge.
Hey, does anybody know how to fix the problem with the invincibility stars remaining intact after hitting an invincibility monitor and turning super in sonic 2? If anybody knows it would be much appreciated!
C++ questions are allowed right? typedef struct { void (*logic)(void); void (*draw)(void); } Delegate; What kind of declaration is this? I'm confused about the contents of the struct specifically.
Basically it's declaring a routine/function named "logic", with void return and with (void) parameters (and the same for "draw"). So imagine you make a structure of the above, and we'll name it Example: Code (Text): Delegate Example; Now example will have an entry named "logic", this will be a pointer to a routine to run, so we'll pretend we have a routine/function which has the same return and parameters: Code (Text): void Routine (void) { printf ("Do something sexy\n"); } We can then pass the address of this routine to the structure's "logic" like so: Code (Text): Example.logic = Routine; Then we can run that routine from the structure, basically calling it as if it were a function/routine to be called: Code (Text): Example.logic ( ); You can get this pointer to point to any other function/routine at any time so long as the return and the parameters are the same. Here is an example with a return and a few parameters: Code (Text): typedef struct { bool (*Routine)(const char *String); } Delegate; Code (Text): bool Name (const char *String) { printf ("String is %s\n", String); return (TRUE); } Then finally: Code (Text): Delegate Example; Example.Routine = Name; bool Return = Example.Routine ("La la la..."); if (Return == TRUE) { // I donno... just an example... }
I see. It'll take some practice and reading to really grasp the benefits of that, but I at least understand the mechanics. Thanks a lot!
Well... the benefits only come into play when you're doing something extremely complicated which involves calling different functions but within a main loop of some sort. I suspect this structure will have different entries, each one may require their own unique routine for "logic" and "drawing", but rather than doing conditional checks to determine which one needs to run which routine, you simply have the pointer point directly to the routine it needs, and just call the pointer and it'll do the rest without any checking needed. It'll save on CPU time and instructions. Unfortunately, the above is circumstantial and it's difficult to give you a really good example. I used a similar method above when I was writing an assembler, I had a structural system and create an "item" for every instruction/directive possible for the CPU, then pointing each one to a specific routine responsible for assembling the binary for the instruction. Thus, I don't need several comparisons or switch tables, I just call the structure's routine which is pointing to the correct routine before hand, and it does all the rest, including reporting back syntax errors and such. I could then allow macros to add themselves to this list, or switch this structural list out for a different CPU architecture without having to have several main assembly loops for every single CPU/macro known to man... Like I said though, this is very circumstantial and it's usefulness is highly dependent on what you need it for. Do have a play around with it though, you might find a use for it yourself at some point. A code example perhaps, with an array of structures together: Code (Text): int Size = 4; Delegate Example [Size]; Example [0].logic = RoutineA; Example [1].logic = RoutineB; Example [2].logic = RoutineA; Example [3].logic = RoutineC; for (Count = 0; Count < Size; Count++) { Example [Count].logic ( ); } Each one can have a different routine pointer inside "logic" and run different code in this crude loop example.
The Sonic Adventure games use structures with function pointers like that to handle all the objects in the game, and not just rings and springs, even background tasks and menus. If you're familiar with S3K, it's like the code pointer at the start of each object there. This kind of programming style I'd think is more common in plain C, since C++ has classes where you can use inheritance and overrides to provide a similar functionality.
Yea these examples make sense, because that code was part of an example game project. Thanks again fellas.