feat: add peaceful adventure mode, clean immersive HUD, and genre toggle

This commit is contained in:
Artvasil09 2026-10-23 09:07:57 +03:00
parent 25bd6377a7
commit 341966debe
14 changed files with 473 additions and 80 deletions

View file

@ -259,3 +259,16 @@ EblanStudio has evolved from a pure level editor into a **Full Game Engine Studi
- `Game_OnCustomHUD`: custom 2D canvas drawing (health bars, gauges, quest markers). - `Game_OnCustomHUD`: custom 2D canvas drawing (health bars, gauges, quest markers).
- `PlayGameSFX(pszRelativePath)`: safe asynchronous WinMM audio player. - `PlayGameSFX(pszRelativePath)`: safe asynchronous WinMM audio player.
- Built-in automatic gameplay toasts for keys, medkits, ammo, switches, doors, and explosions. - Built-in automatic gameplay toasts for keys, medkits, ammo, switches, doors, and explosions.
6. **Game Genres, Peaceful Adventure Mode & Immersive Clean HUD**:
- `GAME_MODE_ADVENTURE (0)`: pure exploration / quest / horror / puzzle mode without weapons, ammo, crosshair, or combat overlays.
- `GAME_MODE_ACTION (1)`: full combat shooter mode with weapon models, crosshair, health bar, and ammo counter.
- `HUD_STYLE_CLEAN (0)`: 100% immersive full-screen view (no debug banners, no text collisions, no bottom controls clutter).
- `HUD_STYLE_DEBUG (1)`: developer mode displaying FPS counter at top-right (X=250), engine title, score, and controls hints.
- Instant in-game switching:
- `[ESC] -> [2]`: toggle weapons / peaceful mode on the fly with toast notifications.
- `[ESC] -> [3]`: toggle clean vs debug HUD.
- `[F1]` or `[H]`: quick hotkey to toggle clean HUD vs debug HUD.
- Editor toolbar 1-click genre switcher: `☮ КВЕСТ / БЕЗ ОРУЖИЯ` vs `⚔ ЭКШЕН / С ОРУЖИЕМ`.
- Project settings (`project.eblanproj`) persist `GameMode`, `ShowWeapons`, and `ShowCrosshair`.

Binary file not shown.

View file

@ -1,22 +1,22 @@
[Window][Workspace] [Window][Workspace]
Pos=0,60 Pos=0,60
Size=2560,1292 Size=2560,1292
LastUsed=20261022 LastUsed=20261023
[Window][Debug##Default] [Window][Debug##Default]
Pos=60,60 Pos=60,60
Size=400,400 Size=400,400
LastUsed=20261022 LastUsed=20261023
[Window][Toolbar] [Window][Toolbar]
Pos=0,20 Pos=0,20
Size=2560,38 Size=2560,38
LastUsed=20261022 LastUsed=20261023
[Window][StatusBar] [Window][StatusBar]
Pos=0,1352 Pos=0,1352
Size=2560,32 Size=2560,32
LastUsed=20261022 LastUsed=20261023
[Window][Не удалось запустить игру] [Window][Не удалось запустить игру]
Pos=649,356 Pos=649,356
@ -29,7 +29,12 @@ Size=900,650
LastUsed=20261022 LastUsed=20261022
[Window][Менеджер ассетов (Texture & Audio Manager)] [Window][Менеджер ассетов (Texture & Audio Manager)]
Pos=60,60 Pos=61,60
Size=820,580 Size=820,580
LastUsed=20261022 LastUsed=20261023
[Window][Редактор кода MASM32 & Игровых скриптов (F7)]
Pos=60,60
Size=1020,680
LastUsed=20261023

View file

@ -255,6 +255,12 @@ bool EditorApp::PreparePlaytest(std::string& startMap, std::string& enginePath)
if (ec) return copyFailure(); if (ec) return copyFailure();
if (fs::is_directory(sounds, ec)) fs::copy(sounds, sandbox / "assets/sounds", fs::copy_options::recursive, ec); if (fs::is_directory(sounds, ec)) fs::copy(sounds, sandbox / "assets/sounds", fs::copy_options::recursive, ec);
if (ec) return copyFailure(); if (ec) return copyFailure();
char modulePath[MAX_PATH]{};
GetModuleFileNameA(nullptr, modulePath, MAX_PATH);
fs::path projFile = fs::path(modulePath).parent_path() / "project.eblanproj";
if (fs::exists(projFile, ec)) {
fs::copy_file(projFile, sandbox / "project.eblanproj", fs::copy_options::overwrite_existing, ec);
}
// Validate all reachable maps and destination spawns, including reverse links. // Validate all reachable maps and destination spawns, including reverse links.
std::vector<std::string> pending{source.filename().string()}; std::vector<std::string> pending{source.filename().string()};
std::set<std::string> visited; std::set<std::string> visited;
@ -1617,6 +1623,42 @@ void EditorApp::RenderToolbar() {
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine(); ImGui::SameLine();
// Game Mode Quick Selector (Peaceful Adventure vs Action Shooter)
if (project && project->gameMode == 0) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.12f, 0.45f, 0.40f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.18f, 0.58f, 0.50f, 1.0f));
if (ImGui::Button(" ☮ КВЕСТ / БЕЗ ОРУЖИЯ ")) {
project->gameMode = 1;
project->showWeapons = true;
char module[MAX_PATH]{};
GetModuleFileNameA(nullptr, module, MAX_PATH);
std::string projPath = (fs::path(module).parent_path() / "project.eblanproj").string();
project->Save(projPath);
statusMessage = "Режим игры: ЭКШЕН / ШУТЕР (с оружием и стрельбой)";
}
ImGui::PopStyleColor(2);
} else if (project) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.55f, 0.30f, 0.10f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.70f, 0.38f, 0.12f, 1.0f));
if (ImGui::Button(" ⚔ ЭКШЕН / С ОРУЖИЕМ ")) {
project->gameMode = 0;
project->showWeapons = false;
char module[MAX_PATH]{};
GetModuleFileNameA(nullptr, module, MAX_PATH);
std::string projPath = (fs::path(module).parent_path() / "project.eblanproj").string();
project->Save(projPath);
statusMessage = "Режим игры: КВЕСТ / МИРНЫЙ (без оружия)";
}
ImGui::PopStyleColor(2);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Нажмите для быстрого переключения жанра игры (Квест без оружия / Шутер с оружием)");
}
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
// Tools // Tools
auto ToolButton = [&](const char* label, ToolMode mode) { auto ToolButton = [&](const char* label, ToolMode mode) {
bool active = (currentTool == mode); bool active = (currentTool == mode);
@ -2292,12 +2334,18 @@ void EditorApp::RenderProjectSettingsModal() {
static char authorBuf[128] = ""; static char authorBuf[128] = "";
static char versionBuf[32] = ""; static char versionBuf[32] = "";
static char windowTitleBuf[128] = ""; static char windowTitleBuf[128] = "";
static int modeChoice = 1;
static bool showWeap = true;
static bool showCross = true;
static bool init = true; static bool init = true;
if (init) { if (init) {
strncpy_s(titleBuf, project->title.c_str(), sizeof(titleBuf) - 1); strncpy_s(titleBuf, project->title.c_str(), sizeof(titleBuf) - 1);
strncpy_s(authorBuf, project->author.c_str(), sizeof(authorBuf) - 1); strncpy_s(authorBuf, project->author.c_str(), sizeof(authorBuf) - 1);
strncpy_s(versionBuf, project->version.c_str(), sizeof(versionBuf) - 1); strncpy_s(versionBuf, project->version.c_str(), sizeof(versionBuf) - 1);
strncpy_s(windowTitleBuf, project->windowTitle.c_str(), sizeof(windowTitleBuf) - 1); strncpy_s(windowTitleBuf, project->windowTitle.c_str(), sizeof(windowTitleBuf) - 1);
modeChoice = project->gameMode;
showWeap = project->showWeapons;
showCross = project->showCrosshair;
init = false; init = false;
} }
@ -2309,6 +2357,20 @@ void EditorApp::RenderProjectSettingsModal() {
ImGui::InputText("Версия", versionBuf, sizeof(versionBuf)); ImGui::InputText("Версия", versionBuf, sizeof(versionBuf));
ImGui::InputText("Заголовок окна", windowTitleBuf, sizeof(windowTitleBuf)); ImGui::InputText("Заголовок окна", windowTitleBuf, sizeof(windowTitleBuf));
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), "Жанр и режим геймплея:");
ImGui::RadioButton("Квест / Приключение / Хоррор (Мирный режим, без оружия)", &modeChoice, 0);
ImGui::RadioButton("Экшен / Шутер (С оружием, патронами и перестрелками)", &modeChoice, 1);
if (modeChoice == 1) {
ImGui::Checkbox("Отображать оружие от 1-го лица", &showWeap);
ImGui::Checkbox("Отображать перекрестие прицела", &showCross);
} else {
showWeap = false;
showCross = false;
ImGui::TextDisabled("В мирном режиме оружие, прицел и счетчик патронов скрыты.");
}
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("Стартовая карта: %s", project->startupMap.c_str()); ImGui::Text("Стартовая карта: %s", project->startupMap.c_str());
@ -2318,6 +2380,9 @@ void EditorApp::RenderProjectSettingsModal() {
project->author = authorBuf; project->author = authorBuf;
project->version = versionBuf; project->version = versionBuf;
project->windowTitle = windowTitleBuf; project->windowTitle = windowTitleBuf;
project->gameMode = modeChoice;
project->showWeapons = showWeap;
project->showCrosshair = showCross;
char module[MAX_PATH]{}; char module[MAX_PATH]{};
GetModuleFileNameA(nullptr, module, MAX_PATH); GetModuleFileNameA(nullptr, module, MAX_PATH);

View file

@ -16,6 +16,9 @@ struct EblanProject {
std::string windowTitle = "EBLAN ENGINE 3D - Standalone Retro Game"; std::string windowTitle = "EBLAN ENGINE 3D - Standalone Retro Game";
int defaultWidth = 1280; int defaultWidth = 1280;
int defaultHeight = 960; int defaultHeight = 960;
int gameMode = 1; // 0 = Adventure (No weapons / Peaceful), 1 = Action (Weapons)
bool showWeapons = true;
bool showCrosshair = true;
bool Save(const std::string& path) const { bool Save(const std::string& path) const {
HANDLE hFile = CreateFileA(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); HANDLE hFile = CreateFileA(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
@ -31,6 +34,10 @@ struct EblanProject {
content += std::string("DefaultWidth=") + numBuf + "\r\n"; content += std::string("DefaultWidth=") + numBuf + "\r\n";
snprintf(numBuf, sizeof(numBuf), "%d", defaultHeight); snprintf(numBuf, sizeof(numBuf), "%d", defaultHeight);
content += std::string("DefaultHeight=") + numBuf + "\r\n"; content += std::string("DefaultHeight=") + numBuf + "\r\n";
snprintf(numBuf, sizeof(numBuf), "%d", gameMode);
content += std::string("GameMode=") + numBuf + "\r\n";
content += std::string("ShowWeapons=") + (showWeapons ? "1" : "0") + "\r\n";
content += std::string("ShowCrosshair=") + (showCrosshair ? "1" : "0") + "\r\n";
DWORD written = 0; DWORD written = 0;
WriteFile(hFile, content.data(), static_cast<DWORD>(content.size()), &written, nullptr); WriteFile(hFile, content.data(), static_cast<DWORD>(content.size()), &written, nullptr);
CloseHandle(hFile); CloseHandle(hFile);
@ -65,6 +72,9 @@ struct EblanProject {
else if (key == "WindowTitle") windowTitle = val; else if (key == "WindowTitle") windowTitle = val;
else if (key == "DefaultWidth") defaultWidth = std::atoi(val.c_str()); else if (key == "DefaultWidth") defaultWidth = std::atoi(val.c_str());
else if (key == "DefaultHeight") defaultHeight = std::atoi(val.c_str()); else if (key == "DefaultHeight") defaultHeight = std::atoi(val.c_str());
else if (key == "GameMode") gameMode = std::atoi(val.c_str());
else if (key == "ShowWeapons") showWeapons = (val == "1" || val == "true");
else if (key == "ShowCrosshair") showCrosshair = (val == "1" || val == "true");
} }
} }

Binary file not shown.

View file

@ -37,11 +37,17 @@ UpdateCombat PROC uses ebx ecx edx esi edi
dec playerHitFlash dec playerHitFlash
@@: @@:
; If player is dead or game is paused, cannot attack ; If player is dead, game is paused, or in peaceful adventure mode, cannot attack
cmp isPlayerDead, 0 cmp isPlayerDead, 0
jne combat_done jne combat_done
cmp isGamePaused, 0 cmp isGamePaused, 0
jne combat_done jne combat_done
cmp gameMode, GAME_MODE_ADVENTURE
je combat_done
cmp enableWeapons, 0
je combat_done
cmp playerWeapon, WEAPON_NONE
je combat_done
; 2. Check attack input ; 2. Check attack input
cmp keyAttack, 0 cmp keyAttack, 0
@ -383,10 +389,21 @@ UpdateCombat ENDP
RenderCombatHUD PROC uses ebx ecx edx esi edi RenderCombatHUD PROC uses ebx ecx edx esi edi
LOCAL barW:DWORD, gunY:DWORD LOCAL barW:DWORD, gunY:DWORD
; If in adventure mode, unarmed, or weapons disabled, skip weapon & combat stats
cmp gameMode, GAME_MODE_ADVENTURE
je hud_damage_flash
cmp enableWeapons, 0
je hud_damage_flash
cmp playerWeapon, WEAPON_NONE
je hud_damage_flash
; 1. Center Crosshair (subtle cross at 160, 120) ; 1. Center Crosshair (subtle cross at 160, 120)
cmp showCrosshair, 0
je skip_crosshair
invoke DrawRect, 156, 120, 9, 1, 00FFFFFFh invoke DrawRect, 156, 120, 9, 1, 00FFFFFFh
invoke DrawRect, 160, 116, 1, 9, 00FFFFFFh invoke DrawRect, 160, 116, 1, 9, 00FFFFFFh
invoke DrawRect, 160, 120, 1, 1, 0000FF00h invoke DrawRect, 160, 120, 1, 1, 0000FF00h
skip_crosshair:
; 2. Render Active Weapon at bottom center ; 2. Render Active Weapon at bottom center
cmp playerWeapon, WEAPON_KNIFE cmp playerWeapon, WEAPON_KNIFE
@ -470,15 +487,15 @@ draw_pistol_barrel:
invoke DrawRect, 152, eax, 17, 22, 00453020h ; brown wood grip plates invoke DrawRect, 152, eax, 17, 22, 00453020h ; brown wood grip plates
hud_stats_draw: hud_stats_draw:
; 3. Health Bar ; 3. Health Bar at top-left: X=8, Y=8
invoke DrawString8x8, 8, 42, OFFSET strHpLabel, COLOR_GREEN invoke DrawString8x8, 8, 8, OFFSET strHpLabel, COLOR_GREEN
invoke DrawNumber8x8, 34, 42, playerHealth, COLOR_WHITE invoke DrawNumber8x8, 34, 8, playerHealth, COLOR_WHITE
invoke DrawRect, 68, 42, 60, 8, 00441010h invoke DrawRect, 66, 8, 50, 8, 00441010h
invoke DrawRectOutline, 68, 42, 60, 8, COLOR_WHITE invoke DrawRectOutline, 66, 8, 50, 8, COLOR_WHITE
mov eax, playerHealth mov eax, playerHealth
imul eax, 58 imul eax, 48
xor edx, edx xor edx, edx
mov ecx, playerMaxHealth mov ecx, playerMaxHealth
test ecx, ecx test ecx, ecx
@ -489,57 +506,63 @@ hud_stats_draw:
mov barW, eax mov barW, eax
cmp barW, 0 cmp barW, 0
jle hud_ammo_draw jle hud_ammo_draw
cmp barW, 58 cmp barW, 48
jle @F jle @F
mov barW, 58 mov barW, 48
@@: @@:
mov edx, 0000CC33h ; Green mov edx, 0000CC33h ; Green
cmp playerHealth, 30 cmp playerHealth, 30
jg @F jg @F
mov edx, 00FF2222h ; Red warning mov edx, 00FF2222h ; Red warning
@@: @@:
invoke DrawRect, 69, 43, barW, 6, edx invoke DrawRect, 67, 9, barW, 6, edx
hud_ammo_draw: hud_ammo_draw:
; 4. Ammo Display ; 4. Ammo Display: X=126, Y=8
invoke DrawString8x8, 140, 42, OFFSET strAmmoLabel, COLOR_YELLOW invoke DrawString8x8, 126, 8, OFFSET strAmmoLabel, COLOR_YELLOW
invoke DrawNumber8x8, 182, 42, playerAmmo, COLOR_YELLOW invoke DrawNumber8x8, 168, 8, playerAmmo, COLOR_YELLOW
; 5. Score Display ; 5. Keys: X=204, Y=8 (if collected)
invoke DrawString8x8, 222, 42, OFFSET strScoreLabel, COLOR_CYAN cmp playerKeys, 0
invoke DrawNumber8x8, 272, 42, playerScore, COLOR_CYAN jle hud_score_draw
invoke DrawString8x8, 204, 8, OFFSET strKeysLabel, COLOR_MAGENTA
invoke DrawNumber8x8, 238, 8, playerKeys, COLOR_MAGENTA
hud_score_draw:
; 6. Score Display: only in Debug HUD style
cmp hudStyle, HUD_STYLE_CLEAN
je draw_weapon_badge
invoke DrawString8x8, 8, 20, OFFSET strScoreLabel, COLOR_CYAN
invoke DrawNumber8x8, 58, 20, playerScore, COLOR_CYAN
draw_weapon_badge:
; 7. Active Weapon Badge at bottom-right corner: X=228, Y=222
invoke DrawRect, 228, 222, 88, 14, 00141414h
invoke DrawRectOutline, 228, 222, 88, 14, 00444444h
; 6. Active Weapon Name & Slot Indicators at top left
invoke DrawString8x8, 8, 26, OFFSET strWeaponLabel, COLOR_WHITE
cmp playerWeapon, WEAPON_KNIFE cmp playerWeapon, WEAPON_KNIFE
jne @F jne @F
invoke DrawString8x8, 42, 26, OFFSET strWeaponKnife, COLOR_CYAN invoke DrawString8x8, 233, 225, OFFSET strWeaponKnife, COLOR_CYAN
jmp draw_weapon_slots jmp draw_weapon_slots
@@: @@:
cmp playerWeapon, WEAPON_SHOTGUN cmp playerWeapon, WEAPON_SHOTGUN
jne @F jne @F
invoke DrawString8x8, 42, 26, OFFSET strWeaponShotgun, 00FFAA00h invoke DrawString8x8, 233, 225, OFFSET strWeaponShotgun, 00FFAA00h
jmp draw_weapon_slots jmp draw_weapon_slots
@@: @@:
invoke DrawString8x8, 42, 26, OFFSET strWeaponPistol, COLOR_YELLOW invoke DrawString8x8, 233, 225, OFFSET strWeaponPistol, COLOR_YELLOW
draw_weapon_slots: draw_weapon_slots:
; Draw small weapon slot indicator boxes: [1] [2] [3] ; Draw small weapon slot indicator boxes: [1] [2] [3]
invoke DrawRectOutline, 100, 26, 8, 8, 00555555h invoke DrawRectOutline, 287, 225, 6, 8, 00555555h
invoke DrawRectOutline, 112, 26, 8, 8, 00555555h invoke DrawRectOutline, 295, 225, 6, 8, 00555555h
invoke DrawRectOutline, 124, 26, 8, 8, 00555555h invoke DrawRectOutline, 303, 225, 6, 8, 00555555h
; Highlight active slot ; Highlight active slot
mov eax, playerWeapon mov eax, playerWeapon
imul eax, 12 imul eax, 8
add eax, 100 add eax, 287
invoke DrawRectOutline, eax, 26, 8, 8, COLOR_YELLOW invoke DrawRectOutline, eax, 225, 6, 8, COLOR_YELLOW
; 7. Keys (if collected)
cmp playerKeys, 0
jle hud_damage_flash
invoke DrawString8x8, 222, 10, OFFSET strKeysLabel, COLOR_MAGENTA
invoke DrawNumber8x8, 258, 10, playerKeys, COLOR_MAGENTA
hud_damage_flash: hud_damage_flash:
; 8. Damage Screen Flash ; 8. Damage Screen Flash
@ -571,19 +594,20 @@ RenderPauseMenu PROC uses ebx ecx edx
cmp isGamePaused, 0 cmp isGamePaused, 0
je pause_menu_done je pause_menu_done
; Semi-transparent dark overlay panel (centered 200x105 at 60, 65) ; Centered semi-transparent panel 220x112 at (50, 64)
invoke DrawRect, 60, 65, 200, 105, 00141416h invoke DrawRect, 50, 64, 220, 112, 00141416h
invoke DrawRectOutline, 60, 65, 200, 105, COLOR_CYAN invoke DrawRectOutline, 50, 64, 220, 112, COLOR_CYAN
invoke DrawRectOutline, 62, 67, 196, 101, COLOR_WHITE invoke DrawRectOutline, 52, 66, 216, 108, COLOR_WHITE
; Header ; Header
invoke DrawString8x8, 112, 75, OFFSET strPauseTitle, COLOR_YELLOW invoke DrawString8x8, 112, 72, OFFSET strPauseTitle, COLOR_YELLOW
; Menu Options ; Menu Options
invoke DrawString8x8, 76, 95, OFFSET strPauseOpt1, COLOR_GREEN invoke DrawString8x8, 64, 88, OFFSET strPauseOpt1, COLOR_GREEN
invoke DrawString8x8, 76, 110, OFFSET strPauseOpt2, COLOR_WHITE invoke DrawString8x8, 64, 102, OFFSET strPauseOpt2, COLOR_WHITE
invoke DrawString8x8, 76, 125, OFFSET strPauseOpt3, COLOR_CYAN invoke DrawString8x8, 64, 116, OFFSET strPauseOpt3, COLOR_CYAN
invoke DrawString8x8, 76, 140, OFFSET strPauseOpt4, 00FF4444h invoke DrawString8x8, 64, 130, OFFSET strPauseOpt4, COLOR_YELLOW
invoke DrawString8x8, 64, 146, OFFSET strPauseOpt5, 00FF4444h
pause_menu_done: pause_menu_done:
ret ret

View file

@ -69,10 +69,18 @@ keyWeapon3 db 0
keyWeapon4 db 0 keyWeapon4 db 0
keyPause db 0 keyPause db 0
keyPauseHeld db 0 keyPauseHeld db 0
keyHudStyleHeld db 0
keyMenuHeld db 0
isGamePaused dd 0 isGamePaused dd 0
; Game Mode & HUD Configuration
gameMode dd GAME_MODE_ACTION ; 0 = Adventure (No weapons), 1 = Action / Combat
hudStyle dd HUD_STYLE_CLEAN ; 0 = Clean / Immersive, 1 = Debug / Classic
enableWeapons dd 1 ; 1 = Weapons active, 0 = Pure exploration
showCrosshair dd 1 ; 1 = Show crosshair in Action mode, 0 = Hidden
; Player Combat & Gameplay Stats ; Player Combat & Gameplay Stats
playerWeapon dd 1 ; Default: WEAPON_PISTOL playerWeapon dd 1 ; Default: WEAPON_PISTOL
playerHealth dd 100 playerHealth dd 100
playerMaxHealth dd 100 playerMaxHealth dd 100
playerAmmo dd 50 playerAmmo dd 50
@ -129,12 +137,13 @@ strWeaponLabel db "WPN:",0
strWeaponKnife db "KNIFE",0 strWeaponKnife db "KNIFE",0
strWeaponPistol db "PISTOL",0 strWeaponPistol db "PISTOL",0
strWeaponShotgun db "SHOTGUN",0 strWeaponShotgun db "SHOTGUN",0
strPauseTitle db "== PAUSED ==",0 strPauseTitle db "== ПАУЗА ==",0
strPauseOpt1 db "[1] RESUME",0 strPauseOpt1 db "[1] ПРОДОЛЖИТЬ",0
strPauseOpt2 db "[2] RESTART LEVEL",0 strPauseOpt2 db "[2] ОРУЖИЕ: ВКЛ/ВЫКЛ",0
strPauseOpt3 db "[3] MINIMAP RADAR",0 strPauseOpt3 db "[3] ИНТЕРФЕЙС: ЧИСТЫЙ/ИНФО",0
strPauseOpt4 db "[4] QUIT TO WINDOWS",0 strPauseOpt4 db "[4] ПЕРЕЗАПУСК УРОВНЯ",0
strYouDied db "YOU DIED! PRESS [R] TO RESTART",0 strPauseOpt5 db "[5] ВЫХОД В WINDOWS",0
strYouDied db "ВЫ ПОГИБЛИ! НАЖМИТЕ [R]",0
cutsceneCount dd 0 cutsceneCount dd 0
cutsceneStringCount dd 0 cutsceneStringCount dd 0
cutsceneFlags dd 0 cutsceneFlags dd 0
@ -262,3 +271,17 @@ szSwitchOffToast db "ПЕРЕКЛЮЧАТЕЛЬ: ВЫКЛ", 0
szDoorOpenedToast db "ДВЕРЬ ОТКРЫТА", 0 szDoorOpenedToast db "ДВЕРЬ ОТКРЫТА", 0
szDoorClosedToast db "ДВЕРЬ ЗАКРЫТА", 0 szDoorClosedToast db "ДВЕРЬ ЗАКРЫТА", 0
szBarrelExplodedToast db "ВЗРЫВ БОЧКИ!", 0 szBarrelExplodedToast db "ВЗРЫВ БОЧКИ!", 0
szWeaponsEnabledToast db "РЕЖИМ: ОРУЖИЕ ВКЛЮЧЕНО", 0
szWeaponsDisabledToast db "РЕЖИМ: БЕЗ ОРУЖИЯ (КВЕСТ)", 0
szHudCleanToast db "ИНТЕРФЕЙС: ЧИСТЫЙ ВИД", 0
szHudDebugToast db "ИНТЕРФЕЙС: РЕЖИМ ОТЛАДКИ", 0
szProjectFile db "project.eblanproj", 0
szProjGameMode0 db "GameMode=0", 0
szProjGameMode1 db "GameMode=1", 0
szProjShowWeapons0 db "ShowWeapons=0", 0
szFlagNoWeapons db "noweapons", 0
szFlagAdventure db "adventure", 0
szFlagAction db "action", 0
szFlagDebugHud db "debug-hud", 0
szFlagCleanHud db "clean-hud", 0
previewPrefix db "--preview:", 0

View file

@ -71,8 +71,20 @@ VK_KEY_1 equ 31h
VK_KEY_2 equ 32h VK_KEY_2 equ 32h
VK_KEY_3 equ 33h VK_KEY_3 equ 33h
VK_KEY_4 equ 34h VK_KEY_4 equ 34h
VK_KEY_5 equ 35h
VK_KEY_F1 equ 70h
VK_KEY_H equ 48h
; Game Modes
GAME_MODE_ADVENTURE equ 0 ; Exploration / Quest / Horror / Puzzle (No weapons, clean view)
GAME_MODE_ACTION equ 1 ; Combat Action (Weapons, Crosshair, Ammo, Health bar)
; HUD Styles
HUD_STYLE_CLEAN equ 0 ; Clean / Immersive (No debug watermark, no bottom bar)
HUD_STYLE_DEBUG equ 1 ; Debug Mode (FPS counter, Engine watermark, Minimap)
; Weapon Archetypes ; Weapon Archetypes
WEAPON_NONE equ -1 ; No weapon (Unarmed / Peaceful / Quest)
WEAPON_KNIFE equ 0 ; Melee blade (no ammo, fast, 30 damage) WEAPON_KNIFE equ 0 ; Melee blade (no ammo, fast, 30 damage)
WEAPON_PISTOL equ 1 ; Handgun (1 ammo, accurate, 35 damage) WEAPON_PISTOL equ 1 ; Handgun (1 ammo, accurate, 35 damage)
WEAPON_SHOTGUN equ 2 ; Pump Shotgun (2 ammo, 3-ray spread, 70 damage) WEAPON_SHOTGUN equ 2 ; Pump Shotgun (2 ammo, 3-ray spread, 70 damage)

View file

@ -240,3 +240,104 @@ RenderGameHUDOverlays PROC
call Game_OnCustomHUD call Game_OnCustomHUD
ret ret
RenderGameHUDOverlays ENDP RenderGameHUDOverlays ENDP
; ------------------------------------------------------------------------------
; CheckCmdFlag: Case-insensitive substring search (EAX = 1 if found, 0 if not)
; ------------------------------------------------------------------------------
CheckCmdFlag PROC uses esi edi ecx, pszCmd:DWORD, pszFlag:DWORD
mov esi, pszCmd
test esi, esi
jz flag_not_found
check_next_char:
cmp byte ptr [esi], 0
je flag_not_found
mov edi, pszFlag
mov ecx, esi
compare_loop:
mov al, byte ptr [edi]
test al, al
jz flag_found
mov ah, byte ptr [ecx]
cmp al, 'A'
jl @F
cmp al, 'Z'
jg @F
add al, 32
@@:
cmp ah, 'A'
jl @F
cmp ah, 'Z'
jg @F
add ah, 32
@@:
cmp al, ah
jne mismatch
inc edi
inc ecx
jmp compare_loop
mismatch:
inc esi
jmp check_next_char
flag_found:
mov eax, 1
ret
flag_not_found:
xor eax, eax
ret
CheckCmdFlag ENDP
; ------------------------------------------------------------------------------
; LoadProjectSettings: Reads project.eblanproj if present in working directory
; ------------------------------------------------------------------------------
LoadProjectSettings PROC uses ebx ecx edx esi edi
LOCAL hFile:DWORD, bytesRead:DWORD
LOCAL projBuf[1024]:BYTE
invoke CreateFileA, OFFSET szProjectFile, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0
cmp eax, INVALID_HANDLE_VALUE
je proj_done
mov hFile, eax
lea edx, projBuf
invoke ReadFile, hFile, edx, 1023, ADDR bytesRead, 0
invoke CloseHandle, hFile
cmp bytesRead, 0
jle proj_done
mov eax, bytesRead
mov byte ptr [projBuf + eax], 0
lea edx, projBuf
invoke CheckCmdFlag, edx, OFFSET szProjGameMode0
test eax, eax
jnz set_adv_mode
lea edx, projBuf
invoke CheckCmdFlag, edx, OFFSET szProjShowWeapons0
test eax, eax
jnz set_adv_mode
lea edx, projBuf
invoke CheckCmdFlag, edx, OFFSET szProjGameMode1
test eax, eax
jnz set_act_mode
jmp proj_done
set_adv_mode:
mov gameMode, GAME_MODE_ADVENTURE
mov enableWeapons, 0
mov playerWeapon, WEAPON_NONE
mov showCrosshair, 0
jmp proj_done
set_act_mode:
mov gameMode, GAME_MODE_ACTION
mov enableWeapons, 1
mov playerWeapon, WEAPON_PISTOL
mov showCrosshair, 1
proj_done:
ret
LoadProjectSettings ENDP

View file

@ -181,7 +181,34 @@ restart_released:
mov keyRestartHeld, 0 mov keyRestartHeld, 0
restart_sampled: restart_sampled:
; Weapon Selection Keys: 1, 2, 3 ; F1 / H: Toggle Clean HUD vs Debug HUD
invoke GetAsyncKeyState, VK_KEY_F1
mov bx, ax
invoke GetAsyncKeyState, VK_KEY_H
or bx, ax
test bx, 8000h
jz hud_key_released
cmp keyHudStyleHeld, 0
jne hud_key_sampled
xor hudStyle, 1
mov keyHudStyleHeld, 1
cmp hudStyle, HUD_STYLE_CLEAN
je toast_hud_clean
invoke ShowGameToast, OFFSET szHudDebugToast, 60, COLOR_CYAN
jmp hud_key_sampled
toast_hud_clean:
invoke ShowGameToast, OFFSET szHudCleanToast, 60, COLOR_GREEN
jmp hud_key_sampled
hud_key_released:
mov keyHudStyleHeld, 0
hud_key_sampled:
; Weapon Selection Keys: 1, 2, 3 (only in Action mode with weapons enabled)
cmp gameMode, GAME_MODE_ACTION
jne skip_weapon_select
cmp enableWeapons, 0
je skip_weapon_select
invoke GetAsyncKeyState, VK_KEY_1 invoke GetAsyncKeyState, VK_KEY_1
test ax, 8000h test ax, 8000h
jz @F jz @F
@ -197,6 +224,7 @@ restart_sampled:
jz @F jz @F
mov playerWeapon, WEAPON_SHOTGUN mov playerWeapon, WEAPON_SHOTGUN
@@: @@:
skip_weapon_select:
; Escape / Pause Key ; Escape / Pause Key
invoke GetAsyncKeyState, VK_KEY_ESCAPE invoke GetAsyncKeyState, VK_KEY_ESCAPE
@ -224,31 +252,83 @@ pause_key_sampled:
mov keyAttack, 0 mov keyAttack, 0
mov keyAction, 0 mov keyAction, 0
; Check if any menu key is pressed
invoke GetAsyncKeyState, VK_KEY_1
mov bx, ax
invoke GetAsyncKeyState, VK_KEY_2
or bx, ax
invoke GetAsyncKeyState, VK_KEY_3
or bx, ax
invoke GetAsyncKeyState, VK_KEY_4
or bx, ax
invoke GetAsyncKeyState, VK_KEY_5
or bx, ax
test bx, 8000h
jz menu_keys_released
; If a key is pressed, check if already held
cmp keyMenuHeld, 0
jne pause_menu_done
mov keyMenuHeld, 1
; Option 1: Resume ; Option 1: Resume
invoke GetAsyncKeyState, VK_KEY_1 invoke GetAsyncKeyState, VK_KEY_1
test ax, 8000h test ax, 8000h
jz @F jz @F
mov isGamePaused, 0 mov isGamePaused, 0
jmp pause_menu_done
@@: @@:
; Option 2: Restart level ; Option 2: Toggle Weapons / Peaceful Mode
invoke GetAsyncKeyState, VK_KEY_2 invoke GetAsyncKeyState, VK_KEY_2
test ax, 8000h test ax, 8000h
jz @F jz @F
xor enableWeapons, 1
cmp enableWeapons, 0
je set_peaceful_mode
mov gameMode, GAME_MODE_ACTION
mov playerWeapon, WEAPON_PISTOL
mov showCrosshair, 1
invoke ShowGameToast, OFFSET szWeaponsEnabledToast, 80, COLOR_YELLOW
jmp pause_menu_done
set_peaceful_mode:
mov gameMode, GAME_MODE_ADVENTURE
mov playerWeapon, WEAPON_NONE
mov showCrosshair, 0
invoke ShowGameToast, OFFSET szWeaponsDisabledToast, 80, COLOR_GREEN
jmp pause_menu_done
@@:
; Option 3: Toggle HUD (Clean vs Debug)
invoke GetAsyncKeyState, VK_KEY_3
test ax, 8000h
jz @F
xor hudStyle, 1
cmp hudStyle, HUD_STYLE_CLEAN
je toast_pause_clean
invoke ShowGameToast, OFFSET szHudDebugToast, 60, COLOR_CYAN
jmp pause_menu_done
toast_pause_clean:
invoke ShowGameToast, OFFSET szHudCleanToast, 60, COLOR_GREEN
jmp pause_menu_done
@@:
; Option 4: Restart level
invoke GetAsyncKeyState, VK_KEY_4
test ax, 8000h
jz @F
mov isGamePaused, 0 mov isGamePaused, 0
mov keyRestart, 1 mov keyRestart, 1
jmp pause_menu_done
@@: @@:
; Option 3: Toggle Map ; Option 5: Quit to Windows
invoke GetAsyncKeyState, VK_KEY_3 invoke GetAsyncKeyState, VK_KEY_5
test ax, 8000h test ax, 8000h
jz @F jz pause_menu_done
xor keyShowMap, 1
@@:
; Option 4: Quit
invoke GetAsyncKeyState, VK_KEY_4
test ax, 8000h
jz @F
mov isRunning, 0 mov isRunning, 0
@@: jmp pause_menu_done
menu_keys_released:
mov keyMenuHeld, 0
pause_menu_done:
ret ret
not_in_pause_menu: not_in_pause_menu:

View file

@ -1,7 +1,6 @@
; Windowless renderer used by EblanStudio. Same Render3DScene and overlays as game. ; Windowless renderer used by EblanStudio. Same Render3DScene and overlays as game.
; Fixed wire layout is mirrored by editor/src/preview_protocol.h. ; Fixed wire layout is mirrored by editor/src/preview_protocol.h.
.data .data
previewPrefix db "--preview:",0
previewHandle dd 0 previewHandle dd 0
previewMemory dd 0 previewMemory dd 0
previewParent dd 0 previewParent dd 0

View file

@ -220,19 +220,30 @@ Engine_RenderHUD PROC uses ebx ecx edx
call RenderCutsceneOverlay call RenderCutsceneOverlay
ret ret
normal_engine_hud: normal_engine_hud:
cmp hudStyle, HUD_STYLE_DEBUG
jne clean_hud_mode
; --- DEBUG / DEVELOPER HUD MODE ---
; 1. Top HUD Header ; 1. Top HUD Header
invoke DrawString8x8, 10, 10, OFFSET strEngineName, COLOR_YELLOW invoke DrawString8x8, 8, 8, OFFSET strEngineName, COLOR_YELLOW
; 2. FPS Counter ; 2. FPS Counter (drawn at top right X=250 so it never overlaps left elements)
invoke DrawString8x8, 10, 24, OFFSET szFpsLabel, COLOR_CYAN invoke DrawString8x8, 250, 8, OFFSET szFpsLabel, COLOR_CYAN
invoke DrawNumber8x8, 42, 24, currentFps, COLOR_CYAN invoke DrawNumber8x8, 282, 8, currentFps, COLOR_CYAN
; 3. Controls Bar at Bottom ; 3. Controls Bar at Bottom (Debug mode only)
invoke DrawRect, 0, 215, SCREEN_W, 25, 00101010h invoke DrawRect, 0, 215, SCREEN_W, 25, 00101010h
invoke DrawString8x8, 10, 218, OFFSET strControls1, COLOR_WHITE invoke DrawString8x8, 10, 218, OFFSET strControls1, COLOR_WHITE
invoke DrawString8x8, 10, 228, OFFSET strControls3, COLOR_GREEN invoke DrawString8x8, 10, 228, OFFSET strControls3, COLOR_GREEN
jmp check_hints
; 4. Door / Portal Interaction Hint (center screen) clean_hud_mode:
; --- CLEAN / IMMERSIVE HUD MODE ---
; In clean mode, no watermarks and no permanent bottom bar.
; Full 320x240 view is dedicated to the game world.
check_hints:
; 4. Door / Portal Interaction Hint (Cleanly centered above crosshair)
call CheckDoorHint call CheckDoorHint
cmp eax, 1 cmp eax, 1
je show_door_hint je show_door_hint
@ -241,11 +252,11 @@ normal_engine_hud:
jmp check_minimap jmp check_minimap
show_door_hint: show_door_hint:
invoke DrawString8x8, 128, 150, OFFSET strDoorHint, COLOR_YELLOW invoke DrawString8x8, 116, 140, OFFSET strDoorHint, COLOR_YELLOW
jmp check_minimap jmp check_minimap
show_portal_hint: show_portal_hint:
invoke DrawString8x8, 110, 150, OFFSET strPortalHint, COLOR_CYAN invoke DrawString8x8, 110, 140, OFFSET strPortalHint, COLOR_CYAN
check_minimap: check_minimap:
cmp mapErrorFrames, 0 cmp mapErrorFrames, 0
@ -369,14 +380,14 @@ ParseStartupMap PROC uses ebx ecx edx esi edi
skip_executable_quote: skip_executable_quote:
lodsb lodsb
test al, al test al, al
jz startup_arg_done jz check_cmdline_flags
cmp al, 34 cmp al, 34
jne skip_executable_quote jne skip_executable_quote
jmp skip_argument_spaces jmp skip_argument_spaces
skip_executable_word: skip_executable_word:
lodsb lodsb
test al, al test al, al
jz startup_arg_done jz check_cmdline_flags
cmp al, 32 cmp al, 32
jne skip_executable_word jne skip_executable_word
skip_argument_spaces: skip_argument_spaces:
@ -413,10 +424,59 @@ write_startup_char:
dec ecx dec ecx
jnz startup_argument_loop jnz startup_argument_loop
mov startupMapPath, 0 mov startupMapPath, 0
ret jmp check_cmdline_flags
terminate_startup_arg: terminate_startup_arg:
mov byte ptr [edi], 0 mov byte ptr [edi], 0
startup_arg_done:
; If startupMapPath starts with '-' and not '--preview:', clear it
mov al, byte ptr [startupMapPath]
cmp al, '-'
jne check_cmdline_flags
mov esi, OFFSET startupMapPath
mov edi, OFFSET previewPrefix
mov ecx, 10
repe cmpsb
je check_cmdline_flags
mov byte ptr [startupMapPath], 0
check_cmdline_flags:
invoke GetCommandLineA
mov ebx, eax
invoke CheckCmdFlag, ebx, OFFSET szFlagNoWeapons
test eax, eax
jz @F
mov gameMode, GAME_MODE_ADVENTURE
mov enableWeapons, 0
mov playerWeapon, WEAPON_NONE
mov showCrosshair, 0
@@:
invoke CheckCmdFlag, ebx, OFFSET szFlagAdventure
test eax, eax
jz @F
mov gameMode, GAME_MODE_ADVENTURE
mov enableWeapons, 0
mov playerWeapon, WEAPON_NONE
mov showCrosshair, 0
@@:
invoke CheckCmdFlag, ebx, OFFSET szFlagAction
test eax, eax
jz @F
mov gameMode, GAME_MODE_ACTION
mov enableWeapons, 1
mov playerWeapon, WEAPON_PISTOL
mov showCrosshair, 1
@@:
invoke CheckCmdFlag, ebx, OFFSET szFlagDebugHud
test eax, eax
jz @F
mov hudStyle, HUD_STYLE_DEBUG
@@:
invoke CheckCmdFlag, ebx, OFFSET szFlagCleanHud
test eax, eax
jz @F
mov hudStyle, HUD_STYLE_CLEAN
@@:
ret ret
ParseStartupMap ENDP ParseStartupMap ENDP
@ -427,6 +487,7 @@ main PROC
LOCAL msg:MSG LOCAL msg:MSG
LOCAL curTick:DWORD, elapsed:DWORD LOCAL curTick:DWORD, elapsed:DWORD
call LoadProjectSettings
call ParseStartupMap call ParseStartupMap
call TryEnginePreview call TryEnginePreview
call Engine_Init call Engine_Init

BIN
main.obj

Binary file not shown.