mirror of
https://github.com/pret/pokeyellow.git
synced 2026-08-26 04:22:58 -05:00
Translate NPC walk state machine; fix SPRITESTATEDATA2 layout; drive IO_DIV
- movement.asm: full UpdateNonPlayerSprite dispatch (status 0-3), Random, Func_5337/5349, TryWalking, CanWalkOntoTile (IsTilePassable + displacement bounds + DetectCollision), UpdateSpriteMovementDelay, NotYetMoving, UpdateSpriteInWalkingAnimation. Direction constraints (ANY_DIR/UP_DOWN/ LEFT_RIGHT/forced) read from SPRITESTATEDATA2_MOVEMENTBYTE2 (offset 0x1). Yellow south-displacement fix: no upper bound on south/east wandering. - gb_memmap.inc: fix MOVEMENTDELAY/MOVEMENTBYTE2 offset swap (was 0x1/0x8, now 0x8/0x1 matching pret). map_sprites.asm auto-corrected via constants. Added NPC_DIR_* and NPC_MOVEMENT_* constants. - overworld.asm: export IsTilePassable (needed by CanWalkOntoTile). - sprite_oam.asm: NPC walk interpolation — subtract YSTEP*WALKANIMCOUNTER and XSTEP*WALKANIMCOUNTER from dos_base so NPCs glide forward (not teleport-back). - Makefile: add src/engine/math/random.asm to GAME_SRCS (was missing → link error). - frame.asm: increment IO_DIV once per frame in commit_shadow_regs so Random_'s LCG has time-varying input; was always 0. Verified live in DOSBox-X: girl and fisher NPCs walk with varied directions and random delays. Walking-frame tile glitch is expected (asset deferred). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2026,3 +2026,51 @@ The original pret visibility range test used 8-bit unsigned byte arithmetic:
|
||||
**Critical:** Lower-bound subtraction must use 32-bit signed registers — `sub al, 3` wraps to `0xFC` when `wYCoord=0`, culling every NPC. Fix: `movzx eax; lea ecx,[eax-N]; cmp ecx,edx; jg .invisible`.
|
||||
|
||||
---
|
||||
|
||||
## UpdateNonPlayerSprite / NPC walk state machine
|
||||
|
||||
- **Source:** `engine/overworld/movement.asm:UpdateNPCSprite` and helpers (pret lines 99–370, 556–666, 990–1016)
|
||||
- **Translated:** `dos_port/src/engine/overworld/movement.asm`
|
||||
- **Date:** 2026-06-23
|
||||
- **H-flag:** not involved
|
||||
- **Bug tags:** BUG(cosmetic) Yellow south-displacement fix applied (see below)
|
||||
|
||||
### Summary
|
||||
|
||||
Full NPC random-walk state machine: status dispatch, delay countdown, direction selection with UP_DOWN/LEFT_RIGHT/forced-dir constraints, tile passability + collision + displacement bounds check, walk-pixel interpolation, and animation counter.
|
||||
|
||||
### Functions translated
|
||||
|
||||
| Pret label | DOS label | Notes |
|
||||
|---|---|---|
|
||||
| `UpdateNPCSprite` | `UpdateNonPlayerSprite` | Status 0→init, 1→ready, 2→delay, 3→walk; BIT_FACE_PLAYER stub |
|
||||
| `Func_5337` | `Func_5337` | Write FACINGDIRECTION/YSTEPVECTOR/XSTEPVECTOR to sprite slot |
|
||||
| `Func_5349` | `Func_5349` | Advance MAPY/MAPX to destination at walk START (not end) |
|
||||
| `TryWalking` | `TryWalking` | Call Func_5337 → CanWalkOntoTile → Func_5349 → STATUS=3 |
|
||||
| `CanWalkOntoTile` | `CanWalkOntoTile` | IsTilePassable + STAY check + displacement bounds + DetectCollision |
|
||||
| `UpdateSpriteMovementDelay` | `UpdateSpriteMovementDelay` | Decrement MOVEMENTDELAY; 0 → STATUS=1, fall into NotYetMoving |
|
||||
| `NotYetMoving` | `NotYetMoving` | Reset ANIMFRAMECOUNTER, UpdateSpriteImage |
|
||||
| `UpdateSpriteInWalkingAnimation` | `UpdateSpriteInWalkingAnimation` | pixel-interpolation (YPIXELS/XPIXELS += YSTEP/XSTEP), WALKANIMCOUNTER |
|
||||
| `Random` | `Random` | Thin wrapper: saves/restores EBX, calls `Random_`, returns H_RANDOM_ADD in AL |
|
||||
|
||||
### SPRITESTATEDATA2 constants bug fixed
|
||||
|
||||
`gb_memmap.inc` had MOVEMENTDELAY at offset 0x1 (unused slot) and MOVEMENTBYTE2 at 0x8 (the real MOVEMENTDELAY slot). This caused map_sprites.asm to write direction constraints to slot 0x8 and delays to slot 0x1. Fix: swap them to match pret (MOVEMENTBYTE2=0x1, MOVEMENTDELAY=0x8). Because map_sprites.asm uses symbolic constants, the write offsets corrected automatically.
|
||||
|
||||
### Func_5349 timing — teleport-prevention
|
||||
|
||||
Pret advances MAPY/MAPX to the **destination** at walk **start** (inside `TryWalking`, before the first pixel step). PrepareOAMData's `dos_base_npc` formula therefore subtracts `YSTEP × WALKANIMCOUNTER` and `XSTEP × WALKANIMCOUNTER` to interpolate back to the source position, counting down to 0 at walk end. Without this, NPCs would appear to teleport one metatile and slide back.
|
||||
|
||||
### wMapSpriteData indirection eliminated
|
||||
|
||||
Pret's `UpdateNPCSprite` reads the direction constraint (`wCurSpriteMovement2`) via a separate `wMapSpriteData` pointer array. The DOS port stores the constraint directly in `SPRITESTATEDATA2[MOVEMENTBYTE2]` (offset 0x1), set by `InitMapSprites`. No separate array needed.
|
||||
|
||||
### Yellow south-displacement fix
|
||||
|
||||
Red/Blue had a bug: the south-displacement upper bound used `cmp a, 5; jnc .blocked` — the same condition as the north lower bound — which meant NPCs could only move 4 tiles south of their starting position. Yellow fixed this by removing the south upper bound check. The DOS port follows Yellow behavior (no south or east upper bound).
|
||||
|
||||
### Random_ / IO_DIV
|
||||
|
||||
`random.asm`'s LCG reads `IO_DIV` (at `[EBP + 0xFF04]`). Previously always 0 (emulated but not driven). Fixed by incrementing `IO_DIV` once per frame inside `commit_shadow_regs` (`frame.asm`) so the LCG has changing input. Verified live: NPCs walk with varied directions and delays.
|
||||
|
||||
---
|
||||
|
||||
@@ -92,7 +92,8 @@ GAME_SRCS := \
|
||||
src/movie/title.asm \
|
||||
src/engine/overworld/overworld.asm \
|
||||
src/engine/overworld/movement.asm \
|
||||
src/engine/overworld/map_sprites.asm
|
||||
src/engine/overworld/map_sprites.asm \
|
||||
src/engine/math/random.asm
|
||||
|
||||
# Debug-only sources (linked only when the corresponding flag is set).
|
||||
# Any debug flag implies SKIP_TITLE — debug harnesses boot straight to the overworld.
|
||||
|
||||
@@ -150,17 +150,38 @@ SPRITESTATEDATA2_XDISPLACEMENT equ 0x3 ; X displacement (init 8)
|
||||
SPRITESTATEDATA2_MAPY equ 0x4 ; map Y position (metatile units, origin+4)
|
||||
SPRITESTATEDATA2_MAPX equ 0x5 ; map X position (metatile units, origin+4)
|
||||
SPRITESTATEDATA2_WALKANIMCOUNTER equ 0x0 ; 8-frame countdown while walking (0=standing)
|
||||
SPRITESTATEDATA2_MOVEMENTDELAY equ 0x1 ; frames until next random walk attempt
|
||||
SPRITESTATEDATA2_MOVEMENTBYTE2 equ 0x1 ; dir constraint from map object (pret unnamed 0x01)
|
||||
SPRITESTATEDATA2_MOVEMENTDELAY equ 0x8 ; frames until next random walk attempt (pret 0x08)
|
||||
SPRITESTATEDATA2_MOVEMENTBYTE1 equ 0x6 ; $ff=STAY, $fe=WALK(random), <$fe=scripted
|
||||
SPRITESTATEDATA2_GRASSPRIORITY equ 0x7 ; $80=under grass (set by CheckSpriteAvailability)
|
||||
SPRITESTATEDATA2_MOVEMENTBYTE2 equ 0x8 ; dir constraint from map object (0x00/0x01/0x02/0xFF)
|
||||
SPRITESTATEDATA2_ISTRAINER equ 0x9 ; 1 if trainer NPC, 0 otherwise
|
||||
SPRITESTATEDATA2_ISTRAINER equ 0x9 ; 1 if trainer NPC, 0 otherwise (pret: ORIGFACINGDIR)
|
||||
SPRITESTATEDATA2_IMAGEBASEOFFSET equ 0xE
|
||||
|
||||
; NPC movement-byte sentinel values (constants/map_object_constants.asm)
|
||||
WALK equ 0xFE ; random movement
|
||||
STAY equ 0xFF ; stationary
|
||||
|
||||
; NPC direction constraint values (movement byte 2 / pret wMapSpriteData byte 0)
|
||||
; constants/map_object_constants.asm: ANY_DIR=0x00, UP_DOWN=0x01, LEFT_RIGHT=0x02
|
||||
; Forced single-direction: DOWN=0xD0, UP=0xD1, LEFT=0xD2, RIGHT=0xD3, NONE=0xFF
|
||||
NPC_DIR_ANY equ 0x00 ; any random direction (pret: ANY_DIR)
|
||||
NPC_DIR_UPDOWN equ 0x01 ; constrained to up/down only (pret: UP_DOWN)
|
||||
NPC_DIR_LR equ 0x02 ; constrained to left/right only (pret: LEFT_RIGHT)
|
||||
NPC_DIR_DOWN equ 0xD0 ; forced down (pret: DOWN)
|
||||
NPC_DIR_UP equ 0xD1 ; forced up
|
||||
NPC_DIR_LEFT equ 0xD2 ; forced left
|
||||
NPC_DIR_RIGHT equ 0xD3 ; forced right
|
||||
NPC_DIR_NONE equ 0xFF ; STAY NPC (no movement constraint)
|
||||
UP_DOWN equ NPC_DIR_UPDOWN
|
||||
LEFT_RIGHT equ NPC_DIR_LR
|
||||
|
||||
; NPC random movement direction thresholds (high 2 bits of hRandomAdd)
|
||||
; constants/sprite_data_constants.asm
|
||||
NPC_MOVEMENT_DOWN equ 0x00 ; hRandomAdd in [0x00,0x3F] → go down
|
||||
NPC_MOVEMENT_UP equ 0x40 ; hRandomAdd in [0x40,0x7F] → go up
|
||||
NPC_MOVEMENT_LEFT equ 0x80 ; hRandomAdd in [0x80,0xBF] → go left
|
||||
NPC_MOVEMENT_RIGHT equ 0xC0 ; hRandomAdd in [0xC0,0xFF] → go right
|
||||
|
||||
; Named WRAM symbols (subset needed by translated routines).
|
||||
; Offsets from pokeyellow.sym (bank 00).
|
||||
W_SHADOW_OAM equ 0xC300 ; wShadowOAM — 40 sprites × 4 bytes
|
||||
|
||||
@@ -6,16 +6,20 @@
|
||||
; engine/overworld/movement.asm:UpdatePlayerSprite / UpdateNonPlayerSprite /
|
||||
; InitializeSpriteStatus / InitializeSpriteScreenPosition / Func_5033 /
|
||||
; CheckSpriteAvailability / GetTileSpriteStandsOn / UpdateSpriteImage /
|
||||
; UpdateSpriteMovementDelay / UpdateSpriteInWalkingAnimation / NotYetMoving /
|
||||
; TryWalking / CanWalkOntoTile / Func_5337 / Func_5349 /
|
||||
; Func_4e32 / Func_5274
|
||||
;
|
||||
; UpdateSprites runs once per overworld-loop iteration. It walks all 16 sprite
|
||||
; slots: slot 0 → UpdatePlayerSprite (facing + walk animation); slots 1-15 →
|
||||
; UpdateNonPlayerSprite (static NPCs: screen-position + image index from
|
||||
; wSpriteStateData1/2, no movement engine yet). PrepareOAMData (sprite_oam.asm)
|
||||
; UpdateNonPlayerSprite (full NPC walk state machine: delay countdown, 16-frame
|
||||
; pixel-step animation, direction selection with UP_DOWN/LEFT_RIGHT constraints,
|
||||
; tile passability + sprite-collision gating). PrepareOAMData (sprite_oam.asm)
|
||||
; then turns the image indices into shadow-OAM entries each DelayFrame.
|
||||
;
|
||||
; NPC scope: static NPCs only (MOVEMENTSTATUS 0→1 init, CheckSpriteAvailability,
|
||||
; InitializeSpriteScreenPosition). Random/scripted NPC movement is deferred.
|
||||
; Direction constraint is read from SPRITESTATEDATA2_MOVEMENTBYTE2 (offset 0x1),
|
||||
; replacing pret's wMapSpriteData/wCurSpriteMovement2 indirection.
|
||||
; Scripted NPC movement (MOVEMENTBYTE1 < WALK) is a stub — not yet implemented.
|
||||
;
|
||||
; Build: nasm -f coff -I include/ -I . -o movement.o src/engine/overworld/movement.asm
|
||||
|
||||
@@ -26,6 +30,9 @@ bits 32
|
||||
|
||||
global UpdateSprites
|
||||
|
||||
extern IsTilePassable
|
||||
extern Random_
|
||||
|
||||
section .text
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
@@ -73,14 +80,22 @@ _UpdateSprites:
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; UpdateNonPlayerSprite — static NPC screen-position + image-index update.
|
||||
; Pret ref: engine/overworld/movement.asm:UpdateNonPlayerSprite.
|
||||
; UpdateNonPlayerSprite — full NPC walk state machine.
|
||||
; Pret ref: engine/overworld/movement.asm:UpdateNPCSprite.
|
||||
;
|
||||
; Scope: static NPCs only (MOVEMENTBYTE1 = STAY). Movement engine deferred.
|
||||
; Status dispatch:
|
||||
; 0 → InitializeSpriteStatus (first-frame init)
|
||||
; 1 → CheckSpriteAvailability; if visible and player not walking: direction
|
||||
; selection → TryWalking (WALK/STAY) or stub (scripted <WALK)
|
||||
; 2 → UpdateSpriteMovementDelay (countdown to next walk)
|
||||
; 3 → UpdateSpriteInWalkingAnimation (pixel-step animation)
|
||||
;
|
||||
; Direction constraint (wCurSpriteMovement2 in pret) is read directly from
|
||||
; SPRITESTATEDATA2_MOVEMENTBYTE2 (offset 0x1). Scripted movement is a stub.
|
||||
;
|
||||
; ESI is reloaded from hCurrentSpriteOffset at entry (clobbers the loop's ESI;
|
||||
; the loop re-derives ESI after the call — see .skip above).
|
||||
; All other registers: caller is UpdateSprites which pushad/popad, so free.
|
||||
; the loop re-derives ESI after the call — see _UpdateSprites above).
|
||||
; All other registers: caller pushad/popad, so free.
|
||||
; ---------------------------------------------------------------------------
|
||||
UpdateNonPlayerSprite:
|
||||
movzx esi, byte [ebp + H_CURRENT_SPRITE_OFFSET]
|
||||
@@ -90,20 +105,379 @@ UpdateNonPlayerSprite:
|
||||
dec al
|
||||
ror al, 4 ; nibble swap: (N-1) → (N-1)*16
|
||||
mov [ebp + H_TILE_PLAYER_STANDING_ON], al
|
||||
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_MOVEMENTSTATUS]
|
||||
test al, al
|
||||
jz .initStatus ; status 0 → first-frame init
|
||||
|
||||
call CheckSpriteAvailability
|
||||
jc .ret ; CF=1 → invisible, done
|
||||
jc .ret ; CF=1 → invisible
|
||||
|
||||
; Re-read status after availability check
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_MOVEMENTSTATUS]
|
||||
test al, 0x80 ; BIT_FACE_PLAYER (bit 7): NPC must face player
|
||||
jnz .facePlayer ; stub: fall through to NotYetMoving
|
||||
|
||||
; Freeze NPC if text/font is loaded (dialog open)
|
||||
mov bl, [ebp + W_FONT_LOADED]
|
||||
test bl, 1 << BIT_FONT_LOADED
|
||||
jnz .notYetMoving
|
||||
|
||||
cmp al, 2
|
||||
je .updateDelay ; status 2 → UpdateSpriteMovementDelay
|
||||
cmp al, 3
|
||||
je .updateWalk ; status 3 → UpdateSpriteInWalkingAnimation
|
||||
; status 1: ready to move
|
||||
cmp byte [ebp + W_WALK_COUNTER], 0
|
||||
jne .ret ; mid-walk → sprite-shift loop handles pixel motion
|
||||
call InitializeSpriteScreenPosition ; standing: snap screen position to map coords
|
||||
jne .ret ; player is walking: don't start new NPC step
|
||||
|
||||
call InitializeSpriteScreenPosition ; snap screen position to map coords
|
||||
|
||||
; Check movement type
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MOVEMENTBYTE1]
|
||||
inc al
|
||||
jz .randomMovement ; STAY (0xFF+1=0) → random (always blocked)
|
||||
inc al
|
||||
jz .randomMovement ; WALK (0xFE+1→0xFF+1=0) → random walk
|
||||
; Scripted movement (MOVEMENTBYTE1 < WALK): not yet implemented — skip
|
||||
jmp .ret
|
||||
|
||||
.updateDelay:
|
||||
call UpdateSpriteMovementDelay
|
||||
jmp .ret
|
||||
|
||||
.updateWalk:
|
||||
call UpdateSpriteInWalkingAnimation
|
||||
jmp .ret
|
||||
|
||||
.facePlayer:
|
||||
; Stub: just reset anim and update image (real MakeNPCFacePlayer sets facing dir)
|
||||
; Sets the BIT_FACE_PLAYER to 0 would go here; for now, just freeze animation.
|
||||
call NotYetMoving
|
||||
jmp .ret
|
||||
|
||||
.notYetMoving:
|
||||
call NotYetMoving
|
||||
jmp .ret
|
||||
|
||||
.randomMovement:
|
||||
; GetTileSpriteStandsOn clobbers EBX, ECX, AL.
|
||||
; Random_ clobbers AL, BL. Push EBX around Random call.
|
||||
call GetTileSpriteStandsOn ; EBX = wTileMap offset of lower-left tile under NPC
|
||||
push ebx ; save tile ptr (BL clobbered by Random_)
|
||||
call Random ; H_RANDOM_ADD updated; AL = random byte
|
||||
pop ebx ; restore tile ptr
|
||||
; AL = random value; CL = direction constraint
|
||||
movzx ecx, byte [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MOVEMENTBYTE2]
|
||||
|
||||
.determineDirection:
|
||||
; Forced single directions: DOWN=0xD0, UP=0xD1, LEFT=0xD2, RIGHT=0xD3
|
||||
cmp cl, NPC_DIR_DOWN
|
||||
je .moveDown
|
||||
cmp cl, NPC_DIR_UP
|
||||
je .moveUp
|
||||
cmp cl, NPC_DIR_LEFT
|
||||
je .moveLeft
|
||||
cmp cl, NPC_DIR_RIGHT
|
||||
je .moveRight
|
||||
; Random direction from high 2 bits of AL; apply UP_DOWN/LEFT_RIGHT constraints
|
||||
cmp al, NPC_MOVEMENT_UP ; < 0x40 → try down
|
||||
jnc .notRandDown
|
||||
cmp cl, LEFT_RIGHT
|
||||
je .moveLeft ; LEFT_RIGHT constraint → go left instead of down
|
||||
jmp .moveDown
|
||||
.notRandDown:
|
||||
cmp al, NPC_MOVEMENT_LEFT ; 0x40-0x7F → try up
|
||||
jnc .notRandUp
|
||||
cmp cl, LEFT_RIGHT
|
||||
je .moveRight ; LEFT_RIGHT → right instead of up
|
||||
jmp .moveUp
|
||||
.notRandUp:
|
||||
cmp al, NPC_MOVEMENT_RIGHT ; 0x80-0xBF → try left
|
||||
jnc .notRandLeft
|
||||
cmp cl, UP_DOWN
|
||||
je .moveUp ; UP_DOWN → up instead of left
|
||||
jmp .moveLeft
|
||||
.notRandLeft: ; 0xC0-0xFF → try right
|
||||
cmp cl, UP_DOWN
|
||||
je .moveDown ; UP_DOWN → down instead of right
|
||||
; fall through to .moveRight
|
||||
|
||||
.moveRight:
|
||||
add ebx, 2
|
||||
mov cl, byte [ebp + ebx] ; tile ID at destination
|
||||
mov bl, 1 ; direction bit
|
||||
mov ch, SPRITE_FACING_RIGHT ; = 0x0C
|
||||
xor dh, dh ; Y step = 0
|
||||
mov dl, 1 ; X step = +1
|
||||
call TryWalking
|
||||
jmp .ret
|
||||
|
||||
.moveLeft:
|
||||
sub ebx, 2
|
||||
mov cl, byte [ebp + ebx]
|
||||
mov bl, 2
|
||||
mov ch, SPRITE_FACING_LEFT ; = 0x08
|
||||
xor dh, dh
|
||||
mov dl, 0xFF ; X step = -1
|
||||
call TryWalking
|
||||
jmp .ret
|
||||
|
||||
.moveDown:
|
||||
add ebx, 2*SCREEN_WIDTH
|
||||
mov cl, byte [ebp + ebx]
|
||||
mov bl, 4
|
||||
mov ch, SPRITE_FACING_DOWN ; = 0x00
|
||||
mov dh, 1 ; Y step = +1
|
||||
xor dl, dl
|
||||
call TryWalking
|
||||
jmp .ret
|
||||
|
||||
.moveUp:
|
||||
sub ebx, 2*SCREEN_WIDTH
|
||||
mov cl, byte [ebp + ebx]
|
||||
mov bl, 8
|
||||
mov ch, SPRITE_FACING_UP ; = 0x04
|
||||
mov dh, 0xFF ; Y step = -1
|
||||
xor dl, dl
|
||||
call TryWalking
|
||||
; fall through to .ret
|
||||
|
||||
.ret:
|
||||
ret
|
||||
.initStatus:
|
||||
call InitializeSpriteStatus
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; Random — update H_RANDOM_ADD/H_RANDOM_SUB, return result in AL.
|
||||
; Pret ref: home/random.asm (calls Random_ from engine/math/random.asm).
|
||||
; AL = H_RANDOM_ADD after update. Clobbers AL, BL.
|
||||
; ---------------------------------------------------------------------------
|
||||
Random:
|
||||
call Random_
|
||||
mov al, [ebp + H_RANDOM_ADD]
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; Func_5337 — set FACINGDIRECTION, YSTEPVECTOR, XSTEPVECTOR for NPC slot ESI.
|
||||
; Pret ref: engine/overworld/movement.asm:Func_5337.
|
||||
; In: CH = facing direction, DH = Y step (-1/0/+1 as 0xFF/0x00/0x01), DL = X step.
|
||||
; ---------------------------------------------------------------------------
|
||||
Func_5337:
|
||||
mov [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_FACINGDIRECTION], ch
|
||||
mov [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_YSTEPVECTOR], dh
|
||||
mov [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_XSTEPVECTOR], dl
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; Func_5349 — advance MAPY and MAPX by the step vectors at walk start.
|
||||
; Pret ref: engine/overworld/movement.asm:Func_5349.
|
||||
; In: DH = Y step, DL = X step, ESI = slot byte offset.
|
||||
; ---------------------------------------------------------------------------
|
||||
Func_5349:
|
||||
mov al, dh
|
||||
add [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MAPY], al
|
||||
mov al, dl
|
||||
add [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MAPX], al
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; TryWalking — attempt to walk the NPC one metatile in a chosen direction.
|
||||
; Pret ref: engine/overworld/movement.asm:TryWalking.
|
||||
;
|
||||
; In: CL = tile ID at destination (from wTileMap), BL = direction bit (1/2/4/8),
|
||||
; CH = facing direction, DH = Y step, DL = X step, ESI = slot byte offset.
|
||||
; Out: CF=0 → walk started (WALKANIMCOUNTER=16, MOVEMENTSTATUS=3);
|
||||
; CF=1 → blocked (CanWalkOntoTile already set MOVEMENTSTATUS=2, random delay).
|
||||
; Clobbers: EAX, ECX, EDX (stack-saved around CanWalkOntoTile).
|
||||
; ---------------------------------------------------------------------------
|
||||
TryWalking:
|
||||
call Func_5337 ; write facing+step vectors to sprite state
|
||||
; CanWalkOntoTile uses DH/DL (reads but does not modify them).
|
||||
; ESI (slot offset) is preserved by CanWalkOntoTile.
|
||||
call CanWalkOntoTile ; CF=1 → blocked
|
||||
jc .ret
|
||||
call Func_5349 ; update MAPY/MAPX to destination (walk start)
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_WALKANIMCOUNTER], 16
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_MOVEMENTSTATUS], 3
|
||||
call UpdateSpriteImage
|
||||
clc
|
||||
.ret:
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; CanWalkOntoTile — passability + sprite-collision check for NPC movement.
|
||||
; Pret ref: engine/overworld/movement.asm:CanWalkOntoTile.
|
||||
;
|
||||
; In: CL = tile ID at destination, BL = direction bit (1/2/4/8),
|
||||
; DH = Y step, DL = X step, ESI = slot byte offset.
|
||||
; Out: CF=0 → passable; CF=1 → blocked (MOVEMENTSTATUS=2 + random delay set).
|
||||
; Clobbers: EAX, ECX. EBX (BL direction bit) and EDX preserved.
|
||||
; ---------------------------------------------------------------------------
|
||||
CanWalkOntoTile:
|
||||
; If scripted movement (MOVEMENTBYTE1 < WALK=0xFE), always allow
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MOVEMENTBYTE1]
|
||||
cmp al, WALK
|
||||
jnc .checkTile
|
||||
clc
|
||||
ret
|
||||
|
||||
.checkTile:
|
||||
push esi ; IsTilePassable clobbers ESI
|
||||
call IsTilePassable ; CL = tile ID; CF=1 if blocked
|
||||
pop esi
|
||||
jc .impassable
|
||||
|
||||
; STAY sentinel (MOVEMENTBYTE1 == 0xFF): never actually walk
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MOVEMENTBYTE1]
|
||||
inc al ; 0xFF+1=0 (ZF)
|
||||
jz .impassable
|
||||
|
||||
; Y displacement bounds — prevents unlimited north/west roaming from start position.
|
||||
; YDISPLACEMENT starts at 8 (set in InitializeSpriteStatus).
|
||||
; Moving north (DH=0xFF): must have YDISPLACEMENT > 0.
|
||||
; Moving south (DH=0x01): always allowed (Yellow bug-fix: no upper bound).
|
||||
movzx eax, byte [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_YDISPLACEMENT]
|
||||
test dh, dh
|
||||
jz .checkXDisp ; DH=0: not moving vertically
|
||||
js .moveNorth ; DH=0xFF (bit 7 set): moving north
|
||||
; moving south: increment displacement, no upper bound (Yellow fix)
|
||||
add al, 1
|
||||
mov [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_YDISPLACEMENT], al
|
||||
jmp .checkXDisp
|
||||
.moveNorth:
|
||||
sub al, 1 ; YDISPLACEMENT - 1; CF=1 if was 0 → blocked
|
||||
jc .impassable
|
||||
mov [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_YDISPLACEMENT], al
|
||||
|
||||
.checkXDisp:
|
||||
movzx eax, byte [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_XDISPLACEMENT]
|
||||
test dl, dl
|
||||
jz .detectCollision
|
||||
js .moveWest
|
||||
; moving east: always allowed
|
||||
add al, 1
|
||||
mov [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_XDISPLACEMENT], al
|
||||
jmp .detectCollision
|
||||
.moveWest:
|
||||
sub al, 1
|
||||
jc .impassable
|
||||
mov [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_XDISPLACEMENT], al
|
||||
|
||||
.detectCollision:
|
||||
; Run collision detection to populate COLLISIONDATA for direction check.
|
||||
; Must save/restore wUpdateSpritesEnabled (pret pattern).
|
||||
movzx eax, byte [ebp + W_UPDATE_SPRITES_ENABLED]
|
||||
push eax
|
||||
mov byte [ebp + W_UPDATE_SPRITES_ENABLED], 0xFF
|
||||
call DetectCollisionBetweenSprites ; preserves EBX, ECX, EDX, ESI, EDI
|
||||
pop eax
|
||||
mov [ebp + W_UPDATE_SPRITES_ENABLED], al
|
||||
; BL = direction bit (preserved through DetectCollisionBetweenSprites)
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_COLLISIONDATA]
|
||||
test al, bl
|
||||
jnz .impassable
|
||||
|
||||
clc
|
||||
ret
|
||||
|
||||
.impassable:
|
||||
; Set status=2 (delayed), zero step vectors, assign random delay.
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_MOVEMENTSTATUS], 2
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_YSTEPVECTOR], 0
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_XSTEPVECTOR], 0
|
||||
call Random ; AL = H_RANDOM_ADD (clobbers AL, BL)
|
||||
and al, 0x7F ; random 0–127 frames
|
||||
mov [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MOVEMENTDELAY], al
|
||||
stc
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; UpdateSpriteMovementDelay — decrement inter-walk delay, transition to status 1.
|
||||
; Pret ref: engine/overworld/movement.asm:UpdateSpriteMovementDelay.
|
||||
; In: ESI = slot byte offset. Clobbers AL.
|
||||
; Falls through to NotYetMoving when ready.
|
||||
; ---------------------------------------------------------------------------
|
||||
UpdateSpriteMovementDelay:
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MOVEMENTBYTE1]
|
||||
cmp al, WALK
|
||||
jnc .tickCounter ; WALK or STAY: decrement counter
|
||||
; Scripted: clear delay immediately → ready to move
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MOVEMENTDELAY], 0
|
||||
jmp .moving
|
||||
|
||||
.tickCounter:
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MOVEMENTDELAY]
|
||||
dec al
|
||||
mov [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MOVEMENTDELAY], al
|
||||
jnz NotYetMoving ; still waiting: freeze animation
|
||||
|
||||
.moving:
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_MOVEMENTSTATUS], 1
|
||||
; fall through to NotYetMoving
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; NotYetMoving — reset NPC animation counter and refresh image index.
|
||||
; Pret ref: engine/overworld/movement.asm:NotYetMoving.
|
||||
; Called whenever the NPC's visual state needs refreshing but position doesn't change.
|
||||
; In: ESI = slot byte offset. Clobbers AL.
|
||||
; ---------------------------------------------------------------------------
|
||||
NotYetMoving:
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_ANIMFRAMECOUNTER], 0
|
||||
call UpdateSpriteImage
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; UpdateSpriteInWalkingAnimation — advance one frame of NPC pixel-step walk.
|
||||
; Pret ref: engine/overworld/movement.asm:UpdateSpriteInWalkingAnimation.
|
||||
;
|
||||
; Per-frame: advance anim counters (Func_5274); YPIXELS+=YSTEP; XPIXELS+=XSTEP;
|
||||
; decrement WALKANIMCOUNTER. When counter reaches 0:
|
||||
; WALK/STAY → random delay (0–127) + status=2; clear step vectors.
|
||||
; scripted → status=1 (ready for next scripted step).
|
||||
;
|
||||
; In: ESI = slot byte offset (H_CURRENT_SPRITE_OFFSET set by outer loop).
|
||||
; Clobbers AL, DL (from Func_5274).
|
||||
; ---------------------------------------------------------------------------
|
||||
UpdateSpriteInWalkingAnimation:
|
||||
call Func_5274 ; advance intra-anim and anim-frame counters
|
||||
|
||||
; YPIXELS += YSTEPVECTOR
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_YSTEPVECTOR]
|
||||
add [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_YPIXELS], al
|
||||
|
||||
; XPIXELS += XSTEPVECTOR
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_XSTEPVECTOR]
|
||||
add [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_XPIXELS], al
|
||||
|
||||
; Decrement walk animation counter; if still > 0, animation continues
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_WALKANIMCOUNTER]
|
||||
dec al
|
||||
mov [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_WALKANIMCOUNTER], al
|
||||
jnz .animRunning
|
||||
|
||||
; Walk finished: check if random (WALK/STAY) or scripted
|
||||
mov al, [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MOVEMENTBYTE1]
|
||||
cmp al, WALK
|
||||
jnc .initNextCounter ; WALK or STAY → random inter-walk delay
|
||||
|
||||
; Scripted: immediately ready for next scripted step
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_MOVEMENTSTATUS], 1
|
||||
ret
|
||||
|
||||
.initNextCounter:
|
||||
call Random ; AL = H_RANDOM_ADD
|
||||
and al, 0x7F ; random 0–127 frames
|
||||
mov [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_MOVEMENTDELAY], al
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_MOVEMENTSTATUS], 2
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_YSTEPVECTOR], 0
|
||||
mov byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_XSTEPVECTOR], 0
|
||||
ret
|
||||
|
||||
.animRunning:
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; InitializeSpriteStatus — first-frame NPC init.
|
||||
; Pret ref: engine/overworld/movement.asm:InitializeSpriteStatus.
|
||||
|
||||
@@ -74,7 +74,7 @@ global LoadCurrentMapView
|
||||
global CopyMapViewToVRAM
|
||||
global OverworldLoop
|
||||
global AdvancePlayerSprite
|
||||
global AdvancePlayerSprite
|
||||
global IsTilePassable
|
||||
global CheckWarpTile
|
||||
global LoadWarpDestination
|
||||
global PlayerStepOutFromDoor
|
||||
|
||||
@@ -135,6 +135,18 @@ PrepareOAMData:
|
||||
imul eax, 16
|
||||
add eax, 96
|
||||
mov [dos_base_x_tmp], eax
|
||||
; NPC walk interpolation: if MOVEMENTSTATUS=3 (walking), Func_5349 already advanced
|
||||
; MAPY/MAPX to the destination at walk start. Subtract YSTEP*WALKANIMCOUNTER
|
||||
; to interpolate between source and destination over the 16-frame animation.
|
||||
cmp byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_MOVEMENTSTATUS], 3
|
||||
jne .dos_base_done
|
||||
movzx eax, byte [ebp + esi + W_SPRITE_STATE_DATA_2 + SPRITESTATEDATA2_WALKANIMCOUNTER]
|
||||
movsx ecx, byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_YSTEPVECTOR]
|
||||
imul ecx, eax
|
||||
sub [dos_base_y_tmp], ecx
|
||||
movsx ecx, byte [ebp + esi + W_SPRITE_STATE_DATA_1 + SPRITESTATEDATA1_XSTEPVECTOR]
|
||||
imul ecx, eax
|
||||
sub [dos_base_x_tmp], ecx
|
||||
.dos_base_done:
|
||||
; Sub-block walk tracking: subtract the player's current walk pixel offset from
|
||||
; NPC dos_base so NPCs scroll in lockstep with the BG during a walk step.
|
||||
|
||||
@@ -98,6 +98,7 @@ update_oam:
|
||||
; ---------------------------------------------------------------------------
|
||||
commit_shadow_regs:
|
||||
push eax
|
||||
inc byte [ebp + IO_DIV] ; advance emulated DIV counter (~16384 Hz on GB; 1/frame is enough for RNG entropy)
|
||||
mov al, [ebp + H_SCX]
|
||||
mov [ebp + IO_SCX], al
|
||||
mov al, [ebp + H_SCY]
|
||||
|
||||
Reference in New Issue
Block a user