feat: add gameplay scripting subsystem, floating toasts, dialogue modals, and IDE quest templates

This commit is contained in:
Artvasil09 2026-10-23 08:44:37 +03:00
parent 5c568b8cba
commit 25bd6377a7
12 changed files with 373 additions and 1 deletions

View file

@ -248,3 +248,14 @@ EblanStudio has evolved from a pure level editor into a **Full Game Engine Studi
- Canvas Zoom buttons (`[-]`, `[100%]`, `[+]`) and pan controls.
- Project configuration file (`project.eblanproj`) defining title, author, version, window title, and startup map.
- "Экспорт автономной игры (.exe)" builds a standalone release folder containing the renamed game executable, packaged assets (maps, textures, sounds), launcher batch file, and readme.
5. **Gameplay Scripting Subsystem & Interactive Modals (`include/engine_gameplay.inc`)**:
- `ShowGameToast(pszText, durationTicks, color)`: floating framed notification banners (auto-centered, custom CP1251 string, colored outline, auto-dismiss).
- `ShowGameDialog(pszSpeaker, pszText)`: modal dialogue & lore note reader freezing player movement until dismissed with `[E]` / `[ENTER]`.
- Extensible engine lifecycle hooks:
- `Game_OnTick`: 60 Hz frame tick hook for custom game logic, timers, and puzzle triggers.
- `Game_OnPlayerInteract(entityIdx, entityType)`: intercepts `[E]` interactions before default behavior.
- `Game_OnEntityKilled(entityIdx, entityType)`: triggers on guard death or barrel explosion for boss encounters.
- `Game_OnCustomHUD`: custom 2D canvas drawing (health bars, gauges, quest markers).
- `PlayGameSFX(pszRelativePath)`: safe asynchronous WinMM audio player.
- Built-in automatic gameplay toasts for keys, medkits, ammo, switches, doors, and explosions.

Binary file not shown.

View file

@ -18,6 +18,7 @@ struct CodeFileEntry {
};
static const CodeFileEntry g_ProjectCodeFiles[] = {
{ "engine_gameplay.inc (Events, Hooks & Dialogs)", "include/engine_gameplay.inc" },
{ "engine_combat.inc (Weapons & Combat)", "include/engine_combat.inc" },
{ "engine_entity.inc (Entities & AI)", "include/engine_entity.inc" },
{ "main.asm (Engine Entry & Frame Loop)", "main.asm" },
@ -37,6 +38,39 @@ struct ScriptTemplate {
};
static const ScriptTemplate g_ScriptTemplates[] = {
{
"NPC Диалог и квест (ShowGameDialog)",
"; ==============================================================================\n"
"; NPC Quest Dialogue: Displays conversation modal and gives objective\n"
"; ==============================================================================\n"
".data\n"
"szNpcSpeaker db \"СТАРИК\", 0\n"
"szNpcText db \"ВОЗЬМИ СИНИЙ КЛЮЧ И ОТКРОЙ ЗАПЕРТУЮ ДВЕРЬ НА СЕВЕРЕ!\", 0\n"
".code\n"
" invoke ShowGameDialog, OFFSET szNpcSpeaker, OFFSET szNpcText\n"
" invoke ShowGameToast, OFFSET szKeyPickedToast, 90, COLOR_CYAN\n"
},
{
"Записка / Терминал с лором (Lore Note)",
"; ==============================================================================\n"
"; Readable Note / Terminal: Player reads lore note on [E]\n"
"; ==============================================================================\n"
".data\n"
"szNoteTitle db \"ДНЕВНИК УЧЕНОГО\", 0\n"
"szNoteBody db \"ЭКСПЕРИМЕНТ ВЫШЕЛ ИЗ-ПОД КОНТРОЛЯ. КОД ОТ ЛИФТА: 4 9 2 1.\", 0\n"
".code\n"
" invoke ShowGameDialog, OFFSET szNoteTitle, OFFSET szNoteBody\n"
},
{
"Всплывающее уведомление (Game Toast)",
"; ==============================================================================\n"
"; Floating Toast Banner (auto-centered with colored outline and fade)\n"
"; ==============================================================================\n"
".data\n"
"szSecretToast db \"СЕКРЕТНАЯ КОМНАТА ОБНАРУЖЕНА!\", 0\n"
".code\n"
" invoke ShowGameToast, OFFSET szSecretToast, 120, COLOR_YELLOW\n"
},
{
"Патрулирующий охранник (Patrol Guard AI)",
"; ==============================================================================\n"
@ -110,6 +144,14 @@ struct EngineApiItem {
};
static const EngineApiItem g_EngineApiCatalog[] = {
{ "Геймплей и Скрипты", "ShowGameToast", "Всплывающий баннер с цветной рамкой и затуханием", "invoke ShowGameToast, OFFSET szMyMessage, 90, COLOR_YELLOW\n" },
{ "Геймплей и Скрипты", "ShowGameDialog", "Диалоговое окно NPC или чтение записки", "invoke ShowGameDialog, OFFSET szSpeaker, OFFSET szText\n" },
{ "Геймплей и Скрипты", "CloseGameDialog", "Принудительно закрыть активное диалоговое окно", "call CloseGameDialog\n" },
{ "Геймплей и Скрипты", "PlayGameSFX", "Асинхронное воспроизведение звука WAV", "invoke PlayGameSFX, OFFSET szMySoundWav\n" },
{ "Геймплей и Скрипты", "Game_OnPlayerInteract", "Хук взаимодействия [E] (1=обработано, 0=дефолт)", "Game_OnPlayerInteract PROC uses ebx ecx, entityIdx:DWORD, entityTypeVal:DWORD\n ; Ваша логика по нажатию [E]\n xor eax, eax\n ret\nGame_OnPlayerInteract ENDP\n" },
{ "Геймплей и Скрипты", "Game_OnEntityKilled", "Хук уничтожения врага/бочки (триггеры боссов)", "Game_OnEntityKilled PROC uses ebx ecx, entityIdx:DWORD, entityTypeVal:DWORD\n ; Смерть сущности\n ret\nGame_OnEntityKilled ENDP\n" },
{ "Геймплей и Скрипты", "Game_OnTick", "Покадровый хук 60 Hz (таймеры, зоны, квесты)", "Game_OnTick PROC uses ebx ecx\n ; Ваша покадровая логика\n ret\nGame_OnTick ENDP\n" },
{ "Бой и оружие", "UpdateCombat", "Обновляет прицел, стрельбу и расчет урона", "call UpdateCombat\n" },
{ "Бой и оружие", "RenderCombatHUD", "Рисует оружие, прицел и статус игрока", "call RenderCombatHUD\n" },
{ "Бой и оружие", "Смена оружия", "Устанавливает активное оружие игрока", "mov playerWeapon, WEAPON_SHOTGUN ; 0=Knife, 1=Pistol, 2=Shotgun\n" },

Binary file not shown.

View file

@ -9,6 +9,8 @@ DrawRect PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
DrawRectOutline PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
DrawString8x8 PROTO :DWORD, :DWORD, :DWORD, :DWORD
DrawNumber8x8 PROTO :DWORD, :DWORD, :DWORD, :DWORD
ShowGameToast PROTO :DWORD, :DWORD, :DWORD
Game_OnEntityKilled PROTO :DWORD, :DWORD
; ------------------------------------------------------------------------------
; UpdateCombat: Process weapon firing, hitscan raycasting, cooldowns & damage
@ -249,6 +251,7 @@ hitscan_eval:
mov [entityState + ecx*4], ENTITY_STATE_DEAD
mov DWORD PTR [entityTimer + ecx*4], 0
add playerScore, 100
invoke Game_OnEntityKilled, closestIdx, ENTITY_TYPE_GUARD
; Drop ammo reward
mov eax, playerAmmo
@ -271,6 +274,8 @@ explode_barrel:
; Fiery orange-red screen flash
mov playerHitFlash, 8
add playerScore, 200
invoke ShowGameToast, OFFSET szBarrelExplodedToast, 60, 00FFA020h
invoke Game_OnEntityKilled, closestIdx, ENTITY_TYPE_BARREL
; AOE damage to all surrounding guards within 128 units (2 cells)
push esi

View file

@ -240,3 +240,25 @@ entityState dd MAX_SPRITES dup(?)
entityTimer dd MAX_SPRITES dup(?)
entitySpeed dd MAX_SPRITES dup(?)
entityAggro dd MAX_SPRITES dup(?)
; Gameplay Variables & Built-in Toast Strings (CP1251)
.data
ALIGN 4
gameToastTimer dd 0
gameToastColor dd 0
gameToastText db 64 dup(0)
ALIGN 4
gameDialogActive dd 0
gameDialogSpeaker db 32 dup(0)
gameDialogText db 128 dup(0)
szDialogCloseHint db "[ENTER / E] - ЗАКРЫТЬ", 0
szKeyPickedToast db "ПОЛУЧЕН КЛЮЧ", 0
szMedkitPickedToast db "АПТЕЧКА +25 ЗДОРОВЬЯ", 0
szAmmoPickedToast db "БОЕПРИПАСЫ +20 ПАТРОНОВ", 0
szSwitchOnToast db "ПЕРЕКЛЮЧАТЕЛЬ: ВКЛ", 0
szSwitchOffToast db "ПЕРЕКЛЮЧАТЕЛЬ: ВЫКЛ", 0
szDoorOpenedToast db "ДВЕРЬ ОТКРЫТА", 0
szDoorClosedToast db "ДВЕРЬ ЗАКРЫТА", 0
szBarrelExplodedToast db "ВЗРЫВ БОЧКИ!", 0

View file

@ -4,6 +4,9 @@
.code
ShowGameToast PROTO :DWORD, :DWORD, :DWORD
Game_OnPlayerInteract PROTO :DWORD, :DWORD
; ------------------------------------------------------------------------------
; InitEntities: Initialize entity runtime state from spriteList flags/defaults
; ------------------------------------------------------------------------------
@ -214,6 +217,7 @@ entity_loop:
mov ecx, i
mov [entityType + ecx*4], ENTITY_TYPE_PROP
add playerScore, 25
invoke ShowGameToast, OFFSET szMedkitPickedToast, 75, COLOR_GREEN
jmp next_entity
; --------------------------------------------------------------------------
@ -237,6 +241,7 @@ check_ammo_pickup:
mov ecx, i
mov [entityType + ecx*4], ENTITY_TYPE_PROP
add playerScore, 10
invoke ShowGameToast, OFFSET szAmmoPickedToast, 75, COLOR_YELLOW
jmp next_entity
; --------------------------------------------------------------------------
@ -244,7 +249,7 @@ check_ammo_pickup:
; --------------------------------------------------------------------------
check_key_pickup:
cmp edx, ENTITY_TYPE_KEY
jne check_guard_entity
jne check_switch_entity
cmp absDX, 32
jge next_entity
cmp absDY, 32
@ -254,6 +259,39 @@ check_key_pickup:
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
; --------------------------------------------------------------------------

242
include/engine_gameplay.inc Normal file
View file

@ -0,0 +1,242 @@
; ==============================================================================
; EBLAN ENGINE - Gameplay Events, Scripting Hooks, HUD Toasts & Dialog Modals
; ==============================================================================
.code
DrawPixel PROTO :DWORD, :DWORD, :DWORD
DrawRect PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
DrawRectOutline PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
DrawString8x8 PROTO :DWORD, :DWORD, :DWORD, :DWORD
DrawNumber8x8 PROTO :DWORD, :DWORD, :DWORD, :DWORD
PlaySoundA PROTO :DWORD, :DWORD, :DWORD
; ------------------------------------------------------------------------------
; ShowGameToast: Displays a floating toast notification banner on screen
; ------------------------------------------------------------------------------
ShowGameToast PROC uses esi edi ecx eax, pszText:DWORD, duration:DWORD, textColor:DWORD
mov esi, pszText
test esi, esi
jz toast_skip_copy
mov edi, OFFSET gameToastText
mov ecx, 63
toast_copy_loop:
lodsb
stosb
test al, al
jz toast_copy_done
dec ecx
jnz toast_copy_loop
xor al, al
stosb
toast_copy_done:
toast_skip_copy:
mov eax, duration
mov gameToastTimer, eax
mov eax, textColor
mov gameToastColor, eax
ret
ShowGameToast ENDP
; ------------------------------------------------------------------------------
; RenderGameToast: Draws active toast notification at top center of screen
; ------------------------------------------------------------------------------
RenderGameToast PROC uses ebx ecx edx esi edi
cmp gameToastTimer, 0
jle rt_done
dec gameToastTimer
; 1. Calculate text character length
mov esi, OFFSET gameToastText
xor ecx, ecx
count_len:
mov al, [esi+ecx]
test al, al
jz count_done
inc ecx
cmp ecx, 63
jb count_len
count_done:
test ecx, ecx
jz rt_done
; Width in pixels = length * 8
mov eax, ecx
shl eax, 3
mov edx, eax
; Center X = (SCREEN_W - width) / 2
mov ebx, SCREEN_W
sub ebx, edx
sar ebx, 1
; Box coordinates: X = ebx - 6, Y = 16, W = edx + 12, H = 14
mov eax, ebx
sub eax, 6
mov esi, edx
add esi, 12
invoke DrawRect, eax, 16, esi, 14, 00101010h
invoke DrawRectOutline, eax, 16, esi, 14, gameToastColor
invoke DrawString8x8, ebx, 19, OFFSET gameToastText, gameToastColor
rt_done:
ret
RenderGameToast ENDP
; ------------------------------------------------------------------------------
; ShowGameDialog: Opens an interactive retro dialogue modal with speaker and text
; ------------------------------------------------------------------------------
ShowGameDialog PROC uses esi edi ecx eax, pszSpeaker:DWORD, pszText:DWORD
mov esi, pszSpeaker
mov edi, OFFSET gameDialogSpeaker
mov ecx, 31
test esi, esi
jz no_speaker
copy_spk:
lodsb
stosb
test al, al
jz spk_done
dec ecx
jnz copy_spk
xor al, al
stosb
spk_done:
jmp setup_dlg_text
no_speaker:
mov BYTE PTR [edi], 0
setup_dlg_text:
mov esi, pszText
mov edi, OFFSET gameDialogText
mov ecx, 127
test esi, esi
jz no_text
copy_txt:
lodsb
stosb
test al, al
jz txt_done
dec ecx
jnz copy_txt
xor al, al
stosb
txt_done:
jmp activate_dlg
no_text:
mov BYTE PTR [edi], 0
activate_dlg:
mov gameDialogActive, 1
ret
ShowGameDialog ENDP
; ------------------------------------------------------------------------------
; CloseGameDialog: Closes active dialogue box
; ------------------------------------------------------------------------------
CloseGameDialog PROC
mov gameDialogActive, 0
ret
CloseGameDialog ENDP
; ------------------------------------------------------------------------------
; RenderGameDialog: Draws active dialogue box at bottom of screen
; ------------------------------------------------------------------------------
RenderGameDialog PROC uses ebx ecx edx esi edi
cmp gameDialogActive, 0
je rd_done
; Dialog Box background: X = 16, Y = 170, W = 288, H = 42
invoke DrawRect, 16, 170, 288, 42, 00121212h
invoke DrawRectOutline, 16, 170, 288, 42, COLOR_CYAN
; Speaker name (if not empty)
cmp BYTE PTR [gameDialogSpeaker], 0
je draw_dlg_body
invoke DrawString8x8, 24, 174, OFFSET gameDialogSpeaker, COLOR_YELLOW
draw_dlg_body:
; Text
invoke DrawString8x8, 24, 186, OFFSET gameDialogText, COLOR_WHITE
; Close hint in bottom-right
invoke DrawString8x8, 140, 200, OFFSET szDialogCloseHint, COLOR_GRAY
; Check if player pressed Action key or Attack to dismiss
cmp keyAction, 0
jne do_dismiss
cmp keyAttack, 0
jne do_dismiss
jmp rd_done
do_dismiss:
mov keyAction, 0
mov keyAttack, 0
mov gameDialogActive, 0
rd_done:
ret
RenderGameDialog ENDP
; ------------------------------------------------------------------------------
; PlayGameSFX: Asynchronously plays a sound effect via WinMM PlaySoundA
; ------------------------------------------------------------------------------
PlayGameSFX PROC uses eax, pszRelativePath:DWORD
cmp isCutsceneActive, 0
jne sfx_done
mov eax, pszRelativePath
test eax, eax
jz sfx_done
cmp BYTE PTR [eax], 0
jz sfx_done
invoke PlaySoundA, eax, 0, 9 ; SND_FILENAME (1) | SND_ASYNC (8) = 9
sfx_done:
ret
PlayGameSFX ENDP
; ------------------------------------------------------------------------------
; Game_OnTick: Hook called every 60 Hz frame during active gameplay
; ------------------------------------------------------------------------------
Game_OnTick PROC uses eax ebx ecx edx
; Extensible hook for custom game logic (quests, timers, zones)
ret
Game_OnTick ENDP
; ------------------------------------------------------------------------------
; Game_OnPlayerInteract: Hook called when player presses [E] in range of an entity
; Returns: EAX = 1 if handled by custom script, 0 if default engine behavior
; ------------------------------------------------------------------------------
Game_OnPlayerInteract PROC uses ebx ecx edx esi edi, entityIdx:DWORD, entityTypeVal:DWORD
; Extensible hook for custom interactions
xor eax, eax
ret
Game_OnPlayerInteract ENDP
; ------------------------------------------------------------------------------
; Game_OnEntityKilled: Hook called when enemy guard or barrel is destroyed
; ------------------------------------------------------------------------------
Game_OnEntityKilled PROC uses eax ebx ecx edx, entityIdx:DWORD, entityTypeVal:DWORD
; Extensible hook for boss fights, doors opening on kill, score multipliers
ret
Game_OnEntityKilled ENDP
; ------------------------------------------------------------------------------
; Game_OnCustomHUD: Hook called during HUD rendering
; ------------------------------------------------------------------------------
Game_OnCustomHUD PROC uses eax ebx ecx edx
; Extensible hook for custom UI bars, quest markers, mini-dialogues
ret
Game_OnCustomHUD ENDP
; ------------------------------------------------------------------------------
; RenderGameHUDOverlays: Renders active toast banner, dialogue modal, and custom HUD
; ------------------------------------------------------------------------------
RenderGameHUDOverlays PROC
call RenderGameToast
call RenderGameDialog
call Game_OnCustomHUD
ret
RenderGameHUDOverlays ENDP

View file

@ -13,6 +13,7 @@ ValidateMapFilename PROTO :DWORD
ValidateStagedCutscene PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
CheckPortalTriggers PROTO
InitEntities PROTO
ShowGameToast PROTO :DWORD, :DWORD, :DWORD
; ------------------------------------------------------------------------------
; InitMap: Creates a rich test level with rooms, hallways, pillars & doors
@ -203,6 +204,7 @@ InteractDoor PROC uses ebx ecx edx esi edi
; Open the door!
invoke SetMapCell, targetX, targetY, CELL_DOOR_OPEN
invoke ShowGameToast, OFFSET szDoorOpenedToast, 50, COLOR_GREEN
ret
check_door_close:
@ -211,6 +213,7 @@ check_door_close:
; Close the door!
invoke SetMapCell, targetX, targetY, CELL_DOOR
invoke ShowGameToast, OFFSET szDoorClosedToast, 50, COLOR_YELLOW
ret
check_portal_interact:

View file

@ -115,6 +115,7 @@ include include/engine_entity.inc
include include/engine_raycaster.inc
include include/engine_render2d.inc
include include/engine_combat.inc
include include/engine_gameplay.inc
include include/engine_cutscene.inc
include include/engine_preview.inc
@ -254,6 +255,9 @@ hud_no_map_error:
; 5. Minimap Radar (Toggle with 'M')
call DrawMinimap
; 6. Gameplay HUD Overlays (Floating Toasts, Dialog Modals, Custom HUD)
call RenderGameHUDOverlays
ret
Engine_RenderHUD ENDP
@ -269,9 +273,13 @@ no_error_tick:
call PollInput
cmp isGamePaused, 0
jne @F
cmp gameDialogActive, 0
jne skip_movement_in_dialog
call UpdatePlayer
call UpdateEntities
call UpdateCombat
skip_movement_in_dialog:
call Game_OnTick
@@:
; 2. 3D Raycasting & 2D HUD Render

BIN
main.obj

Binary file not shown.

View file

@ -20,6 +20,7 @@ include include/engine_entity.inc
include include/engine_raycaster.inc
include include/engine_render2d.inc
include include/engine_combat.inc
include include/engine_gameplay.inc
include include/engine_cutscene.inc
include startup_parser.inc
include project_paths.inc