OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
marvin.cpp
Go to the documentation of this file.
1#include "marvin.h"
2
3#include <charconv>
4#include <cstdint>
5#include <cctype>
6
7#include "utils/string_frm.h"
8#include "world/objects/npc.h"
11#include "camera.h"
12#include "gothic.h"
13
14static bool startsWith(std::string_view str, std::string_view needle) {
15 if(needle.size()>str.size())
16 return false;
17 for(size_t i=0; i<needle.size(); ++i) {
18 int n = std::tolower(needle[i]);
19 int s = std::tolower(str [i]);
20 if(n!=s)
21 return false;
22 }
23 return true;
24 }
25
26static bool compareNoCase(std::string_view sa, std::string_view sb) {
27 if(sa.size()!=sb.size())
28 return false;
29 for(size_t i=0; i<sa.size(); ++i) {
30 int a = std::tolower(sa[i]);
31 int b = std::tolower(sb[i]);
32 if(a!=b)
33 return false;
34 }
35 return true;
36 }
37
38template<class T>
39static bool fromString(std::string_view str, T& t) {
40 auto err = std::from_chars(str.data(), str.data()+str.size(),t).ec;
41 if(err!=std::errc())
42 return false;
43 return true;
44 }
45
46static bool fromString(std::string_view str, float& t) {
47 t = std::stof(std::string(str));
48 return true;
49 }
50
52 /* Legend:
53 %c - instance variable from script
54 %v - variable from script
55 %s - any string
56 %d - integer
57 %f - floating point number
58 */
59 cmd = std::vector<Cmd>{
60 // gdb-like commands
61 {"p %v", C_PrintVar},
62 // similar to "Marvin Helper" https://steamcommunity.com/sharedfiles/filedetails/?id=2847617433
63 {"set var %v %s", C_SetVar},
64
65 // animation
66 {"play ani %s", C_PlayAni},
67 {"play faceani %s", C_Invalid},
68 {"remove overlaymds %s", C_Invalid},
69 {"toggle aniinfo", C_Invalid},
70 {"zoverlaymds apply", C_Invalid},
71 {"zoverlaymds remove", C_Invalid},
72 {"zstartani %s %s", C_Invalid},
73 {"ztoggle modelskeleton", C_Invalid},
74 {"ztoggle rootsmoothnode", C_Invalid},
75 {"ztoggle vobmorph", C_Invalid},
76
77 // rendering
78 {"set clipfactor %d", C_Invalid},
79 {"toggle frame", C_ToggleFrame},
80 {"zfogzone", C_Invalid},
81 {"zhighqualityrender", C_Invalid},
82 {"zmark", C_Invalid},
83 {"zprogmeshlod", C_Invalid},
84 {"zrmode flat", C_Invalid},
85 {"zrmode mat", C_Invalid},
86 {"zrmode wire", C_Invalid},
87 {"zrmode wmat", C_Invalid},
88 {"zset levelclipscaler %f", C_Invalid},
89 {"zset nearclipz %f", C_Invalid},
90 {"zset vobfarclipscaler %f", C_Invalid},
91 {"ztoggle ambientvobs", C_Invalid},
92 {"ztoggle flushambientcol", C_Invalid},
93 {"ztoggle lightstat", C_Invalid},
94 {"ztoggle markpmeshmaterials", C_Invalid},
95 {"ztoggle matmorph", C_Invalid},
96 {"ztoggle renderorder", C_Invalid},
97 {"ztoggle renderportals", C_Invalid},
98 {"ztoggle rendervob", C_Invalid},
99 {"ztoggle showportals", C_Invalid},
100 {"ztoggle showtraceray", C_Invalid},
101 {"ztoggle tnl", C_Invalid},
102 {"ztoggle vobbox", C_Invalid},
103 {"zvideores %d %d %d", C_Invalid},
104
105 // game
106 {"LC1", C_Invalid},
107 {"LC2", C_Invalid},
108 {"load game", C_Invalid},
109 {"save game", C_Invalid},
110 {"save zen", C_Invalid},
111 {"set time %d %d", C_SetTime},
112 {"spawnmass %d", C_Invalid},
113 {"spawnmass giga %d", C_Invalid},
114 {"toggle desktop", C_ToggleDesktop},
115 {"toggle freepoints", C_Invalid},
116 {"toggle screen", C_Invalid},
117 {"toggle time", C_ToggleTime},
118 {"toggle wayboxes", C_Invalid},
119 {"toggle waynet", C_Invalid},
120 {"version", C_Invalid},
121 {"zstartrain", C_Invalid},
122 {"zstartsnow", C_Invalid},
123 {"ztimer multiplyer %f", C_TimeMultiplyer},
124 {"ztimer realtime", C_TimeRealtime},
125 {"ztoggle helpervisuals", C_Invalid},
126 {"ztoggle showzones", C_Invalid},
127 {"ztrigger %s", C_ZTrigger},
128 {"zuntrigger %s", C_ZUntrigger},
129
130 {"cheat full", C_CheatFull},
131 {"cheat god", C_CheatGod},
132 {"kill", C_Kill},
133
134 {"aigoto %s", C_AiGoTo},
135 {"goto waypoint %s", C_GoToWayPoint},
136 {"goto pos %f %f %f", C_GoToPos},
137 {"goto vob %c %d", C_GoToVob},
138 {"goto camera", C_GoToCamera},
139
140 {"camera autoswitch", C_CamAutoswitch},
141 {"camera mode", C_CamMode},
142 {"toggle camdebug", C_ToggleCamDebug},
143 {"toggle camera", C_ToggleCamera},
144 {"toggle inertiatarget", C_ToggleInertia},
145 {"ztoggle timedemo", C_ZToggleTimeDemo},
146 {"insert %c", C_Insert},
147
148 {"toggle gi", C_ToggleGI},
149 {"toggle vsm", C_ToggleVsm},
150 {"toggle rtsm", C_ToggleRtsm},
151 };
152 }
153
154Marvin::CmdVal Marvin::isMatch(std::string_view inp, const Cmd& cmd) const {
155 std::string_view ref = cmd.cmd;
156 CmdVal ret = C_Invalid;
157 int argc = 0;
158
159 while(!ref.empty()) {
160 size_t wr = ref.find(' ');
161 if(wr==std::string_view::npos)
162 wr = ref.size();
163 size_t wi = inp.find(' ');
164 if(wi==std::string_view::npos)
165 wi = inp.size();
166
167 auto wref = ref.substr(0,wr);
168 auto winp = inp.substr(0,wi);
169
170 bool fullword = true;
171 if(wref=="%c")
172 wref = completeInstanceName(winp,fullword);
173 else if(wref=="%v")
174 wref = completeInstanceName(winp,fullword);
175 else if(wref=="%s")
176 wref = winp;
177 else if(wref=="%f" || wref=="%d")
178 wref = winp;
179
180 if(!compareNoCase(wref,winp)) {
181 if(!startsWith(wref,winp))
182 return C_Invalid;
183 ret.cmd = cmd;
184 ret.cmd.type = C_Incomplete;
185 ret.complete = wref.substr(winp.size());
186 ret.fullword = fullword;
187 return ret;
188 }
189
190 if(ref[0]=='%' && argc<4) {
191 ret.argv[argc] = wref;
192 ++argc;
193 }
194
195 if(ref.size()==wr) {
196 if(inp.size()!=winp.size())
197 return C_Extra;
198 break;
199 }
200
201 if(inp.size()==wr)
202 return C_Incomplete;
203
204 ref = ref.substr(wr+1);
205 inp = inp.substr(std::min(wi+1,inp.size()));
206 while(inp.size()>0 && inp[0]==' ')
207 inp = inp.substr(1);
208 }
209
210 ret.cmd = cmd;
211 return ret;
212 }
213
214Marvin::CmdVal Marvin::recognize(std::string_view inp) {
215 while(inp.size()>0 && inp.back()==' ')
216 inp = inp.substr(0,inp.size()-1);
217 while(inp.size()>0 && inp[0]==' ')
218 inp = inp.substr(1);
219
220 if(inp.size()==0)
221 return C_None;
222
223 CmdVal suggestion = C_Invalid;
224
225 for(auto& i:cmd) {
226 auto m = isMatch(inp,i);
227 if(m.cmd.type==C_Invalid)
228 continue;
229 if(m.cmd.type!=C_Incomplete)
230 return m;
231 if(suggestion.cmd.type==C_Incomplete) {
232 if(suggestion.cmd.cmd!=m.cmd.cmd && suggestion.complete!=m.complete)
233 return C_Incomplete; // multiple distinct commands do match - no suggestion
234 suggestion = m;
235 continue;
236 }
237 suggestion = m;
238 }
239
240 return suggestion;
241 }
242
243bool Marvin::autoComplete(std::string& v) {
244 auto ret = recognize(v);
245 if(ret.cmd.type==C_Incomplete && !ret.complete.empty()) {
246 for(auto& i:ret.complete)
247 v.push_back(i);
248 if(ret.fullword)
249 v.push_back(' ');
250 return true;
251 }
252 return false;
253 }
254
255bool Marvin::exec(std::string_view v) {
256 auto ret = recognize(v);
257 switch(ret.cmd.type) {
258 case C_None:
259 return true;
260 case C_Incomplete:
261 case C_Invalid:
262 case C_Extra:
263 return false;
264 case C_CheatFull:{
265 if(auto pl = Gothic::inst().player()) {
266 pl->changeAttribute(ATR_HITPOINTS,pl->attribute(ATR_HITPOINTSMAX),false);
267 }
268 return true;
269 }
270 case C_CheatGod: {
271 auto& fnt = Resources::font(1.0);
272 if(Gothic::inst().isGodMode()) {
273 Gothic::inst().setGodMode(false);
274 Gothic::inst().onPrintScreen("Godmode off",2,4,1,fnt);
275 } else {
276 Gothic::inst().setGodMode(true);
277 Gothic::inst().onPrintScreen("Godmode on",2,4,1,fnt);
278 }
279 return true;
280 }
281 case C_Kill: {
282 Npc* player = Gothic::inst().player();
283 if(player==nullptr || player->target()==nullptr)
284 return false;
285 auto target = player->target();
286 target->changeAttribute(ATR_HITPOINTS,-target->attribute(ATR_HITPOINTSMAX),false);
287 return true;
288 }
289 case C_AiGoTo: {
290 World* world = Gothic::inst().world();
291 Npc* player = Gothic::inst().player();
292 if(world==nullptr || player==nullptr || !player->setInteraction(nullptr))
293 return false;
294 auto wpoint = world->findPoint(ret.argv[0]);
295 if(wpoint==nullptr)
296 return false;
297 player->aiPush(AiQueue::aiGoToPoint(*wpoint));
298 return true;
299 }
300 case C_GoToPos: {
301 Npc* player = Gothic::inst().player();
302 if(player==nullptr || !player->setInteraction(nullptr))
303 return false;
304 int c[3] = {};
305 for(int i=0; i<3; ++i) {
306 if(!fromString(ret.argv[i], c[i]))
307 return false;
308 }
309 player->setPosition(float(c[0]),float(c[1]),float(c[2]));
310 player->updateTransform();
311 Gothic::inst().camera()->reset(player);
312 return true;
313 }
314 case C_GoToWayPoint: {
315 World* world = Gothic::inst().world();
316 Npc* player = Gothic::inst().player();
317 if(world==nullptr || player==nullptr || !player->setInteraction(nullptr))
318 return false;
319 auto wpoint = world->findPoint(ret.argv[0]);
320 if(wpoint==nullptr)
321 return false;
322 player->setPosition(wpoint->position());
323 player->updateTransform();
324 Gothic::inst().camera()->reset(player);
325 return true;
326 }
327 case C_GoToVob: {
328 World* world = Gothic::inst().world();
329 Npc* player = Gothic::inst().player();
330 auto c = Gothic::inst().camera();
331 if(world==nullptr || c==nullptr || player==nullptr)
332 return false;
333 size_t n = 1;
334 if(!ret.argv[1].empty() && !fromString(ret.argv[1], n)) {
335 return false;
336 }
337 return goToVob(*world,*player,*c,ret.argv[0],--n);
338 }
339 case C_GoToCamera: {
340 auto c = Gothic::inst().camera();
341 Npc* player = Gothic::inst().player();
342 if(c==nullptr || player==nullptr)
343 return false;
344 auto pos = c->destPosition();
345 player->setPosition(pos.x,pos.y,pos.z);
346 player->updateTransform();
347 c->reset();
348 return true;
349 }
350 case C_ToggleFrame:{
351 Gothic::inst().setFRate(!Gothic::inst().doFrate());
352 return true;
353 }
354 case C_ToggleTime:{
355 Gothic::inst().setClock(!Gothic::inst().doClock());
356 return true;
357 }
358 case C_TimeMultiplyer: {
359 if(auto g = Gothic::inst().gameSession()) {
360 float mul = 1;
361 if(!fromString(ret.argv[0], mul))
362 return false;
363 mul = std::max(mul, 0.f);
364 g->setTimeMultiplyer(mul);
365 return true;
366 }
367 return false;
368 }
369 case C_TimeRealtime:{
370 if(auto g = Gothic::inst().gameSession()) {
371 g->setTimeMultiplyer(1);
372 return true;
373 }
374 return false;
375 }
376 case C_CamAutoswitch:
377 return true;
378 case C_CamMode:
379 return true;
380 case C_ToggleCamDebug:
381 if(auto c = Gothic::inst().camera())
382 c->toggleDebug();
383 return true;
384 case C_ToggleCamera: {
385 if(auto c = Gothic::inst().camera())
386 c->setToggleEnable(!c->isToggleEnabled());
387 return true;
388 }
389 case C_ToggleInertia: {
390 if(auto c = Gothic::inst().camera())
391 c->setInertiaTargetEnable(!c->isInertiaTargetEnabled());
392 return true;
393 }
394 case C_ZToggleTimeDemo: {
395 World* world = Gothic::inst().world();
396 if(world==nullptr)
397 return false;
398 const TriggerEvent evt("TIMEDEMO","",world->tickCount(),TriggerEvent::T_Trigger);
399 world->triggerEvent(evt);
400 return true;
401 }
402 case C_ToggleDesktop: {
404 return true;
405 }
406 case C_ZTrigger: {
407 World* world = Gothic::inst().world();
408 if(world==nullptr)
409 return false;
410 const TriggerEvent evt(std::string(ret.argv[0]),"",world->tickCount(),TriggerEvent::T_Trigger);
411 world->triggerEvent(evt);
412 return true;
413 }
414 case C_ZUntrigger: {
415 World* world = Gothic::inst().world();
416 if(world==nullptr)
417 return false;
418 const TriggerEvent evt(std::string(ret.argv[0]),"",world->tickCount(),TriggerEvent::T_Untrigger);
419 world->triggerEvent(evt);
420 return true;
421 }
422 case C_Insert: {
423 World* world = Gothic::inst().world();
424 Npc* player = Gothic::inst().player();
425 if(world==nullptr || player==nullptr)
426 return false;
427 return addItemOrNpcBySymbolName(world, ret.argv[0], player->position());
428 }
429 case C_SetTime: {
430 World* world = Gothic::inst().world();
431 if(world==nullptr)
432 return false;
433 return setTime(*world, ret.argv[0], ret.argv[1]);
434 }
435 case C_PrintVar: {
436 World* world = Gothic::inst().world();
437 if(world==nullptr)
438 return false;
439 return printVariable(world, ret.argv[0]);
440 }
441
442 case C_SetVar: {
443 World* world = Gothic::inst().world();
444 Npc* player = Gothic::inst().player();
445 if(world==nullptr || player==nullptr)
446 return false;
447 return setVariable(world, ret.argv[0], ret.argv[1]);
448 }
449
450 case C_PlayAni: {
451 Npc* player = Gothic::inst().player();
452 if(player==nullptr)
453 return false;
454 return player->playAnimByName(ret.argv[0], BS_NONE) != nullptr;
455 }
456
457 case C_ToggleGI:
459 return true;
460 case C_ToggleVsm:
462 return true;
463 case C_ToggleRtsm:
465 return true;
466 }
467
468 return true;
469 }
470
471bool Marvin::addItemOrNpcBySymbolName(World* world, std::string_view name, const Tempest::Vec3& at) {
472 auto& sc = world->script();
473 size_t id = sc.findSymbolIndex(name);
474 if(id==size_t(-1))
475 return false;
476
477 auto* sym = sc.findSymbol(id);
478 if(sym==nullptr || sym->parent()==uint32_t(-1))
479 return false;
480
481 if(sym->type()!=zenkit::DaedalusDataType::INSTANCE || sym->address()==0)
482 return false;
483
484 const auto* cls = sym;
485 while(cls!=nullptr && cls->parent()!=uint32_t(-1)) {
486 cls = sc.findSymbol(cls->parent());
487 }
488
489 if(cls==nullptr)
490 return false;
491
492 if(cls->name()=="C_NPC")
493 return (world->addNpc(id, at)!=nullptr);
494 if(cls->name()=="C_ITEM")
495 return (world->addItem(id, at)!=nullptr);
496 return false;
497 }
498
499bool Marvin::printVariable(World* world, std::string_view name) {
500 string_frm buf;
501 auto& sc = world->script();
502 auto* sym = sc.findSymbol(name);
503 if(sym==nullptr)
504 return false;
505 switch(sym->type()) {
506 case zenkit::DaedalusDataType::INT:
507 buf = string_frm(name," = ",sym->get_int(0));
508 break;
509 case zenkit::DaedalusDataType::FLOAT:
510 buf = string_frm(name," = ",sym->get_float(0));
511 break;
512 case zenkit::DaedalusDataType::STRING:
513 buf = string_frm(name," = ",sym->get_string(0));
514 break;
515 case zenkit::DaedalusDataType::INSTANCE:
516 buf = string_frm(name," = ",sym->get_instance().get());
517 break;
518 default:
519 break;
520 }
521 print(buf);
522 return true;
523 }
524
525bool Marvin::setVariable(World* world, std::string_view name, std::string_view value) {
526 auto& sc = world->script();
527 auto* sym = sc.findSymbol(name);
528 if(sym==nullptr)
529 return false;
530 switch(sym->type()) {
531 case zenkit::DaedalusDataType::INT: {
532 int32_t valueI = 0;
533 if(!fromString(value, valueI))
534 return false;
535 sym->set_int(valueI);
536 break;
537 }
538 case zenkit::DaedalusDataType::FLOAT: {
539 float valueF = 0;
540 if(!fromString(value, valueF))
541 return false;
542 sym->set_float(valueF);
543 break;
544 }
545 case zenkit::DaedalusDataType::STRING:
546 sym->set_string(value);
547 break;
548 case zenkit::DaedalusDataType::INSTANCE:
549 print("unable to override instance variable");
550 break;
551 default:
552 return false;
553 }
554 printVariable(world, name);
555 return true;
556 }
557
558bool Marvin::setTime(World& world, std::string_view hh, std::string_view mm) {
559 int hv = 0, mv = 0;
560
561 auto err = std::from_chars(hh.data(), hh.data()+hh.size(), hv, 10).ec;
562 if(err!=std::errc())
563 return false;
564
565 err = std::from_chars(mm.data(), mm.data()+mm.size(), mv, 10).ec;
566 if(err!=std::errc()) {
567 mv = 0;
568 }
569
570 if(hv<0 || hv>=24 || mv<0 || mv>=60)
571 return false;
572
573 world.setDayTime(hv,mv);
574 return true;
575 }
576
577bool Marvin::goToVob(World& world, Npc& player, Camera& c, std::string_view name, size_t n) {
578 auto& sc = world.script();
579 size_t id = sc.findSymbolIndex(name);
580 if(id==size_t(-1))
581 return false;
582
583 Tempest::Vec3 pos;
584 if(auto npc = world.findNpcByInstance(id,n))
585 pos = npc->position();
586 else if(auto it = world.findItemByInstance(id,n))
587 pos = it->position();
588 else
589 return false;
590
591 if(!player.setInteraction(nullptr))
592 return false;
593 player.setPosition(pos);
594 player.updateTransform();
595 c.reset();
596 return true;
597 }
598
599std::string_view Marvin::completeInstanceName(std::string_view inp, bool& fullword) const {
600 World* world = Gothic::inst().world();
601 if(world==nullptr || inp.size()==0)
602 return "";
603
604 auto& sc = world->script();
605 std::string_view match = "";
606 for(size_t i=0; i<sc.symbolsCount(); ++i) {
607 auto* sym = sc.findSymbol(i);
608 auto name = std::string_view(sym->name());
609 if(!startsWith(name,inp))
610 continue;
611
612 if(match.empty()) {
613 match = name;
614 fullword = true;
615 continue;
616 }
617 if(name.size()>match.size())
618 name = name.substr(0,match.size());
619 for(size_t i=0; i<name.size(); ++i) {
620 int n = std::tolower(name [i]);
621 int m = std::tolower(match[i]);
622 if(m!=n) {
623 name = name.substr(0,i);
624 fullword = false;
625 break;
626 }
627 }
628 match = name;
629 }
630 return match;
631 }
static AiAction aiGoToPoint(const WayPoint &to)
Definition aiqueue.cpp:181
void reset()
Definition camera.cpp:42
size_t findSymbolIndex(std::string_view s)
zenkit::DaedalusSymbol * findSymbol(std::string_view s)
Camera * camera()
Definition gothic.cpp:319
void toggleDesktop()
Definition gothic.h:130
Tempest::Signal< void(std::string_view, int, int, int, const GthFont &)> onPrintScreen
Definition gothic.h:173
const World * world() const
Definition gothic.cpp:278
void setFRate(bool f)
Definition gothic.h:134
static Gothic & inst()
Definition gothic.cpp:249
void setGodMode(bool g)
Definition gothic.h:128
void setClock(bool t)
Definition gothic.h:137
Tempest::Signal< void()> toggleGi
Definition gothic.h:143
Tempest::Signal< void()> toggleVsm
Definition gothic.h:143
Npc * player()
Definition gothic.cpp:313
Tempest::Signal< void()> toggleRtsm
Definition gothic.h:143
Marvin()
Definition marvin.cpp:51
bool exec(std::string_view v)
Definition marvin.cpp:255
Tempest::Signal< void(std::string_view)> print
Definition marvin.h:13
bool autoComplete(std::string &v)
Definition marvin.cpp:243
Definition npc.h:25
void updateTransform()
Definition npc.cpp:4565
auto playAnimByName(std::string_view name, BodyState bs) -> const Animation::Sequence *
Definition npc.cpp:937
void aiPush(AiQueue::AiAction &&a)
Definition npc.cpp:3218
bool setPosition(float x, float y, float z)
Definition npc.cpp:388
auto position() const -> Tempest::Vec3
Definition npc.cpp:628
void changeAttribute(Attribute a, int32_t val, bool allowUnconscious)
Definition npc.cpp:1177
bool setInteraction(Interactive *id, bool quick=false)
Definition npc.cpp:4101
Npc * target() const
Definition npc.cpp:2916
static const GthFont & font(const float scale)
Definition world.h:31
void triggerEvent(const TriggerEvent &e)
Definition world.cpp:495
uint64_t tickCount() const
Definition world.cpp:387
Npc * addNpc(std::string_view name, std::string_view at)
Definition world.cpp:590
const WayPoint * findPoint(std::string_view name, bool inexact=true) const
Definition world.cpp:871
Npc * findNpcByInstance(size_t instance, size_t n=0)
Definition world.cpp:307
Item * addItem(size_t itemInstance, std::string_view at)
Definition world.cpp:609
void setDayTime(int32_t h, int32_t min)
Definition world.cpp:391
Item * findItemByInstance(size_t instance, size_t n=0)
Definition world.cpp:303
GameScript & script() const
Definition world.cpp:1019
@ ATR_HITPOINTSMAX
Definition constants.h:464
@ ATR_HITPOINTS
Definition constants.h:463
@ BS_NONE
Definition constants.h:141
static bool compareNoCase(std::string_view sa, std::string_view sb)
Definition marvin.cpp:26
static bool startsWith(std::string_view str, std::string_view needle)
Definition marvin.cpp:14
static bool fromString(std::string_view str, T &t)
Definition marvin.cpp:39