1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * aint32 with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 *
22 * Based on the original sources
23 * Faery Tale II -- The Halls of the Dead
24 * (c) 1993-1996 The Wyrmkeep Entertainment Co.
25 */
26
27 #include "saga2/saga2.h"
28 #include "saga2/dispnode.h"
29 #include "saga2/tile.h"
30 #include "saga2/motion.h"
31 #include "saga2/tilemode.h"
32 #include "saga2/magic.h"
33 #include "saga2/spellbuk.h"
34 #include "saga2/contain.h"
35 #include "saga2/intrface.h"
36
37 namespace Saga2 {
38
39 // Turns on visual debugging aids
40 #define VISUAL1 0
41
42 /* ===================================================================== *
43 Globals
44 * ===================================================================== */
45
46 bool interruptableMotionsPaused;
47
48 /* ===================================================================== *
49 Test Functions
50 * ===================================================================== */
51
52 bool unstickObject(GameObject *obj);
53 int32 currentGamePerformance(void);
54
55 /* ===================================================================== *
56 Functions
57 * ===================================================================== */
58
59 /* Different motion types we want to simulate:
60
61 Transient motions
62 ~~~~~~~~~~~~~~~~~
63 x Walk to Point
64 Walk to Object (moving)
65 x Run
66 Ballistic Motion
67 Jump up (vertical motion only)
68 Leap to X distance
69 Running Leap
70 x Fall off of cliff or height
71 Object Ballistic Motion
72 Climb
73 Climb up ladder
74 Climb up Rope
75 Climb up Ledge
76 Talk & Gesture
77 Give Item (requires cooperation)
78 Stoop to pick up item
79 Stoop to dodge
80 Fight
81 Cast Magic spell
82 Swing High
83 Swing Low
84 Parry
85 Lunge
86 Shoot Bow
87 Consume Food
88 x Cycle through arbitrary frames
89 x Forward
90 x Backward
91 x Random
92 x Ping-Pong
93 x Looped
94 x Single
95 Die
96
97 Persistent Motions
98 ~~~~~~~~~~~~~~~~~~
99 Wait cycle / Twitch (not for FTA, but eventually)
100 Be Dead
101 Sleep
102 Sit
103 */
104
105
106 /* ===================================================================== *
107 Motion Constants
108 * ===================================================================== */
109
110 const StaticTilePoint dirTable[8] = {
111 { 2, 2, 0},
112 { 0, 3, 0},
113 {-2, 2, 0},
114 {-3, 0, 0},
115 {-2, -2, 0},
116 { 0, -3, 0},
117 { 2, -2, 0},
118 { 3, 0, 0}
119 };
120
121 // Incremental direction table
122 const StaticTilePoint incDirTable[8] = {
123 { 1, 1, 0},
124 { 0, 1, 0},
125 {-1, 1, 0},
126 {-1, 0, 0},
127 {-1, -1, 0},
128 { 0, -1, 0},
129 { 1, -1, 0},
130 { 1, 0, 0}
131 };
132
133 extern uint16 uMaxMasks[4],
134 uMinMasks[4],
135 vMaxMasks[4],
136 vMinMasks[4];
137
138 extern SpellStuff *spellBook;
139 void fallingDamage(GameObject *obj, int16 speed);
140
141 /* ===================================================================== *
142 PathMinder
143 * ===================================================================== */
144
getPathFindIQ(GameObject * obj)145 int32 getPathFindIQ(GameObject *obj) {
146 int32 pfIQ = 50;
147 if (isActor(obj)) {
148 Actor *a = (Actor *)obj;
149
150 if (a == getCenterActor())
151 pfIQ = 400;
152 else if (isPlayerActor(a))
153 pfIQ = 300;
154 else {
155 if (objRoofRipped(obj))
156 pfIQ = 75;
157 else if (a->_disposition == 1)
158 pfIQ = 250;
159 else
160 pfIQ = 100;
161 if (g_vm->_rnd->getRandomNumber(9) == 5)
162 pfIQ += 200;
163
164 }
165 int32 p = clamp(50, currentGamePerformance(), 200);
166 pfIQ = (pfIQ * p) / 200;
167 }
168
169
170
171 return pfIQ;
172 }
173
174 /* ===================================================================== *
175 Utility functions
176 * ===================================================================== */
177
178 // This subroutine detects if the actor has landed on an active
179 // tile, and checks to see if the active tile's script should
180 // be triggered.
181
setObjectSurface(GameObject * obj,StandingTileInfo & sti)182 void setObjectSurface(GameObject *obj, StandingTileInfo &sti) {
183 ActiveItemID tagID = sti.surfaceTAG != NULL
184 ? sti.surfaceTAG->thisID()
185 : NoActiveItem;
186
187 if (!(sti.surfaceRef.flags & trTileSensitive))
188 tagID = NoActiveItem;
189
190 if (obj->_data.currentTAG != tagID) {
191 ObjectID objID = obj->thisID(),
192 enactorID = isActor(objID) ? objID : Nothing;
193
194 if (obj->_data.currentTAG != NoActiveItem) {
195 ActiveItem *oldTAG =
196 ActiveItem::activeItemAddress(obj->_data.currentTAG);
197
198 oldTAG->release(enactorID, objID);
199
200 obj->_data.currentTAG = NoActiveItem;
201 }
202
203 if (tagID != NoActiveItem) {
204 if (sti.surfaceTAG->trigger(enactorID, objID))
205 obj->_data.currentTAG = tagID;
206 }
207 }
208 }
209
spinLeft(int16 dir,int16 amt=1)210 inline int16 spinLeft(int16 dir, int16 amt = 1) {
211 return (dir + amt) & 7;
212 }
spinRight(int16 dir,int16 amt=1)213 inline int16 spinRight(int16 dir, int16 amt = 1) {
214 return (dir - amt) & 7;
215 }
216
217 // Special code to avoid actors sticking in walls, which occasionally
218 // happens due to the point-sampled nature of the environment.
219
unstickObject(GameObject * obj)220 bool unstickObject(GameObject *obj) {
221 assert(isObject(obj) || isActor(obj));
222
223 TilePoint pos;
224 int16 mapNum;
225 bool outside;
226
227 mapNum = obj->getMapNum();
228 outside = objRoofID(obj, mapNum, obj->getLocation()) == 0;
229
230 if (checkBlocked(obj, obj->getLocation()) == blockageNone)
231 return false;
232
233 #if 1
234 #if DEBUG
235 WriteStatusF(9, "Unsticking");
236 #endif
237 // A stochastic unsticker, written by Talin
238 // Basically, it tightens the constraints each time a solution
239 // is found.
240 int32 radius = 256;
241 int16 objZ = obj->getLocation().z;
242 TilePoint bestPos;
243
244 for (int tries = 128; tries >= 0; tries--) {
245 int32 dx = g_vm->_rnd->getRandomNumber(radius * 2) - radius,
246 dy = g_vm->_rnd->getRandomNumber(radius * 2) - radius,
247 dz = g_vm->_rnd->getRandomNumber(radius * 2) - radius;
248 int16 tHeight;
249
250 // Compute the actual _data.location of the new point
251 pos = obj->getLocation() + TilePoint(dx, dy, dz);
252
253 // Get the surface height at that point
254 tHeight = tileSlopeHeight(pos, obj);
255
256 // If the surface height is too far away from the sample
257 // height, then ignore it.
258 if (tHeight > pos.z + kMaxStepHeight
259 || tHeight < pos.z - kMaxStepHeight * 4) continue;
260
261 // Recompute the coordinate
262 dz = tHeight - objZ;
263
264 // If under the same roof, and no blockages...
265
266 if (outside == (objRoofID(obj, mapNum, pos) == 0)
267 && checkBlocked(obj, pos) == blockageNone) {
268 int32 newRadius;
269
270 // Then this is the best one found so far.
271
272 // Set new radius to maximum of abs of the 3 coords, minus 1
273 // (Because we want solution to converge faster)
274 newRadius = MAX(MAX(ABS(dx), ABS(dy)), ABS(dz)) - 1;
275 if (newRadius < radius) {
276 radius = newRadius;
277
278 // Each time radius gets reduced, we try a few more times
279 // to find a better solution.
280 tries = radius * 2 + 8;
281 }
282
283 pos.z = tHeight;
284 bestPos = pos;
285 }
286 }
287
288 if (radius < 128) {
289 #if DEBUG
290 WriteStatusF(9, "Unstick Dist: %d", radius);
291 #endif
292 obj->move(bestPos);
293 return true;
294 }
295
296 #else
297 for (dist = 4; dist < 64; dist += 4) {
298 int level;
299
300 for (level = 0; level < dist / 2; level++) {
301 bool up = (level & 1) == 0;
302
303 height = 8 * (up ? level >> 1 : -1 - (level >> 1));
304
305 for (dir = 0; dir < 8; dir++) {
306 pos = obj->getLocation() + (dirTable[dir] * dist);
307 pos.z += height;
308
309 if (outside == (objRoofID(obj, mapNum, pos) == 0)
310 && checkBlocked(obj, pos) == blockageNone) {
311 int16 tHeight;
312
313 tHeight = tileSlopeHeight(pos, obj);
314 if (tHeight <= pos.z + kMaxStepHeight
315 && tHeight >= pos.z - kMaxStepHeight * 4) {
316 pos.z = tHeight;
317 obj->move(pos);
318 return true;
319 }
320 }
321 }
322 }
323 }
324 #endif
325 #if DEBUG
326 WriteStatusF(9, "Unstick Failed!");
327 #endif
328 return true;
329 }
330
331 // Calculates the direction of a missile based upon the velocity vector
missileDir(const TilePoint & vector)332 uint8 missileDir(const TilePoint &vector) {
333 return (((ptToAngle(vector.u, vector.v) + 8) >> 4) - 2) & 0xF;
334 }
335
336 // Computes the frames needed to turn from one direction to another
computeTurnFrames(Direction fromDir,Direction toDir)337 uint8 computeTurnFrames(Direction fromDir, Direction toDir) {
338 Direction relDir = (toDir - fromDir) & 0x7;
339
340 return relDir <= 4 ? relDir : 8 - relDir;
341 }
342
343 /* ===================================================================== *
344 MotionTaskList member functions
345 * ===================================================================== */
346
347 //-----------------------------------------------------------------------
348 // Initialize the MotionTaskList
349
MotionTaskList(void)350 MotionTaskList::MotionTaskList(void) {
351 _nextMT = _list.end();
352 }
353
MotionTaskList(Common::SeekableReadStream * stream)354 MotionTaskList::MotionTaskList(Common::SeekableReadStream *stream) {
355 read(stream);
356 _nextMT = _list.end();
357 }
358
359
read(Common::InSaveFile * in)360 void MotionTaskList::read(Common::InSaveFile *in) {
361 int16 motionTaskCount;
362 // Retrieve the motion task count
363 motionTaskCount = in->readSint16LE();
364
365 for (int i = 0; i < motionTaskCount; i++) {
366 MotionTask *mt;
367
368 mt = new MotionTask;
369 _list.push_back(mt);
370
371 mt->read(in);
372 }
373 }
374
375 //-----------------------------------------------------------------------
376 // Return the number of bytes needed to archive the motion tasks
377 // in a buffer
378
archiveSize(void)379 int32 MotionTaskList::archiveSize(void) {
380 // Initilialize with sizeof motion task count
381 int32 size = sizeof(int16);
382
383 for (Common::List<MotionTask *>::iterator it = _list.begin(); it != _list.end(); ++it)
384 size += (*it)->archiveSize();
385
386 return size;
387 }
388
write(Common::MemoryWriteStreamDynamic * out)389 void MotionTaskList::write(Common::MemoryWriteStreamDynamic *out) {
390 int16 motionTaskCount = _list.size();
391
392 // Store the motion task count
393 out->writeSint16LE(motionTaskCount);
394
395 // Archive the active motion tasks
396 for (Common::List<MotionTask *>::iterator it = _list.begin(); it != _list.end(); ++it)
397 (*it)->write(out);
398 }
399
400 //-----------------------------------------------------------------------
401 // Cleanup the motion tasks
402
cleanup(void)403 void MotionTaskList::cleanup(void) {
404 for (Common::List<MotionTask *>::iterator it = _list.begin(); it != _list.end(); ++it) {
405 abortPathFind(*it);
406 (*it)->pathFindTask = NULL;
407
408 delete *it;
409 }
410
411 _list.clear();
412 }
413
414 //-----------------------------------------------------------------------
415 // Get a new motion task, if there is one available, and initialize it.
416
newTask(GameObject * obj)417 MotionTask *MotionTaskList::newTask(GameObject *obj) {
418 MotionTask *mt = nullptr;
419
420 // Check see if there's already motion associated with this object.
421 for (Common::List<MotionTask *>::iterator it = _list.begin(); it != _list.end(); ++it) {
422
423 if ((*it)->object == obj) {
424 mt = *it;
425 wakeUpThread(mt->thread, motionInterrupted);
426 mt->thread = NoThread;
427
428 break;
429 }
430 }
431
432 if (mt == nullptr) {
433 mt = new MotionTask;
434
435 mt->object = obj;
436 mt->motionType = mt->prevMotionType = MotionTask::motionTypeNone;
437 mt->pathFindTask = nullptr;
438 mt->pathCount = -1;
439 mt->flags = 0;
440 mt->velocity = TilePoint(0, 0, 0);
441 mt->immediateLocation = mt->finalTarget = obj->getLocation();
442 mt->thread = NoThread;
443
444 mt->targetObj = nullptr;
445 mt->targetTAG = nullptr;
446 mt->spellObj = nullptr;
447
448 _list.push_back(mt);
449
450 if (isActor(obj))
451 ((Actor *)obj)->_moveTask = mt;
452 }
453
454 obj->_data.objectFlags |= objectMoving;
455
456 return mt;
457 }
458
459 /* ===================================================================== *
460 MotionTask member functions
461 * ===================================================================== */
462
read(Common::InSaveFile * in)463 void MotionTask::read(Common::InSaveFile *in) {
464 ObjectID objectID;
465
466 // Restore the motion type and previous motion type
467 motionType = in->readByte();
468 prevMotionType = in->readByte();
469
470 // Restore the thread ID
471 thread = in->readSint16LE();
472
473 // Restore the motion flags
474 flags = in->readUint16LE();
475
476 // Get the object ID
477 objectID = in->readUint16LE();
478
479 // Convert the object ID to and object address
480 object = objectID != Nothing
481 ? GameObject::objectAddress(objectID)
482 : NULL;
483
484 // If the object is an actor, plug this motion task into the actor
485 if (object && isActor(object))
486 ((Actor *)object)->_moveTask = this;
487
488 if (motionType == motionTypeWalk
489 || prevMotionType == motionTypeWalk) {
490 // Restore the target _data.locations
491 immediateLocation.load(in);
492 finalTarget.load(in);
493
494 // If there is a tether restore it
495 if (flags & tethered) {
496 tetherMinU = in->readSint16LE();
497 tetherMinV = in->readSint16LE();
498 tetherMaxU = in->readSint16LE();
499 tetherMaxV = in->readSint16LE();
500 }
501
502 // Restore the direction
503 direction = in->readByte();
504
505 // Restore the path index and path count
506 pathIndex = in->readSint16LE();
507 pathCount = in->readSint16LE();
508 runCount = in->readSint16LE();
509
510 // Restore the action counter if needed
511 if (flags & agitated)
512 actionCounter = in->readSint16LE();
513
514 // If there were valid path way points, restore those
515 if (pathIndex >= 0 && pathIndex < pathCount) {
516 int16 wayPointIndex = pathIndex;
517
518 while (wayPointIndex < pathCount) {
519 pathList[wayPointIndex].load(in);
520
521 wayPointIndex++;
522 }
523 }
524
525 // If this motion task previously had a path finding request
526 // it must be restarted
527 pathFindTask = NULL;
528 }
529
530 if (motionType == motionTypeThrown || motionType == motionTypeShot) {
531 // Restore the velocity
532 velocity.load(in);
533
534 // Restore other ballistic motion variables
535 steps = in->readSint16LE();
536 uFrac = in->readSint16LE();
537 vFrac = in->readSint16LE();
538 uErrorTerm = in->readSint16LE();
539 vErrorTerm = in->readSint16LE();
540
541 if (motionType == motionTypeShot) {
542 ObjectID targetObjID,
543 enactorID;
544
545 targetObjID = in->readUint16LE();
546
547 targetObj = targetObjID
548 ? GameObject::objectAddress(targetObjID)
549 : NULL;
550
551 enactorID = in->readUint16LE();
552
553 o.enactor = enactorID != Nothing
554 ? (Actor *)GameObject::objectAddress(enactorID)
555 : NULL;
556 }
557 } else if (motionType == motionTypeClimbUp
558 || motionType == motionTypeClimbDown) {
559 immediateLocation.load(in);
560 } else if (motionType == motionTypeJump) {
561 velocity.load(in);
562 } else if (motionType == motionTypeTurn) {
563 direction = in->readByte();
564 } else if (motionType == motionTypeGive) {
565 ObjectID id = in->readUint16LE();
566 targetObj = id != Nothing
567 ? GameObject::objectAddress(id)
568 : NULL;
569 } else if (motionType == motionTypeWait) {
570 actionCounter = in->readSint16LE();
571 } else if (motionType == motionTypeUseObject
572 || motionType == motionTypeUseObjectOnObject
573 || motionType == motionTypeUseObjectOnTAI
574 || motionType == motionTypeUseObjectOnLocation
575 || motionType == motionTypeDropObject
576 || motionType == motionTypeDropObjectOnObject
577 || motionType == motionTypeDropObjectOnTAI) {
578 ObjectID directObjID = in->readUint16LE();
579 o.directObject = directObjID != Nothing
580 ? GameObject::objectAddress(directObjID)
581 : NULL;
582
583 direction = in->readByte();
584
585 if (motionType == motionTypeUseObjectOnObject
586 || motionType == motionTypeDropObjectOnObject) {
587 ObjectID indirectObjID = in->readUint16LE();
588 o.indirectObject = indirectObjID != Nothing
589 ? GameObject::objectAddress(indirectObjID)
590 : NULL;
591 } else {
592 if (motionType == motionTypeUseObjectOnTAI
593 || motionType == motionTypeDropObjectOnTAI) {
594 ActiveItemID tai(in->readSint16LE());
595 o.TAI = tai != NoActiveItem
596 ? ActiveItem::activeItemAddress(tai)
597 : NULL;
598 }
599
600 if (motionType == motionTypeUseObjectOnLocation
601 || motionType == motionTypeDropObject
602 || motionType == motionTypeDropObjectOnTAI) {
603 targetLoc.load(in);
604 }
605 }
606 } else if (motionType == motionTypeUseTAI) {
607 ActiveItemID tai(in->readSint16LE());
608 o.TAI = tai != NoActiveItem
609 ? ActiveItem::activeItemAddress(tai)
610 : NULL;
611
612 direction = in->readByte();
613 } else if (motionType == motionTypeTwoHandedSwing
614 || motionType == motionTypeOneHandedSwing
615 || motionType == motionTypeFireBow
616 || motionType == motionTypeCastSpell
617 || motionType == motionTypeUseWand) {
618 ObjectID targetObjID;
619
620 // Restore the direction
621 direction = in->readByte();
622
623 // Restore the combat motion type
624 combatMotionType = in->readByte();
625
626 // Get the target object ID
627 targetObjID = in->readUint16LE();
628
629 // Convert the target object ID to a pointer
630 targetObj = targetObjID != Nothing
631 ? GameObject::objectAddress(targetObjID)
632 : NULL;
633
634 if (motionType == motionTypeCastSpell) {
635 SpellID sid ;
636 ObjectID toid ;
637 ActiveItemID ttaid;
638
639 // restore the spell prototype
640 warning("MotionTask::read: Check SpellID size");
641 sid = (SpellID)in->readUint32LE();
642 spellObj = sid != nullSpell
643 ? skillProtoFromID(sid)
644 : NULL;
645
646 // restore object target
647 toid = in->readUint16LE();
648 targetObj = toid != Nothing
649 ? GameObject::objectAddress(toid)
650 : NULL;
651
652 // restore TAG target
653 ttaid = in->readSint16LE();
654 targetTAG = ttaid != NoActiveItem
655 ? ActiveItem::activeItemAddress(ttaid)
656 : NULL;
657
658 // restore _data.location target
659 targetLoc.load(in);
660 }
661
662 // Restore the action counter
663 actionCounter = in->readSint16LE();
664 } else if (motionType == motionTypeTwoHandedParry
665 || motionType == motionTypeOneHandedParry
666 || motionType == motionTypeShieldParry) {
667 ObjectID attackerID,
668 defensiveObjID;
669
670 // Restore the direction
671 direction = in->readByte();
672
673 // Get the attacker's and defensive object's IDs
674 attackerID = in->readByte();
675 defensiveObjID = in->readByte();
676
677 // Convert IDs to pointers
678 d.attacker = attackerID != Nothing
679 ? (Actor *)GameObject::objectAddress(attackerID)
680 : NULL;
681
682 d.defensiveObj = defensiveObjID != Nothing
683 ? GameObject::objectAddress(defensiveObjID)
684 : NULL;
685
686 // Restore the defense flags
687 d.defenseFlags = in->readByte();
688
689 // Restore the action counter
690 actionCounter = in->readSint16LE();
691
692 if (motionType == motionTypeOneHandedParry) {
693 // Restore the combat sub-motion type
694 combatMotionType = in->readByte();
695 }
696 } else if (motionType == motionTypeDodge
697 || motionType == motionTypeAcceptHit
698 || motionType == motionTypeFallDown) {
699 ObjectID attackerID;
700
701 // Get the attacker's ID
702 attackerID = in->readUint16LE();
703
704 // Convert ID to pointer
705 d.attacker = attackerID != Nothing
706 ? (Actor *)GameObject::objectAddress(attackerID)
707 : NULL;
708
709 // Restore the action counter
710 actionCounter = in->readSint16LE();
711 }
712 }
713
714 //-----------------------------------------------------------------------
715 // Return the number of bytes needed to archive this MotionTask
716
archiveSize(void)717 int32 MotionTask::archiveSize(void) {
718 int32 size = 0;
719
720 size = sizeof(motionType)
721 + sizeof(prevMotionType)
722 + sizeof(thread)
723 + sizeof(flags)
724 + sizeof(ObjectID); // object
725
726 if (motionType == motionTypeWalk
727 || prevMotionType == motionTypeWalk) {
728 size += sizeof(immediateLocation)
729 + sizeof(finalTarget);
730
731 if (flags & tethered) {
732 size += sizeof(tetherMinU)
733 + sizeof(tetherMinV)
734 + sizeof(tetherMaxU)
735 + sizeof(tetherMaxV);
736 }
737
738 size += sizeof(direction)
739 + sizeof(pathIndex)
740 + sizeof(pathCount)
741 + sizeof(runCount);
742
743 if (flags & agitated)
744 size += sizeof(actionCounter);
745
746 if (pathIndex >= 0 && pathIndex < pathCount)
747 size += sizeof(TilePoint) * (pathCount - pathIndex);
748 }
749
750 if (motionType == motionTypeThrown || motionType == motionTypeShot) {
751 size += sizeof(velocity)
752 + sizeof(steps)
753 + sizeof(uFrac)
754 + sizeof(vFrac)
755 + sizeof(uErrorTerm)
756 + sizeof(vErrorTerm);
757
758 if (motionType == motionTypeShot) {
759 size += sizeof(ObjectID) // targetObj ID
760 + sizeof(ObjectID); // enactor ID
761 }
762 } else if (motionType == motionTypeClimbUp
763 || motionType == motionTypeClimbDown) {
764 size += sizeof(immediateLocation);
765 } else if (motionType == motionTypeJump) {
766 size += sizeof(velocity);
767 } else if (motionType == motionTypeTurn) {
768 size += sizeof(direction);
769 } else if (motionType == motionTypeGive) {
770 size += sizeof(ObjectID); // targetObj ID
771 } else if (motionType == motionTypeUseObject
772 || motionType == motionTypeUseObjectOnObject
773 || motionType == motionTypeUseObjectOnTAI
774 || motionType == motionTypeUseObjectOnLocation
775 || motionType == motionTypeDropObject
776 || motionType == motionTypeDropObjectOnObject
777 || motionType == motionTypeDropObjectOnTAI) {
778 size += sizeof(ObjectID)
779 + sizeof(direction);
780
781 if (motionType == motionTypeUseObjectOnObject
782 || motionType == motionTypeDropObjectOnObject) {
783 size += sizeof(ObjectID);
784 } else {
785 if (motionType == motionTypeUseObjectOnTAI
786 || motionType == motionTypeDropObjectOnTAI) {
787 size += sizeof(ActiveItemID);
788 }
789
790 if (motionType == motionTypeUseObjectOnLocation
791 || motionType == motionTypeDropObject
792 || motionType == motionTypeDropObjectOnTAI) {
793 size += sizeof(targetLoc);
794 }
795 }
796 } else if (motionType == motionTypeUseTAI) {
797 size += sizeof(ActiveItemID)
798 + sizeof(direction);
799 } else if (motionType == motionTypeTwoHandedSwing
800 || motionType == motionTypeOneHandedSwing
801 || motionType == motionTypeFireBow
802 || motionType == motionTypeCastSpell
803 || motionType == motionTypeUseWand) {
804 size += sizeof(direction)
805 + sizeof(combatMotionType)
806 + sizeof(ObjectID); // targetObj
807
808 if (motionType == motionTypeCastSpell) {
809 size += sizeof(SpellID); // spellObj
810 size += sizeof(ObjectID); // targetObj
811 size += sizeof(ActiveItemID); // targetTAG
812 size += sizeof(targetLoc); // targetLoc
813 }
814
815 size += sizeof(actionCounter);
816
817 } else if (motionType == motionTypeTwoHandedParry
818 || motionType == motionTypeOneHandedParry
819 || motionType == motionTypeShieldParry) {
820 size += sizeof(direction)
821 + sizeof(ObjectID) // attacker ID
822 + sizeof(ObjectID) // defensiveObj ID
823 + sizeof(d.defenseFlags)
824 + sizeof(actionCounter);
825
826 if (motionType == motionTypeOneHandedParry)
827 size += sizeof(combatMotionType);
828 } else if (motionType == motionTypeDodge
829 || motionType == motionTypeAcceptHit
830 || motionType == motionTypeFallDown) {
831 size += sizeof(ObjectID) // attacker ID
832 + sizeof(actionCounter);
833 }
834
835 return size;
836 }
837
write(Common::MemoryWriteStreamDynamic * out)838 void MotionTask::write(Common::MemoryWriteStreamDynamic *out) {
839 ObjectID objectID;
840
841 // Store the motion type and previous motion type
842 out->writeByte(motionType);
843 out->writeByte(prevMotionType);
844
845 // Store the thread ID
846 out->writeSint16LE(thread);
847
848 // Store the motion flags
849 out->writeUint16LE(flags);
850
851 // Convert the object pointer to an object ID
852 objectID = object != NULL ? object->thisID() : Nothing;
853
854 // Store the object ID
855 out->writeUint16LE(objectID);
856
857 if (motionType == motionTypeWalk
858 || prevMotionType == motionTypeWalk) {
859 // Store the target _data.locations
860 immediateLocation.write(out);
861 finalTarget.write(out);
862
863 // If there is a tether store it
864 if (flags & tethered) {
865 out->writeSint16LE(tetherMinU);
866 out->writeSint16LE(tetherMinV);
867 out->writeSint16LE(tetherMaxU);
868 out->writeSint16LE(tetherMaxV);
869 }
870
871 // Store the direction
872 out->writeByte(direction);
873
874 // Store the path index and path count
875 out->writeSint16LE(pathIndex);
876 out->writeSint16LE(pathCount);
877 out->writeSint16LE(runCount);
878
879 // Store the action counter if needed
880 if (flags & agitated)
881 out->writeSint16LE(actionCounter);
882
883 // If there are valid path way points, store them
884 if (pathIndex >= 0 && pathIndex < pathCount) {
885 int16 wayPointIndex = pathIndex;
886
887 while (wayPointIndex < pathCount) {
888 pathList[wayPointIndex].write(out);
889
890 wayPointIndex++;
891 }
892 }
893 }
894
895 if (motionType == motionTypeThrown || motionType == motionTypeShot) {
896 // Store the velocity
897 velocity.write(out);
898
899 // Store other ballistic motion variables
900 out->writeSint16LE(steps);
901 out->writeSint16LE(uFrac);
902 out->writeSint16LE(vFrac);
903 out->writeSint16LE(uErrorTerm);
904 out->writeSint16LE(vErrorTerm);
905
906 if (motionType == motionTypeShot) {
907 ObjectID targetObjID,
908 enactorID;
909
910 targetObjID = targetObj != NULL
911 ? targetObj->thisID()
912 : Nothing;
913
914 out->writeUint16LE(targetObjID);
915
916 enactorID = o.enactor != NULL
917 ? o.enactor->thisID()
918 : Nothing;
919
920 out->writeUint16LE(enactorID);
921 }
922 } else if (motionType == motionTypeClimbUp
923 || motionType == motionTypeClimbDown) {
924 immediateLocation.write(out);
925 } else if (motionType == motionTypeJump) {
926 velocity.write(out);
927 } else if (motionType == motionTypeTurn) {
928 out->writeByte(direction);
929 } else if (motionType == motionTypeGive) {
930 if (targetObj != NULL)
931 out->writeUint16LE(targetObj->thisID());
932 else
933 out->writeUint16LE(Nothing);
934 } else if (motionType == motionTypeUseObject
935 || motionType == motionTypeUseObjectOnObject
936 || motionType == motionTypeUseObjectOnTAI
937 || motionType == motionTypeUseObjectOnLocation
938 || motionType == motionTypeDropObject
939 || motionType == motionTypeDropObjectOnObject
940 || motionType == motionTypeDropObjectOnTAI) {
941 if (o.directObject != NULL)
942 out->writeUint16LE(o.directObject->thisID());
943 else
944 out->writeUint16LE(Nothing);
945
946 out->writeByte(direction);
947
948 if (motionType == motionTypeUseObjectOnObject
949 || motionType == motionTypeDropObjectOnObject) {
950 if (o.indirectObject != NULL)
951 out->writeUint16LE(o.indirectObject->thisID());
952 else
953 out->writeUint16LE(Nothing);
954 } else {
955 if (motionType == motionTypeUseObjectOnTAI
956 || motionType == motionTypeDropObjectOnTAI) {
957 if (o.TAI != NULL)
958 out->writeSint16LE(o.TAI->thisID());
959 else
960 out->writeSint16LE(NoActiveItem.val);
961 }
962
963 if (motionType == motionTypeUseObjectOnLocation
964 || motionType == motionTypeDropObject
965 || motionType == motionTypeDropObjectOnTAI) {
966 targetLoc.write(out);
967 }
968 }
969 } else if (motionType == motionTypeUseTAI) {
970 if (o.TAI != NULL)
971 out->writeSint16LE(o.TAI->thisID());
972 else
973 out->writeSint16LE(NoActiveItem.val);
974
975 out->writeByte(direction);
976 } else if (motionType == motionTypeTwoHandedSwing
977 || motionType == motionTypeOneHandedSwing
978 || motionType == motionTypeFireBow
979 || motionType == motionTypeCastSpell
980 || motionType == motionTypeUseWand) {
981 ObjectID targetObjID;
982
983 // Store the direction
984 out->writeByte(direction);
985
986 // Store the combat motion type
987 out->writeByte(combatMotionType);
988
989 // Convert the target object pointer to an ID
990 targetObjID = targetObj != NULL ? targetObj->thisID() : Nothing;
991
992 // Store the target object ID
993 out->writeUint16LE(targetObjID);
994
995 if (motionType == motionTypeCastSpell) {
996 // Convert the spell object pointer to an ID
997
998 SpellID sid = spellObj != NULL
999 ? spellObj->getSpellID()
1000 : nullSpell;
1001
1002 ObjectID toid = targetObj != NULL
1003 ? targetObj->thisID()
1004 : Nothing;
1005
1006 ActiveItemID ttaid = targetTAG != NULL
1007 ? targetTAG->thisID()
1008 : NoActiveItem;
1009
1010 // Store the spell prototype
1011 warning("MotionTask::write: Check SpellID size");
1012 out->writeUint32LE(sid);
1013
1014 // Store object target
1015 out->writeUint16LE(toid);
1016
1017 // Store TAG target
1018 out->writeSint16LE(ttaid.val);
1019
1020 // Store _data.location target
1021 targetLoc.write(out);
1022 }
1023
1024 // Store the action counter
1025 out->writeSint16LE(actionCounter);
1026
1027 } else if (motionType == motionTypeTwoHandedParry
1028 || motionType == motionTypeOneHandedParry
1029 || motionType == motionTypeShieldParry) {
1030 ObjectID attackerID,
1031 defensiveObjID;
1032
1033 // Store the direction
1034 out->writeByte(direction);
1035
1036 attackerID = d.attacker != NULL ? d.attacker->thisID() : Nothing;
1037 defensiveObjID = d.defensiveObj != NULL ? d.defensiveObj->thisID() : Nothing;
1038
1039 // Store the attacker's and defensive object's IDs
1040 out->writeUint16LE(attackerID);
1041 out->writeUint16LE(defensiveObjID);
1042
1043 // Store the defense flags
1044 out->writeByte(d.defenseFlags);
1045
1046 // Store the action counter
1047 out->writeSint16LE(actionCounter);
1048
1049 if (motionType == motionTypeOneHandedParry) {
1050 // Store the combat sub-motion type
1051 out->writeByte(combatMotionType);
1052 }
1053 } else if (motionType == motionTypeDodge
1054 || motionType == motionTypeAcceptHit
1055 || motionType == motionTypeFallDown) {
1056 ObjectID attackerID;
1057
1058 attackerID = d.attacker != NULL ? d.attacker->thisID() : Nothing;
1059
1060 // Store the attacker's ID
1061 out->writeUint16LE(attackerID);
1062
1063 // Store the action counter
1064 out->writeSint16LE(actionCounter);
1065 }
1066 }
1067
1068 //-----------------------------------------------------------------------
1069 // When a motion task is finished, call this function to delete it.
1070
remove(int16 returnVal)1071 void MotionTask::remove(int16 returnVal) {
1072 if (g_vm->_mTaskList->_nextMT != g_vm->_mTaskList->_list.end() && *(g_vm->_mTaskList->_nextMT) == this)
1073 ++g_vm->_mTaskList->_nextMT;
1074
1075 object->_data.objectFlags &= ~objectMoving;
1076 if (objObscured(object))
1077 object->_data.objectFlags |= objectObscured;
1078 else
1079 object->_data.objectFlags &= ~objectObscured;
1080
1081 if (isActor(object)) {
1082 Actor *a = (Actor *)object;
1083
1084 a->_moveTask = NULL;
1085 a->_cycleCount = g_vm->_rnd->getRandomNumber(19);
1086
1087 // Make sure the actor is not left in a permanently
1088 // uninterruptable state with no motion task to reset it
1089 if (a->isPermanentlyUninterruptable())
1090 a->setInterruptablity(true);
1091 }
1092
1093 g_vm->_mTaskList->_list.remove(this);
1094
1095 abortPathFind(this);
1096 pathFindTask = NULL;
1097
1098 wakeUpThread(thread, returnVal);
1099 }
1100
1101 //-----------------------------------------------------------------------
1102 // Determine the immediate target _data.location
1103
getImmediateTarget(void)1104 TilePoint MotionTask::getImmediateTarget(void) {
1105 if (immediateLocation != Nowhere)
1106 return immediateLocation;
1107
1108 Direction dir;
1109
1110 // If the wandering then simply go in the direction the actor is
1111 // facing, else if avoiding a block go in the previously selected
1112 // random direction
1113 if (flags & agitated)
1114 dir = direction;
1115 else
1116 dir = ((Actor *)object)->_currentFacing;
1117
1118 return object->_data.location
1119 + incDirTable[dir] * kTileUVSize;
1120 }
1121
1122 //-----------------------------------------------------------------------
1123 // This calculates the velocity for a ballistic motion
1124
calcVelocity(const TilePoint & vector,int16 turns)1125 void MotionTask::calcVelocity(const TilePoint &vector, int16 turns) {
1126 TilePoint veloc;
1127
1128 // Here is the formula for calculating the velocity Z vector
1129
1130 // Vz = - 1/2gt + 1/t(Dz - Sz)
1131
1132 // Vz = Velocity Z Coords
1133 // g = gravity
1134 // t = turns
1135 // Dz = Destination Z Coords
1136 // Sz = Source Z Coords
1137
1138 veloc.u = vector.u / turns;
1139 veloc.v = vector.v / turns;
1140
1141 // This is used in ballistic motion to make up for rounding
1142
1143 steps = turns;
1144 uFrac = vector.u % turns;
1145 vFrac = vector.v % turns;
1146 uErrorTerm = 0;
1147 vErrorTerm = 0;
1148
1149 veloc.z = ((gravity * turns) >> 1) + vector.z / turns;
1150 velocity = veloc;
1151 }
1152
1153 //-----------------------------------------------------------------------
1154 // This initiates a motion task for turning an actor
1155
turn(Actor & obj,Direction dir)1156 void MotionTask::turn(Actor &obj, Direction dir) {
1157 assert(dir < 8);
1158
1159 MotionTask *mt;
1160
1161 if ((mt = g_vm->_mTaskList->newTask(&obj)) != NULL) {
1162 mt->direction = dir;
1163 mt->motionType = motionTypeTurn;
1164 mt->flags = reset;
1165 }
1166 }
1167
1168 //-----------------------------------------------------------------------
1169 // This initiates a motion task for turning an actor
1170
turnTowards(Actor & obj,const TilePoint & where)1171 void MotionTask::turnTowards(Actor &obj, const TilePoint &where) {
1172 MotionTask *mt;
1173
1174 if ((mt = g_vm->_mTaskList->newTask(&obj)) != NULL) {
1175 mt->direction = (where - obj.getLocation()).quickDir();
1176 mt->motionType = motionTypeTurn;
1177 mt->flags = reset;
1178 }
1179 }
1180
1181 //-----------------------------------------------------------------------
1182 // This initiates a motion task for going through the motions of giving
1183 // an object to another actor
1184
give(Actor & actor,Actor & givee)1185 void MotionTask::give(Actor &actor, Actor &givee) {
1186 MotionTask *mt;
1187
1188 if ((mt = g_vm->_mTaskList->newTask(&actor)) != NULL) {
1189 mt->targetObj = &givee;
1190 mt->motionType = motionTypeGive;
1191 mt->flags = reset;
1192 }
1193 }
1194
1195 //-----------------------------------------------------------------------
1196 // This initiates a motion task for throwing an object
1197
throwObject(GameObject & obj,const TilePoint & velocity)1198 void MotionTask::throwObject(GameObject &obj, const TilePoint &velocity) {
1199 MotionTask *mt;
1200
1201 if ((mt = g_vm->_mTaskList->newTask(&obj)) != NULL) {
1202 if (obj.isMissile()) obj._data.missileFacing = missileNoFacing;
1203 mt->velocity = velocity;
1204 mt->motionType = motionTypeThrown;
1205 }
1206 }
1207
1208 //-----------------------------------------------------------------------
1209 // This function is intended to allow the character to throw an object
1210 // to a specific point. It is in no way functional yet.
1211
1212 // REM: we need to know if we are indoors or outdoors!
1213 // REM: we need to know celing height!!!
1214
throwObjectTo(GameObject & obj,const TilePoint & where)1215 void MotionTask::throwObjectTo(GameObject &obj, const TilePoint &where) {
1216 MotionTask *mt;
1217 const int16 turns = 15;
1218
1219 if ((mt = g_vm->_mTaskList->newTask(&obj)) != NULL) {
1220 if (obj.isMissile()) obj._data.missileFacing = missileNoFacing;
1221 mt->calcVelocity(where - obj.getLocation(), turns);
1222 mt->motionType = motionTypeThrown;
1223 }
1224 }
1225
1226 //-----------------------------------------------------------------------
1227 // This function initiates a ballistic motion towards a specified target
1228 // _data.location at a specified horizontal speed.
1229
shootObject(GameObject & obj,Actor & doer,GameObject & target,int16 speed)1230 void MotionTask::shootObject(
1231 GameObject &obj,
1232 Actor &doer,
1233 GameObject &target,
1234 int16 speed) {
1235 MotionTask *mt;
1236
1237 if ((mt = g_vm->_mTaskList->newTask(&obj)) != NULL) {
1238 TilePoint targetLoc = target.getLocation();
1239
1240 targetLoc.z += target.proto()->height / 2;
1241
1242 TilePoint vector = targetLoc - obj.getLocation();
1243 int16 turns = MAX(vector.quickHDistance() / speed, 1);
1244
1245 if (isActor(&target)) {
1246 Actor *targetActor = (Actor *)⌖
1247
1248 if (targetActor->_moveTask != NULL) {
1249 MotionTask *targetMotion = targetActor->_moveTask;
1250
1251 if (targetMotion->motionType == motionTypeWalk)
1252 vector += targetMotion->velocity * turns;
1253 }
1254 }
1255
1256 mt->calcVelocity(vector, turns);
1257
1258 if (obj.isMissile())
1259 obj._data.missileFacing = missileDir(mt->velocity);
1260
1261 mt->motionType = motionTypeShot;
1262 mt->o.enactor = &doer;
1263 mt->targetObj = ⌖
1264 }
1265 }
1266
1267 //-----------------------------------------------------------------------
1268 // Walk to a specific point, using pathfinding.
1269
walkTo(Actor & actor,const TilePoint & target,bool run,bool canAgitate)1270 void MotionTask::walkTo(
1271 Actor &actor,
1272 const TilePoint &target,
1273 bool run,
1274 bool canAgitate) {
1275 MotionTask *mt;
1276
1277 if ((mt = g_vm->_mTaskList->newTask(&actor)) != NULL) {
1278 if (!mt->isReflex() && !actor.isImmobile()) {
1279 unstickObject(&actor);
1280 mt->finalTarget = mt->immediateLocation = target;
1281 mt->motionType = mt->prevMotionType = motionTypeWalk;
1282 mt->pathCount = mt->pathIndex = 0;
1283 mt->flags = pathFind | reset;
1284 mt->runCount = 12; // # of frames until we can run
1285
1286 if (run && actor.isActionAvailable(actionRun))
1287 mt->flags |= requestRun;
1288 if (canAgitate)
1289 mt->flags |= agitatable;
1290
1291 RequestPath(mt, getPathFindIQ(&actor));
1292 }
1293 }
1294 }
1295
1296 //-----------------------------------------------------------------------
1297 // Walk to a specific point without pathfinding
1298
walkToDirect(Actor & actor,const TilePoint & target,bool run,bool canAgitate)1299 void MotionTask::walkToDirect(
1300 Actor &actor,
1301 const TilePoint &target,
1302 bool run,
1303 bool canAgitate) {
1304 MotionTask *mt;
1305
1306 if ((mt = g_vm->_mTaskList->newTask(&actor)) != NULL) {
1307 if (!mt->isReflex() && !actor.isImmobile()) {
1308 // Abort any pending path finding task
1309 abortPathFind(mt);
1310 mt->pathFindTask = NULL;
1311
1312 unstickObject(&actor);
1313 mt->motionType = mt->prevMotionType = motionTypeWalk;
1314 mt->finalTarget = mt->immediateLocation = target;
1315 mt->pathCount = mt->pathIndex = 0;
1316 mt->flags = reset;
1317 mt->runCount = 12;
1318
1319 if (run && actor.isActionAvailable(actionRun))
1320 mt->flags |= requestRun;
1321 if (canAgitate)
1322 mt->flags |= agitatable;
1323 }
1324 }
1325 }
1326
1327 //-----------------------------------------------------------------------
1328 // Wander around
1329
wander(Actor & actor,bool run)1330 void MotionTask::wander(
1331 Actor &actor,
1332 bool run) {
1333 MotionTask *mt;
1334
1335 if ((mt = g_vm->_mTaskList->newTask(&actor)) != NULL) {
1336 if (!mt->isReflex() && !actor.isImmobile()) {
1337 // Abort any pending path finding task
1338 abortPathFind(mt);
1339 mt->pathFindTask = NULL;
1340
1341 unstickObject(&actor);
1342 mt->motionType = mt->prevMotionType = motionTypeWalk;
1343 mt->immediateLocation = Nowhere;
1344 mt->pathCount = mt->pathIndex = 0;
1345 mt->flags = reset | wandering;
1346 mt->runCount = 12;
1347
1348 if (run && actor.isActionAvailable(actionRun))
1349 mt->flags |= requestRun;
1350
1351 RequestWanderPath(mt, getPathFindIQ(&actor));
1352 }
1353 }
1354 }
1355
1356 //-----------------------------------------------------------------------
1357 // Wander around within a tether region
1358
tetheredWander(Actor & actor,const TileRegion & tetherReg,bool run)1359 void MotionTask::tetheredWander(
1360 Actor &actor,
1361 const TileRegion &tetherReg,
1362 bool run) {
1363 MotionTask *mt;
1364
1365 if ((mt = g_vm->_mTaskList->newTask(&actor)) != NULL) {
1366 if (!mt->isReflex() && !actor.isImmobile()) {
1367 // Abort any pending path finding task
1368 abortPathFind(mt);
1369 mt->pathFindTask = NULL;
1370
1371 unstickObject(&actor);
1372 mt->motionType = mt->prevMotionType = motionTypeWalk;
1373 mt->immediateLocation = Nowhere;
1374 mt->tetherMinU = tetherReg.min.u;
1375 mt->tetherMinV = tetherReg.min.v;
1376 mt->tetherMaxU = tetherReg.max.u;
1377 mt->tetherMaxV = tetherReg.max.v;
1378 mt->pathCount = mt->pathIndex = 0;
1379 mt->flags = reset | wandering | tethered;
1380 mt->runCount = 12;
1381
1382 if (run && actor.isActionAvailable(actionRun))
1383 mt->flags |= requestRun;
1384
1385 RequestWanderPath(mt, getPathFindIQ(&actor));
1386 }
1387 }
1388 }
1389
1390 //-----------------------------------------------------------------------
1391 // Create a climb up ladder motion task.
1392
upLadder(Actor & actor)1393 void MotionTask::upLadder(Actor &actor) {
1394 MotionTask *mt;
1395
1396 if ((mt = g_vm->_mTaskList->newTask(&actor)) != NULL) {
1397 if (mt->motionType != motionTypeClimbUp) {
1398 mt->motionType = motionTypeClimbUp;
1399 mt->flags = reset;
1400 }
1401 }
1402 }
1403
1404 //-----------------------------------------------------------------------
1405 // Create a climb down ladder motion task.
1406
downLadder(Actor & actor)1407 void MotionTask::downLadder(Actor &actor) {
1408 MotionTask *mt;
1409
1410 if ((mt = g_vm->_mTaskList->newTask(&actor)) != NULL) {
1411 if (mt->motionType != motionTypeClimbDown) {
1412 mt->motionType = motionTypeClimbDown;
1413 mt->flags = reset;
1414 }
1415 }
1416 }
1417
1418 //-----------------------------------------------------------------------
1419 // Create a talk motion task.
1420
talk(Actor & actor)1421 void MotionTask::talk(Actor &actor) {
1422 MotionTask *mt;
1423
1424 if ((mt = g_vm->_mTaskList->newTask(&actor)) != NULL) {
1425 if (mt->motionType != motionTypeTalk) {
1426 mt->motionType = motionTypeTalk;
1427 mt->flags = reset;
1428 }
1429 }
1430 }
1431
1432 //-----------------------------------------------------------------------
1433 // Begin a jump. REM: This should probably have a parameter for jumping
1434 // forward, backward, etc.
1435
jump(Actor & actor)1436 void MotionTask::jump(Actor &actor) {
1437 MotionTask *mt;
1438
1439 if ((mt = g_vm->_mTaskList->newTask(&actor)) != NULL) {
1440 if (mt->motionType != motionTypeThrown) {
1441 mt->velocity.z = 10;
1442 mt->motionType = motionTypeJump;
1443 mt->flags = reset;
1444 }
1445 }
1446 }
1447
1448
1449 //-----------------------------------------------------------------------
1450 // Don't move -- simply eat some time
1451
wait(Actor & a)1452 void MotionTask::wait(Actor &a) {
1453 MotionTask *mt;
1454
1455 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1456 if (mt->motionType != motionTypeWait) {
1457 mt->motionType = motionTypeWait;
1458 mt->flags = reset;
1459 }
1460 }
1461 }
1462
1463 //-----------------------------------------------------------------------
1464 // Use an object
1465
useObject(Actor & a,GameObject & dObj)1466 void MotionTask::useObject(Actor &a, GameObject &dObj) {
1467 MotionTask *mt;
1468
1469 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1470 if (mt->motionType != motionTypeUseObject) {
1471 mt->motionType = motionTypeUseObject;
1472 mt->o.directObject = &dObj;
1473 mt->flags = reset;
1474 if (isPlayerActor(&a)) mt->flags |= privledged;
1475 }
1476 }
1477 }
1478
1479 //-----------------------------------------------------------------------
1480 // Use one object on another
1481
useObjectOnObject(Actor & a,GameObject & dObj,GameObject & target)1482 void MotionTask::useObjectOnObject(
1483 Actor &a,
1484 GameObject &dObj,
1485 GameObject &target) {
1486 MotionTask *mt;
1487
1488 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1489 if (mt->motionType != motionTypeUseObjectOnObject) {
1490 mt->motionType = motionTypeUseObjectOnObject;
1491 mt->o.directObject = &dObj;
1492 mt->o.indirectObject = ⌖
1493 mt->flags = reset;
1494 if (isPlayerActor(&a)) mt->flags |= privledged;
1495 }
1496 }
1497 }
1498
1499 //-----------------------------------------------------------------------
1500 // Use an object on a TAI
1501
useObjectOnTAI(Actor & a,GameObject & dObj,ActiveItem & target)1502 void MotionTask::useObjectOnTAI(
1503 Actor &a,
1504 GameObject &dObj,
1505 ActiveItem &target) {
1506 MotionTask *mt;
1507
1508 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1509 if (mt->motionType != motionTypeUseObjectOnTAI) {
1510 mt->motionType = motionTypeUseObjectOnTAI;
1511 mt->o.directObject = &dObj;
1512 mt->o.TAI = ⌖
1513 mt->flags = reset;
1514 }
1515 }
1516 }
1517
1518 //-----------------------------------------------------------------------
1519 // Use on object on a TilePoint
1520
useObjectOnLocation(Actor & a,GameObject & dObj,const Location & target)1521 void MotionTask::useObjectOnLocation(
1522 Actor &a,
1523 GameObject &dObj,
1524 const Location &target) {
1525 MotionTask *mt;
1526
1527 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1528 if (mt->motionType != motionTypeUseObjectOnLocation) {
1529 mt->motionType = motionTypeUseObjectOnLocation;
1530 mt->o.directObject = &dObj;
1531 mt->targetLoc = target;
1532 mt->flags = reset;
1533 }
1534 }
1535 }
1536
1537 //-----------------------------------------------------------------------
1538 // Use a TAI
1539
useTAI(Actor & a,ActiveItem & dTAI)1540 void MotionTask::useTAI(Actor &a, ActiveItem &dTAI) {
1541 MotionTask *mt;
1542
1543 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1544 if (mt->motionType != motionTypeUseTAI) {
1545 mt->motionType = motionTypeUseTAI;
1546 mt->o.TAI = &dTAI;
1547 mt->flags = reset;
1548 }
1549 }
1550 }
1551
1552 //-----------------------------------------------------------------------
1553 // Drop an object
1554
dropObject(Actor & a,GameObject & dObj,const Location & loc,int16 num)1555 void MotionTask::dropObject(Actor &a,
1556 GameObject &dObj,
1557 const Location &loc,
1558 int16 num) {
1559 MotionTask *mt;
1560
1561 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1562 if (mt->motionType != motionTypeDropObject) {
1563 mt->motionType = motionTypeDropObject;
1564 mt->o.directObject = &dObj;
1565 mt->targetLoc = loc;
1566 mt->flags = reset;
1567 mt->moveCount = num;
1568 }
1569 }
1570 }
1571
1572 //-----------------------------------------------------------------------
1573 // Drop one object on another
1574
dropObjectOnObject(Actor & a,GameObject & dObj,GameObject & target,int16 num)1575 void MotionTask::dropObjectOnObject(
1576 Actor &a,
1577 GameObject &dObj,
1578 GameObject &target,
1579 int16 num) {
1580 MotionTask *mt;
1581
1582 // If actor is dropping object on himself, and object is already
1583 // in actor's container then consider it a "use" (if the object
1584 // is of the correct type).
1585
1586 if (isActor(&target)
1587 && isPlayerActor((Actor *)&target)
1588 && dObj.IDParent() == target.thisID()
1589 && !(dObj.proto()->containmentSet() & ProtoObj::isContainer)) {
1590 useObject(a, dObj);
1591 return;
1592 }
1593
1594 // Otherwise, drop it on the object
1595
1596 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1597 if (mt->motionType != motionTypeDropObjectOnObject) {
1598 mt->motionType = motionTypeDropObjectOnObject;
1599 mt->o.directObject = &dObj;
1600 mt->o.indirectObject = ⌖
1601 mt->flags = reset;
1602 mt->moveCount = num;
1603 }
1604 }
1605 }
1606
1607 //-----------------------------------------------------------------------
1608 // Drop an object on a TAI
1609
dropObjectOnTAI(Actor & a,GameObject & dObj,ActiveItem & target,const Location & loc)1610 void MotionTask::dropObjectOnTAI(
1611 Actor &a,
1612 GameObject &dObj,
1613 ActiveItem &target,
1614 const Location &loc) {
1615 MotionTask *mt;
1616
1617 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1618 if (mt->motionType != motionTypeDropObjectOnTAI) {
1619 mt->motionType = motionTypeDropObjectOnTAI;
1620 mt->o.directObject = &dObj;
1621 mt->o.TAI = ⌖
1622 mt->targetLoc = loc;
1623 mt->flags = reset;
1624 }
1625 }
1626 }
1627
1628 //-----------------------------------------------------------------------
1629 // Determine if this MotionTask is a reflex ( motion over which an actor
1630 // has no control )
1631
isReflex(void)1632 bool MotionTask::isReflex(void) {
1633 return motionType == motionTypeThrown
1634 || motionType == motionTypeFall
1635 || motionType == motionTypeLand
1636 || motionType == motionTypeAcceptHit
1637 || motionType == motionTypeFallDown
1638 || motionType == motionTypeDie;
1639 }
1640
1641 // Offensive combat actions
1642
1643 //-----------------------------------------------------------------------
1644 // Initiate a two-handed swing
1645
twoHandedSwing(Actor & a,GameObject & target)1646 void MotionTask::twoHandedSwing(Actor &a, GameObject &target) {
1647 MotionTask *mt;
1648
1649 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1650 if (mt->motionType != motionTypeTwoHandedSwing) {
1651 mt->motionType = motionTypeTwoHandedSwing;
1652 mt->targetObj = ⌖
1653 mt->flags = reset;
1654 }
1655 }
1656 }
1657
1658 //-----------------------------------------------------------------------
1659 // Initiate a one-handed swing
1660
oneHandedSwing(Actor & a,GameObject & target)1661 void MotionTask::oneHandedSwing(Actor &a, GameObject &target) {
1662 MotionTask *mt;
1663
1664 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1665 if (mt->motionType != motionTypeOneHandedSwing) {
1666 mt->motionType = motionTypeOneHandedSwing;
1667 mt->targetObj = ⌖
1668 mt->flags = reset;
1669 }
1670 }
1671 }
1672
1673 //-----------------------------------------------------------------------
1674 // Initiate a fire bow motion
1675
fireBow(Actor & a,GameObject & target)1676 void MotionTask::fireBow(Actor &a, GameObject &target) {
1677 MotionTask *mt;
1678
1679 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1680 if (mt->motionType != motionTypeFireBow) {
1681 mt->motionType = motionTypeFireBow;
1682 mt->targetObj = ⌖
1683 mt->flags = reset;
1684 }
1685 }
1686 }
1687
1688 //-----------------------------------------------------------------------
1689 // Initiate a cast spell motion
1690
castSpell(Actor & a,SkillProto & spell,GameObject & target)1691 void MotionTask::castSpell(Actor &a, SkillProto &spell, GameObject &target) {
1692 MotionTask *mt;
1693 motionTypes type =
1694 (spellBook[spell.getSpellID()].getManaType() == sManaIDSkill) ?
1695 motionTypeGive :
1696 motionTypeCastSpell;
1697
1698
1699 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1700 if (mt->motionType != type) {
1701 mt->motionType = type;
1702 mt->spellObj = &spell;
1703 mt->targetObj = ⌖
1704 mt->flags = reset;
1705 mt->direction = (mt->targetObj->getLocation() - a.getLocation()).quickDir();
1706 if (isPlayerActor(&a)) mt->flags |= privledged;
1707 }
1708 }
1709 }
1710
castSpell(Actor & a,SkillProto & spell,Location & target)1711 void MotionTask::castSpell(Actor &a, SkillProto &spell, Location &target) {
1712 MotionTask *mt;
1713 motionTypes type =
1714 (spellBook[spell.getSpellID()].getManaType() == sManaIDSkill) ?
1715 motionTypeGive :
1716 motionTypeCastSpell;
1717
1718 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1719 if (mt->motionType != type) {
1720 mt->motionType = type;
1721 mt->spellObj = &spell;
1722 mt->targetLoc = target; //target;
1723 mt->flags = reset | LocTarg;
1724 mt->direction = (target - a.getLocation()).quickDir();
1725 if (isPlayerActor(&a)) mt->flags |= privledged;
1726 }
1727 }
1728 }
1729
castSpell(Actor & a,SkillProto & spell,ActiveItem & target)1730 void MotionTask::castSpell(Actor &a, SkillProto &spell, ActiveItem &target) {
1731 MotionTask *mt;
1732 motionTypes type =
1733 (spellBook[spell.getSpellID()].getManaType() == sManaIDSkill) ?
1734 motionTypeGive :
1735 motionTypeCastSpell;
1736
1737 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1738 if (mt->motionType != type) {
1739 Location loc;
1740 assert(target._data.itemType == activeTypeInstance);
1741 mt->motionType = type;
1742 mt->spellObj = &spell;
1743 mt->targetTAG = ⌖
1744 loc = Location(
1745 target._data.instance.u << kTileUVShift,
1746 target._data.instance.v << kTileUVShift,
1747 target._data.instance.h,
1748 a.world()->thisID());
1749 mt->targetLoc = loc; //target;
1750 mt->flags = reset | TAGTarg;
1751 mt->direction = (loc - a.getLocation()).quickDir();
1752 if (isPlayerActor(&a)) mt->flags |= privledged;
1753 }
1754 }
1755 }
1756
1757 //-----------------------------------------------------------------------
1758 // Initiate a use wand motion
1759
useWand(Actor & a,GameObject & target)1760 void MotionTask::useWand(Actor &a, GameObject &target) {
1761 MotionTask *mt;
1762
1763 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1764 if (mt->motionType != motionTypeUseWand) {
1765 mt->motionType = motionTypeUseWand;
1766 mt->targetObj = ⌖
1767 mt->flags = reset;
1768 }
1769 }
1770 }
1771
1772 // Defensive combat actions
1773
1774 //-----------------------------------------------------------------------
1775 // Initiate a two-handed parry
1776
twoHandedParry(Actor & a,GameObject & weapon,Actor & opponent)1777 void MotionTask::twoHandedParry(
1778 Actor &a,
1779 GameObject &weapon,
1780 Actor &opponent) {
1781 MotionTask *mt;
1782
1783 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1784 if (mt->motionType != motionTypeTwoHandedParry) {
1785 mt->motionType = motionTypeTwoHandedParry;
1786 mt->d.attacker = &opponent;
1787 mt->d.defensiveObj = &weapon;
1788 }
1789 mt->flags = reset;
1790 mt->d.defenseFlags = 0;
1791 }
1792 }
1793
1794 //-----------------------------------------------------------------------
1795 // Initiate a one-handed parry
1796
oneHandedParry(Actor & a,GameObject & weapon,Actor & opponent)1797 void MotionTask::oneHandedParry(
1798 Actor &a,
1799 GameObject &weapon,
1800 Actor &opponent) {
1801 MotionTask *mt;
1802
1803 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1804 if (mt->motionType != motionTypeOneHandedParry) {
1805 mt->motionType = motionTypeOneHandedParry;
1806 mt->d.attacker = &opponent;
1807 mt->d.defensiveObj = &weapon;
1808 }
1809 mt->flags = reset;
1810 mt->d.defenseFlags = 0;
1811 }
1812 }
1813
1814 //-----------------------------------------------------------------------
1815 // Initiate a shield parry
1816
shieldParry(Actor & a,GameObject & shield,Actor & opponent)1817 void MotionTask::shieldParry(
1818 Actor &a,
1819 GameObject &shield,
1820 Actor &opponent) {
1821 MotionTask *mt;
1822
1823 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1824 if (mt->motionType != motionTypeShieldParry) {
1825 mt->motionType = motionTypeShieldParry;
1826 mt->d.attacker = &opponent;
1827 mt->d.defensiveObj = &shield;
1828 }
1829 mt->flags = reset;
1830 mt->d.defenseFlags = 0;
1831 }
1832 }
1833
1834 //-----------------------------------------------------------------------
1835 // Initiate a dodge
1836
dodge(Actor & a,Actor & opponent)1837 void MotionTask::dodge(Actor &a, Actor &opponent) {
1838 MotionTask *mt;
1839
1840 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1841 if (mt->motionType != motionTypeDodge) {
1842 mt->motionType = motionTypeDodge;
1843 mt->d.attacker = &opponent;
1844 }
1845 mt->flags = reset;
1846 mt->d.defenseFlags = 0;
1847 }
1848 }
1849
1850 // Other combat actions
1851
1852 //-----------------------------------------------------------------------
1853 // Initiate an accept hit motion
1854
acceptHit(Actor & a,Actor & opponent)1855 void MotionTask::acceptHit(Actor &a, Actor &opponent) {
1856 MotionTask *mt;
1857
1858 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1859 if (mt->motionType != motionTypeAcceptHit) {
1860 mt->motionType = motionTypeAcceptHit;
1861 mt->d.attacker = &opponent;
1862 mt->flags = reset;
1863 }
1864 }
1865 }
1866
1867 //-----------------------------------------------------------------------
1868 // Initiate a fall down motion
1869
fallDown(Actor & a,Actor & opponent)1870 void MotionTask::fallDown(Actor &a, Actor &opponent) {
1871 MotionTask *mt;
1872
1873 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1874 if (mt->motionType != motionTypeFallDown) {
1875 mt->motionType = motionTypeFallDown;
1876 mt->d.attacker = &opponent;
1877 mt->flags = reset;
1878 }
1879 }
1880 }
1881
1882 //-----------------------------------------------------------------------
1883 // Initiate a die motion
1884
die(Actor & a)1885 void MotionTask::die(Actor &a) {
1886 MotionTask *mt;
1887
1888 if ((mt = g_vm->_mTaskList->newTask(&a)) != NULL) {
1889 if (mt->motionType != motionTypeDie) {
1890 mt->motionType = motionTypeDie;
1891 mt->flags = reset;
1892 }
1893 }
1894 }
1895
1896 //-----------------------------------------------------------------------
1897 // Determine if this MotionTask is a defensive motion
1898
isDefense(void)1899 bool MotionTask::isDefense(void) {
1900 return motionType == motionTypeOneHandedParry
1901 || motionType == motionTypeTwoHandedParry
1902 || motionType == motionTypeShieldParry
1903 || motionType == motionTypeDodge;
1904 }
1905
1906 //-----------------------------------------------------------------------
1907 // Determine if this MotionTask is an offensive motion
1908
isAttack(void)1909 bool MotionTask::isAttack(void) {
1910 return isMeleeAttack()
1911 || motionType == motionTypeFireBow
1912 || motionType == motionTypeCastSpell
1913 || motionType == motionTypeUseWand;
1914 }
1915
1916 //-----------------------------------------------------------------------
1917 // Determine if this MotionTask is an offensive melee motion
1918
isMeleeAttack(void)1919 bool MotionTask::isMeleeAttack(void) {
1920 return motionType == motionTypeOneHandedSwing
1921 || motionType == motionTypeTwoHandedSwing;
1922 }
1923
1924 //-----------------------------------------------------------------------
1925 // Determine if this MotionTask is a walk motion
1926
isWalk(void)1927 bool MotionTask::isWalk(void) {
1928 return prevMotionType == motionTypeWalk;
1929 }
1930
1931 //-----------------------------------------------------------------------
1932 // Return the wandering tether region
1933
getTether(void)1934 TileRegion MotionTask::getTether(void) {
1935 TileRegion reg;
1936
1937 if (flags & tethered) {
1938 reg.min = TilePoint(tetherMinU, tetherMinV, 0);
1939 reg.max = TilePoint(tetherMaxU, tetherMaxV, 0);
1940 } else {
1941 reg.min = Nowhere;
1942 reg.max = Nowhere;
1943 }
1944
1945 return reg;
1946 }
1947
1948 //-----------------------------------------------------------------------
1949 // If the target has changed position since the last path find started,
1950 // then call this function.
1951
changeTarget(const TilePoint & newPos,bool run)1952 void MotionTask::changeTarget(const TilePoint &newPos, bool run) {
1953 if (prevMotionType == motionTypeWalk) {
1954 uint16 oldFlags = flags;
1955
1956 abortPathFind(this);
1957
1958 finalTarget = immediateLocation = newPos;
1959 pathCount = pathIndex = 0;
1960
1961 flags = pathFind | reset;
1962 if (oldFlags & agitatable)
1963 flags |= agitatable;
1964
1965 // Set run flag if requested
1966 if (run
1967 // Check if actor capable of running...
1968 && ((Actor *)object)->isActionAvailable(actionRun))
1969
1970 flags |= requestRun;
1971 else
1972 flags &= ~requestRun;
1973
1974 RequestPath(this, getPathFindIQ(object));
1975 }
1976 }
1977
1978 //-----------------------------------------------------------------------
1979 // If the target has changed position since the walk/run started, then
1980 // call this function.
1981
changeDirectTarget(const TilePoint & newPos,bool run)1982 void MotionTask::changeDirectTarget(const TilePoint &newPos, bool run) {
1983 if (prevMotionType == motionTypeWalk) {
1984 prevMotionType = motionTypeWalk;
1985
1986 finalTarget = immediateLocation = newPos;
1987
1988 // Reset motion task
1989 flags |= reset;
1990 flags &= ~pathFind;
1991
1992 // Set run flag if requested
1993 if (run
1994 // Check if actor capable of running...
1995 && ((Actor *)object)->isActionAvailable(actionRun))
1996
1997 flags |= requestRun;
1998 else
1999 flags &= ~requestRun;
2000 }
2001 }
2002
2003 // Cancel actor movement if walking...
finishWalk(void)2004 void MotionTask::finishWalk(void) {
2005 // If the actor is in a running state
2006 if (motionType == motionTypeWalk) {
2007 remove();
2008 // If there is currently a path finding request, abort it.
2009 /* abortPathFind( this );
2010
2011 // Simply set actor's target _data.location to "here".
2012 finalTarget = immediateLocation = object->getLocation();
2013 pathList[0] = finalTarget;
2014 flags = reset;
2015 pathCount = 0;
2016 pathIndex = 0;*/
2017 }
2018 }
2019
2020 // Cancel actor movement if talking...
finishTalking(void)2021 void MotionTask::finishTalking(void) {
2022 if (motionType == motionTypeTalk) {
2023 if (isActor(object)) {
2024 Actor *a = (Actor *)object;
2025 if (a->_currentAnimation != actionStand)
2026 a->setAction(actionStand, 0);
2027 }
2028 remove();
2029 }
2030 }
2031
2032 //-----------------------------------------------------------------------
2033 // Handle actions for characters and objects in free-fall
2034
ballisticAction(void)2035 void MotionTask::ballisticAction(void) {
2036 TilePoint totalVelocity, // total velocity vector
2037 stepVelocity, // sub-velocity vector
2038 location,
2039 newPos;
2040
2041 int16 minDim,
2042 vectorSteps;
2043
2044 GameObject *obj = object;
2045 ProtoObj *proto = obj->proto();
2046
2047 if (isActor(obj)) {
2048 // Before anything else make sure the actor is in an
2049 // uninterruptable state.
2050 ((Actor *)obj)->setInterruptablity(false);
2051 }
2052
2053
2054 // Add the force of gravity to the acceleration.
2055
2056 if (!(flags & inWater)) {
2057 velocity.z -= gravity;
2058 } else {
2059 velocity.u = velocity.v = 0;
2060 velocity.z = -gravity;
2061 }
2062 location = obj->getLocation();
2063
2064 // WriteStatusF( 6, "%d %d %d", _data.location.u, _data.location.v, _data.location.z );
2065
2066 // Because we live in a point-sampled universe, we need to make
2067 // sure that objects which are moving extremely fast don't
2068 // undersample the terrain. We do this by breaking the velocity
2069 // vector into smaller vectors, and handling them individually.
2070
2071 totalVelocity = velocity;
2072
2073 // Make Up For Rounding Errors In ThrowTo
2074
2075 if (uFrac) {
2076 uErrorTerm += ABS(uFrac);
2077
2078 if (uErrorTerm >= steps) {
2079 uErrorTerm -= steps;
2080 if (uFrac > 0)
2081 totalVelocity.u++;
2082 else
2083 totalVelocity.u--;
2084 }
2085 }
2086
2087 if (vFrac) {
2088 vErrorTerm += ABS(vFrac);
2089
2090 if (vErrorTerm >= steps) {
2091 vErrorTerm -= steps;
2092 if (vFrac > 0)
2093 totalVelocity.v++;
2094 else
2095 totalVelocity.v--;
2096 }
2097 }
2098
2099 // Determine which dimension is smaller, width or height.
2100 minDim = MAX<int16>(MIN<int16>(proto->height, proto->crossSection * 2), 1);
2101
2102 // "vectorSteps" is the number of increments we are going to process
2103 // this vector.
2104
2105 vectorSteps = ((totalVelocity.magnitude() - 1) / minDim) + 1;
2106
2107 if (isActor(obj) && velocity.magnitude() > 16) {
2108 Actor *a = (Actor *)obj;
2109
2110 if (a->isActionAvailable(actionFreeFall))
2111 a->setAction(actionFreeFall, 0);
2112 }
2113
2114 for (int i = 0; i < vectorSteps; i++) {
2115 int16 stepsLeft = vectorSteps - i;
2116 GameObject *collisionObject;
2117
2118 // REM: This would be better as a rounded division...
2119
2120 // Compute the small velocity vector for this increment,
2121 // and then subtract it from the total velocity.
2122
2123 stepVelocity = totalVelocity / stepsLeft;
2124 totalVelocity -= stepVelocity;
2125
2126 // Compute the new position of the object
2127
2128 newPos = location + stepVelocity;
2129
2130
2131
2132 // See if the object ran into anything. If it didn't, then
2133 // update the coord and try again.
2134
2135 if (isActor(obj)) {
2136 Actor *a = (Actor *)obj;
2137
2138 if (a == getCenterActor() && checkLadder(a, newPos))
2139 return;
2140 }
2141
2142 if (checkContact(obj, newPos, &collisionObject) == false) {
2143 location = newPos;
2144 } else {
2145 TilePoint oldVelocity = velocity;
2146
2147 if (motionType == motionTypeShot && collisionObject != NULL) {
2148 // If this motion is for a shot arrow and we did not
2149 // collide with our target object just continue the
2150 // motion as if there was no collision.
2151 if (collisionObject == targetObj) {
2152 if (object->strike(
2153 o.enactor->thisID(),
2154 targetObj->thisID())) {
2155 // The arrow struck, so delete the arrow and
2156 // end this motion
2157 remove();
2158 object->deleteObject();
2159 return;
2160 } else {
2161 // If the arrow failed to strike continue the
2162 // arrows flight as if there was no collision.
2163 targetObj = NULL;
2164 location = newPos;
2165 continue;
2166 }
2167 } else {
2168 location = newPos;
2169 continue;
2170 }
2171 }
2172
2173 if (unstickObject(obj)) return;
2174
2175 // "probe" is a bitfield which will indicate which
2176 // directions the obstructions lie in.
2177
2178 int16 probe = 0;
2179
2180 // Probe along each of the three coordinate axes
2181
2182 if (checkBlocked(obj,
2183 TilePoint(newPos.u,
2184 obj->_data.location.v,
2185 obj->_data.location.z))) {
2186 probe |= (1 << 0);
2187 }
2188
2189 if (checkBlocked(obj,
2190 TilePoint(obj->_data.location.u,
2191 newPos.v,
2192 obj->_data.location.z))) {
2193 probe |= (1 << 1);
2194 }
2195
2196 if (checkContact(obj,
2197 TilePoint(obj->_data.location.u,
2198 obj->_data.location.v,
2199 newPos.z))) {
2200 probe |= (1 << 2);
2201 }
2202
2203 // If there are no obstructions along the orthogonal
2204 // directions, then we must have hit a corner. In this
2205 // case, we just bounce directly backwards.
2206
2207 if (probe == 0) {
2208 velocity = -velocity / 2;
2209 totalVelocity = -totalVelocity / 2;
2210 } else {
2211 if (probe & (1 << 0)) { // If struck wall in U direction
2212 velocity.u = -velocity.u / 2;
2213 totalVelocity.u = -totalVelocity.u / 2;
2214 } else {
2215 velocity.u = (velocity.u * 2) / 3;
2216 totalVelocity.u = (totalVelocity.u * 2) / 3;
2217 }
2218
2219 if (probe & (1 << 1)) { // If struck wall in V direction
2220 velocity.v = -velocity.v / 2;
2221 totalVelocity.v = -totalVelocity.v / 2;
2222 } else {
2223 velocity.v = (velocity.v * 2) / 3;
2224 totalVelocity.v = (totalVelocity.v * 2) / 3;
2225 }
2226
2227 if (probe & (1 << 2)) { // If struct wall in Z direction
2228 velocity.z = -velocity.z / 2;
2229 totalVelocity.z = -totalVelocity.z / 2;
2230 } else {
2231 velocity.z = (velocity.z * 2) / 3;
2232 totalVelocity.z = (totalVelocity.z * 2) / 3;
2233 }
2234 }
2235 uFrac = vFrac = 0;
2236 if (motionType == motionTypeShot && obj->isMissile())
2237 obj->_data.missileFacing = missileDir(velocity);
2238
2239 // If the ballistic object is an actor hitting the
2240 // ground, then instead of bouncing, we'll just have
2241 // them absorb the impact
2242
2243 if (isActor(obj) && probe & (1 << 2)) {
2244 StandingTileInfo sti;
2245
2246 if (freeFall(location, sti) == false) {
2247 int16 velocityMagnitude = oldVelocity.magnitude();
2248
2249 fallingDamage(obj, velocityMagnitude);
2250 obj->move(location);
2251 if (!((Actor *)obj)->isDead()) {
2252 motionType = velocityMagnitude <= 16
2253 ? motionTypeLand
2254 : motionTypeLandBadly;
2255 flags |= reset;
2256 setObjectSurface(obj, sti);
2257 } else {
2258 setObjectSurface(obj, sti);
2259 remove();
2260 }
2261 return;
2262 } else {
2263 setObjectSurface(obj, sti);
2264 // If the object is falling, then
2265 // freeFall will have already modified the
2266 // object's _data.location
2267 return;
2268 }
2269 } else if (velocity.u < 2 && velocity.u > -2
2270 && velocity.v < 2 && velocity.v > -2
2271 && velocity.z < 2 && velocity.z > -2) {
2272 StandingTileInfo sti;
2273
2274 // If the reduced velocity after impact is
2275 // very small, then we'll assume that the object
2276 // has come to rest.
2277
2278 if (freeFall(location, sti) == false) {
2279 obj->move(location);
2280 remove(); // delete motion task
2281 setObjectSurface(obj, sti);
2282 return;
2283 }
2284 setObjectSurface(obj, sti);
2285 return;
2286 }
2287 // Otherwise, since we struck a wall at high velocity,
2288 // we just drop this small velocity vector from our
2289 // calculations, and continue with the next iteration
2290 // of the loop.
2291 }
2292 }
2293
2294 obj->move(location);
2295 }
2296
2297
2298 //-----------------------------------------------------------------------
2299 // Get the coordinates of the next waypoint.
2300
nextWayPoint(void)2301 bool MotionTask::nextWayPoint(void) {
2302 // If the pathfinder hasn't managed to determine waypoints
2303 // yet, then return failure.
2304 // if ( ( flags & pathFind ) && pathCount < 0 ) return false;
2305
2306 // If there are still waypoints in the path list, then
2307 // retrieve the next waypoint.
2308 if ((flags & (pathFind | wandering)) && pathIndex < pathCount) {
2309 TilePoint wayPointVector(0, 0, 0);
2310
2311 if (pathIndex > 0)
2312 wayPointVector = immediateLocation - object->_data.location;
2313
2314 if (wayPointVector.quickHDistance() == 0)
2315 // Next vertex in path polyline
2316 immediateLocation = pathList[pathIndex++];
2317 else
2318 return false;
2319 } else {
2320 if (flags & wandering) {
2321 immediateLocation = Nowhere;
2322 if (pathFindTask == NULL)
2323 RequestWanderPath(this, getPathFindIQ(object));
2324 } else if (flags & agitated) {
2325 immediateLocation = Nowhere;
2326 } else {
2327 // If we've gone off the end of the path list,
2328 // and we're not at the target yet, request more waypoints then
2329 // use dumb pathfinding until the pathfinder finishes it's task.
2330
2331 if ((finalTarget - object->_data.location).quickHDistance() > 0
2332 || ABS(finalTarget.z - object->_data.location.z) > kMaxStepHeight) {
2333 // If no pathfind in progress
2334 if ((flags & pathFind)
2335 && !(flags & finalPath)
2336 && pathFindTask == NULL)
2337 RequestPath(this, getPathFindIQ(object));
2338
2339 // Set the immediate target to the final target,
2340 immediateLocation = finalTarget;
2341 }
2342 // else we're close enough to call it quits.
2343 else return false;
2344 }
2345 }
2346
2347 return true;
2348 }
2349
2350
2351 //-----------------------------------------------------------------------
2352 // Test to see if actor can walk in a given direction
2353
checkWalk(int16 dir,int16 speed,int16 stepUp,TilePoint & pos)2354 bool MotionTask::checkWalk(
2355 int16 dir,
2356 int16 speed,
2357 int16 stepUp,
2358 TilePoint &pos) {
2359 TilePoint newPos;
2360
2361 // Check the terrain in various directions.
2362 // Check in the forward direction first, at various heights
2363
2364 newPos = object->_data.location + (dirTable[dir] * speed) / 2;
2365 newPos.z = object->_data.location.z + stepUp;
2366
2367 if (checkWalkable(object, newPos)) return false;
2368
2369 // movementDirection = direction;
2370 pos = newPos;
2371 return true;
2372 }
2373
2374 //-----------------------------------------------------------------------
2375 // Handle actions for characters walking and running
2376
walkAction(void)2377 void MotionTask::walkAction(void) {
2378 enum WalkType {
2379 walkNormal = 0,
2380 walkSlow,
2381 walkRun,
2382 walkStairs
2383 };
2384
2385 TilePoint immediateTarget = getImmediateTarget(),
2386 newPos,
2387 targetVector;
2388 int16 targetDist = 0;
2389 int16 movementDirection,
2390 directionAngle;
2391 int16 moveBlocked,
2392 speed = walkSpeed,
2393 speedScale = 2;
2394 Actor *a;
2395 ActorAppearance *aa;
2396 StandingTileInfo sti;
2397
2398 bool moveTaskWaiting = false,
2399 moveTaskDone = false;
2400 WalkType walkType = walkNormal;
2401
2402 assert(isActor(object));
2403 a = (Actor *)object;
2404 aa = a->_appearance;
2405
2406 if (a->isImmobile()) {
2407 remove(motionWalkBlocked);
2408 return;
2409 }
2410
2411 // Make sure that the actor is interruptable
2412 a->setInterruptablity(true);
2413
2414 // Set the speed of movement based on whether we are walking
2415 // or running. Running only occurs after we have accelerated.
2416 if (flags & requestRun
2417 && runCount == 0
2418 && !(flags & (inWater | onStairs))) {
2419 speed = runSpeed;
2420 speedScale = 4;
2421 walkType = walkRun;
2422
2423 // If we can see this actor, and the actor's run frames
2424 // have not been loaded, then downgrade this action to
2425 // a walk (but request the run frames).
2426 if (aa && !aa->isBankLoaded(sprRunBankNum)) {
2427 walkType = walkNormal;
2428 aa->requestBank(sprRunBankNum);
2429 }
2430 }
2431
2432 // If for some reason we cannot run at this time, then
2433 // set up for a walk instead.
2434 if (walkType != walkRun) {
2435 if (!(flags & onStairs)) {
2436 if (!(flags & inWater)) {
2437 speed = walkSpeed;
2438 speedScale = 2;
2439 walkType = walkNormal;
2440 } else {
2441 speed = slowWalkSpeed;
2442 speedScale = 1;
2443 walkType = walkSlow;
2444
2445 // reset run count if actor walking slowly
2446 runCount = MAX<int16>(runCount, 8);
2447 }
2448
2449 // If we can see this actor, and this actor's walk
2450 // frames have not been loaded, then downgrade this
2451 // action to a stand (i.e. do nothing).
2452 if (aa && !aa->isBankLoaded(sprWalkBankNum)) {
2453 aa->requestBank(sprWalkBankNum);
2454 return;
2455 }
2456 } else {
2457 speed = slowWalkSpeed;
2458 speedScale = 1;
2459 walkType = walkStairs;
2460
2461 // reset run count if actor walking on stairs
2462 runCount = MAX<int16>(runCount, 8);
2463 }
2464 }
2465
2466 if ((flags & agitated)
2467 && --actionCounter <= 0) {
2468 flags &= ~agitated;
2469 flags |= pathFind | reset;
2470 }
2471
2472 for (;;) {
2473 // The "reset" flag indicates that the final target has
2474 // changed since the last time this routine was called.
2475 if (!(flags & reset)) {
2476 // Compute the vector and distance of the current
2477 // position to the next "immediate" target.
2478 targetVector = immediateTarget - object->_data.location;
2479 targetDist = targetVector.quickHDistance();
2480
2481 // If we're not already there, then proceed towards
2482 // the target.
2483 if (targetDist > 0 || ABS(targetVector.z) > kMaxStepHeight)
2484 break;
2485 }
2486
2487 if (nextWayPoint() == false) {
2488 // If no waypoint could be found and this motion task has
2489 // a path find request, then go into "wait" mode.
2490 if (pathFindTask)
2491 moveTaskWaiting = true;
2492 else moveTaskDone = true;
2493 break;
2494 } else {
2495 flags &= ~reset;
2496 immediateTarget = getImmediateTarget();
2497 }
2498 }
2499
2500 #if VISUAL1
2501 extern void ShowObjectSection(GameObject * obj);
2502 extern void TPLine(const TilePoint & start, const TilePoint & stop);
2503 {
2504 TilePoint curPt,
2505 wayPt,
2506 pt1,
2507 pt2;
2508
2509 // TPLine( a->getLocation(), immediateLocation );
2510 curPt = a->getLocation();
2511 wayPt = immediateTarget;
2512
2513 for (int i = pathIndex - 1; i < pathCount;) {
2514 TPLine(curPt, wayPt);
2515 pt1 = pt2 = wayPt;
2516 pt1.u -= 2;
2517 pt1.v -= 2;
2518 pt2.u -= 2;
2519 pt2.v += 2;
2520 TPLine(pt1, pt2);
2521 pt1.u += 4;
2522 pt1.v += 4;
2523 TPLine(pt1, pt2);
2524 pt2.u += 4;
2525 pt2.v -= 4;
2526 TPLine(pt1, pt2);
2527 pt1.u -= 4;
2528 pt1.v -= 4;
2529 TPLine(pt1, pt2);
2530
2531 curPt = wayPt;
2532 wayPt = pathList[++i];
2533 }
2534
2535 ShowObjectSection(a);
2536 }
2537 #endif
2538
2539 moveBlocked = false;
2540
2541 if (moveTaskDone || moveTaskWaiting) {
2542 movementDirection = a->_currentFacing;
2543 } else if (targetDist == 0 && ABS(targetVector.z) > kMaxStepHeight) {
2544 if (pathFindTask) {
2545 movementDirection = a->_currentFacing;
2546 moveTaskWaiting = true;
2547 } else {
2548 movementDirection = a->_currentFacing;
2549 moveBlocked = true;
2550 }
2551 } else if (targetDist <= speed) {
2552 int16 blockageType;
2553
2554 // If we're near the target, then don't bother with
2555 // a smooth movement, just jump right there.
2556 movementDirection = targetVector.quickDir();
2557 // movementDirection = a->currentFacing;
2558
2559 // Set the new _data.location to the character's _data.location.
2560 newPos.u = immediateTarget.u;
2561 newPos.v = immediateTarget.v;
2562 newPos.z = object->_data.location.z;
2563
2564 // Determine the direction the character must spin
2565 // to be at the correct movement angle.
2566 directionAngle =
2567 (((movementDirection - a->_currentFacing) + 4) & 7) - 4;
2568
2569 // Test terrain. Note that if the character is spinning more than 1
2570 // octant this frame, then they cannot move so a terrain test is unneeded.
2571 if (directionAngle <= 1 && directionAngle >= -1) {
2572 // Test the terrain to see if we can go there.
2573 if ((blockageType = checkWalkable(object, newPos)) != false) {
2574 // Try stepping up to a higher terrain too.
2575 newPos.z = object->_data.location.z + kMaxStepHeight;
2576 if (checkWalkable(object, newPos) != blockageNone) {
2577 // If there is a path find task pending, put the walk action
2578 // on hold until it finishes, else, abort the walk action.
2579 if (pathFindTask)
2580 moveTaskWaiting = true;
2581 else {
2582 movementDirection = a->_currentFacing;
2583 moveBlocked = true;
2584 }
2585 /* if (!(flags & pathFind) || nextWayPoint() == false)
2586 {
2587 moveBlocked = true;
2588 flags |= blocked;
2589 newPos.z = object->_data.location.z;
2590
2591 }*/
2592 }
2593 }
2594 }
2595 } else {
2596 int16 height;
2597 bool foundPath = false;
2598
2599 movementDirection = targetVector.quickDir();
2600
2601 // Calculate new object position along direction vector.
2602 TilePoint pos = object->_data.location
2603 + targetVector * speed / targetDist;
2604
2605 #if DEBUG*0
2606 TPLine(object->_data.location, pos);
2607 #endif
2608
2609 // Check the terrain in various directions.
2610 // Check in the forward direction first, at various heights
2611
2612 for (height = 0; height <= kMaxStepHeight; height += kMaxSmoothStep) {
2613 // This code has him move along the exact direction
2614 // vector, even if it's not aligned with one of the
2615 // cardinal directions.
2616
2617 pos.z = object->_data.location.z + height;
2618
2619 if (!checkWalkable(object, pos)) {
2620 newPos = pos;
2621 foundPath = true;
2622 break;
2623 }
2624 }
2625
2626
2627 // Check left and right facings if a path was not found in
2628 // the forward direction.
2629
2630 if (foundPath == false) {
2631 int16 leftDir = spinLeft(movementDirection),
2632 rightDir = spinRight(movementDirection);
2633
2634 for (height = 0; height <= kMaxStepHeight; height += 8) {
2635 if (checkWalk(rightDir, speedScale, height, newPos)) {
2636 movementDirection = rightDir;
2637 foundPath = true;
2638 break;
2639 }
2640
2641 if (checkWalk(leftDir, speedScale, height, newPos)) {
2642 movementDirection = leftDir;
2643 foundPath = true;
2644 break;
2645 }
2646 }
2647 }
2648
2649 // Let's try moving at a right angle to the current path to
2650 // get around this annoying obstacle...
2651
2652 if (foundPath == false) {
2653 if (targetVector.u > speed / 2
2654 && checkWalk(dirUpRight, speedScale, 0, newPos)) {
2655 movementDirection = dirUpRight;
2656 foundPath = true;
2657 } else if (-targetVector.u > speed / 2
2658 && checkWalk(dirDownLeft, speedScale, 0, newPos)) {
2659 movementDirection = dirDownLeft;
2660 foundPath = true;
2661 } else if (targetVector.v > speed / 2
2662 && checkWalk(dirUpLeft, speedScale, 0, newPos)) {
2663 movementDirection = dirUpLeft;
2664 foundPath = true;
2665 } else if (-targetVector.v > speed / 2
2666 && checkWalk(dirDownRight, speedScale, 0, newPos)) {
2667 movementDirection = dirDownRight;
2668 foundPath = true;
2669 }
2670 }
2671
2672 // If we just couldn't find a valid path no matter how hard
2673 // we tried, then just give up and say that we were blocked.
2674
2675 if (foundPath == false) {
2676
2677 // If there is a path find task pending, put the walk action
2678 // on hold until it finishes, else, abort the walk action.
2679 if (pathFindTask)
2680 moveTaskWaiting = true;
2681 else {
2682 movementDirection = a->_currentFacing;
2683 moveBlocked = true;
2684 }
2685 }
2686 }
2687
2688 // REM: Test the terrain at the new spot.
2689
2690 if (movementDirection != a->_currentFacing) {
2691 // Determine the direction the character must spin
2692 // to be at the correct movement angle.
2693 directionAngle =
2694 (((movementDirection - a->_currentFacing) + 4) & 7) - 4;
2695
2696 // If the direction is at a right angle or behind
2697 // the character, then they cannot move.
2698
2699 if (directionAngle < 0) {
2700 a->_currentFacing = spinRight(a->_currentFacing);
2701 } else {
2702 a->_currentFacing = spinLeft(a->_currentFacing);
2703 }
2704 }
2705
2706 if (moveTaskDone) {
2707 remove(motionCompleted);
2708 } else if (moveBlocked) {
2709 a->setAction(actionStand, 0);
2710 if (flags & agitatable) {
2711 if (freeFall(object->_data.location, sti)) return;
2712
2713 // When he starts running again, then have him walk only.
2714 runCount = MAX<int16>(runCount, 8);
2715
2716 // We're blocked so we're going to wander in a random
2717 // direction for a random duration
2718 flags |= agitated | reset;
2719
2720 direction = g_vm->_rnd->getRandomNumber(7);
2721 actionCounter = 8 + g_vm->_rnd->getRandomNumber(7);
2722
2723 // Discard the path
2724 if (flags & pathFind) {
2725 flags &= ~finalPath;
2726 pathIndex = pathCount = 0;
2727 }
2728 } else
2729 remove(motionWalkBlocked);
2730 } else if (moveTaskWaiting
2731 || movementDirection != a->_currentFacing) {
2732 // When he starts running again, then have him walk only.
2733 runCount = MAX<int16>(runCount, 8);
2734
2735 a->setAction(actionStand, 0);
2736 freeFall(object->_data.location, sti);
2737 } else {
2738 if (a == getCenterActor() && checkLadder(a, newPos)) return;
2739
2740 int16 tHeight;
2741
2742 flags &= ~blocked;
2743
2744 tHeight = tileSlopeHeight(newPos, object, &sti);
2745
2746
2747 // This is a kludge to keep the character from
2748 // "jumping" as he climbs up a small step.
2749
2750 if (tHeight >= object->_data.location.z - kMaxSmoothStep
2751 * ((sti.surfaceTile != NULL
2752 && (sti.surfaceTile->combinedTerrainMask() & terrainStair))
2753 ? 4
2754 : 1)
2755 && tHeight < newPos.z)
2756 newPos.z = tHeight;
2757
2758 if (freeFall(newPos, sti) == false) {
2759 int16 newAction;
2760
2761 if (sti.surfaceTile != NULL
2762 && (sti.surfaceTile->combinedTerrainMask() & terrainStair)
2763 && a->isActionAvailable(actionSpecial7)) {
2764 Direction stairsDir;
2765 uint8 *cornerHeight;
2766
2767 cornerHeight = sti.surfaceTile->attrs.cornerHeight;
2768
2769 if (cornerHeight[0] == 0 && cornerHeight[1] == 0)
2770 stairsDir = 1;
2771 else if (cornerHeight[1] == 0 && cornerHeight[2] == 0)
2772 stairsDir = 3;
2773 else if (cornerHeight[2] == 0 && cornerHeight[3] == 0)
2774 stairsDir = 5;
2775 else
2776 stairsDir = 7;
2777
2778 if (a->_currentFacing == stairsDir) {
2779 // walk up stairs
2780 newAction = actionSpecial7;
2781 flags |= onStairs;
2782 } else if (a->_currentFacing == ((stairsDir - 4) & 0x7)) {
2783 // walk down stairs
2784 newAction = actionSpecial8;
2785 flags |= onStairs;
2786 } else {
2787 flags &= ~onStairs;
2788 if (walkType == walkStairs) walkType = walkNormal;
2789 newAction = (walkType == walkRun) ? actionRun : actionWalk;
2790 }
2791 } else {
2792 flags &= ~onStairs;
2793 if (walkType == walkStairs) walkType = walkNormal;
2794 newAction = (walkType == walkRun) ? actionRun : actionWalk;
2795 }
2796
2797
2798 object->move(newPos);
2799
2800 // Determine if the new action is running
2801 // or walking.
2802
2803 if (a->_currentAnimation == newAction) {
2804 // If we are already doing that action, then
2805 // just continue doing it.
2806 if (walkType != walkSlow)
2807 a->nextAnimationFrame();
2808 else {
2809 if (flags & nextAnim)
2810 a->nextAnimationFrame();
2811 flags ^= nextAnim;
2812 }
2813 } else if (a->_currentAnimation == actionWalk
2814 || a->_currentAnimation == actionRun
2815 || a->_currentAnimation == actionSpecial7
2816 || a->_currentAnimation == actionSpecial8) {
2817 // If we are running instead of walking or
2818 // vice versa, then change to the new action
2819 // but don't break stride
2820 a->setAction(newAction,
2821 animateRepeat | animateNoRestart);
2822
2823 if (walkType != walkSlow)
2824 a->nextAnimationFrame();
2825 else {
2826 if (flags & nextAnim)
2827 a->nextAnimationFrame();
2828 flags ^= nextAnim;
2829 }
2830 } else {
2831 // If we weren't walking or running before, then start
2832 // walking/running and reset the sequence.
2833 a->setAction(newAction, animateRepeat);
2834 if (walkType == walkSlow) flags |= nextAnim;
2835 }
2836
2837 if (runCount > 0) runCount--;
2838 setObjectSurface(object, sti);
2839 }
2840 }
2841 }
2842
2843 //-----------------------------------------------------------------------
2844 // Climb up a ladder
2845
upLadderAction(void)2846 void MotionTask::upLadderAction(void) {
2847 Actor *a = (Actor *)object;
2848
2849 if (flags & reset) {
2850 a->setAction(actionClimbLadder, animateRepeat);
2851 flags &= ~reset;
2852 } else {
2853 TilePoint loc = a->getLocation();
2854 uint8 crossSection = a->proto()->crossSection,
2855 height = a->proto()->height;
2856 int16 mapNum = a->getMapNum();
2857 TileRegion actorTileReg;
2858 TileInfo *ti;
2859 TilePoint tileLoc;
2860 StandingTileInfo sti = {nullptr, nullptr, {0, 0, 0}, 0};
2861
2862 loc.z += 6;
2863
2864 // Determine the tile region which the actor overlays
2865 actorTileReg.min.u = (loc.u - crossSection) >> kTileUVShift;
2866 actorTileReg.min.v = (loc.v - crossSection) >> kTileUVShift;
2867 actorTileReg.max.u =
2868 (loc.u + crossSection + kTileUVMask) >> kTileUVShift;
2869 actorTileReg.max.v =
2870 (loc.v + crossSection + kTileUVMask) >> kTileUVShift;
2871 actorTileReg.min.z = actorTileReg.max.z = 0;
2872
2873 TileIterator iter(mapNum, actorTileReg);
2874
2875 // Iterate through all the tiles in the actor's tile region
2876 for (ti = iter.first(&tileLoc, &sti);
2877 ti != NULL;
2878 ti = iter.next(&tileLoc, &sti)) {
2879 if (!(ti->combinedTerrainMask() & terrainLadder)) continue;
2880
2881 if (sti.surfaceHeight
2882 + ti->attrs.terrainHeight
2883 <= loc.z
2884 + height
2885 || sti.surfaceHeight > loc.z + height)
2886 continue;
2887
2888 uint16 footPrintMask = 0xFFFF,
2889 ladderMask;
2890 TilePoint subTileLoc(
2891 tileLoc.u << kTileSubShift,
2892 tileLoc.v << kTileSubShift,
2893 0);
2894 TileRegion actorSubTileReg;
2895
2896 actorSubTileReg.min.u = (loc.u - crossSection) >> kSubTileShift;
2897 actorSubTileReg.min.v = (loc.v - crossSection) >> kSubTileShift;
2898 actorSubTileReg.max.u =
2899 (loc.u + crossSection + kSubTileMask) >> kSubTileShift;
2900 actorSubTileReg.max.v =
2901 (loc.v + crossSection + kSubTileMask) >> kSubTileShift;
2902
2903 if (actorSubTileReg.min.u >= subTileLoc.u)
2904 footPrintMask &=
2905 uMinMasks[actorSubTileReg.min.u - subTileLoc.u];
2906
2907 if (actorSubTileReg.min.v >= subTileLoc.v)
2908 footPrintMask &=
2909 vMinMasks[actorSubTileReg.min.v - subTileLoc.v];
2910
2911 if (actorSubTileReg.max.u < subTileLoc.u + kTileSubSize)
2912 footPrintMask &=
2913 uMaxMasks[actorSubTileReg.max.u - subTileLoc.u];
2914
2915 if (actorSubTileReg.max.v < subTileLoc.v + kTileSubSize)
2916 footPrintMask &=
2917 vMaxMasks[actorSubTileReg.max.v - subTileLoc.v];
2918
2919 ladderMask = ti->attrs.fgdTerrain == terrNumLadder
2920 ? ti->attrs.terrainMask
2921 : ~ti->attrs.terrainMask;
2922
2923 if (footPrintMask & ladderMask) {
2924 a->nextAnimationFrame();
2925 a->move(loc);
2926 return;
2927 }
2928 }
2929
2930 TilePoint newLoc;
2931
2932 newLoc = loc + incDirTable[a->_currentFacing] * crossSection * 2;
2933 newLoc.z = tileSlopeHeight(newLoc, a);
2934
2935 if (!checkBlocked(a, newLoc))
2936 a->move(newLoc);
2937 else {
2938 newLoc = loc
2939 + incDirTable[(a->_currentFacing - 2) & 7]
2940 * crossSection * 2;
2941 newLoc.z = tileSlopeHeight(newLoc, a);
2942
2943 if (!checkBlocked(a, newLoc))
2944 a->move(newLoc);
2945 else {
2946 newLoc = loc
2947 + incDirTable[(a->_currentFacing + 2) & 7]
2948 * crossSection * 2;
2949 newLoc.z = tileSlopeHeight(newLoc, a);
2950
2951 if (!checkBlocked(a, newLoc))
2952 a->move(newLoc);
2953 else {
2954 newLoc = loc
2955 + incDirTable[a->_currentFacing]
2956 * crossSection * 2;
2957 newLoc.z = tileSlopeHeight(newLoc, a);
2958 a->move(newLoc);
2959 unstickObject(a);
2960 }
2961 }
2962 }
2963
2964 a->setAction(actionStand, 0);
2965
2966 remove();
2967 }
2968 }
2969
2970 //-----------------------------------------------------------------------
2971 // Climb down a ladder
2972
downLadderAction(void)2973 void MotionTask::downLadderAction(void) {
2974 Actor *a = (Actor *)object;
2975
2976 if (flags & reset) {
2977 a->setAction(actionClimbLadder, animateRepeat | animateReverse);
2978 flags &= ~reset;
2979 } else {
2980 TilePoint loc = a->getLocation();
2981 uint8 crossSection = a->proto()->crossSection;
2982 int16 mapNum = a->getMapNum();
2983 TileRegion actorTileReg;
2984 TileInfo *ti;
2985 TilePoint tileLoc;
2986 StandingTileInfo sti = {nullptr, nullptr, {0, 0, 0}, 0};
2987
2988 loc.z -= 6;
2989
2990 actorTileReg.min.u = (loc.u - crossSection) >> kTileUVShift;
2991 actorTileReg.min.v = (loc.v - crossSection) >> kTileUVShift;
2992 actorTileReg.max.u =
2993 (loc.u + crossSection + kTileUVMask) >> kTileUVShift;
2994 actorTileReg.max.v =
2995 (loc.v + crossSection + kTileUVMask) >> kTileUVShift;
2996 actorTileReg.min.z = actorTileReg.max.z = 0;
2997
2998 TileIterator iter(mapNum, actorTileReg);
2999
3000 for (ti = iter.first(&tileLoc, &sti);
3001 ti != NULL;
3002 ti = iter.next(&tileLoc, &sti)) {
3003 if (!(ti->combinedTerrainMask() & terrainLadder)) continue;
3004
3005 if (sti.surfaceHeight + ti->attrs.terrainHeight <= loc.z
3006 || sti.surfaceHeight > loc.z)
3007 continue;
3008
3009 uint16 footPrintMask = 0xFFFF,
3010 ladderMask;
3011 TilePoint subTileLoc(
3012 tileLoc.u << kTileSubShift,
3013 tileLoc.v << kTileSubShift,
3014 0);
3015 TileRegion actorSubTileReg;
3016
3017 actorSubTileReg.min.u = (loc.u - crossSection) >> kSubTileShift;
3018 actorSubTileReg.min.v = (loc.v - crossSection) >> kSubTileShift;
3019 actorSubTileReg.max.u =
3020 (loc.u + crossSection + kSubTileMask) >> kSubTileShift;
3021 actorSubTileReg.max.v =
3022 (loc.v + crossSection + kSubTileMask) >> kSubTileShift;
3023
3024 if (actorSubTileReg.min.u >= subTileLoc.u)
3025 footPrintMask &=
3026 uMinMasks[actorSubTileReg.min.u - subTileLoc.u];
3027
3028 if (actorSubTileReg.min.v >= subTileLoc.v)
3029 footPrintMask &=
3030 vMinMasks[actorSubTileReg.min.v - subTileLoc.v];
3031
3032 if (actorSubTileReg.max.u < subTileLoc.u + kTileSubSize)
3033 footPrintMask &=
3034 uMaxMasks[actorSubTileReg.max.u - subTileLoc.u];
3035
3036 if (actorSubTileReg.max.v < subTileLoc.v + kTileSubSize)
3037 footPrintMask &=
3038 vMaxMasks[actorSubTileReg.max.v - subTileLoc.v];
3039
3040 ladderMask = ti->attrs.fgdTerrain == terrNumLadder
3041 ? ti->attrs.terrainMask
3042 : ~ti->attrs.terrainMask;
3043
3044 if (footPrintMask & ladderMask) {
3045 a->nextAnimationFrame();
3046 a->move(loc);
3047 return;
3048 }
3049 }
3050
3051 TilePoint newLoc;
3052
3053 newLoc = loc - incDirTable[a->_currentFacing] * kTileUVSize;
3054 newLoc.z = tileSlopeHeight(newLoc, a);
3055
3056 if (!checkBlocked(a, newLoc))
3057 a->move(newLoc);
3058 else {
3059 newLoc = loc
3060 - incDirTable[(a->_currentFacing - 2) & 7]
3061 * kTileUVSize;
3062 newLoc.z = tileSlopeHeight(newLoc, a);
3063
3064 if (!checkBlocked(a, newLoc))
3065 a->move(newLoc);
3066 else {
3067 newLoc = loc
3068 - incDirTable[(a->_currentFacing + 2) & 7]
3069 * kTileUVSize;
3070 newLoc.z = tileSlopeHeight(newLoc, a);
3071
3072 if (!checkBlocked(a, newLoc))
3073 a->move(newLoc);
3074 else {
3075 newLoc = loc
3076 - incDirTable[a->_currentFacing]
3077 * kTileUVSize;
3078 newLoc.z = tileSlopeHeight(newLoc, a);
3079 a->move(newLoc);
3080 unstickObject(a);
3081 }
3082 }
3083 }
3084
3085 a->setAction(actionStand, 0);
3086
3087 remove();
3088 }
3089 }
3090
3091 // Go through the giving motions
giveAction(void)3092 void MotionTask::giveAction(void) {
3093 Actor *a = (Actor *)object;
3094 Direction targetDir = (targetObj->getLocation()
3095 - a->getLocation()).quickDir();
3096
3097 if (flags & reset) {
3098 a->setAction(actionGiveItem, 0);
3099 flags &= ~reset;
3100 }
3101
3102 if (a->_currentFacing != targetDir)
3103 a->turn(targetDir);
3104 else if (a->nextAnimationFrame())
3105 remove(motionCompleted);
3106 }
3107
3108
3109 // Set up specified animation and run through the frames
genericAnimationAction(uint8 actionType)3110 void MotionTask::genericAnimationAction(uint8 actionType) {
3111 Actor *const a = (Actor *)object;
3112
3113 if (flags & reset) {
3114 a->setAction(actionType, 0);
3115 flags &= ~reset;
3116 } else if (a->nextAnimationFrame())
3117 remove(motionCompleted);
3118 }
3119
3120 // This class is specifically designed to aid in the selection of
3121 // of a combat motion type from a selected subset
3122 struct CombatMotionSet {
3123 const uint8 *list; // Array of motion types
3124 uint16 listSize; // Size of array
3125
3126 // Select randome element from the array
selectRandomSaga2::CombatMotionSet3127 uint8 selectRandom(void) const {
3128 return list[g_vm->_rnd->getRandomNumber(listSize - 1)];
3129 }
3130 };
3131
3132 // Offensive combat actions
3133
3134 // Construct a set of all two handed swing types
3135 const uint8 twoHandedSwingArray[] = {
3136 MotionTask::twoHandedSwingHigh,
3137 MotionTask::twoHandedSwingLow,
3138 MotionTask::twoHandedSwingLeftHigh,
3139 MotionTask::twoHandedSwingLeftLow,
3140 MotionTask::twoHandedSwingRightHigh,
3141 MotionTask::twoHandedSwingRightLow,
3142 };
3143
3144 const CombatMotionSet twoHandedSwingSet = {
3145 twoHandedSwingArray,
3146 ARRAYSIZE(twoHandedSwingArray)
3147 };
3148
3149 // Construct a subset of all high two handed swing types
3150 const uint8 twoHandedHighSwingArray[] = {
3151 MotionTask::twoHandedSwingHigh,
3152 MotionTask::twoHandedSwingLeftHigh,
3153 MotionTask::twoHandedSwingRightHigh,
3154 };
3155
3156 const CombatMotionSet twoHandedHighSwingSet = {
3157 twoHandedHighSwingArray,
3158 ARRAYSIZE(twoHandedHighSwingArray)
3159 };
3160
3161 // Construct a subset of all low two handed swing types
3162 const uint8 twoHandedLowSwingArray[] = {
3163 MotionTask::twoHandedSwingLow,
3164 MotionTask::twoHandedSwingLeftLow,
3165 MotionTask::twoHandedSwingRightLow,
3166 };
3167
3168 const CombatMotionSet twoHandedLowSwingSet = {
3169 twoHandedLowSwingArray,
3170 ARRAYSIZE(twoHandedLowSwingArray)
3171 };
3172
3173 //-----------------------------------------------------------------------
3174 // Handle all two handed swing motions
3175
twoHandedSwingAction(void)3176 void MotionTask::twoHandedSwingAction(void) {
3177 // If the reset flag is set, initialize the motion
3178 if (flags & reset) {
3179 // Let the game engine know about this aggressive act
3180 logAggressiveAct(object->thisID(), targetObj->thisID());
3181
3182 // Notify the target actor that he is being attacked
3183 if (isActor(targetObj))
3184 ((Actor *)targetObj)->evaluateMeleeAttack((Actor *)object);
3185
3186 // Create an animation type lookup table
3187 static const uint8 animationTypeArray[] = {
3188 actionTwoHandSwingHigh,
3189 actionTwoHandSwingLow,
3190 actionTwoHandSwingLeftHigh,
3191 actionTwoHandSwingLeftLow,
3192 actionTwoHandSwingRightHigh,
3193 actionTwoHandSwingRightLow,
3194 };
3195
3196 Actor *a = (Actor *)object;
3197 uint8 actorAnimation;
3198 int16 actorMidAltitude,
3199 targetAltitude = targetObj->getLocation().z;
3200
3201 const CombatMotionSet *availableSet;
3202
3203 // Calculate the altitude of the actor's mid section
3204 actorMidAltitude = a->getLocation().z + (a->proto()->height >> 1);
3205
3206 if (targetAltitude > actorMidAltitude)
3207 // The target is higher than the actor's midsection
3208 availableSet = &twoHandedHighSwingSet;
3209 else {
3210 uint8 targetHeight = targetObj->proto()->height;
3211
3212 if (targetAltitude + targetHeight < actorMidAltitude)
3213 // The target is below the actor's midsection
3214 availableSet = &twoHandedLowSwingSet;
3215 else
3216 // The target is nearly the same altitude as the actor
3217 availableSet = &twoHandedSwingSet;
3218 }
3219
3220 // Calculate the direction of the attack
3221 direction = (targetObj->getLocation() - a->getLocation()).quickDir();
3222
3223 // Randomly select a combat motion type from the available set
3224 combatMotionType = availableSet->selectRandom();
3225 actorAnimation = animationTypeArray[combatMotionType];
3226
3227 if (a->_appearance != NULL
3228 && a->isActionAvailable(actorAnimation)) {
3229 // Compute the number of frames in the animation before the
3230 // actual strike
3231 actionCounter = a->animationFrames(actorAnimation, direction) - 2;
3232
3233 a->setAction(actorAnimation, 0);
3234
3235 // Set this flag to indicate that the animation is actually
3236 // being played
3237 flags |= nextAnim;
3238 } else {
3239 actionCounter = 2;
3240
3241 // Clear this flag to indicate that the animation is not
3242 // being played
3243 flags &= ~nextAnim;
3244 }
3245
3246 a->setActionPoints(
3247 computeTurnFrames(a->_currentFacing, direction) + 10);
3248
3249 flags &= ~reset;
3250 } else
3251 // Call the generic offensive melee function
3252 offensiveMeleeAction();
3253 }
3254
3255
3256 // Construct a set of all one handed swing types
3257 const uint8 oneHandedSwingArray[] = {
3258 MotionTask::oneHandedSwingHigh,
3259 MotionTask::oneHandedSwingLow,
3260 // MotionTask::oneHandedThrust,
3261 };
3262
3263 const CombatMotionSet oneHandedSwingSet = {
3264 oneHandedSwingArray,
3265 ARRAYSIZE(oneHandedSwingArray)
3266 };
3267
3268 // Construct a subset of all high one handed swing types
3269 const uint8 oneHandedHighSwingArray[] = {
3270 MotionTask::oneHandedSwingHigh,
3271 };
3272
3273 const CombatMotionSet oneHandedHighSwingSet = {
3274 oneHandedHighSwingArray,
3275 ARRAYSIZE(oneHandedHighSwingArray)
3276 };
3277
3278 // Construct a subset of all low one handed swing types
3279 const uint8 oneHandedLowSwingArray[] = {
3280 MotionTask::oneHandedSwingLow,
3281 };
3282
3283 const CombatMotionSet oneHandedLowSwingSet = {
3284 oneHandedLowSwingArray,
3285 ARRAYSIZE(oneHandedLowSwingArray)
3286 };
3287
3288 //-----------------------------------------------------------------------
3289 // Handle all one handed swing motions
3290
oneHandedSwingAction(void)3291 void MotionTask::oneHandedSwingAction(void) {
3292 if (flags & reset) {
3293 // Let the game engine know about this aggressive act
3294 logAggressiveAct(object->thisID(), targetObj->thisID());
3295
3296 // Notify the target actor that he is being attacked
3297 if (isActor(targetObj))
3298 ((Actor *)targetObj)->evaluateMeleeAttack((Actor *)object);
3299
3300 // Create an animation type lookup table
3301 static const uint8 animationTypeArray[] = {
3302 actionSwingHigh,
3303 actionSwingLow,
3304 };
3305
3306 Actor *const a = (Actor *)object;
3307 uint8 actorAnimation;
3308 int16 actorMidAltitude,
3309 targetAltitude = targetObj->getLocation().z;
3310
3311 const CombatMotionSet *availableSet;
3312
3313 // Calculate the altitude of the actor's mid section
3314 actorMidAltitude = a->getLocation().z + (a->proto()->height >> 1);
3315
3316 if (targetAltitude > actorMidAltitude)
3317 // The target is higher than the actor's midsection
3318 availableSet = &oneHandedHighSwingSet;
3319 else {
3320 uint8 targetHeight = targetObj->proto()->height;
3321
3322 if (targetAltitude + targetHeight < actorMidAltitude)
3323 // The target is below the actor's midsection
3324 availableSet = &oneHandedLowSwingSet;
3325 else
3326 // The target is nearly the same altitude as the actor
3327 availableSet = &oneHandedSwingSet;
3328 }
3329
3330 // Calculate the direction of the attack
3331 direction = (targetObj->getLocation() - a->getLocation()).quickDir();
3332
3333 // Randomly select a combat motion type from the available set
3334 combatMotionType = availableSet->selectRandom();
3335
3336 /* if ( combatMotionType == oneHandedThrust )
3337 {
3338 // Initialize the thrust motion
3339 }
3340 else*/
3341 {
3342 actorAnimation = animationTypeArray[combatMotionType];
3343 if (a->_appearance != NULL
3344 && a->isActionAvailable(actorAnimation)) {
3345 // Compute the number of frames in the animation before the
3346 // actual strike
3347 actionCounter = a->animationFrames(actorAnimation, direction) - 2;
3348
3349 a->setAction(actorAnimation, 0);
3350
3351 // Set this flag to indicate that the animation is actually
3352 // being played
3353 flags |= nextAnim;
3354 } else {
3355 actionCounter = 1;
3356
3357 // Clear this flag to indicate that the animation is not
3358 // being played
3359 flags &= ~nextAnim;
3360 }
3361
3362 }
3363
3364 a->setActionPoints(actionCounter * 2);
3365
3366 a->setActionPoints(
3367 computeTurnFrames(a->_currentFacing, direction) + 10);
3368
3369 flags &= ~reset;
3370 } else
3371 // Call the generic offensive melee function
3372 offensiveMeleeAction();
3373 }
3374
3375 //-----------------------------------------------------------------------
3376 // Compute the number of frames before the actual strike in an
3377 // offensive melee motion
3378
framesUntilStrike(void)3379 uint16 MotionTask::framesUntilStrike(void) {
3380 // If the melee action has not been initialized, return a safe value
3381 if (flags & reset) return maxuint16;
3382
3383 uint16 turnFrames;
3384
3385 turnFrames = (direction - ((Actor *)object)->_currentFacing) & 0x7;
3386 if (turnFrames > 4) turnFrames = 8 - turnFrames;
3387
3388 return turnFrames + actionCounter;
3389 }
3390
3391 //-----------------------------------------------------------------------
3392 // Returns a pointer to the blocking object if it applicable to
3393 // this motion task
3394
blockingObject(Actor * thisAttacker)3395 GameObject *MotionTask::blockingObject(Actor *thisAttacker) {
3396 return isDefense()
3397 && (d.defenseFlags & blocking)
3398 && thisAttacker == d.attacker
3399 ? d.defensiveObj
3400 : NULL;
3401 }
3402
3403 //-----------------------------------------------------------------------
3404 // Handle bow firing motions
3405
fireBowAction(void)3406 void MotionTask::fireBowAction(void) {
3407 Actor *a = (Actor *)object;
3408
3409 assert(a->_leftHandObject != Nothing);
3410
3411 // Initialize the bow firing motion
3412 if (flags & reset) {
3413 // Let the game engine know about this aggressive act
3414 logAggressiveAct(object->thisID(), targetObj->thisID());
3415
3416 // Compute the direction to the target
3417 direction = (targetObj->getLocation() - a->getLocation()).quickDir();
3418
3419 if (a->_appearance != NULL
3420 && a->isActionAvailable(actionFireBow)) {
3421 // Calculate the number of frames in the animation before the
3422 // projectile is actually fired
3423 actionCounter = a->animationFrames(actionFireBow, direction) - 1;
3424 a->setAction(actionFireBow, 0);
3425
3426 // Set this flag to indicate that the animation is actually
3427 // being played
3428 flags |= nextAnim;
3429 } else {
3430 actionCounter = 1;
3431
3432 // Clear this flag to indicate that the animation is not
3433 // being played
3434 flags &= ~nextAnim;
3435 }
3436
3437 a->setActionPoints(
3438 computeTurnFrames(a->_currentFacing, direction) + 10);
3439
3440 if (a->_currentFacing != direction)
3441 a->turn(direction);
3442
3443 flags &= ~reset;
3444 } else if (a->_currentFacing != direction)
3445 a->turn(direction);
3446 else {
3447 // If the actors appearance becomes NULL, make sure this action
3448 // no longer depends upon the animation
3449 if ((flags & nextAnim) && a->_appearance == NULL)
3450 flags &= ~nextAnim;
3451
3452 // If the action counter has reached zero, get a projectile and
3453 // fire it
3454 if (actionCounter == 0) {
3455 GameObject *missileWeapon;
3456
3457 missileWeapon = GameObject::objectAddress(a->_leftHandObject);
3458 if (missileWeapon != NULL) {
3459 GameObject *proj;
3460
3461 // Ask the missile weapon's prototype to get a projectile
3462 proj = missileWeapon->proto()->getProjectile(
3463 a->_leftHandObject,
3464 a->thisID());
3465
3466 // Shoot the projectile
3467 if (proj != NULL) {
3468 TilePoint actorLoc = a->getLocation();
3469 uint8 actorCrossSection = a->proto()->crossSection,
3470 projCrossSection = proj->proto()->crossSection;
3471 ObjectID projID;
3472
3473 actorLoc.u += incDirTable[a->_currentFacing].u
3474 * (actorCrossSection + projCrossSection);
3475 actorLoc.v += incDirTable[a->_currentFacing].v
3476 * (actorCrossSection + projCrossSection);
3477 actorLoc.z += a->proto()->height * 7 / 8;
3478
3479 if ((projID = proj->extractMerged(Location(actorLoc, a->IDParent()), 1)) != Nothing) {
3480 g_vm->_cnm->setUpdate(a->thisID());
3481 proj = GameObject::objectAddress(projID);
3482 shootObject(*proj, *a, *targetObj, 16);
3483 }
3484 }
3485 }
3486 }
3487
3488 if (flags & nextAnim) {
3489 // Run through the animation frames
3490 if (!a->nextAnimationFrame()) {
3491 if (actionCounter >= 0) actionCounter--;
3492 } else
3493 remove();
3494 } else {
3495 if (actionCounter > 0)
3496 actionCounter--;
3497 else
3498 remove();
3499 }
3500 }
3501 }
3502
3503 //-----------------------------------------------------------------------
3504 // Handle spell casting motions
3505
castSpellAction(void)3506 void MotionTask::castSpellAction(void) {
3507 Actor *a = (Actor *)object;
3508
3509 // Turn until facing the target
3510 if (a->_currentFacing != direction)
3511 a->turn(direction);
3512 else {
3513 if (flags & reset) {
3514 if (a->_appearance != NULL
3515 && a->isActionAvailable(actionCastSpell)) {
3516 // Calculate the number of frames in the animation before the
3517 // spell is case
3518 actionCounter = a->animationFrames(actionCastSpell, direction) - 1;
3519 a->setAction(actionCastSpell, 0);
3520
3521 // Set this flag to indicate that the animation is actually
3522 // being played
3523 flags |= nextAnim;
3524 } else {
3525 actionCounter = 3;
3526
3527 // Clear this flag to indicate that the animation is not
3528 // being played
3529 flags &= ~nextAnim;
3530 }
3531
3532 flags &= ~reset;
3533 }
3534
3535 // If the actors appearance becomes NULL, make sure this action
3536 // no longer depends upon the animation
3537 if ((flags & nextAnim) && a->_appearance == NULL)
3538 flags &= ~nextAnim;
3539
3540 if (actionCounter == 0) {
3541 if (spellObj) {
3542 if (flags & TAGTarg) {
3543 assert(targetTAG->_data.itemType == activeTypeInstance);
3544 spellObj->implementAction(spellObj->getSpellID(), a->thisID(), targetTAG);
3545 } else if (flags & LocTarg) {
3546 spellObj->implementAction(spellObj->getSpellID(), a->thisID(), targetLoc);
3547 } else if (targetObj) {
3548 spellObj->implementAction(spellObj->getSpellID(), a->thisID(), targetObj->thisID());
3549 }
3550 }
3551 }
3552
3553 if (flags & nextAnim) {
3554 // Run through the animation frames
3555 if (!a->nextAnimationFrame()) {
3556 if (actionCounter >= 0) actionCounter--;
3557 } else
3558 remove();
3559 } else {
3560 if (actionCounter > 0)
3561 actionCounter--;
3562 else
3563 remove();
3564 }
3565 }
3566 }
3567
3568 //-----------------------------------------------------------------------
3569 // Handle wand using motions
3570
useWandAction(void)3571 void MotionTask::useWandAction(void) {
3572 // Initialize the wand using motion
3573 if (flags & reset) {
3574 // Let the game engine know about this aggressive act
3575 logAggressiveAct(object->thisID(), targetObj->thisID());
3576
3577 Actor *a = (Actor *)object;
3578
3579 direction = (targetObj->getLocation() - a->getLocation()).quickDir();
3580
3581 if (a->_appearance != NULL
3582 && a->isActionAvailable(actionUseWand)) {
3583 actionCounter = a->animationFrames(actionUseWand, direction) - 1;
3584 a->setAction(actionUseWand, 0);
3585
3586 // Set this flag to indicate that the animation is actually
3587 // being played
3588 flags |= nextAnim;
3589 } else {
3590 actionCounter = 3;
3591
3592 // Clear this flag to indicate that the animation is not
3593 // being played
3594 flags &= ~nextAnim;
3595 }
3596
3597 a->setActionPoints(
3598 computeTurnFrames(a->_currentFacing, direction) + 10);
3599
3600 flags &= ~reset;
3601 }
3602 useMagicWeaponAction();
3603 }
3604
3605 // Defensive combat actions
3606 //-----------------------------------------------------------------------
3607 // Handle two handed parrying motions
3608
twoHandedParryAction(void)3609 void MotionTask::twoHandedParryAction(void) {
3610 if (flags & reset) {
3611 Actor *a = (Actor *)object;
3612 int16 animationFrames;
3613
3614 direction = (d.attacker->getLocation() - a->getLocation()).quickDir();
3615
3616 if (a->_appearance != NULL
3617 && a->isActionAvailable(actionTwoHandParry)) {
3618 a->setAction(actionTwoHandParry, 0);
3619 animationFrames = a->animationFrames(actionTwoHandParry, direction);
3620
3621 // Set this flag to indicate that the animation is actually
3622 // being played
3623 flags |= nextAnim;
3624 } else {
3625 animationFrames = 2;
3626
3627 // Clear this flag to indicate that the animation is not
3628 // being played
3629 flags &= ~nextAnim;
3630 }
3631
3632 a->setActionPoints(
3633 computeTurnFrames(a->_currentFacing, direction)
3634 + animationFrames + 1);
3635
3636 flags &= ~reset;
3637 }
3638 defensiveMeleeAction();
3639 }
3640
3641 //-----------------------------------------------------------------------
3642 // Handle one handed parrying motions
3643
oneHandedParryAction(void)3644 void MotionTask::oneHandedParryAction(void) {
3645 if (flags & reset) {
3646 Actor *a = (Actor *)object;
3647 int16 animationFrames;
3648
3649 direction = (d.attacker->getLocation() - a->getLocation()).quickDir();
3650
3651 combatMotionType = oneHandedParryHigh;
3652 if (a->_appearance != NULL
3653 && a->isActionAvailable(actionParryHigh)) {
3654 a->setAction(actionParryHigh, 0);
3655 animationFrames = a->animationFrames(actionParryHigh, direction);
3656
3657 // Set this flag to indicate that the animation is actually
3658 // being played
3659 flags |= nextAnim;
3660 } else {
3661 animationFrames = 2;
3662
3663 // Clear this flag to indicate that the animation is not
3664 // being played
3665 flags &= ~nextAnim;
3666 }
3667
3668 a->setActionPoints(
3669 computeTurnFrames(a->_currentFacing, direction)
3670 + animationFrames + 1);
3671
3672 flags &= ~reset;
3673 }
3674 defensiveMeleeAction();
3675 }
3676
3677 //-----------------------------------------------------------------------
3678 // Handle shield parrying motions
3679
shieldParryAction(void)3680 void MotionTask::shieldParryAction(void) {
3681 if (flags & reset) {
3682 Actor *a = (Actor *)object;
3683 int16 animationFrames;
3684
3685 direction = (d.attacker->getLocation() - a->getLocation()).quickDir();
3686
3687 if (a->_appearance != NULL
3688 && a->isActionAvailable(actionShieldParry)) {
3689 a->setAction(actionShieldParry, 0);
3690 animationFrames = a->animationFrames(actionShieldParry, direction);
3691
3692 // Set this flag to indicate that the animation is actually
3693 // being played
3694 flags |= nextAnim;
3695 } else {
3696 animationFrames = 1;
3697
3698 // Clear this flag to indicate that the animation is not
3699 // being played
3700 flags &= ~nextAnim;
3701 }
3702
3703 a->setActionPoints(
3704 computeTurnFrames(a->_currentFacing, direction)
3705 + animationFrames + 1);
3706
3707 flags &= ~reset;
3708 }
3709 defensiveMeleeAction();
3710 }
3711
3712 //-----------------------------------------------------------------------
3713 // Handle dodging motions
3714
dodgeAction(void)3715 void MotionTask::dodgeAction(void) {
3716 Actor *a = (Actor *)object;
3717 MotionTask *attackerMotion = d.attacker->_moveTask;
3718
3719 if (flags & reset) {
3720 // If the attacker is not attacking, we're done
3721 if (attackerMotion == NULL
3722 || !attackerMotion->isMeleeAttack()) {
3723 a->setInterruptablity(true);
3724 remove();
3725 return;
3726 }
3727
3728 // If the strike is about to land start the dodging motion
3729 if (attackerMotion->framesUntilStrike() <= 2) {
3730 int16 animationFrames;
3731
3732 if (a->_appearance != NULL
3733 && a->isActionAvailable(actionJumpUp, a->_currentFacing)) {
3734 a->setAction(actionJumpUp, 0);
3735 animationFrames = a->animationFrames(actionJumpUp, a->_currentFacing);
3736
3737 // Set this flag to indicate that the animation is actually
3738 // being played
3739 flags |= nextAnim;
3740 } else {
3741 animationFrames = 3;
3742
3743 // Clear this flag to indicate that the animation is not
3744 // being played
3745 flags &= ~nextAnim;
3746 }
3747
3748 actionCounter = animationFrames - 1;
3749 a->setActionPoints(animationFrames + 1);
3750
3751 flags &= ~reset;
3752 }
3753 } else {
3754 // If the actors appearance becomes NULL, make sure this action
3755 // no longer depends upon the animation
3756 if ((flags & nextAnim) && a->_appearance == NULL)
3757 flags &= ~nextAnim;
3758
3759 if (flags & nextAnim) {
3760 // Run through the animation frames
3761 if (!a->nextAnimationFrame()) {
3762 if (actionCounter > 0) actionCounter--;
3763 } else
3764 remove();
3765 } else {
3766 if (actionCounter > 0)
3767 actionCounter--;
3768 else
3769 remove();
3770 }
3771 }
3772 }
3773
3774 //-----------------------------------------------------------------------
3775 // Handle accept hit motions
3776
acceptHitAction(void)3777 void MotionTask::acceptHitAction(void) {
3778 Actor *a = (Actor *)object;
3779
3780 if (flags & reset) {
3781 TilePoint newLoc = a->getLocation();
3782 StandingTileInfo sti;
3783 int16 animationFrames;
3784
3785 a->_currentFacing =
3786 (d.attacker->getWorldLocation() - a->getLocation()).quickDir();
3787
3788 if (a->_appearance != NULL
3789 && a->isActionAvailable(actionHit, a->_currentFacing)) {
3790 a->setAction(actionHit, 0);
3791 animationFrames = a->animationFrames(actionHit, a->_currentFacing);
3792
3793 // Set this flag to indicate that the animation is actually
3794 // being played
3795 flags |= nextAnim;
3796 } else {
3797 animationFrames = 1;
3798
3799 // Clear this flag to indicate that the animation is not
3800 // being played
3801 flags &= ~nextAnim;
3802 }
3803
3804 a->setActionPoints(animationFrames + 1);
3805
3806 if (g_vm->_rnd->getRandomNumber(1)) {
3807 // Calculate the new position to knock the actor back to
3808 newLoc += dirTable[(a->_currentFacing - 4) & 0x7];
3809
3810 // If the actor is not blocked, move him back
3811 if (!checkBlocked(a, newLoc)) {
3812 newLoc.z = tileSlopeHeight(newLoc, a, &sti);
3813 a->move(newLoc);
3814 setObjectSurface(a, sti);
3815 }
3816 }
3817
3818 flags &= ~reset;
3819 } else {
3820 // If the actors appearance becomes NULL, make sure this action
3821 // no longer depends upon the animation
3822 if ((flags & nextAnim) && a->_appearance == NULL)
3823 flags &= ~nextAnim;
3824
3825 if (flags & nextAnim) {
3826 if (a->nextAnimationFrame()) remove();
3827 } else
3828 remove();
3829 }
3830 }
3831
3832 //-----------------------------------------------------------------------
3833 // Handle fall down motions
3834
fallDownAction(void)3835 void MotionTask::fallDownAction(void) {
3836 Actor *a = (Actor *)object;
3837
3838 if (flags & reset) {
3839 TilePoint newLoc = a->getLocation();
3840 StandingTileInfo sti;
3841 int16 animationFrames;
3842
3843 a->_currentFacing =
3844 (d.attacker->getWorldLocation() - a->getLocation()).quickDir();
3845
3846 if (a->_appearance != NULL
3847 && a->isActionAvailable(actionKnockedDown, a->_currentFacing)) {
3848 a->setAction(actionKnockedDown, 0);
3849 animationFrames = a->animationFrames(
3850 actionKnockedDown,
3851 a->_currentFacing);
3852
3853 // Set this flag to indicate that the animation is actually
3854 // being played
3855 flags |= nextAnim;
3856 } else {
3857 animationFrames = 6;
3858
3859 // Clear this flag to indicate that the animation is not
3860 // being played
3861 flags &= ~nextAnim;
3862 }
3863
3864 a->setActionPoints(animationFrames + 1);
3865
3866 if (g_vm->_rnd->getRandomNumber(1)) {
3867 // Calculate the new position to knock the actor back to
3868 newLoc += dirTable[(a->_currentFacing - 4) & 0x7];
3869 newLoc.z = tileSlopeHeight(newLoc, a, &sti);
3870
3871 // If the actor is not blocked, move him back
3872 if (!checkBlocked(a, newLoc)) {
3873 a->move(newLoc);
3874 setObjectSurface(a, sti);
3875 }
3876 }
3877
3878 flags &= ~reset;
3879 } else {
3880 // If the actors appearance becomes NULL, make sure this action
3881 // no longer depends upon the animation
3882 if ((flags & nextAnim) && a->_appearance == NULL)
3883 flags &= ~nextAnim;
3884
3885 if (flags & nextAnim) {
3886 if (a->nextAnimationFrame()) remove();
3887 } else
3888 remove();
3889 }
3890 }
3891
3892 //-----------------------------------------------------------------------
3893 // Generic offensive melee code. Called by twoHandedSwingAction()
3894 // and oneHandedSwingAction()
3895
offensiveMeleeAction(void)3896 void MotionTask::offensiveMeleeAction(void) {
3897 Actor *a = (Actor *)object;
3898
3899 // Turn until facing the target
3900 if (a->_currentFacing != direction)
3901 a->turn(direction);
3902 else {
3903 // If the actors appearance becomes NULL, make sure this action
3904 // no longer depends upon the animation
3905 if ((flags & nextAnim) && a->_appearance == NULL)
3906 flags &= ~nextAnim;
3907
3908 // If the action counter has reached zero, use the weapon on
3909 // the target
3910 if (actionCounter == 0) {
3911 GameObject *weapon;
3912
3913 weapon = a->offensiveObject();
3914 if (weapon) weapon->strike(a->thisID(), targetObj->thisID());
3915 }
3916
3917 if (flags & nextAnim) {
3918 // Run through the animation frames
3919 if (!a->nextAnimationFrame()) {
3920 if (actionCounter >= 0) actionCounter--;
3921 } else
3922 remove();
3923 } else {
3924 if (actionCounter > 0)
3925 actionCounter--;
3926 else
3927 remove();
3928 }
3929 }
3930 }
3931
3932 //-----------------------------------------------------------------------
3933 // Generic magic weapon code. Called by useWandAction().
3934
useMagicWeaponAction(void)3935 void MotionTask::useMagicWeaponAction(void) {
3936 Actor *a = (Actor *)object;
3937
3938 // Turn until facing the target
3939 if (a->_currentFacing != direction)
3940 a->turn(direction);
3941 else {
3942 // If the actors appearance becomes NULL, make sure this action
3943 // no longer depends upon the animation
3944 if ((flags & nextAnim) && a->_appearance == NULL)
3945 flags &= ~nextAnim;
3946
3947 // If the action counter has reached zero, get a spell and
3948 // use it
3949 if (actionCounter == 0) {
3950 GameObject *magicWeapon;
3951
3952 magicWeapon = a->offensiveObject();
3953
3954 if (magicWeapon != NULL && magicWeapon->IDChild() != Nothing) {
3955 GameObject *spell;
3956 SkillProto *spellProto;
3957
3958 spell = GameObject::objectAddress(magicWeapon->IDChild());
3959 spellProto = (SkillProto *)spell->proto();
3960
3961 assert(spellProto->containmentSet() & ProtoObj::isSkill);
3962
3963 // use the spell
3964 spellProto->implementAction(
3965 spellProto->getSpellID(),
3966 magicWeapon->thisID(),
3967 targetObj->thisID());
3968 }
3969 }
3970
3971 if (flags & nextAnim) {
3972 // Run through the animation frames
3973 if (!a->nextAnimationFrame()) {
3974 if (actionCounter >= 0) actionCounter--;
3975 } else
3976 remove();
3977 } else {
3978 if (actionCounter > 0)
3979 actionCounter--;
3980 else
3981 remove();
3982 }
3983 }
3984 }
3985
3986 //-----------------------------------------------------------------------
3987 // Generic defensive melee code. Called by twoHandedParryAction(),
3988 // oneHandedParryAction() and shieldParryAction().
3989
defensiveMeleeAction(void)3990 void MotionTask::defensiveMeleeAction(void) {
3991 Actor *a = (Actor *)object;
3992 MotionTask *attackerMotion = d.attacker->_moveTask;
3993
3994 // Determine if the blocking action has been initiated
3995 if (!(d.defenseFlags & blocking)) {
3996 // If the attacker is not attacking, we're done
3997 if (attackerMotion == NULL
3998 || !attackerMotion->isMeleeAttack()) {
3999 a->setInterruptablity(true);
4000 remove();
4001 return;
4002 }
4003
4004 // turn towards attacker
4005 if (a->_currentFacing != direction)
4006 a->turn(direction);
4007
4008 // If the strike is about to land start the blocking motion
4009 if (attackerMotion->framesUntilStrike() <= 1)
4010 d.defenseFlags |= blocking;
4011 } else {
4012 // If the actors appearance becomes NULL, make sure this action
4013 // no longer depends upon the animation
4014 if ((flags & nextAnim) && a->_appearance == NULL)
4015 flags &= ~nextAnim;
4016
4017 // Run through the animation frames
4018 if (!(flags & nextAnim) || a->nextAnimationFrame()) {
4019 // Wait for the attacker's attack
4020 if (attackerMotion == NULL
4021 || !attackerMotion->isMeleeAttack()) {
4022 a->setInterruptablity(true);
4023 remove();
4024 }
4025 }
4026 }
4027 }
4028
4029 //-----------------------------------------------------------------------
4030 // Routine to update positions of all moving objects using MotionTasks
4031
updatePositions(void)4032 void MotionTask::updatePositions(void) {
4033 TilePoint targetVector;
4034 TilePoint fallVelocity, terminalVelocity(15, 15, 0);
4035 TilePoint curLoc;
4036 int16 targetDist;
4037 StandingTileInfo sti;
4038
4039 for (Common::List<MotionTask *>::iterator it = g_vm->_mTaskList->_list.begin(); it != g_vm->_mTaskList->_list.end(); it = g_vm->_mTaskList->_nextMT) {
4040 MotionTask *mt = *it;
4041 GameObject *obj = mt->object;
4042 ProtoObj *proto = obj->proto();
4043 Actor *a = (Actor *)obj;
4044 bool moveTaskDone = false;
4045
4046 g_vm->_mTaskList->_nextMT = it;
4047 g_vm->_mTaskList->_nextMT++;
4048
4049 if (!isWorld(obj->IDParent())) {
4050 mt->remove();
4051 continue;
4052 }
4053
4054 // Determine if this motion should be skipped
4055 if (interruptableMotionsPaused
4056 && isActor(obj)
4057 && a->isInterruptable())
4058 continue;
4059
4060 if (obj->_data.location.z < -(proto->height >> 2))
4061 mt->flags |= inWater;
4062 else
4063 mt->flags &= ~inWater;
4064
4065 switch (mt->motionType) {
4066 case motionTypeThrown:
4067 case motionTypeShot:
4068 mt->ballisticAction();
4069 break;
4070
4071 case motionTypeWalk:
4072 mt->walkAction();
4073 break;
4074
4075 case motionTypeClimbUp:
4076 mt->upLadderAction();
4077 break;
4078
4079 case motionTypeClimbDown:
4080 mt->downLadderAction();
4081 break;
4082
4083 case motionTypeTalk:
4084
4085 if (mt->flags & reset) {
4086 a->setAction(actionStand, 0);
4087 a->_cycleCount = g_vm->_rnd->getRandomNumber(3);
4088 mt->flags &= ~(reset | nextAnim);
4089 }
4090 if (a->_cycleCount == 0) {
4091 a->setAction(actionTalk, 0);
4092 mt->flags |= nextAnim;
4093 a->_cycleCount = -1;
4094 } else if (mt->flags & nextAnim) {
4095 if (a->nextAnimationFrame()) {
4096 a->setAction(actionStand, 0);
4097 a->_cycleCount = g_vm->_rnd->getRandomNumber(3);
4098 mt->flags &= ~nextAnim;
4099 }
4100 } else
4101 a->_cycleCount--;
4102 break;
4103
4104 case motionTypeLand:
4105 case motionTypeLandBadly:
4106
4107 if (mt->flags & reset) {
4108 int16 newAction = mt->motionType == motionTypeLand
4109 ? actionJumpUp
4110 : actionFallBadly;
4111
4112 if (!a->isActionAvailable(newAction)) {
4113 if (mt->prevMotionType == motionTypeWalk) {
4114 mt->motionType = mt->prevMotionType;
4115 if (mt->flags & pathFind) {
4116 mt->changeTarget(
4117 mt->finalTarget,
4118 (mt->flags & requestRun) != 0);
4119 } else {
4120 mt->changeDirectTarget(
4121 mt->finalTarget,
4122 (mt->flags & requestRun) != 0);
4123 }
4124 g_vm->_mTaskList->_nextMT = it;
4125 }
4126 } else {
4127 a->setAction(newAction, 0);
4128 a->setInterruptablity(false);
4129 mt->flags &= ~reset;
4130 }
4131 } else if (a->nextAnimationFrame() || (mt->flags & inWater)) {
4132 if (mt->prevMotionType == motionTypeWalk) {
4133 mt->motionType = mt->prevMotionType;
4134 if (mt->flags & pathFind) {
4135 mt->changeTarget(
4136 mt->finalTarget,
4137 (mt->flags & requestRun) != 0);
4138 } else {
4139 mt->changeDirectTarget(
4140 mt->finalTarget,
4141 (mt->flags & requestRun) != 0);
4142 }
4143 g_vm->_mTaskList->_nextMT = it;
4144 } else if (mt->freeFall(obj->_data.location, sti) == false)
4145 moveTaskDone = true;
4146 } else {
4147 // If actor was running, go through an abreviated
4148 // landing sequence by aborting the landing animation
4149 // after the first frame.
4150 if (mt->prevMotionType == motionTypeWalk
4151 && mt->flags & requestRun
4152 && mt->runCount == 0
4153 && !(mt->flags & inWater)) {
4154 mt->motionType = mt->prevMotionType;
4155 if (mt->flags & pathFind) {
4156 mt->changeTarget(
4157 mt->finalTarget,
4158 (mt->flags & requestRun) != 0);
4159 } else {
4160 mt->changeDirectTarget(
4161 mt->finalTarget,
4162 (mt->flags & requestRun) != 0);
4163 }
4164 g_vm->_mTaskList->_nextMT = it;
4165 }
4166 }
4167 break;
4168
4169 case motionTypeJump:
4170
4171 if (mt->flags & reset) {
4172 a->setAction(actionJumpUp, 0);
4173 a->setInterruptablity(false);
4174 mt->flags &= ~reset;
4175 } else if (a->nextAnimationFrame()) {
4176 mt->motionType = motionTypeThrown;
4177 a->setAction(actionFreeFall, 0);
4178 }
4179 break;
4180
4181 case motionTypeTurn:
4182
4183 mt->turnAction();
4184 break;
4185
4186 case motionTypeGive:
4187
4188 mt->giveAction();
4189 break;
4190
4191 case motionTypeRise:
4192
4193 if (a->_data.location.z < mt->immediateLocation.z) {
4194 a->_data.location.z++;
4195 if (mt->flags & nextAnim)
4196 a->nextAnimationFrame();
4197 mt->flags ^= nextAnim;
4198 } else {
4199 targetVector = mt->finalTarget - obj->_data.location;
4200 targetDist = targetVector.quickHDistance();
4201
4202 if (targetDist > kTileUVSize) {
4203 mt->motionType = mt->prevMotionType;
4204 mt->flags |= reset;
4205 g_vm->_mTaskList->_nextMT = it;
4206 } else
4207 moveTaskDone = true;
4208 }
4209 break;
4210
4211 case motionTypeWait:
4212
4213 if (mt->flags & reset) {
4214 mt->actionCounter = 5;
4215 mt->flags &= ~reset;
4216 } else if (--mt->actionCounter == 0)
4217 moveTaskDone = true;
4218 break;
4219
4220 case motionTypeUseObject:
4221
4222 // This will be uninterrutable for 2 frames
4223 a->setActionPoints(2);
4224 mt->o.directObject->use(a->thisID());
4225 //g_vm->_mTaskList->_nextMT=mt;
4226 moveTaskDone = true;
4227 break;
4228
4229 case motionTypeUseObjectOnObject:
4230
4231 if (isWorld(mt->o.indirectObject->IDParent())) {
4232 if (
4233 1
4234 #ifdef THIS_SHOULD_BE_IN_TILEMODE
4235 a->inUseRange(
4236 mt->o.indirectObject->getLocation(),
4237 mt->o.directObject)
4238 #endif
4239 ) {
4240 mt->direction = (mt->o.indirectObject->getLocation()
4241 - a->getLocation()).quickDir();
4242 if (a->_currentFacing != mt->direction)
4243 a->turn(mt->direction);
4244 else {
4245 // The actor will now be uniterruptable
4246 a->setActionPoints(2);
4247 mt->o.directObject->useOn(
4248 a->thisID(),
4249 mt->o.indirectObject->thisID());
4250 if (mt->motionType == motionTypeUseObjectOnObject)
4251 moveTaskDone = true;
4252 else
4253 g_vm->_mTaskList->_nextMT = it;
4254 }
4255 }
4256 } else {
4257 // The actor will now be uniterruptable
4258 a->setActionPoints(2);
4259 mt->o.directObject->useOn(
4260 a->thisID(),
4261 mt->o.indirectObject->thisID());
4262 if (mt->motionType == motionTypeUseObjectOnObject)
4263 moveTaskDone = true;
4264 else
4265 g_vm->_mTaskList->_nextMT = it;
4266 }
4267
4268 break;
4269
4270 case motionTypeUseObjectOnTAI:
4271
4272 if (mt->flags & reset) {
4273 TilePoint actorLoc = a->getLocation(),
4274 TAILoc;
4275 TileRegion TAIReg;
4276 ActiveItem *TAG = mt->o.TAI->getGroup();
4277
4278 // Compute in points the region of the TAI
4279 TAIReg.min.u = mt->o.TAI->_data.instance.u << kTileUVShift;
4280 TAIReg.min.v = mt->o.TAI->_data.instance.v << kTileUVShift;
4281 TAIReg.max.u = TAIReg.min.u
4282 + (TAG->_data.group.uSize << kTileUVShift);
4283 TAIReg.max.v = TAIReg.min.v
4284 + (TAG->_data.group.vSize << kTileUVShift);
4285 TAIReg.min.z = TAIReg.max.z = 0;
4286
4287 // Find the point on the TAI closest to the actor
4288 TAILoc.u = clamp(TAIReg.min.u, actorLoc.u, TAIReg.max.u - 1);
4289 TAILoc.v = clamp(TAIReg.min.v, actorLoc.v, TAIReg.max.v - 1);
4290 TAILoc.z = actorLoc.z;
4291
4292 // Compute the direction from the actor to the TAI
4293 mt->direction = (TAILoc - actorLoc).quickDir();
4294 mt->flags &= ~reset;
4295 }
4296
4297 if (a->_currentFacing != mt->direction)
4298 a->turn(mt->direction);
4299 else {
4300 // The actor will now be uniterruptable
4301 a->setActionPoints(2);
4302 mt->o.directObject->useOn(a->thisID(), mt->o.TAI);
4303 if (mt->motionType == motionTypeUseObjectOnTAI)
4304 moveTaskDone = true;
4305 else
4306 g_vm->_mTaskList->_nextMT = it;
4307 }
4308 break;
4309
4310 case motionTypeUseObjectOnLocation:
4311
4312 if (mt->flags & reset) {
4313 mt->direction = (mt->targetLoc - a->getLocation()).quickDir();
4314 mt->flags &= ~reset;
4315 }
4316
4317 if (a->_currentFacing != mt->direction)
4318 a->turn(mt->direction);
4319 else {
4320 // The actor will now be uniterruptable
4321 a->setActionPoints(2);
4322 mt->o.directObject->useOn(a->thisID(), mt->targetLoc);
4323 if (mt->motionType == motionTypeUseObjectOnLocation)
4324 moveTaskDone = true;
4325 else
4326 g_vm->_mTaskList->_nextMT = it;
4327 }
4328 break;
4329
4330 case motionTypeUseTAI:
4331
4332 if (mt->flags & reset) {
4333 TilePoint actorLoc = a->getLocation(),
4334 TAILoc;
4335 TileRegion TAIReg;
4336 ActiveItem *TAG = mt->o.TAI->getGroup();
4337
4338 // Compute in points the region of the TAI
4339 TAIReg.min.u = mt->o.TAI->_data.instance.u << kTileUVShift;
4340 TAIReg.min.v = mt->o.TAI->_data.instance.v << kTileUVShift;
4341 TAIReg.max.u = TAIReg.min.u
4342 + (TAG->_data.group.uSize << kTileUVShift);
4343 TAIReg.max.v = TAIReg.min.v
4344 + (TAG->_data.group.vSize << kTileUVShift);
4345 TAIReg.min.z = TAIReg.max.z = 0;
4346
4347 // Find the point on the TAI closest to the actor
4348 TAILoc.u = clamp(TAIReg.min.u, actorLoc.u, TAIReg.max.u - 1);
4349 TAILoc.v = clamp(TAIReg.min.v, actorLoc.v, TAIReg.max.v - 1);
4350 TAILoc.z = actorLoc.z;
4351
4352 // Compute the direction from the actor to the TAI
4353 mt->direction = (TAILoc - actorLoc).quickDir();
4354 mt->flags &= ~reset;
4355 }
4356
4357 if (a->_currentFacing != mt->direction)
4358 a->turn(mt->direction);
4359 else {
4360 // The actor will now be uniterruptable
4361 a->setActionPoints(2);
4362 mt->o.TAI->use(a->thisID());
4363 moveTaskDone = true;
4364 }
4365 break;
4366
4367 case motionTypeDropObject:
4368
4369 if (isWorld(mt->targetLoc.context)) {
4370 if (mt->flags & reset) {
4371 mt->direction = (mt->targetLoc - a->getLocation()).quickDir();
4372 mt->flags &= ~reset;
4373 }
4374
4375 if (a->_currentFacing != mt->direction)
4376 a->turn(mt->direction);
4377 else {
4378 // The actor will now be uniterruptable
4379 a->setActionPoints(2);
4380 mt->o.directObject->drop(a->thisID(),
4381 mt->targetLoc,
4382 mt->moveCount);
4383 if (mt->motionType == motionTypeDropObject)
4384 moveTaskDone = true;
4385 else
4386 g_vm->_mTaskList->_nextMT = it;
4387 }
4388 } else {
4389 // The actor will now be uniterruptable
4390 a->setActionPoints(2);
4391 mt->o.directObject->drop(a->thisID(),
4392 mt->targetLoc,
4393 mt->moveCount);
4394 if (mt->motionType == motionTypeDropObject)
4395 moveTaskDone = true;
4396 else
4397 g_vm->_mTaskList->_nextMT = it;
4398 }
4399
4400 CMassWeightIndicator::bRedraw = true; // tell the mass/weight indicators to refresh
4401
4402 break;
4403
4404 case motionTypeDropObjectOnObject:
4405
4406 if (isWorld(mt->o.indirectObject->IDParent())) {
4407 mt->direction = (mt->o.indirectObject->getLocation()
4408 - a->getLocation()).quickDir();
4409 if (a->_currentFacing != mt->direction)
4410 a->turn(mt->direction);
4411 else {
4412 // The actor will now be uniterruptable
4413 a->setActionPoints(2);
4414 mt->o.directObject->dropOn(
4415 a->thisID(),
4416 mt->o.indirectObject->thisID(),
4417 mt->moveCount);
4418 if (mt->motionType == motionTypeDropObjectOnObject)
4419 moveTaskDone = true;
4420 else
4421 g_vm->_mTaskList->_nextMT = it;
4422 }
4423 } else {
4424 // The actor will now be uniterruptable
4425 a->setActionPoints(2);
4426 mt->o.directObject->dropOn(
4427 a->thisID(),
4428 mt->o.indirectObject->thisID(),
4429 mt->moveCount);
4430 if (mt->motionType == motionTypeDropObjectOnObject)
4431 moveTaskDone = true;
4432 else
4433 g_vm->_mTaskList->_nextMT = it;
4434 }
4435
4436 CMassWeightIndicator::bRedraw = true; // tell the mass/weight indicators to refresh
4437
4438 break;
4439
4440 case motionTypeDropObjectOnTAI:
4441
4442 if (mt->flags & reset) {
4443 mt->direction = (mt->targetLoc - a->getLocation()).quickDir();
4444 mt->flags &= ~reset;
4445 }
4446
4447 if (a->_currentFacing != mt->direction)
4448 a->turn(mt->direction);
4449 else {
4450 // The actor will now be uniterruptable
4451 a->setActionPoints(2);
4452 mt->o.directObject->dropOn(
4453 a->thisID(),
4454 mt->o.TAI,
4455 mt->targetLoc);
4456 if (mt->motionType == motionTypeDropObjectOnTAI)
4457 moveTaskDone = true;
4458 else
4459 g_vm->_mTaskList->_nextMT = it;
4460 }
4461 break;
4462
4463 case motionTypeTwoHandedSwing:
4464 mt->twoHandedSwingAction();
4465 break;
4466
4467 case motionTypeOneHandedSwing:
4468 mt->oneHandedSwingAction();
4469 break;
4470
4471 case motionTypeFireBow:
4472 mt->fireBowAction();
4473 break;
4474
4475 case motionTypeCastSpell:
4476 mt->castSpellAction();
4477 break;
4478
4479 case motionTypeUseWand:
4480 mt->useWandAction();
4481 break;
4482
4483 case motionTypeTwoHandedParry:
4484 mt->twoHandedParryAction();
4485 break;
4486
4487 case motionTypeOneHandedParry:
4488 mt->oneHandedParryAction();
4489 break;
4490
4491 case motionTypeShieldParry:
4492 mt->shieldParryAction();
4493 break;
4494
4495 case motionTypeDodge:
4496 mt->dodgeAction();
4497 break;
4498
4499 case motionTypeAcceptHit:
4500 mt->acceptHitAction();
4501 break;
4502
4503 case motionTypeFallDown:
4504 mt->fallDownAction();
4505 break;
4506
4507 case motionTypeDie:
4508 if (mt->flags & reset) {
4509 if (a->isActionAvailable(actionDie)) {
4510 a->setAction(actionDie, 0);
4511 a->setInterruptablity(false);
4512 mt->flags &= ~reset;
4513 } else {
4514 moveTaskDone = true;
4515 a->setInterruptablity(true);
4516 if (!a->hasEffect(actorDisappearOnDeath)) {
4517 a->setAction(actionDead, 0);
4518 a->die();
4519 } else {
4520 a->die();
4521 a->dropInventory();
4522 a->deleteObjectRecursive();
4523 }
4524 }
4525 } else if (a->nextAnimationFrame()) {
4526 moveTaskDone = true;
4527 a->setInterruptablity(true);
4528 if (!a->hasEffect(actorDisappearOnDeath)) {
4529 a->setAction(actionDead, 0);
4530 a->die();
4531 } else {
4532 a->die();
4533 a->dropInventory();
4534 a->deleteObjectRecursive();
4535 }
4536 }
4537
4538 break;
4539 }
4540 if (moveTaskDone) mt->remove();
4541 }
4542 }
4543
4544 //-----------------------------------------------------------------------
4545 // Manages any object which has no supporting surface.
4546 // Returns true if object is still falling.
4547
freeFall(TilePoint & newPos,StandingTileInfo & sti)4548 bool MotionTask::freeFall(TilePoint &newPos, StandingTileInfo &sti) {
4549 int16 tHeight;
4550 TilePoint tPos;
4551 uint8 objCrossSection;
4552
4553 tHeight = tileSlopeHeight(newPos, object, &sti);
4554
4555 if (object->_data.objectFlags & objectFloating) return false;
4556
4557 velocity.u = (newPos.u - object->_data.location.u) * 2 / 3;
4558 velocity.v = (newPos.v - object->_data.location.v) * 2 / 3;
4559 velocity.z = (newPos.z - object->_data.location.z) * 2 / 3;
4560 // velocity.z = 0;
4561
4562 // If terrain is HIGHER (or even sligtly lower) than we are
4563 // currently at, then try climbing it.
4564
4565 if (tHeight >= newPos.z - gravity * 4) {
4566 supported:
4567 if (motionType != motionTypeWalk
4568 || tHeight <= newPos.z
4569 || !(flags & inWater)) {
4570 if (tHeight > newPos.z + kMaxStepHeight) {
4571 unstickObject(object);
4572 tHeight = tileSlopeHeight(newPos, object, &sti);
4573 }
4574 newPos.z = tHeight;
4575 // setObjectSurface( object, sti );
4576 return false;
4577 } else {
4578 motionType = motionTypeRise;
4579 immediateLocation.z = tHeight;
4580 object->move(newPos);
4581 return true;
4582 }
4583
4584 }
4585
4586 // Otherwise, begin a fall sequence...
4587 tPos = newPos;
4588
4589 // Attempt to solve cases where he gets stuck in falling,
4590 // by checking the contact of what he's about to fall on.
4591 if (tPos.z > tHeight) tPos.z--;
4592 // See if we fell on something.
4593 if (checkContact(object, tPos) == blockageNone) {
4594 falling:
4595 if (motionType != motionTypeWalk
4596 || newPos.z > gravity * 4
4597 || tHeight >= 0) {
4598 motionType = motionTypeThrown;
4599
4600 // newPos = tPos;
4601 object->move(tPos);
4602 return true;
4603 } else {
4604 newPos = tPos;
4605 return false;
4606 }
4607 }
4608
4609 // If we fall on something, reduce velocity due to impact.
4610 // Try a couple of probes to see if we can fall in
4611 // other directions.
4612 objCrossSection = object->proto()->crossSection;
4613
4614 tPos.u += objCrossSection;
4615 if (!checkBlocked(object, tPos)
4616 && !checkContact(object, tPos))
4617 goto falling;
4618
4619 tPos.u -= objCrossSection * 2;
4620 if (!checkBlocked(object, tPos)
4621 && !checkContact(object, tPos))
4622 goto falling;
4623
4624 tPos.u += objCrossSection;
4625 tPos.v += objCrossSection;
4626 if (!checkBlocked(object, tPos)
4627 && !checkContact(object, tPos))
4628 goto falling;
4629
4630 tPos.v -= objCrossSection * 2;
4631 if (!checkBlocked(object, tPos)
4632 && !checkContact(object, tPos))
4633 goto falling;
4634
4635 // There is no support for the object and there is no place to fall
4636 // so cheat and pretend this whole mess never happened.
4637 tPos = newPos;
4638
4639 tPos.u += objCrossSection;
4640 tHeight = tileSlopeHeight(tPos, object, &sti);
4641 if (tHeight <= tPos.z + kMaxStepHeight
4642 && tHeight >= tPos.z - gravity * 4) {
4643 newPos = tPos;
4644 goto supported;
4645 }
4646
4647 tPos.u -= objCrossSection * 2;
4648 tHeight = tileSlopeHeight(tPos, object, &sti);
4649 if (tHeight <= tPos.z + kMaxStepHeight
4650 && tHeight >= tPos.z - gravity * 4) {
4651 newPos = tPos;
4652 goto supported;
4653 }
4654
4655 tPos.u += objCrossSection;
4656 tPos.v += objCrossSection;
4657 tHeight = tileSlopeHeight(tPos, object, &sti);
4658 if (tHeight <= tPos.z + kMaxStepHeight
4659 && tHeight >= tPos.z - gravity * 4) {
4660 newPos = tPos;
4661 goto supported;
4662 }
4663
4664 tPos.v -= objCrossSection * 2;
4665 tHeight = tileSlopeHeight(tPos, object, &sti);
4666 if (tHeight <= tPos.z + kMaxStepHeight
4667 && tHeight >= tPos.z - gravity * 4) {
4668 newPos = tPos;
4669 goto supported;
4670 }
4671
4672 // If we STILL cannot find support for the object, change its
4673 // position and try again. This should be very rare.
4674 newPos.z--;
4675 object->move(newPos);
4676 unstickObject(object);
4677 newPos = object->getLocation();
4678 return true;
4679 }
4680
4681 //-----------------------------------------------------------------------
4682 // Calls the handling routine for each active motion task
4683
moveActors(int32 deltaTime)4684 void moveActors(int32 deltaTime) {
4685 MotionTask::updatePositions();
4686 }
4687
4688 //-----------------------------------------------------------------------
4689 // Check the actor's area to see if he is intersecting ladder terrain, and
4690 // if so, make him climb it.
4691
checkLadder(Actor * a,const TilePoint & loc)4692 bool checkLadder(Actor *a, const TilePoint &loc) {
4693 TileRegion actorTileReg;
4694 uint8 crossSection = a->proto()->crossSection,
4695 height = a->proto()->height;
4696 int16 mapNum = a->getMapNum();
4697 TileInfo *ti;
4698 TilePoint tileLoc;
4699 StandingTileInfo sti = {nullptr, nullptr, {0, 0, 0}, 0};
4700
4701 actorTileReg.min.u = (loc.u - crossSection) >> kTileUVShift;
4702 actorTileReg.min.v = (loc.v - crossSection) >> kTileUVShift;
4703 actorTileReg.max.u = (loc.u + crossSection + kTileUVMask) >> kTileUVShift;
4704 actorTileReg.max.v = (loc.v + crossSection + kTileUVMask) >> kTileUVShift;
4705 actorTileReg.min.z = actorTileReg.max.z = 0;
4706
4707 TileIterator iter(mapNum, actorTileReg);
4708
4709 for (ti = iter.first(&tileLoc, &sti);
4710 ti != NULL;
4711 ti = iter.next(&tileLoc, &sti)) {
4712 if (!(ti->combinedTerrainMask() & terrainLadder)) continue;
4713
4714 if (sti.surfaceHeight + ti->attrs.terrainHeight < loc.z
4715 || sti.surfaceHeight > loc.z + height)
4716 continue;
4717
4718 uint16 footPrintMask = 0xFFFF,
4719 ladderMask;
4720 TilePoint subTileLoc(
4721 tileLoc.u << kTileSubShift,
4722 tileLoc.v << kTileSubShift,
4723 0);
4724 TileRegion actorSubTileReg;
4725
4726 actorSubTileReg.min.u = (loc.u - crossSection) >> kSubTileShift;
4727 actorSubTileReg.min.v = (loc.v - crossSection) >> kSubTileShift;
4728 actorSubTileReg.max.u =
4729 (loc.u + crossSection + kSubTileMask) >> kSubTileShift;
4730 actorSubTileReg.max.v =
4731 (loc.v + crossSection + kSubTileMask) >> kSubTileShift;
4732
4733 if (actorSubTileReg.min.u >= subTileLoc.u)
4734 footPrintMask &=
4735 uMinMasks[actorSubTileReg.min.u - subTileLoc.u];
4736
4737 if (actorSubTileReg.min.v >= subTileLoc.v)
4738 footPrintMask &=
4739 vMinMasks[actorSubTileReg.min.v - subTileLoc.v];
4740
4741 if (actorSubTileReg.max.u < subTileLoc.u + kTileSubSize)
4742 footPrintMask &=
4743 uMaxMasks[actorSubTileReg.max.u - subTileLoc.u];
4744
4745 if (actorSubTileReg.max.v < subTileLoc.v + kTileSubSize)
4746 footPrintMask &=
4747 vMaxMasks[actorSubTileReg.max.v - subTileLoc.v];
4748
4749 ladderMask = ti->attrs.fgdTerrain == terrNumLadder
4750 ? ti->attrs.terrainMask
4751 : ~ti->attrs.terrainMask;
4752
4753 if (footPrintMask & ladderMask) {
4754 if (!(~ladderMask & 0xF000)) {
4755 a->_currentFacing = 7;
4756 a->move(
4757 TilePoint(
4758 (tileLoc.u << kTileUVShift)
4759 + kTileUVSize
4760 - crossSection,
4761 (tileLoc.v << kTileUVShift) + kTileUVSize / 2,
4762 loc.z));
4763 } else if (!(~ladderMask & 0x000F)) {
4764 a->_currentFacing = 3;
4765 a->move(
4766 TilePoint(
4767 (tileLoc.u << kTileUVShift) + crossSection,
4768 (tileLoc.v << kTileUVShift) + kTileUVSize / 2,
4769 loc.z));
4770 } else if (!(~ladderMask & 0x8888)) {
4771 a->_currentFacing = 1;
4772 a->move(
4773 TilePoint(
4774 (tileLoc.u << kTileUVShift) + kTileUVSize / 2,
4775 (tileLoc.v << kTileUVShift)
4776 + kTileUVSize
4777 - crossSection,
4778 loc.z));
4779 } else {
4780 a->_currentFacing = 3;
4781 a->move(
4782 TilePoint(
4783 (tileLoc.u << kTileUVShift) + kTileUVSize / 2,
4784 (tileLoc.v << kTileUVShift) + crossSection,
4785 loc.z));
4786 }
4787
4788 if (loc.z
4789 < tileSlopeHeight(a->getLocation(), a) + kMaxStepHeight)
4790 MotionTask::upLadder(*a);
4791 else
4792 MotionTask::downLadder(*a);
4793
4794 return true;
4795 }
4796 }
4797
4798 return false;
4799 }
4800
4801
pauseInterruptableMotions(void)4802 void pauseInterruptableMotions(void) {
4803 interruptableMotionsPaused = true;
4804 }
4805
resumeInterruptableMotions(void)4806 void resumeInterruptableMotions(void) {
4807 interruptableMotionsPaused = false;
4808 }
4809
4810 /* ===================================================================== *
4811 MotionTask list management functions
4812 * ===================================================================== */
4813
4814 //-----------------------------------------------------------------------
4815 // Initialize the motion task list
4816
initMotionTasks(void)4817 void initMotionTasks(void) {
4818 // Simply call the default MotionTaskList constructor
4819 //new (g_vm->_mTaskList) MotionTaskList;
4820 }
4821
saveMotionTasks(Common::OutSaveFile * outS)4822 void saveMotionTasks(Common::OutSaveFile *outS) {
4823 debugC(2, kDebugSaveload, "Saving MotionTasks");
4824
4825 outS->write("MOTN", 4);
4826 CHUNK_BEGIN;
4827 g_vm->_mTaskList->write(out);
4828 CHUNK_END;
4829 }
4830
loadMotionTasks(Common::InSaveFile * in,int32 chunkSize)4831 void loadMotionTasks(Common::InSaveFile *in, int32 chunkSize) {
4832 debugC(2, kDebugSaveload, "Loading MotionTasks");
4833
4834 // If there is no saved data, simply call the default constructor
4835 if (chunkSize == 0) {
4836 //new (g_vm->_mTaskList) MotionTaskList;
4837 return;
4838 }
4839
4840 // Reconstruct g_vm->_mTaskList from archived data
4841 g_vm->_mTaskList->read(in);
4842 }
4843
4844 //-----------------------------------------------------------------------
4845 // Cleanup the motion task list
4846
cleanupMotionTasks(void)4847 void cleanupMotionTasks(void) {
4848 // Simply call stackList's cleanup
4849 g_vm->_mTaskList->cleanup();
4850 }
4851
4852 } // end of namespace Saga2
4853