DoorNroll/src/render/CyberRenderer.cpp

1969 lines
84 KiB
C++

#include "CyberRenderer.hpp"
#include "hub/HubRoom.hpp"
#include "hub/RestRoom.hpp"
#include "combat/CombatSystem.hpp"
#include "rlgl.h"
#include <cmath>
#include <algorithm>
#include <string>
#include <cstdio>
namespace dnr {
namespace {
// Draw a textured quad in 3D using rlgl
void drawTexturedQuad(Texture2D tex, Vector3 v0, Vector3 v1, Vector3 v2, Vector3 v3, float uTiles = 1.0f, float vTiles = 1.0f, Color tint = WHITE) {
rlDisableBackfaceCulling();
rlSetTexture(tex.id);
rlBegin(RL_QUADS);
rlColor4ub(tint.r, tint.g, tint.b, tint.a);
rlTexCoord2f(0.0f, 0.0f);
rlVertex3f(v0.x, v0.y, v0.z);
rlTexCoord2f(0.0f, vTiles);
rlVertex3f(v1.x, v1.y, v1.z);
rlTexCoord2f(uTiles, vTiles);
rlVertex3f(v2.x, v2.y, v2.z);
rlTexCoord2f(uTiles, 0.0f);
rlVertex3f(v3.x, v3.y, v3.z);
rlEnd();
rlSetTexture(0);
rlEnableBackfaceCulling();
}
// Draw a textured 3D box using rlgl
void drawTexturedBox(Texture2D tex, Vector3 pos, Vector3 size, float uTile = 1.0f, float vTile = 1.0f, Color tint = WHITE) {
float x = pos.x - size.x * 0.5f;
float y = pos.y - size.y * 0.5f;
float z = pos.z - size.z * 0.5f;
float w = size.x;
float h = size.y;
float d = size.z;
rlDisableBackfaceCulling();
rlSetTexture(tex.id);
rlBegin(RL_QUADS);
rlColor4ub(tint.r, tint.g, tint.b, tint.a);
// Front face (+Z)
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x, y + h, z + d);
rlTexCoord2f(0.0f, vTile); rlVertex3f(x, y, z + d);
rlTexCoord2f(uTile, vTile); rlVertex3f(x + w, y, z + d);
rlTexCoord2f(uTile, 0.0f); rlVertex3f(x + w, y + h, z + d);
// Back face (-Z)
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + w, y + h, z);
rlTexCoord2f(0.0f, vTile); rlVertex3f(x + w, y, z);
rlTexCoord2f(uTile, vTile); rlVertex3f(x, y, z);
rlTexCoord2f(uTile, 0.0f); rlVertex3f(x, y + h, z);
// Top face (+Y)
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x, y + h, z);
rlTexCoord2f(0.0f, vTile); rlVertex3f(x, y + h, z + d);
rlTexCoord2f(uTile, vTile); rlVertex3f(x + w, y + h, z + d);
rlTexCoord2f(uTile, 0.0f); rlVertex3f(x + w, y + h, z);
// Bottom face (-Y)
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x, y, z + d);
rlTexCoord2f(0.0f, vTile); rlVertex3f(x, y, z);
rlTexCoord2f(uTile, vTile); rlVertex3f(x + w, y, z);
rlTexCoord2f(uTile, 0.0f); rlVertex3f(x + w, y, z + d);
// Right face (+X)
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + w, y + h, z + d);
rlTexCoord2f(0.0f, vTile); rlVertex3f(x + w, y, z + d);
rlTexCoord2f(uTile, vTile); rlVertex3f(x + w, y, z);
rlTexCoord2f(uTile, 0.0f); rlVertex3f(x + w, y + h, z);
// Left face (-X)
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x, y + h, z);
rlTexCoord2f(0.0f, vTile); rlVertex3f(x, y, z);
rlTexCoord2f(uTile, vTile); rlVertex3f(x, y, z + d);
rlTexCoord2f(uTile, 0.0f); rlVertex3f(x, y + h, z + d);
rlEnd();
rlSetTexture(0);
rlEnableBackfaceCulling();
}
// Procedural generator: Warm Oak Parquet Floor (256x256)
Texture2D createParquetTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 145, 95, 52, 255 });
Color* pixels = (Color*)img.data;
const int plankHeight = 32;
for (int y = 0; y < h; ++y) {
int row = y / plankHeight;
int rowY = y % plankHeight;
bool isHorizontalSeam = (rowY == 0 || rowY == plankHeight - 1);
for (int x = 0; x < w; ++x) {
int shift = (row % 2 == 0) ? 0 : 48;
int plankW = 96;
int plankCol = (x + shift) / plankW;
int plankX = (x + shift) % plankW;
bool isVerticalSeam = (plankX == 0 || plankX == plankW - 1);
if (isHorizontalSeam || isVerticalSeam) {
pixels[y * w + x] = Color{ 48, 28, 14, 255 };
continue;
}
int plankSeed = (row * 17 + plankCol * 31);
int toneOffset = (plankSeed % 31) - 15;
int baseR = 158 + toneOffset;
int baseG = 108 + (toneOffset * 3) / 4;
int baseB = 60 + toneOffset / 2;
float grainSin = std::sin((y * 4.0f + std::sin(x * 0.05f) * 6.0f) * 0.4f);
int grainVal = static_cast<int>(grainSin * 14.0f);
int fiberNoise = ((x * 13 + y * 29) % 9) - 4;
int finalR = std::clamp(baseR + grainVal + fiberNoise, 40, 240);
int finalG = std::clamp(baseG + (grainVal * 3) / 4 + fiberNoise, 25, 200);
int finalB = std::clamp(baseB + grainVal / 2 + fiberNoise, 10, 160);
pixels[y * w + x] = Color{ (unsigned char)finalR, (unsigned char)finalG, (unsigned char)finalB, 255 };
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Luxury Royal Navy Persian/Cyber Rug (256x256)
Texture2D createRugTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 22, 32, 54, 255 });
Color* pixels = (Color*)img.data;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int distEdge = std::min({ x, w - 1 - x, y, h - 1 - y });
int weave = ((x + y) % 2 == 0) ? 6 : -6;
Color c = Color{ 24, 35, 58, 255 };
if (distEdge < 4) {
c = Color{ 210, 185, 130, 255 }; // Fringes
} else if (distEdge >= 6 && distEdge <= 22) {
// Wide ornate gold border
int pattern = ((x / 6 + y / 6) % 2 == 0) ? 35 : -15;
c = Color{ (unsigned char)std::clamp(185 + pattern, 0, 255),
(unsigned char)std::clamp(150 + pattern, 0, 255),
(unsigned char)std::clamp(75 + pattern, 0, 255), 255 };
} else if (distEdge == 24 || distEdge == 25) {
c = Color{ 165, 35, 45, 255 }; // Crimson accent piping
} else if (distEdge >= 28 && distEdge <= 38) {
if ((x / 4) % 2 == (y / 4) % 2) {
c = Color{ 125, 98, 58, 255 };
} else {
c = Color{ 36, 52, 84, 255 };
}
} else {
int cx = w / 2;
int cy = h / 2;
int dx = std::abs(x - cx);
int dy = std::abs(y - cy);
int manhattanDist = dx + dy;
int eucDist = static_cast<int>(std::sqrt(dx * dx + dy * dy));
if (manhattanDist < 60 && manhattanDist > 54) {
c = Color{ 215, 175, 85, 255 }; // Gold diamond outline
} else if (manhattanDist <= 54 && manhattanDist >= 40) {
c = Color{ 150, 32, 48, 255 }; // Crimson medallion body
} else if (eucDist < 25) {
if (eucDist < 8) {
c = Color{ 245, 215, 125, 255 };
} else if ((dx == dy) || dx == 0 || dy == 0) {
c = Color{ 215, 175, 85, 255 };
} else {
c = Color{ 20, 32, 54, 255 };
}
} else if (manhattanDist < 85 && (dx < 12 || dy < 12)) {
c = Color{ 175, 145, 75, 255 };
}
}
int r = std::clamp(c.r + weave, 0, 255);
int g = std::clamp(c.g + weave, 0, 255);
int b = std::clamp(c.b + weave, 0, 255);
pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 };
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Textured Apartment Stucco/Wallpaper (256x256)
Texture2D createWallTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 78, 84, 94, 255 });
Color* pixels = (Color*)img.data;
for (int y = 0; y < h; ++y) {
float bottomShadow = (y > 220) ? (1.0f - (y - 220) / 36.0f * 0.35f) : 1.0f;
float topShadow = (y < 30) ? (0.82f + (y / 30.0f) * 0.18f) : 1.0f;
float vLighting = bottomShadow * topShadow;
for (int x = 0; x < w; ++x) {
int seamMod = x % 64;
int seamShade = 0;
if (seamMod == 0) seamShade = -22;
else if (seamMod == 1) seamShade = +16;
int stipple = ((x * 19 + y * 37 + (x ^ y) * 11) % 15) - 7;
int baseR = static_cast<int>((82 + stipple + seamShade) * vLighting);
int baseG = static_cast<int>((88 + stipple + seamShade) * vLighting);
int baseB = static_cast<int>((98 + stipple + seamShade) * vLighting);
pixels[y * w + x] = Color{
(unsigned char)std::clamp(baseR, 0, 255),
(unsigned char)std::clamp(baseG, 0, 255),
(unsigned char)std::clamp(baseB, 0, 255),
255
};
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Cyberpunk Synthwave Neon Wall Art (128x256)
Texture2D createPosterTexture() {
const int w = 128;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 15, 10, 24, 255 });
Color* pixels = (Color*)img.data;
for (int y = 0; y < h; ++y) {
float ny = (float)y / h;
for (int x = 0; x < w; ++x) {
float nx = (float)x / w;
if (x < 4 || x >= w - 4 || y < 4 || y >= h - 4) {
pixels[y * w + x] = Color{ 25, 25, 30, 255 }; // Metal frame
continue;
}
Color c;
if (ny < 0.65f) {
int r = static_cast<int>(20 + ny * 140);
int g = static_cast<int>(10 + ny * 20);
int b = static_cast<int>(40 + ny * 70);
c = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 };
// Retro Neon Sun
float sdx = (float)(x - 64);
float sdy = (float)(y - 110);
float sDist = std::sqrt(sdx * sdx + sdy * sdy);
if (sDist < 38.0f) {
bool isCutout = (y > 105 && (y % 6 < 2));
if (!isCutout) {
float sunT = (y - 72) / 76.0f;
int sr = 255;
int sg = static_cast<int>(220 - sunT * 180);
int sb = static_cast<int>(40 + sunT * 120);
c = Color{ (unsigned char)sr, (unsigned char)sg, (unsigned char)sb, 255 };
}
}
} else {
float gridY = (ny - 0.65f) / 0.35f;
c = Color{ 10, 12, 22, 255 };
int lineIndex = static_cast<int>(std::pow(gridY, 0.6f) * 10.0f);
int prevIndex = static_cast<int>(std::pow(std::max(0.0f, gridY - 0.015f), 0.6f) * 10.0f);
if (lineIndex != prevIndex) {
c = Color{ 0, 230, 255, 255 };
}
float rayAngle = std::atan2(gridY, nx - 0.5f);
int rayId = static_cast<int>((rayAngle + 3.14159f) * 6.0f);
int rayIdNext = static_cast<int>((std::atan2(gridY, (nx + 0.03f) - 0.5f) + 3.14159f) * 6.0f);
if (rayId != rayIdNext) {
c = Color{ 255, 0, 160, 255 };
}
}
pixels[y * w + x] = c;
}
}
ImageDrawText(&img, "NETRUNNER", 14, 12, 12, Color{ 0, 240, 255, 255 });
ImageDrawText(&img, "2088", 50, 26, 10, Color{ 255, 50, 150, 255 });
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Polished Dark Walnut Desk Wood (256x256)
Texture2D createDeskWoodTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 90, 54, 32, 255 });
Color* pixels = (Color*)img.data;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
float grain1 = std::sin((x * 0.4f + std::sin(y * 0.08f) * 4.0f));
float grain2 = std::sin((x * 1.2f));
int gVal = static_cast<int>(grain1 * 12.0f + grain2 * 6.0f);
int r = std::clamp(92 + gVal, 30, 200);
int g = std::clamp(56 + (gVal * 2) / 3, 20, 150);
int b = std::clamp(34 + gVal / 2, 10, 100);
int sheen = static_cast<int>(std::sin((x + y * 0.5f) * 0.05f) * 10.0f);
if (sheen > 0) {
r = std::clamp(r + sheen, 0, 255);
g = std::clamp(g + sheen, 0, 255);
b = std::clamp(b + sheen, 0, 255);
}
pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 };
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Code Editor / IDE Monitor Screen (512x256)
Texture2D createMonitorCodeTexture() {
const int w = 512;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 18, 20, 28, 255 });
ImageDrawRectangle(&img, 0, 0, w, 22, Color{ 28, 32, 44, 255 });
ImageDrawCircle(&img, 12, 11, 4, Color{ 255, 95, 86, 255 });
ImageDrawCircle(&img, 26, 11, 4, Color{ 255, 189, 46, 255 });
ImageDrawCircle(&img, 40, 11, 4, Color{ 39, 201, 63, 255 });
ImageDrawText(&img, "neural_daemon.cpp - [ACTIVE SESSION]", 60, 6, 10, Color{ 160, 175, 200, 255 });
ImageDrawRectangle(&img, 0, 22, 38, h - 38, Color{ 22, 25, 34, 255 });
ImageDrawRectangle(&img, 0, h - 16, w, 16, Color{ 0, 122, 204, 255 });
ImageDrawText(&img, " UTF-8 C++20 CYBER-LINK: 10 Gbps ICE_SECURITY: BYPASS READY", 8, h - 13, 10, WHITE);
ImageDrawRectangle(&img, w - 45, 22, 45, h - 38, Color{ 22, 24, 32, 255 });
const char* codeLines[] = {
"#include <cyberspace/neural_link.h>",
"namespace netrunner {",
" // Primary ICE Breaker protocol",
" class NeuralExploit : public IDaemon {",
" public:",
" bool injectPayload(uint64_t targetNode) {",
" CipherBuffer buffer = decryptChamber(6);",
" if (!buffer.isValid()) {",
" triggerAlarm(ICE_WARNING);",
" return false; // lockout",
" }",
" log(\"JACK IN SEQUENCE CONFIRMED\");",
" return m_revolver.spinAndFire();",
" }",
" private:",
" CyberRevolver m_revolver{ 6 };",
" };",
"} // namespace netrunner",
"// Awaiting Neural Sync command..."
};
int lineCount = sizeof(codeLines) / sizeof(codeLines[0]);
for (int i = 0; i < lineCount; ++i) {
int lineY = 28 + i * 11;
char numBuf[8];
snprintf(numBuf, sizeof(numBuf), "%02d", i + 1);
ImageDrawText(&img, numBuf, 8, lineY, 10, Color{ 85, 95, 115, 255 });
Color syntaxCol = Color{ 220, 225, 235, 255 };
std::string s = codeLines[i];
if (s.rfind("#include", 0) == 0) syntaxCol = Color{ 210, 140, 230, 255 };
else if (s.find("//") != std::string::npos) syntaxCol = Color{ 100, 210, 115, 255 };
else if (s.find("class") != std::string::npos || s.find("namespace") != std::string::npos) syntaxCol = Color{ 86, 156, 214, 255 };
else if (s.find("bool") != std::string::npos || s.find("uint64_t") != std::string::npos) syntaxCol = Color{ 78, 201, 176, 255 };
else if (s.find("return") != std::string::npos) syntaxCol = Color{ 197, 134, 192, 255 };
else if (s.find("\"") != std::string::npos) syntaxCol = Color{ 206, 145, 120, 255 };
else if (s.find("injectPayload") != std::string::npos || s.find("decryptChamber") != std::string::npos) syntaxCol = Color{ 220, 220, 130, 255 };
ImageDrawText(&img, codeLines[i], 46, lineY, 10, syntaxCol);
ImageDrawRectangle(&img, w - 40, lineY, static_cast<int>(s.length() * 0.8f), 2, syntaxCol);
}
ImageDrawRectangle(&img, 46 + 220, 28 + 18 * 11, 7, 10, Color{ 0, 240, 255, 255 });
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Cyberspace Telemetry Radar Monitor (256x256)
Texture2D createMonitorRadarTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 10, 16, 26, 255 });
ImageDrawRectangle(&img, 0, 0, w, 20, Color{ 16, 26, 42, 255 });
ImageDrawText(&img, "NET SCAN // SUBNET 192.168.0", 8, 5, 10, Color{ 0, 220, 255, 255 });
int cx = w / 2;
int cy = h / 2 + 10;
ImageDrawCircleLines(&img, cx, cy, 30, Color{ 0, 140, 180, 180 });
ImageDrawCircleLines(&img, cx, cy, 60, Color{ 0, 140, 180, 180 });
ImageDrawCircleLines(&img, cx, cy, 90, Color{ 0, 140, 180, 180 });
ImageDrawLine(&img, cx - 100, cy, cx + 100, cy, Color{ 0, 180, 220, 120 });
ImageDrawLine(&img, cx, cy - 100, cx, cy + 100, Color{ 0, 180, 220, 120 });
ImageDrawLine(&img, cx, cy, cx + 65, cy - 65, Color{ 0, 255, 180, 255 });
Vector2 nodes[] = {
{ 70.0f, 90.0f },
{ 180.0f, 80.0f },
{ 160.0f, 190.0f },
{ 90.0f, 180.0f },
{ 128.0f, 138.0f }
};
for (int i = 0; i < 4; ++i) {
ImageDrawLine(&img, (int)nodes[i].x, (int)nodes[i].y, (int)nodes[4].x, (int)nodes[4].y, Color{ 0, 200, 255, 160 });
ImageDrawCircle(&img, (int)nodes[i].x, (int)nodes[i].y, 4, Color{ 255, 190, 0, 255 });
}
ImageDrawCircle(&img, (int)nodes[4].x, (int)nodes[4].y, 6, Color{ 0, 255, 120, 255 });
ImageDrawText(&img, "GATEWAY STATUS: STANDBY", 10, h - 26, 10, Color{ 0, 255, 120, 255 });
ImageDrawText(&img, "ICE BREAKER: READY", 10, h - 14, 10, Color{ 255, 200, 60, 255 });
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Diamond-Quilted Bedspread (256x256)
Texture2D createBedQuiltTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 42, 54, 76, 255 });
Color* pixels = (Color*)img.data;
const int gridSize = 32;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int d1 = (x + y) % gridSize;
int d2 = (x - y + 1000 * gridSize) % gridSize;
bool isSeam = (d1 <= 1 || d2 <= 1);
int distToSeam = std::min({ d1, gridSize - d1, d2, gridSize - d2 });
float puff = std::clamp(distToSeam / (gridSize * 0.5f), 0.0f, 1.0f);
int puffShade = static_cast<int>(puff * 22.0f);
int weave = ((x ^ y) % 3) * 3 - 3;
int r = 40 + puffShade + weave;
int g = 52 + puffShade + weave;
int b = 74 + puffShade + weave;
if (isSeam) {
bool isStitch = ((x / 4 + y / 4) % 2 == 0);
if (isStitch) {
r = 110; g = 135; b = 175;
} else {
r = 24; g = 32; b = 46;
}
}
pixels[y * w + x] = Color{
(unsigned char)std::clamp(r, 0, 255),
(unsigned char)std::clamp(g, 0, 255),
(unsigned char)std::clamp(b, 0, 255),
255
};
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Megacity Night Skyline Window Backdrop (512x256)
Texture2D createCityNightTexture() {
const int w = 512;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 8, 12, 24, 255 });
Color* pixels = (Color*)img.data;
for (int y = 0; y < h; ++y) {
float t = (float)y / h;
int r = static_cast<int>(6 + t * 24);
int g = static_cast<int>(10 + t * 16);
int b = static_cast<int>(20 + t * 35);
for (int x = 0; x < w; ++x) {
pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 };
}
}
// Stars
for (int i = 0; i < 70; ++i) {
int sx = (i * 73 + 17) % w;
int sy = (i * 37 + 5) % (h / 2);
int brightness = 140 + (i * 29) % 115;
ImageDrawPixel(&img, sx, sy, Color{ (unsigned char)brightness, (unsigned char)brightness, 255, 255 });
}
// Distant dark silhouettes
for (int b = 0; b < 24; ++b) {
int bx = b * 22;
int bw = 18 + (b * 7) % 15;
int bh = 70 + (b * 31) % 80;
int by = h - bh;
ImageDrawRectangle(&img, bx, by, bw, bh, Color{ 14, 18, 32, 255 });
}
// Foreground lit skyscrapers
for (int b = 0; b < 16; ++b) {
int bx = b * 32 + (b % 3) * 4;
int bw = 24 + (b * 11) % 18;
int bh = 110 + (b * 47) % 115;
int by = h - bh;
Color bldgCol = Color{ 20, 26, 42, 255 };
ImageDrawRectangle(&img, bx, by, bw, bh, bldgCol);
if (bh > 160) {
ImageDrawLine(&img, bx + bw / 2, by - 25, bx + bw / 2, by, Color{ 60, 70, 90, 255 });
ImageDrawCircle(&img, bx + bw / 2, by - 26, 2, Color{ 255, 40, 40, 255 });
}
for (int wy = by + 12; wy < h - 8; wy += 8) {
for (int wx = bx + 4; wx < bx + bw - 4; wx += 5) {
int litSeed = (wx * 17 + wy * 31 + b * 53);
if (litSeed % 5 == 0) {
Color winCol = Color{ 255, 230, 130, 255 };
if (litSeed % 3 == 0) winCol = Color{ 140, 230, 255, 255 };
else if (litSeed % 7 == 0) winCol = Color{ 255, 120, 160, 255 };
ImageDrawRectangle(&img, wx, wy, 3, 4, winCol);
}
}
}
}
// Traffic streaks
ImageDrawLine(&img, 0, h - 35, w, h - 25, Color{ 255, 140, 0, 120 });
ImageDrawLine(&img, 0, h - 33, w, h - 23, Color{ 0, 230, 255, 100 });
ImageDrawLine(&img, 0, h - 18, w, h - 12, Color{ 255, 50, 80, 140 });
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Brushed Steel & Hazard Stripe Cyber Gateway (256x256)
Texture2D createCyberGateTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 28, 35, 48, 255 });
Color* pixels = (Color*)img.data;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int metalNoise = ((y * 11 + (x ^ y) * 7) % 9) - 4;
Color c = Color{
(unsigned char)std::clamp(32 + metalNoise, 0, 255),
(unsigned char)std::clamp(38 + metalNoise, 0, 255),
(unsigned char)std::clamp(50 + metalNoise, 0, 255),
255
};
int borderDist = std::min({ x, w - 1 - x, y, h - 1 - y });
if (borderDist < 20) {
bool isYellow = (((x + y) / 14) % 2 == 0);
if (isYellow) {
c = Color{ 240, 195, 25, 255 };
} else {
c = Color{ 22, 22, 26, 255 };
}
} else if (borderDist >= 20 && borderDist <= 23) {
c = Color{ 12, 16, 24, 255 };
} else if (borderDist == 26) {
if ((x % 24 == 12 && (y <= 28 || y >= h - 28)) ||
(y % 24 == 12 && (x <= 28 || x >= w - 28))) {
c = Color{ 190, 205, 225, 255 };
}
}
pixels[y * w + x] = c;
}
}
ImageDrawRectangle(&img, 32, h / 2 - 12, w - 64, 24, Color{ 20, 26, 38, 255 });
ImageDrawRectangleLines(&img, Rectangle{ 32.0f, h / 2.0f - 12.0f, (float)(w - 64), 24.0f }, 2, Color{ 0, 180, 220, 200 });
ImageDrawText(&img, "NEURAL DIVER GATEWAY", 46, h / 2 - 8, 12, Color{ 0, 240, 255, 255 });
ImageDrawText(&img, "CYBERSPACE ICE LEVEL 1+", 54, 40, 10, Color{ 240, 195, 25, 255 });
ImageDrawText(&img, "AUTHORIZED RUNNERS ONLY", 50, h - 50, 10, Color{ 220, 50, 60, 255 });
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: High-Tech Gunmetal Dungeon Wall (256x256)
Texture2D createDungeonWallTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 18, 24, 36, 255 });
Color* pixels = (Color*)img.data;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int n = ((x * 13 + y * 29) % 17) - 8;
int r = std::clamp(22 + n, 0, 255);
int g = std::clamp(28 + n, 0, 255);
int b = std::clamp(42 + n, 0, 255);
Color c = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 };
// Panel bevel seams
if (x < 4 || x >= w - 4 || y < 4 || y >= h - 4 ||
y == 85 || y == 86 || y == 170 || y == 171 ||
(x == 128 && (y < 85 || y > 170))) {
c = Color{ 8, 12, 18, 255 };
}
// Hex bolts in corners
for (int bx : { 16, w - 16 }) {
for (int by : { 16, 96, 182, h - 16 }) {
if (std::abs(x - bx) <= 3 && std::abs(y - by) <= 3) {
c = Color{ 130, 160, 200, 255 };
}
}
}
// Vertical glowing cyan data fiber conduit
if (x >= 124 && x <= 132) {
int dist = std::abs(x - 128);
if (dist <= 1) {
c = Color{ 0, 240, 255, 255 };
} else if (dist <= 3) {
c = Color{ 0, 160, 220, 255 };
} else {
c = Color{ 0, 80, 140, 255 };
}
}
// Hazard warning stripes at bottom
if (y >= h - 22 && y < h - 4) {
bool isYellow = (((x + y) / 10) % 2 == 0);
c = isYellow ? Color{ 230, 180, 25, 255 } : Color{ 15, 18, 24, 255 };
}
// Server status LEDs
if (x >= 40 && x <= 85 && y >= 110 && y <= 145) {
if (x % 12 < 4 && y % 10 < 4) {
c = ((x + y) % 3 == 0) ? CyberColors::NeonGreen :
(((x + y) % 3 == 1) ? CyberColors::NeonCyan : CyberColors::NeonOrange);
} else {
c = Color{ 12, 16, 24, 255 };
}
}
pixels[y * w + x] = c;
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Industrial Tech Floor Grating (256x256)
Texture2D createDungeonFloorTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 12, 16, 24, 255 });
Color* pixels = (Color*)img.data;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
bool isMeshLine = (((x + y) % 16 == 0) || ((x - y + 512) % 16 == 0));
bool isGrateHole = (((x + y) % 16 >= 6 && (x + y) % 16 <= 10) &&
((x - y + 512) % 16 >= 6 && (x - y + 512) % 16 <= 10));
Color c = Color{ 20, 28, 40, 255 };
if (isMeshLine) {
c = Color{ 70, 90, 120, 255 };
} else if (isGrateHole) {
bool isCircuit = (x % 64 < 6 || y % 64 < 6);
c = isCircuit ? Color{ 0, 200, 240, 255 } : Color{ 6, 8, 14, 255 };
}
if (x < 6 || x >= w - 6 || y < 6 || y >= h - 6) {
c = Color{ 35, 45, 60, 255 };
if ((x % 32 < 4) && (y % 32 < 4)) {
c = Color{ 140, 165, 195, 255 };
}
}
pixels[y * w + x] = c;
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Industrial Grid Ceiling with Neon Fixture (256x256)
Texture2D createDungeonCeilTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 10, 14, 20, 255 });
Color* pixels = (Color*)img.data;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
Color c = Color{ 12, 16, 24, 255 };
if (x % 64 < 8 || y % 64 < 8) {
c = Color{ 30, 40, 55, 255 };
}
if (x >= 96 && x <= 160 && y >= 32 && y <= 224) {
int distFromCenter = std::abs(x - 128);
if (distFromCenter <= 12) {
c = Color{ 245, 252, 255, 255 };
} else if (distFromCenter <= 22) {
c = Color{ 0, 220, 255, 255 };
} else {
c = Color{ 0, 80, 140, 255 };
}
}
pixels[y * w + x] = c;
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Armored Bulkhead Sliding Door (256x256)
Texture2D createBulkheadDoorTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 22, 30, 44, 255 });
Color* pixels = (Color*)img.data;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
Color c = Color{ 26, 36, 52, 255 };
if (x < 6 || x >= w - 6 || y < 6 || y >= h - 6 || std::abs(x - 128) <= 2) {
c = Color{ 12, 16, 24, 255 };
}
if ((y >= 8 && y <= 32) || (y >= h - 34 && y <= h - 10)) {
bool isYellow = (((x + y) / 14) % 2 == 0);
c = isYellow ? Color{ 235, 185, 20, 255 } : Color{ 18, 20, 26, 255 };
}
if (x == 50 || x == 51 || x == 205 || x == 206) {
c = Color{ 60, 80, 110, 255 };
}
if (x >= 88 && x <= 168 && y >= 96 && y <= 160) {
int dx = std::abs(x - 128);
int dy = std::abs(y - 128);
if (dx <= 32 && dy <= 24) {
c = Color{ 10, 14, 20, 255 };
if (dx <= 20 && dy <= 12) {
c = Color{ 255, 60, 40, 255 };
}
}
}
pixels[y * w + x] = c;
}
}
ImageDrawText(&img, "RESTRICTED ICE", 70, 44, 12, Color{ 0, 220, 255, 255 });
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Structural Door Frame (128x256)
Texture2D createDoorFrameTexture() {
const int w = 128;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 30, 40, 58, 255 });
Color* pixels = (Color*)img.data;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
Color c = Color{ 28, 38, 54, 255 };
if (x < 6 || x >= w - 6) {
c = Color{ 14, 18, 26, 255 };
}
if (x >= 40 && x <= 88) {
c = Color{ 55, 75, 100, 255 };
if (x >= 56 && x <= 72) {
c = Color{ 160, 185, 215, 255 };
}
}
if (x >= w - 18 && x <= w - 6) {
bool isYellow = (((x + y) / 10) % 2 == 0);
c = isYellow ? Color{ 230, 180, 25, 255 } : Color{ 16, 20, 28, 255 };
}
pixels[y * w + x] = c;
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Ancient Palace Ashlar Sandstone Wall with Greek Key Meander (256x256)
Texture2D createAncientStoneWallTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 185, 170, 142, 255 });
Color* pixels = (Color*)img.data;
const int rowH = 64;
for (int y = 0; y < h; ++y) {
int row = y / rowH;
int rowY = y % rowH;
bool isHorizMortar = (rowY <= 2 || rowY >= rowH - 2);
for (int x = 0; x < w; ++x) {
int shift = (row % 2 == 0) ? 0 : 128;
int blockCol = (x + shift) / 128;
int blockX = (x + shift) % 128;
bool isVertMortar = (blockX <= 2 || blockX >= 126);
if (isHorizMortar || isVertMortar) {
pixels[y * w + x] = Color{ 88, 76, 62, 255 };
continue;
}
int blockSeed = (row * 23 + blockCol * 41);
int blockTone = (blockSeed % 25) - 12;
int chiselNoise = ((x * 17 + y * 31 + (x ^ y) * 7) % 17) - 8;
int r = std::clamp(188 + blockTone + chiselNoise, 0, 255);
int g = std::clamp(172 + blockTone + chiselNoise, 0, 255);
int b = std::clamp(144 + blockTone + chiselNoise, 0, 255);
if (row == 0 && rowY >= 14 && rowY <= 48) {
int fretX = x % 32;
bool isFret = (rowY >= 18 && rowY <= 22) || (rowY >= 40 && rowY <= 44) ||
(fretX >= 14 && fretX <= 18 && rowY >= 22 && rowY <= 40);
if (isFret) {
r = std::clamp(r - 45, 0, 255);
g = std::clamp(g - 50, 0, 255);
b = std::clamp(b - 55, 0, 255);
}
}
pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 };
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Ancient Roman Opus Sectile Floor (Terracotta & Marble Flagstones) (256x256)
Texture2D createAncientFloorTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 175, 100, 65, 255 });
Color* pixels = (Color*)img.data;
const int tileSize = 32;
for (int y = 0; y < h; ++y) {
int tY = y / tileSize;
int py = y % tileSize;
bool isHorizGrout = (py <= 1 || py >= tileSize - 1);
for (int x = 0; x < w; ++x) {
int tX = x / tileSize;
int px = x % tileSize;
bool isVertGrout = (px <= 1 || px >= tileSize - 1);
if (isHorizGrout || isVertGrout) {
pixels[y * w + x] = Color{ 58, 48, 40, 255 };
continue;
}
bool isMarble = ((tX + tY) % 2 == 0);
int stoneNoise = ((x * 19 + y * 23) % 11) - 5;
if (isMarble) {
int vein = ((x + y * 2) % 31 == 0 || (x * 2 - y + 500) % 37 == 0) ? -28 : 0;
int r = std::clamp(224 + stoneNoise + vein, 0, 255);
int g = std::clamp(218 + stoneNoise + vein, 0, 255);
int b = std::clamp(205 + stoneNoise + vein, 0, 255);
pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 };
} else {
int r = std::clamp(178 + stoneNoise, 0, 255);
int g = std::clamp(96 + stoneNoise / 2, 0, 255);
int b = std::clamp(62 + stoneNoise / 3, 0, 255);
pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 };
}
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Ancient Timber Ceiling Beams (256x256)
Texture2D createAncientCeilTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 70, 42, 22, 255 });
Color* pixels = (Color*)img.data;
const int beamSpan = 64;
for (int y = 0; y < h; ++y) {
int bY = y % beamSpan;
bool isBeam = (bY >= 14 && bY <= 50);
for (int x = 0; x < w; ++x) {
if (isBeam) {
int grain = static_cast<int>(std::sin((x * 0.08f + std::sin(y * 0.3f)) * 4.0f) * 12.0f);
int fiber = ((x * 13 + y * 29) % 7) - 3;
int r = std::clamp(76 + grain + fiber, 0, 255);
int g = std::clamp(46 + grain / 2 + fiber, 0, 255);
int b = std::clamp(26 + grain / 3 + fiber, 0, 255);
if ((x % 48 >= 22 && x % 48 <= 26) && (bY >= 30 && bY <= 34)) {
r = 35; g = 35; b = 40;
}
pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 };
} else {
int stipple = ((x * 19 + y * 23) % 15) - 7;
int r = std::clamp(142 + stipple, 0, 255);
int g = std::clamp(132 + stipple, 0, 255);
int b = std::clamp(116 + stipple, 0, 255);
pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 };
}
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// Procedural generator: Weathered Ancient Storage Wood & Chest Planks (256x256)
Texture2D createAncientWoodTexture() {
const int w = 256;
const int h = 256;
Image img = GenImageColor(w, h, Color{ 105, 68, 38, 255 });
Color* pixels = (Color*)img.data;
const int plankW = 42;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int px = x % plankW;
bool isSeam = (px == 0 || px == plankW - 1);
if (isSeam) {
pixels[y * w + x] = Color{ 42, 26, 14, 255 };
continue;
}
int grain = static_cast<int>(std::sin((y * 0.12f + std::sin(x * 0.05f)) * 5.0f) * 14.0f);
int noise = ((x * 17 + y * 29) % 9) - 4;
int r = std::clamp(115 + grain + noise, 0, 255);
int g = std::clamp(75 + (grain * 2) / 3 + noise, 0, 255);
int b = std::clamp(45 + grain / 2 + noise, 0, 255);
pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 };
}
}
Texture2D tex = LoadTextureFromImage(img);
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
UnloadImage(img);
return tex;
}
// 3D Ancient Prop Helpers
void drawAncientAmphora(Vector3 pos, float height, Color clayColor) {
DrawCylinder({ pos.x, pos.y + height * 0.05f, pos.z }, height * 0.14f, height * 0.18f, height * 0.10f, 12, clayColor);
DrawCylinder({ pos.x, pos.y + height * 0.40f, pos.z }, height * 0.18f, height * 0.38f, height * 0.60f, 12, clayColor);
DrawCylinder({ pos.x, pos.y + height * 0.72f, pos.z }, height * 0.38f, height * 0.16f, height * 0.20f, 12, clayColor);
DrawCylinder({ pos.x, pos.y + height * 0.88f, pos.z }, height * 0.16f, height * 0.18f, height * 0.20f, 12, clayColor);
DrawCircle3D({ pos.x, pos.y + height, pos.z }, height * 0.20f, { 1.0f, 0.0f, 0.0f }, 90.0f, clayColor);
// Twin loop handles
DrawCube({ pos.x - height * 0.26f, pos.y + height * 0.62f, pos.z }, height * 0.06f, height * 0.28f, height * 0.06f, clayColor);
DrawCube({ pos.x + height * 0.26f, pos.y + height * 0.62f, pos.z }, height * 0.06f, height * 0.28f, height * 0.06f, clayColor);
}
void drawAncientCrate(Texture2D woodTex, Vector3 pos, Vector3 size) {
drawTexturedBox(woodTex, pos, size, 1.0f, 1.0f);
DrawCubeWires(pos, size.x + 0.015f, size.y + 0.015f, size.z + 0.015f, Color{ 145, 105, 55, 255 });
}
void drawMarbleColumn(Vector3 pos, float height, float radius) {
DrawCube({ pos.x, pos.y + 0.12f, pos.z }, radius * 2.4f, 0.24f, radius * 2.4f, Color{ 215, 210, 200, 255 });
DrawCylinder({ pos.x, pos.y + height * 0.5f, pos.z }, radius, radius * 0.95f, height - 0.48f, 16, Color{ 232, 226, 216, 255 });
DrawCylinderWires({ pos.x, pos.y + height * 0.5f, pos.z }, radius + 0.01f, radius * 0.95f + 0.01f, height - 0.48f, 16, Color{ 185, 180, 170, 255 });
DrawCube({ pos.x, pos.y + height - 0.12f, pos.z }, radius * 2.4f, 0.24f, radius * 2.4f, Color{ 215, 210, 200, 255 });
}
void drawMarbleNectarFountain(Vector3 pos, bool recharged, float animTime) {
// Stepped octagonal marble dais
DrawCylinder({ pos.x, pos.y + 0.08f, pos.z }, 1.6f, 1.7f, 0.16f, 16, Color{ 210, 205, 195, 255 });
DrawCylinder({ pos.x, pos.y + 0.24f, pos.z }, 1.3f, 1.4f, 0.16f, 16, Color{ 225, 220, 210, 255 });
// Basin pedestal and upper marble bowl
DrawCylinder({ pos.x, pos.y + 0.65f, pos.z }, 1.0f, 0.45f, 0.65f, 16, Color{ 240, 235, 225, 255 });
DrawCylinderWires({ pos.x, pos.y + 0.65f, pos.z }, 1.02f, 0.47f, 0.66f, 16, Color{ 185, 155, 95, 255 });
// Shimmering restorative nectar pool
float pulse = 0.5f + 0.5f * std::sin(animTime * 3.5f);
Color nectarCol = recharged ? Color{ 80, 230, 160, 235 } :
Color{ 255, 195, static_cast<unsigned char>(60 + pulse * 45), 235 };
DrawCylinder({ pos.x, pos.y + 0.88f, pos.z }, 0.88f, 0.88f, 0.12f, 16, nectarCol);
// Spouting urn in center
DrawCylinder({ pos.x, pos.y + 1.25f, pos.z }, 0.22f, 0.14f, 0.55f, 12, Color{ 185, 110, 70, 255 });
DrawCircle3D({ pos.x, pos.y + 1.05f + pulse * 0.12f, pos.z }, 0.55f, { 1.0f, 0.0f, 0.0f }, 90.0f, nectarCol);
DrawSphere({ pos.x, pos.y + 1.55f, pos.z }, 0.12f + pulse * 0.04f, WHITE);
}
void drawScribeStele(Vector3 pos) {
// Stele base plinth
DrawCube({ pos.x, pos.y + 0.15f, pos.z }, 0.90f, 0.30f, 0.70f, Color{ 195, 185, 170, 255 });
// Inscribed marble monolith
DrawCube({ pos.x, pos.y + 1.15f, pos.z }, 0.60f, 1.70f, 0.30f, Color{ 228, 222, 210, 255 });
DrawCubeWires({ pos.x, pos.y + 1.15f, pos.z }, 0.62f, 1.72f, 0.32f, Color{ 170, 135, 75, 255 });
// Engraved glowing inscription lines on East face (facing room center)
for (float ly = 0.55f; ly <= 1.75f; ly += 0.22f) {
DrawLine3D({ pos.x + 0.31f, pos.y + ly, pos.z - 0.22f },
{ pos.x + 0.31f, pos.y + ly, pos.z + 0.22f },
Color{ 240, 195, 60, 220 });
}
// Bronze reading lectern
DrawCube({ pos.x + 0.26f, pos.y + 0.88f, pos.z }, 0.26f, 0.06f, 0.55f, Color{ 145, 105, 45, 255 });
// Wax tablet ledger
DrawCube({ pos.x + 0.28f, pos.y + 0.93f, pos.z }, 0.18f, 0.02f, 0.38f, Color{ 210, 165, 95, 255 });
}
void drawCraftsmanWorkbench(Texture2D woodTex, Vector3 pos) {
// Heavy timber workbench top
drawTexturedBox(woodTex, { pos.x, pos.y + 0.75f, pos.z }, { 1.0f, 0.14f, 2.6f });
// 4 Heavy timber legs
DrawCube({ pos.x - 0.35f, 0.37f, pos.z - 1.05f }, 0.14f, 0.74f, 0.14f, Color{ 65, 42, 22, 255 });
DrawCube({ pos.x + 0.35f, 0.37f, pos.z - 1.05f }, 0.14f, 0.74f, 0.14f, Color{ 65, 42, 22, 255 });
DrawCube({ pos.x - 0.35f, 0.37f, pos.z + 1.05f }, 0.14f, 0.74f, 0.14f, Color{ 65, 42, 22, 255 });
DrawCube({ pos.x + 0.35f, 0.37f, pos.z + 1.05f }, 0.14f, 0.74f, 0.14f, Color{ 65, 42, 22, 255 });
// Bronze gunsmith anvil
DrawCube({ pos.x - 0.15f, 0.92f, pos.z - 0.60f }, 0.32f, 0.20f, 0.48f, Color{ 135, 95, 45, 255 });
DrawCylinder({ pos.x - 0.15f, 0.96f, pos.z - 0.90f }, 0.02f, 0.12f, 0.24f, 8, Color{ 165, 120, 60, 255 });
// Cartridge vise and bullet mold
DrawCube({ pos.x - 0.12f, 0.88f, pos.z + 0.20f }, 0.22f, 0.12f, 0.36f, Color{ 65, 70, 80, 255 });
DrawCube({ pos.x - 0.14f, 0.96f, pos.z + 0.20f }, 0.04f, 0.08f, 0.20f, Color{ 180, 140, 60, 255 });
// Terracotta crucible with glowing bronze / embers
DrawCylinder({ pos.x - 0.08f, 0.88f, pos.z + 0.75f }, 0.16f, 0.12f, 0.16f, 10, Color{ 180, 85, 45, 255 });
DrawSphere({ pos.x - 0.08f, 0.94f, pos.z + 0.75f }, 0.11f, Color{ 255, 160, 45, 255 });
// Ancient clay oil lamp with flame
DrawCylinder({ pos.x - 0.10f, 0.86f, pos.z - 0.05f }, 0.12f, 0.08f, 0.08f, 8, Color{ 190, 100, 55, 255 });
DrawSphere({ pos.x - 0.10f, 0.93f, pos.z - 0.05f }, 0.05f, Color{ 255, 225, 110, 255 });
}
} // anonymous namespace
CyberRenderer::CyberRenderer() = default;
CyberRenderer::~CyberRenderer() {
cleanup();
}
void CyberRenderer::init() {
m_camera.position = { 0.0f, 0.8f, 0.0f };
m_camera.target = { 0.0f, 0.8f, -1.0f };
m_camera.up = { 0.0f, 1.0f, 0.0f };
m_camera.fovy = 70.0f;
m_camera.projection = CAMERA_PERSPECTIVE;
// 1. Offscreen Render Target for 3D Post-Processing
m_renderTarget = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());
SetTextureFilter(m_renderTarget.texture, TEXTURE_FILTER_BILINEAR);
// 2. Post-Processing GLSL Shader
std::vector<std::pair<std::string, std::string>> shaderPaths = {
{ "assets/shaders/postprocess.vs", "assets/shaders/postprocess.fs" },
{ "../assets/shaders/postprocess.vs", "../assets/shaders/postprocess.fs" }
};
for (const auto& sp : shaderPaths) {
if (FileExists(sp.first.c_str()) && FileExists(sp.second.c_str())) {
m_shaderPostProcess = LoadShader(sp.first.c_str(), sp.second.c_str());
if (m_shaderPostProcess.id != 0) {
m_locTime = GetShaderLocation(m_shaderPostProcess, "u_time");
m_locGlitch = GetShaderLocation(m_shaderPostProcess, "u_glitchIntensity");
m_locRes = GetShaderLocation(m_shaderPostProcess, "u_resolution");
m_postProcessLoaded = true;
break;
}
}
}
generateTextures();
}
void CyberRenderer::cleanup() {
if (m_postProcessLoaded) {
UnloadShader(m_shaderPostProcess);
UnloadRenderTexture(m_renderTarget);
m_postProcessLoaded = false;
}
unloadTextures();
}
void CyberRenderer::generateTextures() {
if (m_texturesLoaded) return;
m_texDungeonWall = createDungeonWallTexture();
m_texDungeonFloor = createDungeonFloorTexture();
m_texDungeonCeil = createDungeonCeilTexture();
m_texDungeonDoor = createBulkheadDoorTexture();
m_texDungeonDoorFrame = createDoorFrameTexture();
m_texParquet = createParquetTexture();
m_texRug = createRugTexture();
m_texPlasterWall = createWallTexture();
m_texPoster = createPosterTexture();
m_texDeskWood = createDeskWoodTexture();
m_texMonitorCode = createMonitorCodeTexture();
m_texMonitorRadar = createMonitorRadarTexture();
m_texBedQuilt = createBedQuiltTexture();
m_texCityNight = createCityNightTexture();
m_texCyberGate = createCyberGateTexture();
m_texAncientWall = createAncientStoneWallTexture();
m_texAncientFloor = createAncientFloorTexture();
m_texAncientCeil = createAncientCeilTexture();
m_texAncientWood = createAncientWoodTexture();
m_texturesLoaded = true;
}
void CyberRenderer::unloadTextures() {
if (!m_texturesLoaded) return;
UnloadTexture(m_texDungeonWall);
UnloadTexture(m_texDungeonFloor);
UnloadTexture(m_texDungeonCeil);
UnloadTexture(m_texDungeonDoor);
UnloadTexture(m_texDungeonDoorFrame);
UnloadTexture(m_texParquet);
UnloadTexture(m_texRug);
UnloadTexture(m_texPlasterWall);
UnloadTexture(m_texPoster);
UnloadTexture(m_texDeskWood);
UnloadTexture(m_texMonitorCode);
UnloadTexture(m_texMonitorRadar);
UnloadTexture(m_texBedQuilt);
UnloadTexture(m_texCityNight);
UnloadTexture(m_texCyberGate);
UnloadTexture(m_texAncientWall);
UnloadTexture(m_texAncientFloor);
UnloadTexture(m_texAncientCeil);
UnloadTexture(m_texAncientWood);
m_texturesLoaded = false;
}
void CyberRenderer::beginPostProcess() {
if (m_postProcessLoaded) {
BeginTextureMode(m_renderTarget);
ClearBackground(Color{ 4, 6, 12, 255 });
}
}
void CyberRenderer::endPostProcess(float animTime) {
if (m_postProcessLoaded) {
EndTextureMode();
float dt = GetFrameTime();
m_glitchIntensity = std::max(0.0f, m_glitchIntensity - dt * 2.5f);
SetShaderValue(m_shaderPostProcess, m_locTime, &animTime, SHADER_UNIFORM_FLOAT);
SetShaderValue(m_shaderPostProcess, m_locGlitch, &m_glitchIntensity, SHADER_UNIFORM_FLOAT);
float res[2] = { (float)GetScreenWidth(), (float)GetScreenHeight() };
SetShaderValue(m_shaderPostProcess, m_locRes, res, SHADER_UNIFORM_VEC2);
BeginShaderMode(m_shaderPostProcess);
DrawTextureRec(m_renderTarget.texture,
Rectangle{ 0.0f, 0.0f, (float)m_renderTarget.texture.width, -(float)m_renderTarget.texture.height },
Vector2{ 0.0f, 0.0f }, WHITE);
EndShaderMode();
}
}
void CyberRenderer::triggerGlitch(float intensity) {
m_glitchIntensity = std::max(m_glitchIntensity, intensity);
}
void CyberRenderer::renderCyberDrone(Vector3 pos, Color coreColor, float scale, float animTime, bool isHit, bool isAttacking) {
float bob = std::sin(animTime * 3.5f) * 0.12f;
Vector3 basePos = { pos.x, pos.y + bob, pos.z };
if (isHit) {
float shakeX = ((std::rand() % 100) / 100.0f - 0.5f) * 0.18f;
float shakeY = ((std::rand() % 100) / 100.0f - 0.5f) * 0.18f;
basePos.x += shakeX;
basePos.y += shakeY;
}
// 1. Armored Faceted Chassis
DrawSphere(basePos, 0.42f * scale, Color{ 22, 28, 42, 255 });
DrawSphereWires(basePos, 0.44f * scale, 8, 8, Color{ 45, 60, 90, 255 });
// 2. Glowing Elemental Core / Reactor
float pulse = 0.5f + 0.5f * std::sin(animTime * (isAttacking ? 14.0f : 5.0f));
Color pulseCol = isHit ? WHITE : (isAttacking ? RED : coreColor);
DrawSphere(basePos, 0.28f * scale, pulseCol);
// 3. Forward Sensory Laser Visor
Vector3 visorPos = { basePos.x, basePos.y + 0.05f * scale, basePos.z - 0.38f * scale };
DrawCube(visorPos, 0.38f * scale, 0.12f * scale, 0.12f * scale, Color{ 14, 18, 26, 255 });
DrawCube(visorPos, 0.34f * scale, 0.08f * scale, 0.14f * scale, isAttacking ? RED : coreColor);
DrawCubeWires(visorPos, 0.36f * scale, 0.10f * scale, 0.14f * scale, WHITE);
// 4. Orbiting Deflection Shields (4 plates rotating around core)
float rot = animTime * 1.6f;
for (int i = 0; i < 4; ++i) {
float angle = rot + i * (3.14159265f * 0.5f);
float dist = 0.68f * scale;
Vector3 shieldPos = {
basePos.x + std::cos(angle) * dist,
basePos.y + std::sin(angle * 2.0f) * 0.10f * scale,
basePos.z + std::sin(angle) * dist
};
DrawCube(shieldPos, 0.18f * scale, 0.38f * scale, 0.06f * scale, Color{ 30, 42, 62, 255 });
DrawCubeWires(shieldPos, 0.19f * scale, 0.39f * scale, 0.07f * scale, coreColor);
}
// 5. Plasma Thruster & Exhaust Plume
Vector3 nozzlePos = { basePos.x, basePos.y - 0.40f * scale, basePos.z };
DrawCylinder(nozzlePos, 0.14f * scale, 0.18f * scale, 0.15f * scale, 8, Color{ 35, 45, 60, 255 });
float flameH = 0.25f + pulse * 0.15f;
Vector3 flamePos = { basePos.x, basePos.y - (0.45f + flameH * 0.5f) * scale, basePos.z };
DrawCylinder(flamePos, 0.10f * scale, 0.01f * scale, flameH * scale, 8, CyberColors::NeonCyan);
DrawSphere({ basePos.x, basePos.y - 0.45f * scale, basePos.z }, 0.08f * scale, WHITE);
}
void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float animTime) {
if (!m_texturesLoaded) {
generateTextures();
}
beginPostProcess();
Vector3 eye = player.getVisualPosition();
m_camera.position = eye;
float rad = player.getVisualYaw() * (3.14159265f / 180.0f);
float dirX = std::sin(rad);
float dirZ = -std::cos(rad);
m_camera.target = { eye.x + dirX, eye.y, eye.z + dirZ };
BeginMode3D(m_camera);
const float tileSize = 2.0f;
const float wallHeight = 2.0f;
int pX = player.getGridX();
int pY = player.getGridY();
int renderDist = 7;
int minX = std::max(0, pX - renderDist);
int maxX = std::min(dungeon.getWidth() - 1, pX + renderDist);
int minY = std::max(0, pY - renderDist);
int maxY = std::min(dungeon.getHeight() - 1, pY + renderDist);
for (int y = minY; y <= maxY; ++y) {
for (int x = minX; x <= maxX; ++x) {
Vector3 center = { x * tileSize, 0.0f, y * tileSize };
TileType tile = dungeon.getTile(x, y);
// Atmospheric depth fog
float dist = std::sqrt(static_cast<float>((x - pX) * (x - pX) + (y - pY) * (y - pY)));
float fog = std::clamp((dist - 1.2f) / (renderDist - 1.2f), 0.0f, 1.0f);
float b = 1.0f - fog * 0.85f;
Color tileTint = Color{ static_cast<unsigned char>(255 * b), static_cast<unsigned char>(255 * b), static_cast<unsigned char>(255 * b), 255 };
if (tile == TileType::WALL) {
Vector3 wallCenter = { center.x, wallHeight * 0.5f, center.z };
Vector3 wallSize = { tileSize, wallHeight, tileSize };
drawTexturedBox(m_texDungeonWall, wallCenter, wallSize, 1.0f, 1.0f, tileTint);
Vector3 accentCenter = { center.x, wallHeight * 0.5f, center.z };
DrawCubeWires(accentCenter, wallSize.x * 1.008f, 0.06f, wallSize.z * 1.008f,
Color{ 0, static_cast<unsigned char>(220 * b), static_cast<unsigned char>(255 * b), 255 });
} else {
// Textured Floor
drawTexturedQuad(m_texDungeonFloor,
{ center.x - tileSize * 0.5f, -0.01f, center.z - tileSize * 0.5f },
{ center.x - tileSize * 0.5f, -0.01f, center.z + tileSize * 0.5f },
{ center.x + tileSize * 0.5f, -0.01f, center.z + tileSize * 0.5f },
{ center.x + tileSize * 0.5f, -0.01f, center.z - tileSize * 0.5f },
1.0f, 1.0f, tileTint);
// Textured Ceiling
drawTexturedQuad(m_texDungeonCeil,
{ center.x + tileSize * 0.5f, wallHeight, center.z - tileSize * 0.5f },
{ center.x + tileSize * 0.5f, wallHeight, center.z + tileSize * 0.5f },
{ center.x - tileSize * 0.5f, wallHeight, center.z + tileSize * 0.5f },
{ center.x - tileSize * 0.5f, wallHeight, center.z - tileSize * 0.5f },
1.0f, 1.0f, tileTint);
if (tile == TileType::DOOR_LOCKED || tile == TileType::DOOR_UNLOCKED) {
bool locked = (tile == TileType::DOOR_LOCKED);
bool wallLeftRight = (dungeon.getTile(x - 1, y) == TileType::WALL && dungeon.getTile(x + 1, y) == TileType::WALL);
if (wallLeftRight) {
Vector3 leftPost = { center.x - 0.9f, wallHeight * 0.5f, center.z };
drawTexturedBox(m_texDungeonDoorFrame, leftPost, { 0.2f, wallHeight, 0.35f }, 1.0f, 1.0f, tileTint);
Vector3 rightPost = { center.x + 0.9f, wallHeight * 0.5f, center.z };
drawTexturedBox(m_texDungeonDoorFrame, rightPost, { 0.2f, wallHeight, 0.35f }, 1.0f, 1.0f, tileTint);
Vector3 topLintel = { center.x, wallHeight - 0.1f, center.z };
drawTexturedBox(m_texDungeonDoorFrame, topLintel, { 1.8f, 0.2f, 0.35f }, 2.0f, 1.0f, tileTint);
if (locked) {
Vector3 doorBody = { center.x, (wallHeight - 0.2f) * 0.5f, center.z };
drawTexturedBox(m_texDungeonDoor, doorBody, { 1.6f, wallHeight - 0.2f, 0.2f }, 1.0f, 1.0f, tileTint);
float pulse = 0.5f + 0.5f * std::sin(animTime * 6.0f);
Color lockCol = (pulse > 0.5f) ? CyberColors::NeonOrange : RED;
Vector3 lockCore = { center.x, 0.9f, center.z };
DrawCube(lockCore, 0.4f, 0.25f, 0.24f, lockCol);
DrawCubeWires(lockCore, 0.42f, 0.27f, 0.26f, CyberColors::NeonYellow);
} else {
Vector3 leftSlab = { center.x - 0.7f, (wallHeight - 0.2f) * 0.5f, center.z };
Vector3 rightSlab = { center.x + 0.7f, (wallHeight - 0.2f) * 0.5f, center.z };
drawTexturedBox(m_texDungeonDoor, leftSlab, { 0.25f, wallHeight - 0.2f, 0.18f }, 0.5f, 1.0f, tileTint);
drawTexturedBox(m_texDungeonDoor, rightSlab, { 0.25f, wallHeight - 0.2f, 0.18f }, 0.5f, 1.0f, tileTint);
}
} else {
Vector3 northPost = { center.x, wallHeight * 0.5f, center.z - 0.9f };
drawTexturedBox(m_texDungeonDoorFrame, northPost, { 0.35f, wallHeight, 0.2f }, 1.0f, 1.0f, tileTint);
Vector3 southPost = { center.x, wallHeight * 0.5f, center.z + 0.9f };
drawTexturedBox(m_texDungeonDoorFrame, southPost, { 0.35f, wallHeight, 0.2f }, 1.0f, 1.0f, tileTint);
Vector3 topLintel = { center.x, wallHeight - 0.1f, center.z };
drawTexturedBox(m_texDungeonDoorFrame, topLintel, { 0.35f, 0.2f, 1.8f }, 1.0f, 2.0f, tileTint);
if (locked) {
Vector3 doorBody = { center.x, (wallHeight - 0.2f) * 0.5f, center.z };
drawTexturedBox(m_texDungeonDoor, doorBody, { 0.2f, wallHeight - 0.2f, 1.6f }, 1.0f, 1.0f, tileTint);
float pulse = 0.5f + 0.5f * std::sin(animTime * 6.0f);
Color lockCol = (pulse > 0.5f) ? CyberColors::NeonOrange : RED;
Vector3 lockCore = { center.x, 0.9f, center.z };
DrawCube(lockCore, 0.24f, 0.25f, 0.4f, lockCol);
DrawCubeWires(lockCore, 0.26f, 0.27f, 0.42f, CyberColors::NeonYellow);
} else {
Vector3 northSlab = { center.x, (wallHeight - 0.2f) * 0.5f, center.z - 0.7f };
Vector3 southSlab = { center.x, (wallHeight - 0.2f) * 0.5f, center.z + 0.7f };
drawTexturedBox(m_texDungeonDoor, northSlab, { 0.18f, wallHeight - 0.2f, 0.25f }, 0.5f, 1.0f, tileTint);
drawTexturedBox(m_texDungeonDoor, southSlab, { 0.18f, wallHeight - 0.2f, 0.25f }, 0.5f, 1.0f, tileTint);
}
}
} else if (tile == TileType::LOOT_CACHE) {
float floatY = 0.6f + std::sin(animTime * 3.0f + x) * 0.12f;
Vector3 lootPos = { center.x, floatY, center.z };
DrawCubeWires(lootPos, 0.6f, 0.6f, 0.6f, CyberColors::NeonYellow);
DrawCube(lootPos, 0.35f, 0.35f, 0.35f, Color{ 255, 230, 0, 180 });
} else if (tile == TileType::EXIT_PORTAL) {
float pulse = 0.5f + 0.5f * std::sin(animTime * 4.0f);
// 1. Raised Octagonal Platform Dais with hazard glow
Vector3 basePos = { center.x, 0.10f, center.z };
DrawCylinder(basePos, 0.95f, 1.0f, 0.20f, 8, Color{ 22, 30, 44, 255 });
DrawCylinderWires(basePos, 0.96f, 1.01f, 0.20f, 8, CyberColors::NeonCyan);
DrawCircle3D({ center.x, 0.21f, center.z }, 0.90f, { 1.0f, 0.0f, 0.0f }, 90.0f, Color{ 0, 240, 255, 120 });
// 2. Four Corner Energy Emitter Pylons with glowing caps
float pylonDist = 0.72f;
Vector3 pylons[4] = {
{ center.x - pylonDist, 0.42f, center.z - pylonDist },
{ center.x + pylonDist, 0.42f, center.z - pylonDist },
{ center.x - pylonDist, 0.42f, center.z + pylonDist },
{ center.x + pylonDist, 0.42f, center.z + pylonDist }
};
for (int p = 0; p < 4; ++p) {
DrawCylinder(pylons[p], 0.06f, 0.08f, 0.65f, 8, Color{ 36, 46, 66, 255 });
DrawCylinderWires(pylons[p], 0.065f, 0.085f, 0.65f, 8, CyberColors::NeonCyan);
DrawSphere({ pylons[p].x, 0.76f, pylons[p].z }, 0.08f, Color{ 0, 255, 200, 255 });
}
// 3. Three Concentric Levitating Counter-Rotating Neon Rings
float ring1Y = 0.42f + 0.05f * std::sin(animTime * 3.0f);
float ring2Y = 0.88f + 0.07f * std::sin(animTime * 3.5f + 1.2f);
float ring3Y = 1.34f + 0.05f * std::sin(animTime * 2.8f + 2.4f);
DrawCircle3D({ center.x, ring1Y, center.z }, 0.75f, { 1.0f, 0.0f, 0.0f }, 90.0f,
Color{ 0, 255, 180, static_cast<unsigned char>(160 + pulse * 95) });
DrawCircle3D({ center.x, ring2Y, center.z }, 0.55f, { 1.0f, 0.0f, 0.0f }, 90.0f,
Color{ 0, 240, 255, static_cast<unsigned char>(180 + pulse * 75) });
DrawCircle3D({ center.x, ring3Y, center.z }, 0.35f, { 1.0f, 0.0f, 0.0f }, 90.0f,
Color{ 180, 0, 255, static_cast<unsigned char>(160 + pulse * 95) });
// 4. Central Swirling Singularity Core
Vector3 corePos = { center.x, 0.85f + pulse * 0.05f, center.z };
DrawCylinder(corePos, 0.20f + pulse * 0.03f, 0.20f + pulse * 0.03f, 1.3f, 16,
Color{ 0, 230, 255, static_cast<unsigned char>(130 + pulse * 80) });
DrawCylinderWires(corePos, 0.23f, 0.23f, 1.32f, 8, CyberColors::NeonGreen);
DrawSphere(corePos, 0.18f + pulse * 0.04f, Color{ 255, 255, 255, 220 });
// 5. Overhead Holographic Beacon Frame
Vector3 signPos = { center.x, wallHeight - 0.20f, center.z };
DrawCube(signPos, 1.3f, 0.18f, 0.06f, Color{ 10, 16, 26, 240 });
DrawCubeWires(signPos, 1.32f, 0.20f, 0.08f, CyberColors::NeonCyan);
}
}
}
}
// Render Corridor Patrol Enemies as 3D Cyber-Drones
auto& enemies = const_cast<Dungeon&>(dungeon).getEnemies();
for (const auto& me : enemies) {
if (!me.alive) continue;
if (std::abs(me.x - pX) > renderDist || std::abs(me.y - pY) > renderDist) continue;
Vector3 ePos = { me.x * tileSize, 0.9f, me.y * tileSize };
renderCyberDrone(ePos, me.enemy.getColor(), 1.0f, animTime);
}
EndMode3D();
endPostProcess(animTime);
}
void CyberRenderer::renderCombat3D(const Dungeon& dungeon, const Player& player, const CombatSystem& combat, float animTime) {
if (!m_texturesLoaded) {
generateTextures();
}
beginPostProcess();
Vector3 eye = player.getVisualPosition();
m_camera.position = eye;
float rad = player.getVisualYaw() * (3.14159265f / 180.0f);
float dirX = std::sin(rad);
float dirZ = -std::cos(rad);
m_camera.target = { eye.x + dirX, eye.y, eye.z + dirZ };
BeginMode3D(m_camera);
const float tileSize = 2.0f;
const float wallHeight = 2.0f;
int pX = player.getGridX();
int pY = player.getGridY();
int renderDist = 5;
int minX = std::max(0, pX - renderDist);
int maxX = std::min(dungeon.getWidth() - 1, pX + renderDist);
int minY = std::max(0, pY - renderDist);
int maxY = std::min(dungeon.getHeight() - 1, pY + renderDist);
// 1. Render surrounding corridor geometry
for (int y = minY; y <= maxY; ++y) {
for (int x = minX; x <= maxX; ++x) {
Vector3 center = { x * tileSize, 0.0f, y * tileSize };
TileType tile = dungeon.getTile(x, y);
float dist = std::sqrt(static_cast<float>((x - pX) * (x - pX) + (y - pY) * (y - pY)));
float fog = std::clamp((dist - 1.0f) / 4.0f, 0.0f, 1.0f);
float b = 1.0f - fog * 0.85f;
Color tileTint = Color{ static_cast<unsigned char>(255 * b), static_cast<unsigned char>(255 * b), static_cast<unsigned char>(255 * b), 255 };
if (tile == TileType::WALL) {
Vector3 wallCenter = { center.x, wallHeight * 0.5f, center.z };
drawTexturedBox(m_texDungeonWall, wallCenter, { tileSize, wallHeight, tileSize }, 1.0f, 1.0f, tileTint);
} else {
drawTexturedQuad(m_texDungeonFloor,
{ center.x - tileSize * 0.5f, -0.01f, center.z - tileSize * 0.5f },
{ center.x - tileSize * 0.5f, -0.01f, center.z + tileSize * 0.5f },
{ center.x + tileSize * 0.5f, -0.01f, center.z + tileSize * 0.5f },
{ center.x + tileSize * 0.5f, -0.01f, center.z - tileSize * 0.5f },
1.0f, 1.0f, tileTint);
drawTexturedQuad(m_texDungeonCeil,
{ center.x + tileSize * 0.5f, wallHeight, center.z - tileSize * 0.5f },
{ center.x + tileSize * 0.5f, wallHeight, center.z + tileSize * 0.5f },
{ center.x - tileSize * 0.5f, wallHeight, center.z + tileSize * 0.5f },
{ center.x - tileSize * 0.5f, wallHeight, center.z - tileSize * 0.5f },
1.0f, 1.0f, tileTint);
}
}
}
// 2. STAGE ENEMY 3D DRONE IN FRONT OF PLAYER
const auto& enemy = combat.getEnemy();
if (enemy && enemy->isAlive()) {
Vector3 combatDronePos = {
eye.x + dirX * 2.5f,
eye.y + 0.02f,
eye.z + dirZ * 2.5f
};
bool isHit = (combat.getPhase() == CombatPhase::PLAYER_ACTION_ANIM);
bool isAttacking = (combat.getPhase() == CombatPhase::ENEMY_ACTION_ANIM);
renderCyberDrone(combatDronePos, enemy->getColor(), 1.5f, animTime, isHit, isAttacking);
}
EndMode3D();
endPostProcess(animTime);
}
void CyberRenderer::renderHub(const HubRoom& hub, const Player& player, float animTime) {
if (!m_texturesLoaded) {
generateTextures();
}
Vector3 eye = player.getVisualPosition();
m_camera.position = eye;
float rad = player.getVisualYaw() * (3.14159265f / 180.0f);
float dirX = std::sin(rad);
float dirZ = -std::cos(rad);
m_camera.target = { eye.x + dirX, eye.y, eye.z + dirZ };
BeginMode3D(m_camera);
const float tileSize = 2.0f;
const float roomHeight = 2.8f;
const float minCoord = 0.6f;
const float maxCoord = 11.4f;
// 1. FLOOR: Warm Oak Parquet Floor (snug room = 10.8m x 10.8m)
drawTexturedQuad(m_texParquet,
{ minCoord, 0.0f, minCoord },
{ minCoord, 0.0f, maxCoord },
{ maxCoord, 0.0f, maxCoord },
{ maxCoord, 0.0f, minCoord },
5.5f, 5.5f);
// 2. LUXURY RUG: Centered in room (6.0m, 6.0m), size 4.0m x 4.0m
Vector3 rugCenter = { 6.0f, 0.02f, 6.0f };
float rw = 2.0f;
float rl = 2.0f;
drawTexturedQuad(m_texRug,
{ rugCenter.x - rw, 0.02f, rugCenter.z - rl },
{ rugCenter.x - rw, 0.02f, rugCenter.z + rl },
{ rugCenter.x + rw, 0.02f, rugCenter.z + rl },
{ rugCenter.x + rw, 0.02f, rugCenter.z - rl },
1.0f, 1.0f);
// 3. CEILING: Solid ceiling covering the entire room and walls
DrawCube({ 6.0f, roomHeight + 0.10f, 6.0f }, 11.6f, 0.20f, 11.6f, Color{ 32, 36, 44, 255 });
DrawCube({ 6.0f, roomHeight - 0.02f, 6.0f }, 2.4f, 0.04f, 2.4f, Color{ 255, 245, 220, 255 }); // Warm LED ceiling lamp
DrawCubeWires({ 6.0f, roomHeight - 0.02f, 6.0f }, 2.42f, 0.04f, 2.42f, Color{ 180, 140, 80, 255 }); // Brass fixture
// 4. SURROUNDING WALLS (Solid textured boxes with 0.24m thickness)
const float wallThick = 0.24f;
const float halfThick = wallThick * 0.5f;
// South Wall (Z = maxCoord = 11.4f)
drawTexturedBox(m_texPlasterWall,
{ 6.0f, roomHeight * 0.5f, maxCoord + halfThick },
{ 10.8f + wallThick * 2.0f, roomHeight, wallThick },
5.5f, 1.4f);
// West Wall (X = minCoord = 0.6f)
drawTexturedBox(m_texPlasterWall,
{ minCoord - halfThick, roomHeight * 0.5f, 6.0f },
{ wallThick, roomHeight, 10.8f + wallThick * 2.0f },
5.5f, 1.4f);
// North Wall: Split around Gateway (Gateway is at X = 6.0f, width 2.6f: X in [4.7, 7.3])
// Left Segment: X from minCoord (0.6f) to 4.7f => length 4.1f, center 2.65f
drawTexturedBox(m_texPlasterWall,
{ 2.65f, roomHeight * 0.5f, minCoord - halfThick },
{ 4.1f + wallThick, roomHeight, wallThick },
2.1f, 1.4f);
// Right Segment: X from 7.3f to maxCoord (11.4f) => length 4.1f, center 9.35f
drawTexturedBox(m_texPlasterWall,
{ 9.35f, roomHeight * 0.5f, minCoord - halfThick },
{ 4.1f + wallThick, roomHeight, wallThick },
2.1f, 1.4f);
// Lintel above Gateway: X in [4.7f, 7.3f], Y from 2.4f to roomHeight (0.4f height)
drawTexturedBox(m_texPlasterWall,
{ 6.0f, 2.6f, minCoord - halfThick },
{ 2.6f, 0.4f, wallThick },
1.3f, 0.2f);
// East Wall: Split around Window (Window at Z in [3.6f, 8.4f], Y in [0.8f, 2.4f])
// North Segment: Z from minCoord (0.6f) to 3.6f => length 3.0f, center 2.1f
drawTexturedBox(m_texPlasterWall,
{ maxCoord + halfThick, roomHeight * 0.5f, 2.1f },
{ wallThick, roomHeight, 3.0f + wallThick },
1.5f, 1.4f);
// South Segment: Z from 8.4f to maxCoord (11.4f) => length 3.0f, center 9.9f
drawTexturedBox(m_texPlasterWall,
{ maxCoord + halfThick, roomHeight * 0.5f, 9.9f },
{ wallThick, roomHeight, 3.0f + wallThick },
1.5f, 1.4f);
// Below Window: Z in [3.6f, 8.4f] (length 4.8f), Y from 0.0f to 0.8f (height 0.8f, center 0.4f)
drawTexturedBox(m_texPlasterWall,
{ maxCoord + halfThick, 0.4f, 6.0f },
{ wallThick, 0.8f, 4.8f },
2.4f, 0.4f);
// Above Window: Z in [3.6f, 8.4f] (length 4.8f), Y from 2.4f to roomHeight (height 0.4f, center 2.6f)
drawTexturedBox(m_texPlasterWall,
{ maxCoord + halfThick, 2.6f, 6.0f },
{ wallThick, 0.4f, 4.8f },
2.4f, 0.2f);
// Baseboards along bottom of walls inside room
DrawCube({ 6.0f, 0.08f, maxCoord - 0.04f }, 10.8f, 0.16f, 0.08f, Color{ 75, 48, 30, 255 });
DrawCube({ minCoord + 0.04f, 0.08f, 6.0f }, 0.08f, 0.16f, 10.8f, Color{ 75, 48, 30, 255 });
DrawCube({ 2.65f, 0.08f, minCoord + 0.04f }, 4.1f, 0.16f, 0.08f, Color{ 75, 48, 30, 255 });
DrawCube({ 9.35f, 0.08f, minCoord + 0.04f }, 4.1f, 0.16f, 0.08f, Color{ 75, 48, 30, 255 });
DrawCube({ maxCoord - 0.04f, 0.08f, 2.1f }, 0.08f, 0.16f, 3.0f, Color{ 75, 48, 30, 255 });
DrawCube({ maxCoord - 0.04f, 0.08f, 9.9f }, 0.08f, 0.16f, 3.0f, Color{ 75, 48, 30, 255 });
// 5. FRAMED SYNTHWAVE POSTER: South Wall (Z = maxCoord - 0.01f, centered at X = 3.8f, Y = 1.55f)
drawTexturedQuad(m_texPoster,
{ 3.0f, 2.35f, maxCoord - 0.01f },
{ 3.0f, 0.75f, maxCoord - 0.01f },
{ 4.6f, 0.75f, maxCoord - 0.01f },
{ 4.6f, 2.35f, maxCoord - 0.01f },
1.0f, 1.0f);
DrawCubeWires({ 3.8f, 1.55f, maxCoord - 0.005f }, 1.62f, 1.62f, 0.02f, Color{ 20, 20, 25, 255 });
// 6. PANORAMIC WINDOW & NIGHT CITY SKYLINE (East Wall, X = maxCoord = 11.4f)
// Step A: Outdoor Megacity Backdrop Quad (rendered first so depth test passes for everything in front)
drawTexturedQuad(m_texCityNight,
{ 16.5f, 6.0f, -2.0f },
{ 16.5f, -2.0f, -2.0f },
{ 16.5f, -2.0f, 14.0f },
{ 16.5f, 6.0f, 14.0f },
1.0f, 1.0f);
// Step B: 3D Skyscraper buildings outside window (X = 13.5 .. 14.8)
Vector3 bldPositions[4] = {
{ 14.0f, 1.2f, 4.4f },
{ 14.6f, 1.7f, 5.8f },
{ 14.2f, 2.1f, 7.0f },
{ 14.5f, 1.1f, 8.0f }
};
for (int i = 0; i < 4; ++i) {
float bldH = 3.4f + (i % 3) * 1.2f;
Vector3 bldPos = { bldPositions[i].x, bldH * 0.5f - 0.5f, bldPositions[i].z };
DrawCube(bldPos, 1.6f, bldH, 1.4f, Color{ 12, 16, 28, 255 });
DrawCubeWires(bldPos, 1.6f, bldH, 1.4f, (i % 2 == 0) ? CyberColors::NeonCyan : CyberColors::NeonPink);
// Roof antenna beacon
DrawLine3D({ bldPos.x, bldH - 0.5f, bldPos.z }, { bldPos.x, bldH + 0.45f, bldPos.z }, Color{ 140, 150, 170, 255 });
DrawSphere({ bldPos.x, bldH + 0.48f, bldPos.z }, 0.06f, Color{ 255, 40, 40, 255 });
}
// Step C: Window frame, mullions, and wooden sill in the wall opening
DrawCubeWires({ maxCoord, 1.6f, 6.0f }, 0.28f, 1.62f, 4.82f, Color{ 15, 20, 30, 255 });
DrawCube({ maxCoord, 1.6f, 6.0f }, 0.08f, 1.6f, 0.06f, Color{ 22, 28, 40, 255 }); // Center vertical mullion
DrawCube({ maxCoord, 1.6f, 6.0f }, 0.08f, 0.06f, 4.8f, Color{ 22, 28, 40, 255 }); // Center horizontal mullion
DrawCube({ maxCoord - 0.12f, 0.78f, 6.0f }, 0.32f, 0.06f, 5.0f, Color{ 85, 55, 35, 255 }); // Interior wooden sill
// Step D: Translucent Glass Reflections (depth mask DISABLED so city behind is NEVER occluded!)
rlDisableDepthMask();
DrawCube({ maxCoord, 1.6f, 6.0f }, 0.01f, 1.56f, 4.74f, Color{ 0, 160, 230, 20 });
DrawLine3D({ maxCoord - 0.01f, 2.25f, 4.3f }, { maxCoord - 0.01f, 0.95f, 5.6f }, Color{ 255, 255, 255, 55 });
DrawLine3D({ maxCoord - 0.01f, 2.15f, 6.5f }, { maxCoord - 0.01f, 0.95f, 7.7f }, Color{ 255, 255, 255, 45 });
rlEnableDepthMask();
// 7. COMPUTER WORKSTATION DESK (West Wall: against X = minCoord = 0.6f, Z = 5.0f)
Vector3 deskPos = { 1.05f, 0.75f, 5.0f };
Vector3 deskSize = { 0.85f, 0.08f, 2.2f };
drawTexturedBox(m_texDeskWood, deskPos, deskSize, 1.0f, 2.0f);
// Metal legs
DrawCube({ 0.70f, 0.36f, 4.0f }, 0.08f, 0.72f, 0.08f, Color{ 40, 44, 52, 255 });
DrawCube({ 1.40f, 0.36f, 4.0f }, 0.08f, 0.72f, 0.08f, Color{ 40, 44, 52, 255 });
DrawCube({ 0.70f, 0.36f, 6.0f }, 0.08f, 0.72f, 0.08f, Color{ 40, 44, 52, 255 });
DrawCube({ 1.40f, 0.36f, 6.0f }, 0.08f, 0.72f, 0.08f, Color{ 40, 44, 52, 255 });
// Primary Monitor (IDE code editor): facing East (+X) toward the player
Vector3 mon1Pos = { 0.90f, 1.25f, 4.5f };
DrawCube(mon1Pos, 0.08f, 0.75f, 0.95f, Color{ 18, 22, 30, 255 });
DrawCube({ mon1Pos.x, 0.92f, mon1Pos.z }, 0.08f, 0.32f, 0.12f, Color{ 35, 40, 50, 255 });
DrawCube({ mon1Pos.x + 0.05f, 0.79f, mon1Pos.z }, 0.22f, 0.02f, 0.32f, Color{ 35, 40, 50, 255 });
// Primary Monitor Screen Quad with C++ Code
drawTexturedQuad(m_texMonitorCode,
{ mon1Pos.x + 0.045f, mon1Pos.y + 0.34f, mon1Pos.z + 0.44f },
{ mon1Pos.x + 0.045f, mon1Pos.y - 0.34f, mon1Pos.z + 0.44f },
{ mon1Pos.x + 0.045f, mon1Pos.y - 0.34f, mon1Pos.z - 0.44f },
{ mon1Pos.x + 0.045f, mon1Pos.y + 0.34f, mon1Pos.z - 0.44f },
1.0f, 1.0f);
// Secondary Monitor (Cyberspace Radar): angled slightly
Vector3 mon2Pos = { 0.92f, 1.25f, 5.5f };
DrawCube(mon2Pos, 0.08f, 0.75f, 0.85f, Color{ 18, 22, 30, 255 });
DrawCube({ mon2Pos.x, 0.92f, mon2Pos.z }, 0.08f, 0.32f, 0.12f, Color{ 35, 40, 50, 255 });
DrawCube({ mon2Pos.x + 0.05f, 0.79f, mon2Pos.z }, 0.22f, 0.02f, 0.32f, Color{ 35, 40, 50, 255 });
// Secondary Monitor Screen Quad with Radar
drawTexturedQuad(m_texMonitorRadar,
{ mon2Pos.x + 0.045f, mon2Pos.y + 0.34f, mon2Pos.z + 0.40f },
{ mon2Pos.x + 0.045f, mon2Pos.y - 0.34f, mon2Pos.z + 0.40f },
{ mon2Pos.x + 0.045f, mon2Pos.y - 0.34f, mon2Pos.z - 0.40f },
{ mon2Pos.x + 0.045f, mon2Pos.y + 0.34f, mon2Pos.z - 0.40f },
1.0f, 1.0f);
// Keyboard & Mouse
DrawCube({ 1.20f, 0.80f, 4.7f }, 0.30f, 0.02f, 0.70f, Color{ 25, 28, 36, 255 });
DrawCubeWires({ 1.20f, 0.80f, 4.7f }, 0.30f, 0.02f, 0.70f, Color{ 0, 180, 220, 160 });
DrawCube({ 1.20f, 0.80f, 5.30f }, 0.14f, 0.025f, 0.10f, Color{ 30, 35, 45, 255 });
// PC Tower on floor under desk with pulsing cyan LED
Vector3 pcPos = { 0.95f, 0.35f, 3.6f };
DrawCube(pcPos, 0.40f, 0.70f, 0.60f, Color{ 14, 18, 26, 255 });
DrawCubeWires(pcPos, 0.40f, 0.70f, 0.60f, Color{ 35, 45, 60, 255 });
float ledPulse = 0.5f + 0.5f * std::sin(animTime * 3.0f);
DrawCube({ pcPos.x + 0.21f, pcPos.y + 0.25f, pcPos.z }, 0.02f, 0.03f, 0.30f,
Color{ 0, static_cast<unsigned char>(180 + ledPulse * 75), 255, 255 });
// 8. BED (South-East corner: X in [8.5f, 11.2f], Z in [8.8f, 11.2f])
Vector3 bedPos = { 9.8f, 0.30f, 10.0f };
Vector3 bedSize = { 2.3f, 0.45f, 2.2f };
DrawCube({ bedPos.x, 0.08f, bedPos.z }, bedSize.x + 0.1f, 0.16f, bedSize.z + 0.1f, Color{ 55, 35, 20, 255 });
drawTexturedBox(m_texBedQuilt, bedPos, bedSize, 2.0f, 2.0f);
// Bed Headboard against South wall
DrawCube({ bedPos.x, 0.75f, maxCoord - 0.08f }, bedSize.x + 0.1f, 1.1f, 0.10f, Color{ 55, 35, 20, 255 });
// Two soft pillows at head of bed
Vector3 pillow1 = { 9.2f, 0.58f, 10.7f };
Vector3 pillow2 = { 10.4f, 0.58f, 10.7f };
DrawCube(pillow1, 0.9f, 0.12f, 0.50f, Color{ 235, 238, 246, 255 });
DrawCube(pillow2, 0.9f, 0.12f, 0.50f, Color{ 235, 238, 246, 255 });
// 9. CYBERSPACE GATEWAY DOOR (North wall at X = 6.0f, Z = minCoord)
Vector3 gateFramePos = { 6.0f, roomHeight * 0.5f, minCoord + 0.05f };
drawTexturedBox(m_texCyberGate, gateFramePos, { 2.6f, roomHeight, 0.30f }, 1.0f, 1.0f);
float pulse = 0.5f + 0.5f * std::sin(animTime * 4.0f);
Vector3 portalField = { 6.0f, (roomHeight - 0.4f) * 0.5f, minCoord + 0.06f };
DrawCube(portalField, 1.8f, roomHeight - 0.4f, 0.08f, Color{ 0, 230, 160, 140 });
DrawCubeWires(portalField, 1.82f, roomHeight - 0.38f, 0.10f,
Color{ 0, 255, 120, static_cast<unsigned char>(180 + pulse * 75) });
Vector3 signPos = { 6.0f, roomHeight - 0.22f, minCoord + 0.24f };
DrawCube(signPos, 1.6f, 0.25f, 0.06f, Color{ 10, 16, 26, 255 });
DrawCubeWires(signPos, 1.6f, 0.25f, 0.06f, CyberColors::NeonCyan);
EndMode3D();
}
void CyberRenderer::renderRestRoom(const RestRoom& restRoom, const Player& player, float animTime) {
if (!m_texturesLoaded) {
generateTextures();
}
beginPostProcess();
Vector3 eye = player.getVisualPosition();
m_camera.position = eye;
float rad = player.getVisualYaw() * (3.14159265f / 180.0f);
float dirX = std::sin(rad);
float dirZ = -std::cos(rad);
m_camera.target = { eye.x + dirX, eye.y, eye.z + dirZ };
BeginMode3D(m_camera);
const float roomHeight = 3.2f;
const float minCoord = 0.6f;
const float maxCoord = 11.4f;
// 1. Ancient Terracotta & Marble Floor
drawTexturedQuad(m_texAncientFloor,
{ minCoord, -0.01f, minCoord },
{ minCoord, -0.01f, maxCoord },
{ maxCoord, -0.01f, maxCoord },
{ maxCoord, -0.01f, minCoord },
5.0f, 5.0f);
// 2. Ancient Cedar Timber Ceiling Beams
drawTexturedQuad(m_texAncientCeil,
{ maxCoord, roomHeight, minCoord },
{ maxCoord, roomHeight, maxCoord },
{ minCoord, roomHeight, maxCoord },
{ minCoord, roomHeight, minCoord },
5.0f, 5.0f);
// 3. Ancient Ashlar Stone Walls
// South Wall (Z = maxCoord)
drawTexturedBox(m_texAncientWall, { 6.0f, roomHeight * 0.5f, maxCoord }, { 11.0f, roomHeight, 0.3f }, 5.5f, 1.0f);
// East Wall (X = maxCoord)
drawTexturedBox(m_texAncientWall, { maxCoord, roomHeight * 0.5f, 6.0f }, { 0.3f, roomHeight, 11.0f }, 5.5f, 1.0f);
// West Wall (X = minCoord)
drawTexturedBox(m_texAncientWall, { minCoord, roomHeight * 0.5f, 6.0f }, { 0.3f, roomHeight, 11.0f }, 5.5f, 1.0f);
// North Wall: Split around Monumental Palace Portal (X = 6.0f, width 2.6f)
drawTexturedBox(m_texAncientWall, { 2.7f, roomHeight * 0.5f, minCoord }, { 4.2f, roomHeight, 0.3f }, 2.0f, 1.0f);
drawTexturedBox(m_texAncientWall, { 9.3f, roomHeight * 0.5f, minCoord }, { 4.2f, roomHeight, 0.3f }, 2.0f, 1.0f);
drawTexturedBox(m_texAncientWall, { 6.0f, roomHeight - 0.3f, minCoord }, { 2.6f, 0.6f, 0.3f }, 1.3f, 0.3f);
// 4. Classical Fluted Marble Columns in 4 corners
drawMarbleColumn({ minCoord + 0.5f, 0.0f, minCoord + 0.5f }, roomHeight, 0.24f);
drawMarbleColumn({ maxCoord - 0.5f, 0.0f, minCoord + 0.5f }, roomHeight, 0.24f);
drawMarbleColumn({ minCoord + 0.5f, 0.0f, maxCoord - 0.5f }, roomHeight, 0.24f);
drawMarbleColumn({ maxCoord - 0.5f, 0.0f, maxCoord - 0.5f }, roomHeight, 0.24f);
// 5. Wall Torches / Sconces with flickering warm flame
auto drawTorchSconce = [&](Vector3 wallPos, Vector3 outDir) {
// Bronze mount bracket
DrawCube(wallPos, 0.08f, 0.28f, 0.08f, Color{ 145, 105, 45, 255 });
Vector3 torchPos = { wallPos.x + outDir.x * 0.22f, wallPos.y + 0.10f, wallPos.z + outDir.z * 0.22f };
DrawCylinder(torchPos, 0.04f, 0.05f, 0.28f, 8, Color{ 80, 50, 25, 255 });
float flick1 = std::sin(animTime * 14.0f + wallPos.x * 3.5f + wallPos.z * 4.2f) * 0.025f;
float flick2 = std::cos(animTime * 18.0f + wallPos.z * 2.8f) * 0.02f;
Vector3 flamePos = { torchPos.x, torchPos.y + 0.16f + flick1, torchPos.z };
DrawSphere(flamePos, 0.11f + flick2, Color{ 255, 160, 45, 240 });
DrawSphere({ flamePos.x, flamePos.y + 0.04f, flamePos.z }, 0.06f, Color{ 255, 230, 110, 255 });
};
// Torch sconces on walls
drawTorchSconce({ 3.2f, 1.7f, minCoord + 0.16f }, { 0.0f, 0.0f, 1.0f });
drawTorchSconce({ 8.8f, 1.7f, minCoord + 0.16f }, { 0.0f, 0.0f, 1.0f });
drawTorchSconce({ 3.5f, 1.7f, maxCoord - 0.16f }, { 0.0f, 0.0f, -1.0f });
drawTorchSconce({ 8.5f, 1.7f, maxCoord - 0.16f }, { 0.0f, 0.0f, -1.0f });
drawTorchSconce({ minCoord + 0.16f, 1.7f, 4.0f }, { 1.0f, 0.0f, 0.0f });
drawTorchSconce({ minCoord + 0.16f, 1.7f, 8.0f }, { 1.0f, 0.0f, 0.0f });
drawTorchSconce({ maxCoord - 0.16f, 1.7f, 4.0f }, { -1.0f, 0.0f, 0.0f });
drawTorchSconce({ maxCoord - 0.16f, 1.7f, 8.0f }, { -1.0f, 0.0f, 0.0f });
// 6. Monumental Palace Portal to Next Floor (North: X = 6.0f, Z = minCoord)
drawMarbleColumn({ 4.7f, 0.0f, minCoord + 0.16f }, roomHeight - 0.2f, 0.22f);
drawMarbleColumn({ 7.3f, 0.0f, minCoord + 0.16f }, roomHeight - 0.2f, 0.22f);
// Classical marble lintel archway
DrawCube({ 6.0f, roomHeight - 0.40f, minCoord + 0.16f }, 3.0f, 0.32f, 0.38f, Color{ 220, 215, 205, 255 });
DrawCubeWires({ 6.0f, roomHeight - 0.40f, minCoord + 0.16f }, 3.02f, 0.34f, 0.40f, Color{ 175, 140, 80, 255 });
// Dark descending stairway passage into catacombs
DrawCube({ 6.0f, (roomHeight - 0.6f) * 0.5f, minCoord + 0.06f }, 2.0f, roomHeight - 0.6f, 0.10f, Color{ 18, 14, 22, 255 });
float portalPulse = 0.5f + 0.5f * std::sin(animTime * 3.2f);
DrawCube({ 6.0f, (roomHeight - 0.6f) * 0.5f, minCoord + 0.08f }, 1.5f, roomHeight - 0.8f, 0.04f,
Color{ 230, 190, 80, static_cast<unsigned char>(110 + portalPulse * 70) });
// 7. Corner Storeroom Clusters (Terracotta Amphorae & Weathered Crates)
// Northwest Cluster at tile (1, 1) -> (2.0f, 0.0f, 2.0f)
drawAncientCrate(m_texAncientWood, { 2.1f, 0.45f, 2.1f }, { 0.90f, 0.90f, 0.90f });
drawAncientCrate(m_texAncientWood, { 2.1f, 1.15f, 2.1f }, { 0.65f, 0.50f, 0.65f });
drawAncientAmphora({ 1.5f, 0.0f, 1.6f }, 1.15f, Color{ 188, 102, 58, 255 });
drawAncientAmphora({ 2.5f, 0.0f, 1.5f }, 0.95f, Color{ 168, 88, 48, 255 });
drawAncientAmphora({ 1.6f, 0.0f, 2.4f }, 1.05f, Color{ 198, 115, 68, 255 });
// Northeast Cluster at tile (5, 1) -> (10.0f, 0.0f, 2.0f)
drawAncientCrate(m_texAncientWood, { 9.9f, 0.50f, 1.8f }, { 1.00f, 1.00f, 1.00f });
drawAncientCrate(m_texAncientWood, { 10.4f, 0.40f, 2.4f }, { 0.80f, 0.80f, 0.80f });
drawAncientAmphora({ 9.4f, 0.0f, 2.3f }, 1.20f, Color{ 180, 95, 52, 255 });
drawAncientAmphora({ 10.4f, 0.0f, 1.4f }, 0.90f, Color{ 205, 120, 72, 255 });
// Additional storeroom props along south walls
drawAncientAmphora({ 1.4f, 0.0f, 10.2f }, 1.10f, Color{ 175, 92, 50, 255 });
drawAncientAmphora({ 2.2f, 0.0f, 10.5f }, 0.95f, Color{ 192, 108, 62, 255 });
drawAncientCrate(m_texAncientWood, { 10.1f, 0.40f, 10.2f }, { 0.80f, 0.80f, 0.80f });
drawAncientAmphora({ 9.4f, 0.0f, 10.4f }, 1.15f, Color{ 185, 100, 56, 255 });
// 8. Stations:
// West: Scribe's Inscribed Stele / Archive at tile (1, 3) -> (2.0f, 0.0f, 6.0f)
drawScribeStele({ 2.0f, 0.0f, 6.0f });
// Center: Marble Nectar Fountain at tile (3, 3) -> (6.0f, 0.0f, 6.0f)
drawMarbleNectarFountain({ 6.0f, 0.0f, 6.0f }, restRoom.isRecharged(), animTime);
// East: Craftsman's Workbench at tile (5, 3) -> (10.0f, 0.0f, 6.0f)
drawCraftsmanWorkbench(m_texAncientWood, { 10.0f, 0.0f, 6.0f });
// Water quenching urn & parts basket beside workbench
drawAncientAmphora({ 10.0f, 0.0f, 7.6f }, 0.85f, Color{ 140, 110, 85, 255 });
drawAncientCrate(m_texAncientWood, { 10.0f, 0.30f, 4.4f }, { 0.65f, 0.60f, 0.65f });
EndMode3D();
endPostProcess(animTime);
}
} // namespace dnr