feat: enhance Game Studio with multi-weapons, pause menu, exploding barrels, dynamic flash lighting, and IDE API catalog

This commit is contained in:
Artvasil09 2026-10-23 08:30:12 +03:00
parent c1da810bd5
commit 5c568b8cba
16 changed files with 720 additions and 148 deletions

View file

@ -218,22 +218,33 @@ EblanStudio has evolved from a pure level editor into a **Full Game Engine Studi
- `3: Ammo Box`: collectible ammunition pickup (+20 rounds up to 100).
- `4: Key`: key card for unlocking locked doors.
- `5: Switch`: interactive trigger/lever toggled by proximity action ([E]).
- `6: Explosive Barrel`: destructible barrel that explodes on weapon hit with 60 AOE damage to surrounding entities and fiery screen flash.
- Packed into `SpriteStruct.flags` without altering binary map compatibility (`sizeof(SpriteEntry) == 24`).
2. **Player Combat & HUD (`include/engine_combat.inc`)**:
- Weapon attack via `Space`, `Ctrl`, or Left Mouse Button (`VK_LBUTTON`).
- Center crosshair and hitscan raycasting against active enemy guards.
- Animated retro pistol with muzzle flash, recoil, and cooldowns.
2. **Player Combat & Multi-Weapon Arsenal (`include/engine_combat.inc`)**:
- Weapon switching via keys `[1]`, `[2]`, `[3]`:
- **[1] Боевой нож (Combat Knife)**: fast melee attack (cooldown 8, 30 damage, no ammo required).
- **[2] Пистолет (Pistol)**: standard handgun (cooldown 12, 35 damage, 1 ammo).
- **[3] Дробовик (Pump Shotgun)**: heavy scatter weapon (cooldown 22, 70 damage, 2 ammo, wide spread).
- Dynamic Muzzle Flash Room Illumination: shooting dynamically brightens ceiling and floor shading across the whole 3D environment for realistic muzzle flare lighting.
- Center crosshair and hitscan raycasting against active enemy guards and barrels.
- Damage screen flash (red overlay), death state screen, and instant level restart with `[R]`.
- Comprehensive combat HUD: Health bar, Ammo counter, Score counter, and Key indicator.
- In-Game Retro Pause Menu via `[ESC]`: sleek semi-transparent pause menu with `[1] ПРОДОЛЖИТЬ`, `[2] ЗАНОВО`, `[3] МИНИ-КАРТА`, `[4] ВЫХОД В WINDOWS`.
- Comprehensive combat HUD: Active weapon badge with slot indicators `[1] [2] [3]`, Health bar, Ammo counter, Score counter, and Key indicator.
3. **In-Editor MASM32 IDE (`F7` / "Код / IDE")**:
- Integrated in-engine code editor (`editor/src/editor_code.inl`) accessible via `F7` or the main menu.
- Integrated in-engine code editor (`editor/src/editor_code.inl`) accessible via `F7`, `Ctrl+B`, or the main menu.
- MASM syntax highlighting (instructions, registers, directives, comments, strings, numbers).
- Project file navigator (`engine_entity.inc`, `engine_combat.inc`, `main.asm`, etc.).
- Script templates (Custom Guard AI, Mega-Medkit, Secret Door Trigger, Custom HUD).
- **Engine API Reference Catalog**: built-in searchable catalog of all engine procedures with 1-click **"Вставить"** snippet insertion.
- Real-time search bar (`Поиск в коде...`) with instant match counter.
- Script templates (Custom Guard AI, Mega-Medkit, Secret Door Trigger, Explosive Barrel, Custom HUD).
- Asynchronous 1-click compiler runner (`build.bat` / `ml.exe`) with interactive output console and clickable error lines.
4. **Project Settings & Standalone Game Exporter (`editor/src/project_manager.h`)**:
4. **Editor UI, 2D Canvas & Project Exporter (`editor/src/editor_app.cpp`)**:
- Modern Game Studio dark theme with Electric Cyan and Neon Amber accents.
- **2D Canvas Guard Aggro Circles**: visualizes guard detection and chase radius on the 2D map.
- **Quick Entity Spawner Palette**: 1-click spawn buttons (`+ Враг`, `+ Аптечка`, `+ Патроны`, `+ Ключ`, `+ Рычаг`, `+ Бочка`, `+ Проп`).
- 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.

Binary file not shown.

View file

@ -1,6 +1,6 @@
[Window][Workspace]
Pos=0,60
Size=1536,708
Size=2560,1292
LastUsed=20261022
[Window][Debug##Default]
@ -10,12 +10,12 @@ LastUsed=20261022
[Window][Toolbar]
Pos=0,20
Size=1536,38
Size=2560,38
LastUsed=20261022
[Window][StatusBar]
Pos=0,768
Size=1536,32
Pos=0,1352
Size=2560,32
LastUsed=20261022
[Window][Не удалось запустить игру]
@ -23,3 +23,13 @@ Pos=649,356
Size=237,80
LastUsed=20261022
[Window][Редактор кода и скриптов MASM32 - EblanStudio IDE]
Pos=60,60
Size=900,650
LastUsed=20261022
[Window][Менеджер ассетов (Texture & Audio Manager)]
Pos=60,60
Size=820,580
LastUsed=20261022

View file

@ -1269,7 +1269,29 @@ void EditorApp::ApplyTool(int gx, int gy) {
if (sprites.size() < MAX_LEVEL_SPRITES) {
SpriteEntry sprite{};
sprite.x = gx * 64 + 32; sprite.y = gy * 64 + 32;
sprite.textureId = 11; sprite.scale = 65536;
sprite.scale = 65536;
if (pendingSpawnArchetype == ENTITY_ARCHETYPE_GUARD) {
sprite.textureId = 13;
SetSpriteEntityData(sprite, ENTITY_ARCHETYPE_GUARD, 100, 2, 6);
} else if (pendingSpawnArchetype == ENTITY_ARCHETYPE_MEDKIT) {
sprite.textureId = 14;
SetSpriteEntityData(sprite, ENTITY_ARCHETYPE_MEDKIT, 1, 0, 0);
} else if (pendingSpawnArchetype == ENTITY_ARCHETYPE_AMMO) {
sprite.textureId = 15;
SetSpriteEntityData(sprite, ENTITY_ARCHETYPE_AMMO, 1, 0, 0);
} else if (pendingSpawnArchetype == ENTITY_ARCHETYPE_KEY) {
sprite.textureId = 10;
SetSpriteEntityData(sprite, ENTITY_ARCHETYPE_KEY, 1, 0, 0);
} else if (pendingSpawnArchetype == ENTITY_ARCHETYPE_SWITCH) {
sprite.textureId = 12;
SetSpriteEntityData(sprite, ENTITY_ARCHETYPE_SWITCH, 1, 0, 0);
} else if (pendingSpawnArchetype == ENTITY_ARCHETYPE_BARREL) {
sprite.textureId = 11;
SetSpriteEntityData(sprite, ENTITY_ARCHETYPE_BARREL, 50, 0, 0);
} else {
sprite.textureId = 11;
SetSpriteEntityData(sprite, ENTITY_ARCHETYPE_PROP, 100, 0, 0);
}
sprites.push_back(sprite);
selectedSpriteIdx = static_cast<int>(sprites.size()) - 1;
selectedTransitionIdx = -1;
@ -1403,6 +1425,10 @@ void EditorApp::RenderUI() {
if (ImGui::RadioButton("Рядом", viewportMode == 0)) viewportMode = 0;
ImGui::SameLine(); if (ImGui::RadioButton("Игровой вид", viewportMode == 1)) viewportMode = 1;
ImGui::SameLine(); if (ImGui::RadioButton("Карта", viewportMode == 2)) viewportMode = 2;
ImGui::SameLine(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine();
if (ImGui::SmallButton(" - ")) { canvasZoom = (std::max)(8.0f, canvasZoom - 4.0f); }
ImGui::SameLine(); if (ImGui::SmallButton(" 100% ")) { canvasZoom = 20.0f; canvasPanX = 20.0f; canvasPanY = 20.0f; }
ImGui::SameLine(); if (ImGui::SmallButton(" + ")) { canvasZoom = (std::min)(48.0f, canvasZoom + 4.0f); }
float sceneWidth = viewportMode == 0 ? ImGui::GetContentRegionAvail().x * 0.5f : 0;
if (viewportMode != 2) {
ImGui::BeginChild("SceneViewport", ImVec2(sceneWidth,0), true);
@ -1745,12 +1771,22 @@ void EditorApp::RenderGridCanvas() {
// Color diamond based on Entity Archetype
uint8_t aType = GetSpriteEntityType(sprites[i]);
// Draw Guard Aggro / Detection circle on the map
if (aType == ENTITY_ARCHETYPE_GUARD) {
uint8_t agg = GetSpriteAggro(sprites[i]);
float aggRadius = agg * tileSize;
drawList->AddCircle(scPos, aggRadius, IM_COL32(235, 45, 45, 60), 32, 1.5f);
drawList->AddCircleFilled(scPos, aggRadius, IM_COL32(235, 45, 45, 12));
}
ImU32 fillCol = IM_COL32(255, 215, 0, 240); // Prop (Gold)
if (aType == ENTITY_ARCHETYPE_GUARD) fillCol = IM_COL32(235, 45, 45, 240); // Red (Enemy Guard)
else if (aType == ENTITY_ARCHETYPE_MEDKIT) fillCol = IM_COL32(40, 220, 80, 240); // Green (Medkit)
else if (aType == ENTITY_ARCHETYPE_AMMO) fillCol = IM_COL32(245, 190, 25, 240); // Yellow (Ammo)
else if (aType == ENTITY_ARCHETYPE_KEY) fillCol = IM_COL32(30, 190, 255, 240); // Cyan (Key)
else if (aType == ENTITY_ARCHETYPE_SWITCH) fillCol = IM_COL32(255, 130, 20, 240); // Orange (Switch)
else if (aType == ENTITY_ARCHETYPE_BARREL) fillCol = IM_COL32(255, 90, 20, 240); // Dark Orange (Barrel)
ImU32 borderCol = IM_COL32(255, 255, 255, 255);
if (static_cast<int>(i) == selectedSpriteIdx) {
@ -1947,6 +1983,36 @@ void EditorApp::RenderTilePalette() {
PaletteButton("9. Мрамор", CELL_MARBLE, ImVec4(0.80f, 0.77f, 0.73f, 1.0f), 9);
PaletteButton("10. Портал перехода", CELL_PORTAL, ImVec4(0.60f, 0.20f, 0.85f, 1.0f), 10);
PaletteButton("0. Пустота (Пол)", CELL_EMPTY, ImVec4(0.15f, 0.15f, 0.15f, 1.0f), 0);
ImGui::Spacing();
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "СПАВН СУЩНОСТЕЙ");
ImGui::Separator();
auto SpawnerBtn = [&](const char* title, uint8_t arch, ImVec4 col) {
bool active = (currentTool == TOOL_SPRITE_SPAWN && pendingSpawnArchetype == arch);
if (active) {
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
}
ImGui::PushStyleColor(ImGuiCol_Button, col);
if (ImGui::Button(title, ImVec2(-1, 24))) {
currentTool = TOOL_SPRITE_SPAWN;
pendingSpawnArchetype = arch;
}
ImGui::PopStyleColor();
if (active) {
ImGui::PopStyleVar();
ImGui::PopStyleColor();
}
};
SpawnerBtn("+ Враг (Guard AI)", ENTITY_ARCHETYPE_GUARD, ImVec4(0.65f, 0.15f, 0.15f, 1.0f));
SpawnerBtn("+ Аптечка (+25 HP)", ENTITY_ARCHETYPE_MEDKIT, ImVec4(0.15f, 0.55f, 0.20f, 1.0f));
SpawnerBtn("+ Патроны (+20)", ENTITY_ARCHETYPE_AMMO, ImVec4(0.65f, 0.55f, 0.10f, 1.0f));
SpawnerBtn("+ Ключ от двери", ENTITY_ARCHETYPE_KEY, ImVec4(0.15f, 0.50f, 0.70f, 1.0f));
SpawnerBtn("+ Переключатель [E]", ENTITY_ARCHETYPE_SWITCH, ImVec4(0.70f, 0.40f, 0.10f, 1.0f));
SpawnerBtn("+ Бочка (Взрывная)", ENTITY_ARCHETYPE_BARREL, ImVec4(0.70f, 0.25f, 0.10f, 1.0f));
SpawnerBtn("+ Декорация (Проп)", ENTITY_ARCHETYPE_PROP, ImVec4(0.35f, 0.35f, 0.40f, 1.0f));
}
void EditorApp::RenderHierarchy() {
@ -1967,6 +2033,7 @@ void EditorApp::RenderHierarchy() {
else if (aType == ENTITY_ARCHETYPE_AMMO) typeTag = "+Ammo";
else if (aType == ENTITY_ARCHETYPE_KEY) typeTag = "Ключ";
else if (aType == ENTITY_ARCHETYPE_SWITCH) typeTag = "Рычаг";
else if (aType == ENTITY_ARCHETYPE_BARREL) typeTag = "Бочка";
char label[64];
snprintf(label, sizeof(label), "[%s] #%zu (%d, %d)", typeTag, i + 1, gx, gy);
@ -2087,20 +2154,21 @@ void EditorApp::RenderInspector() {
// Entity Archetype & Logic
ImGui::Separator();
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Геймплей / Архетип сущности:");
static const char* s_ArchetypeNames[6] = {
static const char* s_ArchetypeNames[7] = {
"0: Декорация / Проп (Static)",
"1: Враг (Enemy Guard AI)",
"2: Аптечка (+25 HP)",
"3: Патроны (+20 Ammo)",
"4: Ключ от двери (Key Pickup)",
"5: Переключатель / Рычаг (Switch)"
"5: Переключатель / Рычаг (Switch)",
"6: Взрывная бочка (Barrel AOE)"
};
int archType = static_cast<int>(GetSpriteEntityType(spr));
int hp = static_cast<int>(GetSpriteHealth(spr));
int spd = static_cast<int>(GetSpriteSpeed(spr));
int agg = static_cast<int>(GetSpriteAggro(spr));
if (ImGui::Combo("Тип сущности", &archType, s_ArchetypeNames, 6)) {
if (ImGui::Combo("Тип сущности", &archType, s_ArchetypeNames, 7)) {
PushUndo();
SetSpriteEntityData(spr, static_cast<uint8_t>(archType), static_cast<uint8_t>(hp), static_cast<uint8_t>(spd), static_cast<uint8_t>(agg));
}
@ -2123,6 +2191,8 @@ void EditorApp::RenderInspector() {
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "Ключ для открытия запертых дверей");
} else if (archType == ENTITY_ARCHETYPE_SWITCH) {
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.2f, 1.0f), "Активируется нажатием клавиши [E] рядом с объектом");
} else if (archType == ENTITY_ARCHETYPE_BARREL) {
ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.15f, 1.0f), "Взрывается от выстрелов, нанося 60 AOE урона всем вокруг!");
}
if (ImGui::Button("Редактировать логику в Редакторе кода (F7)")) {

View file

@ -96,6 +96,7 @@ public:
// Editor state
ToolMode currentTool = TOOL_BRUSH;
CellType selectedPaletteCell = CELL_BRICK;
uint8_t pendingSpawnArchetype = ENTITY_ARCHETYPE_PROP;
int selectedSpriteIdx = -1;
int selectedTransitionIdx = -1;
bool isEngineRunning = false;

View file

@ -6,6 +6,7 @@
#include <string>
#include <filesystem>
#include <cctype>
#include <cstring>
#include <windows.h>
#include "../imgui/imgui.h"
@ -17,14 +18,16 @@ struct CodeFileEntry {
};
static const CodeFileEntry g_ProjectCodeFiles[] = {
{ "engine_entity.inc (Entity AI & Pickups)", "include/engine_entity.inc" },
{ "engine_combat.inc (Player Weapons & Combat)", "include/engine_combat.inc" },
{ "main.asm (Engine Entry & Main Loop)", "main.asm" },
{ "engine_map.inc (World Grid & Doors)", "include/engine_map.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" },
{ "engine_defs.inc (Constants & Structs)", "include/engine_defs.inc" },
{ "engine_data.inc (Memory & Game State)", "include/engine_data.inc" },
{ "engine_map.inc (World Grid & Doors)", "include/engine_map.inc" },
{ "engine_render2d.inc (2D Canvas & HUD)", "include/engine_render2d.inc" },
{ "engine_input.inc (Keyboard Input)", "include/engine_input.inc" },
{ "engine_physics.inc (Player Physics)", "include/engine_physics.inc" }
{ "engine_raycaster.inc (3D DDA Raycaster)", "include/engine_raycaster.inc" },
{ "engine_input.inc (Keyboard & Mouse)", "include/engine_input.inc" },
{ "engine_physics.inc (Player Movement)", "include/engine_physics.inc" }
};
static const int g_CodeFileCount = sizeof(g_ProjectCodeFiles) / sizeof(g_ProjectCodeFiles[0]);
@ -78,6 +81,15 @@ static const ScriptTemplate g_ScriptTemplates[] = {
" invoke SetMapCell, switchTargetX, switchTargetY, CELL_DOOR\n"
"switch_done:\n"
},
{
"Взрывная бочка (AOE Explosion Trigger)",
"; ==============================================================================\n"
"; Explosive Barrel: Deals 60 AOE damage and triggers fiery screen flash\n"
"; ==============================================================================\n"
" mov [entityState + ecx*4], ENTITY_STATE_DEAD\n"
" mov playerHitFlash, 8\n"
" add playerScore, 200\n"
},
{
"Кастомный элемент HUD (Custom Gauge Widget)",
"; ==============================================================================\n"
@ -90,6 +102,34 @@ static const ScriptTemplate g_ScriptTemplates[] = {
};
static const int g_ScriptTemplateCount = sizeof(g_ScriptTemplates) / sizeof(g_ScriptTemplates[0]);
struct EngineApiItem {
const char* category;
const char* name;
const char* description;
const char* snippet;
};
static const EngineApiItem g_EngineApiCatalog[] = {
{ "Бой и оружие", "UpdateCombat", "Обновляет прицел, стрельбу и расчет урона", "call UpdateCombat\n" },
{ "Бой и оружие", "RenderCombatHUD", "Рисует оружие, прицел и статус игрока", "call RenderCombatHUD\n" },
{ "Бой и оружие", "Смена оружия", "Устанавливает активное оружие игрока", "mov playerWeapon, WEAPON_SHOTGUN ; 0=Knife, 1=Pistol, 2=Shotgun\n" },
{ "Бой и оружие", "Нанесение урона", "Уменьшает здоровье врага по индексу", "mov ecx, targetIdx\nmov eax, [entityHealth + ecx*4]\nsub eax, 35\nmov [entityHealth + ecx*4], eax\n" },
{ "Сущности и AI", "InitEntities", "Сбрасывает и спавнит сущности уровня", "call InitEntities\n" },
{ "Сущности и AI", "UpdateEntities", "AI преследования, патруля и подбор предметов", "call UpdateEntities\n" },
{ "Сущности и AI", "Патруль врага", "Инкремент таймера контрольной точки патруля", "mov ecx, [entityTimer + edx*4]\ninc ecx\nmov [entityTimer + edx*4], ecx\n" },
{ "2D Рендеринг", "DrawRect", "Рисует закрашенный прямоугольник (X, Y, W, H, Color)", "invoke DrawRect, 10, 20, 80, 12, 00336699h\n" },
{ "2D Рендеринг", "DrawRectOutline", "Рисует рамку прямоугольника", "invoke DrawRectOutline, 10, 20, 80, 12, COLOR_WHITE\n" },
{ "2D Рендеринг", "DrawString8x8", "Выводит строку шрифтом 8x8 (CP1251)", "invoke DrawString8x8, 10, 22, OFFSET myString, COLOR_YELLOW\n" },
{ "2D Рендеринг", "DrawNumber8x8", "Выводит целое число на экран", "invoke DrawNumber8x8, 60, 22, myNumberVar, COLOR_WHITE\n" },
{ "Рэйкастинг и Мир", "tableCos / tableSin", "Чтение тригонометрии 16.16 (угол 0..511)", "mov eax, playerAngle\nand eax, ANGLE_MASK\nmov ebx, [tableCos + eax*4]\nmov edx, [tableSin + eax*4]\n" },
{ "Рэйкастинг и Мир", "depthBuffer", "Глубина стен для спрайтового z-теста", "mov eax, [depthBuffer + screenX*4]\n" },
{ "Рэйкастинг и Мир", "CheckWallCollision", "Проверка коллизии с ячейкой карты", "invoke CheckWallCollision, targetX, targetY\n" }
};
static const int g_EngineApiCount = sizeof(g_EngineApiCatalog) / sizeof(g_EngineApiCatalog[0]);
struct CodeEditorState {
int currentFile = 0;
std::string fileContent;
@ -105,6 +145,10 @@ struct CodeEditorState {
int buildExitCode = 0;
bool hasBuildResult = false;
// Search bar state
char searchBuf[64] = "";
int searchMatchCount = 0;
struct ParsedError {
std::string file;
int line = 0;
@ -178,8 +222,9 @@ struct CodeEditorState {
buildLog.clear();
buildErrors.clear();
hasBuildResult = false;
buildExitCode = 0;
fs::path root(projectRoot);
fs::path root = fs::path(projectRoot);
fs::path buildBat = root / "build.bat";
if (!fs::exists(buildBat)) {
buildBat = root.parent_path() / "build.bat";
@ -187,7 +232,7 @@ struct CodeEditorState {
}
if (!fs::exists(buildBat)) {
buildLog = "[ERROR] Файл build.bat не найден в корне проекта!\n";
buildLog = "[ERROR] Не найден скрипт сборки build.bat в: " + buildBat.string() + "\n";
hasBuildResult = true;
buildExitCode = 1;
return;
@ -299,14 +344,13 @@ struct CodeEditorState {
if (!std::isdigit(static_cast<unsigned char>(c))) { isNum = false; break; }
}
if (isNum) {
ParsedError err{};
ParsedError err;
err.file = line.substr(0, openParen);
err.line = std::atoi(lineStr.c_str());
err.message = line.substr(colon + 3);
buildErrors.push_back(err);
}
}
if (end == std::string::npos) break;
start = end + 1;
}
@ -320,25 +364,23 @@ void EditorApp::RenderCodeEditorWindow() {
g_CodeEditor.PollBuild();
ImGui::SetNextWindowSize(ImVec2(900, 650), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Редактор кода и скриптов MASM32 - EblanStudio IDE", &showCodeEditorModal, ImGuiWindowFlags_MenuBar)) {
ImGui::SetNextWindowSize(ImVec2(1020, 680), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Редактор кода MASM32 & Игровых скриптов (F7)", &showCodeEditorModal, ImGuiWindowFlags_MenuBar)) {
ImGui::End();
return;
}
// Top Menu Bar
char module[MAX_PATH]{};
GetModuleFileNameA(nullptr, module, MAX_PATH);
std::string root = fs::path(module).parent_path().string();
// Menu Bar
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("Файл")) {
if (ImGui::MenuItem("Сохранить (Ctrl+S)", "Ctrl+S")) {
char module[MAX_PATH]{};
GetModuleFileNameA(nullptr, module, MAX_PATH);
std::string root = fs::path(module).parent_path().string();
g_CodeEditor.SaveFile(root);
}
if (ImGui::MenuItem("Собрать движок (F7)", "F7")) {
char module[MAX_PATH]{};
GetModuleFileNameA(nullptr, module, MAX_PATH);
std::string root = fs::path(module).parent_path().string();
if (ImGui::MenuItem("Собрать проект (F7 / Ctrl+B)", "Ctrl+B")) {
g_CodeEditor.StartBuild(root);
}
ImGui::Separator();
@ -351,7 +393,6 @@ void EditorApp::RenderCodeEditorWindow() {
if (ImGui::BeginMenu("Шаблоны кода")) {
for (int i = 0; i < g_ScriptTemplateCount; ++i) {
if (ImGui::MenuItem(g_ScriptTemplates[i].title)) {
// Append template code to text buffer
size_t curLen = std::strlen(g_CodeEditor.textBuffer.data());
size_t tLen = std::strlen(g_ScriptTemplates[i].code);
g_CodeEditor.EnsureBufferSize(curLen + tLen + 1024);
@ -366,12 +407,8 @@ void EditorApp::RenderCodeEditorWindow() {
ImGui::EndMenuBar();
}
// Toolbar Buttons
// Top Toolbar
ImGui::BeginGroup();
char module[MAX_PATH]{};
GetModuleFileNameA(nullptr, module, MAX_PATH);
std::string root = fs::path(module).parent_path().string();
if (ImGui::Button("Сохранить (Ctrl+S)")) {
g_CodeEditor.SaveFile(root);
}
@ -381,54 +418,113 @@ void EditorApp::RenderCodeEditorWindow() {
ImGui::Button("Компиляция...");
ImGui::PopStyleColor();
} else {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.1f, 0.5f, 0.2f, 1.0f));
if (ImGui::Button("Собрать движок (F7)")) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.12f, 0.55f, 0.22f, 1.0f));
if (ImGui::Button("Собрать проект (F7)")) {
g_CodeEditor.StartBuild(root);
}
ImGui::PopStyleColor();
}
ImGui::SameLine();
ImGui::TextDisabled("|");
ImGui::SameLine();
// Search bar
ImGui::SetNextItemWidth(200);
if (ImGui::InputTextWithHint("##SearchCode", "Поиск в коде...", g_CodeEditor.searchBuf, sizeof(g_CodeEditor.searchBuf))) {
if (g_CodeEditor.searchBuf[0]) {
int count = 0;
const char* p = g_CodeEditor.textBuffer.data();
while ((p = std::strstr(p, g_CodeEditor.searchBuf)) != nullptr) {
count++;
p += std::strlen(g_CodeEditor.searchBuf);
}
g_CodeEditor.searchMatchCount = count;
} else {
g_CodeEditor.searchMatchCount = 0;
}
}
if (g_CodeEditor.searchBuf[0]) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.4f, 0.9f, 1.0f, 1.0f), "[%d совп.]", g_CodeEditor.searchMatchCount);
}
ImGui::SameLine();
ImGui::TextDisabled("| %s", g_CodeEditor.statusText.c_str());
ImGui::EndGroup();
ImGui::Separator();
// Split Left (File list) and Right (Editor + Output)
// Split Left (Navigator & API Catalog) and Right (Editor + Output)
ImGui::Columns(2, "CodeEditorColumns", true);
static bool initCol = true;
if (initCol) {
ImGui::SetColumnWidth(0, 240);
ImGui::SetColumnWidth(0, 270);
initCol = false;
// Load initial file
g_CodeEditor.LoadFile(0, root);
}
// Left Column: Source File List
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Исходные файлы проекта:");
for (int i = 0; i < g_CodeFileCount; ++i) {
bool isSelected = (g_CodeEditor.currentFile == i);
if (ImGui::Selectable(g_ProjectCodeFiles[i].displayName.c_str(), isSelected)) {
if (g_CodeEditor.isModified) {
g_CodeEditor.SaveFile(root);
// Left Column: Tabbed Navigation (Files / API Catalog / Templates)
ImGui::BeginTabBar("NavTabs");
if (ImGui::BeginTabItem("Файлы")) {
ImGui::Spacing();
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Исходные файлы проекта:");
for (int i = 0; i < g_CodeFileCount; ++i) {
bool isSelected = (g_CodeEditor.currentFile == i);
if (ImGui::Selectable(g_ProjectCodeFiles[i].displayName.c_str(), isSelected)) {
if (g_CodeEditor.isModified) {
g_CodeEditor.SaveFile(root);
}
g_CodeEditor.LoadFile(i, root);
}
g_CodeEditor.LoadFile(i, root);
}
ImGui::EndTabItem();
}
ImGui::Spacing();
ImGui::Separator();
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "Быстрые шаблоны:");
for (int i = 0; i < g_ScriptTemplateCount; ++i) {
if (ImGui::SmallButton(g_ScriptTemplates[i].title)) {
size_t curLen = std::strlen(g_CodeEditor.textBuffer.data());
size_t tLen = std::strlen(g_ScriptTemplates[i].code);
g_CodeEditor.EnsureBufferSize(curLen + tLen + 1024);
std::strcat(g_CodeEditor.textBuffer.data(), "\n");
std::strcat(g_CodeEditor.textBuffer.data(), g_ScriptTemplates[i].code);
g_CodeEditor.isModified = true;
if (ImGui::BeginTabItem("Справочник API")) {
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.2f, 0.85f, 1.0f, 1.0f), "Функции и процедуры движка:");
ImGui::BeginChild("ApiCatalogChild", ImVec2(0, 0), false);
for (int i = 0; i < g_EngineApiCount; ++i) {
ImGui::PushID(i);
ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.3f, 1.0f), "%s", g_EngineApiCatalog[i].name);
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 65);
if (ImGui::SmallButton("Вставить")) {
size_t curLen = std::strlen(g_CodeEditor.textBuffer.data());
size_t sLen = std::strlen(g_EngineApiCatalog[i].snippet);
g_CodeEditor.EnsureBufferSize(curLen + sLen + 1024);
std::strcat(g_CodeEditor.textBuffer.data(), "\n");
std::strcat(g_CodeEditor.textBuffer.data(), g_EngineApiCatalog[i].snippet);
g_CodeEditor.isModified = true;
g_CodeEditor.statusText = std::string("Вставлен код: ") + g_EngineApiCatalog[i].name;
}
ImGui::TextDisabled(" %s", g_EngineApiCatalog[i].description);
ImGui::Separator();
ImGui::PopID();
}
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Шаблоны")) {
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.4f, 0.9f, 0.5f, 1.0f), "Быстрые шаблоны скриптов:");
for (int i = 0; i < g_ScriptTemplateCount; ++i) {
if (ImGui::Button(g_ScriptTemplates[i].title, ImVec2(-1, 26))) {
size_t curLen = std::strlen(g_CodeEditor.textBuffer.data());
size_t tLen = std::strlen(g_ScriptTemplates[i].code);
g_CodeEditor.EnsureBufferSize(curLen + tLen + 1024);
std::strcat(g_CodeEditor.textBuffer.data(), "\n");
std::strcat(g_CodeEditor.textBuffer.data(), g_ScriptTemplates[i].code);
g_CodeEditor.isModified = true;
g_CodeEditor.statusText = std::string("Вставлен шаблон: ") + g_ScriptTemplates[i].title;
}
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
ImGui::NextColumn();
// Right Column: Code Editor & Compiler Console
@ -436,7 +532,6 @@ void EditorApp::RenderCodeEditorWindow() {
float editorHeight = availY * 0.65f;
float outputHeight = availY - editorHeight - 20;
// Multiline Text Editor with monospace font
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f), "Файл: %s %s",
g_ProjectCodeFiles[g_CodeEditor.currentFile].relativePath.c_str(),
g_CodeEditor.isModified ? "*" : "");
@ -453,10 +548,10 @@ void EditorApp::RenderCodeEditorWindow() {
}
ImGui::PopStyleColor();
// Hotkey: Ctrl+S to save
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_S)) {
g_CodeEditor.SaveFile(root);
// Hotkey: Ctrl+S to save, Ctrl+B to build
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && ImGui::GetIO().KeyCtrl) {
if (ImGui::IsKeyPressed(ImGuiKey_S)) g_CodeEditor.SaveFile(root);
if (ImGui::IsKeyPressed(ImGuiKey_B)) g_CodeEditor.StartBuild(root);
}
ImGui::Separator();
@ -466,9 +561,8 @@ void EditorApp::RenderCodeEditorWindow() {
if (ImGui::BeginTabItem("Вывод компилятора (ml.exe)")) {
ImGui::BeginChild("CompilerOutputChild", ImVec2(0, outputHeight), true);
if (g_CodeEditor.buildLog.empty() && !g_CodeEditor.isBuilding) {
ImGui::TextDisabled("Нажмите 'Собрать движок (F7)' для компиляции ассемблерного кода...");
ImGui::TextDisabled("Нажмите 'Собрать проект (F7)' для компиляции ассемблерного кода...");
} else {
// Display log line by line with color tags
size_t logStart = 0;
while (logStart < g_CodeEditor.buildLog.size()) {
size_t logEnd = g_CodeEditor.buildLog.find('\n', logStart);
@ -508,7 +602,6 @@ void EditorApp::RenderCodeEditorWindow() {
snprintf(lineNumBuf, sizeof(lineNumBuf), "%d", err.line);
std::string label = err.file + ":" + lineNumBuf + " - " + err.message;
if (ImGui::Selectable(label.c_str())) {
// Switch to corresponding file if matched
for (int f = 0; f < g_CodeFileCount; ++f) {
if (err.file.find(g_ProjectCodeFiles[f].relativePath) != std::string::npos ||
g_ProjectCodeFiles[f].relativePath.find(err.file) != std::string::npos) {

View file

@ -48,7 +48,8 @@ enum EntityArchetype : uint8_t {
ENTITY_ARCHETYPE_AMMO = 3, // Ammo pickup (+20 Ammo)
ENTITY_ARCHETYPE_KEY = 4, // Door Key pickup
ENTITY_ARCHETYPE_SWITCH = 5, // Interactive Switch / Trigger
ENTITY_ARCHETYPE_COUNT = 6
ENTITY_ARCHETYPE_BARREL = 6, // Explosive Barrel (AOE Blast)
ENTITY_ARCHETYPE_COUNT = 7
};
#pragma pack(push, 1)
@ -104,6 +105,7 @@ struct SpriteEntry {
inline uint8_t GetSpriteEntityType(const SpriteEntry& s) {
uint8_t t = static_cast<uint8_t>(s.flags & 0xFF);
if (t == 0) {
if (s.textureId == 11) return ENTITY_ARCHETYPE_BARREL;
if (s.textureId == 13) return ENTITY_ARCHETYPE_GUARD;
if (s.textureId == 14) return ENTITY_ARCHETYPE_MEDKIT;
if (s.textureId == 15) return ENTITY_ARCHETYPE_AMMO;

View file

@ -30,46 +30,51 @@ void SetupGodotStyle() {
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
style.WindowRounding = 4.0f;
style.FrameRounding = 3.0f;
style.PopupRounding = 4.0f;
style.ScrollbarRounding = 4.0f;
style.GrabRounding = 3.0f;
style.TabRounding = 4.0f;
style.WindowRounding = 6.0f;
style.ChildRounding = 5.0f;
style.FrameRounding = 4.0f;
style.PopupRounding = 6.0f;
style.ScrollbarRounding = 6.0f;
style.GrabRounding = 4.0f;
style.TabRounding = 5.0f;
style.FrameBorderSize = 1.0f;
style.WindowBorderSize = 1.0f;
style.WindowPadding = ImVec2(10.0f, 10.0f);
style.FramePadding = ImVec2(7.0f, 4.0f);
style.ItemSpacing = ImVec2(8.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
// Dark graphite palette (Godot 4 inspired)
colors[ImGuiCol_Text] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.52f, 0.55f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.14f, 0.15f, 0.17f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.12f, 0.13f, 0.15f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.16f, 0.17f, 0.20f, 1.00f);
colors[ImGuiCol_Border] = ImVec4(0.24f, 0.25f, 0.28f, 1.00f);
// Modern Game Studio Dark Slate Palette with Electric Cyan & Neon Amber accents
colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.52f, 0.55f, 0.60f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.12f, 0.14f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.13f, 0.14f, 0.17f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.14f, 0.15f, 0.18f, 0.98f);
colors[ImGuiCol_Border] = ImVec4(0.22f, 0.24f, 0.28f, 1.00f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.21f, 0.24f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.28f, 0.30f, 0.34f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.32f, 0.35f, 0.40f, 1.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.11f, 0.12f, 0.13f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.17f, 0.20f, 1.00f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.11f, 0.12f, 0.14f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.10f, 0.11f, 0.12f, 0.60f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.25f, 0.27f, 0.30f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.35f, 0.38f, 0.42f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.45f, 0.48f, 0.52f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.30f, 0.70f, 1.00f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.30f, 0.65f, 0.95f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.40f, 0.75f, 1.00f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.22f, 0.24f, 0.27f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.30f, 0.33f, 0.38f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.38f, 0.42f, 0.48f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.24f, 0.26f, 0.30f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.32f, 0.35f, 0.40f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.38f, 0.42f, 0.48f, 1.00f);
colors[ImGuiCol_Separator] = ImVec4(0.22f, 0.24f, 0.27f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.16f, 0.18f, 0.20f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.32f, 0.35f, 0.40f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.24f, 0.26f, 0.30f, 1.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.20f, 0.24f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.24f, 0.28f, 0.34f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.28f, 0.33f, 0.40f, 1.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.14f, 0.16f, 0.20f, 1.00f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.10f, 0.11f, 0.13f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.09f, 0.10f, 0.12f, 0.80f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.24f, 0.27f, 0.33f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.32f, 0.37f, 0.45f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.40f, 0.46f, 0.56f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.20f, 0.75f, 1.00f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.20f, 0.70f, 0.98f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.35f, 0.82f, 1.00f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.20f, 0.23f, 0.28f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.34f, 0.42f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.34f, 0.42f, 0.52f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.24f, 0.30f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.28f, 0.34f, 0.42f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.34f, 0.42f, 0.52f, 1.00f);
colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.22f, 0.26f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.14f, 0.16f, 0.19f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.28f, 0.33f, 0.40f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.22f, 0.26f, 0.33f, 1.00f);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

Binary file not shown.

View file

@ -1,5 +1,5 @@
; ==============================================================================
; EBLAN ENGINE - Player Combat, Weapon Shooting & Combat HUD
; EBLAN ENGINE - Player Combat, Multi-Weapon Arsenal & Combat HUD
; ==============================================================================
.code
@ -19,6 +19,7 @@ UpdateCombat PROC uses ebx ecx edx esi edi
LOCAL sprX:DWORD, sprY:DWORD
LOCAL transX:DWORD, transY:DWORD
LOCAL sprScreenX:DWORD, wallDepth:DWORD
LOCAL weaponDamage:DWORD, maxHitDist:DWORD, hitTolerance:DWORD
; 1. Decrement cooldowns
cmp playerAttackCooldown, 0
@ -34,9 +35,11 @@ UpdateCombat PROC uses ebx ecx edx esi edi
dec playerHitFlash
@@:
; If player is dead, cannot attack
; If player is dead or game is paused, cannot attack
cmp isPlayerDead, 0
jne combat_done
cmp isGamePaused, 0
jne combat_done
; 2. Check attack input
cmp keyAttack, 0
@ -44,16 +47,51 @@ UpdateCombat PROC uses ebx ecx edx esi edi
cmp playerAttackCooldown, 0
jne combat_done
; Check ammo
cmp playerAmmo, 0
jle combat_dry_fire
; Set weapon parameters based on playerWeapon
mov weaponDamage, 35
mov maxHitDist, 999999
mov hitTolerance, 22
; Fire weapon!
cmp playerWeapon, WEAPON_KNIFE
je setup_knife
cmp playerWeapon, WEAPON_SHOTGUN
je setup_shotgun
jmp setup_pistol
setup_knife:
; Knife: no ammo needed, close range (within 64 units = 1 cell), fast cooldown
mov weaponDamage, 30
mov maxHitDist, 64
mov hitTolerance, 32
mov playerAttackCooldown, 8
mov playerFlashTimer, 3
jmp fire_hitscan
setup_shotgun:
; Shotgun: needs 2 ammo, high damage (70), wide spread (hitTolerance 40), long cooldown
cmp playerAmmo, 2
jl combat_dry_fire
sub playerAmmo, 2
mov weaponDamage, 70
mov maxHitDist, 999999
mov hitTolerance, 40
mov playerAttackCooldown, 22
mov playerFlashTimer, 7
jmp fire_hitscan
setup_pistol:
; Pistol: needs 1 ammo, accurate (hitTolerance 22), medium cooldown (12)
cmp playerAmmo, 1
jl combat_dry_fire
dec playerAmmo
mov playerAttackCooldown, 10
mov weaponDamage, 35
mov maxHitDist, 999999
mov hitTolerance, 22
mov playerAttackCooldown, 12
mov playerFlashTimer, 5
; 3. Hitscan check against enemies in center crosshair
fire_hitscan:
; 3. Hitscan check against enemies & destructible barrels in crosshair
; Camera basis for -playerAngle
mov eax, ANGLE_512
sub eax, playerAngle
@ -78,9 +116,24 @@ hitscan_loop:
mov ecx, i
mov eax, [entityType + ecx*4]
cmp eax, ENTITY_TYPE_GUARD
jne next_hitscan_target
; Check if Guard or Barrel
cmp eax, ENTITY_TYPE_GUARD
je hitscan_target_valid
cmp eax, ENTITY_TYPE_BARREL
je hitscan_target_valid
; Also check if prop has barrel texture (11)
mov ebx, i
imul ebx, sizeof SpriteStruct
add ebx, OFFSET spriteList
mov eax, [ebx].SpriteStruct.textureId
cmp eax, 11
je hitscan_target_valid
jmp next_hitscan_target
hitscan_target_valid:
mov eax, [entityState + ecx*4]
cmp eax, ENTITY_STATE_DEAD
je next_hitscan_target
@ -117,6 +170,11 @@ hitscan_loop:
cmp transY, 16
jle next_hitscan_target
; Must be closer than maximum weapon hit distance
mov eax, transY
cmp eax, maxHitDist
jg next_hitscan_target
; Must be closer than the wall at center screen
mov eax, transY
cmp eax, wallDepth
@ -141,12 +199,12 @@ hitscan_loop:
add eax, SCREEN_HALF_W
mov sprScreenX, eax
; Crosshair hitbox: within 22 pixels of center (SCREEN_HALF_W = 160)
; Crosshair hitbox check
sub eax, SCREEN_HALF_W
cdq
xor eax, edx
sub eax, edx ; abs(sprScreenX - 160)
cmp eax, 22
cmp eax, hitTolerance
jg next_hitscan_target
; Check if closer than previous target
@ -165,10 +223,21 @@ hitscan_eval:
cmp closestIdx, -1
je combat_done
; Apply damage to hit guard
mov ecx, closestIdx
mov eax, [entityType + ecx*4]
; Check if hit an explosive barrel!
cmp eax, ENTITY_TYPE_BARREL
je explode_barrel
mov ebx, closestIdx
imul ebx, sizeof SpriteStruct
add ebx, OFFSET spriteList
cmp [ebx].SpriteStruct.textureId, 11
je explode_barrel
; Hit an enemy guard!
mov eax, [entityHealth + ecx*4]
sub eax, 35
sub eax, weaponDamage
jns @F
xor eax, eax
@@:
@ -181,7 +250,7 @@ hitscan_eval:
mov DWORD PTR [entityTimer + ecx*4], 0
add playerScore, 100
; Drop 10 ammo reward
; Drop ammo reward
mov eax, playerAmmo
add eax, 10
cmp eax, playerMaxAmmo
@ -191,6 +260,111 @@ hitscan_eval:
mov playerAmmo, eax
jmp combat_done
explode_barrel:
; Exploding Barrel: Destroy barrel and trigger AOE blast!
mov [entityState + ecx*4], ENTITY_STATE_DEAD
mov ebx, closestIdx
imul ebx, sizeof SpriteStruct
add ebx, OFFSET spriteList
mov [ebx].SpriteStruct.textureId, 0 ; disappear
; Fiery orange-red screen flash
mov playerHitFlash, 8
add playerScore, 200
; AOE damage to all surrounding guards within 128 units (2 cells)
push esi
push edi
mov esi, [ebx].SpriteStruct.x
mov edi, [ebx].SpriteStruct.y
mov i, 0
aoe_loop:
mov eax, i
cmp eax, spriteCount
jge aoe_check_player
mov ecx, i
cmp ecx, closestIdx
je next_aoe
mov eax, [entityType + ecx*4]
cmp eax, ENTITY_TYPE_GUARD
jne next_aoe
cmp [entityState + ecx*4], ENTITY_STATE_DEAD
je next_aoe
mov edx, i
imul edx, sizeof SpriteStruct
add edx, OFFSET spriteList
mov eax, [edx].SpriteStruct.x
sub eax, esi
cdq
xor eax, edx
sub eax, edx
cmp eax, 128
jg next_aoe
mov eax, [edx].SpriteStruct.y
sub eax, edi
cdq
xor eax, edx
sub eax, edx
cmp eax, 128
jg next_aoe
; Within blast: deal 60 blast damage!
mov eax, [entityHealth + ecx*4]
sub eax, 60
jns @F
xor eax, eax
@@:
mov [entityHealth + ecx*4], eax
test eax, eax
jnz next_aoe
mov [entityState + ecx*4], ENTITY_STATE_DEAD
add playerScore, 150
next_aoe:
inc i
jmp aoe_loop
aoe_check_player:
; If player was standing too close to the barrel (< 96 units), player takes blast damage!
mov eax, playerX
sub eax, esi
cdq
xor eax, edx
sub eax, edx
cmp eax, 96
jg aoe_done
mov eax, playerY
sub eax, edi
cdq
xor eax, edx
sub eax, edx
cmp eax, 96
jg aoe_done
; Player blast damage
mov eax, playerHealth
sub eax, 25
jns @F
xor eax, eax
@@:
mov playerHealth, eax
test eax, eax
jnz aoe_done
mov isPlayerDead, 1
aoe_done:
pop edi
pop esi
jmp combat_done
combat_dry_fire:
mov playerAttackCooldown, 12
@ -209,19 +383,75 @@ RenderCombatHUD PROC uses ebx ecx edx esi edi
invoke DrawRect, 160, 116, 1, 9, 00FFFFFFh
invoke DrawRect, 160, 120, 1, 1, 0000FF00h
; 2. Render Retro Weapon at bottom center
; 2. Render Active Weapon at bottom center
cmp playerWeapon, WEAPON_KNIFE
je render_knife_weapon
cmp playerWeapon, WEAPON_SHOTGUN
je render_shotgun_weapon
jmp render_pistol_weapon
render_knife_weapon:
; Knife Combat Animation: thrusts forward when cooldown active
mov gunY, 190
cmp playerFlashTimer, 0
je draw_knife
sub gunY, 12 ; thrust forward!
draw_knife:
; Silver steel blade
invoke DrawRect, 166, gunY, 6, 26, 00D0D0D0h
invoke DrawRect, 168, gunY, 2, 26, 00FFFFFFh ; shine
invoke DrawRect, 167, gunY - 4, 4, 4, 00B0B0B0h ; blade tip
invoke DrawRect, 168, gunY - 7, 2, 3, 00E0E0E0h
; Brass crossguard
mov eax, gunY
add eax, 26
invoke DrawRect, 160, eax, 18, 5, 00D4AF37h
; Wooden grip
mov eax, gunY
add eax, 31
invoke DrawRect, 164, eax, 10, 22, 00502A10h
jmp hud_stats_draw
render_shotgun_weapon:
; Heavy Pump Shotgun
mov gunY, 188
cmp playerFlashTimer, 0
je draw_shotgun_barrels
add gunY, 4 ; heavy recoil!
; Massive dual blast flash!
invoke DrawRect, 146, 155, 28, 22, 00FFAA00h
invoke DrawRect, 150, 158, 20, 16, 00FFFF00h
invoke DrawRect, 154, 162, 12, 10, 00FFFFFFh
draw_shotgun_barrels:
; Twin heavy steel barrels
invoke DrawRect, 148, gunY, 10, 32, 002B2B2Bh
invoke DrawRect, 150, gunY, 6, 32, 00484848h
invoke DrawRect, 162, gunY, 10, 32, 002B2B2Bh
invoke DrawRect, 164, gunY, 6, 32, 00484848h
; Barrel rib connector
invoke DrawRect, 158, gunY, 4, 32, 00222222h
; Wooden pump forend
mov eax, gunY
add eax, 20
invoke DrawRect, 144, eax, 32, 18, 00603818h
invoke DrawRect, 146, eax, 28, 14, 007A4820h
jmp hud_stats_draw
render_pistol_weapon:
mov gunY, 192
cmp playerFlashTimer, 0
je draw_gun_barrel
; Recoil offset
add gunY, 3
je draw_pistol_barrel
add gunY, 3 ; recoil offset
; Muzzle Flash Burst (Bright yellow & white star)
invoke DrawRect, 157, 165, 7, 18, 00FFFF00h
invoke DrawRect, 152, 170, 17, 8, 00FFAA00h
invoke DrawRect, 158, 168, 5, 12, 00FFFFFFh
draw_gun_barrel:
draw_pistol_barrel:
; Weapon barrel & slide
invoke DrawRect, 154, gunY, 13, 28, 003A3A3Ah
invoke DrawRect, 156, gunY, 9, 28, 00555555h
@ -234,6 +464,7 @@ draw_gun_barrel:
add eax, 26
invoke DrawRect, 152, eax, 17, 22, 00453020h ; brown wood grip plates
hud_stats_draw:
; 3. Health Bar
invoke DrawString8x8, 8, 42, OFFSET strHpLabel, COLOR_GREEN
invoke DrawNumber8x8, 34, 42, playerHealth, COLOR_WHITE
@ -273,14 +504,40 @@ hud_ammo_draw:
invoke DrawString8x8, 222, 42, OFFSET strScoreLabel, COLOR_CYAN
invoke DrawNumber8x8, 272, 42, playerScore, COLOR_CYAN
; 6. Keys (if collected)
; 6. Active Weapon Name & Slot Indicators at top left
invoke DrawString8x8, 8, 26, OFFSET strWeaponLabel, COLOR_WHITE
cmp playerWeapon, WEAPON_KNIFE
jne @F
invoke DrawString8x8, 42, 26, OFFSET strWeaponKnife, COLOR_CYAN
jmp draw_weapon_slots
@@:
cmp playerWeapon, WEAPON_SHOTGUN
jne @F
invoke DrawString8x8, 42, 26, OFFSET strWeaponShotgun, 00FFAA00h
jmp draw_weapon_slots
@@:
invoke DrawString8x8, 42, 26, OFFSET strWeaponPistol, COLOR_YELLOW
draw_weapon_slots:
; Draw small weapon slot indicator boxes: [1] [2] [3]
invoke DrawRectOutline, 100, 26, 8, 8, 00555555h
invoke DrawRectOutline, 112, 26, 8, 8, 00555555h
invoke DrawRectOutline, 124, 26, 8, 8, 00555555h
; Highlight active slot
mov eax, playerWeapon
imul eax, 12
add eax, 100
invoke DrawRectOutline, eax, 26, 8, 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:
; 7. Damage Screen Flash
; 8. Damage Screen Flash
cmp playerHitFlash, 0
jle hud_death_check
; Red border vignette
@ -290,7 +547,7 @@ hud_damage_flash:
invoke DrawRect, 317, 0, 3, SCREEN_H, 00FF0000h
hud_death_check:
; 8. Death Overlay Screen
; 9. Death Overlay Screen
cmp isPlayerDead, 0
je combat_hud_done
@ -301,3 +558,28 @@ hud_death_check:
combat_hud_done:
ret
RenderCombatHUD ENDP
; ------------------------------------------------------------------------------
; RenderPauseMenu: Renders stylish retro pause overlay when isGamePaused != 0
; ------------------------------------------------------------------------------
RenderPauseMenu PROC uses ebx ecx edx
cmp isGamePaused, 0
je pause_menu_done
; Semi-transparent dark overlay panel (centered 200x105 at 60, 65)
invoke DrawRect, 60, 65, 200, 105, 00141416h
invoke DrawRectOutline, 60, 65, 200, 105, COLOR_CYAN
invoke DrawRectOutline, 62, 67, 196, 101, COLOR_WHITE
; Header
invoke DrawString8x8, 112, 75, OFFSET strPauseTitle, COLOR_YELLOW
; Menu Options
invoke DrawString8x8, 76, 95, OFFSET strPauseOpt1, COLOR_GREEN
invoke DrawString8x8, 76, 110, OFFSET strPauseOpt2, COLOR_WHITE
invoke DrawString8x8, 76, 125, OFFSET strPauseOpt3, COLOR_CYAN
invoke DrawString8x8, 76, 140, OFFSET strPauseOpt4, 00FF4444h
pause_menu_done:
ret
RenderPauseMenu ENDP

View file

@ -63,8 +63,16 @@ keyAttack db 0
keyAttackHeld db 0
keyRestart db 0
keyRestartHeld db 0
keyWeapon1 db 0
keyWeapon2 db 0
keyWeapon3 db 0
keyWeapon4 db 0
keyPause db 0
keyPauseHeld db 0
isGamePaused dd 0
; Player Combat & Gameplay Stats
playerWeapon dd 1 ; Default: WEAPON_PISTOL
playerHealth dd 100
playerMaxHealth dd 100
playerAmmo dd 50
@ -117,6 +125,15 @@ strHpLabel db "HP:",0
strAmmoLabel db "AMMO:",0
strScoreLabel db "SCORE:",0
strKeysLabel db "KEY:",0
strWeaponLabel db "WPN:",0
strWeaponKnife db "KNIFE",0
strWeaponPistol db "PISTOL",0
strWeaponShotgun db "SHOTGUN",0
strPauseTitle db "== PAUSED ==",0
strPauseOpt1 db "[1] RESUME",0
strPauseOpt2 db "[2] RESTART LEVEL",0
strPauseOpt3 db "[3] MINIMAP RADAR",0
strPauseOpt4 db "[4] QUIT TO WINDOWS",0
strYouDied db "YOU DIED! PRESS [R] TO RESTART",0
cutsceneCount dd 0
cutsceneStringCount dd 0

View file

@ -67,6 +67,16 @@ VK_KEY_TAB equ 9
VK_KEY_R equ 52h
VK_KEY_CONTROL equ 11h
VK_LBUTTON equ 01h
VK_KEY_1 equ 31h
VK_KEY_2 equ 32h
VK_KEY_3 equ 33h
VK_KEY_4 equ 34h
; Weapon Archetypes
WEAPON_KNIFE equ 0 ; Melee blade (no ammo, fast, 30 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_COUNT equ 3
; Entity Archetypes & States
ENTITY_TYPE_PROP equ 0
@ -75,6 +85,7 @@ ENTITY_TYPE_MEDKIT equ 2 ; Health pickup (+25 HP)
ENTITY_TYPE_AMMO equ 3 ; Ammo pickup (+20 Ammo)
ENTITY_TYPE_KEY equ 4 ; Door Key pickup
ENTITY_TYPE_SWITCH equ 5 ; Interactive switch trigger
ENTITY_TYPE_BARREL equ 6 ; Explosive barrel (AOE damage)
ENTITY_STATE_IDLE equ 0
ENTITY_STATE_CHASE equ 1

View file

@ -181,6 +181,77 @@ restart_released:
mov keyRestartHeld, 0
restart_sampled:
; Weapon Selection Keys: 1, 2, 3
invoke GetAsyncKeyState, VK_KEY_1
test ax, 8000h
jz @F
mov playerWeapon, WEAPON_KNIFE
@@:
invoke GetAsyncKeyState, VK_KEY_2
test ax, 8000h
jz @F
mov playerWeapon, WEAPON_PISTOL
@@:
invoke GetAsyncKeyState, VK_KEY_3
test ax, 8000h
jz @F
mov playerWeapon, WEAPON_SHOTGUN
@@:
; Escape / Pause Key
invoke GetAsyncKeyState, VK_KEY_ESCAPE
test ax, 8000h
jz pause_key_released
cmp keyPauseHeld, 0
jne pause_key_sampled
xor isGamePaused, 1
mov keyPauseHeld, 1
jmp pause_key_sampled
pause_key_released:
mov keyPauseHeld, 0
pause_key_sampled:
; If paused, handle pause menu choices
cmp isGamePaused, 0
je not_in_pause_menu
; While paused, mask normal movement & attack
mov keyForward, 0
mov keyBackward, 0
mov keyStrafeLeft, 0
mov keyStrafeRight, 0
mov keyTurnLeft, 0
mov keyTurnRight, 0
mov keyAttack, 0
mov keyAction, 0
; Option 1: Resume
invoke GetAsyncKeyState, VK_KEY_1
test ax, 8000h
jz @F
mov isGamePaused, 0
@@:
; Option 2: Restart level
invoke GetAsyncKeyState, VK_KEY_2
test ax, 8000h
jz @F
mov isGamePaused, 0
mov keyRestart, 1
@@:
; Option 3: Toggle Map
invoke GetAsyncKeyState, VK_KEY_3
test ax, 8000h
jz @F
xor keyShowMap, 1
@@:
; Option 4: Quit
invoke GetAsyncKeyState, VK_KEY_4
test ax, 8000h
jz @F
mov isRunning, 0
@@:
ret
not_in_pause_menu:
cmp isCutsceneActive, 0
je normal_keys_enabled
mov keyForward, 0
@ -193,12 +264,6 @@ restart_sampled:
mov keyAction, 0
mov keyAttack, 0
normal_keys_enabled:
; 11. Escape - Exit application
invoke GetAsyncKeyState, VK_KEY_ESCAPE
test ax, 8000h
jz @F
mov isRunning, 0
@@:
ret
PollInput ENDP

View file

@ -42,10 +42,18 @@ Render3DScene PROC uses ebx ecx edx esi edi
; --------------------------------------------------------------------------
mov edi, OFFSET screenBuffer
mov eax, mapCeilingColor
cmp playerFlashTimer, 0
je @F
add eax, 00202020h ; Dynamic Muzzle Flash ambient lighting boost
@@:
mov ecx, (SCREEN_W * SCREEN_HALF_H)
rep stosd
mov eax, mapFloorColor
cmp playerFlashTimer, 0
je @F
add eax, 00202020h ; Dynamic Muzzle Flash ambient lighting boost
@@:
mov ecx, (SCREEN_W * SCREEN_HALF_H)
rep stosd

View file

@ -133,14 +133,7 @@ WndProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
ret
handle_keydown:
cmp wParam, VK_KEY_ESCAPE
jne check_f12
invoke PostQuitMessage, 0
mov isRunning, 0
xor eax, eax
ret
check_f12:
; Movement and action edges are handled only by PollInput.
; Key edges and ESC pause menu are handled via PollInput
invoke DefWindowProcA, hWnd, uMsg, wParam, lParam
ret
@ -274,9 +267,12 @@ Engine_Frame PROC uses ebx esi edi
dec mapErrorFrames
no_error_tick:
call PollInput
cmp isGamePaused, 0
jne @F
call UpdatePlayer
call UpdateEntities
call UpdateCombat
@@:
; 2. 3D Raycasting & 2D HUD Render
call Render3DScene
@ -286,6 +282,7 @@ no_error_tick:
call RenderCombatHUD
@@:
call ApplyCutsceneFade
call RenderPauseMenu
; 2b. Screenshot request (F12): dump fresh framebuffer to BMP
cmp keyScreenshot, 0

BIN
main.obj

Binary file not shown.