OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
animationsolver.cpp
Go to the documentation of this file.
1#include "animationsolver.h"
2
4#include "world/world.h"
5#include "game/serialize.h"
6#include "utils/fileext.h"
7#include "gothic.h"
8#include "skeleton.h"
9#include "pose.h"
10#include "resources.h"
11
12using namespace Tempest;
13
16
18 fout.write(uint32_t(overlay.size()));
19 for(auto& i:overlay){
20 fout.write(i.skeleton->name(),i.time);
21 }
22 }
23
25 uint32_t sz=0;
26 std::string s;
27
28 fin.read(sz);
29 overlay.resize(sz);
30 for(auto& i:overlay){
31 fin.read(s,i.time);
32 i.skeleton = Resources::loadSkeleton(s);
33 }
34
35 sz=0;
36 for(size_t i=0;i<overlay.size();++i){
37 overlay[sz]=overlay[i];
38 if(overlay[i].skeleton!=nullptr)
39 ++sz;
40 }
41 overlay.resize(sz);
42 invalidateCache();
43 }
44
46 baseSk = sk;
47 invalidateCache();
48 }
49
51 for(auto& i:overlay)
52 if(i.skeleton==sk)
53 return true;
54 return false;
55 }
56
57void AnimationSolver::addOverlay(const Skeleton* sk, uint64_t time) {
58 if(sk==nullptr)
59 return;
60 // incompatible overlay
61 if(baseSk==nullptr || sk->nodes.size()!=baseSk->nodes.size())
62 return;
63 Overlay ov;
64 ov.skeleton = sk;
65 ov.time = time;
66 overlay.push_back(ov);
67 invalidateCache();
68 }
69
70void AnimationSolver::delOverlay(std::string_view sk) {
71 if(overlay.size()==0)
72 return;
73 auto skelet = Resources::loadSkeleton(sk);
74 delOverlay(skelet);
75 }
76
78 for(size_t i=0;i<overlay.size();++i)
79 if(overlay[i].skeleton==sk){
80 overlay.erase(overlay.begin()+int(i));
81 invalidateCache();
82 return;
83 }
84 }
85
87 overlay.clear();
88 invalidateCache();
89 }
90
91void AnimationSolver::update(uint64_t tickCount) {
92 for(size_t i=0;i<overlay.size();){
93 auto& ov = overlay[i];
94 if(ov.time!=0 && ov.time<tickCount) {
95 overlay.erase(overlay.begin()+int(i));
96 invalidateCache();
97 } else {
98 ++i;
99 }
100 }
101 }
102
104 if(0<a && a<=AnimationSolver::CacheLast && int(st)<2 && int(wlkMode)<2) {
105 auto& ptr = cache[a-1][int(st)][int(wlkMode)];
106 if(ptr==nullptr)
107 ptr = implSolveAnim(a,st,wlkMode,pose);
108 return ptr;
109 }
110 return implSolveAnim(a,st,wlkMode,pose);
111 }
112
113const Animation::Sequence* AnimationSolver::implSolveAnim(AnimationSolver::Anim a, WeaponState st, WalkBit wlkMode, const Pose& pose) const {
114 // Attack
115 if(st==WeaponState::Fist) {
116 if(a==Anim::Attack) {
117 if(pose.isInAnim("S_FISTRUNL"))
118 return solveFrm("T_FISTATTACKMOVE");
119 return solveFrm("S_FISTATTACK");
120 }
121 if(a==Anim::AttackBlock) {
122 bool g2 = Gothic::inst().version().game==2;
123 return g2 ? solveFrm("T_FISTPARADE_0") : solveFrm("T_FISTPARADE_O");
124 }
125 }
126 else if(st==WeaponState::W1H || st==WeaponState::W2H) {
127 if(a==Anim::Attack && pose.hasState(BS_RUN))
128 return solveFrm("T_%sATTACKMOVE",st);
129 if(a==Anim::AttackL)
130 return solveFrm("T_%sATTACKL",st);
131 if(a==Anim::AttackR)
132 return solveFrm("T_%sATTACKR",st);
133 if(a==Anim::Attack || a==Anim::AttackL || a==Anim::AttackR)
134 return solveFrm("S_%sATTACK",st);
135 if(a==Anim::AttackBlock) {
136 bool g2 = Gothic::inst().version().game==2;
137 if(g2) {
138 const Animation::Sequence* s=nullptr;
139 switch(std::rand()%3) {
140 case 0: s = solveFrm("T_%sPARADE_0", st); break;
141 case 1: s = solveFrm("T_%sPARADE_0_A2",st); break;
142 case 2: s = solveFrm("T_%sPARADE_0_A3",st); break;
143 }
144 if(s==nullptr)
145 s = solveFrm("T_%sPARADE_0",st);
146 return s;
147 } else {
148 return solveFrm("T_%sPARADE_O",st);
149 }
150 }
151 if(a==Anim::AttackFinish)
152 return solveFrm("T_%sSFINISH",st);
153 }
154 else if(st==WeaponState::Bow || st==WeaponState::CBow) {
155 // S_BOWAIM -> S_BOWSHOOT+T_BOWRELOAD -> S_BOWAIM
156 if(a==AimBow) {
157 auto bs = pose.bodyState();
158 if(bs==BS_HIT)
159 return solveFrm("T_%sRELOAD",st);
160 if(bs==BS_AIMNEAR || bs==BS_AIMFAR || pose.isStanding())
161 return solveFrm("S_%sAIM",st);
162 return solveFrm("S_%sRUN",st);
163 }
164 if(a==Attack) {
165 auto bs = pose.bodyState();
166 if(bs==BS_AIMNEAR || bs==BS_AIMFAR)
167 return solveFrm("S_%sSHOOT",st);
168 }
169 }
170
171 if(a==MagNoMana)
172 return solveFrm("T_CASTFAIL");
173 // Move
174 if(a==Idle) {
175 const Animation::Sequence* s = nullptr;
176 if(bool(wlkMode & WalkBit::WM_Dive))
177 s = solveFrm("S_DIVE");
178 else if(bool(wlkMode & WalkBit::WM_Swim))
179 s = solveFrm("S_SWIM");
180 else if(bool(wlkMode&WalkBit::WM_Sneak))
181 s = solveFrm("S_%sSNEAK",st);
182 else if(bool(wlkMode&WalkBit::WM_Walk))
183 s = solveFrm("S_%sWALK",st);
184 else
185 s = solveFrm("S_%sRUN",st);
186
187 if(s==nullptr) {
188 // make sure that 'Idle' has something at least
189 s = solveFrm("S_%sWALK",st);
190 }
191 return s;
192 }
193 if(a==Move) {
194 if(bool(wlkMode & WalkBit::WM_Dive)) {
195 if(pose.bodyState()==BS_DIVE)
196 return solveFrm("S_DIVEF",st); else
197 return solveFrm("S_DIVE");
198 }
199 const Animation::Sequence* s = nullptr;
200 if(bool(wlkMode & WalkBit::WM_Swim))
201 s = solveFrm("S_SWIMF",st);
202 else if(bool(wlkMode & WalkBit::WM_Sneak))
203 s = solveFrm("S_%sSNEAKL",st);
204 else if(bool(wlkMode & WalkBit::WM_Walk))
205 s = solveFrm("S_%sWALKL",st);
206 else if(bool(wlkMode & WalkBit::WM_Water))
207 s = solveFrm("S_%sWALKWL",st);
208 if(s!=nullptr)
209 return s;
210 return solveFrm("S_%sRUNL",st);
211 }
212 if(a==MoveL) {
213 if(bool(wlkMode & WalkBit::WM_Dive))
214 return solveFrm("S_DIVE"); // ???
215 if(bool(wlkMode & WalkBit::WM_Swim))
216 return solveFrm("S_SWIM"); // ???
217 if(bool(wlkMode & WalkBit::WM_Sneak))
218 return solveFrm("T_%sSNEAKSTRAFEL",st);
219 if(bool(wlkMode & WalkBit::WM_Walk))
220 return solveFrm("T_%sWALKWSTRAFEL",st);
221 if(bool(wlkMode & WalkBit::WM_Water))
222 return solveFrm("T_%sWALKWSTRAFEL",st);
223 return solveFrm("T_%sRUNSTRAFEL",st);
224 }
225 if(a==MoveR) {
226 if(bool(wlkMode & WalkBit::WM_Dive))
227 return solveFrm("S_DIVE"); // ???
228 if(bool(wlkMode & WalkBit::WM_Swim))
229 return solveFrm("S_SWIM"); // ???
230 if(bool(wlkMode & WalkBit::WM_Sneak))
231 return solveFrm("T_%sSNEAKSTRAFER",st);
232 if(bool(wlkMode & WalkBit::WM_Walk))
233 return solveFrm("T_%sWALKWSTRAFER",st);
234 if(bool(wlkMode & WalkBit::WM_Water))
235 return solveFrm("T_%sWALKWSTRAFER",st);
236 return solveFrm("T_%sRUNSTRAFER",st);
237 }
238 if(a==MoveBack) {
239 const Animation::Sequence* s = nullptr;
240 if(bool(wlkMode & WalkBit::WM_Dive))
241 s = solveFrm("S_DIVE");
242 else if(bool(wlkMode & WalkBit::WM_Swim))
243 s = solveFrm("S_SWIMB");
244 else if(bool(wlkMode & WalkBit::WM_Sneak))
245 s = solveFrm("S_%sSNEAKBL",st);
246 else if(st==WeaponState::Fist)
247 s = solveFrm("T_%sPARADEJUMPB",st);
248 if(s!=nullptr)
249 return s;
250 // This is bases on original game: if no move-back animation, even in water, game defaults to standard walk-back
251 return solveFrm("T_%sJUMPB",st);
252 }
253 // Rotation
254 if(a==RotL) {
255 if(bool(wlkMode & WalkBit::WM_Dive))
256 return solveFrm("T_DIVETURNL");
257 if(bool(wlkMode & WalkBit::WM_Swim))
258 return solveFrm("T_SWIMTURNL");
259 if(bool(wlkMode & WalkBit::WM_Sneak))
260 return solveFrm("T_SNEAKTURNL");
261 if(bool(wlkMode & WalkBit::WM_Walk))
262 return solveFrm("T_%sWALKTURNL",st);
263 if(bool(wlkMode & WalkBit::WM_Water))
264 return solveFrm("T_%sWALKWTURNL",st);
265 return solveFrm("T_%sRUNTURNL",st);
266 }
267 if(a==RotR) {
268 if(bool(wlkMode & WalkBit::WM_Dive))
269 return solveFrm("T_DIVETURNR");
270 if(bool(wlkMode & WalkBit::WM_Swim))
271 return solveFrm("T_SWIMTURNR");
272 if(bool(wlkMode & WalkBit::WM_Sneak))
273 return solveFrm("T_SNEAKTURNR");
274 if(bool(wlkMode & WalkBit::WM_Walk))
275 return solveFrm("T_%sWALKTURNR",st);
276 if(bool(wlkMode & WalkBit::WM_Water))
277 return solveFrm("T_%sWALKWTURNR",st);
278 return solveFrm("T_%sRUNTURNR",st);
279 }
280 // Whirl around
281 if(a==WhirlL) {
282 // Whirling in water should not happen, but just to make sure
283 if(bool(wlkMode & WalkBit::WM_Dive))
284 return solveFrm("T_DIVETURNL");
285 if(bool(wlkMode & WalkBit::WM_Swim))
286 return solveFrm("T_SWIMTURNL");
287
288 // whirling is only used in G1, but the script function is present
289 // in all games. In G2 all models except the human also have the
290 // needed animation.
291 const Animation::Sequence* s = solveFrm("T_SURPRISE_CCW");
292 if(s==nullptr)
293 s = solveFrm("T_%sRUNTURNL",st);
294 return s;
295 }
296 if(a==WhirlR) {
297 // Whirling in water should not happen, but just to make sure
298 if(bool(wlkMode & WalkBit::WM_Dive))
299 return solveFrm("T_DIVETURNR");
300 if(bool(wlkMode & WalkBit::WM_Swim))
301 return solveFrm("T_SWIMTURNR");
302
303 const Animation::Sequence* s = solveFrm("T_SURPRISE_CW");
304 if(s==nullptr)
305 s = solveFrm("T_%sRUNTURNR",st);
306 return s;
307 }
308 // Jump regular
309 if(a==Jump)
310 return solveFrm("S_JUMP");
311 if(a==JumpUpLow)
312 return solveFrm("S_JUMPUPLOW");
313 if(a==JumpUpMid)
314 return solveFrm("S_JUMPUPMID");
315 if(a==JumpUp)
316 return solveFrm("S_JUMPUP");
317
318 if(a==JumpHang) {
319 if(pose.bodyState()==BS_JUMP) {
320 if(auto ret = solveFrm("T_JUMPUP_2_HANG"))
321 return ret;
322 }
323 //return solveFrm("S_HANG");
324 return solveFrm("T_HANG_2_STAND");
325 }
326
327 if(a==Anim::Fall)
328 return solveFrm("S_FALLDN");
329
330 if(a==Anim::Fallen) {
331 if(pose.isInAnim("S_FALL") || pose.isInAnim("S_FALLEN"))
332 return solveFrm("S_FALLEN");
333 if(pose.isInAnim("S_FALLB") || pose.isInAnim("S_FALLENB"))
334 return solveFrm("S_FALLENB");
335 return solveFrm("S_FALLEN");
336 }
337 if(a==Anim::FallenA)
338 return solveFrm("S_FALLEN");
339 if(a==Anim::FallenB)
340 return solveFrm("S_FALLENB");
341
342 if(a==Anim::FallDeep) {
343 if(pose.bodyState()==BS_FALL || pose.bodyState()==BS_JUMP)
344 return solveFrm("S_FALL");
345 return solveFrm("S_FALLB");
346 }
347 if(a==Anim::FallDeepA)
348 return solveFrm("S_FALL");
349 if(a==Anim::FallDeepB)
350 return solveFrm("S_FALLB");
351
352 if(a==Anim::SlideA)
353 return solveFrm("S_SLIDE");
354 if(a==Anim::SlideB)
355 return solveFrm("S_SLIDEB");
356 if(a==Anim::StumbleA)
357 return solveFrm("T_STUMBLE");
358 if(a==Anim::StumbleB)
359 return solveFrm("T_STUMBLEB");
360 if(a==Anim::DeadA) {
361 if(pose.isInAnim("S_WOUNDED") || pose.isInAnim("T_STAND_2_WOUNDED") ||
362 pose.isInAnim("S_WOUNDEDB") || pose.isInAnim("T_STAND_2_WOUNDEDB"))
363 return solveDead("T_WOUNDED_2_DEAD","T_WOUNDEDB_2_DEADB");
364 if(pose.bodyState()==BS_FALL)
365 return solveDead("T_DEAD", "T_DEADB");
366 if(pose.hasAnim())
367 return solveDead("T_DEAD", "T_DEADB");
368 return solveDead("S_DEAD", "S_DEADB");
369 }
370 if(a==Anim::DeadB) {
371 if(pose.isInAnim("S_WOUNDED") || pose.isInAnim("T_STAND_2_WOUNDED") ||
372 pose.isInAnim("S_WOUNDEDB") || pose.isInAnim("T_STAND_2_WOUNDEDB"))
373 return solveDead("T_WOUNDEDB_2_DEADB","T_WOUNDED_2_DEAD");
374 if(pose.hasAnim())
375 return solveDead("T_DEADB","T_DEAD"); else
376 return solveDead("S_DEADB","S_DEAD");
377 }
378
379 if(a==Anim::UnconsciousA)
380 return solveFrm("T_STAND_2_WOUNDED");
381 if(a==Anim::UnconsciousB)
382 return solveFrm("T_STAND_2_WOUNDEDB");
383
384 if(a==Anim::ItmGet)
385 return solveFrm("S_IGET");
386 if(a==Anim::ItmDrop)
387 return solveFrm("S_IDROP");
388 if(a==Anim::PointAt)
389 return solveFrm("T_POINT");
390
391 return nullptr;
392 }
393
395 // Weapon draw/undraw
396 if(st==cur)
397 return nullptr;
398 switch(st) {
400 if(run)
401 return solveFrm("T_%sMOVE_2_MOVE",cur);
402 return solveFrm("T_%sRUN_2_%s",cur);
405 case WeaponState::W1H:
406 case WeaponState::W2H:
407 case WeaponState::Bow:
409 if(run)
410 return solveFrm("T_MOVE_2_%sMOVE",st);
411 return solveFrm("T_%s_2_%sRUN",st);
412 }
413 return nullptr;
414 }
415
416const Animation::Sequence* AnimationSolver::solveAnim(std::string_view scheme, bool run, bool invest) const {
417 // example: "T_MAGWALK_2_FBTSHOOT"
418
419 string_frm name("");
420 if(run && invest)
421 name = string_frm("T_MAGMOVE_2_",scheme,"CAST");
422 else if(run)
423 name = string_frm("T_MAGMOVE_2_",scheme,"SHOOT");
424 else if(invest)
425 name = string_frm("T_MAGRUN_2_",scheme,"CAST");
426 else
427 name = string_frm("T_MAGRUN_2_",scheme,"SHOOT");
428
429 if(auto sq = solveFrm(name))
430 return sq;
431
432 name = string_frm("S_",scheme,"SHOOT");
433 return solveFrm(name);
434 }
435
437 if(inter==nullptr)
438 return nullptr;
439 switch(a) {
441 return inter->animNpc(*this,Interactive::In);
443 return inter->animNpc(*this,Interactive::Out);
445 return inter->animNpc(*this,Interactive::ToStand);
447 return inter->animNpc(*this,Interactive::FromStand);
448 default:
449 return nullptr;
450 }
451 }
452
453const Animation::Sequence* AnimationSolver::solveFrm(std::string_view fview, WeaponState st) const {
454 char format[256] = {};
455 std::snprintf(format,sizeof(format),"%.*s",int(fview.size()),fview.data());
456
457 static const char* weapon[] = {
458 "",
459 "FIST",
460 "1H",
461 "2H",
462 "BOW",
463 "CBOW",
464 "MAG"
465 };
466 char name[128]={};
467 std::snprintf(name,sizeof(name),format,weapon[int(st)],weapon[int(st)]);
468 if(auto ret=solveFrm(name))
469 return ret;
470 std::snprintf(name,sizeof(name),format,"","");
471 if(auto ret=solveFrm(name))
472 return ret;
473 std::snprintf(name,sizeof(name),format,"FIST","");
474 return solveFrm(name);
475 }
476
477const Animation::Sequence* AnimationSolver::solveMag(std::string_view fview, std::string_view spell) const {
478 char format[256] = {};
479 std::snprintf(format,sizeof(format),"%.*s",int(fview.size()),fview.data());
480
481 char name[128]={};
482 std::snprintf(name,sizeof(name),format,std::string(spell).c_str()); //FIXME
483 return solveFrm(name);
484 }
485
486const Animation::Sequence *AnimationSolver::solveDead(std::string_view format1, std::string_view format2) const {
487 if(auto a=solveFrm(format1))
488 return a;
489 return solveFrm(format2);
490 }
491
492void AnimationSolver::invalidateCache() {
493 std::memset(cache,0,sizeof(cache));
494 }
495
497 if(sq.next.empty())
498 return nullptr;
499 std::string_view name = sq.next;
500 for(size_t i=overlay.size();i>0;){
501 --i;
502 if(overlay[i].skeleton->animation()==sq.owner && sq.nextPtr!=nullptr)
503 return sq.nextPtr; // fast-forward path
504 if(auto s = overlay[i].skeleton->sequence(name))
505 return s;
506 }
507 if(baseSk->animation()==sq.owner)
508 return sq.nextPtr; // fast-forward path
509 return baseSk ? baseSk->sequence(name) : nullptr;
510 }
511
512const Animation::Sequence *AnimationSolver::solveFrm(std::string_view name) const {
513 if(name.empty())
514 return nullptr;
515
516 for(size_t i=overlay.size();i>0;){
517 --i;
518 if(auto s = overlay[i].skeleton->sequence(name))
519 return s;
520 }
521 if(baseSk==nullptr)
522 return nullptr;
523 return baseSk->sequence(name);
524 }
void addOverlay(const Skeleton *sk, uint64_t time)
const Animation::Sequence * solveNext(const Animation::Sequence &sq) const
const Animation::Sequence * solveFrm(std::string_view format) const
void load(Serialize &fin)
const Animation::Sequence * solveAnim(Anim a, WeaponState st, WalkBit wlk, const Pose &pose) const
void setSkeleton(const Skeleton *sk)
void delOverlay(std::string_view sk)
void update(uint64_t tickCount)
void save(Serialize &fout) const
bool hasOverlay(const Skeleton *sk) const
static Gothic & inst()
Definition gothic.cpp:249
auto version() const -> const VersionInfo &
Definition gothic.cpp:263
auto animNpc(const AnimationSolver &solver, Anim t) const -> const Animation::Sequence *
Definition pose.h:16
BodyState bodyState() const
Definition pose.cpp:117
bool hasAnim() const
Definition pose.cpp:705
bool isStanding() const
Definition pose.cpp:654
bool isInAnim(std::string_view sq) const
Definition pose.cpp:691
bool hasState(BodyState s) const
Definition pose.cpp:124
static const Skeleton * loadSkeleton(std::string_view name)
void write(const Arg &... a)
Definition serialize.h:76
void read(Arg &... a)
Definition serialize.h:81
const Animation * animation() const
Definition skeleton.h:36
const Animation::Sequence * sequence(std::string_view name) const
Definition skeleton.cpp:68
std::vector< Node > nodes
Definition skeleton.h:22
WalkBit
Definition constants.h:209
@ BS_AIMNEAR
Definition constants.h:178
@ BS_DIVE
Definition constants.h:161
@ BS_RUN
Definition constants.h:157
@ BS_JUMP
Definition constants.h:162
@ BS_FALL
Definition constants.h:164
@ BS_HIT
Definition constants.h:180
@ BS_AIMFAR
Definition constants.h:179
WeaponState
Definition constants.h:191
const Skeleton * skeleton
std::string next
Definition animation.h:112
const Sequence * nextPtr
Definition animation.h:113
const Animation * owner
Definition animation.h:114