OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
mainwindow.cpp
Go to the documentation of this file.
1#include "mainwindow.h"
2
3#include <Tempest/Except>
4#include <Tempest/Painter>
5
6#include <Tempest/Brush>
7#include <Tempest/Pen>
8#include <Tempest/Layout>
9#include <Tempest/Application>
10#include <Tempest/Log>
11
12#include "ui/dialogmenu.h"
13#include "ui/menuroot.h"
14#include "ui/stacklayout.h"
15#include "ui/videowidget.h"
16
17#include "utils/mouseutil.h"
18#include "utils/string_frm.h"
20#include "world/objects/npc.h"
21#include "game/serialize.h"
22#include "game/globaleffects.h"
23#include "utils/gthfont.h"
24#include "utils/dbgpainter.h"
25
26#include "commandline.h"
27#include "gothic.h"
28
29using namespace Tempest;
30
32 : Window(Maximized),device(device),swapchain(device,hwnd()),
33 atlas(device),renderer(swapchain),
34 rootMenu(keycodec),inventory(keycodec),
35 dialogs(inventory),document(keycodec),
36 console(*this),
37#if defined(__MOBILE_PLATFORM__)
38 mobileUi(player),
39#endif
40 player(dialogs,inventory) {
41 Gothic::inst().onSettingsChanged.bind(this,&MainWindow::onSettings);
42 onSettings();
43
44 if(Gothic::inst().version().game==2)
45 setWindowTitle("Gothic II"); else
46 setWindowTitle("Gothic");
47
48 if(!CommandLine::inst().isWindowMode())
49 setFullscreen(true);
50
51 //renderer.resetSwapchain();
52 setupUi();
53
54 barBack = Resources::loadTexture("BAR_BACK.TGA");
55 barHp = Resources::loadTexture("BAR_HEALTH.TGA");
56 barMisc = Resources::loadTexture("BAR_MISC.TGA");
57 barMana = Resources::loadTexture("BAR_MANA.TGA");
58
59 focusImg = Resources::loadTexture("FOCUS_HIGHLIGHT.TGA");
60
61 loadBox = Resources::loadTexture("PROGRESS.TGA");
62 loadVal = Resources::loadTexture("PROGRESS_BAR.TGA");
63
64 Gothic::inst().onStartGame .bind(this,&MainWindow::startGame);
65 Gothic::inst().onLoadGame .bind(this,&MainWindow::loadGame);
66 Gothic::inst().onSaveGame .bind(this,&MainWindow::saveGame);
67
68 Gothic::inst().onStartLoading.bind(this,&MainWindow::onStartLoading);
69 Gothic::inst().onWorldLoaded .bind(this,&MainWindow::onWorldLoaded);
70 Gothic::inst().onSessionExit .bind(this,&MainWindow::onSessionExit);
71
72 Gothic::inst().onVideo .bind(this,&MainWindow::onVideo);
73
74 Gothic::inst().onBenchmarkFinished.bind(this,&MainWindow::onBenchmarkFinished);
75
76 if(!Gothic::inst().defaultSave().empty()){
77 Gothic::inst().load(Gothic::inst().defaultSave());
78 rootMenu.popMenu();
79 }
80 else if(!CommandLine::inst().doStartMenu()) {
81 startGame(Gothic::inst().defaultWorld());
82 rootMenu.popMenu();
83 }
84 else {
85 rootMenu.processMusicTheme();
86 }
87
88 funcKey[2] = Shortcut(*this,Event::M_NoModifier,Event::K_F2);
89 funcKey[2].onActivated.bind(this, &MainWindow::onMarvinKey<Event::K_F2>);
90
91 funcKey[3] = Shortcut(*this,Event::M_NoModifier,Event::K_F3);
92 funcKey[3].onActivated.bind(this, &MainWindow::onMarvinKey<Event::K_F3>);
93
94 funcKey[4] = Shortcut(*this,Event::M_NoModifier,Event::K_F4);
95 funcKey[4].onActivated.bind(this, &MainWindow::onMarvinKey<Event::K_F4>);
96
97 funcKey[5] = Shortcut(*this,Event::M_NoModifier,Event::K_F5);
98 funcKey[5].onActivated.bind(this, &MainWindow::onMarvinKey<Event::K_F5>);
99
100 funcKey[6] = Shortcut(*this,Event::M_NoModifier,Event::K_F6);
101 funcKey[6].onActivated.bind(this, &MainWindow::onMarvinKey<Event::K_F6>);
102
103 funcKey[7] = Shortcut(*this,Event::M_NoModifier,Event::K_F7);
104 funcKey[7].onActivated.bind(this, &MainWindow::onMarvinKey<Event::K_F7>);
105
106 funcKey[9] = Shortcut(*this,Event::M_NoModifier,Event::K_F9);
107 funcKey[9].onActivated.bind(this, &MainWindow::onMarvinKey<Event::K_F9>);
108
109 funcKey[10] = Shortcut(*this,Event::M_NoModifier,Event::K_F10);
110 funcKey[10].onActivated.bind(this, &MainWindow::onMarvinKey<Event::K_F10>);
111
112 displayPos = Shortcut(*this,Event::M_Alt,Event::K_P);
113 displayPos.onActivated.bind(this, &MainWindow::onMarvinKey<Event::K_P>);
114 }
115
119 device.waitIdle();
120 takeWidget(&dialogs);
121 takeWidget(&inventory);
122 takeWidget(&chapter);
123 takeWidget(&document);
124 takeWidget(&video);
125 takeWidget(&rootMenu);
126#if defined(__MOBILE_PLATFORM__)
127 takeWidget(&mobileUi);
128#endif
129 removeAllWidgets();
130 // unload
131 Gothic::inst().setGame(std::unique_ptr<GameSession>());
132 }
133
134float MainWindow::uiScale() const {
135 return SystemApi::uiScale(hwnd());
136 }
137
138void MainWindow::setupUi() {
139 setLayout(new StackLayout());
140 addWidget(&document);
141 addWidget(&dialogs);
142 addWidget(&inventory);
143 addWidget(&chapter);
144 addWidget(&video);
145 addWidget(&rootMenu);
146#if defined(__MOBILE_PLATFORM__)
147 addWidget(&mobileUi);
148#endif
149
150 rootMenu.setMainMenu();
151
153 Gothic::inst().isNpcInDialogFn = std::bind(&DialogMenu::isNpcInDialog, &dialogs, std::placeholders::_1);
154
156 Gothic::inst().onPrint .bind(&dialogs,&DialogMenu::print);
157
160 }
161
162void MainWindow::paintEvent(PaintEvent& event) {
163 Painter p(event);
164 auto world = Gothic::inst().world();
165 auto st = Gothic::inst().checkLoading();
166
167 if(!Gothic::inst().isInGame() && st==Gothic::LoadState::Idle && background.isEmpty()) {
168 background = Resources::loadTextureUncached("STARTSCREEN.TGA");
169 }
170
171 if(world==nullptr && !background.isEmpty()) {
172 p.setBrush(Color(0.0));
173 p.drawRect(0,0,w(),h());
174
176 p.setBrush(Brush(background,Painter::NoBlend));
177 p.drawRect(0,0,w(),h(),
178 0,0,background.w(),background.h());
179 }
180 }
181
182 if(world!=nullptr) {
183 world->globalFx()->scrBlend(p,Rect(0,0,w(),h()));
184 }
185
188 drawSaving(p);
189 } else {
190 if(auto back = Gothic::inst().loadingBanner()) {
191 p.setBrush(Brush(*back,Painter::NoBlend));
192 p.drawRect(0,0,this->w(),this->h(),
193 0,0,back->w(),back->h());
194 }
195 if(loadBox!=nullptr && !loadBox->isEmpty()) {
196 if(Gothic::inst().version().game==1) {
197 int lw = int(w()*0.5);
198 int lh = int(h()*0.05);
199 drawLoading(p,(w()-lw)/2, int(h()*0.75), lw, lh);
200 } else {
201 drawLoading(p,int(w()*0.92)-loadBox->w(), int(h()*0.12), loadBox->w(),loadBox->h());
202 }
203 }
204 }
205 } else {
206 if(world!=nullptr && world->view()){
207 auto& camera = *Gothic::inst().camera();
208
209 auto vp = camera.viewProj();
210 p.setBrush(Color(1.0));
211
212 drawMsg(p);
213
214 auto focus = world->validateFocus(player.focus());
215 paintFocus(p,focus,vp);
216
217 if(auto pl = Gothic::inst().player()){
218 if (!Gothic::inst().isDesktop()) {
219 auto& opt = Gothic::options();
220 float hp = float(pl->attribute(ATR_HITPOINTS))/float(pl->attribute(ATR_HITPOINTSMAX));
221 float mp = float(pl->attribute(ATR_MANA)) /float(pl->attribute(ATR_MANAMAX));
222
223 bool showHealthBar = opt.showHealthBar;
224 bool showManaBar = (opt.showManaBar==2) || (opt.showManaBar==1 && (pl->weaponState()==WeaponState::Mage || inventory.isActive()));
225 bool showSwimBar = (opt.showSwimBar==2) || (opt.showSwimBar==1 && pl->isDive());
226
227 if(showHealthBar)
228 drawBar(p,barHp, 10, h()-10, hp, AlignLeft | AlignBottom);
229 if(showManaBar)
230 drawBar(p,barMana, w()-10, h()-10, mp, AlignRight | AlignBottom);
231 if(showSwimBar) {
232 uint32_t gl = pl->guild();
233 auto v = float(pl->world().script().guildVal().dive_time[gl]);
234 if(v>0) {
235 auto t = float(pl->diveTime())/1000.f;
236 drawBar(p,barMisc,w()/2,h()-10, (v-t)/(v), AlignHCenter | AlignBottom);
237 }
238 }
239 }
240 }
241 }
242 }
243
244 if(auto c = Gothic::inst().camera()) {
245 DbgPainter dbg(p,c->viewProj(),w(),h());
246 c->debugDraw(dbg);
247 if(world!=nullptr) {
248 world->marchPoints(dbg);
249 world->marchInteractives(dbg);
250 world->view()->dbgLights(dbg);
251 world->marchCsCameras(dbg);
252 }
253 }
254
255 renderer.dbgDraw(p);
256
257 const float scale = Gothic::interfaceScale(this);
258 if(Gothic::inst().doFrate() && !Gothic::inst().isDesktop()) {
259 char fpsT[64]={};
260 std::snprintf(fpsT,sizeof(fpsT),"fps = %.2f",fps.get());
261
262 auto& fnt = Resources::font(scale);
263 fnt.drawText(p,5,fnt.pixelSize()+5,fpsT);
264 }
265
266 if(Gothic::inst().doClock() && world!=nullptr) {
267 if (!Gothic::inst().isDesktop()) {
268 auto hour = world->time().hour();
269 auto min = world->time().minute();
270 auto& fnt = Resources::font(scale);
271 string_frm clockT(int(hour),":",int(min));
272 fnt.drawText(p,w()-fnt.textSize(clockT).w-5,fnt.pixelSize()+5,clockT);
273 }
274 }
275
276 if(auto wx = Gothic::inst().worldView()) {
277 wx->dbgClusters(p, Vec2(float(w()), float(h())));
278 }
279 }
280
281void MainWindow::resizeEvent(SizeEvent&) {
282 for(auto& i:fence)
283 i.wait();
284 swapchain.reset();
285 renderer.resetSwapchain();
286 if(auto camera = Gothic::inst().camera())
287 camera->setViewport(swapchain.w(),swapchain.h());
288
289 const bool fs = SystemApi::isFullscreen(hwnd());
290 auto rect = SystemApi::windowClientRect(hwnd());
291 setCursorPosition(rect.w/2,rect.h/2);
292 setCursorShape(fs ? CursorShape::Hidden : CursorShape::Arrow);
293 dMouse = Point();
294 }
295
296void MainWindow::mouseDownEvent(MouseEvent &event) {
297 if(event.button<sizeof(mouseP))
298 mouseP[event.button]=true;
299 player.onKeyPressed(keycodec.tr(event),KeyEvent::K_NoKey);
300 }
301
302void MainWindow::mouseUpEvent(MouseEvent &event) {
303 player.onKeyReleased(keycodec.tr(event));
304 if(event.button<sizeof(mouseP))
305 mouseP[event.button]=false;
306 }
307
308void MainWindow::mouseDragEvent(MouseEvent &event) {
309 const bool fs = SystemApi::isFullscreen(hwnd());
310 if(!mouseP[Event::ButtonLeft] && !fs)
311 return;
312 if(player.focus().npc && !fs)
313 return;
314 processMouse(event,true);
315 }
316
317void MainWindow::mouseMoveEvent(MouseEvent &event) {
318 processMouse(event,SystemApi::isFullscreen(hwnd()));
319 }
320
321void MainWindow::processMouse(MouseEvent& event, bool enable) {
322 auto center = Point(w()/2,h()/2);
323 if(enable && event.pos()!=center && hasFocus()) {
324 dMouse += (event.pos()-center);
325 setCursorPosition(center);
326 }
327 }
328
329void MainWindow::tickMouse() {
330 auto camera = Gothic::inst().camera();
331 if(dialogs.hasContent() || Gothic::inst().isPause() || camera==nullptr || camera->isCutscene()) {
332 dMouse = Point();
333 return;
334 }
335
336 const bool enableMouse = Gothic::inst().settingsGetI("GAME","enableMouse");
337 if(enableMouse==0) {
338 dMouse = Point();
339 return;
340 }
341
342 const bool camLookaroundInverse = Gothic::inst().settingsGetI("GAME","camLookaroundInverse");
343 const float mouseSensitivity = Gothic::inst().settingsGetF("GAME","mouseSensitivity")/MouseUtil::mouseSysSpeed();
344 PointF dpScaled = PointF(float(dMouse.x)*mouseSensitivity,float(dMouse.y)*mouseSensitivity);
345 dpScaled.x/=float(w());
346 dpScaled.y/=float(h());
347
348 dpScaled*=1000.f;
349 dpScaled.y /= 7.f;
350 if(camLookaroundInverse)
351 dpScaled.y *= -1.f;
352
353 camera->onRotateMouse(PointF(dpScaled.y,-dpScaled.x));
354 if(!inventory.isActive()) {
355 player.onRotateMouse (-dpScaled.x);
356 player.onRotateMouseDy(-dpScaled.y);
357 }
358
359 dMouse = Point();
360 }
361
362void MainWindow::onSettings() {
363 auto zMaxFps = Gothic::options().fpsLimit;
364 if(zMaxFps<=0)
365 zMaxFps = Gothic::inst().settingsGetI("ENGINE", "zMaxFps");
366 if(zMaxFps>0)
367 maxFpsInv = 1000u/uint64_t(zMaxFps); else
368 maxFpsInv = 0;
369 }
370
371void MainWindow::mouseWheelEvent(MouseEvent &event) {
372 if(auto camera = Gothic::inst().camera())
373 camera->changeZoom(event.delta);
374 }
375
376void MainWindow::keyDownEvent(KeyEvent &event) {
377 if(video.isActive()){
378 event.accept();
379 video.keyDownEvent(event);
380 if(event.isAccepted()){
381 uiKeyUp=&video;
382 return;
383 }
384 }
385
386 if(rootMenu.isActive()) {
387 event.accept();
388 rootMenu.keyDownEvent(event);
389 if(event.isAccepted()){
390 uiKeyUp=&rootMenu;
391 return;
392 }
393 }
394
395 if(chapter.isActive()){
396 event.accept();
397 chapter.keyDownEvent(event);
398 if(event.isAccepted()){
399 uiKeyUp=&chapter;
400 return;
401 }
402 }
403
404 if(document.isActive()){
405 event.accept();
406 document.keyDownEvent(event);
407 if(event.isAccepted()){
408 uiKeyUp=&document;
409 return;
410 }
411 }
412
413 if(dialogs.isActive()){
414 event.accept();
415 dialogs.keyDownEvent(event);
416 if(event.isAccepted()){
417 uiKeyUp=&dialogs;
418 return;
419 }
420 }
421
422 if(inventory.isActive()){
423 event.accept();
424 inventory.keyDownEvent(event);
425 if(event.isAccepted()){
426 uiKeyUp=&inventory;
427 return;
428 }
429 }
430 uiKeyUp=nullptr;
431
432 auto act = keycodec.tr(event);
433 auto mapping = keycodec.mapping(event);
434 player.onKeyPressed(act,event.key,mapping);
435
436 if(event.key==Event::K_F11) {
437 auto tex = renderer.screenshoot(cmdId);
438 auto pm = device.readPixels(textureCast<const Texture2d&>(tex));
439 pm.save("dbg.png");
440 }
441 event.accept();
442 }
443
444void MainWindow::keyRepeatEvent(KeyEvent& event) {
445 if(uiKeyUp==&video){
446 if(event.isAccepted())
447 return;
448 }
449 if(uiKeyUp==&rootMenu){
450 rootMenu.keyRepeatEvent(event);
451 if(event.isAccepted())
452 return;
453 }
454 if(uiKeyUp==&chapter){
455 if(event.isAccepted())
456 return;
457 }
458 if(uiKeyUp==&document){
459 if(event.isAccepted())
460 return;
461 }
462 if(uiKeyUp==&dialogs){
463 if(event.isAccepted())
464 return;
465 }
466 if(uiKeyUp==&inventory){
467 inventory.keyRepeatEvent(event);
468 if(event.isAccepted())
469 return;
470 }
471 }
472
473void MainWindow::keyUpEvent(KeyEvent &event) {
474 if(uiKeyUp==&video){
475 video.keyUpEvent(event);
476 if(event.isAccepted())
477 return;
478 }
479 if(uiKeyUp==&rootMenu){
480 if(event.isAccepted())
481 return;
482 }
483 if(uiKeyUp==&chapter){
484 chapter.keyUpEvent(event);
485 if(event.isAccepted())
486 return;
487 }
488 if(uiKeyUp==&document){
489 document.keyUpEvent(event);
490 if(event.isAccepted())
491 return;
492 }
493 if(uiKeyUp==&dialogs){
494 dialogs.keyUpEvent(event);
495 if(event.isAccepted())
496 return;
497 }
498 if(uiKeyUp==&inventory){
499 inventory.keyUpEvent(event);
500 if(event.isAccepted())
501 return;
502 }
503
504
505 auto act = keycodec.tr(event);
506 auto mapping = keycodec.mapping(event);
507
508 std::string_view menuEv;
509 if(act==KeyCodec::Escape)
510 menuEv = Gothic::inst().menuMain();
511 else if(act==KeyCodec::Log)
512 menuEv = "MENU_LOG";
513 else if(act==KeyCodec::Status)
514 menuEv = "MENU_STATUS";
515
516 if(!menuEv.empty()) {
517 rootMenu.setMenu(menuEv,act);
518 rootMenu.showVersion(act==KeyCodec::Escape);
519 if(auto pl = Gothic::inst().player())
520 rootMenu.setPlayer(*pl);
521 clearInput();
522 }
523 else if(act==KeyCodec::Inventory && !dialogs.isActive()) {
524 if(inventory.isActive()) {
525 inventory.close();
526 } else {
527 auto pl = Gothic::inst().player();
528 if(pl!=nullptr)
529 inventory.open(*pl);
530 }
531 clearInput();
532 }
533 player.onKeyReleased(act, mapping);
534 }
535
536void MainWindow::focusEvent(FocusEvent &event) {
537 if(!event.in)
538 return;
539 dMouse = Point();
540 auto center = Point(w()/2,h()/2);
541 setCursorPosition(center);
542 }
543
544void MainWindow::paintFocus(Painter& p, const Focus& focus, const Matrix4x4& vp) {
545 if(!focus || dialogs.isActive())
546 return;
547
548 const float scale = Gothic::interfaceScale(this);
549 auto world = Gothic::inst().world();
550 auto pl = world==nullptr ? nullptr : world->player();
551 if(pl==nullptr)
552 return;
553
554 auto pos = focus.displayPosition();
555 vp.project(pos.x,pos.y,pos.z);
556
557 int ix = int((0.5f*pos.x+0.5f)*float(w()));
558 int iy = int((0.5f*pos.y+0.5f)*float(h()));
559 auto& fnt = Resources::font(scale);
560
561 auto tsize = fnt.textSize(focus.displayName());
562 ix-=tsize.w/2;
563 if(iy<tsize.h)
564 iy = tsize.h;
565 if(iy>h())
566 iy = h();
567 fnt.drawText(p,ix,iy,focus.displayName());
568
569 if(focus.npc!=nullptr && !focus.npc->isDead()) {
570 float hp = float(focus.npc->attribute(ATR_HITPOINTS))/float(focus.npc->attribute(ATR_HITPOINTSMAX));
571 drawBar(p,barHp, w()/2,10, hp, AlignHCenter|AlignTop);
572 }
573
574 const int foc = Gothic::settingsGetI("GAME","highlightMeleeFocus");
575 if(focus.npc!=nullptr &&
576 (foc==1 || foc==3) &&
578 pl->weaponState()!=WeaponState::NoWeapon &&
579 pl->weaponState()!=WeaponState::Fist) {
580 auto tr = vp;
581 tr.mul(focus.npc->transform());
582
583 auto b = focus.npc->bounds();
584 Vec3 bx[] = {
585 {b.bbox[0].x,b.bbox[0].y,b.bbox[0].z},
586 {b.bbox[1].x,b.bbox[0].y,b.bbox[0].z},
587 {b.bbox[1].x,b.bbox[1].y,b.bbox[0].z},
588 {b.bbox[0].x,b.bbox[1].y,b.bbox[0].z},
589 {b.bbox[0].x,b.bbox[0].y,b.bbox[1].z},
590 {b.bbox[1].x,b.bbox[0].y,b.bbox[1].z},
591 {b.bbox[1].x,b.bbox[1].y,b.bbox[1].z},
592 {b.bbox[0].x,b.bbox[1].y,b.bbox[1].z},
593 };
594
595 int min[2]={ix,iy-20}, max[2]={ix,iy-20};
596 for(int i=0; i<8; ++i) {
597 tr.project(bx[i]);
598 int x = int((bx[i].x*0.5f+0.5f)*float(w()));
599 int y = int((bx[i].y*0.5f+0.5f)*float(h()));
600 min[0] = std::min(x,min[0]);
601 min[1] = std::min(y,min[1]);
602 max[0] = std::max(x,max[0]);
603 max[1] = std::max(y,max[1]);
604 }
605
606 paintFocus(p,Rect(min[0],min[1],max[0]-min[0],max[1]-min[1]));
607 }
608
609 // focusImg
610 /*
611 if(auto pl = focus.interactive){
612 pl->marchInteractives(p,vp,w(),h());
613 }*/
614 }
615
616void MainWindow::paintFocus(Painter& p, Rect rect) {
617 if(focusImg==nullptr)
618 return;
619 const int w2 = focusImg->w();
620 const int h2 = focusImg->h();
621 const int w = w2/2;
622 const int h = h2/2;
623
624 if(rect.w<w) {
625 int dw = w-rect.w;
626 rect.x -= dw/2;
627 rect.w += dw;
628 }
629 if(rect.h<h) {
630 int dh = h-rect.h;
631 rect.y -= dh/2;
632 rect.h += dh;
633 }
634
635 p.setBrush(Brush(*focusImg,Painter::Add));
636 p.drawRect(rect.x, rect.y, w,h, 0,0, w, h);
637 p.drawRect(rect.x+rect.w-w,rect.y, w,h, w,0, w2,h);
638 p.drawRect(rect.x, rect.y+rect.h-h,w,h, 0,h, w, h2);
639 p.drawRect(rect.x+rect.w-w,rect.y+rect.h-h,w,h, w,h, w2,h2);
640 }
641
642void MainWindow::drawBar(Painter &p, const Tempest::Texture2d* bar, int x, int y, float v, AlignFlag flg) {
643 if(barBack==nullptr || bar==nullptr)
644 return;
645 const float scale = Gothic::interfaceScale(this);
646 const float destW = 200.f*scale*float(std::min(w(),800))/800.f;
647 const float k = float(destW)/float(std::max(barBack->w(),1));
648 const float destH = float(barBack->h())*k;
649 const float destHin = float(destH)*24.f/32.f;
650 //const float destHin = 20;//float(destH)*24.f/32.f;
651
652 v = std::max(0.f,std::min(v,1.f));
653 if(flg & AlignRight)
654 x-=int(destW);
655 else if(flg & AlignHCenter)
656 x-=int(destW)/2;
657 if(flg & AlignBottom)
658 y-=int(destH);
659
660 p.setBrush(*barBack);
661 p.drawRect(x,y,int(destW),int(destH), 0,0,barBack->w(),barBack->h());
662
663 int dy = int(0.5f*(destH-destHin));
664 float pd = 9.f*k;
665 p.setBrush(*bar);
666 p.drawRect(x+int(pd),y+dy,int(float(destW-pd*2)*v),int(destHin),
667 0,0,bar->w(),bar->h());
668 }
669
670void MainWindow::drawMsg(Tempest::Painter& p) {
671 const float scale = Gothic::interfaceScale(this);
672 const float destW = 200.f*scale*float(std::min(w(),800))/800.f;
673 const float k = float(destW)/float(std::max(barBack->w(),1));
674 const float destH = float(barBack->h())*k;
675
676 const int y = 10 + int(destH) + 10;
677 dialogs.drawMsg(p, y);
678 }
679
680void MainWindow::drawProgress(Painter &p, int x, int y, int w, int h, float v) {
681 if(v<0.1f)
682 v=0.1f;
683 p.setBrush(*loadBox);
684 p.drawRect(x,y,w,h, 0,0,loadBox->w(),loadBox->h());
685
686 int paddL = int((float(w)*75.f)/float(loadBox->w()));
687 int paddT = int((float(h)*10.f)/float(loadBox->h()));
688
689 if(Gothic::inst().version().game==1) {
690 paddL = int((float(w)*30.f)/float(loadBox->w()));
691 paddT = int((float(h)* 5.f)/float(loadBox->h()));
692 }
693
694 p.setBrush(*loadVal);
695 p.drawRect(x+paddL,y+paddT,int(float(w-2*paddL)*v),h-2*paddT,
696 0,0,loadVal->w(),loadVal->h());
697 }
698
699void MainWindow::drawLoading(Painter &p, int x, int y, int w, int h) {
700 float v = float(Gothic::inst().loadingProgress())/100.f;
701 drawProgress(p,x,y,w,h,v);
702 }
703
704void MainWindow::drawSaving(Painter &p) {
705 if(auto back = Gothic::inst().loadingBanner()) {
706 p.setBrush(Brush(*back,Painter::NoBlend));
707 p.drawRect(0,0,this->w(),this->h(),
708 0,0,back->w(),back->h());
709 }
710
711 if(saveback==nullptr)
712 saveback = Resources::loadTexture("SAVING.TGA");
713 if(saveback==nullptr)
714 return;
715
716 const float scale = Gothic::interfaceScale(this);
717 int szX = Gothic::options().saveGameImageSize.w;
718 int szY = Gothic::options().saveGameImageSize.h;
719
720 if(szX<=460 || szY<=300) {
721 // way too small otherwise
722 szX = 460;
723 szY = 300;
724 }
725 szX = int(float(szX)*scale);
726 szY = int(float(szY)*scale);
727 drawSaving(p,*saveback,szX,szY,scale);
728 }
729
730void MainWindow::drawSaving(Painter& p, const Tempest::Texture2d& back, int sw, int sh, float scale) {
731 const int x = (w()-sw)/2, y = (h()-sh)/2;
732
733 // SAVING.TGA is semi-transparent image with the idea to accomulate alpha over time
734 // ... for loop for now
735 p.setBrush(back);
736 for(int i=0;i<10;++i) {
737 p.drawRect(x,y,sw,sh, 0,0,back.w(),back.h());
738 }
739
740 float v = float(Gothic::inst().loadingProgress())/100.f;
741 drawProgress(p, x+int(100.f*scale), y+sh-int(75.f*scale), sw-2*int(100.f*scale), int(40.f*scale), v);
742 }
743
744void MainWindow::isDialogClosed(bool& ret) {
745 ret = !(dialogs.isActive() || document.isActive());
746 }
747
748template<Tempest::KeyEvent::KeyType k>
749void MainWindow::onMarvinKey() {
750 switch(k) {
751 case Event::K_F2:
752 if(Gothic::inst().isMarvinEnabled()) {
753 console.resize(w(),h());
754 console.setFocus(true);
755 console.exec();
756 }
757 break;
758 case Event::K_F3:
759 setFullscreen(!SystemApi::isFullscreen(hwnd()));
760 break;
761 case Event::K_F4:
762 if(Gothic::inst().isMarvinEnabled()) {
763 auto camera = Gothic::inst().camera();
764 auto pl = Gothic::inst().player();
765 if(camera!=nullptr && pl!=nullptr) {
766 camera->setMarvinMode(Camera::M_Normal);
767 camera->reset(pl);
768 }
769 }
770 break;
771 case Event::K_F5: {
772 const bool useQuickSaveKeys = Gothic::settingsGetI("GAME", "useQuickSaveKeys")!=0;
773#ifdef NDEBUG
774 const bool debug = false;
775#else
776 const bool debug = true;
777#endif
778 if(!debug && Gothic::inst().isMarvinEnabled() && !dialogs.isActive()) {
779 if(auto camera = Gothic::inst().camera()) {
780 camera->setMarvinMode(Camera::M_Freeze);
781 }
782 }
783 else if(Gothic::inst().isInGameAndAlive() && !Gothic::inst().isPause() && useQuickSaveKeys) {
785 }
786 break;
787 }
788
789 case Event::K_F6:
790 if(Gothic::inst().isMarvinEnabled() && !dialogs.isActive()) {
791 auto camera = Gothic::inst().camera();
792 auto pl = Gothic::inst().player();
793 auto inter = pl!=nullptr ? pl->interactive() : nullptr;
794 if(camera!=nullptr && inter==nullptr)
795 camera->setMarvinMode(Camera::M_Free);
796 }
797 break;
798 case Event::K_F7:
799 if(Gothic::inst().isMarvinEnabled() && !dialogs.isActive()) {
800 if(auto camera = Gothic::inst().camera()) {
801 camera->setMarvinMode(Camera::M_Pinned);
802 }
803 }
804 break;
805 case Event::K_F8:
806 //player.marvinF8();
807 break;
808
809 case Event::K_F9: {
810 const bool useQuickSaveKeys = Gothic::settingsGetI("GAME", "useQuickSaveKeys")!=0;
811 if(Gothic::inst().isMarvinEnabled()) {
812 if(runtimeMode==R_Normal)
813 runtimeMode = R_Suspended; else
814 runtimeMode = R_Normal;
815 }
816 else if(!Gothic::inst().isPause() && useQuickSaveKeys) {
818 }
819 break;
820 }
821 case Event::K_F10:
822 if(runtimeMode==R_Suspended)
823 runtimeMode = R_Step;
824 break;
825 case Event::K_P:
826 if(Gothic::inst().isMarvinEnabled()) {
827 if(auto p = Gothic::inst().player()) {
828 auto pos = p->position();
829 string_frm buf("Position: ", pos.x,'/',pos.y,'/',pos.z);
830 Gothic::inst().onPrint(buf);
831 }
832 }
833 break;
834 }
835 }
836
837uint64_t MainWindow::tick() {
838 auto time = Application::tickCount();
839 auto dt = time-lastTick;
840 // NOTE: limit to ~200 FPS in game logic to avoid math issues
841 if(dt<5)
842 return 0;
843 lastTick = time;
844
845 auto st = Gothic::inst().checkLoading();
849 rootMenu.setMainMenu();
851 Gothic::inst().onPrint("unable to write savegame file");
852 return 0;
853 }
854 else if(st!=Gothic::LoadState::Idle) {
857 rootMenu.processMusicTheme();
858 return 0;
859 }
860
861 video.tick();
862 if(video.isActive())
863 return 0;
864
865 if(Gothic::inst().isPause()) {
866 return 0;
867 }
868
869 if(dt>50)
870 dt=50;
871
872 if(runtimeMode==R_Step) {
873 runtimeMode = R_Suspended;
874 dt = 1000/60; //60 fps
875 }
876 else if(runtimeMode==R_Suspended) {
877 auto camera = Gothic::inst().camera();
878 if(camera!=nullptr && camera->isFree()) {
879 player.tickCameraMove(dt);
880 tickMouse();
881 }
882 update();
883 return dt;
884 }
885
886 dialogs.tick(dt);
887 inventory.tick(dt);
888 Gothic::inst().tick(dt);
889 player.tickFocus();
890
891 if(dialogs.isActive())
892 ;//clearInput();
893 if(document.isActive())
894 clearInput();
895 tickMouse();
896 player.tickMove(dt);
897 update();
898 return dt;
899 }
900
901void MainWindow::updateAnimation(uint64_t dt) {
903 }
904
905void MainWindow::tickCamera(uint64_t dt) {
906 auto pcamera = Gothic::inst().camera();
907 auto pl = Gothic::inst().player();
908 if(pcamera==nullptr)
909 return;
910
911 auto& camera = *pcamera;
912 const auto ws = pl!=nullptr ? pl->weaponState() : WeaponState::NoWeapon;
913 const bool meleeFocus = (ws==WeaponState::Fist ||
914 ws==WeaponState::W1H ||
915 ws==WeaponState::W2H);
916 auto pos = pl!=nullptr ? pl->cameraBone(camera.isFirstPerson()) : Vec3();
917
918 if(!camera.isCutscene()) {
919 const bool fs = SystemApi::isFullscreen(hwnd());
920 if(!fs && mouseP[Event::ButtonLeft]) {
921 camera.setSpin(camera.destSpin());
922 camera.setDestPosition(pos);
923 }
924 else if(dialogs.isActive() && !dialogs.isMobsiDialog()) {
925 dialogs.dialogCamera(camera);
926 }
927 else if(inventory.isActive()) {
928 camera.setDestPosition(pos);
929 }
930 else if(player.focus().npc!=nullptr && meleeFocus && pl!=nullptr) {
931 auto spin = camera.destSpin();
932 spin.y = pl->rotation();
933 camera.setDestSpin(spin);
934 camera.setDestPosition(pos);
935 }
936 else if(pl!=nullptr) {
937 auto spin = camera.destSpin();
938 if(pl->interactive()==nullptr && !pl->isDown())
939 spin.y = pl->rotation();
940 if(pl->isDive() && !camera.isMarvin())
941 spin.x = -pl->rotationY();
942 camera.setDestSpin(spin);
943 camera.setDestPosition(pos);
944 }
945 }
946
947 if(dt==0)
948 return;
949 if(camera.isToggleEnabled() && !camera.isCutscene())
950 camera.setMode(solveCameraMode());
951 camera.tick(dt);
952 }
953
954Camera::Mode MainWindow::solveCameraMode() const {
955 if(auto camera = Gothic::inst().camera()) {
956 if(camera->isFree())
957 return Camera::Normal;
958 }
959
960 if(inventory.isOpen()==InventoryMenu::State::Equip ||
962 return Camera::Inventory;
963
964 if(auto pl=Gothic::inst().player()) {
965 if(pl->interactive()!=nullptr)
966 return Camera::Mobsi;
967 }
968
969 if(dialogs.isActive())
970 return Camera::Dialog;
971
972 if(auto pl = Gothic::inst().player()) {
973 if(pl->isDead())
974 return Camera::Death;
975 if(pl->isDive())
976 return Camera::Dive;
977 if(pl->isSwim())
978 return Camera::Swim;
979 if(pl->isFallingDeep())
980 return Camera::Fall;
981 bool g2 = Gothic::inst().version().game==2;
982 switch(pl->weaponState()){
984 case WeaponState::W1H:
985 case WeaponState::W2H:
986 return Camera::Melee;
987 case WeaponState::Bow:
989 return g2 ? Camera::Ranged : Camera::Normal;
991 return g2 ? Camera::Ranged : Camera::Melee;
993 return Camera::Normal;
994 }
995 }
996
997 return Camera::Normal;
998 }
999
1000void MainWindow::startGame(std::string_view slot) {
1001 // gothic.emitGlobalSound(gothic.loadSoundFx("NEWGAME"));
1002
1003 if(Gothic::inst().checkLoading()==Gothic::LoadState::Idle){
1004 setGameImpl(nullptr);
1005 }
1006
1007 Gothic::inst().startLoad("LOADING.TGA",[slot=std::string(slot)](std::unique_ptr<GameSession>&& game){
1008 game = nullptr; // clear world-memory now
1009 std::unique_ptr<GameSession> w(new GameSession(slot));
1010 return w;
1011 });
1012
1013 background = Texture2d();
1014 update();
1015 }
1016
1017void MainWindow::loadGame(std::string_view slot) {
1018 if(Gothic::inst().checkLoading()==Gothic::LoadState::Idle){
1019 setGameImpl(nullptr);
1020 }
1021
1023 Gothic::inst().startLoad("LOADING.TGA",[slot=std::string(slot)](std::unique_ptr<GameSession>&& game){
1024 game = nullptr; // clear world-memory now
1025 Tempest::RFile file(slot);
1026 Serialize s(file);
1027 std::unique_ptr<GameSession> w(new GameSession(s));
1028 return w;
1029 });
1030
1031 background = Texture2d();
1032 update();
1033 }
1034
1035void MainWindow::saveGame(std::string_view slot, std::string_view name) {
1036 auto tex = renderer.screenshoot(cmdId);
1037 auto pm = device.readPixels(textureCast<const Texture2d&>(tex));
1038
1039 if(dialogs.isActive())
1040 return;
1041 if(auto w = Gothic::inst().world(); w!=nullptr && w->currentCs()!=nullptr)
1042 return;
1043
1044 Gothic::inst().startSave(std::move(textureCast<Texture2d&>(tex)),[slot=std::string(slot),name=std::string(name),pm](std::unique_ptr<GameSession>&& game){
1045 if(!game)
1046 return std::move(game);
1047
1048 Tempest::WFile f(slot);
1049 Serialize s(f);
1050 game->save(s,name,pm);
1051
1052 // no print yet, because threading
1053 // gothic.print("Game saved");
1054 return std::move(game);
1055 });
1056
1057 update();
1058 }
1059
1060void MainWindow::onVideo(std::string_view fname) {
1061 if(Gothic::inst().isBenchmarkMode())
1062 return;
1063 video.pushVideo(fname);
1064 }
1065
1066void MainWindow::onStartLoading() {
1067 player .clearInput();
1068 inventory.onWorldChanged();
1069 dialogs .onWorldChanged();
1070 }
1071
1072void MainWindow::onWorldLoaded() {
1073 dMouse = Point();
1074
1075 if(Gothic::inst().isBenchmarkMode()) {
1076 if(auto world = Gothic::inst().world()) {
1077 const TriggerEvent evt("TIMEDEMO","",world->tickCount(),TriggerEvent::T_Trigger);
1078 world->execTriggerEvent(evt);
1079 }
1080 benchmark.clear();
1081 }
1082
1083 player .clearInput();
1084 inventory.onWorldChanged();
1085 dialogs .onWorldChanged();
1086
1087 device.waitIdle();
1088 for(auto& c:commands)
1089 c = device.commandBuffer();
1090
1091 if(auto c = Gothic::inst().camera())
1092 c->setViewport(uint32_t(w()),uint32_t(h()));
1093
1094 renderer.onWorldChanged();
1095
1096 if(auto pl = Gothic::inst().player())
1097 pl->multSpeed(1.f);
1098 lastTick = Application::tickCount();
1099 player.clearFocus();
1100 }
1101
1102void MainWindow::onSessionExit() {
1103 rootMenu.setMainMenu();
1104 }
1105
1106void MainWindow::onBenchmarkFinished() {
1107 if(benchmark.numFrames==0)
1108 return;
1109
1110 double fps = benchmark.fpsSum/double(benchmark.numFrames);
1111 double low1 = 0;
1112 size_t num1 = 0;
1113 for(size_t i=0; i<benchmark.low1procent.size(); ++i) {
1114 auto v = benchmark.low1procent[i];
1115 if(v<=0)
1116 continue;
1117 low1 += 1000.0/double(v);
1118 num1 += 1;
1119 }
1120 low1 = num1>0 ? low1/double(num1) : 0.0;
1121 benchmark.clear();
1122
1123 string_frm str("Benchmark: low 1% = ", low1, " fps = ", fps);
1124 Log::i(str.c_str());
1125 console.printLine(str);
1126
1127 if(Gothic::inst().isBenchmarkModeCi()) {
1128 Log::i("Exiting benchmark");
1129 Tempest::SystemApi::exit();
1130 return;
1131 }
1132
1133 console.setFocus(true);
1134 console.exec();
1135 }
1136
1137void MainWindow::setGameImpl(std::unique_ptr<GameSession> &&w) {
1138 Gothic::inst().setGame(std::move(w));
1139 }
1140
1141void MainWindow::clearInput() {
1142 player.clearInput();
1143 std::memset(mouseP,0,sizeof(mouseP));
1144 }
1145
1146void MainWindow::setFullscreen(bool fs) {
1147 SystemApi::setAsFullscreen(hwnd(),fs);
1148 }
1149
1150void MainWindow::render(){
1151 try {
1152 static uint64_t time=Application::tickCount();
1153
1154 static bool once=true;
1155 if(once) {
1156 Gothic::inst().emitGlobalSoundWav("GAMESTART.WAV");
1157 once=false;
1158 }
1159
1160 if(T_UNLIKELY(Gothic::inst().isBenchmarkModeCi())) {
1161 const auto st = Gothic::inst().checkLoading();
1163 // skip loading frames in benchmark-ci mode, for sake of easier tooling
1164 return;
1165 }
1166 }
1167
1168 /*
1169 Note: game update goes first
1170 once player position is updated, animation bones(cameraBone in particular) can be updated
1171 lastly - camera position
1172 */
1173 const uint64_t dt = tick();
1174 updateAnimation(dt);
1175 tickCamera(dt);
1176
1177 auto& sync = fence[cmdId];
1178 if(!sync.wait(0)) {
1179 // GPU rendering is not done, pass to next frame
1180 std::this_thread::yield();
1181 return;
1182 }
1184
1185 if(video.isActive()) {
1186 video.paint(device,cmdId);
1187 uiLayer.clear();
1188 PaintEvent p(uiLayer,atlas,this->w(),this->h());
1189 video.paintEvent(p);
1190 }
1191 else if(needToUpdate() || Gothic::inst().checkLoading()!=Gothic::LoadState::Idle) {
1192 dispatchPaintEvent(uiLayer,atlas);
1193
1194 numOverlay.clear();
1195 PaintEvent p(numOverlay,atlas,this->w(),this->h());
1196 inventory.paintNumOverlay(p);
1197 }
1198 uiMesh [cmdId].update(device,uiLayer);
1199 numMesh[cmdId].update(device,numOverlay);
1200
1201 CommandBuffer& cmd = commands[cmdId];
1202 {
1203 auto enc = cmd.startEncoding(device);
1204 renderer.draw(enc,cmdId,swapchain.currentImage(),uiMesh[cmdId],numMesh[cmdId],inventory,video);
1205 }
1206 sync = device.submit(cmd);
1207 device.present(swapchain);
1208 cmdId = (cmdId+1u)%Resources::MaxFramesInFlight;
1209
1210 auto t = Application::tickCount();
1211 if(t-time<16 && !Gothic::inst().isInGame() && !video.isActive()) {
1212 uint32_t delay = uint32_t(16-(t-time));
1213 Application::sleep(delay);
1214 t += delay;
1215 }
1216 else if(maxFpsInv>0 && t-time<maxFpsInv) {
1217 uint32_t delay = uint32_t(maxFpsInv-(t-time));
1218 Application::sleep(delay);
1219 t += delay;
1220 }
1221 fps.push(t-time);
1222 if(Gothic::inst().isBenchmarkMode() && Gothic::inst().world()!=nullptr && Gothic::inst().world()->currentCs()!=nullptr)
1223 benchmark.push(t-time);
1224 time = t;
1225 }
1226 catch(const Tempest::SwapchainSuboptimal&) {
1227 Log::e("swapchain is outdated - reset renderer");
1228 device.waitIdle();
1229 swapchain.reset();
1230 renderer.resetSwapchain();
1231 }
1232 }
1233
1234double MainWindow::Fps::get() const {
1235 uint64_t sum=0,num=0;
1236 for(auto& i:dt)
1237 if(i>0) {
1238 sum+=i;
1239 num++;
1240 }
1241 if(num==0 || sum==0)
1242 return 60;
1243 uint64_t fps = (1000*100*num)/sum;
1244 return double(fps)/100.0;
1245 }
1246
1247void MainWindow::Fps::push(uint64_t t) {
1248 for(size_t i=9;i>0;--i)
1249 dt[i]=dt[i-1];
1250 dt[0]=t;
1251 }
1252
1253void MainWindow::BenchmarkData::push(uint64_t t) {
1254 fpsSum += t>0 ? (1000.0/double((t))) : 60.0;
1255 numFrames++;
1256 auto at = std::lower_bound(low1procent.begin(), low1procent.end(), t, std::greater<uint64_t>());
1257 low1procent.insert(at, t);
1258 low1procent.resize(std::min(low1procent.size(), (numFrames+99)/100));
1259 }
1260
1261void MainWindow::BenchmarkData::clear() {
1262 low1procent.reserve(128);
1263 low1procent.clear();
1264 numFrames = 0;
1265 fpsSum = 0;
1266 }
Tempest::Vec3 bbox[2]
Definition bounds.h:22
@ M_Free
Definition camera.h:38
@ M_Normal
Definition camera.h:36
@ M_Freeze
Definition camera.h:37
@ M_Pinned
Definition camera.h:39
Tempest::Matrix4x4 viewProj() const
Definition camera.cpp:909
@ Ranged
Definition camera.h:25
@ Death
Definition camera.h:28
@ Normal
Definition camera.h:22
@ Swim
Definition camera.h:29
@ Inventory
Definition camera.h:23
@ Dive
Definition camera.h:30
@ Dialog
Definition camera.h:21
@ Melee
Definition camera.h:24
@ Fall
Definition camera.h:31
@ Mobsi
Definition camera.h:27
void keyUpEvent(Tempest::KeyEvent &e)
void keyDownEvent(Tempest::KeyEvent &e)
void show(const Show &image)
bool isActive() const
static const CommandLine & inst()
void printLine(std::string_view s)
void printScreen(std::string_view msg, int x, int y, int time, const GthFont &font)
void keyUpEvent(Tempest::KeyEvent &event) override
bool isNpcInDialog(const Npc *npc) const
bool isActive() const
void tick(uint64_t dt)
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 dialogCamera(Camera &camera)
bool hasContent() const
void print(std::string_view msg)
void keyDownEvent(Tempest::KeyEvent &e)
void keyUpEvent(Tempest::KeyEvent &e)
bool isActive() const
void show(const Show &doc)
Definition focus.h:10
Npc * npc
Definition focus.h:22
Tempest::Vec3 displayPosition() const
Definition focus.cpp:20
std::string_view displayName() const
Definition focus.cpp:30
static GameMusic & inst()
void setMusic(Music m)
void stopMusic()
Camera * camera()
Definition gothic.cpp:319
Tempest::Signal< void()> onSettingsChanged
Definition gothic.h:182
bool finishLoading()
Definition gothic.cpp:504
static auto options() -> const Options &
Definition gothic.cpp:496
Tempest::Signal< void(std::string_view, int, int, int, const GthFont &)> onPrintScreen
Definition gothic.h:173
Tempest::Signal< void(std::string_view)> onLoadGame
Definition gothic.h:167
void updateAnimation(uint64_t dt)
Definition gothic.cpp:610
static float interfaceScale(const Tempest::Widget *w)
Definition gothic.cpp:471
const World * world() const
Definition gothic.cpp:278
LoadState checkLoading() const
Definition gothic.cpp:500
Tempest::Signal< void(std::string_view)> onStartGame
Definition gothic.h:166
void load(std::string_view slot)
Definition gothic.cpp:627
static Gothic & inst()
Definition gothic.cpp:249
Tempest::Signal< void(Npc &, Npc &, AiOuputPipe *&)> onDialogPipe
Definition gothic.h:170
std::string_view menuMain() const
Definition gothic.cpp:705
auto version() const -> const VersionInfo &
Definition gothic.cpp:263
void startLoad(std::string_view banner, const std::function< std::unique_ptr< GameSession >(std::unique_ptr< GameSession > &&)> f)
Definition gothic.cpp:526
Tempest::Signal< void()> onStartLoading
Definition gothic.h:180
static int settingsGetI(std::string_view sec, std::string_view name)
Definition gothic.cpp:807
Tempest::Signal< void(std::string_view)> onVideo
Definition gothic.h:175
void cancelLoading()
Definition gothic.cpp:591
Tempest::Signal< void(const DocumentMenu::Show &)> onShowDocument
Definition gothic.h:178
Tempest::Signal< void()> onBenchmarkFinished
Definition gothic.h:184
static float settingsGetF(std::string_view sec, std::string_view name)
Definition gothic.cpp:841
Npc * player()
Definition gothic.cpp:313
void setBenchmarkMode(Benchmark b)
Definition gothic.cpp:492
bool isPause() const
Definition gothic.cpp:459
std::function< bool(const Npc *)> isNpcInDialogFn
Definition gothic.h:171
void setGame(std::unique_ptr< GameSession > &&w)
Definition gothic.cpp:290
Tempest::Signal< void(const ChapterScreen::Show &)> onIntroChapter
Definition gothic.h:177
void emitGlobalSoundWav(std::string_view wav)
Definition gothic.cpp:432
Tempest::Signal< void(std::string_view)> onPrint
Definition gothic.h:174
void quickLoad()
Definition gothic.cpp:619
Tempest::Signal< void(std::string_view, std::string_view)> onSaveGame
Definition gothic.h:168
void startSave(Tempest::Texture2d &&tex, const std::function< std::unique_ptr< GameSession >(std::unique_ptr< GameSession > &&)> f)
Definition gothic.cpp:520
Tempest::Signal< void()> onSessionExit
Definition gothic.h:181
void tick(uint64_t dt)
Definition gothic.cpp:598
void quickSave()
Definition gothic.cpp:615
Tempest::Signal< void()> onWorldLoaded
Definition gothic.h:179
void keyUpEvent(Tempest::KeyEvent &e) override
void keyDownEvent(Tempest::KeyEvent &e) override
bool isActive() const
void tick(uint64_t dt)
void keyRepeatEvent(Tempest::KeyEvent &e) override
void paintNumOverlay(Tempest::PaintEvent &e)
State isOpen() const
void open(Npc &pl)
@ ActionGeneric
Definition keycodec.h:45
@ Inventory
Definition keycodec.h:27
Mapping mapping(Tempest::KeyEvent const &e) const
Gets a mapping out of a key event.
Definition keycodec.cpp:135
Action tr(Tempest::KeyEvent const &e) const
Definition keycodec.cpp:107
~MainWindow() override
MainWindow(Tempest::Device &device)
Constructs the main window.
float uiScale() const
Returns the current UI scaling factor.
void popMenu()
Definition menuroot.cpp:79
void setPlayer(const Npc &pl)
Definition menuroot.cpp:110
void processMusicTheme()
Definition menuroot.cpp:115
bool isActive() const
Definition menuroot.cpp:106
void setMainMenu()
Definition menuroot.cpp:47
void showVersion(bool s)
Definition menuroot.cpp:120
void keyDownEvent(Tempest::KeyEvent &event) override
Definition menuroot.cpp:171
void keyRepeatEvent(Tempest::KeyEvent &event) override
Definition menuroot.cpp:155
void setMenu(std::string_view menu, KeyCodec::Action key=KeyCodec::Escape)
Definition menuroot.cpp:52
auto weaponState() const -> WeaponState
Definition npc.cpp:3621
Bounds bounds() const
Definition npc.cpp:674
auto transform() const -> Tempest::Matrix4x4
Definition npc.cpp:632
int32_t attribute(Attribute a) const
Definition npc.cpp:1171
bool isDead() const
Definition npc.cpp:3962
auto interactive() const -> Interactive *
Definition npc.h:294
bool isPressed(KeyCodec::Action a) const
void onRotateMouse(float dAngle)
void onKeyReleased(KeyCodec::Action a, KeyCodec::Mapping mapping=KeyCodec::Mapping::Primary)
bool tickMove(uint64_t dt)
void onRotateMouseDy(float dAngle)
bool tickCameraMove(uint64_t dt)
Focus focus() const
void onKeyPressed(KeyCodec::Action a, Tempest::Event::KeyType key, KeyCodec::Mapping mapping=KeyCodec::Mapping::Primary)
void resetSwapchain()
Definition renderer.cpp:109
Tempest::Attachment screenshoot(uint8_t frameId)
void onWorldChanged()
Definition renderer.cpp:282
void dbgDraw(Tempest::Painter &painter)
Definition renderer.cpp:539
void draw(Tempest::Encoder< Tempest::CommandBuffer > &cmd, uint8_t cmdId, size_t imgId, Tempest::VectorImage::Mesh &uiLayer, Tempest::VectorImage::Mesh &numOverlay, InventoryMenu &inventory, VideoWidget &video)
static void resetRecycled(uint8_t fId)
@ MaxFramesInFlight
Definition resources.h:48
static Tempest::Texture2d loadTextureUncached(std::string_view name, bool forceMips=false)
static const GthFont & font(const float scale)
static const Tempest::Texture2d * loadTexture(std::string_view name, bool forceMips=false)
void paintEvent(Tempest::PaintEvent &event) override
void keyUpEvent(Tempest::KeyEvent &event) override
void paint(Tempest::Device &device, uint8_t fId)
void keyDownEvent(Tempest::KeyEvent &event) override
bool isActive() const
void pushVideo(std::string_view filename)
Npc * player() const
Definition world.h:111
@ ATR_MANAMAX
Definition constants.h:466
@ ATR_HITPOINTSMAX
Definition constants.h:464
@ ATR_HITPOINTS
Definition constants.h:463
@ ATR_MANA
Definition constants.h:465
Main application window for OpenGothic.
float mouseSysSpeed()
Definition mouseutil.cpp:12