feat: enhance Game Studio with multi-weapons, pause menu, exploding barrels, dynamic flash lighting, and IDE API catalog
This commit is contained in:
parent
c1da810bd5
commit
5c568b8cba
16 changed files with 720 additions and 148 deletions
27
AGENTS.md
27
AGENTS.md
|
|
@ -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.
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)")) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
BIN
engine.exe
BIN
engine.exe
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
13
main.asm
13
main.asm
|
|
@ -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
BIN
main.obj
Binary file not shown.
Loading…
Add table
Reference in a new issue