space-game001/src/dialogue/DialogueOverlay.cpp
2026-06-12 14:40:41 +03:00

306 lines
9.7 KiB
C++

#include "dialogue/DialogueOverlay.h"
#include "dialogue/DialogueTypes.h"
#include "GameConstants.h"
#include "Environment.h"
#include <algorithm>
#include <array>
namespace ZL
{
extern float x;
extern float y;
}
namespace ZL::Dialogue {
bool DialogueOverlay::init(Renderer& renderer, const std::string& zipFile) {
rendererRef = &renderer;
zipFilename = zipFile;
textboxTexture = renderer.textureManager.LoadFromPng("resources/dialogue/textbox_bg.png", zipFile);
textboxChoiceTexture = renderer.textureManager.LoadFromPng("resources/dialogue/DialogChoice001.png", zipFile);
choiceMainTexture = renderer.textureManager.LoadFromPng("resources/dialogue/ChoiceNormal001.png", zipFile);
choiceOptionalTexture = renderer.textureManager.LoadFromPng("resources/dialogue/ChoiceNormal001.png", zipFile);
choiceSelectedTexture = renderer.textureManager.LoadFromPng("resources/dialogue/ChoiceSelected001.png", zipFile);
nameRenderer = std::make_unique<TextRenderer>();
bodyRenderer = std::make_unique<TextRenderer>();
choiceRenderer = std::make_unique<TextRenderer>();
return
nameRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 28, zipFile) &&
bodyRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 24, zipFile) &&
choiceRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 22, zipFile);
}
void DialogueOverlay::update(const PresentationModel& model, int deltaMs) {
(void)deltaMs;
if (model.mode == PresentationMode::Hidden) {
hoveredChoiceIndex = -1;
lastChoiceRects.clear();
lastDialogueAdvanceRect = {};
return;
}
if (model.mode != PresentationMode::Choice) {
hoveredChoiceIndex = -1;
}
}
void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) {
if (model.mode != PresentationMode::Dialogue && model.mode != PresentationMode::Choice) {
lastChoiceRects.clear();
lastDialogueAdvanceRect = {};
return;
}
const float W = Environment::projectionWidth;
const float nameX = 312 + 8;
const float nameY = 232 - 38.0f;
const float bodyX = 312 + 8;
const float bodyY = 232 - 78.0f;
const float bodyMaxWidthPx = 1280.0 - nameX - 48.f - 60.f;
float centeredShiftX = ((W - nameX - 48.f - 60.f) - bodyMaxWidthPx)*0.5;
UiRect portraitRect{ 24.0f + 90 + centeredShiftX, 24.0f + 16, 176.0f, 176.0f };
UiRect textboxRect{ 30.f + centeredShiftX, -48.f, 1222.f, 340.0f };
UiRect textboxChoiceRect{ 30.f + centeredShiftX, -90.f, 1200.f, 619.5f };
lastDialogueAdvanceRect = { portraitRect.x, portraitRect.y, textboxRect.x + textboxRect.w - portraitRect.x, textboxRect.h };
if (!portraitQuad.initialized || portraitQuad.rect.w != portraitRect.w || portraitQuad.rect.h != portraitRect.h ||
portraitQuad.rect.x != portraitRect.x || portraitQuad.rect.y != portraitRect.y) {
portraitQuad.rebuild(portraitRect);
}
if (!textboxQuad.initialized || textboxQuad.rect.w != textboxRect.w || textboxQuad.rect.h != textboxRect.h ||
textboxQuad.rect.x != textboxRect.x || textboxQuad.rect.y != textboxRect.y) {
textboxQuad.rebuild(textboxRect);
}
if (!textboxChoiceQuad.initialized || textboxChoiceQuad.rect.w != textboxChoiceRect.w || textboxChoiceQuad.rect.h != textboxChoiceRect.h ||
textboxChoiceQuad.rect.x != textboxChoiceRect.x || textboxChoiceQuad.rect.y != textboxChoiceRect.y) {
textboxChoiceQuad.rebuild(textboxChoiceRect);
}
glEnable(GL_BLEND);
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.PushProjectionMatrix(0.0f, W, 0.0f, Environment::projectionHeight, -10.0f, 10.0f);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.RenderUniform1f("uAlpha", 1.0f);
if (model.mode == PresentationMode::Choice)
{
glBindTexture(GL_TEXTURE_2D, textboxChoiceTexture->getTexID());
renderer.DrawVertexRenderStruct(textboxChoiceQuad.mesh);
}
else
{
glBindTexture(GL_TEXTURE_2D, textboxTexture->getTexID());
renderer.DrawVertexRenderStruct(textboxQuad.mesh);
}
{
renderer.PushMatrix();
if (model.mode == PresentationMode::Choice)
{
renderer.TranslateMatrix(Vector3f{ 20, 230, 0 });
}
auto portrait = loadTextureCached(model.portraitPath);
if (portrait) {
glBindTexture(GL_TEXTURE_2D, portrait->getTexID());
renderer.DrawVertexRenderStruct(portraitQuad.mesh);
}
renderer.PopMatrix();
}
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
lastChoiceRects.clear();
if (!model.speaker.empty()) {
if (model.mode == PresentationMode::Choice)
{
nameRenderer->drawText(model.speaker, nameX+20 + centeredShiftX, nameY+230, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f });
}
else
{
nameRenderer->drawText(model.speaker, nameX + centeredShiftX, nameY, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f });
}
}
const std::string wrappedBody = wrapTextToWidth(model.visibleText, *bodyRenderer, bodyMaxWidthPx, 1.0f);
bodyRenderer->drawText(wrappedBody, bodyX + centeredShiftX, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f });
if (model.mode == PresentationMode::Choice) {
const float choiceStartY = 270.f-35;
const float choiceHeight = 121.f*0.9;
const float choiceSpacing = -30.f;
const float choiceWidth = 913.f * 0.9;
//UiRect textboxRect{ 30.f, -48.f, 1222.f, 340.0f };
if (choiceQuads.size() < model.choices.size()) {
choiceQuads.resize(model.choices.size());
}
glEnable(GL_BLEND);
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.PushProjectionMatrix(0.0f, W, 0.0f, Environment::projectionHeight, -10.0f, 10.0f);
renderer.PushMatrix();
renderer.LoadIdentity();
for (size_t i = 0; i < model.choices.size(); ++i) {
const float cy = choiceStartY + (choiceHeight + choiceSpacing) * static_cast<float>(model.choices.size() - 1 - i);
UiRect rect{ 320.f + centeredShiftX , cy, choiceWidth, choiceHeight };
lastChoiceRects.push_back(rect);
choiceQuads[i].rebuild(rect);
const bool isHighlighted = static_cast<int>(i) == hoveredChoiceIndex || static_cast<int>(i) == model.selectedChoice;
std::shared_ptr<Texture> choiceTexture =
(model.choices[i].kind == ChoiceKind::Optional) ? choiceOptionalTexture : choiceMainTexture;
if (isHighlighted) choiceTexture = choiceSelectedTexture;
glBindTexture(GL_TEXTURE_2D, choiceTexture->getTexID());
renderer.DrawVertexRenderStruct(choiceQuads[i].mesh);
}
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
for (size_t i = 0; i < model.choices.size(); ++i) {
const UiRect& rect = lastChoiceRects[i];
const bool isHighlighted = static_cast<int>(i) == hoveredChoiceIndex || static_cast<int>(i) == model.selectedChoice;
const std::array<float, 4> color = (model.choices[i].kind == ChoiceKind::Optional)
? std::array<float, 4>{0.82f, 0.82f, 0.82f, 1.0f}
: std::array<float, 4>{ 1.0f, 0.93f, 0.65f, 1.0f };
const std::string wrappedChoice = wrapTextToWidth(model.choices[i].text, *choiceRenderer, rect.w - 28.0f, 1.0f);
choiceRenderer->drawText(
wrappedChoice, rect.x + 32.0f, rect.y + 50.f, 1.5f, false,
isHighlighted ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : color
);
}
}
glDisable(GL_BLEND);
}
void DialogueOverlay::handlePointerDown(float x, float y, const PresentationModel& model) {
if (model.mode == PresentationMode::Choice) {
handlePointerMoved(x, y, model);
}
}
void DialogueOverlay::handlePointerMoved(float x, float y, const PresentationModel& model) {
if (model.mode == PresentationMode::Choice) {
hoveredChoiceIndex = -1;
for (size_t i = 0; i < lastChoiceRects.size(); ++i) {
if (rectContains(lastChoiceRects[i], x, y)) {
hoveredChoiceIndex = static_cast<int>(i);
break;
}
}
return;
}
hoveredChoiceIndex = -1;
}
bool DialogueOverlay::handlePointerReleased(float x, float y, const PresentationModel& model,
int& outChoiceIndex, bool& outAdvanceDialogue)
{
outChoiceIndex = -1;
outAdvanceDialogue = false;
if (model.mode == PresentationMode::Choice) {
for (size_t i = 0; i < lastChoiceRects.size(); ++i) {
if (rectContains(lastChoiceRects[i], x, y)) {
outChoiceIndex = static_cast<int>(i);
return true;
}
}
return false;
}
if (model.mode == PresentationMode::Dialogue) {
outAdvanceDialogue = rectContains(lastDialogueAdvanceRect, x, y);
return outAdvanceDialogue;
}
return false;
}
std::shared_ptr<Texture> DialogueOverlay::loadTextureCached(const std::string& path) {
if (path.empty()) return nullptr;
return rendererRef->textureManager.LoadFromPng(path, zipFilename);
}
std::string DialogueOverlay::wrapTextToWidth(
const std::string& input,
const TextRenderer& textRenderer,
float maxWidthPx,
float scale)
{
if (input.empty() || maxWidthPx <= 1.0f) return input;
std::string output;
std::string currentLine;
std::string currentWord;
auto flushLine = [&]() {
if (!currentLine.empty()) {
if (!output.empty()) output.push_back('\n');
output += currentLine;
currentLine.clear();
}
};
auto pushWord = [&](const std::string& word) {
if (word.empty()) return;
if (currentLine.empty()) { currentLine = word; return; }
const std::string candidate = currentLine + " " + word;
if (textRenderer.measureTextWidth(candidate, scale) <= maxWidthPx) {
currentLine = candidate;
}
else {
flushLine();
currentLine = word;
}
};
for (size_t i = 0; i < input.size(); ++i) {
const char ch = input[i];
if (ch == '\n') { pushWord(currentWord); currentWord.clear(); flushLine(); continue; }
if (ch == ' ' || ch == '\t' || ch == '\r') { pushWord(currentWord); currentWord.clear(); continue; }
currentWord.push_back(ch);
}
pushWord(currentWord);
flushLine();
return output;
}
bool DialogueOverlay::rectContains(const UiRect& rect, float x, float y) {
return x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h;
}
} // namespace ZL::Dialogue