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.
432 lines
10 KiB
PHP
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
|