OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
gamesession.cpp
Go to the documentation of this file.
1#include "gamesession.h"
2#include "savegameheader.h"
3
4#include <Tempest/Log>
5#include <Tempest/MemReader>
6#include <Tempest/MemWriter>
7#include <cctype>
8
9#include "utils/string_frm.h"
10#include "worldstatestorage.h"
11#include "world/objects/npc.h"
12#include "world/world.h"
13#include "sound/soundfx.h"
14#include "serialize.h"
15#include "camera.h"
16#include "gothic.h"
17
18using namespace Tempest;
19
20// rate 14.5 to 1
21const uint64_t GameSession::multTime=14500;
22const uint64_t GameSession::divTime =1000;
23
24void GameSession::HeroStorage::save(Npc& npc) {
25 storage.clear();
26 Tempest::MemWriter wr{storage};
27 Serialize sr{wr};
28 sr.setEntry("hero");
29
30 npc.save(sr,0,"/npc/");
31 }
32
33void GameSession::HeroStorage::putToWorld(World& owner, std::string_view wayPoint) const {
34 if(storage.size()==0)
35 return;
36 Tempest::MemReader rd{storage};
37 Serialize sr{rd};
38 sr.setEntry("hero");
39
40 if(auto pl = owner.player()) {
41 pl->load(sr,0,"/npc/");
42 auto pos = owner.findPoint(wayPoint);
43 if(pos==nullptr) {
44 // freemine.zen
45 pos = &owner.startPoint();
46 }
47 pl->attachToPoint(pos);
48 } else {
49 auto ptr = std::make_unique<Npc>(owner,-1,wayPoint);
50 ptr->load(sr,0,"/npc/");
51 owner.insertPlayer(std::move(ptr),wayPoint);
52 }
53
54 if(auto pl = owner.player()) {
55 if(auto pos = pl->currentWayPoint()) {
56 pl->setPosition (pos->position() );
57 pl->setDirection(pos->direction());
58 }
59 if(pl->isInAir()) {
60 pl->stopAnim("");
61 pl->setAnim(Npc::Anim::Idle);
62 }
63 pl->clearSpeed();
64 pl->updateTransform();
65 }
66 }
67
68
69GameSession::GameSession(std::string file) {
70 cam.reset(new Camera());
71
74 setTime(gtime(8,0));
75
76 vm.reset(new GameScript(*this));
77 initPerceptions();
78
79 setWorld(std::unique_ptr<World>(new World(*this,std::move(file),true,[&](int v){
80 Gothic::inst().setLoadingProgress(int(v*0.55));
81 })));
82
83 vm->initDialogs();
85
86 const bool testMode=false;
87
88 std::string_view hero = testMode ? "PC_ROCKEFELLER" : Gothic::inst().defaultPlayer();
89 //std::string_view hero = "PC_ROCKEFELLER";
90 //std::string_view hero = "PC_HERO";
91 //std::string_view hero = "FireGolem";
92 //std::string_view hero = "Dragon_Undead";
93 //std::string_view hero = "Sheep";
94 //std::string_view hero = "Giant_Bug";
95 //std::string_view hero = "OrcWarrior_Rest";
96 //std::string_view hero = "Snapper";
97 //std::string_view hero = "Lurker";
98 //std::string_view hero = "Scavenger";
99 //std::string_view hero = "StoneGolem";
100 //std::string_view hero = "Waran";
101 //std::string_view hero = "FireWaran";
102 //std::string_view hero = "Bloodfly";
103 //std::string_view hero = "Gobbo_Skeleton";
104 if(!Gothic::inst().isBenchmarkMode())
105 wrld->createPlayer(hero);
106 wrld->postInit();
107
108 if(!testMode)
109 initScripts(true);
110 wrld->triggerOnStart(true);
111 cam->reset(wrld->player());
113 ticks = 1;
114 // wrld->setDayTime(8,0);
115 }
116
120
121 SaveGameHeader hdr;
122 fin.setEntry("header");
123 fin.read(hdr);
124 fin.setGlobalVersion(hdr.version);
125
126 {
127 uint16_t wssSize=0;
128 fin.read(wssSize);
129 visitedWorlds.resize(wssSize);
130 for(size_t i=0; i<wssSize; ++i)
131 fin.read(visitedWorlds[i].name);
132 for(size_t i=0; i<wssSize; ++i)
133 visitedWorlds[i].load(fin);
134 }
135
136 std::string wname;
137 fin.setEntry("game/session");
138 fin.read(ticks,wrldTime,wrldTimePart,wname);
139
140 cam.reset(new Camera());
141 vm.reset(new GameScript(*this));
142 vm->initDialogs();
143
144 if(true) {
145 setWorld(std::unique_ptr<World>(new World(*this,wname,false,[&](int v){
146 Gothic::inst().setLoadingProgress(int(v*0.55));
147 })));
148 wrld->load(fin);
149 }
150
152
153 if(fin.setEntry("game/perc"))
154 vm->loadPerc(fin); else
155 initPerceptions();
156
157 fin.setEntry("game/quests");
158 vm->loadQuests(fin);
159
160 fin.setEntry("game/daedalus");
161 vm->loadVar(fin);
162
163 if(auto hero = wrld->player())
164 vm->setInstanceNPC("HERO",*hero);
165
166 fin.setEntry("game/camera");
167 cam->load(fin,wrld->player());
169 }
170
173
174void GameSession::save(Serialize &fout, std::string_view name, const Pixmap& screen) {
175 SaveGameHeader hdr;
177 hdr.name = name;
178 hdr.world = wrld->name();
179 {
180 time_t now = std::time(nullptr);
181 tm* tp = std::localtime(&now);
182 hdr.pcTime = *tp;
183 }
184 hdr.wrldTime = wrldTime;
185 hdr.playTime = ticks;
186 hdr.isGothic2 = Gothic::inst().version().game;
187
188 fout.setEntry("header");
189 fout.write(hdr);
190 {
191 uint16_t wssSize = uint16_t(visitedWorlds.size());
192 fout.write(wssSize);
193 for(auto& i:visitedWorlds)
194 fout.write(i.name);
195 }
196
197 fout.setEntry("preview.png");
198 fout.write(screen);
199
200 fout.setEntry("game/session");
201 fout.write(ticks,wrldTime,wrldTimePart,wrld->name());
202
203 fout.setEntry("game/camera");
204 cam->save(fout);
206
207 for(auto& i:visitedWorlds) {
208 fout.setEntry("worlds/",i.name);
209 i.save(fout);
210 }
212
213 wrld->save(fout);
215
216 fout.setEntry("game/perc");
217 vm->savePerc(fout);
218
219 fout.setEntry("game/quests");
220 vm->saveQuests(fout);
221
222 fout.setEntry("game/daedalus");
223 vm->saveVar(fout);
225 }
226
228 constexpr const float soundScale = 2.f;
229
230 const float soundVolume = Gothic::inst().settingsGetF("SOUND","soundVolume");
231 sound.setGlobalVolume(soundVolume*soundScale);
232 }
233
234void GameSession::setWorld(std::unique_ptr<World> &&w) {
235 if(wrld) {
236 if(!isWorldKnown(wrld->name()))
237 visitedWorlds.emplace_back(*wrld);
238 }
239 wrld = std::move(w);
240 }
241
242std::unique_ptr<World> GameSession::clearWorld() {
243 if(wrld) {
244 if(!isWorldKnown(wrld->name())) {
245 visitedWorlds.emplace_back(*wrld);
246 }
247 }
248 return std::move(wrld);
249 }
250
251void GameSession::changeWorld(std::string_view world, std::string_view wayPoint) {
252 chWorld.zen = world;
253 chWorld.wp = wayPoint;
254 }
255
257 exitSessionFlg=true;
258 }
259
261 return Gothic::inst().version();
262 }
263
265 if(wrld)
266 return wrld->view();
267 return nullptr;
268 }
269
270Tempest::SoundEffect GameSession::loadSound(const Tempest::Sound &raw) {
271 try {
272 return sound.load(raw);
273 }
274 catch(std::bad_alloc&) {
275 Tempest::Log::d("Exceeding OpenAL source limit");
276 return Tempest::SoundEffect();
277 }
278 }
279
280Tempest::SoundEffect GameSession::loadSound(const SoundFx &fx, bool& looped) {
281 try {
282 return fx.load(sound,looped);
283 }
284 catch(std::bad_alloc&) {
285 Tempest::Log::d("Exceeding OpenAL source limit");
286 return Tempest::SoundEffect();
287 }
288 }
289
291 if(wrld)
292 return wrld->player();
293 return nullptr;
294 }
295
297 sound.setListenerPosition (lpos.pos);
298 sound.setListenerDirection(lpos.front, lpos.up);
299 }
300
302 wrldTime = t;
303 }
304
305void GameSession::tick(uint64_t dt) {
306 wrld->scaleTime(dt);
307
308 // apply ztime multiplyer
309 dt = dt*timeMul + timeMulFract;
310 timeMulFract = dt%1000;
311 dt /= 1000;
312
313 ticks+=dt;
314
315 uint64_t add = dt*multTime + wrldTimePart;
316 wrldTimePart = add%divTime;
317 wrldTime.addMilis(add/divTime);
318
319 vm->tick(dt);
320 wrld->tick(dt);
321 // std::this_thread::sleep_for(std::chrono::milliseconds(60));
322
323 if(exitSessionFlg) {
324 exitSessionFlg = false;
327 return;
328 }
329
330 if(!chWorld.zen.empty()) {
331 for(auto& c:chWorld.zen)
332 c = char(std::tolower(c));
333 size_t beg = chWorld.zen.rfind('\\');
334 size_t end = chWorld.zen.rfind('.');
335
336 std::string wname;
337 if(beg!=std::string::npos && end!=std::string::npos)
338 wname = chWorld.zen.substr(beg+1,end-beg-1);
339 else if(end!=std::string::npos)
340 wname = chWorld.zen.substr(0,end); else
341 wname = chWorld.zen;
342
343 const char *w = (beg!=std::string::npos) ? (chWorld.zen.c_str()+beg+1) : chWorld.zen.c_str();
344
345 if(Resources::hasFile(w)) {
346 string_frm name("LOADING_",wname,".TGA"); // format load-screen name, like "LOADING_OLDWORLD.TGA"
347
348 Gothic::inst().startLoad(name,[this](std::unique_ptr<GameSession>&& game){
349 auto ret = implChangeWorld(std::move(game),chWorld.zen,chWorld.wp);
350 chWorld.zen.clear();
351 return ret;
352 });
353 }
354 }
355 }
356
358 timeMul = uint64_t(t*1000);
359 }
360
361auto GameSession::implChangeWorld(std::unique_ptr<GameSession>&& game,
362 std::string_view world, std::string_view wayPoint) -> std::unique_ptr<GameSession> {
363 size_t cut = world.rfind('\\');
364 std::string_view w = world;
365 if(cut!=std::string::npos)
366 w = world.substr(cut+1);
367
368 if(!Resources::hasFile(w)) {
369 Log::i("World not found[",world,"]");
370 return std::move(game);
371 }
372
373 HeroStorage hdata;
374 if(auto hero = wrld->player())
375 hdata.save(*hero);
376 clearWorld();
377
378 vm->resetVarPointers();
379
380 const WorldStateStorage& wss = findStorage(w);
381 // Update world name for non-empty wss in case we have a mixed-case world name - otherwise wss is empty for already visited world
382 if(!wss.isEmpty())
383 w = wss.name;
384
385 auto loadProgress = [](int v) {
387 };
388
389 initPerceptions();
390 std::unique_ptr<World> ret = std::unique_ptr<World>(new World(*this,w,wss.isEmpty(),loadProgress));
391 setWorld(std::move(ret));
392
393 if(!wss.isEmpty()) {
394 Tempest::MemReader rd {wss.storage.data(),wss.storage.size()};
395 Serialize fin{rd};
396 wrld->load(fin);
397 }
398
399 if(1) {
400 // put hero to world
401 hdata.putToWorld(*game->wrld,wayPoint);
402 }
403 if(auto hero = wrld->player())
404 vm->setInstanceNPC("HERO",*hero);
405
406 initScripts(wss.isEmpty());
407 wrld->triggerOnStart(wss.isEmpty());
408
409 for(auto& i:visitedWorlds)
410 if(i.compareName(wrld->name())){
411 i = std::move(visitedWorlds.back());
412 visitedWorlds.pop_back();
413 break;
414 }
415
416 cam->reset(wrld->player());
417 Log::i("Done loading world[",world,"]");
418 return std::move(game);
419 }
420
421const WorldStateStorage& GameSession::findStorage(std::string_view name) {
422 for(auto& i:visitedWorlds)
423 if(i.compareName(name))
424 return i;
425 static WorldStateStorage wss;
426 return wss;
427 }
428
430 if(wrld)
431 wrld->updateAnimation(dt);
432 }
433
434std::vector<GameScript::DlgChoice> GameSession::updateDialog(const GameScript::DlgChoice &dlg, Npc& player, Npc& npc) {
435 return vm->updateDialog(dlg,player,npc);
436 }
437
438void GameSession::dialogExec(const GameScript::DlgChoice &dlg, Npc& player, Npc& npc) {
439 return vm->exec(dlg,player,npc);
440 }
441
442std::string_view GameSession::messageFromSvm(std::string_view id, int voice) const {
443 if(!wrld)
444 return "";
445 return vm->messageFromSvm(id,voice);
446 }
447
448std::string_view GameSession::messageByName(std::string_view id) const {
449 if(!wrld)
450 return "";
451 return vm->messageByName(id);
452 }
453
454uint32_t GameSession::messageTime(std::string_view id) const {
455 if(!wrld)
456 return 0;
457 return vm->messageTime(id);
458 }
459
461 AiOuputPipe* ret=nullptr;
463 return ret;
464 }
465
466bool GameSession::isNpcInDialog(const Npc& npc) const {
467 return Gothic::inst().isNpcInDialog(npc);
468 }
469
471 return Gothic::inst().isInDialog();
472 }
473
474bool GameSession::isWorldKnown(std::string_view name) const {
475 for(auto& i:visitedWorlds)
476 if(i.name==name)
477 return true;
478 return false;
479 }
480
481void GameSession::initPerceptions() {
482 // NOTE: world is null at this point and most scrip-api will be prone to crash
483 if(vm->hasSymbolName("initPerceptions"))
484 vm->getVm().call_function("initPerceptions");
485 }
486
487void GameSession::initScripts(bool firstTime) {
488 auto wname = wrld->name();
489 auto dot = wname.rfind('.');
490 auto name = (dot==std::string::npos ? wname : wname.substr(0,dot));
491
492 if(firstTime) {
493 if(vm->hasSymbolName("startup_global"))
494 vm->getVm().call_function("startup_global");
495
496 string_frm startup("startup_", name);
497 if(vm->hasSymbolName(startup))
498 vm->getVm().call_function(startup);
499 }
500
501 if(vm->hasSymbolName("init_global"))
502 vm->getVm().call_function("init_global");
503
504 string_frm init("init_",name);
505 if(vm->hasSymbolName(init))
506 vm->getVm().call_function(init);
507
508 wrld->resetPositionToTA();
509 }
std::string_view messageFromSvm(std::string_view id, int voice) const
auto updateDialog(const GameScript::DlgChoice &dlg, Npc &player, Npc &npc) -> std::vector< GameScript::DlgChoice >
void tick(uint64_t dt)
GameSession()=delete
auto clearWorld() -> std::unique_ptr< World >
void changeWorld(std::string_view world, std::string_view wayPoint)
void dialogExec(const GameScript::DlgChoice &dlg, Npc &player, Npc &npc)
void setTime(gtime t)
void setTimeMultiplyer(float t)
void updateAnimation(uint64_t dt)
void exitSession()
uint32_t messageTime(std::string_view id) const
bool isNpcInDialog(const Npc &npc) const
void setupSettings()
void updateListenerPos(const Camera::ListenerPos &lpos)
bool isInDialog() const
auto version() const -> const VersionInfo &
void save(Serialize &fout, std::string_view name, const Tempest::Pixmap &screen)
AiOuputPipe * openDlgOuput(Npc &player, Npc &npc)
WorldView * view() const
void setWorld(std::unique_ptr< World > &&w)
const World * world() const
Definition gamesession.h:42
std::string_view messageByName(std::string_view id) const
auto loadSound(const Tempest::Sound &raw) -> Tempest::SoundEffect
std::string_view defaultPlayer() const
Definition gothic.cpp:689
void openDialogPipe(Npc &player, Npc &npc, AiOuputPipe *&pipe)
Definition gothic.cpp:639
static Gothic & inst()
Definition gothic.cpp:249
auto version() const -> const VersionInfo &
Definition gothic.cpp:263
auto clearGame() -> std::unique_ptr< GameSession >
Definition gothic.cpp:295
void startLoad(std::string_view banner, const std::function< std::unique_ptr< GameSession >(std::unique_ptr< GameSession > &&)> f)
Definition gothic.cpp:526
bool isNpcInDialog(const Npc &npc) const
Definition gothic.cpp:643
bool isInDialog() const
Definition gothic.cpp:647
static float settingsGetF(std::string_view sec, std::string_view name)
Definition gothic.cpp:841
void setLoadingProgress(int v)
Definition gothic.cpp:335
Tempest::Signal< void()> onSessionExit
Definition gothic.h:181
Definition npc.h:25
void save(Serialize &fout, size_t id, std::string_view directory)
Definition npc.cpp:205
static bool hasFile(std::string_view fname)
std::string world
std::string name
void write(const Arg &... a)
Definition serialize.h:76
bool setEntry(const Args &... args)
Definition serialize.h:57
void read(Arg &... a)
Definition serialize.h:81
void setGlobalVersion(uint16_t v)
Definition serialize.h:54
Tempest::SoundEffect load(Tempest::SoundDevice &dev, bool &loop) const
Definition soundfx.cpp:49
std::vector< uint8_t > storage
Definition world.h:31
const WayPoint * findPoint(std::string_view name, bool inexact=true) const
Definition world.cpp:871
void insertPlayer(std::unique_ptr< Npc > &&npc, std::string_view waypoint)
Definition world.cpp:138
const WayPoint & startPoint() const
Definition world.cpp:961
Npc * player() const
Definition world.h:111
void addMilis(uint64_t t)
Definition gametime.h:15
Tempest::Vec3 front
Definition camera.h:44
Tempest::Vec3 up
Definition camera.h:43
Tempest::Vec3 pos
Definition camera.h:45