306 lines
9.7 KiB
C++
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
|