OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
playercontrol.cpp
Go to the documentation of this file.
1#include "playercontrol.h"
2
3#include <cmath>
4
5#include "world/objects/npc.h"
8#include "world/world.h"
9#include "ui/dialogmenu.h"
10#include "ui/inventorymenu.h"
11#include "gothic.h"
12
14 :dlg(dlg),inv(inv) {
15 Gothic::inst().onSettingsChanged.bind(this,&PlayerControl::setupSettings);
16 setupSettings();
17 }
18
20 Gothic::inst().onSettingsChanged.ubind(this,&PlayerControl::setupSettings);
21 }
22
23void PlayerControl::setupSettings() {
24 if(Gothic::inst().version().game==2) {
25 g2Ctrl = Gothic::inst().settingsGetI("GAME","USEGOTHIC1CONTROLS")==0;
26 } else {
27 g2Ctrl = false;
28 }
29 }
30
32 auto w = Gothic::inst().world();
33 auto pl = w ? w->player() : nullptr;
34 if(pl==nullptr || pl->isFinishingMove())
35 return;
36 const auto ws = pl->weaponState();
37 const bool melle = (ws==WeaponState::Fist || ws==WeaponState::W1H || ws==WeaponState::W2H);
38 if(other==nullptr) {
39 if(!(melle && ctrl[Action::ActionGeneric])) {
40 // dont lose focus in melee combat
41 pl->setTarget(nullptr);
42 }
43 } else {
44 pl->setTarget(other);
45 }
46 }
47
48void PlayerControl::onKeyPressed(KeyCodec::Action a, Tempest::KeyEvent::KeyType key, KeyCodec::Mapping mapping) {
49 auto w = Gothic::inst().world();
50 auto c = Gothic::inst().camera();
51 auto pl = w ? w->player() : nullptr;
52 auto ws = pl ? pl->weaponState() : WeaponState::NoWeapon;
53 uint8_t slot = pl ? pl->inventory().currentSpellSlot() : Item::NSLOT;
54
55 if(w!=nullptr && w->isCutsceneLock())
56 return;
57
58 handleMovementAction(KeyCodec::ActionMapping{a,mapping}, true);
59
60 if(pl!=nullptr && pl->interactive()!=nullptr && c!=nullptr && !c->isFree()) {
61 auto inter = pl->interactive();
62 if(inter->needToLockpick(*pl)) {
63 processPickLock(*pl,*inter,a);
64 return;
65 }
66 if(inter->isLadder()) {
67 ctrl[a] = true;
68 return;
69 }
70 }
71
72 if(pl!=nullptr) {
73 if(a==Action::Weapon) {
74 if(ws!=WeaponState::NoWeapon) //Currently a weapon is active
75 wctrl[WeaponClose] = true;
76 else {
77 if(wctrlLast>=WeaponAction::Weapon3 && pl->inventory().currentSpell(static_cast<uint8_t>(wctrlLast-3))==nullptr)
78 wctrlLast=WeaponAction::WeaponBow; //Spell no longer available -> fallback to Bow.
79 if(wctrlLast==WeaponAction::WeaponBow && pl->currentRangedWeapon()==nullptr)
80 wctrlLast=WeaponAction::WeaponMele; //Bow no longer available -> fallback to Mele.
81 wctrl[wctrlLast] = true;
82 }
83 return;
84 }
85
86 if(a==Action::WeaponMele) {
88 wctrl[WeaponClose] = true; else
89 wctrl[WeaponMele ] = true;
90 return;
91 }
92
93 if(a==Action::WeaponBow) {
95 wctrl[WeaponClose] = true; else
96 wctrl[WeaponBow ] = true;
97 return;
98 }
99
100 if(a>=Action::WeaponMage3 && a<=Action::WeaponMage10) {
101 int id = (a-Action::WeaponMage3+3);
102 if(ws==WeaponState::Mage && slot==id)
103 wctrl[WeaponClose] = true; else
104 wctrl[id ] = true;
105 return;
106 }
107
108 if(key==Tempest::KeyEvent::K_Return)
109 ctrl[Action::K_ENTER] = true;
110 }
111
112 // this odd behaviour is from original game, seem more like a bug
113 // const bool actTunneling = (pl!=nullptr && pl->isAttackAnim());
114 const bool actTunneling = false;
115
116 int fk = -1;
117 if((ctrl[KeyCodec::ActionGeneric] || actTunneling) && !g2Ctrl) {
118 if(a==Action::Forward) {
119 if(pl!=nullptr && pl->target()!=nullptr && pl->canFinish(*pl->target()) && !pl->isAttackAnim()) {
120 fk = ActKill;
121 } else {
122 fk = ActForward;
123 }
124 }
126 if(a==Action::Back)
127 fk = ActBack;
128 }
129 if(ws!=WeaponState::NoWeapon && !g2Ctrl && !pl->hasState(BS_RUN)) {
130 if(a==Action::Left || a==Action::RotateL)
131 fk = ActLeft;
132 if(a==Action::Right || a==Action::RotateR)
133 fk = ActRight;
134 }
135 }
136
137 if(g2Ctrl) {
138 if(ws!=WeaponState::NoWeapon) {
139 if(a==Action::ActionGeneric) {
140 if(pl!=nullptr && pl->target()!=nullptr && pl->canFinish(*pl->target()) && !pl->isAttackAnim()) {
141 fk = ActKill;
142 } else {
143 if(this->wantsToMoveForward())
144 fk = ActMove; else
145 fk = ActForward;
146 }
147 }
148 }
150 if(a==Action::Parade)
151 fk = ActBack;
152 }
153 if(ws!=WeaponState::NoWeapon && !pl->hasState(BS_RUN)) {
154 if(a==Action::ActionLeft)
155 fk = ActLeft;
156 if(a==Action::ActionRight)
157 fk = ActRight;
158 }
159 }
160
161 if(fk>=0) {
162 std::memset(actrl,0,sizeof(actrl));
163 actrl[ActGeneric] = ctrl[KeyCodec::ActionGeneric];
164 actrl[fk] = true;
165
166 ctrl[a] = true;
167 return;
168 }
169
171 FocusAction fk = ActGeneric;
172 if(this->wantsToMoveForward())
173 fk = ActMove;
174 std::memset(actrl,0,sizeof(actrl));
175 actrl[fk] = true;
176 ctrl[a] = true;
177 return;
178 }
179
180 if(a==Action::Walk) {
181 toggleWalkMode();
182 return;
183 }
184
185 if(a==Action::Sneak) {
186 toggleSneakMode();
187 return;
188 }
189
190 if(a==Action::FirstPerson) {
191 if(auto c = Gothic::inst().camera())
192 c->setFirstPerson(!c->isFirstPerson());
193 return;
194 }
195
196 if(a==Action::K_O && Gothic::inst().isMarvinEnabled())
197 marvinO();
198
199 ctrl[a] = true;
200 }
201
203 ctrl[a] = false;
204
205 handleMovementAction(KeyCodec::ActionMapping{a, mapping}, false);
206
207 auto w = Gothic::inst().world();
208 auto pl = w ? w->player() : nullptr;
209
210 if(a==KeyCodec::Map && pl!=nullptr) {
211 w->script().playerHotKeyScreenMap(*pl);
212 }
213 if(a==KeyCodec::Heal && pl!=nullptr) {
214 w->script().playerHotLameHeal(*pl);
215 }
216 if(a==KeyCodec::Potion && pl!=nullptr) {
217 w->script().playerHotLamePotion(*pl);
218 }
219
220 auto ws = pl==nullptr ? WeaponState::NoWeapon : pl->weaponState();
222 if(a==KeyCodec::ActionGeneric || (!g2Ctrl && ws==WeaponState::Mage && a==KeyCodec::Forward))
223 std::memset(actrl,0,sizeof(actrl));
224 } else {
225 std::memset(actrl,0,sizeof(actrl));
226 }
227 }
228
229auto PlayerControl::handleMovementAction(KeyCodec::ActionMapping actionMapping, bool pressed) -> void {
230 auto[action, mapping] = actionMapping;
231 auto mappingIndex = (mapping == KeyCodec::Mapping::Primary ? size_t(0) : size_t(1));
232 if (action == Action::Forward)
233 movement.forwardBackward.main[mappingIndex] = pressed;
234 else if (action == Action::Back)
235 movement.forwardBackward.reverse[mappingIndex] = pressed;
236 else if (action == Action::Right)
237 movement.strafeRightLeft.main[mappingIndex] = pressed;
238 else if (action == Action::Left)
239 movement.strafeRightLeft.reverse[mappingIndex] = pressed;
240 else if (action == Action::RotateR)
241 movement.turnRightLeft.main[mappingIndex] = pressed;
242 else if (action == Action::RotateL)
243 movement.turnRightLeft.reverse[mappingIndex] = pressed;
244 }
245
247 return ctrl[a];
248 }
249
251 dAngle = std::max(-40.f,std::min(dAngle,40.f));
252 rotMouse += dAngle*0.3f;
253 }
254
256 dAngle = std::max(-100.f,std::min(dAngle,100.f));
257 rotMouseY += dAngle*0.2f;
258 }
259
261 currentFocus = findFocus(&currentFocus);
262 setTarget(currentFocus.npc);
263
264 if(!ctrl[Action::ActionGeneric])
265 return;
266
267 auto focus = currentFocus;
268 if(focus.interactive!=nullptr && interact(*focus.interactive)) {
269 clearInput();
270 }
271 else if(focus.npc!=nullptr && interact(*focus.npc)) {
272 clearInput();
273 }
274 else if(focus.item!=nullptr && interact(*focus.item)) {
275 clearInput();
276 }
277
278 if(focus.npc)
279 actionFocus(*focus.npc); else
280 emptyFocus();
281 }
282
284 currentFocus = Focus();
285 }
286
288 setTarget(&other);
289 }
290
292 setTarget(nullptr);
293 }
294
296 return currentFocus;
297 }
298
300 if(!ctrl[Action::ActionGeneric])
301 return false;
302 return currentFocus.npc!=nullptr;
303 }
304
306 auto w = Gothic::inst().world();
307 if(w==nullptr || w->player()==nullptr)
308 return false;
309 auto pl = w->player();
310 if(w->player()->isDown())
311 return true;
312 if(!canInteract())
313 return false;
314 if(it.isContainer()){
315 inv.open(*pl,it);
316 return true;
317 }
318 if(pl->setInteraction(&it)){
319 }
320 return true;
321 }
322
324 auto w = Gothic::inst().world();
325 if(w==nullptr || w->player()==nullptr)
326 return false;
327 auto pl = w->player();
328 if(pl->isDown())
329 return true;
330 auto state = pl->bodyStateMasked();
331 if(state!=BS_STAND && state!=BS_SNEAK)
332 return false;
333 if(!canInteract())
334 return false;
335 if(w->script().isDead(other) || w->script().isUnconscious(other)) {
336 if(!inv.ransack(*w->player(),other))
337 w->script().printNothingToGet();
338 }
339 if((pl->bodyStateMasked()&BS_MAX)!=BS_NONE)
340 return false;
341 other.startDialog(*pl);
342 return true;
343 }
344
346 auto w = Gothic::inst().world();
347 if(w==nullptr || w->player()==nullptr)
348 return false;
349 auto pl = w->player();
350 if(item.isTorchBurn() && pl->isUsingTorch())
351 return false;
352 if(pl->isDown())
353 return true;
354 if(!canInteract())
355 return false;
356 return pl->takeItem(item)!=nullptr;
357 }
358
359void PlayerControl::moveFocus(FocusAction act) {
360 auto w = Gothic::inst().world();
361 auto c = Gothic::inst().camera();
362 if(w==nullptr || c==nullptr || currentFocus.npc==nullptr)
363 return;
364
365 auto vp = c->viewProj();
366 auto pos = currentFocus.npc->position()+Tempest::Vec3(0,currentFocus.npc->translateY(),0);
367 vp.project(pos);
368
369 Npc* next = nullptr;
370 auto npos = Tempest::Vec3();
371 for(uint32_t i=0; i<w->npcCount(); ++i) {
372 auto npc = w->npcById(i);
373 if(npc->isPlayer())
374 continue;
375 auto p = npc->position()+Tempest::Vec3(0,npc->translateY(),0);
376 vp.project(p);
377
378 if(std::abs(p.x)>1.f || std::abs(p.y)>1.f || p.z<0.f)
379 continue;
380
381 if(!w->testFocusNpc(npc))
382 continue;
383
384 if(act==ActLeft && p.x<pos.x && (next==nullptr || npos.x<p.x)) {
385 npos = p;
386 next = npc;
387 }
388 if(act==ActRight && p.x>pos.x && (next==nullptr || npos.x>p.x)) {
389 npos = p;
390 next = npc;
391 }
392 }
393
394 if(next==nullptr)
395 return;
396 currentFocus.npc = next;
397 }
398
399void PlayerControl::toggleWalkMode() {
400 auto w = Gothic::inst().world();
401 if(w==nullptr || w->player()==nullptr)
402 return;
403 auto pl = w->player();
404 pl->setWalkMode(pl->walkMode()^WalkBit::WM_Walk);
405 }
406
407void PlayerControl::toggleSneakMode() {
408 auto w = Gothic::inst().world();
409 if(w==nullptr || w->player()==nullptr)
410 return;
411 auto pl = w->player();
412 if(pl->canSneak())
413 pl->setWalkMode(pl->walkMode()^WalkBit::WM_Sneak);
414 }
415
416bool PlayerControl::canInteract() const {
417 auto w = Gothic::inst().world();
418 if(w==nullptr || w->player()==nullptr)
419 return false;
420 auto pl = w->player();
421 if(pl->weaponState()!=WeaponState::NoWeapon || pl->isAiBusy())
422 return false;
423 return true;
424 }
425
427 movement.reset();
428 std::memset(ctrl, 0,sizeof(ctrl));
429 std::memset(actrl,0,sizeof(actrl));
430 std::memset(wctrl,0,sizeof(wctrl));
431 }
432
433void PlayerControl::marvinF8(uint64_t dt) {
434 auto w = Gothic::inst().world();
435 if(w==nullptr || w->player()==nullptr)
436 return;
437
438 auto& pl = *w->player();
439 auto pos = pl.position();
440 float rot = pl.rotationRad();
441 float s = std::sin(rot), c = std::cos(rot);
442
443 Tempest::Vec3 dp(s,0.8f,-c);
444 pos += dp*6000*float(dt)/1000.f;
445
446 pl.changeAttribute(ATR_HITPOINTS,pl.attribute(ATR_HITPOINTSMAX),false);
447 pl.changeAttribute(ATR_MANA, pl.attribute(ATR_MANAMAX), false);
448 pl.clearState(false);
449 pl.clearSpeed();
450 pl.clearAiQueue();
451 pl.setPosition(pos);
452 pl.setInteraction(nullptr,true);
453 pl.setAnim(AnimationSolver::Idle);
454
455 if(auto c = Gothic::inst().camera())
456 c->reset();
457 }
458
459void PlayerControl::marvinK(uint64_t dt) {
460 auto w = Gothic::inst().world();
461 if (w == nullptr || w->player() == nullptr)
462 return;
463
464 auto& pl = *w->player();
465 auto pos = pl.position();
466 float rot = pl.rotationRad();
467 float s = std::sin(rot), c = std::cos(rot);
468
469 Tempest::Vec3 dp(s, 0.0f, -c);
470 pos += dp * 6000 * float(dt) / 1000.f;
471
472 pl.clearState(false);
473 pl.clearSpeed();
474 pl.setPosition(pos);
475 pl.setInteraction(nullptr,true);
476 // pl.setAnim(AnimationSolver::Idle); // Original G2 behaviour: K doesn't stop running
477 }
478
479void PlayerControl::marvinO() {
480 auto w = Gothic::inst().world();
481 if (w == nullptr || w->player() == nullptr || w->player()->target() == nullptr)
482 return;
483
484 auto target = w->player()->target();
485
486 w->setPlayer(target);
487 }
488
489Focus PlayerControl::findFocus(Focus* prev) {
490 auto w = Gothic::inst().world();
491 auto c = Gothic::inst().camera();
492 if(w==nullptr)
493 return Focus();
494 if(w->player()!=nullptr && w->player()->isDown())
495 return Focus();
496 if(c!=nullptr && c->isCutscene())
497 return Focus();
498 if(!cacheFocus)
499 prev = nullptr;
500
501 if(prev)
502 return w->findFocus(*prev);
503 return w->findFocus(Focus());
504 }
505
507 auto w = Gothic::inst().world();
508 if(w==nullptr)
509 return false;
510
511 Npc* pl = w->player();
512 auto camera = Gothic::inst().camera();
513 if(camera==nullptr || (pl!=nullptr && !camera->isFree()))
514 return false;
515
516 rotMouse = 0;
517 if(ctrl[KeyCodec::Left] || (ctrl[KeyCodec::RotateL] && ctrl[KeyCodec::Jump])) {
518 camera->moveLeft(dt);
519 return true;
520 }
521 if(ctrl[KeyCodec::Right] || (ctrl[KeyCodec::RotateR] && ctrl[KeyCodec::Jump])) {
522 camera->moveRight(dt);
523 return true;
524 }
525
526 auto turningVal = movement.turnRightLeft.value();
527 if(turningVal > 0.f)
528 camera->rotateRight(dt);
529 else if(turningVal < 0.f)
530 camera->rotateLeft(dt);
531
532 auto forwardVal = movement.forwardBackward.value();
533 if(forwardVal > 0.f)
534 camera->moveForward(dt);
535 else if(forwardVal < 0.f)
536 camera->moveBack(dt);
537 return true;
538 }
539
540bool PlayerControl::tickMove(uint64_t dt) {
541 auto w = Gothic::inst().world();
542 if(w==nullptr)
543 return false;
544 const float dtF = float(dt)/1000.f;
545
546 Npc* pl = w->player();
547 auto camera = Gothic::inst().camera();
548
549 if(w->isCutsceneLock())
550 clearInput();
551
552 if(tickCameraMove(dt))
553 return true;
554
555 if(ctrl[Action::K_F8] && Gothic::inst().isMarvinEnabled())
556 marvinF8(dt);
557 if(ctrl[Action::K_K] && Gothic::inst().isMarvinEnabled())
558 marvinK(dt);
559 cacheFocus = ctrl[Action::ActionGeneric];
560 if(camera!=nullptr)
561 camera->setLookBack(ctrl[Action::LookBack]);
562
563 if(pl==nullptr)
564 return true;
565
566 static const float speedRotX = 750.f;
567 rotMouse = std::min(std::abs(rotMouse), speedRotX*dtF) * (rotMouse>=0 ? 1 : -1);
568 implMove(dt);
569
570 float runAngle = pl->runAngle();
571 if(runAngle!=0.f || std::fabs(runAngleDest)>0.01f) {
572 const float speed = 35.f;
573 if(runAngle<runAngleDest) {
574 runAngle+=speed*dtF;
575 if(runAngle>runAngleDest)
576 runAngle = runAngleDest;
577 pl->setRunAngle(runAngle);
578 }
579 else if(runAngle>runAngleDest) {
580 runAngle-=speed*dtF;
581 if(runAngle<runAngleDest)
582 runAngle = runAngleDest;
583 pl->setRunAngle(runAngle);
584 }
585 }
586
587 rotMouseY = 0;
588 return true;
589 }
590
591void PlayerControl::implMove(uint64_t dt) {
592 auto w = Gothic::inst().world();
593 Npc& pl = *w->player();
594 float rot = pl.rotation();
595 float rotY = pl.rotationY();
596 // 100 / 200 according to some sources, yet my mesures are 90/180
597 float rspeed = (pl.weaponState()==WeaponState::NoWeapon ? 90.f : 180.f)*(float(dt)/1000.f);
598 auto ws = pl.weaponState();
599 auto bs = pl.bodyStateMasked();
600 bool allowRot = !ctrl[KeyCodec::ActionGeneric] && pl.isRotationAllowed();
601
602 Npc::Anim ani = Npc::Anim::Idle;
603
604 if(bs==BS_DEAD)
605 return;
606 if(bs==BS_UNCONSCIOUS)
607 return;
608
609 if(!pl.isAiQueueEmpty()) {
610 runAngleDest = 0;
611 return;
612 }
613
614 if(pl.interactive()!=nullptr) {
615 runAngleDest = 0;
616 implMoveMobsi(pl,dt);
617 return;
618 }
619
620 if(pl.canSwitchWeapon()) {
621 if(wctrl[WeaponClose]) {
622 wctrl[WeaponClose] = !(pl.closeWeapon(false) || pl.isMonster());
623 return;
624 }
625 if(wctrl[WeaponMele]) {
626 bool ret=false;
627 if(pl.currentMeleeWeapon()!=nullptr)
628 ret = pl.drawWeaponMelee(); else
629 ret = pl.drawWeaponFist();
630 wctrl[WeaponMele] = !ret;
631 wctrlLast = WeaponMele;
632 return;
633 }
634 if(wctrl[WeaponBow]) {
635 if(pl.currentRangedWeapon()!=nullptr) {
636 wctrl[WeaponBow] = !pl.drawWeaponBow();
637 wctrlLast = WeaponBow;
638 } else {
639 wctrl[WeaponBow] = false;
640 }
641 return;
642 }
643 for(uint8_t i=0;i<8;++i) {
644 if(wctrl[Weapon3+i]){
645 if(pl.inventory().currentSpell(i)!=nullptr){
646 bool ret = pl.drawMage(uint8_t(3+i));
647 wctrl[Weapon3+i] = !ret;
648 wctrlLast = static_cast<WeaponAction>(Weapon3+i);
649 if(ret) {
650 if(auto spl = pl.inventory().currentSpell(i)) {
651 Gothic::inst().onPrint(spl->description());
652 }
653 }
654 } else {
655 wctrl[Weapon3+i] = false;
656 }
657 return;
658 }
659 }
660 }
661
662 if(!pl.isInState(ScriptFn()) || dlg.isActive()) {
663 runAngleDest = 0;
664 return;
665 }
666
667 int rotation=0;
668 if(allowRot) {
669 if(this->wantsToTurnLeft()) {
670 rot += rspeed;
671 rotation = -1;
672 rotMouse=0;
673 }
674 if(this->wantsToTurnRight()) {
675 rot -= rspeed;
676 rotation = 1;
677 rotMouse=0;
678 }
679 if(std::fabs(rotMouse)>0.f) {
680 if(rotMouse>0)
681 rotation = -1; else
682 rotation = 1;
683 rot +=rotMouse;
684 rotMouse = 0;
685 }
686 rotY+=rotMouseY;
687 } else {
688 rotMouse = 0;
689 rotMouseY = 0;
690 }
691
692 pl.setDirectionY(rotY);
693 if(pl.isFalling() || pl.isSlide() || pl.isInAir()){
694 pl.setDirection(rot);
695 runAngleDest = 0;
696 return;
697 }
698
699 if(casting) {
700 if(!actrl[ActForward] || (Gothic::inst().version().game==1 && pl.attribute(ATR_MANA)==0)) {
701 casting = false;
702 pl.endCastSpell(true);
703 }
704 return;
705 }
706
707 if(ctrl[Action::K_ENTER]) {
708 pl.transformBack();
709 ctrl[Action::K_ENTER] = false;
710 }
711
712 if((ws==WeaponState::Bow || ws==WeaponState::CBow) && pl.hasAmmunition()) {
713 if(actrl[ActGeneric] || actrl[ActForward]) {
714 if(auto other = pl.target()) {
715 auto dp = other->position()-pl.position();
716 pl.turnTo(dp.x,dp.z,true,dt);
717 pl.aimBow();
718 } else
719 if(currentFocus.interactive!=nullptr) {
720 auto dp = currentFocus.interactive->position()-pl.position();
721 pl.turnTo(dp.x,dp.z,false,dt);
722 }
723 pl.aimBow();
724 if(actrl[ActLeft]) {
725 moveFocus(ActLeft);
726 actrl[ActLeft] = false;
727 }
728 if(actrl[ActRight]) {
729 moveFocus(ActRight);
730 actrl[ActRight] = false;
731 }
732 if(!actrl[ActForward])
733 return;
734 }
735 }
736
737 if(ws==WeaponState::Mage) {
738 if(actrl[ActGeneric] || actrl[ActForward] || ctrl[KeyCodec::ActionGeneric]) {
739 if(auto other = pl.target()) {
740 auto dp = other->position()-pl.position();
741 pl.turnTo(dp.x,dp.z,true,dt);
742 } else
743 if(currentFocus.interactive!=nullptr) {
744 auto dp = currentFocus.interactive->position()-pl.position();
745 pl.turnTo(dp.x,dp.z,false,dt);
746 }
747
748 if(actrl[ActLeft]) {
749 moveFocus(ActLeft);
750 actrl[ActLeft] = false;
751 }
752 if(actrl[ActRight]) {
753 moveFocus(ActRight);
754 actrl[ActRight] = false;
755 }
756 if(!actrl[ActForward])
757 return;
758 }
759 }
760
761 if(actrl[ActForward] || actrl[ActMove]) {
762 ctrl [Action::Forward] = actrl[ActMove];
763 actrl[ActMove] = false;
764 if(ws!=WeaponState::Mage && !(g2Ctrl && (ws==WeaponState::Bow || ws==WeaponState::CBow))) {
765 actrl[ActForward] = false;
766 if(!ctrl[Action::Forward])
767 movement.reset();
768 }
769 switch(ws) {
771 break;
773 pl.fistShoot();
774 return;
775 case WeaponState::W1H:
776 case WeaponState::W2H: {
777 pl.swingSword();
778 return;
779 }
780 case WeaponState::Bow:
781 case WeaponState::CBow: {
782 pl.shootBow(currentFocus.interactive);
783 return;
784 }
785 case WeaponState::Mage: {
786 casting = (pl.beginCastSpell()==Npc::BC_Invest);
787 if(!casting)
788 actrl[ActForward] = false;
789 return;
790 }
791 }
792 }
793
794 if(actrl[ActKill]) {
795 if((ws==WeaponState::W1H || ws==WeaponState::W2H) && pl.target()!=nullptr && pl.canFinish(*pl.target()))
796 pl.finishingMove();
797 actrl[ActKill] = false;
798 }
799
800 if(actrl[ActLeft] || actrl[ActRight] || actrl[ActBack]) {
801 auto ws = pl.weaponState();
802 if(ws==WeaponState::Fist) {
803 if(actrl[ActBack])
804 pl.blockFist();
805 return;
806 }
807 else if(ws==WeaponState::W1H || ws==WeaponState::W2H) {
808 if(actrl[ActLeft] && pl.swingSwordL()) {
809 movement.strafeRightLeft.reset();
810 }
811 else if(actrl[ActRight] && pl.swingSwordR()) {
812 movement.strafeRightLeft.reset();
813 }
814 else if(actrl[ActBack] && pl.blockSword()) {
815 // movement.forwardBackward.reset();
816 }
817
818 actrl[ActLeft] = false;
819 actrl[ActRight] = false;
820 // actrl[ActBack] = false;
821 return;
822 }
823 else if(ws==WeaponState::Mage) {
824 if(actrl[ActLeft]) {
825 moveFocus(ActLeft);
826 actrl[ActLeft] = false;
827 }
828 if(actrl[ActRight]) {
829 moveFocus(ActRight);
830 actrl[ActRight] = false;
831 }
832 }
833 }
834
835 if(this->wantsToStrafeLeft()) {
836 ani = Npc::Anim::MoveL;
837 }
838 else if(this->wantsToStrafeRight()) {
839 ani = Npc::Anim::MoveR;
840 }
841 else if(this->wantsToMoveForward()) {
843 ani = Npc::Anim::Move;
844 }
845 else if(pl.isDive()) {
846 pl.setDirectionY(rotY - rspeed);
847 return;
848 }
849 }
850 else if(this->wantsToMoveBackward()) {
852 ani = Npc::Anim::MoveBack;
853 } else if(pl.isDive()) {
854 pl.setDirectionY(rotY + rspeed);
855 return;
856 }
857 }
858
859
860 if(ctrl[Action::Jump]) {
861 if(pl.bodyStateMasked()==BS_JUMP) {
862 ani = Npc::Anim::Idle;
863 }
864 else if(pl.isDive()) {
865 ani = Npc::Anim::Move;
866 }
867 else if(pl.isSwim()) {
868 pl.startDive();
869 }
870 else if(pl.isInWater()) {
871 auto& g = w->script().guildVal();
872 auto gl = pl.guild();
873
874 if(0<=gl && gl<GIL_MAX && pl.isStanding()) {
876 jump.anim = Npc::Anim::JumpUp;
877 jump.height = float(g.jumpup_height[gl])+pl.position().y;
878 pl.startClimb(jump);
879 }
880 }
881 else if(pl.isStanding()) {
882 auto jump = pl.tryJump();
883 if(!pl.isFalling() && !pl.isSlide() && jump.anim!=Npc::Anim::Jump){
884 pl.startClimb(jump);
885 return;
886 }
887 ani = Npc::Anim::Jump;
888 }
889 else {
890 ani = Npc::Anim::Jump;
891 }
892 }
893
894 if(!pl.isCasting()) {
895 if(ani==Npc::Anim::Jump) {
896 pl.setAnimRotate(0);
897 rotation = 0;
898 }
899
900 if(pl.isAttackAnim()) {
901 if((ani==Npc::Anim::MoveL || ani==Npc::Anim::MoveR/* || ani==Npc::Anim::MoveBack*/) && pl.hasState(BS_RUN)) {
902 ani = Npc::Anim::Idle;
903 }
904
905 if(!pl.hasState(BS_RUN) && ani==Npc::Anim::Idle) {
906 // charge-run
907 ani = Npc::Anim::NoAnim;
908 }
909 if((ani==Npc::Anim::MoveL || ani==Npc::Anim::MoveR) &&
910 pl.hasState(BS_STAND) && pl.hasState(BS_HIT)) {
911 // no charge to strafe transition
912 ani = Npc::Anim::NoAnim;
913 }
914 }
915
916 if(bs==BS_LIE) {
917 ani = (ani==Npc::Anim::Move) ? Npc::Anim::Idle : Npc::Anim::NoAnim;
918 rot = pl.rotation();
919 }
920
921 if(ani!=Npc::Anim::NoAnim)
922 pl.setAnim(ani);
923 }
924
925 setAnimRotate(pl, rot, ani==Npc::Anim::Idle ? rotation : 0, movement.turnRightLeft.any(), dt);
926 if(actrl[ActGeneric] || ani==Npc::Anim::MoveL || ani==Npc::Anim::MoveR || pl.isFinishingMove()) {
927 processAutoRotate(pl,rot,dt);
928 }
929
930 if(ani==Npc::Anim::Move && (rotation!=0 || rotY!=0)) {
931 assignRunAngle(pl,rot,dt);
932 } else {
933 assignRunAngle(pl,pl.rotation(),dt);
934 }
935 pl.setDirection(rot);
936 }
937
938void PlayerControl::implMoveMobsi(Npc& pl, uint64_t /*dt*/) {
939 // animation handled in MOBSI
940 auto inter = pl.interactive();
941
942 if(ctrl[KeyCodec::Back] && !inter->isLadder()) {
943 pl.setInteraction(nullptr);
944 return;
945 }
946
947 if(inter->needToLockpick(pl) && !inter->isCracked()) {
948 return;
949 }
950
951 if(!inter->isLadder() && inter->isStaticState() && !inter->isDetachState(pl)) {
952 auto stateId = inter->stateId();
953 if(inter->canQuitAtState(pl,stateId))
954 pl.setInteraction(nullptr,false);
955 }
956
957 if(inter->isLadder()) {
958 if(ctrl[KeyCodec::ActionGeneric]) {
959 inter->onKeyInput(KeyCodec::ActionGeneric);
960 ctrl[KeyCodec::ActionGeneric] = false;
961 }
962 else if(ctrl[KeyCodec::Forward]) {
963 inter->onKeyInput(KeyCodec::Forward);
964 }
965 else if(ctrl[KeyCodec::Back]) {
966 inter->onKeyInput(KeyCodec::Back);
967 }
968 }
969 }
970
971void PlayerControl::processPickLock(Npc& pl, Interactive& inter, KeyCodec::Action k) {
972 auto w = Gothic::inst().world();
973 auto& script = w->script();
974 const size_t ItKE_lockpick = script.lockPickId();
975
976 char ch = '\0';
978 ch = 'L';
979 else if(k==KeyCodec::Right || k==KeyCodec::RotateR)
980 ch = 'R';
981 else if(k==KeyCodec::Back) {
982 quitPicklock(pl);
983 return;
984 }
985 else
986 return;
987
988 auto cmp = inter.pickLockCode();
989 while(pickLockProgress<cmp.size()) {
990 auto c = cmp[pickLockProgress];
991 if(c=='l' || c=='L' || c=='r' || c=='R')
992 break;
993 ++pickLockProgress;
994 }
995
996 if(pickLockProgress<cmp.size() && std::toupper(cmp[pickLockProgress])!=ch) {
997 pickLockProgress = 0;
998 const int32_t dex = Gothic::inst().version().game==2 ? pl.attribute(ATR_DEXTERITY) : (100 - pl.talentValue(TALENT_PICKLOCK));
999 if(dex<=int32_t(script.rand(100))) {
1000 script.invokePickLock(pl,0,1);
1001 pl.delItem(ItKE_lockpick,1);
1002 if(pl.inventory().itemCount(ItKE_lockpick)==0) {
1003 quitPicklock(pl);
1004 return;
1005 }
1006 } else {
1007 script.invokePickLock(pl,0,0);
1008 }
1009 } else {
1010 pickLockProgress++;
1011 if(pickLockProgress>=cmp.size()) {
1012 script.invokePickLock(pl,1,1);
1013 inter.setAsCracked(true);
1014 pickLockProgress = 0;
1015 } else {
1016 script.invokePickLock(pl,1,0);
1017 }
1018 }
1019 }
1020
1021void PlayerControl::processLadder(Npc& pl, Interactive& inter, KeyCodec::Action key) {
1023 return;
1024
1025 ctrl[key] = true;
1026 inter.onKeyInput(key);
1027 }
1028
1029void PlayerControl::quitPicklock(Npc& pl) {
1030 inv.close();
1031 pickLockProgress = 0;
1032 pl.setInteraction(nullptr);
1033 }
1034
1035void PlayerControl::assignRunAngle(Npc& pl, float rotation, uint64_t dt) {
1036 float dtF = (float(dt)/1000.f);
1037 float angle = pl.rotation();
1038 float dangle = (rotation-angle)/dtF;
1039 float sgn = (dangle>0 ? 1 : -1);
1040 auto& wrld = pl.world();
1041
1042 if(std::fabs(dangle)<0.1f || pl.walkMode()!=WalkBit::WM_Run) {
1043 if(runAngleSmooth<wrld.tickCount())
1044 runAngleDest = 0;
1045 return;
1046 }
1047
1048 const float maxV = 15.0f;
1049 dangle = std::pow(std::abs(dangle)/maxV,2.f)*maxV*sgn;
1050
1051 float dest = 0;
1052 if(angle<rotation)
1053 dest = std::min( dangle,maxV);
1054 if(angle>rotation)
1055 dest = -std::min(-dangle,maxV);
1056
1057 float a = std::clamp(dtF*2.5f, 0.f, 1.f);
1058 runAngleDest = runAngleDest*(1.f-a)+dest*a;
1059 runAngleSmooth = wrld.tickCount() + 200;
1060 }
1061
1062void PlayerControl::setAnimRotate(Npc& pl, float rotation, int anim, bool force, uint64_t dt) {
1063 float dtF = (float(dt)/1000.f);
1064 float angle = pl.rotation();
1065 float dangle = (rotation-angle)/dtF;
1066 auto& wrld = pl.world();
1067
1068 if(std::fabs(dangle)<100.f && !force) // 100 deg per second threshold
1069 anim = 0;
1070 if(anim!=0 && pl.isAttackAnim())
1071 anim = 0;
1072 if(rotationAni==anim && anim!=0)
1073 force = true;
1074 if(!force && wrld.tickCount()<turnAniSmooth)
1075 return;
1076 turnAniSmooth = wrld.tickCount() + 150;
1077 rotationAni = anim;
1078 pl.setAnimRotate(anim);
1079 }
1080
1081void PlayerControl::processAutoRotate(Npc& pl, float& rot, uint64_t dt) {
1082 if(auto other = pl.target()) {
1084 pl.setTarget(nullptr);
1085 }
1086 else if(!pl.isAttack()) {
1087 auto dp = other->position()-pl.position();
1088 auto gl = pl.guild();
1089 float step = float(pl.world().script().guildVal().turn_speed[gl]);
1090 if(actrl[ActGeneric])
1091 step*=2.f;
1092 pl.rotateTo(dp.x,dp.z,step,AnimationSolver::TurnType::Std,dt);
1093 rot = pl.rotation();
1094 }
1095 }
1096 }
Tempest::Matrix4x4 viewProj() const
Definition camera.cpp:909
bool isActive() const
Definition focus.h:10
Npc * npc
Definition focus.h:22
Interactive * interactive
Definition focus.h:21
Item * item
Definition focus.h:23
uint32_t lockPickId() const
Camera * camera()
Definition gothic.cpp:319
Tempest::Signal< void()> onSettingsChanged
Definition gothic.h:182
const World * world() const
Definition gothic.cpp:278
static Gothic & inst()
Definition gothic.cpp:249
auto version() const -> const VersionInfo &
Definition gothic.cpp:263
static int settingsGetI(std::string_view sec, std::string_view name)
Definition gothic.cpp:807
Tempest::Signal< void(std::string_view)> onPrint
Definition gothic.h:174
std::string_view pickLockCode() const
Definition interactive.h:64
bool isContainer() const
void onKeyInput(KeyCodec::Action act)
void setAsCracked(bool c)
Definition interactive.h:65
bool ransack(Npc &pl, Npc &tr)
void open(Npc &pl)
Definition item.h:14
virtual bool isTorchBurn() const
Definition item.cpp:105
@ NSLOT
Definition item.h:16
@ RotateR
Definition keycodec.h:42
@ ActionGeneric
Definition keycodec.h:45
@ RotateL
Definition keycodec.h:41
@ Forward
Definition keycodec.h:37
Definition npc.h:25
bool drawWeaponFist()
Definition npc.cpp:3515
bool drawWeaponBow()
Definition npc.cpp:3565
void startDive()
Definition npc.cpp:4333
void startDialog(Npc &other)
Definition npc.cpp:4016
auto weaponState() const -> WeaponState
Definition npc.cpp:3621
bool startClimb(JumpStatus jump)
Definition npc.cpp:543
float rotation() const
Definition npc.cpp:658
float translateY() const
Definition npc.cpp:680
auto walkMode() const
Definition npc.h:113
bool setAnim(Anim a)
Definition npc.cpp:941
uint32_t guild() const
Definition npc.cpp:1223
bool isInState(ScriptFn stateFn) const
Definition npc.cpp:4153
bool isInWater() const
Definition npc.cpp:1013
bool drawMage(uint8_t slot)
Definition npc.cpp:3588
bool swingSwordR()
Definition npc.cpp:3701
bool isSwim() const
Definition npc.cpp:1009
void setWalkMode(WalkBit m)
Definition npc.cpp:535
void fistShoot()
Definition npc.cpp:3662
bool isSlide() const
Definition npc.cpp:1041
bool blockSword()
Definition npc.cpp:3708
bool rotateTo(float dx, float dz, float speed, AnimationSolver::TurnType anim, uint64_t dt)
Definition npc.cpp:3398
void setDirection(const Tempest::Vec3 &pos)
Definition npc.cpp:436
float rotationY() const
Definition npc.cpp:666
void setAnimRotate(int rot)
Definition npc.cpp:961
bool shootBow(Interactive *focOverride=nullptr)
Definition npc.cpp:3905
int32_t talentValue(Talent t) const
Definition npc.cpp:1143
bool hasState(BodyState s) const
Definition npc.cpp:3166
bool canFinish(Npc &oth)
Definition npc.cpp:3625
bool isInAir() const
Definition npc.cpp:1045
int32_t attribute(Attribute a) const
Definition npc.cpp:1171
bool blockFist()
Definition npc.cpp:3666
void setDirectionY(float rotation)
Definition npc.cpp:446
JumpStatus tryJump()
Definition npc.cpp:4235
auto inventory() const -> const Inventory &
Definition npc.h:330
auto interactive() const -> Interactive *
Definition npc.h:294
Item * currentMeleeWeapon()
Definition npc.cpp:3362
bool closeWeapon(bool noAnim)
Definition npc.cpp:3490
void endCastSpell(bool playerCtrl=false)
Definition npc.cpp:3869
bool isFalling() const
Definition npc.cpp:1033
auto world() -> World &
Definition npc.cpp:624
auto position() const -> Tempest::Vec3
Definition npc.cpp:628
bool isRotationAllowed() const
Definition npc.cpp:3444
void setRunAngle(float angle)
Definition npc.cpp:458
bool finishingMove()
Definition npc.cpp:3674
void transformBack()
Definition npc.cpp:4337
bool isAttackAnim() const
Definition npc.cpp:3986
bool aimBow()
Definition npc.cpp:3895
bool turnTo(float dx, float dz, bool noAnim, uint64_t dt)
Definition npc.cpp:3394
bool isCasting() const
Definition npc.cpp:1021
Item * takeItem(Item &i)
Definition npc.cpp:3241
bool drawWeaponMelee()
Definition npc.cpp:3539
bool setInteraction(Interactive *id, bool quick=false)
Definition npc.cpp:4101
bool isAiQueueEmpty() const
Definition npc.cpp:4357
bool isDive() const
Definition npc.cpp:1017
bool hasAmmunition() const
Definition npc.cpp:3948
bool isStanding() const
Definition npc.cpp:1005
void delItem(size_t id, uint32_t amount)
Definition npc.cpp:3466
bool isAttack() const
Definition npc.cpp:3978
void setTarget(Npc *t)
Definition npc.cpp:2907
Item * currentRangedWeapon()
Definition npc.cpp:3366
float runAngle() const
Definition npc.h:100
bool isMonster() const
Definition npc.cpp:1227
void swingSword()
Definition npc.cpp:3687
bool swingSwordL()
Definition npc.cpp:3694
Npc * target() const
Definition npc.cpp:2916
BodyState bodyStateMasked() const
Definition npc.cpp:3161
bool canSwitchWeapon() const
Definition npc.cpp:3486
@ BC_Invest
Definition npc.h:72
bool isFinishingMove() const
Definition npc.cpp:999
auto beginCastSpell() -> BeginCastResult
Definition npc.cpp:3716
void actionFocus(Npc &other)
bool isPressed(KeyCodec::Action a) const
bool interact(Interactive &it)
void setTarget(Npc *other)
void onRotateMouse(float dAngle)
void onKeyReleased(KeyCodec::Action a, KeyCodec::Mapping mapping=KeyCodec::Mapping::Primary)
bool tickMove(uint64_t dt)
void onRotateMouseDy(float dAngle)
PlayerControl(DialogMenu &dlg, InventoryMenu &inv)
bool tickCameraMove(uint64_t dt)
bool hasActionFocus() const
Focus focus() const
void onKeyPressed(KeyCodec::Action a, Tempest::Event::KeyType key, KeyCodec::Mapping mapping=KeyCodec::Mapping::Primary)
Tempest::Vec3 position() const
Definition vob.cpp:57
GameScript & script() const
Definition world.cpp:1019
Npc * player() const
Definition world.h:111
@ GIL_MAX
Definition constants.h:79
@ TALENT_PICKLOCK
Definition constants.h:441
@ ATR_MANAMAX
Definition constants.h:466
@ ATR_HITPOINTSMAX
Definition constants.h:464
@ ATR_DEXTERITY
Definition constants.h:468
@ ATR_HITPOINTS
Definition constants.h:463
@ ATR_MANA
Definition constants.h:465
@ BS_SNEAK
Definition constants.h:156
@ BS_NONE
Definition constants.h:141
@ BS_RUN
Definition constants.h:157
@ BS_LIE
Definition constants.h:166
@ BS_JUMP
Definition constants.h:162
@ BS_MAX
Definition constants.h:185
@ BS_HIT
Definition constants.h:180
@ BS_DEAD
Definition constants.h:177
@ BS_UNCONSCIOUS
Definition constants.h:176
@ BS_STAND
Definition constants.h:186
Encapsulates an in-game action and a key mapping that caused it to be fired.
Definition keycodec.h:76
AnimationSolver::Anim anim
Definition movealgo.h:26