EBLAN_ENGINE/include/engine_entity.inc
Игорь Федосеев aa64510def feat: stabilize fixed-tick simulation
Decouple gameplay from presentation and preserve signed movement fractions. Separate cinematic camera ownership with safe body handoff and transactional current-room restarts.

Resample input latches on focus regain, discard modal timing debt, and attach cinematic lighting to the view to match editor preview.

Verify editor 137/137 and ASM 288/288, cinematic traces, RGB bake and IPC framebuffer parity without changing project assets.
2026-10-31 22:31:20 +03:00

432 lines
10 KiB
PHP

; ==============================================================================
; EBLAN ENGINE - Entity & Gameplay Behavior System
; ==============================================================================
.code
ShowGameToast PROTO :DWORD, :DWORD, :DWORD
Game_OnPlayerInteract PROTO :DWORD, :DWORD
RestartCurrentRoom PROTO
; ------------------------------------------------------------------------------
; InitEntities: Initialize entity runtime state from spriteList flags/defaults
; ------------------------------------------------------------------------------
InitEntities PROC uses eax ebx ecx edx esi edi
; 1. Clear runtime arrays
xor eax, eax
mov ecx, MAX_SPRITES
mov edi, OFFSET entityType
rep stosd
mov ecx, MAX_SPRITES
mov eax, 100
mov edi, OFFSET entityHealth
rep stosd
xor eax, eax
mov ecx, MAX_SPRITES
mov edi, OFFSET entityState
rep stosd
mov ecx, MAX_SPRITES
mov edi, OFFSET entityTimer
rep stosd
mov ecx, MAX_SPRITES
mov eax, 2
mov edi, OFFSET entitySpeed
rep stosd
mov ecx, MAX_SPRITES
mov eax, 384
mov edi, OFFSET entityAggro
rep stosd
; 2. Iterate spriteList and configure entity archetypes
xor ecx, ecx
init_spr_loop:
cmp ecx, spriteCount
jge init_spr_done
mov ebx, ecx
imul ebx, sizeof SpriteStruct
add ebx, OFFSET spriteList
mov eax, [ebx].SpriteStruct.flags
test eax, eax
jz fallback_archetype
; Parse flags:
; Byte 0: Entity Type (0=Prop, 1=Guard, 2=Medkit, 3=Ammo, 4=Key, 5=Switch)
; Byte 1: Health (if Guard)
; Byte 2: Speed (1..8)
; Byte 3: Aggro multiplier (1..15 -> cells * 64)
mov edx, eax
and edx, 0FFh
mov [entityType + ecx*4], edx
mov edx, eax
shr edx, 8
and edx, 0FFh
test edx, edx
jz @F
mov [entityHealth + ecx*4], edx
@@:
mov edx, eax
shr edx, 16
and edx, 0FFh
test edx, edx
jz @F
mov [entitySpeed + ecx*4], edx
@@:
mov edx, eax
shr edx, 24
and edx, 0FFh
test edx, edx
jz next_spr_slot
shl edx, 6 ; cells -> raw units
mov [entityAggro + ecx*4], edx
jmp next_spr_slot
fallback_archetype:
; If flags == 0, infer archetype from textureId:
; Slot 11 = Explosive Barrel, 13 = Guard, 14 = Medkit, 15 = Ammo
mov edx, [ebx].SpriteStruct.textureId
cmp edx, 11
jne check_guard_slot
mov [entityType + ecx*4], ENTITY_TYPE_BARREL
jmp next_spr_slot
check_guard_slot:
cmp edx, 13
jne check_medkit_slot
mov [entityType + ecx*4], ENTITY_TYPE_GUARD
mov [entityHealth + ecx*4], 100
mov [entitySpeed + ecx*4], 2
jmp next_spr_slot
check_medkit_slot:
cmp edx, 14
jne check_ammo_slot
mov [entityType + ecx*4], ENTITY_TYPE_MEDKIT
jmp next_spr_slot
check_ammo_slot:
cmp edx, 15
jne next_spr_slot
mov [entityType + ecx*4], ENTITY_TYPE_AMMO
next_spr_slot:
inc ecx
jmp init_spr_loop
init_spr_done:
ret
InitEntities ENDP
; ------------------------------------------------------------------------------
; UpdateEntities: Updates AI, pickups, collision and interactions each tick
; ------------------------------------------------------------------------------
UpdateEntities PROC uses ebx ecx edx esi edi
LOCAL i:DWORD
LOCAL sprX:DWORD, sprY:DWORD
LOCAL deltaX:DWORD, deltaY:DWORD
LOCAL absDX:DWORD, absDY:DWORD
LOCAL distSq:DWORD
LOCAL speed:DWORD
LOCAL stepX:DWORD, stepY:DWORD
LOCAL cellX:DWORD, cellY:DWORD
; If player died and pressed Restart (R)
cmp isPlayerDead, 0
je entities_active
cmp keyRestart, 0
je entities_done
call RestartCurrentRoom
ret
entities_active:
call IsGameplayBlocked
test eax, eax
jnz entities_done
mov i, 0
entity_loop:
call IsGameplayBlocked
test eax, eax
jnz entities_done
mov eax, i
cmp eax, spriteCount
jge entities_done
mov ebx, i
imul ebx, sizeof SpriteStruct
add ebx, OFFSET spriteList
; If sprite is inactive (textureId == 0), skip
mov eax, [ebx].SpriteStruct.textureId
test eax, eax
jz next_entity
mov eax, [ebx].SpriteStruct.x
mov sprX, eax
mov eax, [ebx].SpriteStruct.y
mov sprY, eax
; Distance to player
mov eax, playerX
sub eax, sprX
mov deltaX, eax
cdq
xor eax, edx
sub eax, edx
mov absDX, eax
mov eax, playerY
sub eax, sprY
mov deltaY, eax
cdq
xor eax, edx
sub eax, edx
mov absDY, eax
; distSq = deltaX^2 + deltaY^2
mov eax, deltaX
imul eax, eax
mov ecx, eax
mov eax, deltaY
imul eax, eax
add eax, ecx
mov distSq, eax
mov ecx, i
mov edx, [entityType + ecx*4]
; --------------------------------------------------------------------------
; Medkit Pickup (+25 HP)
; --------------------------------------------------------------------------
cmp edx, ENTITY_TYPE_MEDKIT
jne check_ammo_pickup
cmp absDX, 32
jge next_entity
cmp absDY, 32
jge next_entity
mov eax, playerHealth
cmp eax, playerMaxHealth
jge next_entity
add eax, 25
cmp eax, playerMaxHealth
jle @F
mov eax, playerMaxHealth
@@:
mov playerHealth, eax
mov [ebx].SpriteStruct.textureId, 0 ; Hide item
mov ecx, i
mov [entityType + ecx*4], ENTITY_TYPE_PROP
add playerScore, 25
invoke ShowGameToast, OFFSET szMedkitPickedToast, 75, COLOR_GREEN
jmp next_entity
; --------------------------------------------------------------------------
; Ammo Pickup (+20 Ammo)
; --------------------------------------------------------------------------
check_ammo_pickup:
cmp edx, ENTITY_TYPE_AMMO
jne check_key_pickup
cmp absDX, 32
jge next_entity
cmp absDY, 32
jge next_entity
mov eax, playerAmmo
add eax, 20
cmp eax, playerMaxAmmo
jle @F
mov eax, playerMaxAmmo
@@:
mov playerAmmo, eax
mov [ebx].SpriteStruct.textureId, 0
mov ecx, i
mov [entityType + ecx*4], ENTITY_TYPE_PROP
add playerScore, 10
invoke ShowGameToast, OFFSET szAmmoPickedToast, 75, COLOR_YELLOW
jmp next_entity
; --------------------------------------------------------------------------
; Key Pickup (+1 Key)
; --------------------------------------------------------------------------
check_key_pickup:
cmp edx, ENTITY_TYPE_KEY
jne check_switch_entity
cmp absDX, 32
jge next_entity
cmp absDY, 32
jge next_entity
inc playerKeys
mov [ebx].SpriteStruct.textureId, 0
mov ecx, i
mov [entityType + ecx*4], ENTITY_TYPE_PROP
add playerScore, 50
invoke ShowGameToast, OFFSET szKeyPickedToast, 90, COLOR_CYAN
jmp next_entity
; --------------------------------------------------------------------------
; Switch Trigger (Toggle on [E])
; --------------------------------------------------------------------------
check_switch_entity:
cmp edx, ENTITY_TYPE_SWITCH
jne check_guard_entity
cmp absDX, 48
jge next_entity
cmp absDY, 48
jge next_entity
cmp keyAction, 0
je next_entity
mov keyAction, 0
; Check if handled by user gameplay script hook
invoke Game_OnPlayerInteract, i, edx
test eax, eax
jnz next_entity
; Default switch behavior: toggle state
mov ecx, i
mov eax, [entityState + ecx*4]
xor eax, 1
mov [entityState + ecx*4], eax
test eax, eax
jz switch_off
invoke ShowGameToast, OFFSET szSwitchOnToast, 60, COLOR_GREEN
jmp next_entity
switch_off:
invoke ShowGameToast, OFFSET szSwitchOffToast, 60, COLOR_YELLOW
jmp next_entity
; --------------------------------------------------------------------------
; Guard Enemy AI
; --------------------------------------------------------------------------
check_guard_entity:
cmp edx, ENTITY_TYPE_GUARD
jne next_entity
mov ecx, i
mov eax, [entityState + ecx*4]
cmp eax, ENTITY_STATE_DEAD
jne guard_alive
; Dead guard: tick timer until disappear
inc DWORD PTR [entityTimer + ecx*4]
cmp DWORD PTR [entityTimer + ecx*4], 120
jl next_entity
mov [ebx].SpriteStruct.textureId, 0
jmp next_entity
guard_alive:
; Check aggro radius
mov eax, [entityAggro + ecx*4]
imul eax, eax ; aggroSq
cmp distSq, eax
jg next_entity
; Guard is alert & chasing player
mov ecx, i
mov [entityState + ecx*4], ENTITY_STATE_CHASE
mov eax, [entitySpeed + ecx*4]
mov speed, eax
; Calculate movement direction
mov stepX, 0
mov stepY, 0
cmp deltaX, 16
jle check_neg_x
mov eax, speed
mov stepX, eax
jmp check_step_y
check_neg_x:
cmp deltaX, -16
jge check_step_y
mov eax, speed
neg eax
mov stepX, eax
check_step_y:
cmp deltaY, 16
jle check_neg_y
mov eax, speed
mov stepY, eax
jmp apply_guard_step
check_neg_y:
cmp deltaY, -16
jge apply_guard_step
mov eax, speed
neg eax
mov stepY, eax
apply_guard_step:
; Try stepping X
cmp stepX, 0
je try_step_y
mov eax, sprX
add eax, stepX
sar eax, 6
mov cellX, eax
mov eax, sprY
sar eax, 6
mov cellY, eax
invoke GetMapCell, cellX, cellY
invoke IsSolidCell, eax
test eax, eax
jnz try_step_y
mov eax, stepX
add [ebx].SpriteStruct.x, eax
mov eax, [ebx].SpriteStruct.x
mov sprX, eax
try_step_y:
cmp stepY, 0
je check_guard_attack
mov eax, sprX
sar eax, 6
mov cellX, eax
mov eax, sprY
add eax, stepY
sar eax, 6
mov cellY, eax
invoke GetMapCell, cellX, cellY
invoke IsSolidCell, eax
test eax, eax
jnz check_guard_attack
mov eax, stepY
add [ebx].SpriteStruct.y, eax
check_guard_attack:
; Attack range check: within 48 raw units
cmp distSq, 2304 ; 48 * 48
jg next_entity
mov ecx, i
inc DWORD PTR [entityTimer + ecx*4]
cmp DWORD PTR [entityTimer + ecx*4], 35
jl next_entity
mov DWORD PTR [entityTimer + ecx*4], 0
; Guard hits player!
mov eax, playerHealth
sub eax, 8
jns @F
xor eax, eax
@@:
mov playerHealth, eax
mov playerHitFlash, 6
test eax, eax
jnz next_entity
mov isPlayerDead, 1
; Nothing else may collect items or attack after a lethal hit this tick.
jmp entities_done
next_entity:
inc i
jmp entity_loop
entities_done:
ret
UpdateEntities ENDP