Исправить баги в коде спиндэша Sonic 2 и добавить уровни скорости
В Sonic the Hedgehog 2 и Sonic 3 & Knuckles счётчик спиндэша хранит значение как word, на которое таблица скоростей спиндэша ссылается как на один байт, а в отдельных случаях используются как значения word, так и byte. Также, каждый раз, когда нажата кнопка, значение счётчика увеличивается на 2, вместо 1, из-за чего некоторые уровни скорости не используются. Из-за путанья word и byte значение счётчика быстро опускается до 0, вследствии чего максимальную возможную скорость спиндэша развить невозможно. Во время игры это вызывает некоторые проблемы, особенно заметные в начале EHZ 2. Если разогнать спиндэш до максимальной скорости и затем отпустить кнопку, Соник столкнётся с бадником-пираньей на мосту и погибнет. Исправление кода увеличит скорость, которой Соник может достичь во время спиндэша. Также прекратится сброс скорости спиндэша при разгоне, позволяя не нажимать так бешенно кнопку, плюс никогда не придётся разгоняться заново, пока Соник находится в позиции спиндэша.
Чтобы исправить код спиндэша, найди:
<asm> Sonic_CheckSpindash: tst.b spindash_flag(a0) bne.s Sonic_UpdateSpindash cmpi.b #8,anim(a0) bne.s return_1AC8C move.b (Ctrl_1_Press_Logical).w,d0 andi.b #$70,d0 beq.w return_1AC8C move.b #9,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addq.l #4,sp move.b #1,spindash_flag(a0) move.w #0,spindash_counter(a0) cmpi.b #$C,air_left(a0) ; if he's drowning, branch to not make dust bcs.s + move.b #2,(Sonic_Dust+anim).w + bsr.w Sonic_LevelBound bsr.w AnglePos </asm>
и замени это на:
<asm> Sonic_CheckSpindash: tst.b spindash_flag(a0) bne.s Sonic_UpdateSpindash cmpi.b #8,anim(a0) bne.s return_1AC8C move.b (Ctrl_1_Press_Logical).w,d0 andi.b #$70,d0 beq.w return_1AC8C move.b #9,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addq.l #4,sp move.b #1,spindash_flag(a0) move.b #0,spindash_counter(a0) cmpi.b #$C,air_left(a0) ; if he's drowning, branch to not make dust bcs.s + move.b #2,(Sonic_Dust+anim).w + bsr.w Sonic_LevelBound bsr.w AnglePos </asm>
Теперь найди:
<asm> Sonic_UpdateSpindash: move.b (Ctrl_1_Held_Logical).w,d0 btst #1,d0 bne.w Sonic_ChargingSpindash
; unleash the charged spindash and start rolling quickly: move.b #$E,y_radius(a0) move.b #7,x_radius(a0) move.b #2,anim(a0) addq.w #5,y_pos(a0) ; add the difference between Sonic's rolling and standing heights move.b #0,spindash_flag(a0) moveq #0,d0 move.b spindash_counter(a0),d0 add.w d0,d0 </asm>
и замени этим:
<asm> Sonic_UpdateSpindash: move.b (Ctrl_1_Held_Logical).w,d0 btst #1,d0 bne.w Sonic_ChargingSpindash
; unleash the charged spindash and start rolling quickly: move.b #$E,y_radius(a0) move.b #7,x_radius(a0) move.b #2,anim(a0) addq.w #5,y_pos(a0) ; add the difference between Sonic's rolling and standing heights move.b #0,spindash_flag(a0) moveq #0,d0 move.b spindash_counter(a0),d0 add.b d0,d0 </asm>
Найди:
<asm> Sonic_ChargingSpindash: ; If still charging the dash... tst.w spindash_counter(a0) beq.s + move.w spindash_counter(a0),d0 lsr.w #5,d0 sub.w d0,spindash_counter(a0) bcc.s + move.w #0,spindash_counter(a0) + move.b (Ctrl_1_Press_Logical).w,d0 andi.b #$70,d0 beq.w Obj01_Spindash_ResetScr move.w #$900,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addi.w #$200,spindash_counter(a0) cmpi.w #$800,spindash_counter(a0) bcs.s Obj01_Spindash_ResetScr move.w #$800,spindash_counter(a0) </asm>
и замени на:
<asm> Sonic_ChargingSpindash: ; If still charging the dash... tst.b spindash_counter(a0) beq.s + move.b spindash_counter(a0),d0 lsr.b #5,d0 sub.b d0,spindash_counter(a0) bcc.s + move.b #0,spindash_counter(a0) + move.b (Ctrl_1_Press_Logical).w,d0 andi.b #$70,d0 beq.w Obj01_Spindash_ResetScr move.w #$900,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addi.b #$1,spindash_counter(a0) cmpi.b #$8,spindash_counter(a0) bcs.s Obj01_Spindash_ResetScr move.b #$8,spindash_counter(a0) bra.s Obj01_Spindash_ResetScr move.b #$8,spindash_counter(a0) </asm>
Найди:
<asm> CheckGameOver: move.b #1,($FFFFEEBE).w move.b #0,spindash_flag(a0) move.w (Camera_Max_Y_pos_now).w,d0 addi.w #$100,d0 cmp.w y_pos(a0),d0 bge.w return_1B31A move.b #8,routine(a0) ; => Obj01_Gone move.w #$3C,spindash_counter(a0) addq.b #1,(Update_HUD_lives).w ; update lives counter subq.b #1,(Life_count).w ; subtract 1 from number of lives bne.s Obj01_ResetLevel ; if it's not a game over, branch move.w #0,spindash_counter(a0) move.b #$39,(Object_RAM+$80).w ; load Obj39 (game over text) move.b #$39,(Object_RAM+$C0).w ; load Obj39 (game over text) move.b #1,(Object_RAM+$C0+mapping_frame).w move.w a0,(Object_RAM+$80+parent).w clr.b (Time_Over_flag).w </asm>
и замени на:
<asm> CheckGameOver: move.b #1,($FFFFEEBE).w move.b #0,spindash_flag(a0) move.w (Camera_Max_Y_pos_now).w,d0 addi.w #$100,d0 cmp.w y_pos(a0),d0 bge.w return_1B31A move.b #8,routine(a0) ; => Obj01_Gone move.b #$3C,spindash_counter(a0) addq.b #1,(Update_HUD_lives).w ; update lives counter subq.b #1,(Life_count).w ; subtract 1 from number of lives bne.s Obj01_ResetLevel ; if it's not a game over, branch move.b #0,spindash_counter(a0) move.b #$39,(Object_RAM+$80).w ; load Obj39 (game over text) move.b #$39,(Object_RAM+$C0).w ; load Obj39 (game over text) move.b #1,(Object_RAM+$C0+mapping_frame).w move.w a0,(Object_RAM+$80+parent).w clr.b (Time_Over_flag).w </asm>
Сейчас найди:
<asm>
Obj01_ResetLevel: tst.b (Time_Over_flag).w beq.s Obj01_ResetLevel_Part2 move.w #0,spindash_counter(a0) move.b #$39,(Object_RAM+$80).w ; load Obj39 move.b #$39,(Object_RAM+$C0).w ; load Obj39 move.b #2,(Object_RAM+$80+mapping_frame).w move.b #3,(Object_RAM+$C0+mapping_frame).w move.w a0,(Object_RAM+$80+parent).w bra.s Obj01_Finished </asm>
и замени на:
<asm> Obj01_ResetLevel: tst.b (Time_Over_flag).w beq.s Obj01_ResetLevel_Part2 move.b #0,spindash_counter(a0) move.b #$39,(Object_RAM+$80).w ; load Obj39 move.b #$39,(Object_RAM+$C0).w ; load Obj39 move.b #2,(Object_RAM+$80+mapping_frame).w move.b #3,(Object_RAM+$C0+mapping_frame).w move.w a0,(Object_RAM+$80+parent).w bra.s Obj01_Finished </asm>
Найди:
<asm> Obj01_ResetLevel_Part2: tst.w (Two_player_mode).w beq.s return_1B31A move.b #0,($FFFFEEBE).w move.b #$A,routine(a0) ; => Obj01_Respawning move.w (Saved_x_pos).w,x_pos(a0) move.w (Saved_y_pos).w,y_pos(a0) move.w (Saved_art_tile).w,art_tile(a0) move.w (Saved_layer).w,layer(a0) clr.w (Ring_count).w clr.b (Extra_life_flags).w move.b #0,obj_control(a0) move.b #5,anim(a0) move.w #0,x_vel(a0) move.w #0,y_vel(a0) move.w #0,inertia(a0) move.b #2,status(a0) move.w #0,move_lock(a0) move.w #0,spindash_counter(a0) </asm>
и замени на:
<asm> Obj01_ResetLevel_Part2: tst.w (Two_player_mode).w beq.s return_1B31A move.b #0,($FFFFEEBE).w move.b #$A,routine(a0) ; => Obj01_Respawning move.w (Saved_x_pos).w,x_pos(a0) move.w (Saved_y_pos).w,y_pos(a0) move.w (Saved_art_tile).w,art_tile(a0) move.w (Saved_layer).w,layer(a0) clr.w (Ring_count).w clr.b (Extra_life_flags).w move.b #0,obj_control(a0) move.b #5,anim(a0) move.w #0,x_vel(a0) move.w #0,y_vel(a0) move.w #0,inertia(a0) move.b #2,status(a0) move.b #0,move_lock(a0) move.b #0,spindash_counter(a0) </asm>
Найди:
<asm> Obj01_Gone: tst.w spindash_counter(a0) beq.s + subq.w #1,spindash_counter(a0) bne.s + move.w #1,(Level_Inactive_flag).w + rts </asm>
и замени на:
<asm> Obj01_Gone: tst.b spindash_counter(a0) beq.s + subq.b #1,spindash_counter(a0) bne.s + move.w #1,(Level_Inactive_flag).w + rts </asm>
Иди к:
<asm> TailsCPU_Respawn: move.w #4,(Tails_CPU_routine).w ; => TailsCPU_Flying move.w x_pos(a1),d0 move.w d0,x_pos(a0) move.w d0,(Tails_CPU_target_x).w move.w y_pos(a1),d0 move.w d0,(Tails_CPU_target_y).w subi.w #$C0,d0 move.w d0,y_pos(a0) ori.w #$8000,art_tile(a0) move.b #0,spindash_flag(a0) move.w #0,spindash_counter(a0) </asm>
и измени это на:
<asm> TailsCPU_Respawn: move.w #4,(Tails_CPU_routine).w ; => TailsCPU_Flying move.w x_pos(a1),d0 move.w d0,x_pos(a0) move.w d0,(Tails_CPU_target_x).w move.w y_pos(a1),d0 move.w d0,(Tails_CPU_target_y).w subi.w #$C0,d0 move.w d0,y_pos(a0) ori.w #$8000,art_tile(a0) move.b #0,spindash_flag(a0) move.b #0,spindash_counter(a0) </asm>
Найди:
<asm> TailsCPU_Normal: cmpi.b #6,(MainCharacter+routine).w ; is Sonic dead? bcs.s TailsCPU_Normal_SonicOK ; if not, branch ; Sonic's dead; fly down to his corpse move.w #4,(Tails_CPU_routine).w ; => TailsCPU_Flying move.b #0,spindash_flag(a0) move.w #0,spindash_counter(a0) move.b #$81,obj_control(a0) move.b #2,status(a0) move.b #$20,anim(a0) rts </asm>
и замени на:
<asm> TailsCPU_Normal: cmpi.b #6,(MainCharacter+routine).w ; is Sonic dead? bcs.s TailsCPU_Normal_SonicOK ; if not, branch ; Sonic's dead; fly down to his corpse move.w #4,(Tails_CPU_routine).w ; => TailsCPU_Flying move.b #0,spindash_flag(a0) move.b #0,spindash_counter(a0) move.b #$81,obj_control(a0) move.b #2,status(a0) move.b #$20,anim(a0) rts </asm>
Сейчас найди:
<asm> Tails_CheckSpindash: tst.b spindash_flag(a0) bne.s Tails_UpdateSpindash cmpi.b #8,anim(a0) bne.s return_1C75C move.b (Ctrl_2_Press_Logical).w,d0 andi.b #$70,d0 beq.w return_1C75C move.b #9,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addq.l #4,sp move.b #1,spindash_flag(a0) move.w #0,spindash_counter(a0) cmpi.b #$C,air_left(a0) ; if he's drowning, branch to not make dust bcs.s loc_1C754 move.b #2,(Tails_Dust+anim).w </asm>
и замени на:
<asm> Tails_CheckSpindash: tst.b spindash_flag(a0) bne.s Tails_UpdateSpindash cmpi.b #8,anim(a0) bne.s return_1C75C move.b (Ctrl_2_Press_Logical).w,d0 andi.b #$70,d0 beq.w return_1C75C move.b #9,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addq.l #4,sp move.b #1,spindash_flag(a0) move.b #0,spindash_counter(a0) cmpi.b #$C,air_left(a0) ; if he's drowning, branch to not make dust bcs.s loc_1C754 move.b #2,(Tails_Dust+anim).w </asm>
затем найди:
<asm> Tails_UpdateSpindash: move.b (Ctrl_2_Held_Logical).w,d0 btst #1,d0 bne.s Tails_ChargingSpindash
; unleash the charged spindash and start rolling quickly: move.b #$E,y_radius(a0) move.b #7,x_radius(a0) move.b #2,anim(a0) addq.w #1,y_pos(a0) ; add the difference between Tails' rolling and standing heights move.b #0,spindash_flag(a0) moveq #0,d0 move.b spindash_counter(a0),d0 add.w d0,d0 </asm>
и замени на:
<asm> Tails_UpdateSpindash: move.b (Ctrl_2_Held_Logical).w,d0 btst #1,d0 bne.s Tails_ChargingSpindash
; unleash the charged spindash and start rolling quickly: move.b #$E,y_radius(a0) move.b #7,x_radius(a0) move.b #2,anim(a0) addq.w #1,y_pos(a0) ; add the difference between Tails' rolling and standing heights move.b #0,spindash_flag(a0) moveq #0,d0 move.b spindash_counter(a0),d0 add.w d0,d0 </asm>
Теперь найди:
<asm> Tails_ChargingSpindash: ; If still charging the dash... tst.w spindash_counter(a0) beq.s loc_1C7F8 move.w spindash_counter(a0),d0 lsr.w #5,d0 sub.w d0,spindash_counter(a0) bcc.s loc_1C7F8 move.w #0,spindash_counter(a0) </asm>
и замени на:
<asm> Tails_ChargingSpindash: ; If still charging the dash... tst.b spindash_counter(a0) beq.s loc_1C7F8 move.b spindash_counter(a0),d0 lsr.b #5,d0 sub.b d0,spindash_counter(a0) bcc.s loc_1C7F8 move.b #0,spindash_counter(a0) </asm>
Найди:
<asm> loc_1C7F8: move.b (Ctrl_2_Press_Logical).w,d0 andi.b #$70,d0 beq.w loc_1C828 move.w #$900,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addi.w #$200,spindash_counter(a0) cmpi.w #$800,spindash_counter(a0) bcs.s loc_1C828 move.w #$800,spindash_counter(a0) </asm>
и замени на:
<asm> loc_1C7F8: move.b (Ctrl_2_Press_Logical).w,d0 andi.b #$70,d0 beq.w loc_1C828 move.w #$900,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addi.b #$1,spindash_counter(a0) cmpi.b #$8,spindash_counter(a0) bcs.s loc_1C828 move.b #$8,spindash_counter(a0) bra.s loc_1C828 move.b #$8,spindash_counter(a0) </asm>
Найди:
<asm> Obj02_CheckGameOver_2Pmode: addq.b #1,(Update_HUD_lives_2P).w subq.b #1,(Life_count_2P).w bne.s Obj02_ResetLevel move.w #0,spindash_counter(a0) move.b #$39,(Object_RAM+$80).w ; load Obj39 move.b #$39,(Object_RAM+$C0).w ; load Obj39 move.b #1,(Object_RAM+$C0+mapping_frame).w move.w a0,(Object_RAM+$80+parent).w clr.b (Time_Over_flag_2P).w </asm>
и замени на:
<asm> Obj02_CheckGameOver_2Pmode: addq.b #1,(Update_HUD_lives_2P).w subq.b #1,(Life_count_2P).w bne.s Obj02_ResetLevel move.b #0,spindash_counter(a0) move.b #$39,(Object_RAM+$80).w ; load Obj39 move.b #$39,(Object_RAM+$C0).w ; load Obj39 move.b #1,(Object_RAM+$C0+mapping_frame).w move.w a0,(Object_RAM+$80+parent).w clr.b (Time_Over_flag_2P).w </asm>
Найди:
<asm> Obj02_ResetLevel: tst.b (Time_Over_flag).w beq.s Obj02_ResetLevel_Part2 tst.b (Time_Over_flag_2P).w beq.s Obj02_ResetLevel_Part3 move.w #0,spindash_counter(a0) clr.b (Update_HUD_timer).w clr.b (Update_HUD_timer_2P).w move.b #8,routine(a0) rts
- ---------------------------------------------------------------------------
Obj02_ResetLevel_Part2: tst.b (Time_Over_flag_2P).w beq.s Obj02_ResetLevel_Part3 move.w #0,spindash_counter(a0) move.b #$39,(Object_RAM+$80).w ; load Obj39 move.b #$39,(Object_RAM+$C0).w ; load Obj39 move.b #2,(Object_RAM+$80+mapping_frame).w move.b #3,(Object_RAM+$C0+mapping_frame).w move.w a0,(Object_RAM+$80+parent).w bra.s Obj02_Finished </asm>
и замени на:
<asm> Obj02_ResetLevel: tst.b (Time_Over_flag).w beq.s Obj02_ResetLevel_Part2 tst.b (Time_Over_flag_2P).w beq.s Obj02_ResetLevel_Part3 move.b #0,spindash_counter(a0) clr.b (Update_HUD_timer).w clr.b (Update_HUD_timer_2P).w move.b #8,routine(a0) rts
- ---------------------------------------------------------------------------
Obj02_ResetLevel_Part2: tst.b (Time_Over_flag_2P).w beq.s Obj02_ResetLevel_Part3 move.b #0,spindash_counter(a0) move.b #$39,(Object_RAM+$80).w ; load Obj39 move.b #$39,(Object_RAM+$C0).w ; load Obj39 move.b #2,(Object_RAM+$80+mapping_frame).w move.b #3,(Object_RAM+$C0+mapping_frame).w move.w a0,(Object_RAM+$80+parent).w bra.s Obj02_Finished </asm>
затем найди:
<asm> Obj02_Gone: tst.w spindash_counter(a0) beq.s + subq.w #1,spindash_counter(a0) bne.s + move.w #1,(Level_Inactive_flag).w + rts </asm>
и замени на:
<asm> Obj02_Gone: tst.b spindash_counter(a0) beq.s + subq.b #1,spindash_counter(a0) bne.s + move.w #1,(Level_Inactive_flag).w + rts </asm>
Это также помогает освободить байт $3B в структуре объекта.
Теперь, если нужно, ты можешь добавить больше значений спиндэша, следуя этим шагам.
Замени:
<asm> Sonic_ChargingSpindash: ; If still charging the dash... tst.b spindash_counter(a0) beq.s + move.b spindash_counter(a0),d0 lsr.b #5,d0 sub.b d0,spindash_counter(a0) bcc.s + move.b #0,spindash_counter(a0) + move.b (Ctrl_1_Press_Logical).w,d0 andi.b #$70,d0 beq.w Obj01_Spindash_ResetScr move.w #$900,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addi.b #$1,spindash_counter(a0) cmpi.b #$8,spindash_counter(a0) bcs.s Obj01_Spindash_ResetScr move.b #$8,spindash_counter(a0) bra.s Obj01_Spindash_ResetScr move.b #$8,spindash_counter(a0) </asm>
и:
<asm> loc_1C7F8: move.b (Ctrl_2_Press_Logical).w,d0 andi.b #$70,d0 beq.w loc_1C828 move.w #$900,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addi.b #$1,spindash_counter(a0) cmpi.b #$8,spindash_counter(a0) bcs.s loc_1C828 move.b #$8,spindash_counter(a0) bra.s loc_1C828 move.b #$8,spindash_counter(a0) </asm>
на:
<asm> Sonic_ChargingSpindash: ; If still charging the dash... tst.b spindash_counter(a0) beq.s + move.b spindash_counter(a0),d0 lsr.b #5,d0 sub.b d0,spindash_counter(a0) bcc.s + move.b #0,spindash_counter(a0) + move.b (Ctrl_1_Press_Logical).w,d0 andi.b #$70,d0 beq.w Obj01_Spindash_ResetScr move.w #$900,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addi.b #$1,spindash_counter(a0) cmpi.b #$X,spindash_counter(a0) bcs.s Obj01_Spindash_ResetScr move.b #$X,spindash_counter(a0) bra.s Obj01_Spindash_ResetScr move.b #$X,spindash_counter(a0) </asm>
и:
<asm> loc_1C7F8: move.b (Ctrl_2_Press_Logical).w,d0 andi.b #$70,d0 beq.w loc_1C828 move.w #$900,anim(a0) move.w #$E0,d0 jsr (PlaySound).l addi.b #$1,spindash_counter(a0) cmpi.b #$X,spindash_counter(a0) bcs.s loc_1C828 move.b #$X,spindash_counter(a0) bra.s loc_1C828 move.b #$X,spindash_counter(a0) </asm>
Теперь добавь требуемые скорости, чтобы уравнять $X, в Sonic_SpindashSpeeds, SpindashSpeedsSuper, и Tails_SpindashSpeeds.
Вот ROM файл, в котором сделаны все изменения, описанные в этом руководстве. (информация)