.386 locals include libs/master.lib/macros.inc include th04/math/motion.inc include th04/main/bullet/bullet.inc include th05/main/bullet/types.inc extrn _randring:byte extrn _randring_p:word extrn _playperf:byte extrn _group_is_special:byte extrn _group_fixedspeed:byte extrn _group_i_spread_angle:byte extrn _group_i:byte extrn _group_i_absolute_angle:byte extrn _group_i_speed:byte extrn _group_i_velocity:Point extrn _bullet_template:bullet_template_t extrn _bullet_zap:byte @PLAYER_ANGLE_FROM$Q20%SUBPIXELBASE$TI$TI%T1UC procdesc pascal near \ x:word, y:word, plus_angle:byte VECTOR2_NEAR procdesc pascal near \ ret:word, angle:byte, length:word _bullets_add_raw procdesc near MAIN_03 group BULLET_A_TEXT ; ---------------------------------------------------------------------------- BULLET_A_TEXT segment word public 'CODE' use16 assume cs:MAIN_03 ; The main algorithm is identical to the TH04 version... public @playperf_speedtune @playperf_speedtune proc near @@speed_from_playperf equ shr al, 1 mov @@speed_from_playperf, al mul _playperf ; ...except for this divisor (32 here vs. 16 in TH04) shr ax, 5 add al, @@speed_from_playperf cmp al, (8 shl 4) jbe short @@below_half_a_pixel_per_frame? mov al, (8 shl 4) @@below_half_a_pixel_per_frame?: cmp al, ((1 shl 4) / 2) jnb short @@ret mov al, ((1 shl 4) / 2) @@ret: ret @playperf_speedtune endp public _bullets_add_regular _bullets_add_regular proc near cmp _bullet_zap, 0 jnz short @@ret push word ptr _bullet_template.BT_angle call _bullets_add_raw pop word ptr _bullet_template.BT_angle @@ret: retn _bullets_add_regular endp even public _bullets_add_special _bullets_add_special proc near cmp _bullet_zap, 0 jnz short @@ret mov _group_is_special, 1 push word ptr _bullet_template.BT_angle call _bullets_add_raw pop word ptr _bullet_template.BT_angle mov _group_is_special, 0 @@ret: retn _bullets_add_special endp even public _bullets_add_regular_fixedspeed _bullets_add_regular_fixedspeed proc near mov _group_fixedspeed, 1 call _bullets_add_regular mov _group_fixedspeed, 0 retn _bullets_add_regular_fixedspeed endp public _bullets_add_special_fixedspeed _bullets_add_special_fixedspeed proc near mov _group_fixedspeed, 1 call _bullets_add_special mov _group_fixedspeed, 0 retn _bullets_add_special_fixedspeed endp public _bullet_velocity_and_angle_set _bullet_velocity_and_angle_set proc pascal near uses si, di @@spread equ @@stack equ @@speed equ @@group_i equ @@angle equ ; Why 16-bit? Just makes the ASM harder to understand. @@done equ set_done_if_last macro value:req local @@nope ifdifi , mov al, value endif dec al cmp al, @@group_i ja short @@nope inc @@done @@nope: endm xor @@angle, @@angle mov @@group_i, _group_i mov @@speed, _bullet_template.speed mov bl, _bullet_template.BT_group xor bh, bh ; This effectively defaults invalid group values to BG_FORCESINGLE, fixing ; TH01's and TH04's bug that completely filled the array in that case. ; But come on, that's one MOV instruction we're wastefully executing for ; regularly conforming code! cmp bx, BG_FORCESINGLE_AIMED mov @@done, 1 ja @@no_aim xor @@done, @@done add bx, bx mov ax, cs:@@switch_table[bx] mov @@spread, _bullet_template.spread mov @@stack, _bullet_template.BT_stack ; Make sure @@spread is ≥ 1, so that we can safely divide by it below. or @@spread, @@spread jnz short @@switch inc @@spread @@switch: jmp ax ; --------------------------------------------------------------------------- @@case_single_aimed: inc @@done jmp @@aim ; --------------------------------------------------------------------------- @@case_single: inc @@done jmp @@no_aim ; --------------------------------------------------------------------------- @@case_spread_or_ring_stack: ; Spawns the @@spread bullets within a single level of the stack first, ; before moving to the next level on the stack. ; @@speed += ((group_i / @@spread) * bullet_template.stack_speed_delta) xor ah, ah mov al, @@group_i div @@spread mov dl, _bullet_template.stack_speed_delta ; Ignores the remainder that the DIV above put into AH. ; Also note that this can easily overflow... mul dl add @@speed, al ; A ring or spread stack consists of (@@spread * @@stack) bullets in total. mov al, @@spread mul @@stack set_done_if_last al ; @@group_i %= @@spread ; Then we can continue with regular spread / ring angle calculation. xor ah, ah mov al, @@group_i div @@spread mov @@group_i, ah ; Again, that's where `DIV r8` leaves the remainder! ; We arrive here for: ; | not aimed | BG_RING_STACK | BG_SPREAD_STACK | ; | aimed | BG_RING_STACK_AIMED | BG_SPREAD_STACK_AIMED | cmp _bullet_template.BT_group, BG_RING_STACK jz short @@calculate_ring_angle cmp _bullet_template.BT_group, BG_RING_STACK_AIMED jz short @@calculate_ring_angle jmp short @@calculate_spread_angle ; --------------------------------------------------------------------------- @@case_spread: set_done_if_last @@spread ; Mostly identical to TH04's code, except that odd- and even-numbered ; spreads now use the same code beyond the first bullet. @@calculate_spread_angle: xor ah, ah ; Remember, @@angle is 16-bit... test @@spread, 1 jz short @@even_spread @@odd_spread: or @@group_i, @@group_i jnz short @@odd_spread_and_not_first_bullet @@odd_spread_and_first_bullet: mov _group_i_spread_angle, 0 ; Remember, @@angle is still 0x00, from the beginning of the function. jmp short @@got_spread_angle @@odd_spread_and_not_first_bullet: test @@group_i, 1 jz short @@even_numbered_bullet @@odd_numbered_bullet: ; @@angle = (group_i_spread_angle += bullet_template.spread_angle_delta) mov al, _bullet_template.spread_angle_delta add _group_i_spread_angle, al mov al, _group_i_spread_angle mov @@angle, ax jmp short @@got_spread_angle @@even_numbered_bullet: ; @@angle = (0x100 - group_i_spread_angle) mov al, _group_i_spread_angle neg al mov @@angle, ax jmp short @@got_spread_angle @@even_spread: or @@group_i, @@group_i jnz short @@even_spread_and_not_first_bullet @@even_spread_and_first_bullet: ; @@angle = group_i_spread_angle = (bullet_template.spread_angle_delta / 2) mov al, _bullet_template.spread_angle_delta shr al, 1 mov _group_i_spread_angle, al mov @@angle, ax jmp short @@got_spread_angle @@even_spread_and_not_first_bullet: test @@group_i, 1 jnz short @@even_numbered_bullet jmp short @@odd_numbered_bullet @@got_spread_angle: ; We arrive here for: ; | not aimed | BG_SPREAD | BG_SPREAD_STACK | ; | aimed | BG_SPREAD_AIMED | BG_SPREAD_STACK_AIMED | cmp _bullet_template.BT_group, BG_SPREAD jz @@no_aim cmp _bullet_template.BT_group, BG_SPREAD_STACK jz @@no_aim jmp short @@aim ; --------------------------------------------------------------------------- @@case_ring: set_done_if_last @@spread @@calculate_ring_angle: ; @@angle = ((@@group_i * 0x100) / @@spread) ; Setting AL to 0 and loading @@group_i directly into AH optimizes away the ; otherwise necessary multiplication / bit shift. Nifty! xor al, al mov ah, @@group_i div @@spread xor ah, ah mov @@angle, ax ; We arrive here for: ; | not aimed | BG_RING | BG_RING_STACK | ; | aimed | BG_RING_AIMED | BG_RING_STACK_AIMED | cmp _bullet_template.BT_group, BG_RING jz short @@no_aim cmp _bullet_template.BT_group, BG_RING_STACK jz short @@no_aim jmp short @@aim ; --------------------------------------------------------------------------- @@case_random_angle_and_added_speed: ; So that's why [randring_p] was needlessly turned into a 16-bit variable ; in TH04! x86 doesn't support indexing with an 8-bit register. (And who ; uses XLAT anyway, right?) ; @@speed += randring_next8_and(to_sp(2.0f) - 1) mov si, _randring_p mov al, _randring[si] inc byte ptr _randring_p and al, 1Fh add @@speed, al @@case_random_angle: ; Equivalent to `set_done_if_last @@spread`. Looks like somewhere down the ; line, ZUN realized that incrementing @@group_i itself is superior over ; moving to a register and decrementing? At least once we're done with that ; register. inc @@group_i cmp @@group_i, @@spread jb short @@random_angle_not_done_yet inc @@done @@random_angle_not_done_yet: ; @@angle = randring_next8() mov si, _randring_p mov @@angle, word ptr _randring[si] and @@angle, (100h - 1) inc byte ptr _randring_p jmp short @@no_aim ; --------------------------------------------------------------------------- @@case_stack: ; @@speed += (bullet_template.stack_speed_delta * group_i) mov al, _bullet_template.stack_speed_delta mul @@group_i add @@speed, al ; Equivalent to `set_done_if_last @@stack`. mov al, @@stack dec al cmp al, @@group_i jle short @@stack_done ; Stop spawning bullets if @@speed >= 10.0f, just like in TH04 cmp @@speed, (10 shl 4) jb short @@stack_not_speed_capped_yet @@stack_done: inc @@done @@stack_not_speed_capped_yet: cmp _bullet_template.BT_group, BG_STACK jz short @@no_aim ; --------------------------------------------------------------------------- @@aim: ; Note that the high byte of @@angle is in fact ignored everywhere. mov _group_i_speed, @@speed call @player_angle_from$q20%SubpixelBase$ti$ti%t1uc pascal, _bullet_template.BT_origin.x, _bullet_template.BT_origin.y, @@angle mov @@speed, _group_i_speed mov @@angle, ax @@no_aim: push offset _group_i_velocity ; ret mov ax, @@angle add al, _bullet_template.BT_angle push ax ; angle mov _group_i_absolute_angle, al mov _group_i_speed, @@speed mov al, @@speed mov ah, 0 push ax ; length call vector2_near mov ax, @@done ret ; --------------------------------------------------------------------------- @@switch_table label word dw offset @@case_single dw offset @@case_single_aimed dw offset @@case_spread dw offset @@case_spread ; (aimed) dw offset @@case_ring dw offset @@case_ring ; (aimed) dw offset @@case_stack dw offset @@case_stack ; (aimed) dw offset @@case_spread_or_ring_stack dw offset @@case_spread_or_ring_stack ; (aimed) dw offset @@case_spread_or_ring_stack dw offset @@case_spread_or_ring_stack ; (aimed) dw offset @@case_random_angle dw offset @@case_random_angle_and_added_speed dw offset @@case_single dw offset @@case_single_aimed _bullet_velocity_and_angle_set endp BULLET_A_TEXT ends end