OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
dialogmenu.cpp
Go to the documentation of this file.
1#include "dialogmenu.h"
2
3#include <Tempest/Painter>
4#include <Tempest/Log>
5#include <algorithm>
6#include <cassert>
7
8#include "utils/gthfont.h"
9#include "utils/string_frm.h"
10#include "world/objects/npc.h"
11#include "gothic.h"
12#include "inventorymenu.h"
13#include "resources.h"
14
15using namespace Tempest;
16
17bool DialogMenu::Pipe::output(Npc &npc, std::string_view text) {
18 return owner.aiOutput(npc,text);
19 }
20
21bool DialogMenu::Pipe::outputSvm(Npc &npc, std::string_view text) {
22 return owner.aiOutput(npc,text);
23 }
24
25bool DialogMenu::Pipe::outputOv(Npc &npc, std::string_view text) {
26 return owner.aiOutput(npc,text);
27 }
28
29bool DialogMenu::Pipe::printScr(Npc& npc, int time, std::string_view msg, int x, int y, std::string_view font) {
30 return owner.aiPrintScr(npc,time,msg,x,y,font);
31 }
32
33bool DialogMenu::Pipe::close() {
34 return owner.aiClose();
35 }
36
37bool DialogMenu::Pipe::isFinished() {
38 return !owner.isNpcInDialog(nullptr);
39 }
40
42 :trade(trade), pipe(*this) {
43 tex = Resources::loadTexture("DLG_CHOICE.TGA");
44 ambient = Resources::loadTexture("DLG_AMBIENT.TGA");
45
46 setCursorShape(CursorShape::Hidden);
47 setFocusPolicy(NoFocus);
48
51 }
52
56
58 dlgAnimation = Gothic::settingsGetI("GAME","animatedWindows");
59 showSubtitles = Gothic::settingsGetI("GAME","subTitles");
60 showSubtitlesPlayer = Gothic::settingsGetI("GAME","subTitlesPlayer");
61 }
62
63void DialogMenu::tick(uint64_t dt) {
64 if(state==State::PreStart) {
65 except.clear();
66 dlgTrade=false;
67 trade.close();
68 onStart(*this->pl,*this->other);
69 return;
70 }
71
72 if(remPrint>0 || choiceAnimTime>0 || current.time>0 || pscreen.size()>0)
73 update();
74
75 if(remPrint<dt) {
76 for(size_t i=1;i<MAX_PRINT;++i)
77 printMsg[i-1u]=printMsg[i];
78 printMsg[MAX_PRINT-1]=PScreen();
79 remPrint=1500;
80 } else {
81 remPrint-=dt;
82 }
83
84 if(isChoiceMenuActive()) {
85 if(choiceAnimTime+dt>ANIM_TIME)
86 choiceAnimTime = ANIM_TIME; else
87 choiceAnimTime+=dt;
88 } else {
89 if(choiceAnimTime<dt)
90 choiceAnimTime = 0; else
91 choiceAnimTime-=dt;
92 }
93
94 if(current.time<=dt){
95 current.time = 0;
96 if(dlgTrade && !haveToWaitOutput()) {
97 startTrade();
98 }
99 else if(choice.size()==0 && state!=State::Idle && !haveToWaitOutput()) {
100 close();
101 }
102 } else {
103 current.time-=dt;
104 }
105
106 for(size_t i=0;i<pscreen.size();){
107 auto& p=pscreen[i];
108 if(p.time<dt){
109 pscreen.erase(pscreen.begin()+int(i));
110 } else {
111 p.time-=dt;
112 ++i;
113 }
114 }
115
116 // update();
117 }
118
119void DialogMenu::drawTextMultiline(Painter &p, int x, int y, int w, int h, std::string_view txt, bool isPl) {
120 auto sz = processTextMultiline(nullptr,0,0,w,h,txt,isPl);
121 y+=std::max(0, (h-sz.h)/2);
122 processTextMultiline(&p,x,y,w,h,txt,isPl);
123 }
124
125Size DialogMenu::processTextMultiline(Painter* p, int x, int y, int w, int h, std::string_view txt, bool isPl) {
126 const float scale = Gothic::interfaceScale(this);
127 const int pdd = int(10*scale);
128
129 auto fnt = Resources::font(scale);
130 Size ret = {0,0};
131 if(!isPl && other!=nullptr) {
132 y += fnt.pixelSize();
133 auto txt = other->displayName();
134 auto sz = fnt.textSize(w,txt.data());
135 if(p!=nullptr)
136 fnt.drawText(*p,x+(w-sz.w)/2,y,txt.data());
137 h -= int(sz.h);
138 ret.w = std::max(ret.w,sz.w);
139 ret.h += sz.h;
140 }
141
142 fnt = Resources::font(isPl ? Resources::FontType::Normal : Resources::FontType::Yellow, scale);
143
144 y += fnt.pixelSize();
145 ret.h += fnt.pixelSize();
146 if(p!=nullptr) {
147 fnt.drawText(*p,x+pdd, y,
148 w-2*pdd, h, txt, Tempest::AlignHCenter);
149 }
150 auto sz = fnt.textSize(w,txt);
151 ret.w = std::max(ret.w,sz.w);
152 ret.h += sz.h;
153
154 return ret;
155 }
156
158 for(auto& i:printMsg)
159 i=PScreen();
160 pscreen.clear();
161 }
162
164 assert(state==State::Idle);
165 close();
166 clear();
167 }
168
170 return pl!=nullptr && other==pl && pl->interactive()!=nullptr;
171 }
172
174 if(pl && other){
175 auto p0 = pl ->cameraBone();
176 auto p1 = other->cameraBone();
177 camera.setPosition((p0+p1)*0.5f + Vec3(0,50,0));
178 p0 -= p1;
179
180 if(pl==other) {
181 // float a = pl->rotation();
182 // camera.setSpin(PointF(0,a));
183 } else {
184 float l = p0.length();
185 float a = 0;
186 if(curentIsPl) {
187 a = other->rotation()-45;
188 } else {
189 a = pl->rotation()+45;
190 }
191
192 camera.setDialogDistance(l);
193 camera.setSpin(PointF(0,a));
194 }
195 }
196 }
197
198void DialogMenu::openPipe(Npc &player, Npc &npc, AiOuputPipe *&out) {
199 out = &pipe;
200 pl = &player;
201 other = &npc;
202 state = State::PreStart;
203 }
204
205bool DialogMenu::isNpcInDialog(const Npc* npc) const {
206 if(state==State::Idle)
207 return false;
208 return npc==pl || npc==other || npc==nullptr;
209 }
210
211bool DialogMenu::aiOutput(Npc &npc, std::string_view msg) {
212 if(&npc!=pl && &npc!=other){
213 Log::e("unexpected aiOutput call: ",msg.data());
214 return true;
215 }
216
217 if(current.time>0)
218 return false;
219
220 if(pl==&npc) {
221 if(other!=nullptr)
222 other->stopDlgAnim();
223 } else {
224 if(pl!=nullptr)
225 pl->stopDlgAnim();
226 }
227
228 current.txt = Gothic::inst().messageByName(msg);
229 current.msgTime = Gothic::inst().messageTime(msg);
230 current.time = current.msgTime + (dlgAnimation ? ANIM_TIME*2 : 0);
231 currentSnd = soundDevice.load(Resources::loadSoundBuffer(string_frm(msg,".wav")));
232 curentIsPl = (pl==&npc);
233
234 currentSnd.play();
235 npc.setAiOutputBarrier(current.msgTime,false);
236 update();
237 return true;
238 }
239
240bool DialogMenu::aiPrintScr(Npc& npc, int time, std::string_view msg, int x, int y, std::string_view font) {
241 if(current.time>0)
242 return false;
243 auto& f = Resources::font(font, Resources::FontType::Normal, 1.0);
244 Gothic::inst().onPrintScreen(msg,x,y,time,f);
245 return true;
246 }
247
248bool DialogMenu::aiClose() {
249 if(current.time>0){
250 return false;
251 }
252
253 choice.clear();
254 close();
255 state=State::Idle;
256 update();
257 return true;
258 }
259
261 return (state!=State::Idle) || current.time>0;
262 }
263
265 return (current.time>0 || choice.size()>0);
266 }
267
268bool DialogMenu::onStart(Npc &p, Npc &ot) {
269 choice = ot.dialogChoices(p,except,state==State::PreStart);
270 state = State::Active;
271 depth = 0;
272 curentIsPl = true;
273 choiceAnimTime = 0;
274
275 if(choice.size()>0 && choice[0].title.size()==0){
276 // important dialog
277 onEntry(choice[0]);
278 }
279
280 dlgSel=0;
281 update();
282 return true;
283 }
284
285void DialogMenu::printScreen(std::string_view msg, int x, int y, int time, const GthFont &font) {
286 PScreen e;
287 e.txt = msg;
288 e.font = &font;
289 e.time = uint32_t(time*1000)+1000;
290 e.x = x;
291 e.y = y;
292 pscreen.emplace(pscreen.begin(),std::move(e));
293 update();
294 }
295
296void DialogMenu::drawMsg(Tempest::Painter& p, int offsetY) {
297 for(size_t i=0;i<MAX_PRINT;++i){
298 auto& sc = printMsg[i];
299 if(sc.font==nullptr)
300 continue;
301
302 auto& fnt = *sc.font;
303 auto sz = fnt.textSize(sc.txt);
304 int x = (w()-sz.w)/2;
305 fnt.drawText(p, x, offsetY + int(i*2+1)*sz.h, sc.txt);
306 }
307 }
308
309void DialogMenu::print(std::string_view msg) {
310 if(msg.empty())
311 return;
312
313 const float scale = Gothic::interfaceScale(this);
314 for(size_t i=1;i<MAX_PRINT;++i)
315 printMsg[i-1u]=printMsg[i];
316
317 remPrint=1500;
318 PScreen& e=printMsg[MAX_PRINT-1];
319 e.txt = msg;
320 e.font = &Resources::font(scale);
321 e.time = remPrint;
322 e.x = -1;
323 e.y = -1;
324 update();
325 }
326
327void DialogMenu::onDoneText() {
328 choice = Gothic::inst().updateDialog(selected,*pl,*other);
329 dlgSel = 0;
330 if(choice.size()==0){
331 if(depth>0) {
332 onStart(*pl,*other);
333 } else {
334 close();
335 }
336 }
337 }
338
339void DialogMenu::close() {
340 auto prevPl = pl;
341 auto prevNpc = other;
342
343 pl=nullptr;
344 other=nullptr;
345 depth=0;
346 dlgTrade=false;
347 current.time=0;
348 choice.clear();
349 state=State::Idle;
350 currentSnd = SoundEffect();
351 update();
352
353 if(prevPl!=nullptr && prevPl==prevNpc){
354 auto i = prevPl->interactive();
355 if(i!=nullptr)
356 prevPl->setInteraction(nullptr);
357 }
358 if(prevPl){
359 prevPl->stopDlgAnim();
360 }
361 if(prevNpc && prevNpc!=prevPl){
362 prevNpc->stopDlgAnim();
363 }
364 }
365
366bool DialogMenu::haveToWaitOutput() const {
367 if(pl && pl->haveOutput()){
368 return true;
369 }
370 if(other && other->haveOutput()){
371 return true;
372 }
373 return false;
374 }
375
376bool DialogMenu::haveToShowSubtitles(bool isPl) const {
377 return showSubtitles && (showSubtitlesPlayer || !isPl);
378 }
379
380void DialogMenu::startTrade() {
381 if(pl!=nullptr && other!=nullptr)
382 trade.trade(*pl,*other);
383 dlgTrade=false;
384 }
385
386void DialogMenu::skipPhrase() {
387 if(current.time>0) {
388 if(pl!=nullptr)
389 pl->setAiOutputBarrier(0,false);
390 if(other!=nullptr)
391 other->setAiOutputBarrier(0,false);
392 currentSnd = SoundEffect();
393 current.time = 1;
394 }
395 }
396
397void DialogMenu::onEntry(const GameScript::DlgChoice &e) {
398 if(pl && other) {
399 selected = e;
400 depth = 1;
401 dlgTrade = e.isTrade;
402 except.push_back(e.scriptFn);
403 Gothic::inst().dialogExec(e,*pl,*other);
404
405 onDoneText();
406 }
407 }
408
409void DialogMenu::paintEvent(Tempest::PaintEvent &e) {
410 const float scale = Gothic::interfaceScale(this);
411
412 Painter p(e);
413 const uint64_t da = dlgAnimation ? ANIM_TIME : 0;
414 const int dw = std::min(w(),int(600*scale));
415 const int dh = int(100*scale);
416
417 if(current.time>0 && trade.isOpen()==InventoryMenu::State::Closed && haveToShowSubtitles(curentIsPl)) {
418 if(ambient!=nullptr) {
419 int dlgW = dw;
420 int dlgH = dh;
421
422 if(current.time>current.msgTime+da) {
423 float k = 1.f-float(current.time-current.msgTime-da)/float(da);
424 dlgW = int(float(dlgW)*k);
425 dlgH = int(float(dlgH)*k);
426 }
427 else if(current.time<da) {
428 float k = float(current.time)/float(da);
429 dlgW = int(float(dlgW)*k);
430 dlgH = int(float(dlgH)*k);
431 }
432
433 p.setBrush(*ambient);
434 p.drawRect((w()-dlgW)/2, 20+(dh-dlgH)/2, dlgW, dlgH,
435 0,0,ambient->w(),ambient->h());
436 }
437
438 if(current.time>da && current.time<current.msgTime+da)
439 drawTextMultiline(p,(w()-dw)/2,20,dw,dh,current.txt,curentIsPl);
440 }
441
442 paintChoice(e);
443
444 for(size_t i=0;i<pscreen.size();++i){
445 auto& sc = pscreen[i];
446 auto fnt = *sc.font;
447 fnt.setScale(Gothic::interfaceScale(this));
448
449 auto sz = fnt.textSize(sc.txt);
450 auto area = this->size();
451
452 int x = sc.x;
453 int y = sc.y;
454 if(x<0){
455 x = (area.w-sz.w)/2;
456 } else {
457 x = (area.w*x)/100;
458 }
459 if(y<0){
460 y = (area.h-sz.h)/2;
461 } else {
462 y = ((area.h-sz.h)*y)/100+sz.h;
463 }
464 fnt.drawText(p, x, y, sc.txt);
465 }
466 }
467
468void DialogMenu::paintChoice(PaintEvent &e) {
469 if(choice.size()==0)
470 return;
471
472 const float scale = Gothic::interfaceScale(this);
473 const int padd = int(20*scale);
474 const int dw = std::min(w(),int(600*scale));
475 const bool isActive = isChoiceMenuActive();
476
477 int dh = padd*2;
478 for(size_t i=0;i<choice.size();++i){
479 auto fnt = Resources::dialogFont(scale);
480 Size choiceTextSize = fnt.textSize(dw-padd, choice[i].title);
481 dh += choiceTextSize.h;
482 }
483
484 const int y = h()-dh-int(20*scale);
485
486 if(tex!=nullptr && (choiceAnimTime>0 || isActive)) {
487 float k = dlgAnimation ? (float(choiceAnimTime)/float(ANIM_TIME)) : 1.f;
488 int dlgW = int(float(dw)*k);
489 int dlgH = int(float(dh)*k);
490
491 Painter p(e);
492 p.setBrush(*tex);
493 p.drawRect((w()-dlgW)/2,y+(dh-dlgH)/2,dlgW,dlgH,
494 0,0,tex->w(),tex->h());
495 }
496
497 if(choiceAnimTime<ANIM_TIME && dlgAnimation)
498 return;
499
500 if(!isChoiceMenuActive())
501 return;
502
503 int textHeightOffset = padd;
504 Painter p(e);
505 for(size_t i=0;i<choice.size();++i){
506 GthFont fnt;
507 if(i==dlgSel)
508 fnt = Resources::font(Resources::FontType::Hi, scale); else
509 fnt = Resources::dialogFont(scale);
510
511 int x = (w()-dw)/2;
512 Size choiceTextSize = fnt.textSize(dw-padd, choice[i].title);
513 fnt.drawText(p,x+padd,y+padd+textHeightOffset,dw-padd,0,choice[i].title,AlignLeft);
514 textHeightOffset += choiceTextSize.h;
515 }
516 }
517
519 return choice.size()!=0 && current.time==0 && !haveToWaitOutput() && trade.isOpen()==InventoryMenu::State::Closed;
520 }
521
523 if(current.time>0 || haveToWaitOutput()){
524 return;
525 }
526
527 if(dlgSel<choice.size()) {
528 onEntry(choice[dlgSel]);
529 choiceAnimTime = dlgAnimation ? ANIM_TIME : 0;
530 }
531 }
532
533void DialogMenu::mouseDownEvent(MouseEvent &event) {
534 if(state==State::Idle || trade.isOpen()!=InventoryMenu::State::Closed){
535 event.ignore();
536 return;
537 }
538
539 if(event.button==MouseEvent::ButtonLeft) {
540 onSelect();
541 }
542 if(event.button==MouseEvent::ButtonRight) {
543 skipPhrase();
544 }
545 }
546
547void DialogMenu::mouseWheelEvent(MouseEvent &e) {
548 if(state==State::Idle || trade.isOpen()!=InventoryMenu::State::Closed){
549 e.ignore();
550 return;
551 }
552
553 if(e.delta>0)
554 dlgSel--;
555 if(e.delta<0)
556 dlgSel++;
557 dlgSel = (dlgSel+choice.size())%std::max<size_t>(choice.size(),1);
558 update();
559 }
560
561void DialogMenu::keyDownEvent(KeyEvent &e) {
562 if(state==State::Idle || trade.isOpen()!=InventoryMenu::State::Closed){
563 e.ignore();
564 return;
565 }
566
567 if(e.key==Event::K_Return){
568 onSelect();
569 return;
570 }
571 if(e.key==Event::K_W || e.key==Event::K_S || e.key==Event::K_Up || e.key==Event::K_Down || e.key==Event::K_ESCAPE){
572 if(e.key==Event::K_W || e.key==Event::K_Up){
573 dlgSel--;
574 }
575 if(e.key==Event::K_S || e.key==Event::K_Down){
576 dlgSel++;
577 }
578 if(e.key==Event::K_ESCAPE){
579 skipPhrase();
580 }
581 dlgSel = (dlgSel+choice.size())%std::max<size_t>(choice.size(),1);
582 update();
583 return;
584 }
585 e.ignore();
586 }
587
588void DialogMenu::keyUpEvent(KeyEvent &event) {
589 if(state==State::Idle && current.time>0){
590 event.ignore();
591 return;
592 }
593 }
594
void setPosition(const Tempest::Vec3 &pos)
Definition camera.cpp:545
void setSpin(const Tempest::PointF &p)
Definition camera.cpp:237
void setDialogDistance(float d)
Definition camera.cpp:556
void printScreen(std::string_view msg, int x, int y, int time, const GthFont &font)
void keyUpEvent(Tempest::KeyEvent &event) override
void paintChoice(Tempest::PaintEvent &e)
bool isNpcInDialog(const Npc *npc) const
void onSelect()
DialogMenu(InventoryMenu &trade)
bool isActive() const
void paintEvent(Tempest::PaintEvent &e) override
void tick(uint64_t dt)
bool isChoiceMenuActive() const
void keyDownEvent(Tempest::KeyEvent &event) override
void openPipe(Npc &player, Npc &npc, AiOuputPipe *&out)
void onWorldChanged()
void drawMsg(Tempest::Painter &p, int offsetY)
bool isMobsiDialog() const
void mouseWheelEvent(Tempest::MouseEvent &event) override
void dialogCamera(Camera &camera)
void mouseDownEvent(Tempest::MouseEvent &event) override
bool hasContent() const
void print(std::string_view msg)
void setupSettings()
void dialogExec(const GameScript::DlgChoice &dlg, Npc &player, Npc &npc)
Definition gothic.cpp:635
Tempest::Signal< void()> onSettingsChanged
Definition gothic.h:182
Tempest::Signal< void(std::string_view, int, int, int, const GthFont &)> onPrintScreen
Definition gothic.h:173
static float interfaceScale(const Tempest::Widget *w)
Definition gothic.cpp:471
static Gothic & inst()
Definition gothic.cpp:249
auto updateDialog(const GameScript::DlgChoice &dlg, Npc &player, Npc &npc) -> std::vector< GameScript::DlgChoice >
Definition gothic.cpp:631
static int settingsGetI(std::string_view sec, std::string_view name)
Definition gothic.cpp:807
uint32_t messageTime(std::string_view id) const
Definition gothic.cpp:679
std::string_view messageByName(std::string_view id) const
Definition gothic.cpp:673
void drawText(Tempest::Painter &p, int x, int y, int w, int h, std::string_view txt, Tempest::AlignFlag align, int firstLine=0) const
auto textSize(const std::string_view txt) const -> Tempest::Size
Definition gthfont.cpp:131
int pixelSize() const
Definition gthfont.cpp:18
void trade(Npc &pl, Npc &tr)
State isOpen() const
Definition npc.h:25
auto cameraBone(bool isFirstPerson=false) const -> Tempest::Vec3
Definition npc.cpp:636
float rotation() const
Definition npc.cpp:658
auto dialogChoices(Npc &player, const std::vector< uint32_t > &except, bool includeImp) -> std::vector< GameScript::DlgChoice >
Definition npc.cpp:4353
void stopDlgAnim()
Definition npc.cpp:519
auto interactive() const -> Interactive *
Definition npc.h:294
bool haveOutput() const
Definition npc.cpp:2934
std::string_view displayName() const
Definition npc.cpp:734
void setAiOutputBarrier(uint64_t dt, bool overlay)
Definition npc.cpp:2940
static const GthFont & font(const float scale)
static const GthFont & dialogFont(const float scale)
static const Tempest::Texture2d * loadTexture(std::string_view name, bool forceMips=false)
static Tempest::Sound loadSoundBuffer(std::string_view name)