ReC98/th04/scoreupd.asm

307 lines
7.2 KiB
NASM

.386
locals
include libs/master.lib/master.inc
include th02/main/hud/hud.inc
include th04/main/hud/popup.inc
include th02/score.inc
include th02/gaiji/boldfont.inc
SCORE_DIGIT_HIGHEST = SCORE_DIGITS - 1
; Also ignoring the last digit. (= 61,110 points)
SCORE_DELTA_FRAME_LIMIT = 6111
if SCORE_DELTA_FRAME_LIMIT ge 100000
.err "SCORE_DELTA_FRAME_LIMIT can't have more than 5 decimal digits"
endif
; Declaring everything here rather than inside a segment avoids fixup
; overflows… yup.
extrn _score_lebcd:byte:SCORE_DIGITS
extrn _hiscore_lebcd:byte:SCORE_DIGITS
extrn _hiscore_popup_shown:byte
extrn _score_delta:dword
extrn _score_delta_frame:dword
extrn _FIVE_DIGIT_POWERS_OF_10:word
extrn _hud_gaiji_row:byte:SCORE_DIGITS
extrn _popup_id_new:byte
extrn _popup:word
POPUP_UPDATE_AND_RENDER procdesc near
if GAME eq 4
extrn _score_unused:byte
extrn _temp_lebcd:byte
MAIN_01 group MAIN_0_TEXT
MAIN_0_TEXT segment byte public 'CODE' use16
SCORE_EXTEND_UPDATE procdesc near
MAIN_0_TEXT ends
else
_temp_lebcd equ _hud_gaiji_row
endif
MAIN_01 group MAIN_01_TEXT
; ----------------------------------------------------------------------------
MAIN_01_TEXT segment word public 'CODE' use16
assume cs:MAIN_01
; ============================================================================
; Renders both the current and high score. In contrast to TH02, it also
; displays the continues digit.
; void pascal near hud_score_put(void);
public HUD_SCORE_PUT
HUD_SCORE_PUT proc pascal near
@@gaiji_p equ bx
@@score_p equ si
@@y equ di
push si
push di
mov @@score_p, offset _hiscore_lebcd[SCORE_DIGIT_HIGHEST]
mov @@y, 4
@@lebcd_to_gaiji:
mov cx, SCORE_DIGITS
mov @@gaiji_p, offset _hud_gaiji_row
@@digit_loop:
mov al, [@@score_p]
add al, GB_DIGITS
mov [@@gaiji_p], al
inc @@gaiji_p
dec @@score_p
loop @@digit_loop
call GAIJI_PUTSA pascal, HUD_LEFT, @@y, ds, offset _hud_gaiji_row, TX_WHITE
add @@y, 2
; Put exactly two lines, high score at (56, 4), and current score at
; (56, 6).
; You might notice that @@score_p is only assigned once. Yes, the code
; assumes that @@score_p now points at the end of _score_lebcd, which in
; turn assumes it's placed exactly before _hiscore_lebcd in memory, with
; no padding.
cmp @@y, 6
jz @@lebcd_to_gaiji
; And if that wasn't enough already, ZUN pops the registers in the wrong
; order. Good thing it doesn't matter for any caller of this function!
;
; This could have easily been fixed by either defining two (or better,
; three) LOCAL variables, or the USES directive if you *really* insist on
; using registers. Both of which would have automatically inserted the
; correct cleanup instructions before RET. Even in the 90's, "using an
; assembler" did very much *not* mean "having to manually spell out every
; instruction executed on the CPU"…
pop si
pop di
ret
HUD_SCORE_PUT endp
; ============================================================================
; void pascal near score_update_and_render(void);
public SCORE_UPDATE_AND_RENDER
SCORE_UPDATE_AND_RENDER proc near
; The TH04 version is functionally identical, just less optimized.
if GAME eq 5
@@delta_remaining_word equ dx
@@delta_remaining_char equ dl
else
@@delta_remaining_word equ cx
@@delta_remaining_char equ cl
endif
mov eax, _score_delta
or eax, eax
jz short @@ret
cmp _score_delta_frame, eax
jbe short @@calculate_frame_delta
mov word ptr _score_delta_frame, ax
@@calculate_frame_delta:
shr eax, 5
or eax, eax
jnz short @@clamp_delta_to_frame_limit
inc ax
jmp short @@prefer_larger_delta
@@clamp_delta_to_frame_limit:
cmp eax, SCORE_DELTA_FRAME_LIMIT
jbe short @@prefer_larger_delta
mov ax, SCORE_DELTA_FRAME_LIMIT
@@prefer_larger_delta:
cmp word ptr _score_delta_frame, ax
jnb short @@commit_frame_delta
mov word ptr _score_delta_frame, ax
@@commit_frame_delta:
mov @@delta_remaining_word, word ptr _score_delta_frame
jmp short @@update
; ---------------------------------------------------------------------------
@@render:
cmp _hiscore_popup_shown, 0
jnz short @@subtract_frame_delta_and_render
or al, al
jz short @@subtract_frame_delta_and_render
mov _hiscore_popup_shown, 1
mov _popup_id_new, POPUP_ID_HISCORE_ENTRY
mov _popup, offset POPUP_UPDATE_AND_RENDER
@@subtract_frame_delta_and_render:
mov eax, _score_delta_frame
sub _score_delta, eax
call HUD_SCORE_PUT
if GAME eq 4
mov _score_unused, 0
call SCORE_EXTEND_UPDATE
endif
@@ret:
retn
; ---------------------------------------------------------------------------
even
@@update:
push si
push di
if GAME eq 5
push ds
pop es
@@bcd_p equ di
@@po10_p equ bx
; Since the delta can have at most 5 digits, we only have to work on the
; range from _temp_lebcd[4] (highest) to _temp_lebcd[0] (lowest).
; Zeroing the remaining 3 digits is simply more convenient to do in a
; single 32-bit instruction if _temp_lebcd[4] is also (unnecessarily)
; zeroed.
mov dword ptr _temp_lebcd[4], 0
else
@@bcd_p equ bx
@@po10_p equ si
endif
mov @@bcd_p, offset _temp_lebcd[4]
mov @@po10_p, offset _FIVE_DIGIT_POWERS_OF_10
if GAME eq 5
; 4 divisions, the units place doesn't need a separate one.
mov cx, 4
endif
@@delta_to_bcd:
mov ax, @@delta_remaining_word
; 16-bit DIV interprets DX:AX as a 32-bit divisor…
xor dx, dx
; … and returns the remainder in DX, so we actually aren't losing
; anything here.
div word ptr [@@po10_p]
if GAME eq 5
add @@po10_p, 2
else
mov @@delta_remaining_word, dx
endif
mov [@@bcd_p], al
dec @@bcd_p
if GAME eq 5
loop @@delta_to_bcd
else
add @@po10_p, 2
cmp word ptr [@@po10_p], 1
ja @@delta_to_bcd
endif
@@last_digit:
mov [@@bcd_p], @@delta_remaining_char
; Obviously skipping the continues digit…
mov si, offset _score_lebcd[1]
; … and the last one seen from there doesn't need special BCD treatment
; either.
mov cx, SCORE_DIGITS - 2
; Yes, completely unnecessary in TH05, considering the next instruction.
xor ah, ah
; @@bcd_p == _temp_lebcd[0]
@@add_next_digit_to_score:
if GAME eq 5
movzx ax, byte ptr [@@bcd_p]
else
mov al, [@@bcd_p]
endif
add al, [si]
aaa ; AL < 9, AH = carry
mov [si], al
inc @@bcd_p
inc si
; Add the carry to next score digit. May now be 0Ah, but who cares, it's
; fixed via AAA on the next digit anyway… *except* if it's already the
; highest one, which is exactly where the infamous >100 million score
; glitch comes from.
add [si], ah
if GAME eq 4
mov ah, 0
endif
loop @@add_next_digit_to_score
mov al, [@@bcd_p]
add [si], al
; Is this the high score?
@@is_hiscore equ dl ; Never read, though
if GAME eq 4
push ds
pop es
; assume es:_DATA
endif
mov si, offset _score_lebcd[SCORE_DIGIT_HIGHEST]
mov di, offset _hiscore_lebcd[SCORE_DIGIT_HIGHEST]
xor @@is_hiscore, @@is_hiscore
mov cx, SCORE_DIGITS
cmp _hiscore_popup_shown, 0
jnz short @@hiscore_confirmed
@@check_next_digit_for_hiscore:
mov al, [si]
cmp [di], al
ja short @@hiscore_denied
jb short @@hiscore_confirmed
dec di
dec si
loop @@check_next_digit_for_hiscore
@@hiscore_confirmed:
; Copy the remaining number of digits in CX, backwards (STD!)
if GAME eq 5
cli
endif
std
rep movsb
cld
if GAME eq 5
sti
endif
inc @@is_hiscore
@@hiscore_denied:
mov al, @@is_hiscore
pop di
pop si
jmp @@render
SCORE_UPDATE_AND_RENDER endp
MAIN_01_TEXT ends
end