#include "dialogue/DialogueOverlay.h" #include "dialogue/DialogueTypes.h" #include "GameConstants.h" #include "Environment.h" #include #include 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(); bodyRenderer = std::make_unique(); choiceRenderer = std::make_unique(); 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(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(i) == hoveredChoiceIndex || static_cast(i) == model.selectedChoice; std::shared_ptr 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(i) == hoveredChoiceIndex || static_cast(i) == model.selectedChoice; const std::array color = (model.choices[i].kind == ChoiceKind::Optional) ? std::array{0.82f, 0.82f, 0.82f, 1.0f} : std::array{ 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{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(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(i); return true; } } return false; } if (model.mode == PresentationMode::Dialogue) { outAdvanceDialogue = rectContains(lastDialogueAdvanceRect, x, y); return outAdvanceDialogue; } return false; } std::shared_ptr 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