Preserve stage-1 stability fixes alongside v5 lighting, IPC v2, weapons and studio updates. Fix barrel AOE pointer corruption and extend modal gameplay gating. Verify 137 editor checks, 191 ASM checks, lighting bake and preview parity. Document stage-2 scope without implementing it.
434 lines
10 KiB
PHP
434 lines
10 KiB
PHP
; ==============================================================================
|
|
; EBLAN ENGINE - Entity & Gameplay Behavior System
|
|
; ==============================================================================
|
|
|
|
.code
|
|
|
|
ShowGameToast PROTO :DWORD, :DWORD, :DWORD
|
|
Game_OnPlayerInteract PROTO :DWORD, :DWORD
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; 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
|
|
; Respawn player
|
|
mov playerHealth, 100
|
|
mov playerAmmo, 50
|
|
mov isPlayerDead, 0
|
|
mov playerHitFlash, 0
|
|
call LoadMapBinary
|
|
call InitEntities
|
|
ret
|
|
|
|
entities_active:
|
|
call IsGameplayBlocked
|
|
test eax, eax
|
|
jnz entities_done
|
|
mov i, 0
|
|
|
|
entity_loop:
|
|
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
|