Basic Questions and Answers Thread

Discussion in 'Discussion & Q&A' started by Malevolence, Jul 7, 2009.

  1. MarkeyJester

    MarkeyJester ! % # @ Member

    Joined:
    Jun 27, 2009
    Messages:
    2,791
    It's not the water tunnel, it's Sonic himself.

    While Sonic is floating in the air, him (as the object) is calling the subroutine "CalcAngle" using his X and Y speeds in order to obtain the angle, this angle is used to determine which direction Sonic is moving towards so it can then decide if a floor/wall/ceiling collision should be tested. If the way you handle the angle adjustment on the end is not correct, and you pull the wrong angle, he may not collide with certain walls/floors/ceilings.

    Sonic's X speed is 0400, and his Y speed is 0000. His Y position is directly manipulated by the tunnel routine, so his Y speed is never changed. Both are positive, and the X is larger than the Y, so your d2 should be 0. Now according to the original code, the resulting change to the angle is.... nothing:

    See specifically:

    Code:
    		tst.w	d1					; check X distance
    		bpl.w	CA_NoMirrorX				; if distance were positive, branch to skip mirror
    		neg.w	d0					; mirror angle
    		addi.w	#$0080,d0				; ''
    
    CA_NoMirrorX:
    		tst.w	d2					; check Y distance
    		bpl.w	CA_NoFlipY				; if distance were positive, branch to skip flip
    		neg.w	d0					; flip angle
    		addi.w	#$0100,d0				; ''
    
    CA_NoFlipY:
    Sonic's X speed being 0400 and Y speed being 0000 should reference 00 from the angle table, and because they are both positive, it should yield no change, thus the angle is 00, on yours, because of the d2 referencing, it'll collect 3F from the table and EOR that against 00, which is 3F. This is basically pointing down, which is why Sonic collides with the floor, but ignores the ceiling. Basically, your CalcAngle subroutine is broken.

    Good effort though, I like that you're trying =3
     
    Trickster likes this.
  2. Nat The Porcupine

    Nat The Porcupine Active Member Member

    Joined:
    Jun 23, 2017
    Messages:
    30
    Location:
    Harrisburg, Pennsylvania (USA)
    Welp, so much for that then XD. Looking at the code of the original, there is one more approach I'm gonna give a go to try & nix a few cycles, so we'll see how that goes.

    It's actually kind of a miracle that this didn't break in more places than it did. Thank you for taking the time to provide an explanation for this. Seriously, it means a lot.

    EDIT: Well now this version just entirely breaks ceiling collision...
    Code:
    ; ---------------------------------------------------------------------------
    ; Subroutine calculate an angle
    ; Optimized by Nat The Porcupine (after multiple failed attempts)
    ; Fixed suboptimal branch sizes & reduced codition checks
    
    ; input:
    ;    d1 = x-axis distance
    ;    d2 = y-axis distance
    
    ; output:
    ;    d0 = angle
    ; ---------------------------------------------------------------------------
    
    ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    
    
    CalcAngle:
            movem.l    d3-d4,-(sp)
            moveq    #0,d3
            moveq    #0,d4
            move.w    d1,d3
            move.w    d2,d4
            or.w    d3,d4
            beq.s    CalcAngle_Zero
            move.w    d2,d4
            moveq    #0,d2
        
            tst.w    d3
            bpl.s    loc_2CC2
            neg.w    d3
            addq.b    #4,d2
    
    loc_2CC2:
            tst.w    d4
            bpl.s    loc_2CCA
            neg.w    d4
            addq.b    #8,d2
    
    loc_2CCA:
            cmp.w    d3,d4
            bcc.s    loc_2CDC
            lsl.l    #8,d4
            divu.w    d3,d4
            moveq    #0,d0
            move.b    Angle_Data(pc,d4.w),d0
            bra.s    loc_2CE6
    ; ===========================================================================
    
    loc_2CDC:
            lsl.l    #8,d3
            divu.w    d4,d3
            moveq    #$40,d0
            sub.b    Angle_Data(pc,d3.w),d0
    
    loc_2CE6:
            jmp        CalcAngle_QuadAdjust(pc,d2.w)
    ; ===========================================================================
    
    ; This address table tells the code where to go to perform the proper quadrant adjustment
    CalcAngle_QuadAdjust:
            dc.l    CalcAngle_XpYp
            dc.l    CalcAngle_XnYp
            dc.l    CalcAngle_XpYn
            dc.l    CalcAngle_XnYn
        
    ; ===========================================================================
    
    CalcAngle_Zero:
            moveq    #$40,d0
        
    CalcAngle_XpYp:
            movem.l    (sp)+,d3-d4
            rts
    ; ===========================================================================
    
    CalcAngle_XnYp:
            neg.w    d0
            addi.w    #$80,d0
            movem.l    (sp)+,d3-d4
            rts
    ; ===========================================================================
    
    CalcAngle_XpYn:
            neg.w    d0
            addi.w    #$100,d0
            movem.l    (sp)+,d3-d4
            rts
    ; ===========================================================================
    
    CalcAngle_XnYn:
            subi.w    #$80,d0
        ;    neg.w    d0
        ;    addi.w    #$80,d0
        ;    neg.w    d0
        ;    addi.w    #$100,d0
            movem.l    (sp)+,d3-d4
            rts
    ; End of function CalcAngle
    
    ; ===========================================================================
    
    Angle_Data:    incbin    misc\angles.bin
            even
    ; ===========================================================================
    I just can't win with this, can I? :V

    EDIT 2: I'm a moron, this isn't how jump tables work. I'll go back & fix it.

    EDIT 3 (final one): I did it! I got it working like a charm! I have redeemed myself for the first ever blunder I posted on here.
     
    Last edited: Nov 9, 2019
  3. MarkeyJester

    MarkeyJester ! % # @ Member

    Joined:
    Jun 27, 2009
    Messages:
    2,791
    Well done~ You managed to save cycles on every instance.

    You save 10 cycles if both X and Y are positive.
    You save 8 cycles if one is positive and the other is negative (save only 4 cycles if the branches in the original code are .s short).
    You save 22 cycles if both X and Y are negative.

    The only negative aspect about your code, is that it does not preserve d2's value, so you better go and check all "CalcAngle" calls to make sure that none of them use the distance in d2 after the call.
     
    Nat The Porcupine likes this.
  4. Nat The Porcupine

    Nat The Porcupine Active Member Member

    Joined:
    Jun 23, 2017
    Messages:
    30
    Location:
    Harrisburg, Pennsylvania (USA)
    Actually, in reality the cycle savings aren't that great over the original in my final version, which I didn't post here yet. Originally, I thought the jump table would just pull the address value listed at the offset, but instead it jumped into the table, executed those addresses as logical OR operations, & fell through to the angle set that happens when both x & y axis distance are 0.

    To fix this, I simply changed the 'dc.l's to 'bra.s's & adjusted d2's offset amount to account for the smaller entry size. The issue with this is that, while the routine now runs flawlessly, it adds 10 cycles of overhead onto each instance & that pushes it into a range of being less efficient than the original. Ultimately, I settled on doing this hackiness (note that these cycle "savings" are by comparison to the original S1 routine not S2 or 3):
    Code:
    ; ---------------------------------------------------------------------------
    ; Subroutine calculate an angle
    ; Optimized by Nat The Porcupine (after multiple failed attempts)
    ; Fixed suboptimal branch sizes & reduced codition checks
    
    ; input:
    ;    d1 = x-axis distance
    ;    d2 = y-axis distance
    
    ; output:
    ;    d0 = angle
    ;    d2 = trashed (safe to do in Sonic 1 by default, but be mindful of it)
    ; --------------------------------------------------------------------------
    
    ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    
    
    CalcAngle:
            movem.l    d3-d4,-(sp)
            moveq    #0,d3
            moveq    #0,d4
            move.w    d1,d3
            move.w    d2,d4
            or.w    d3,d4
            beq.s    CalcAngle_Zero
            move.w    d2,d4
            moveq    #0,d2
         
            tst.w    d3
            bpl.s    loc_2CC2
            neg.w    d3
            addq.b    #4,d2
    
    loc_2CC2:
            tst.w    d4
            bpl.s    loc_2CCA
            neg.w    d4
            addq.b    #8,d2
    
    loc_2CCA:
            cmp.w    d3,d4
            bcc.s    loc_2CDC
            lsl.l    #8,d4
            divu.w    d3,d4
            moveq    #0,d0
            move.b    Angle_Data(pc,d4.w),d0
            bra.s    loc_2CE6
    ; ===========================================================================
    
    loc_2CDC:
            lsl.l    #8,d3
            divu.w    d4,d3
            moveq    #$40,d0
            sub.b    Angle_Data(pc,d3.w),d0
    
    loc_2CE6:
            movem.l    (sp)+,d3-d4
            add.w    d2,d2
            jmp        CalcAngle_XpYp(pc,d2.w)
    ; ===========================================================================
    
    CalcAngle_XpYp:
            rts                            ; ‭2‬ cycles lost
         
            nop                            ; these 'nop' instructions are just here to pad this, as each
            nop                            ; of these code blocks, except the last one, needs to
            nop                            ; assemble to exactly 8 bytes in size, so don't remove them
    ; ===========================================================================
    
    CalcAngle_XnYp:                        ; ‭break even
            neg.w    d0
            addi.w    #$80,d0
            rts
    ; ===========================================================================
    
    CalcAngle_XpYn:                        ; ‭‭break even
            neg.w    d0
            addi.w    #$100,d0
            rts
    ; ===========================================================================
    
    CalcAngle_XnYn:                        ; 18 cycles saved
            subi.w    #$80,d0
            rts
    ; ===========================================================================
    
    CalcAngle_Zero:
            moveq    #$40,d0
            movem.l    (sp)+,d3-d4
            rts
    ; End of function CalcAngle
    
    ; ===========================================================================
    
    Angle_Data:    incbin    misc\angles.bin
            even
    ; ===========================================================================
    As you can see, this only really saves cycles in one particular instance. There is another idea I'd like to give a go, just to see if it works, but I have my doubts.

    Oh also, I did double check & d2 is totally safe to trash after these calls in the stock game.

    EDIT: Last idea was a bust. Oh well, I tried & I got to somewhere I'm satisfied with. It was good experience & practice.
     
    Last edited: Nov 9, 2019
    ProjectFM, TheInvisibleSun and Aier like this.
  5. Bluestreak

    Bluestreak Lady in red, living in dread. Member

    Joined:
    Apr 1, 2016
    Messages:
    182
    Location:
    Eastwatch Island
    It seems I cannot correctly port the Sonic 2-style level Select into Sonic 1. I've done exactly what Sonic Retro's guide has told me, but no dice. I'm kinda wondering if the entirety of S2Menu.asm could need overhauled or something at this point. Any advice?
     
  6. Iso Kilo

    Iso Kilo Hater of all things Git Member

    Joined:
    Oct 9, 2017
    Messages:
    165
    Location:
    Small Town in BC, Canada
    I've used that guide before, and if I recall, the Retro version is broken. However, the SSRG one seems to work. Albeit, the file link is broken.
    So here's what to do; Download the provided file on Sonic Retro, then follow the SSRG tutorial. Hopefully that works out for you as well as it does for me.
     
    Bluestreak likes this.
  7. Bluestreak

    Bluestreak Lady in red, living in dread. Member

    Joined:
    Apr 1, 2016
    Messages:
    182
    Location:
    Eastwatch Island
    Thanks Iso Kilo! :D
     
    Iso Kilo likes this.
  8. AsuharaMoon

    AsuharaMoon kakyoin did you lay this egg Member

    Joined:
    Aug 15, 2013
    Messages:
    60
    Here's a quick one. How do I make a projectile object (that damages) interact with S1's Final Boss object? (ID 85)
     
    Bluestreak and Trickster like this.
  9. Giovanni

    Giovanni Well-Known Member Member

    Joined:
    Apr 16, 2015
    Messages:
    118
    Location:
    Vercelli, Italy
    Suppose that I want to decrease the score by 100. If I do that normally, it updates just fine.
    However, I can't seem to remove digits from the score in the HUD. So if my score is 1000, it will go to 900 and be displayed as 1900.

    I've attempted adding this at the bottom of the Hud_Score subroutine:
    Code:
    Hud_ClrScore:
            tst.w    d6
            beq.s    loc_1C8FE
            moveq    #$F,d5
    
    Hud_ClrScoreLoop:
            move.l    #0,(a6)
            dbf    d5,Hud_ClrScoreLoop
            bra.s    loc_1C92C
    Which are slightly modified copies of Hud_ClrLives and Hud_ClrLivesLoop. I've chosen those specifically because I thought that, since they're part of a variable that increases and decreases, they might've helped, however, they do nothing to fix the problem.

    How can I properly fix this issue?
     
    Last edited: Nov 11, 2019
  10. Ralakimia

    Ralakimia Pour your misery down on me Member

    Joined:
    Aug 26, 2013
    Messages:
    979
    Yeah, the HUD number drawing routines only really account for numbers going up, with the exception of the ring count, in which a special check is added to reset it back to "0".

    What I did when I encountered this issue what set up a flag in the number drawing routine to draw a blank space if the digit is 0 when it's set, and when it's clear, actually draw a "0". This flag is then cleared when it's the last digit or when it finds a nonzero digit, that way it'll always properly display the number, no matter if it went up or down. You can even remove the ring count reset flag hack with this!
     
  11. MarkeyJester

    MarkeyJester ! % # @ Member

    Joined:
    Jun 27, 2009
    Messages:
    2,791
    Normal bosses for Sonic 1 use the touch response mechanism, this means Sonic is technically not prevented from entering the collision area, and entering it will cause a mild refraction and harm infliction.

    Final Zone's boss is different in that Robotnik is in solid tubes, thus, Sonic should not be allowed to enter the area, only touch and harm. Thus, the solid object subroutine is used by the boss instead, if Sonic touches the object as if it were some sort of solid block, then it'll inflict harm and refract Sonic back, but Sonic cannot linger within the collision area, it is completely solid. Another notable difference, is that Sonic himself cannot be harmed from touching this boss unlike the other bosses. $35 is an invulnerable timer, and $21 is the hit counter. However, $20 is not the touch response value, he has none, so checking his width/height will have to be done manually. The object when hit will set $35 to a specific time amount, it'll add 7 to the random number generator value, and it'll play the damage SFX.

    I have written up a subroutine here which will search object RAM for all FZ boss objects in RAM, and it'll check the necessary variables to work out if the boss (Robotnik himself) has been touched, it performs the same random number change, and SFX play that the boss itself does when Sonic touches it via SolidObject:
    Code:
    ; ===========================================================================
    ; ---------------------------------------------------------------------------
    ; Subroutine to	find a boss object and hit it
    ; ---------------------------------------------------------------------------
    
    ProjWidth	=	$08
    ProjHeight	=	$08
    BossWidth	=	$20
    BossHeight	=	$14
    
    CheckHitBoss:
    		move.w	$08(a0),d2				; load X position
    		addi.w	#ProjWidth,d2				; minus width
    		move.w	#ProjWidth*2,a2				; prepare width x2
    		move.w	$0C(a0),d3				; load Y position
    		addi.w	#ProjHeight,d3				; minus height
    		move.w	#ProjHeight*2,a3			; prepare height x2
    		lea	($FFFFD800).w,a1			; load object RAM
    		moveq	#($1800/$40)-1,d4			; set size of RAM to check
    		moveq	#$FFFFFF85,d0				; prepare object ID to check
    
    CHB_CheckObject:
    		cmp.b	(a1),d0					; is this an FZ boss object?
    		bne.s	CHB_NextObject				; if not, branch
    		tst.b	$35(a1)					; is the boss already invulnerable?
    		bne.s	CHB_NextObject				; if so, branch
    		moveq	#BossWidth,d1				; prepare correct boss width
    		move.w	$08(a1),d0				; load X position
    		subi.w	#BossWidth,d0				; move to left side of object
    		sub.w	d2,d0					; subtract right side of projectile
    		bpl.s	CHB_NoTouch				; if not touching, branch (object is to the right)
    		addi.w	#BossWidth*2,d0				; get right side of object
    		add.w	a2,d0					; add projectile's width
    		bmi.s	CHB_NoTouch				; if the object is not touching projectile on it's right, branch (object is to the left)
    		move.w	$0C(a1),d0				; load Y position
    		subi.w	#BossHeight,d0				; move to top of object
    		sub.w	d3,d0					; subtract bottom of projectile
    		bpl.s	CHB_NoTouch				; if not touching, branch (object is too far below)
    		addi.w	#BossHeight*2,d0			; get bottom of object
    		add.w	a3,d0					; add projectile's height
    		bpl.s	CHB_YesTouch				; if the object is touching the top of projectile, branch
    						; ... nothing	; object is too far above projectile
    
    CHB_NoTouch:
    		moveq	#$FFFFFF85,d0				; prepare object ID to check
    
    CHB_NextObject:
    		lea	$40(a1),a1				; advance to next slot
    		dbf	d4,CHB_CheckObject			; repeat for all objects in RAM
    		rts						; return
    
    ; ---------------------------------------------------------------------------
    ; Boss in a1 has been touched by projectile in a0
    ; ---------------------------------------------------------------------------
    
    CHB_YesTouch:
    		addq.w	#$07,($FFFFF636).w			; add 7 to the random number storage
    		subq.b	#$01,$21(a1)				; decrease hit counter
    		move.b	#$64,$35(a1)				; set invulnerable timer
    		moveq	#$FFFFFFAC,d0				; play boss hit SFX
    		jsr	(PlaySound_Special).l			; ''
    
    	; ...do whatever you want to the projectile, reflect, etc...
    
    		rts						; return
    
    ; ===========================================================================
    Simply get your projectile object to call this subroutine, you can change the equates if you require different width/height for your projectile (or the boss), I've left the bottom part blank which is the area you would use to make the projectile do something different after hitting the boss.
     
    ProjectFM and AsuharaMoon like this.
  12. AsuharaMoon

    AsuharaMoon kakyoin did you lay this egg Member

    Joined:
    Aug 15, 2013
    Messages:
    60
    Markey, dude, you're too generous for this community. Thanks again!
     
  13. Natsumi

    Natsumi Phoenix egg Member

    Joined:
    Oct 7, 2011
    Messages:
    699
    Location:
    Long and dangerous river
    Additionally, to save on resources of the poor 68k, when the final boss loads, you can save its address (a0) into an unused RAM location. This way, instead of checking every object for the boss, you can load its address into a1 and directly check for a collision. This method is very often used in S3K bosses to simplify and optimize the code, and since S3K bosses are often more complex, this is really badly needed. It may not be necessary in this case, but perhaps if you run into lag issues its worth considering.
     
  14. Aier

    Aier "Aieru Dotsuto" Member

    Joined:
    Nov 10, 2018
    Messages:
    33
    Location:
    Gensokyo's Boundaries
    Soo, I'm here again, and I don't like being stuck with so basic stuff like this ugh.
    Anyways, this evening I have been trying to edit the Main Level load routine in order to modify when the Level Card appears, turn out there's an specific line that allows me to modify that:

    Code:
    Level_PlayBgm:
        move.b    (a1,d0.w),d0        ; load from music playlist
        move.w    d0,(Level_Music).w    ; store level music
        bsr.w    PlayMusic        ; play level music
            move.b    #ObjID_TitleCard,(TitleCard+id).w ; load Obj34 (level title card) at $FFFFB080    <<<<<<<<< this
    ; loc_40DA:
    Level_TtlCard:
    But everytime I move it from there, say, above Level_MainLoop does weird stuff to the chunk loading and to the object itself (obj34):
    upload_2019-11-15_23-8-10.png
    Well, the object (obj34) does not appear at all, I've also tried to add the fade from black line above Level_MainLoop and it doesn't work at all.
    What I'm trying to do is the following:
    * Fade to black (from title screen)
    * Music plays (still black)
    * Wait for the level to load (still black)
    * Fade from black
    * Load Level Card
    Just like Sonic CD, but I can't figure out what I'm doing wrong.
     

    Attached Files:

    Trickster likes this.