scummvm/engines/saga2/motion.cpp
2022-10-29 23:46:00 +02:00

4851 lines
135 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* aint32 with this program; if not, write to the Free Software
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#include "saga2/saga2.h"
#include "saga2/dispnode.h"
#include "saga2/tile.h"
#include "saga2/motion.h"
#include "saga2/tilemode.h"
#include "saga2/magic.h"
#include "saga2/spellbuk.h"
#include "saga2/contain.h"
#include "saga2/intrface.h"
namespace Saga2 {
// Turns on visual debugging aids
#define VISUAL1 0
/* ===================================================================== *
Globals
* ===================================================================== */
bool interruptableMotionsPaused;
/* ===================================================================== *
Test Functions
* ===================================================================== */
bool unstickObject(GameObject *obj);
int32 currentGamePerformance();
/* ===================================================================== *
Functions
* ===================================================================== */
/* Different motion types we want to simulate:
Transient motions
~~~~~~~~~~~~~~~~~
x Walk to Point
Walk to Object (moving)
x Run
Ballistic Motion
Jump up (vertical motion only)
Leap to X distance
Running Leap
x Fall off of cliff or height
Object Ballistic Motion
Climb
Climb up ladder
Climb up Rope
Climb up Ledge
Talk & Gesture
Give Item (requires cooperation)
Stoop to pick up item
Stoop to dodge
Fight
Cast Magic spell
Swing High
Swing Low
Parry
Lunge
Shoot Bow
Consume Food
x Cycle through arbitrary frames
x Forward
x Backward
x Random
x Ping-Pong
x Looped
x Single
Die
Persistent Motions
~~~~~~~~~~~~~~~~~~
Wait cycle / Twitch (not for FTA, but eventually)
Be Dead
Sleep
Sit
*/
/* ===================================================================== *
Motion Constants
* ===================================================================== */
const StaticTilePoint dirTable[8] = {
{ 2, 2, 0},
{ 0, 3, 0},
{-2, 2, 0},
{-3, 0, 0},
{-2, -2, 0},
{ 0, -3, 0},
{ 2, -2, 0},
{ 3, 0, 0}
};
// Incremental direction table
const StaticTilePoint incDirTable[8] = {
{ 1, 1, 0},
{ 0, 1, 0},
{-1, 1, 0},
{-1, 0, 0},
{-1, -1, 0},
{ 0, -1, 0},
{ 1, -1, 0},
{ 1, 0, 0}
};
extern uint16 uMaxMasks[4],
uMinMasks[4],
vMaxMasks[4],
vMinMasks[4];
extern SpellStuff *spellBook;
/* ===================================================================== *
PathMinder
* ===================================================================== */
int32 getPathFindIQ(GameObject *obj) {
int32 pfIQ = 50;
if (isActor(obj)) {
Actor *a = (Actor *)obj;
if (a == getCenterActor())
pfIQ = 400;
else if (isPlayerActor(a))
pfIQ = 300;
else {
if (objRoofRipped(obj))
pfIQ = 75;
else if (a->_disposition == 1)
pfIQ = 250;
else
pfIQ = 100;
if (g_vm->_rnd->getRandomNumber(9) == 5)
pfIQ += 200;
}
int32 p = clamp(50, currentGamePerformance(), 200);
pfIQ = (pfIQ * p) / 200;
}
return pfIQ;
}
/* ===================================================================== *
Utility functions
* ===================================================================== */
// This subroutine detects if the actor has landed on an active
// tile, and checks to see if the active tile's script should
// be triggered.
void setObjectSurface(GameObject *obj, StandingTileInfo &sti) {
ActiveItemID tagID = sti.surfaceTAG != nullptr
? sti.surfaceTAG->thisID()
: NoActiveItem;
if (!(sti.surfaceRef.flags & kTrTileSensitive))
tagID = NoActiveItem;
if (obj->_data.currentTAG != tagID) {
ObjectID objID = obj->thisID(),
enactorID = isActor(objID) ? objID : Nothing;
if (obj->_data.currentTAG != NoActiveItem) {
ActiveItem *oldTAG =
ActiveItem::activeItemAddress(obj->_data.currentTAG);
oldTAG->release(enactorID, objID);
obj->_data.currentTAG = NoActiveItem;
}
if (tagID != NoActiveItem) {
if (sti.surfaceTAG->trigger(enactorID, objID))
obj->_data.currentTAG = tagID;
}
}
}
inline int16 spinLeft(int16 dir, int16 amt = 1) {
return (dir + amt) & 7;
}
inline int16 spinRight(int16 dir, int16 amt = 1) {
return (dir - amt) & 7;
}
// Special code to avoid actors sticking in walls, which occasionally
// happens due to the point-sampled nature of the environment.
bool unstickObject(GameObject *obj) {
assert(isObject(obj) || isActor(obj));
TilePoint pos;
int16 mapNum;
bool outside;
mapNum = obj->getMapNum();
outside = objRoofID(obj, mapNum, obj->getLocation()) == 0;
if (checkBlocked(obj, obj->getLocation()) == kBlockageNone)
return false;
#if 1
#if DEBUG
WriteStatusF(9, "Unsticking");
#endif
// A stochastic unsticker, written by Talin
// Basically, it tightens the constraints each time a solution
// is found.
int32 radius = 256;
int16 objZ = obj->getLocation().z;
TilePoint bestPos;
for (int tries = 128; tries >= 0; tries--) {
int32 dx = g_vm->_rnd->getRandomNumber(radius * 2) - radius,
dy = g_vm->_rnd->getRandomNumber(radius * 2) - radius,
dz = g_vm->_rnd->getRandomNumber(radius * 2) - radius;
int16 tHeight;
// Compute the actual _data.location of the new point
pos = obj->getLocation() + TilePoint(dx, dy, dz);
// Get the surface height at that point
tHeight = tileSlopeHeight(pos, obj);
// If the surface height is too far away from the sample
// height, then ignore it.
if (tHeight > pos.z + kMaxStepHeight
|| tHeight < pos.z - kMaxStepHeight * 4) continue;
// Recompute the coordinate
dz = tHeight - objZ;
// If under the same roof, and no blockages...
if (outside == (objRoofID(obj, mapNum, pos) == 0)
&& checkBlocked(obj, pos) == kBlockageNone) {
int32 newRadius;
// Then this is the best one found so far.
// Set new radius to maximum of abs of the 3 coords, minus 1
// (Because we want solution to converge faster)
newRadius = MAX(MAX(ABS(dx), ABS(dy)), ABS(dz)) - 1;
if (newRadius < radius) {
radius = newRadius;
// Each time radius gets reduced, we try a few more times
// to find a better solution.
tries = radius * 2 + 8;
}
pos.z = tHeight;
bestPos = pos;
}
}
if (radius < 128) {
#if DEBUG
WriteStatusF(9, "Unstick Dist: %d", radius);
#endif
obj->move(bestPos);
return true;
}
#else
for (dist = 4; dist < 64; dist += 4) {
int level;
for (level = 0; level < dist / 2; level++) {
bool up = (level & 1) == 0;
height = 8 * (up ? level >> 1 : -1 - (level >> 1));
for (dir = 0; dir < 8; dir++) {
pos = obj->getLocation() + (dirTable[dir] * dist);
pos.z += height;
if (outside == (objRoofID(obj, mapNum, pos) == 0)
&& checkBlocked(obj, pos) == kBlockageNone) {
int16 tHeight;
tHeight = tileSlopeHeight(pos, obj);
if (tHeight <= pos.z + kMaxStepHeight
&& tHeight >= pos.z - kMaxStepHeight * 4) {
pos.z = tHeight;
obj->move(pos);
return true;
}
}
}
}
}
#endif
#if DEBUG
WriteStatusF(9, "Unstick Failed!");
#endif
return true;
}
// Calculates the direction of a missile based upon the velocity vector
uint8 missileDir(const TilePoint &vector) {
return (((ptToAngle(vector.u, vector.v) + 8) >> 4) - 2) & 0xF;
}
// Computes the frames needed to turn from one direction to another
uint8 computeTurnFrames(Direction fromDir, Direction toDir) {
Direction relDir = (toDir - fromDir) & 0x7;
return relDir <= 4 ? relDir : 8 - relDir;
}
/* ===================================================================== *
MotionTaskList member functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Initialize the MotionTaskList
MotionTaskList::MotionTaskList() {
_nextMT = _list.end();
}
MotionTaskList::MotionTaskList(Common::SeekableReadStream *stream) {
read(stream);
_nextMT = _list.end();
}
void MotionTaskList::read(Common::InSaveFile *in) {
int16 motionTaskCount;
// Retrieve the motion task count
motionTaskCount = in->readSint16LE();
for (int i = 0; i < motionTaskCount; i++) {
MotionTask *mt;
mt = new MotionTask;
_list.push_back(mt);
mt->read(in);
}
}
//-----------------------------------------------------------------------
// Return the number of bytes needed to archive the motion tasks
// in a buffer
int32 MotionTaskList::archiveSize() {
// Initilialize with sizeof motion task count
int32 size = sizeof(int16);
for (Common::List<MotionTask *>::iterator it = _list.begin(); it != _list.end(); ++it)
size += (*it)->archiveSize();
return size;
}
void MotionTaskList::write(Common::MemoryWriteStreamDynamic *out) {
int16 motionTaskCount = _list.size();
// Store the motion task count
out->writeSint16LE(motionTaskCount);
// Archive the active motion tasks
for (Common::List<MotionTask *>::iterator it = _list.begin(); it != _list.end(); ++it)
(*it)->write(out);
}
//-----------------------------------------------------------------------
// Cleanup the motion tasks
void MotionTaskList::cleanup() {
for (Common::List<MotionTask *>::iterator it = _list.begin(); it != _list.end(); ++it) {
abortPathFind(*it);
(*it)->_pathFindTask = nullptr;
delete *it;
}
_list.clear();
}
//-----------------------------------------------------------------------
// Get a new motion task, if there is one available, and initialize it.
MotionTask *MotionTaskList::newTask(GameObject *obj) {
MotionTask *mt = nullptr;
// Check see if there's already motion associated with this object.
for (Common::List<MotionTask *>::iterator it = _list.begin(); it != _list.end(); ++it) {
if ((*it)->_object == obj) {
mt = *it;
wakeUpThread(mt->_thread, kMotionInterrupted);
mt->_thread = NoThread;
break;
}
}
if (mt == nullptr) {
mt = new MotionTask;
mt->_object = obj;
mt->_motionType = mt->_prevMotionType = MotionTask::kMotionTypeNone;
mt->_pathFindTask = nullptr;
mt->_pathCount = -1;
mt->_flags = 0;
mt->_velocity = TilePoint(0, 0, 0);
mt->_immediateLocation = mt->_finalTarget = obj->getLocation();
mt->_thread = NoThread;
mt->_targetObj = nullptr;
mt->_targetTAG = nullptr;
mt->_spellObj = nullptr;
_list.push_back(mt);
if (isActor(obj))
((Actor *)obj)->_moveTask = mt;
}
obj->_data.objectFlags |= kObjectMoving;
return mt;
}
/* ===================================================================== *
MotionTask member functions
* ===================================================================== */
void MotionTask::read(Common::InSaveFile *in) {
ObjectID objectID;
// Restore the motion type and previous motion type
_motionType = in->readByte();
_prevMotionType = in->readByte();
// Restore the thread ID
_thread = in->readSint16LE();
// Restore the motion flags
_flags = in->readUint16LE();
// Get the object ID
objectID = in->readUint16LE();
// Convert the object ID to and object address
_object = objectID != Nothing
? GameObject::objectAddress(objectID)
: nullptr;
// If the object is an actor, plug this motion task into the actor
if (_object && isActor(_object))
((Actor *)_object)->_moveTask = this;
if (_motionType == kMotionTypeWalk
|| _prevMotionType == kMotionTypeWalk) {
// Restore the target _data.locations
_immediateLocation.load(in);
_finalTarget.load(in);
// If there is a tether restore it
if (_flags & kMfTethered) {
_tetherMinU = in->readSint16LE();
_tetherMinV = in->readSint16LE();
_tetherMaxU = in->readSint16LE();
_tetherMaxV = in->readSint16LE();
}
// Restore the direction
_direction = in->readByte();
// Restore the path index and path count
_pathIndex = in->readSint16LE();
_pathCount = in->readSint16LE();
_runCount = in->readSint16LE();
// Restore the action counter if needed
if (_flags & kMfAgitated)
actionCounter = in->readSint16LE();
// If there were valid path way points, restore those
if (_pathIndex >= 0 && _pathIndex < _pathCount) {
int16 wayPointIndex = _pathIndex;
while (wayPointIndex < _pathCount) {
_pathList[wayPointIndex].load(in);
wayPointIndex++;
}
}
// If this motion task previously had a path finding request
// it must be restarted
_pathFindTask = nullptr;
}
if (_motionType == kMotionTypeThrown || _motionType == kMotionTypeShot) {
// Restore the velocity
_velocity.load(in);
// Restore other ballistic motion variables
_steps = in->readSint16LE();
_uFrac = in->readSint16LE();
_vFrac = in->readSint16LE();
_uErrorTerm = in->readSint16LE();
_vErrorTerm = in->readSint16LE();
if (_motionType == kMotionTypeShot) {
ObjectID _targetObjID,
enactorID;
_targetObjID = in->readUint16LE();
_targetObj = _targetObjID
? GameObject::objectAddress(_targetObjID)
: nullptr;
enactorID = in->readUint16LE();
_o.enactor = enactorID != Nothing
? (Actor *)GameObject::objectAddress(enactorID)
: nullptr;
}
} else if (_motionType == kMotionTypeClimbUp
|| _motionType == kMotionTypeClimbDown) {
_immediateLocation.load(in);
} else if (_motionType == kMotionTypeJump) {
_velocity.load(in);
} else if (_motionType == kMotionTypeTurn) {
_direction = in->readByte();
} else if (_motionType == kMotionTypeGive) {
ObjectID id = in->readUint16LE();
_targetObj = id != Nothing
? GameObject::objectAddress(id)
: nullptr;
} else if (_motionType == kMotionTypeWait) {
actionCounter = in->readSint16LE();
} else if (_motionType == kMotionTypeUseObject
|| _motionType == kMotionTypeUseObjectOnObject
|| _motionType == kMotionTypeUseObjectOnTAI
|| _motionType == kMotionTypeUseObjectOnLocation
|| _motionType == kMotionTypeDropObject
|| _motionType == kMotionTypeDropObjectOnObject
|| _motionType == kMotionTypeDropObjectOnTAI) {
ObjectID directObjID = in->readUint16LE();
_o.directObject = directObjID != Nothing
? GameObject::objectAddress(directObjID)
: nullptr;
_direction = in->readByte();
if (_motionType == kMotionTypeUseObjectOnObject
|| _motionType == kMotionTypeDropObjectOnObject) {
ObjectID indirectObjID = in->readUint16LE();
_o.indirectObject = indirectObjID != Nothing
? GameObject::objectAddress(indirectObjID)
: nullptr;
} else {
if (_motionType == kMotionTypeUseObjectOnTAI
|| _motionType == kMotionTypeDropObjectOnTAI) {
ActiveItemID tai(in->readSint16LE());
_o.TAI = tai != NoActiveItem
? ActiveItem::activeItemAddress(tai)
: nullptr;
}
if (_motionType == kMotionTypeUseObjectOnLocation
|| _motionType == kMotionTypeDropObject
|| _motionType == kMotionTypeDropObjectOnTAI) {
_targetLoc.load(in);
}
}
} else if (_motionType == kMotionTypeUseTAI) {
ActiveItemID tai(in->readSint16LE());
_o.TAI = tai != NoActiveItem
? ActiveItem::activeItemAddress(tai)
: nullptr;
_direction = in->readByte();
} else if (_motionType == kMotionTypeTwoHandedSwing
|| _motionType == kMotionTypeOneHandedSwing
|| _motionType == kMotionTypeFireBow
|| _motionType == kMotionTypeCastSpell
|| _motionType == kMotionTypeUseWand) {
ObjectID _targetObjID;
// Restore the direction
_direction = in->readByte();
// Restore the combat motion type
_combatMotionType = in->readByte();
// Get the target object ID
_targetObjID = in->readUint16LE();
// Convert the target object ID to a pointer
_targetObj = _targetObjID != Nothing
? GameObject::objectAddress(_targetObjID)
: nullptr;
if (_motionType == kMotionTypeCastSpell) {
SpellID sid ;
ObjectID toid ;
ActiveItemID ttaid;
// restore the spell prototype
warning("MotionTask::read: Check SpellID size");
sid = (SpellID)in->readUint32LE();
_spellObj = sid != kNullSpell
? skillProtoFromID(sid)
: nullptr;
// restore object target
toid = in->readUint16LE();
_targetObj = toid != Nothing
? GameObject::objectAddress(toid)
: nullptr;
// restore TAG target
ttaid = in->readSint16LE();
_targetTAG = ttaid != NoActiveItem
? ActiveItem::activeItemAddress(ttaid)
: nullptr;
// restore _data.location target
_targetLoc.load(in);
}
// Restore the action counter
actionCounter = in->readSint16LE();
} else if (_motionType == kMotionTypeTwoHandedParry
|| _motionType == kMotionTypeOneHandedParry
|| _motionType == kMotionTypeShieldParry) {
ObjectID attackerID,
defensiveObjID;
// Restore the direction
_direction = in->readByte();
// Get the attacker's and defensive object's IDs
attackerID = in->readByte();
defensiveObjID = in->readByte();
// Convert IDs to pointers
_d.attacker = attackerID != Nothing
? (Actor *)GameObject::objectAddress(attackerID)
: nullptr;
_d.defensiveObj = defensiveObjID != Nothing
? GameObject::objectAddress(defensiveObjID)
: nullptr;
// Restore the defense flags
_d.defenseFlags = in->readByte();
// Restore the action counter
actionCounter = in->readSint16LE();
if (_motionType == kMotionTypeOneHandedParry) {
// Restore the combat sub-motion type
_combatMotionType = in->readByte();
}
} else if (_motionType == kMotionTypeDodge
|| _motionType == kMotionTypeAcceptHit
|| _motionType == kMotionTypeFallDown) {
ObjectID attackerID;
// Get the attacker's ID
attackerID = in->readUint16LE();
// Convert ID to pointer
_d.attacker = attackerID != Nothing
? (Actor *)GameObject::objectAddress(attackerID)
: nullptr;
// Restore the action counter
actionCounter = in->readSint16LE();
}
}
//-----------------------------------------------------------------------
// Return the number of bytes needed to archive this MotionTask
int32 MotionTask::archiveSize() {
int32 size = 0;
size = sizeof(_motionType)
+ sizeof(_prevMotionType)
+ sizeof(_thread)
+ sizeof(_flags)
+ sizeof(ObjectID); // object
if (_motionType == kMotionTypeWalk
|| _prevMotionType == kMotionTypeWalk) {
size += sizeof(_immediateLocation)
+ sizeof(_finalTarget);
if (_flags & kMfTethered) {
size += sizeof(_tetherMinU)
+ sizeof(_tetherMinV)
+ sizeof(_tetherMaxU)
+ sizeof(_tetherMaxV);
}
size += sizeof(_direction)
+ sizeof(_pathIndex)
+ sizeof(_pathCount)
+ sizeof(_runCount);
if (_flags & kMfAgitated)
size += sizeof(actionCounter);
if (_pathIndex >= 0 && _pathIndex < _pathCount)
size += sizeof(TilePoint) * (_pathCount - _pathIndex);
}
if (_motionType == kMotionTypeThrown || _motionType == kMotionTypeShot) {
size += sizeof(_velocity)
+ sizeof(_steps)
+ sizeof(_uFrac)
+ sizeof(_vFrac)
+ sizeof(_uErrorTerm)
+ sizeof(_vErrorTerm);
if (_motionType == kMotionTypeShot) {
size += sizeof(ObjectID) // _targetObj ID
+ sizeof(ObjectID); // enactor ID
}
} else if (_motionType == kMotionTypeClimbUp
|| _motionType == kMotionTypeClimbDown) {
size += sizeof(_immediateLocation);
} else if (_motionType == kMotionTypeJump) {
size += sizeof(_velocity);
} else if (_motionType == kMotionTypeTurn) {
size += sizeof(_direction);
} else if (_motionType == kMotionTypeGive) {
size += sizeof(ObjectID); // _targetObj ID
} else if (_motionType == kMotionTypeUseObject
|| _motionType == kMotionTypeUseObjectOnObject
|| _motionType == kMotionTypeUseObjectOnTAI
|| _motionType == kMotionTypeUseObjectOnLocation
|| _motionType == kMotionTypeDropObject
|| _motionType == kMotionTypeDropObjectOnObject
|| _motionType == kMotionTypeDropObjectOnTAI) {
size += sizeof(ObjectID)
+ sizeof(_direction);
if (_motionType == kMotionTypeUseObjectOnObject
|| _motionType == kMotionTypeDropObjectOnObject) {
size += sizeof(ObjectID);
} else {
if (_motionType == kMotionTypeUseObjectOnTAI
|| _motionType == kMotionTypeDropObjectOnTAI) {
size += sizeof(ActiveItemID);
}
if (_motionType == kMotionTypeUseObjectOnLocation
|| _motionType == kMotionTypeDropObject
|| _motionType == kMotionTypeDropObjectOnTAI) {
size += sizeof(_targetLoc);
}
}
} else if (_motionType == kMotionTypeUseTAI) {
size += sizeof(ActiveItemID)
+ sizeof(_direction);
} else if (_motionType == kMotionTypeTwoHandedSwing
|| _motionType == kMotionTypeOneHandedSwing
|| _motionType == kMotionTypeFireBow
|| _motionType == kMotionTypeCastSpell
|| _motionType == kMotionTypeUseWand) {
size += sizeof(_direction)
+ sizeof(_combatMotionType)
+ sizeof(ObjectID); // _targetObj
if (_motionType == kMotionTypeCastSpell) {
size += sizeof(SpellID); // _spellObj
size += sizeof(ObjectID); // _targetObj
size += sizeof(ActiveItemID); // _targetTAG
size += sizeof(_targetLoc); // _targetLoc
}
size += sizeof(actionCounter);
} else if (_motionType == kMotionTypeTwoHandedParry
|| _motionType == kMotionTypeOneHandedParry
|| _motionType == kMotionTypeShieldParry) {
size += sizeof(_direction)
+ sizeof(ObjectID) // attacker ID
+ sizeof(ObjectID) // defensiveObj ID
+ sizeof(_d.defenseFlags)
+ sizeof(actionCounter);
if (_motionType == kMotionTypeOneHandedParry)
size += sizeof(_combatMotionType);
} else if (_motionType == kMotionTypeDodge
|| _motionType == kMotionTypeAcceptHit
|| _motionType == kMotionTypeFallDown) {
size += sizeof(ObjectID) // attacker ID
+ sizeof(actionCounter);
}
return size;
}
void MotionTask::write(Common::MemoryWriteStreamDynamic *out) {
ObjectID objectID;
// Store the motion type and previous motion type
out->writeByte(_motionType);
out->writeByte(_prevMotionType);
// Store the thread ID
out->writeSint16LE(_thread);
// Store the motion flags
out->writeUint16LE(_flags);
// Convert the object pointer to an object ID
objectID = _object != nullptr ? _object->thisID() : Nothing;
// Store the object ID
out->writeUint16LE(objectID);
if (_motionType == kMotionTypeWalk
|| _prevMotionType == kMotionTypeWalk) {
// Store the target _data.locations
_immediateLocation.write(out);
_finalTarget.write(out);
// If there is a tether store it
if (_flags & kMfTethered) {
out->writeSint16LE(_tetherMinU);
out->writeSint16LE(_tetherMinV);
out->writeSint16LE(_tetherMaxU);
out->writeSint16LE(_tetherMaxV);
}
// Store the direction
out->writeByte(_direction);
// Store the path index and path count
out->writeSint16LE(_pathIndex);
out->writeSint16LE(_pathCount);
out->writeSint16LE(_runCount);
// Store the action counter if needed
if (_flags & kMfAgitated)
out->writeSint16LE(actionCounter);
// If there are valid path way points, store them
if (_pathIndex >= 0 && _pathIndex < _pathCount) {
int16 wayPointIndex = _pathIndex;
while (wayPointIndex < _pathCount) {
_pathList[wayPointIndex].write(out);
wayPointIndex++;
}
}
}
if (_motionType == kMotionTypeThrown || _motionType == kMotionTypeShot) {
// Store the velocity
_velocity.write(out);
// Store other ballistic motion variables
out->writeSint16LE(_steps);
out->writeSint16LE(_uFrac);
out->writeSint16LE(_vFrac);
out->writeSint16LE(_uErrorTerm);
out->writeSint16LE(_vErrorTerm);
if (_motionType == kMotionTypeShot) {
ObjectID _targetObjID,
enactorID;
_targetObjID = _targetObj != nullptr
? _targetObj->thisID()
: Nothing;
out->writeUint16LE(_targetObjID);
enactorID = _o.enactor != nullptr
? _o.enactor->thisID()
: Nothing;
out->writeUint16LE(enactorID);
}
} else if (_motionType == kMotionTypeClimbUp
|| _motionType == kMotionTypeClimbDown) {
_immediateLocation.write(out);
} else if (_motionType == kMotionTypeJump) {
_velocity.write(out);
} else if (_motionType == kMotionTypeTurn) {
out->writeByte(_direction);
} else if (_motionType == kMotionTypeGive) {
if (_targetObj != nullptr)
out->writeUint16LE(_targetObj->thisID());
else
out->writeUint16LE(Nothing);
} else if (_motionType == kMotionTypeUseObject
|| _motionType == kMotionTypeUseObjectOnObject
|| _motionType == kMotionTypeUseObjectOnTAI
|| _motionType == kMotionTypeUseObjectOnLocation
|| _motionType == kMotionTypeDropObject
|| _motionType == kMotionTypeDropObjectOnObject
|| _motionType == kMotionTypeDropObjectOnTAI) {
if (_o.directObject != nullptr)
out->writeUint16LE(_o.directObject->thisID());
else
out->writeUint16LE(Nothing);
out->writeByte(_direction);
if (_motionType == kMotionTypeUseObjectOnObject
|| _motionType == kMotionTypeDropObjectOnObject) {
if (_o.indirectObject != nullptr)
out->writeUint16LE(_o.indirectObject->thisID());
else
out->writeUint16LE(Nothing);
} else {
if (_motionType == kMotionTypeUseObjectOnTAI
|| _motionType == kMotionTypeDropObjectOnTAI) {
if (_o.TAI != nullptr)
out->writeSint16LE(_o.TAI->thisID());
else
out->writeSint16LE(NoActiveItem.val);
}
if (_motionType == kMotionTypeUseObjectOnLocation
|| _motionType == kMotionTypeDropObject
|| _motionType == kMotionTypeDropObjectOnTAI) {
_targetLoc.write(out);
}
}
} else if (_motionType == kMotionTypeUseTAI) {
if (_o.TAI != nullptr)
out->writeSint16LE(_o.TAI->thisID());
else
out->writeSint16LE(NoActiveItem.val);
out->writeByte(_direction);
} else if (_motionType == kMotionTypeTwoHandedSwing
|| _motionType == kMotionTypeOneHandedSwing
|| _motionType == kMotionTypeFireBow
|| _motionType == kMotionTypeCastSpell
|| _motionType == kMotionTypeUseWand) {
ObjectID _targetObjID;
// Store the direction
out->writeByte(_direction);
// Store the combat motion type
out->writeByte(_combatMotionType);
// Convert the target object pointer to an ID
_targetObjID = _targetObj != nullptr ? _targetObj->thisID() : Nothing;
// Store the target object ID
out->writeUint16LE(_targetObjID);
if (_motionType == kMotionTypeCastSpell) {
// Convert the spell object pointer to an ID
SpellID sid = _spellObj != nullptr
? _spellObj->getSpellID()
: kNullSpell;
ObjectID toid = _targetObj != nullptr
? _targetObj->thisID()
: Nothing;
ActiveItemID ttaid = _targetTAG != nullptr
? _targetTAG->thisID()
: NoActiveItem;
// Store the spell prototype
warning("MotionTask::write: Check SpellID size");
out->writeUint32LE(sid);
// Store object target
out->writeUint16LE(toid);
// Store TAG target
out->writeSint16LE(ttaid.val);
// Store _data.location target
_targetLoc.write(out);
}
// Store the action counter
out->writeSint16LE(actionCounter);
} else if (_motionType == kMotionTypeTwoHandedParry
|| _motionType == kMotionTypeOneHandedParry
|| _motionType == kMotionTypeShieldParry) {
ObjectID attackerID,
defensiveObjID;
// Store the direction
out->writeByte(_direction);
attackerID = _d.attacker != nullptr ? _d.attacker->thisID() : Nothing;
defensiveObjID = _d.defensiveObj != nullptr ? _d.defensiveObj->thisID() : Nothing;
// Store the attacker's and defensive object's IDs
out->writeUint16LE(attackerID);
out->writeUint16LE(defensiveObjID);
// Store the defense flags
out->writeByte(_d.defenseFlags);
// Store the action counter
out->writeSint16LE(actionCounter);
if (_motionType == kMotionTypeOneHandedParry) {
// Store the combat sub-motion type
out->writeByte(_combatMotionType);
}
} else if (_motionType == kMotionTypeDodge
|| _motionType == kMotionTypeAcceptHit
|| _motionType == kMotionTypeFallDown) {
ObjectID attackerID;
attackerID = _d.attacker != nullptr ? _d.attacker->thisID() : Nothing;
// Store the attacker's ID
out->writeUint16LE(attackerID);
// Store the action counter
out->writeSint16LE(actionCounter);
}
}
//-----------------------------------------------------------------------
// When a motion task is finished, call this function to delete it.
void MotionTask::remove(int16 returnVal) {
if (g_vm->_mTaskList->_nextMT != g_vm->_mTaskList->_list.end() && *(g_vm->_mTaskList->_nextMT) == this)
++g_vm->_mTaskList->_nextMT;
_object->_data.objectFlags &= ~kObjectMoving;
if (objObscured(_object))
_object->_data.objectFlags |= kObjectObscured;
else
_object->_data.objectFlags &= ~kObjectObscured;
if (isActor(_object)) {
Actor *a = (Actor *)_object;
a->_moveTask = nullptr;
a->_cycleCount = g_vm->_rnd->getRandomNumber(19);
// Make sure the actor is not left in a permanently
// uninterruptable state with no motion task to reset it
if (a->isPermanentlyUninterruptable())
a->setInterruptablity(true);
}
g_vm->_mTaskList->_list.remove(this);
abortPathFind(this);
_pathFindTask = nullptr;
wakeUpThread(_thread, returnVal);
}
//-----------------------------------------------------------------------
// Determine the immediate target _data.location
TilePoint MotionTask::getImmediateTarget() {
if (_immediateLocation != Nowhere)
return _immediateLocation;
Direction dir;
// If the wandering then simply go in the direction the actor is
// facing, else if avoiding a block go in the previously selected
// random direction
if (_flags & kMfAgitated)
dir = _direction;
else
dir = ((Actor *)_object)->_currentFacing;
return _object->_data.location
+ incDirTable[dir] * kTileUVSize;
}
//-----------------------------------------------------------------------
// This calculates the velocity for a ballistic motion
void MotionTask::calcVelocity(const TilePoint &vector, int16 turns) {
TilePoint veloc;
// Here is the formula for calculating the velocity Z vector
// Vz = - 1/2gt + 1/t(Dz - Sz)
// Vz = Velocity Z Coords
// g = gravity
// t = turns
// Dz = Destination Z Coords
// Sz = Source Z Coords
veloc.u = vector.u / turns;
veloc.v = vector.v / turns;
// This is used in ballistic motion to make up for rounding
_steps = turns;
_uFrac = vector.u % turns;
_vFrac = vector.v % turns;
_uErrorTerm = 0;
_vErrorTerm = 0;
veloc.z = ((kGravity * turns) >> 1) + vector.z / turns;
_velocity = veloc;
}
//-----------------------------------------------------------------------
// This initiates a motion task for turning an actor
void MotionTask::turn(Actor &obj, Direction dir) {
assert(dir < 8);
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&obj)) != nullptr) {
mt->_direction = dir;
mt->_motionType = kMotionTypeTurn;
mt->_flags = kMfReset;
}
}
//-----------------------------------------------------------------------
// This initiates a motion task for turning an actor
void MotionTask::turnTowards(Actor &obj, const TilePoint &where) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&obj)) != nullptr) {
mt->_direction = (where - obj.getLocation()).quickDir();
mt->_motionType = kMotionTypeTurn;
mt->_flags = kMfReset;
}
}
//-----------------------------------------------------------------------
// This initiates a motion task for going through the motions of giving
// an object to another actor
void MotionTask::give(Actor &actor, Actor &givee) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&actor)) != nullptr) {
mt->_targetObj = &givee;
mt->_motionType = kMotionTypeGive;
mt->_flags = kMfReset;
}
}
//-----------------------------------------------------------------------
// This initiates a motion task for throwing an object
void MotionTask::throwObject(GameObject &obj, const TilePoint &velocity) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&obj)) != nullptr) {
if (obj.isMissile()) obj._data.missileFacing = kMissileNoFacing;
mt->_velocity = velocity;
mt->_motionType = kMotionTypeThrown;
}
}
//-----------------------------------------------------------------------
// This function is intended to allow the character to throw an object
// to a specific point. It is in no way functional yet.
// REM: we need to know if we are indoors or outdoors!
// REM: we need to know celing height!!!
void MotionTask::throwObjectTo(GameObject &obj, const TilePoint &where) {
MotionTask *mt;
const int16 turns = 15;
if ((mt = g_vm->_mTaskList->newTask(&obj)) != nullptr) {
if (obj.isMissile()) obj._data.missileFacing = kMissileNoFacing;
mt->calcVelocity(where - obj.getLocation(), turns);
mt->_motionType = kMotionTypeThrown;
}
}
//-----------------------------------------------------------------------
// This function initiates a ballistic motion towards a specified target
// _data.location at a specified horizontal speed.
void MotionTask::shootObject(
GameObject &obj,
Actor &doer,
GameObject &target,
int16 speed) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&obj)) != nullptr) {
TilePoint _targetLoc = target.getLocation();
_targetLoc.z += target.proto()->height / 2;
TilePoint vector = _targetLoc - obj.getLocation();
int16 turns = MAX(vector.quickHDistance() / speed, 1);
if (isActor(&target)) {
Actor *targetActor = (Actor *)&target;
if (targetActor->_moveTask != nullptr) {
MotionTask *targetMotion = targetActor->_moveTask;
if (targetMotion->_motionType == kMotionTypeWalk)
vector += targetMotion->_velocity * turns;
}
}
mt->calcVelocity(vector, turns);
if (obj.isMissile())
obj._data.missileFacing = missileDir(mt->_velocity);
mt->_motionType = kMotionTypeShot;
mt->_o.enactor = &doer;
mt->_targetObj = &target;
}
}
//-----------------------------------------------------------------------
// Walk to a specific point, using pathfinding.
void MotionTask::walkTo(
Actor &actor,
const TilePoint &target,
bool run,
bool canAgitate) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&actor)) != nullptr) {
if (!mt->isReflex() && !actor.isImmobile()) {
unstickObject(&actor);
mt->_finalTarget = mt->_immediateLocation = target;
mt->_motionType = mt->_prevMotionType = kMotionTypeWalk;
mt->_pathCount = mt->_pathIndex = 0;
mt->_flags = kMfPathFind | kMfReset;
mt->_runCount = 12; // # of frames until we can run
if (run && actor.isActionAvailable(kActionRun))
mt->_flags |= kMfRequestRun;
if (canAgitate)
mt->_flags |= kMfAgitatable;
RequestPath(mt, getPathFindIQ(&actor));
}
}
}
//-----------------------------------------------------------------------
// Walk to a specific point without pathfinding
void MotionTask::walkToDirect(
Actor &actor,
const TilePoint &target,
bool run,
bool canAgitate) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&actor)) != nullptr) {
if (!mt->isReflex() && !actor.isImmobile()) {
// Abort any pending path finding task
abortPathFind(mt);
mt->_pathFindTask = nullptr;
unstickObject(&actor);
mt->_motionType = mt->_prevMotionType = kMotionTypeWalk;
mt->_finalTarget = mt->_immediateLocation = target;
mt->_pathCount = mt->_pathIndex = 0;
mt->_flags = kMfReset;
mt->_runCount = 12;
if (run && actor.isActionAvailable(kActionRun))
mt->_flags |= kMfRequestRun;
if (canAgitate)
mt->_flags |= kMfAgitatable;
}
}
}
//-----------------------------------------------------------------------
// Wander around
void MotionTask::wander(
Actor &actor,
bool run) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&actor)) != nullptr) {
if (!mt->isReflex() && !actor.isImmobile()) {
// Abort any pending path finding task
abortPathFind(mt);
mt->_pathFindTask = nullptr;
unstickObject(&actor);
mt->_motionType = mt->_prevMotionType = kMotionTypeWalk;
mt->_immediateLocation = Nowhere;
mt->_pathCount = mt->_pathIndex = 0;
mt->_flags = kMfReset | kMfWandering;
mt->_runCount = 12;
if (run && actor.isActionAvailable(kActionRun))
mt->_flags |= kMfRequestRun;
RequestWanderPath(mt, getPathFindIQ(&actor));
}
}
}
//-----------------------------------------------------------------------
// Wander around within a tether region
void MotionTask::tetheredWander(
Actor &actor,
const TileRegion &tetherReg,
bool run) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&actor)) != nullptr) {
if (!mt->isReflex() && !actor.isImmobile()) {
// Abort any pending path finding task
abortPathFind(mt);
mt->_pathFindTask = nullptr;
unstickObject(&actor);
mt->_motionType = mt->_prevMotionType = kMotionTypeWalk;
mt->_immediateLocation = Nowhere;
mt->_tetherMinU = tetherReg.min.u;
mt->_tetherMinV = tetherReg.min.v;
mt->_tetherMaxU = tetherReg.max.u;
mt->_tetherMaxV = tetherReg.max.v;
mt->_pathCount = mt->_pathIndex = 0;
mt->_flags = kMfReset | kMfWandering | kMfTethered;
mt->_runCount = 12;
if (run && actor.isActionAvailable(kActionRun))
mt->_flags |= kMfRequestRun;
RequestWanderPath(mt, getPathFindIQ(&actor));
}
}
}
//-----------------------------------------------------------------------
// Create a climb up ladder motion task.
void MotionTask::upLadder(Actor &actor) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&actor)) != nullptr) {
if (mt->_motionType != kMotionTypeClimbUp) {
mt->_motionType = kMotionTypeClimbUp;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Create a climb down ladder motion task.
void MotionTask::downLadder(Actor &actor) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&actor)) != nullptr) {
if (mt->_motionType != kMotionTypeClimbDown) {
mt->_motionType = kMotionTypeClimbDown;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Create a talk motion task.
void MotionTask::talk(Actor &actor) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&actor)) != nullptr) {
if (mt->_motionType != kMotionTypeTalk) {
mt->_motionType = kMotionTypeTalk;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Begin a jump. REM: This should probably have a parameter for jumping
// forward, backward, etc.
void MotionTask::jump(Actor &actor) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&actor)) != nullptr) {
if (mt->_motionType != kMotionTypeThrown) {
mt->_velocity.z = 10;
mt->_motionType = kMotionTypeJump;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Don't move -- simply eat some time
void MotionTask::wait(Actor &a) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeWait) {
mt->_motionType = kMotionTypeWait;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Use an object
void MotionTask::useObject(Actor &a, GameObject &dObj) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeUseObject) {
mt->_motionType = kMotionTypeUseObject;
mt->_o.directObject = &dObj;
mt->_flags = kMfReset;
if (isPlayerActor(&a)) mt->_flags |= kMfPrivledged;
}
}
}
//-----------------------------------------------------------------------
// Use one object on another
void MotionTask::useObjectOnObject(
Actor &a,
GameObject &dObj,
GameObject &target) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeUseObjectOnObject) {
mt->_motionType = kMotionTypeUseObjectOnObject;
mt->_o.directObject = &dObj;
mt->_o.indirectObject = &target;
mt->_flags = kMfReset;
if (isPlayerActor(&a)) mt->_flags |= kMfPrivledged;
}
}
}
//-----------------------------------------------------------------------
// Use an object on a TAI
void MotionTask::useObjectOnTAI(
Actor &a,
GameObject &dObj,
ActiveItem &target) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeUseObjectOnTAI) {
mt->_motionType = kMotionTypeUseObjectOnTAI;
mt->_o.directObject = &dObj;
mt->_o.TAI = &target;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Use on object on a TilePoint
void MotionTask::useObjectOnLocation(
Actor &a,
GameObject &dObj,
const Location &target) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeUseObjectOnLocation) {
mt->_motionType = kMotionTypeUseObjectOnLocation;
mt->_o.directObject = &dObj;
mt->_targetLoc = target;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Use a TAI
void MotionTask::useTAI(Actor &a, ActiveItem &dTAI) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeUseTAI) {
mt->_motionType = kMotionTypeUseTAI;
mt->_o.TAI = &dTAI;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Drop an object
void MotionTask::dropObject(Actor &a,
GameObject &dObj,
const Location &loc,
int16 num) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeDropObject) {
mt->_motionType = kMotionTypeDropObject;
mt->_o.directObject = &dObj;
mt->_targetLoc = loc;
mt->_flags = kMfReset;
mt->moveCount = num;
}
}
}
//-----------------------------------------------------------------------
// Drop one object on another
void MotionTask::dropObjectOnObject(
Actor &a,
GameObject &dObj,
GameObject &target,
int16 num) {
MotionTask *mt;
// If actor is dropping object on himself, and object is already
// in actor's container then consider it a "use" (if the object
// is of the correct type).
if (isActor(&target)
&& isPlayerActor((Actor *)&target)
&& dObj.IDParent() == target.thisID()
&& !(dObj.proto()->containmentSet() & ProtoObj::kIsContainer)) {
useObject(a, dObj);
return;
}
// Otherwise, drop it on the object
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeDropObjectOnObject) {
mt->_motionType = kMotionTypeDropObjectOnObject;
mt->_o.directObject = &dObj;
mt->_o.indirectObject = &target;
mt->_flags = kMfReset;
mt->moveCount = num;
}
}
}
//-----------------------------------------------------------------------
// Drop an object on a TAI
void MotionTask::dropObjectOnTAI(
Actor &a,
GameObject &dObj,
ActiveItem &target,
const Location &loc) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeDropObjectOnTAI) {
mt->_motionType = kMotionTypeDropObjectOnTAI;
mt->_o.directObject = &dObj;
mt->_o.TAI = &target;
mt->_targetLoc = loc;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Determine if this MotionTask is a reflex ( motion over which an actor
// has no control )
bool MotionTask::isReflex() {
return _motionType == kMotionTypeThrown
|| _motionType == kMotionTypeFall
|| _motionType == kMotionTypeLand
|| _motionType == kMotionTypeAcceptHit
|| _motionType == kMotionTypeFallDown
|| _motionType == kMotionTypeDie;
}
// Offensive combat actions
//-----------------------------------------------------------------------
// Initiate a two-handed swing
void MotionTask::twoHandedSwing(Actor &a, GameObject &target) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeTwoHandedSwing) {
mt->_motionType = kMotionTypeTwoHandedSwing;
mt->_targetObj = &target;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Initiate a one-handed swing
void MotionTask::oneHandedSwing(Actor &a, GameObject &target) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeOneHandedSwing) {
mt->_motionType = kMotionTypeOneHandedSwing;
mt->_targetObj = &target;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Initiate a fire bow motion
void MotionTask::fireBow(Actor &a, GameObject &target) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeFireBow) {
mt->_motionType = kMotionTypeFireBow;
mt->_targetObj = &target;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Initiate a cast spell motion
void MotionTask::castSpell(Actor &a, SkillProto &spell, GameObject &target) {
MotionTask *mt;
motionTypes type =
(spellBook[spell.getSpellID()].getManaType() == ksManaIDSkill) ?
kMotionTypeGive :
kMotionTypeCastSpell;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != type) {
mt->_motionType = type;
mt->_spellObj = &spell;
mt->_targetObj = &target;
mt->_flags = kMfReset;
mt->_direction = (mt->_targetObj->getLocation() - a.getLocation()).quickDir();
if (isPlayerActor(&a)) mt->_flags |= kMfPrivledged;
}
}
}
void MotionTask::castSpell(Actor &a, SkillProto &spell, Location &target) {
MotionTask *mt;
motionTypes type =
(spellBook[spell.getSpellID()].getManaType() == ksManaIDSkill) ?
kMotionTypeGive :
kMotionTypeCastSpell;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != type) {
mt->_motionType = type;
mt->_spellObj = &spell;
mt->_targetLoc = target; //target;
mt->_flags = kMfReset | kMfLocTarg;
mt->_direction = (target - a.getLocation()).quickDir();
if (isPlayerActor(&a)) mt->_flags |= kMfPrivledged;
}
}
}
void MotionTask::castSpell(Actor &a, SkillProto &spell, ActiveItem &target) {
MotionTask *mt;
motionTypes type =
(spellBook[spell.getSpellID()].getManaType() == ksManaIDSkill) ?
kMotionTypeGive :
kMotionTypeCastSpell;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != type) {
Location loc;
assert(target._data.itemType == kActiveTypeInstance);
mt->_motionType = type;
mt->_spellObj = &spell;
mt->_targetTAG = &target;
loc = Location(
target._data.instance.u << kTileUVShift,
target._data.instance.v << kTileUVShift,
target._data.instance.h,
a.world()->thisID());
mt->_targetLoc = loc; //target;
mt->_flags = kMfReset | kMfTAGTarg;
mt->_direction = (loc - a.getLocation()).quickDir();
if (isPlayerActor(&a)) mt->_flags |= kMfPrivledged;
}
}
}
//-----------------------------------------------------------------------
// Initiate a use wand motion
void MotionTask::useWand(Actor &a, GameObject &target) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeUseWand) {
mt->_motionType = kMotionTypeUseWand;
mt->_targetObj = &target;
mt->_flags = kMfReset;
}
}
}
// Defensive combat actions
//-----------------------------------------------------------------------
// Initiate a two-handed parry
void MotionTask::twoHandedParry(
Actor &a,
GameObject &weapon,
Actor &opponent) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeTwoHandedParry) {
mt->_motionType = kMotionTypeTwoHandedParry;
mt->_d.attacker = &opponent;
mt->_d.defensiveObj = &weapon;
}
mt->_flags = kMfReset;
mt->_d.defenseFlags = 0;
}
}
//-----------------------------------------------------------------------
// Initiate a one-handed parry
void MotionTask::oneHandedParry(
Actor &a,
GameObject &weapon,
Actor &opponent) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeOneHandedParry) {
mt->_motionType = kMotionTypeOneHandedParry;
mt->_d.attacker = &opponent;
mt->_d.defensiveObj = &weapon;
}
mt->_flags = kMfReset;
mt->_d.defenseFlags = 0;
}
}
//-----------------------------------------------------------------------
// Initiate a shield parry
void MotionTask::shieldParry(
Actor &a,
GameObject &shield,
Actor &opponent) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeShieldParry) {
mt->_motionType = kMotionTypeShieldParry;
mt->_d.attacker = &opponent;
mt->_d.defensiveObj = &shield;
}
mt->_flags = kMfReset;
mt->_d.defenseFlags = 0;
}
}
//-----------------------------------------------------------------------
// Initiate a dodge
void MotionTask::dodge(Actor &a, Actor &opponent) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeDodge) {
mt->_motionType = kMotionTypeDodge;
mt->_d.attacker = &opponent;
}
mt->_flags = kMfReset;
mt->_d.defenseFlags = 0;
}
}
// Other combat actions
//-----------------------------------------------------------------------
// Initiate an accept hit motion
void MotionTask::acceptHit(Actor &a, Actor &opponent) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeAcceptHit) {
mt->_motionType = kMotionTypeAcceptHit;
mt->_d.attacker = &opponent;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Initiate a fall down motion
void MotionTask::fallDown(Actor &a, Actor &opponent) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeFallDown) {
mt->_motionType = kMotionTypeFallDown;
mt->_d.attacker = &opponent;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Initiate a die motion
void MotionTask::die(Actor &a) {
MotionTask *mt;
if ((mt = g_vm->_mTaskList->newTask(&a)) != nullptr) {
if (mt->_motionType != kMotionTypeDie) {
mt->_motionType = kMotionTypeDie;
mt->_flags = kMfReset;
}
}
}
//-----------------------------------------------------------------------
// Determine if this MotionTask is a defensive motion
bool MotionTask::isDefense() {
return _motionType == kMotionTypeOneHandedParry
|| _motionType == kMotionTypeTwoHandedParry
|| _motionType == kMotionTypeShieldParry
|| _motionType == kMotionTypeDodge;
}
//-----------------------------------------------------------------------
// Determine if this MotionTask is an offensive motion
bool MotionTask::isAttack() {
return isMeleeAttack()
|| _motionType == kMotionTypeFireBow
|| _motionType == kMotionTypeCastSpell
|| _motionType == kMotionTypeUseWand;
}
//-----------------------------------------------------------------------
// Determine if this MotionTask is an offensive melee motion
bool MotionTask::isMeleeAttack() {
return _motionType == kMotionTypeOneHandedSwing
|| _motionType == kMotionTypeTwoHandedSwing;
}
//-----------------------------------------------------------------------
// Determine if this MotionTask is a walk motion
bool MotionTask::isWalk() {
return _prevMotionType == kMotionTypeWalk;
}
//-----------------------------------------------------------------------
// Return the wandering tether region
TileRegion MotionTask::getTether() {
TileRegion reg;
if (_flags & kMfTethered) {
reg.min = TilePoint(_tetherMinU, _tetherMinV, 0);
reg.max = TilePoint(_tetherMaxU, _tetherMaxV, 0);
} else {
reg.min = Nowhere;
reg.max = Nowhere;
}
return reg;
}
//-----------------------------------------------------------------------
// If the target has changed position since the last path find started,
// then call this function.
void MotionTask::changeTarget(const TilePoint &newPos, bool run) {
if (_prevMotionType == kMotionTypeWalk) {
uint16 oldFlags = _flags;
abortPathFind(this);
_finalTarget = _immediateLocation = newPos;
_pathCount = _pathIndex = 0;
_flags = kMfPathFind | kMfReset;
if (oldFlags & kMfAgitatable)
_flags |= kMfAgitatable;
// Set run flag if requested
if (run
// Check if actor capable of running...
&& ((Actor *)_object)->isActionAvailable(kActionRun))
_flags |= kMfRequestRun;
else
_flags &= ~kMfRequestRun;
RequestPath(this, getPathFindIQ(_object));
}
}
//-----------------------------------------------------------------------
// If the target has changed position since the walk/run started, then
// call this function.
void MotionTask::changeDirectTarget(const TilePoint &newPos, bool run) {
if (_prevMotionType == kMotionTypeWalk) {
_prevMotionType = kMotionTypeWalk;
_finalTarget = _immediateLocation = newPos;
// Reset motion task
_flags |= kMfReset;
_flags &= ~kMfPathFind;
// Set run flag if requested
if (run
// Check if actor capable of running...
&& ((Actor *)_object)->isActionAvailable(kActionRun))
_flags |= kMfRequestRun;
else
_flags &= ~kMfRequestRun;
}
}
// Cancel actor movement if walking...
void MotionTask::finishWalk() {
// If the actor is in a running state
if (_motionType == kMotionTypeWalk) {
remove();
// If there is currently a path finding request, abort it.
/* abortPathFind( this );
// Simply set actor's target _data.location to "here".
_finalTarget = _immediateLocation = _object->getLocation();
_pathList[0] = _finalTarget;
flags = kMfReset;
_pathCount = 0;
_pathIndex = 0;*/
}
}
// Cancel actor movement if talking...
void MotionTask::finishTalking() {
if (_motionType == kMotionTypeTalk) {
if (isActor(_object)) {
Actor *a = (Actor *)_object;
if (a->_currentAnimation != kActionStand)
a->setAction(kActionStand, 0);
}
remove();
}
}
//-----------------------------------------------------------------------
// Handle actions for characters and objects in free-fall
void MotionTask::ballisticAction() {
TilePoint totalVelocity, // total velocity vector
stepVelocity, // sub-velocity vector
location,
newPos;
int16 minDim,
vectorSteps;
GameObject *obj = _object;
ProtoObj *proto = obj->proto();
if (isActor(obj)) {
// Before anything else make sure the actor is in an
// uninterruptable state.
((Actor *)obj)->setInterruptablity(false);
}
// Add the force of gravity to the acceleration.
if (!(_flags & kMfInWater)) {
_velocity.z -= kGravity;
} else {
_velocity.u = _velocity.v = 0;
_velocity.z = -kGravity;
}
location = obj->getLocation();
// WriteStatusF( 6, "%d %d %d", _data.location.u, _data.location.v, _data.location.z );
// Because we live in a point-sampled universe, we need to make
// sure that objects which are moving extremely fast don't
// undersample the terrain. We do this by breaking the velocity
// vector into smaller vectors, and handling them individually.
totalVelocity = _velocity;
// Make Up For Rounding Errors In ThrowTo
if (_uFrac) {
_uErrorTerm += ABS(_uFrac);
if (_uErrorTerm >= _steps) {
_uErrorTerm -= _steps;
if (_uFrac > 0)
totalVelocity.u++;
else
totalVelocity.u--;
}
}
if (_vFrac) {
_vErrorTerm += ABS(_vFrac);
if (_vErrorTerm >= _steps) {
_vErrorTerm -= _steps;
if (_vFrac > 0)
totalVelocity.v++;
else
totalVelocity.v--;
}
}
// Determine which dimension is smaller, width or height.
minDim = MAX<int16>(MIN<int16>(proto->height, proto->crossSection * 2), 1);
// "vectorSteps" is the number of increments we are going to process
// this vector.
vectorSteps = ((totalVelocity.magnitude() - 1) / minDim) + 1;
if (isActor(obj) && _velocity.magnitude() > 16) {
Actor *a = (Actor *)obj;
if (a->isActionAvailable(kActionFreeFall))
a->setAction(kActionFreeFall, 0);
}
for (int i = 0; i < vectorSteps; i++) {
int16 stepsLeft = vectorSteps - i;
GameObject *collisionObject;
// REM: This would be better as a rounded division...
// Compute the small velocity vector for this increment,
// and then subtract it from the total _velocity.
stepVelocity = totalVelocity / stepsLeft;
totalVelocity -= stepVelocity;
// Compute the new position of the object
newPos = location + stepVelocity;
// See if the object ran into anything. If it didn't, then
// update the coord and try again.
if (isActor(obj)) {
Actor *a = (Actor *)obj;
if (a == getCenterActor() && checkLadder(a, newPos))
return;
}
if (checkContact(obj, newPos, &collisionObject) == false) {
location = newPos;
} else {
TilePoint oldVelocity = _velocity;
if (_motionType == kMotionTypeShot && collisionObject != nullptr) {
// If this motion is for a shot arrow and we did not
// collide with our target object just continue the
// motion as if there was no collision.
if (collisionObject == _targetObj) {
if (_object->strike(
_o.enactor->thisID(),
_targetObj->thisID())) {
// The arrow struck, so delete the arrow and
// end this motion
remove();
_object->deleteObject();
return;
} else {
// If the arrow failed to strike continue the
// arrows flight as if there was no collision.
_targetObj = nullptr;
location = newPos;
continue;
}
} else {
location = newPos;
continue;
}
}
if (unstickObject(obj)) return;
// "probe" is a bitfield which will indicate which
// directions the obstructions lie in.
int16 probe = 0;
// Probe along each of the three coordinate axes
if (checkBlocked(obj,
TilePoint(newPos.u,
obj->_data.location.v,
obj->_data.location.z))) {
probe |= (1 << 0);
}
if (checkBlocked(obj,
TilePoint(obj->_data.location.u,
newPos.v,
obj->_data.location.z))) {
probe |= (1 << 1);
}
if (checkContact(obj,
TilePoint(obj->_data.location.u,
obj->_data.location.v,
newPos.z))) {
probe |= (1 << 2);
}
// If there are no obstructions along the orthogonal
// directions, then we must have hit a corner. In this
// case, we just bounce directly backwards.
if (probe == 0) {
_velocity = -_velocity / 2;
totalVelocity = -totalVelocity / 2;
} else {
if (probe & (1 << 0)) { // If struck wall in U direction
_velocity.u = -_velocity.u / 2;
totalVelocity.u = -totalVelocity.u / 2;
} else {
_velocity.u = (_velocity.u * 2) / 3;
totalVelocity.u = (totalVelocity.u * 2) / 3;
}
if (probe & (1 << 1)) { // If struck wall in V direction
_velocity.v = -_velocity.v / 2;
totalVelocity.v = -totalVelocity.v / 2;
} else {
_velocity.v = (_velocity.v * 2) / 3;
totalVelocity.v = (totalVelocity.v * 2) / 3;
}
if (probe & (1 << 2)) { // If struct wall in Z direction
_velocity.z = -_velocity.z / 2;
totalVelocity.z = -totalVelocity.z / 2;
} else {
_velocity.z = (_velocity.z * 2) / 3;
totalVelocity.z = (totalVelocity.z * 2) / 3;
}
}
_uFrac = _vFrac = 0;
if (_motionType == kMotionTypeShot && obj->isMissile())
obj->_data.missileFacing = missileDir(_velocity);
// If the ballistic object is an actor hitting the
// ground, then instead of bouncing, we'll just have
// them absorb the impact
if (isActor(obj) && probe & (1 << 2)) {
StandingTileInfo sti;
if (freeFall(location, sti) == false) {
int16 velocityMagnitude = oldVelocity.magnitude();
fallingDamage(obj, velocityMagnitude);
obj->move(location);
if (!((Actor *)obj)->isDead()) {
_motionType = velocityMagnitude <= 16
? kMotionTypeLand
: kMotionTypeLandBadly;
_flags |= kMfReset;
setObjectSurface(obj, sti);
} else {
setObjectSurface(obj, sti);
remove();
}
return;
} else {
setObjectSurface(obj, sti);
// If the object is falling, then
// freeFall will have already modified the
// object's _data.location
return;
}
} else if (_velocity.u < 2 && _velocity.u > -2
&& _velocity.v < 2 && _velocity.v > -2
&& _velocity.z < 2 && _velocity.z > -2) {
StandingTileInfo sti;
// If the reduced velocity after impact is
// very small, then we'll assume that the object
// has come to rest.
if (freeFall(location, sti) == false) {
obj->move(location);
remove(); // delete motion task
setObjectSurface(obj, sti);
return;
}
setObjectSurface(obj, sti);
return;
}
// Otherwise, since we struck a wall at high velocity,
// we just drop this small velocity vector from our
// calculations, and continue with the next iteration
// of the loop.
}
}
obj->move(location);
}
//-----------------------------------------------------------------------
// Get the coordinates of the next waypoint.
bool MotionTask::nextWayPoint() {
// If the pathfinder hasn't managed to determine waypoints
// yet, then return failure.
// if ( ( _flags & kMfPathFind ) && _pathCount < 0 ) return false;
// If there are still waypoints in the path list, then
// retrieve the next waypoint.
if ((_flags & (kMfPathFind | kMfWandering)) && _pathIndex < _pathCount) {
TilePoint wayPointVector(0, 0, 0);
if (_pathIndex > 0)
wayPointVector = _immediateLocation - _object->_data.location;
if (wayPointVector.quickHDistance() == 0)
// Next vertex in path polyline
_immediateLocation = _pathList[_pathIndex++];
else
return false;
} else {
if (_flags & kMfWandering) {
_immediateLocation = Nowhere;
if (_pathFindTask == nullptr)
RequestWanderPath(this, getPathFindIQ(_object));
} else if (_flags & kMfAgitated) {
_immediateLocation = Nowhere;
} else {
// If we've gone off the end of the path list,
// and we're not at the target yet, request more waypoints then
// use dumb pathfinding until the pathfinder finishes it's task.
if ((_finalTarget - _object->_data.location).quickHDistance() > 0
|| ABS(_finalTarget.z - _object->_data.location.z) > kMaxStepHeight) {
// If no pathfind in progress
if ((_flags & kMfPathFind)
&& !(_flags & kMfFinalPath)
&& _pathFindTask == nullptr)
RequestPath(this, getPathFindIQ(_object));
// Set the immediate target to the final target,
_immediateLocation = _finalTarget;
}
// else we're close enough to call it quits.
else return false;
}
}
return true;
}
//-----------------------------------------------------------------------
// Test to see if actor can walk in a given direction
bool MotionTask::checkWalk(
int16 dir,
int16 speed,
int16 stepUp,
TilePoint &pos) {
TilePoint newPos;
// Check the terrain in various directions.
// Check in the forward direction first, at various heights
newPos = _object->_data.location + (dirTable[dir] * speed) / 2;
newPos.z = _object->_data.location.z + stepUp;
if (checkWalkable(_object, newPos)) return false;
// movementDirection = direction;
pos = newPos;
return true;
}
//-----------------------------------------------------------------------
// Handle actions for characters walking and running
void MotionTask::walkAction() {
enum WalkType {
kWalkNormal = 0,
kWalkSlow,
kWalkRun,
kWalkStairs
};
TilePoint immediateTarget = getImmediateTarget(),
newPos,
targetVector;
int16 targetDist = 0;
int16 movementDirection,
directionAngle;
int16 moveBlocked,
speed = kWalkSpeed,
speedScale = 2;
Actor *a;
ActorAppearance *aa;
StandingTileInfo sti;
bool moveTaskWaiting = false,
moveTaskDone = false;
WalkType walkType = kWalkNormal;
assert(isActor(_object));
a = (Actor *)_object;
aa = a->_appearance;
if (a->isImmobile()) {
remove(kMotionWalkBlocked);
return;
}
// Make sure that the actor is interruptable
a->setInterruptablity(true);
// Set the speed of movement based on whether we are walking
// or running. Running only occurs after we have accelerated.
if (_flags & kMfRequestRun
&& _runCount == 0
&& !(_flags & (kMfInWater | kMfOnStairs))) {
speed = kRunSpeed;
speedScale = 4;
walkType = kWalkRun;
// If we can see this actor, and the actor's run frames
// have not been loaded, then downgrade this action to
// a walk (but request the run frames).
if (aa && !aa->isBankLoaded(kSprRunBankNum)) {
walkType = kWalkNormal;
aa->requestBank(kSprRunBankNum);
}
}
// If for some reason we cannot run at this time, then
// set up for a walk instead.
if (walkType != kWalkRun) {
if (!(_flags & kMfOnStairs)) {
if (!(_flags & kMfInWater)) {
speed = kWalkSpeed;
speedScale = 2;
walkType = kWalkNormal;
} else {
speed = kSlowWalkSpeed;
speedScale = 1;
walkType = kWalkSlow;
// reset run count if actor walking slowly
_runCount = MAX<int16>(_runCount, 8);
}
// If we can see this actor, and this actor's walk
// frames have not been loaded, then downgrade this
// action to a stand (i.e. do nothing).
if (aa && !aa->isBankLoaded(kSprWalkBankNum)) {
aa->requestBank(kSprWalkBankNum);
return;
}
} else {
speed = kSlowWalkSpeed;
speedScale = 1;
walkType = kWalkStairs;
// reset run count if actor walking on stairs
_runCount = MAX<int16>(_runCount, 8);
}
}
if ((_flags & kMfAgitated)
&& --actionCounter <= 0) {
_flags &= ~kMfAgitated;
_flags |= kMfPathFind | kMfReset;
}
for (;;) {
// The "reset" flag indicates that the final target has
// changed since the last time this routine was called.
if (!(_flags & kMfReset)) {
// Compute the vector and distance of the current
// position to the next "immediate" target.
targetVector = immediateTarget - _object->_data.location;
targetDist = targetVector.quickHDistance();
// If we're not already there, then proceed towards
// the target.
if (targetDist > 0 || ABS(targetVector.z) > kMaxStepHeight)
break;
}
if (nextWayPoint() == false) {
// If no waypoint could be found and this motion task has
// a path find request, then go into "wait" mode.
if (_pathFindTask)
moveTaskWaiting = true;
else moveTaskDone = true;
break;
} else {
_flags &= ~kMfReset;
immediateTarget = getImmediateTarget();
}
}
#if VISUAL1
extern void ShowObjectSection(GameObject * obj);
extern void TPLine(const TilePoint & start, const TilePoint & stop);
{
TilePoint curPt,
wayPt,
pt1,
pt2;
// TPLine( a->getLocation(), _immediateLocation );
curPt = a->getLocation();
wayPt = immediateTarget;
for (int i = _pathIndex - 1; i < _pathCount;) {
TPLine(curPt, wayPt);
pt1 = pt2 = wayPt;
pt1.u -= 2;
pt1.v -= 2;
pt2.u -= 2;
pt2.v += 2;
TPLine(pt1, pt2);
pt1.u += 4;
pt1.v += 4;
TPLine(pt1, pt2);
pt2.u += 4;
pt2.v -= 4;
TPLine(pt1, pt2);
pt1.u -= 4;
pt1.v -= 4;
TPLine(pt1, pt2);
curPt = wayPt;
wayPt = _pathList[++i];
}
ShowObjectSection(a);
}
#endif
moveBlocked = false;
if (moveTaskDone || moveTaskWaiting) {
movementDirection = a->_currentFacing;
} else if (targetDist == 0 && ABS(targetVector.z) > kMaxStepHeight) {
if (_pathFindTask) {
movementDirection = a->_currentFacing;
moveTaskWaiting = true;
} else {
movementDirection = a->_currentFacing;
moveBlocked = true;
}
} else if (targetDist <= speed) {
int16 blockageType;
// If we're near the target, then don't bother with
// a smooth movement, just jump right there.
movementDirection = targetVector.quickDir();
// movementDirection = a->currentFacing;
// Set the new _data.location to the character's _data.location.
newPos.u = immediateTarget.u;
newPos.v = immediateTarget.v;
newPos.z = _object->_data.location.z;
// Determine the direction the character must spin
// to be at the correct movement angle.
directionAngle =
(((movementDirection - a->_currentFacing) + 4) & 7) - 4;
// Test terrain. Note that if the character is spinning more than 1
// octant this frame, then they cannot move so a terrain test is unneeded.
if (directionAngle <= 1 && directionAngle >= -1) {
// Test the terrain to see if we can go there.
if ((blockageType = checkWalkable(_object, newPos)) != false) {
// Try stepping up to a higher terrain too.
newPos.z = _object->_data.location.z + kMaxStepHeight;
if (checkWalkable(_object, newPos) != kBlockageNone) {
// If there is a path find task pending, put the walk action
// on hold until it finishes, else, abort the walk action.
if (_pathFindTask)
moveTaskWaiting = true;
else {
movementDirection = a->_currentFacing;
moveBlocked = true;
}
/* if (!(_flags & kMfPathFind) || nextWayPoint() == false)
{
moveBlocked = true;
_flags |= blocked;
newPos.z = _object->_data.location.z;
}*/
}
}
}
} else {
int16 height;
bool foundPath = false;
movementDirection = targetVector.quickDir();
// Calculate new object position along direction vector.
TilePoint pos = _object->_data.location
+ targetVector * speed / targetDist;
#if DEBUG*0
TPLine(_object->_data.location, pos);
#endif
// Check the terrain in various directions.
// Check in the forward direction first, at various heights
for (height = 0; height <= kMaxStepHeight; height += kMaxSmoothStep) {
// This code has him move along the exact direction
// vector, even if it's not aligned with one of the
// cardinal directions.
pos.z = _object->_data.location.z + height;
if (!checkWalkable(_object, pos)) {
newPos = pos;
foundPath = true;
break;
}
}
// Check left and right facings if a path was not found in
// the forward direction.
if (foundPath == false) {
int16 leftDir = spinLeft(movementDirection),
rightDir = spinRight(movementDirection);
for (height = 0; height <= kMaxStepHeight; height += 8) {
if (checkWalk(rightDir, speedScale, height, newPos)) {
movementDirection = rightDir;
foundPath = true;
break;
}
if (checkWalk(leftDir, speedScale, height, newPos)) {
movementDirection = leftDir;
foundPath = true;
break;
}
}
}
// Let's try moving at a right angle to the current path to
// get around this annoying obstacle...
if (foundPath == false) {
if (targetVector.u > speed / 2
&& checkWalk(kDirUpRight, speedScale, 0, newPos)) {
movementDirection = kDirUpRight;
foundPath = true;
} else if (-targetVector.u > speed / 2
&& checkWalk(kDirDownLeft, speedScale, 0, newPos)) {
movementDirection = kDirDownLeft;
foundPath = true;
} else if (targetVector.v > speed / 2
&& checkWalk(kDirUpLeft, speedScale, 0, newPos)) {
movementDirection = kDirUpLeft;
foundPath = true;
} else if (-targetVector.v > speed / 2
&& checkWalk(kDirDownRight, speedScale, 0, newPos)) {
movementDirection = kDirDownRight;
foundPath = true;
}
}
// If we just couldn't find a valid path no matter how hard
// we tried, then just give up and say that we were blocked.
if (foundPath == false) {
// If there is a path find task pending, put the walk action
// on hold until it finishes, else, abort the walk action.
if (_pathFindTask)
moveTaskWaiting = true;
else {
movementDirection = a->_currentFacing;
moveBlocked = true;
}
}
}
// REM: Test the terrain at the new spot.
if (movementDirection != a->_currentFacing) {
// Determine the direction the character must spin
// to be at the correct movement angle.
directionAngle =
(((movementDirection - a->_currentFacing) + 4) & 7) - 4;
// If the direction is at a right angle or behind
// the character, then they cannot move.
if (directionAngle < 0) {
a->_currentFacing = spinRight(a->_currentFacing);
} else {
a->_currentFacing = spinLeft(a->_currentFacing);
}
}
if (moveTaskDone) {
remove(kMotionCompleted);
} else if (moveBlocked) {
a->setAction(kActionStand, 0);
if (_flags & kMfAgitatable) {
if (freeFall(_object->_data.location, sti)) return;
// When he starts running again, then have him walk only.
_runCount = MAX<int16>(_runCount, 8);
// We're blocked so we're going to wander in a random
// direction for a random duration
_flags |= kMfAgitated | kMfReset;
_direction = g_vm->_rnd->getRandomNumber(7);
actionCounter = 8 + g_vm->_rnd->getRandomNumber(7);
// Discard the path
if (_flags & kMfPathFind) {
_flags &= ~kMfFinalPath;
_pathIndex = _pathCount = 0;
}
} else
remove(kMotionWalkBlocked);
} else if (moveTaskWaiting
|| movementDirection != a->_currentFacing) {
// When he starts running again, then have him walk only.
_runCount = MAX<int16>(_runCount, 8);
a->setAction(kActionStand, 0);
freeFall(_object->_data.location, sti);
} else {
if (a == getCenterActor() && checkLadder(a, newPos)) return;
int16 tHeight;
_flags &= ~kMfBlocked;
tHeight = tileSlopeHeight(newPos, _object, &sti);
// This is a kludge to keep the character from
// "jumping" as he climbs up a small step.
if (tHeight >= _object->_data.location.z - kMaxSmoothStep
* ((sti.surfaceTile != nullptr
&& (sti.surfaceTile->combinedTerrainMask() & kTerrainStair))
? 4
: 1)
&& tHeight < newPos.z)
newPos.z = tHeight;
if (freeFall(newPos, sti) == false) {
int16 newAction;
if (sti.surfaceTile != nullptr
&& (sti.surfaceTile->combinedTerrainMask() & kTerrainStair)
&& a->isActionAvailable(kActionSpecial7)) {
Direction stairsDir;
uint8 *cornerHeight;
cornerHeight = sti.surfaceTile->attrs.cornerHeight;
if (cornerHeight[0] == 0 && cornerHeight[1] == 0)
stairsDir = 1;
else if (cornerHeight[1] == 0 && cornerHeight[2] == 0)
stairsDir = 3;
else if (cornerHeight[2] == 0 && cornerHeight[3] == 0)
stairsDir = 5;
else
stairsDir = 7;
if (a->_currentFacing == stairsDir) {
// walk up stairs
newAction = kActionSpecial7;
_flags |= kMfOnStairs;
} else if (a->_currentFacing == ((stairsDir - 4) & 0x7)) {
// walk down stairs
newAction = kActionSpecial8;
_flags |= kMfOnStairs;
} else {
_flags &= ~kMfOnStairs;
if (walkType == kWalkStairs) walkType = kWalkNormal;
newAction = (walkType == kWalkRun) ? kActionRun : kActionWalk;
}
} else {
_flags &= ~kMfOnStairs;
if (walkType == kWalkStairs) walkType = kWalkNormal;
newAction = (walkType == kWalkRun) ? kActionRun : kActionWalk;
}
_object->move(newPos);
// Determine if the new action is running
// or walking.
if (a->_currentAnimation == newAction) {
// If we are already doing that action, then
// just continue doing it.
if (walkType != kWalkSlow)
a->nextAnimationFrame();
else {
if (_flags & kMfNextAnim)
a->nextAnimationFrame();
_flags ^= kMfNextAnim;
}
} else if (a->_currentAnimation == kActionWalk
|| a->_currentAnimation == kActionRun
|| a->_currentAnimation == kActionSpecial7
|| a->_currentAnimation == kActionSpecial8) {
// If we are running instead of walking or
// vice versa, then change to the new action
// but don't break stride
a->setAction(newAction,
kAnimateRepeat | kAnimateNoRestart);
if (walkType != kWalkSlow)
a->nextAnimationFrame();
else {
if (_flags & kMfNextAnim)
a->nextAnimationFrame();
_flags ^= kMfNextAnim;
}
} else {
// If we weren't walking or running before, then start
// walking/running and reset the sequence.
a->setAction(newAction, kAnimateRepeat);
if (walkType == kWalkSlow) _flags |= kMfNextAnim;
}
if (_runCount > 0) _runCount--;
setObjectSurface(_object, sti);
}
}
}
//-----------------------------------------------------------------------
// Climb up a ladder
void MotionTask::upLadderAction() {
Actor *a = (Actor *)_object;
if (_flags & kMfReset) {
a->setAction(kActionClimbLadder, kAnimateRepeat);
_flags &= ~kMfReset;
} else {
TilePoint loc = a->getLocation();
uint8 crossSection = a->proto()->crossSection,
height = a->proto()->height;
int16 mapNum = a->getMapNum();
TileRegion actorTileReg;
TileInfo *ti;
TilePoint tileLoc;
StandingTileInfo sti = {nullptr, nullptr, {0, 0, 0}, 0};
loc.z += 6;
// Determine the tile region which the actor overlays
actorTileReg.min.u = (loc.u - crossSection) >> kTileUVShift;
actorTileReg.min.v = (loc.v - crossSection) >> kTileUVShift;
actorTileReg.max.u =
(loc.u + crossSection + kTileUVMask) >> kTileUVShift;
actorTileReg.max.v =
(loc.v + crossSection + kTileUVMask) >> kTileUVShift;
actorTileReg.min.z = actorTileReg.max.z = 0;
TileIterator iter(mapNum, actorTileReg);
// Iterate through all the tiles in the actor's tile region
for (ti = iter.first(&tileLoc, &sti);
ti != nullptr;
ti = iter.next(&tileLoc, &sti)) {
if (!(ti->combinedTerrainMask() & kTerrainLadder)) continue;
if (sti.surfaceHeight
+ ti->attrs.terrainHeight
<= loc.z
+ height
|| sti.surfaceHeight > loc.z + height)
continue;
uint16 footPrintMask = 0xFFFF,
ladderMask;
TilePoint subTileLoc(
tileLoc.u << kTileSubShift,
tileLoc.v << kTileSubShift,
0);
TileRegion actorSubTileReg;
actorSubTileReg.min.u = (loc.u - crossSection) >> kSubTileShift;
actorSubTileReg.min.v = (loc.v - crossSection) >> kSubTileShift;
actorSubTileReg.max.u =
(loc.u + crossSection + kSubTileMask) >> kSubTileShift;
actorSubTileReg.max.v =
(loc.v + crossSection + kSubTileMask) >> kSubTileShift;
if (actorSubTileReg.min.u >= subTileLoc.u)
footPrintMask &=
uMinMasks[actorSubTileReg.min.u - subTileLoc.u];
if (actorSubTileReg.min.v >= subTileLoc.v)
footPrintMask &=
vMinMasks[actorSubTileReg.min.v - subTileLoc.v];
if (actorSubTileReg.max.u < subTileLoc.u + kTileSubSize)
footPrintMask &=
uMaxMasks[actorSubTileReg.max.u - subTileLoc.u];
if (actorSubTileReg.max.v < subTileLoc.v + kTileSubSize)
footPrintMask &=
vMaxMasks[actorSubTileReg.max.v - subTileLoc.v];
ladderMask = ti->attrs.fgdTerrain == kTerrNumLadder
? ti->attrs.terrainMask
: ~ti->attrs.terrainMask;
if (footPrintMask & ladderMask) {
a->nextAnimationFrame();
a->move(loc);
return;
}
}
TilePoint newLoc;
newLoc = loc + incDirTable[a->_currentFacing] * crossSection * 2;
newLoc.z = tileSlopeHeight(newLoc, a);
if (!checkBlocked(a, newLoc))
a->move(newLoc);
else {
newLoc = loc
+ incDirTable[(a->_currentFacing - 2) & 7]
* crossSection * 2;
newLoc.z = tileSlopeHeight(newLoc, a);
if (!checkBlocked(a, newLoc))
a->move(newLoc);
else {
newLoc = loc
+ incDirTable[(a->_currentFacing + 2) & 7]
* crossSection * 2;
newLoc.z = tileSlopeHeight(newLoc, a);
if (!checkBlocked(a, newLoc))
a->move(newLoc);
else {
newLoc = loc
+ incDirTable[a->_currentFacing]
* crossSection * 2;
newLoc.z = tileSlopeHeight(newLoc, a);
a->move(newLoc);
unstickObject(a);
}
}
}
a->setAction(kActionStand, 0);
remove();
}
}
//-----------------------------------------------------------------------
// Climb down a ladder
void MotionTask::downLadderAction() {
Actor *a = (Actor *)_object;
if (_flags & kMfReset) {
a->setAction(kActionClimbLadder, kAnimateRepeat | kAnimateReverse);
_flags &= ~kMfReset;
} else {
TilePoint loc = a->getLocation();
uint8 crossSection = a->proto()->crossSection;
int16 mapNum = a->getMapNum();
TileRegion actorTileReg;
TileInfo *ti;
TilePoint tileLoc;
StandingTileInfo sti = {nullptr, nullptr, {0, 0, 0}, 0};
loc.z -= 6;
actorTileReg.min.u = (loc.u - crossSection) >> kTileUVShift;
actorTileReg.min.v = (loc.v - crossSection) >> kTileUVShift;
actorTileReg.max.u =
(loc.u + crossSection + kTileUVMask) >> kTileUVShift;
actorTileReg.max.v =
(loc.v + crossSection + kTileUVMask) >> kTileUVShift;
actorTileReg.min.z = actorTileReg.max.z = 0;
TileIterator iter(mapNum, actorTileReg);
for (ti = iter.first(&tileLoc, &sti);
ti != nullptr;
ti = iter.next(&tileLoc, &sti)) {
if (!(ti->combinedTerrainMask() & kTerrainLadder)) continue;
if (sti.surfaceHeight + ti->attrs.terrainHeight <= loc.z
|| sti.surfaceHeight > loc.z)
continue;
uint16 footPrintMask = 0xFFFF,
ladderMask;
TilePoint subTileLoc(
tileLoc.u << kTileSubShift,
tileLoc.v << kTileSubShift,
0);
TileRegion actorSubTileReg;
actorSubTileReg.min.u = (loc.u - crossSection) >> kSubTileShift;
actorSubTileReg.min.v = (loc.v - crossSection) >> kSubTileShift;
actorSubTileReg.max.u =
(loc.u + crossSection + kSubTileMask) >> kSubTileShift;
actorSubTileReg.max.v =
(loc.v + crossSection + kSubTileMask) >> kSubTileShift;
if (actorSubTileReg.min.u >= subTileLoc.u)
footPrintMask &=
uMinMasks[actorSubTileReg.min.u - subTileLoc.u];
if (actorSubTileReg.min.v >= subTileLoc.v)
footPrintMask &=
vMinMasks[actorSubTileReg.min.v - subTileLoc.v];
if (actorSubTileReg.max.u < subTileLoc.u + kTileSubSize)
footPrintMask &=
uMaxMasks[actorSubTileReg.max.u - subTileLoc.u];
if (actorSubTileReg.max.v < subTileLoc.v + kTileSubSize)
footPrintMask &=
vMaxMasks[actorSubTileReg.max.v - subTileLoc.v];
ladderMask = ti->attrs.fgdTerrain == kTerrNumLadder
? ti->attrs.terrainMask
: ~ti->attrs.terrainMask;
if (footPrintMask & ladderMask) {
a->nextAnimationFrame();
a->move(loc);
return;
}
}
TilePoint newLoc;
newLoc = loc - incDirTable[a->_currentFacing] * kTileUVSize;
newLoc.z = tileSlopeHeight(newLoc, a);
if (!checkBlocked(a, newLoc))
a->move(newLoc);
else {
newLoc = loc
- incDirTable[(a->_currentFacing - 2) & 7]
* kTileUVSize;
newLoc.z = tileSlopeHeight(newLoc, a);
if (!checkBlocked(a, newLoc))
a->move(newLoc);
else {
newLoc = loc
- incDirTable[(a->_currentFacing + 2) & 7]
* kTileUVSize;
newLoc.z = tileSlopeHeight(newLoc, a);
if (!checkBlocked(a, newLoc))
a->move(newLoc);
else {
newLoc = loc
- incDirTable[a->_currentFacing]
* kTileUVSize;
newLoc.z = tileSlopeHeight(newLoc, a);
a->move(newLoc);
unstickObject(a);
}
}
}
a->setAction(kActionStand, 0);
remove();
}
}
// Go through the giving motions
void MotionTask::giveAction() {
Actor *a = (Actor *)_object;
Direction targetDir = (_targetObj->getLocation()
- a->getLocation()).quickDir();
if (_flags & kMfReset) {
a->setAction(kActionGiveItem, 0);
_flags &= ~kMfReset;
}
if (a->_currentFacing != targetDir)
a->turn(targetDir);
else if (a->nextAnimationFrame())
remove(kMotionCompleted);
}
// Set up specified animation and run through the frames
void MotionTask::genericAnimationAction(uint8 actionType) {
Actor *const a = (Actor *)_object;
if (_flags & kMfReset) {
a->setAction(actionType, 0);
_flags &= ~kMfReset;
} else if (a->nextAnimationFrame())
remove(kMotionCompleted);
}
// This class is specifically designed to aid in the selection of
// of a combat motion type from a selected subset
struct CombatMotionSet {
const uint8 *list; // Array of motion types
uint16 listSize; // Size of array
// Select randome element from the array
uint8 selectRandom() const {
return list[g_vm->_rnd->getRandomNumber(listSize - 1)];
}
};
// Offensive combat actions
// Construct a set of all two handed swing types
const uint8 twoHandedSwingArray[] = {
MotionTask::kTwoHandedSwingHigh,
MotionTask::kTwoHandedSwingLow,
MotionTask::kTwoHandedSwingLeftHigh,
MotionTask::kTwoHandedSwingLeftLow,
MotionTask::kTwoHandedSwingRightHigh,
MotionTask::kTwoHandedSwingRightLow,
};
const CombatMotionSet twoHandedSwingSet = {
twoHandedSwingArray,
ARRAYSIZE(twoHandedSwingArray)
};
// Construct a subset of all high two handed swing types
const uint8 twoHandedHighSwingArray[] = {
MotionTask::kTwoHandedSwingHigh,
MotionTask::kTwoHandedSwingLeftHigh,
MotionTask::kTwoHandedSwingRightHigh,
};
const CombatMotionSet twoHandedHighSwingSet = {
twoHandedHighSwingArray,
ARRAYSIZE(twoHandedHighSwingArray)
};
// Construct a subset of all low two handed swing types
const uint8 twoHandedLowSwingArray[] = {
MotionTask::kTwoHandedSwingLow,
MotionTask::kTwoHandedSwingLeftLow,
MotionTask::kTwoHandedSwingRightLow,
};
const CombatMotionSet twoHandedLowSwingSet = {
twoHandedLowSwingArray,
ARRAYSIZE(twoHandedLowSwingArray)
};
//-----------------------------------------------------------------------
// Handle all two handed swing motions
void MotionTask::twoHandedSwingAction() {
// If the reset flag is set, initialize the motion
if (_flags & kMfReset) {
// Let the game engine know about this aggressive act
logAggressiveAct(_object->thisID(), _targetObj->thisID());
// Notify the target actor that he is being attacked
if (isActor(_targetObj))
((Actor *)_targetObj)->evaluateMeleeAttack((Actor *)_object);
// Create an animation type lookup table
static const uint8 animationTypeArray[] = {
kActionTwoHandSwingHigh,
kActionTwoHandSwingLow,
kActionTwoHandSwingLeftHigh,
kActionTwoHandSwingLeftLow,
kActionTwoHandSwingRightHigh,
kActionTwoHandSwingRightLow,
};
Actor *a = (Actor *)_object;
uint8 actorAnimation;
int16 actorMidAltitude,
targetAltitude = _targetObj->getLocation().z;
const CombatMotionSet *availableSet;
// Calculate the altitude of the actor's mid section
actorMidAltitude = a->getLocation().z + (a->proto()->height >> 1);
if (targetAltitude > actorMidAltitude)
// The target is higher than the actor's midsection
availableSet = &twoHandedHighSwingSet;
else {
uint8 targetHeight = _targetObj->proto()->height;
if (targetAltitude + targetHeight < actorMidAltitude)
// The target is below the actor's midsection
availableSet = &twoHandedLowSwingSet;
else
// The target is nearly the same altitude as the actor
availableSet = &twoHandedSwingSet;
}
// Calculate the direction of the attack
_direction = (_targetObj->getLocation() - a->getLocation()).quickDir();
// Randomly select a combat motion type from the available set
_combatMotionType = availableSet->selectRandom();
actorAnimation = animationTypeArray[_combatMotionType];
if (a->_appearance != nullptr
&& a->isActionAvailable(actorAnimation)) {
// Compute the number of frames in the animation before the
// actual strike
actionCounter = a->animationFrames(actorAnimation, _direction) - 2;
a->setAction(actorAnimation, 0);
// Set this flag to indicate that the animation is actually
// being played
_flags |= kMfNextAnim;
} else {
actionCounter = 2;
// Clear this flag to indicate that the animation is not
// being played
_flags &= ~kMfNextAnim;
}
a->setActionPoints(
computeTurnFrames(a->_currentFacing, _direction) + 10);
_flags &= ~kMfReset;
} else
// Call the generic offensive melee function
offensiveMeleeAction();
}
// Construct a set of all one handed swing types
const uint8 oneHandedSwingArray[] = {
MotionTask::kOneHandedSwingHigh,
MotionTask::kOneHandedSwingLow,
// MotionTask::kOneHandedThrust,
};
const CombatMotionSet oneHandedSwingSet = {
oneHandedSwingArray,
ARRAYSIZE(oneHandedSwingArray)
};
// Construct a subset of all high one handed swing types
const uint8 oneHandedHighSwingArray[] = {
MotionTask::kOneHandedSwingHigh,
};
const CombatMotionSet oneHandedHighSwingSet = {
oneHandedHighSwingArray,
ARRAYSIZE(oneHandedHighSwingArray)
};
// Construct a subset of all low one handed swing types
const uint8 oneHandedLowSwingArray[] = {
MotionTask::kOneHandedSwingLow,
};
const CombatMotionSet oneHandedLowSwingSet = {
oneHandedLowSwingArray,
ARRAYSIZE(oneHandedLowSwingArray)
};
//-----------------------------------------------------------------------
// Handle all one handed swing motions
void MotionTask::oneHandedSwingAction() {
if (_flags & kMfReset) {
// Let the game engine know about this aggressive act
logAggressiveAct(_object->thisID(), _targetObj->thisID());
// Notify the target actor that he is being attacked
if (isActor(_targetObj))
((Actor *)_targetObj)->evaluateMeleeAttack((Actor *)_object);
// Create an animation type lookup table
static const uint8 animationTypeArray[] = {
kActionSwingHigh,
kActionSwingLow,
};
Actor *const a = (Actor *)_object;
uint8 actorAnimation;
int16 actorMidAltitude,
targetAltitude = _targetObj->getLocation().z;
const CombatMotionSet *availableSet;
// Calculate the altitude of the actor's mid section
actorMidAltitude = a->getLocation().z + (a->proto()->height >> 1);
if (targetAltitude > actorMidAltitude)
// The target is higher than the actor's midsection
availableSet = &oneHandedHighSwingSet;
else {
uint8 targetHeight = _targetObj->proto()->height;
if (targetAltitude + targetHeight < actorMidAltitude)
// The target is below the actor's midsection
availableSet = &oneHandedLowSwingSet;
else
// The target is nearly the same altitude as the actor
availableSet = &oneHandedSwingSet;
}
// Calculate the direction of the attack
_direction = (_targetObj->getLocation() - a->getLocation()).quickDir();
// Randomly select a combat motion type from the available set
_combatMotionType = availableSet->selectRandom();
/* if ( _combatMotionType == kOneHandedThrust )
{
// Initialize the thrust motion
}
else*/
{
actorAnimation = animationTypeArray[_combatMotionType];
if (a->_appearance != nullptr
&& a->isActionAvailable(actorAnimation)) {
// Compute the number of frames in the animation before the
// actual strike
actionCounter = a->animationFrames(actorAnimation, _direction) - 2;
a->setAction(actorAnimation, 0);
// Set this flag to indicate that the animation is actually
// being played
_flags |= kMfNextAnim;
} else {
actionCounter = 1;
// Clear this flag to indicate that the animation is not
// being played
_flags &= ~kMfNextAnim;
}
}
a->setActionPoints(actionCounter * 2);
a->setActionPoints(
computeTurnFrames(a->_currentFacing, _direction) + 10);
_flags &= ~kMfReset;
} else
// Call the generic offensive melee function
offensiveMeleeAction();
}
//-----------------------------------------------------------------------
// Compute the number of frames before the actual strike in an
// offensive melee motion
uint16 MotionTask::framesUntilStrike() {
// If the melee action has not been initialized, return a safe value
if (_flags & kMfReset) return maxuint16;
uint16 turnFrames;
turnFrames = (_direction - ((Actor *)_object)->_currentFacing) & 0x7;
if (turnFrames > 4) turnFrames = 8 - turnFrames;
return turnFrames + actionCounter;
}
//-----------------------------------------------------------------------
// Returns a pointer to the blocking object if it applicable to
// this motion task
GameObject *MotionTask::blockingObject(Actor *thisAttacker) {
return isDefense()
&& (_d.defenseFlags & kDfBlocking)
&& thisAttacker == _d.attacker
? _d.defensiveObj
: nullptr;
}
//-----------------------------------------------------------------------
// Handle bow firing motions
void MotionTask::fireBowAction() {
Actor *a = (Actor *)_object;
assert(a->_leftHandObject != Nothing);
// Initialize the bow firing motion
if (_flags & kMfReset) {
// Let the game engine know about this aggressive act
logAggressiveAct(_object->thisID(), _targetObj->thisID());
// Compute the direction to the target
_direction = (_targetObj->getLocation() - a->getLocation()).quickDir();
if (a->_appearance != nullptr
&& a->isActionAvailable(kActionFireBow)) {
// Calculate the number of frames in the animation before the
// projectile is actually fired
actionCounter = a->animationFrames(kActionFireBow, _direction) - 1;
a->setAction(kActionFireBow, 0);
// Set this flag to indicate that the animation is actually
// being played
_flags |= kMfNextAnim;
} else {
actionCounter = 1;
// Clear this flag to indicate that the animation is not
// being played
_flags &= ~kMfNextAnim;
}
a->setActionPoints(
computeTurnFrames(a->_currentFacing, _direction) + 10);
if (a->_currentFacing != _direction)
a->turn(_direction);
_flags &= ~kMfReset;
} else if (a->_currentFacing != _direction)
a->turn(_direction);
else {
// If the actors appearance becomes NULL, make sure this action
// no longer depends upon the animation
if ((_flags & kMfNextAnim) && a->_appearance == nullptr)
_flags &= ~kMfNextAnim;
// If the action counter has reached zero, get a projectile and
// fire it
if (actionCounter == 0) {
GameObject *missileWeapon;
missileWeapon = GameObject::objectAddress(a->_leftHandObject);
if (missileWeapon != nullptr) {
GameObject *proj;
// Ask the missile weapon's prototype to get a projectile
proj = missileWeapon->proto()->getProjectile(
a->_leftHandObject,
a->thisID());
// Shoot the projectile
if (proj != nullptr) {
TilePoint actorLoc = a->getLocation();
uint8 actorCrossSection = a->proto()->crossSection,
projCrossSection = proj->proto()->crossSection;
ObjectID projID;
actorLoc.u += incDirTable[a->_currentFacing].u
* (actorCrossSection + projCrossSection);
actorLoc.v += incDirTable[a->_currentFacing].v
* (actorCrossSection + projCrossSection);
actorLoc.z += a->proto()->height * 7 / 8;
if ((projID = proj->extractMerged(Location(actorLoc, a->IDParent()), 1)) != Nothing) {
g_vm->_cnm->setUpdate(a->thisID());
proj = GameObject::objectAddress(projID);
shootObject(*proj, *a, *_targetObj, 16);
}
}
}
}
if (_flags & kMfNextAnim) {
// Run through the animation frames
if (!a->nextAnimationFrame()) {
if (actionCounter >= 0) actionCounter--;
} else
remove();
} else {
if (actionCounter > 0)
actionCounter--;
else
remove();
}
}
}
//-----------------------------------------------------------------------
// Handle spell casting motions
void MotionTask::castSpellAction() {
Actor *a = (Actor *)_object;
// Turn until facing the target
if (a->_currentFacing != _direction)
a->turn(_direction);
else {
if (_flags & kMfReset) {
if (a->_appearance != nullptr
&& a->isActionAvailable(kActionCastSpell)) {
// Calculate the number of frames in the animation before the
// spell is case
actionCounter = a->animationFrames(kActionCastSpell, _direction) - 1;
a->setAction(kActionCastSpell, 0);
// Set this flag to indicate that the animation is actually
// being played
_flags |= kMfNextAnim;
} else {
actionCounter = 3;
// Clear this flag to indicate that the animation is not
// being played
_flags &= ~kMfNextAnim;
}
_flags &= ~kMfReset;
}
// If the actors appearance becomes NULL, make sure this action
// no longer depends upon the animation
if ((_flags & kMfNextAnim) && a->_appearance == nullptr)
_flags &= ~kMfNextAnim;
if (actionCounter == 0) {
if (_spellObj) {
if (_flags & kMfTAGTarg) {
assert(_targetTAG->_data.itemType == kActiveTypeInstance);
_spellObj->implementAction(_spellObj->getSpellID(), a->thisID(), _targetTAG);
} else if (_flags & kMfLocTarg) {
_spellObj->implementAction(_spellObj->getSpellID(), a->thisID(), _targetLoc);
} else if (_targetObj) {
_spellObj->implementAction(_spellObj->getSpellID(), a->thisID(), _targetObj->thisID());
}
}
}
if (_flags & kMfNextAnim) {
// Run through the animation frames
if (!a->nextAnimationFrame()) {
if (actionCounter >= 0) actionCounter--;
} else
remove();
} else {
if (actionCounter > 0)
actionCounter--;
else
remove();
}
}
}
//-----------------------------------------------------------------------
// Handle wand using motions
void MotionTask::useWandAction() {
// Initialize the wand using motion
if (_flags & kMfReset) {
// Let the game engine know about this aggressive act
logAggressiveAct(_object->thisID(), _targetObj->thisID());
Actor *a = (Actor *)_object;
_direction = (_targetObj->getLocation() - a->getLocation()).quickDir();
if (a->_appearance != nullptr
&& a->isActionAvailable(kActionUseWand)) {
actionCounter = a->animationFrames(kActionUseWand, _direction) - 1;
a->setAction(kActionUseWand, 0);
// Set this flag to indicate that the animation is actually
// being played
_flags |= kMfNextAnim;
} else {
actionCounter = 3;
// Clear this flag to indicate that the animation is not
// being played
_flags &= ~kMfNextAnim;
}
a->setActionPoints(
computeTurnFrames(a->_currentFacing, _direction) + 10);
_flags &= ~kMfReset;
}
useMagicWeaponAction();
}
// Defensive combat actions
//-----------------------------------------------------------------------
// Handle two handed parrying motions
void MotionTask::twoHandedParryAction() {
if (_flags & kMfReset) {
Actor *a = (Actor *)_object;
int16 animationFrames;
_direction = (_d.attacker->getLocation() - a->getLocation()).quickDir();
if (a->_appearance != nullptr
&& a->isActionAvailable(kActionTwoHandParry)) {
a->setAction(kActionTwoHandParry, 0);
animationFrames = a->animationFrames(kActionTwoHandParry, _direction);
// Set this flag to indicate that the animation is actually
// being played
_flags |= kMfNextAnim;
} else {
animationFrames = 2;
// Clear this flag to indicate that the animation is not
// being played
_flags &= ~kMfNextAnim;
}
a->setActionPoints(
computeTurnFrames(a->_currentFacing, _direction)
+ animationFrames + 1);
_flags &= ~kMfReset;
}
defensiveMeleeAction();
}
//-----------------------------------------------------------------------
// Handle one handed parrying motions
void MotionTask::oneHandedParryAction() {
if (_flags & kMfReset) {
Actor *a = (Actor *)_object;
int16 animationFrames;
_direction = (_d.attacker->getLocation() - a->getLocation()).quickDir();
_combatMotionType = kOneHandedParryHigh;
if (a->_appearance != nullptr
&& a->isActionAvailable(kActionParryHigh)) {
a->setAction(kActionParryHigh, 0);
animationFrames = a->animationFrames(kActionParryHigh, _direction);
// Set this flag to indicate that the animation is actually
// being played
_flags |= kMfNextAnim;
} else {
animationFrames = 2;
// Clear this flag to indicate that the animation is not
// being played
_flags &= ~kMfNextAnim;
}
a->setActionPoints(
computeTurnFrames(a->_currentFacing, _direction)
+ animationFrames + 1);
_flags &= ~kMfReset;
}
defensiveMeleeAction();
}
//-----------------------------------------------------------------------
// Handle shield parrying motions
void MotionTask::shieldParryAction() {
if (_flags & kMfReset) {
Actor *a = (Actor *)_object;
int16 animationFrames;
_direction = (_d.attacker->getLocation() - a->getLocation()).quickDir();
if (a->_appearance != nullptr
&& a->isActionAvailable(kActionShieldParry)) {
a->setAction(kActionShieldParry, 0);
animationFrames = a->animationFrames(kActionShieldParry, _direction);
// Set this flag to indicate that the animation is actually
// being played
_flags |= kMfNextAnim;
} else {
animationFrames = 1;
// Clear this flag to indicate that the animation is not
// being played
_flags &= ~kMfNextAnim;
}
a->setActionPoints(
computeTurnFrames(a->_currentFacing, _direction)
+ animationFrames + 1);
_flags &= ~kMfReset;
}
defensiveMeleeAction();
}
//-----------------------------------------------------------------------
// Handle dodging motions
void MotionTask::dodgeAction() {
Actor *a = (Actor *)_object;
MotionTask *attackerMotion = _d.attacker->_moveTask;
if (_flags & kMfReset) {
// If the attacker is not attacking, we're done
if (attackerMotion == nullptr
|| !attackerMotion->isMeleeAttack()) {
a->setInterruptablity(true);
remove();
return;
}
// If the strike is about to land start the dodging motion
if (attackerMotion->framesUntilStrike() <= 2) {
int16 animationFrames;
if (a->_appearance != nullptr
&& a->isActionAvailable(kActionJumpUp, a->_currentFacing)) {
a->setAction(kActionJumpUp, 0);
animationFrames = a->animationFrames(kActionJumpUp, a->_currentFacing);
// Set this flag to indicate that the animation is actually
// being played
_flags |= kMfNextAnim;
} else {
animationFrames = 3;
// Clear this flag to indicate that the animation is not
// being played
_flags &= ~kMfNextAnim;
}
actionCounter = animationFrames - 1;
a->setActionPoints(animationFrames + 1);
_flags &= ~kMfReset;
}
} else {
// If the actors appearance becomes NULL, make sure this action
// no longer depends upon the animation
if ((_flags & kMfNextAnim) && a->_appearance == nullptr)
_flags &= ~kMfNextAnim;
if (_flags & kMfNextAnim) {
// Run through the animation frames
if (!a->nextAnimationFrame()) {
if (actionCounter > 0) actionCounter--;
} else
remove();
} else {
if (actionCounter > 0)
actionCounter--;
else
remove();
}
}
}
//-----------------------------------------------------------------------
// Handle accept hit motions
void MotionTask::acceptHitAction() {
Actor *a = (Actor *)_object;
if (_flags & kMfReset) {
TilePoint newLoc = a->getLocation();
StandingTileInfo sti;
int16 animationFrames;
a->_currentFacing =
(_d.attacker->getWorldLocation() - a->getLocation()).quickDir();
if (a->_appearance != nullptr
&& a->isActionAvailable(kActionHit, a->_currentFacing)) {
a->setAction(kActionHit, 0);
animationFrames = a->animationFrames(kActionHit, a->_currentFacing);
// Set this flag to indicate that the animation is actually
// being played
_flags |= kMfNextAnim;
} else {
animationFrames = 1;
// Clear this flag to indicate that the animation is not
// being played
_flags &= ~kMfNextAnim;
}
a->setActionPoints(animationFrames + 1);
if (g_vm->_rnd->getRandomNumber(1)) {
// Calculate the new position to knock the actor back to
newLoc += dirTable[(a->_currentFacing - 4) & 0x7];
// If the actor is not blocked, move him back
if (!checkBlocked(a, newLoc)) {
newLoc.z = tileSlopeHeight(newLoc, a, &sti);
a->move(newLoc);
setObjectSurface(a, sti);
}
}
_flags &= ~kMfReset;
} else {
// If the actors appearance becomes NULL, make sure this action
// no longer depends upon the animation
if ((_flags & kMfNextAnim) && a->_appearance == nullptr)
_flags &= ~kMfNextAnim;
if (_flags & kMfNextAnim) {
if (a->nextAnimationFrame()) remove();
} else
remove();
}
}
//-----------------------------------------------------------------------
// Handle fall down motions
void MotionTask::fallDownAction() {
Actor *a = (Actor *)_object;
if (_flags & kMfReset) {
TilePoint newLoc = a->getLocation();
StandingTileInfo sti;
int16 animationFrames;
a->_currentFacing =
(_d.attacker->getWorldLocation() - a->getLocation()).quickDir();
if (a->_appearance != nullptr
&& a->isActionAvailable(kActionKnockedDown, a->_currentFacing)) {
a->setAction(kActionKnockedDown, 0);
animationFrames = a->animationFrames(
kActionKnockedDown,
a->_currentFacing);
// Set this flag to indicate that the animation is actually
// being played
_flags |= kMfNextAnim;
} else {
animationFrames = 6;
// Clear this flag to indicate that the animation is not
// being played
_flags &= ~kMfNextAnim;
}
a->setActionPoints(animationFrames + 1);
if (g_vm->_rnd->getRandomNumber(1)) {
// Calculate the new position to knock the actor back to
newLoc += dirTable[(a->_currentFacing - 4) & 0x7];
newLoc.z = tileSlopeHeight(newLoc, a, &sti);
// If the actor is not blocked, move him back
if (!checkBlocked(a, newLoc)) {
a->move(newLoc);
setObjectSurface(a, sti);
}
}
_flags &= ~kMfReset;
} else {
// If the actors appearance becomes NULL, make sure this action
// no longer depends upon the animation
if ((_flags & kMfNextAnim) && a->_appearance == nullptr)
_flags &= ~kMfNextAnim;
if (_flags & kMfNextAnim) {
if (a->nextAnimationFrame()) remove();
} else
remove();
}
}
//-----------------------------------------------------------------------
// Generic offensive melee code. Called by twoHandedSwingAction()
// and oneHandedSwingAction()
void MotionTask::offensiveMeleeAction() {
Actor *a = (Actor *)_object;
// Turn until facing the target
if (a->_currentFacing != _direction)
a->turn(_direction);
else {
// If the actors appearance becomes NULL, make sure this action
// no longer depends upon the animation
if ((_flags & kMfNextAnim) && a->_appearance == nullptr)
_flags &= ~kMfNextAnim;
// If the action counter has reached zero, use the weapon on
// the target
if (actionCounter == 0) {
GameObject *weapon;
weapon = a->offensiveObject();
if (weapon) weapon->strike(a->thisID(), _targetObj->thisID());
}
if (_flags & kMfNextAnim) {
// Run through the animation frames
if (!a->nextAnimationFrame()) {
if (actionCounter >= 0) actionCounter--;
} else
remove();
} else {
if (actionCounter > 0)
actionCounter--;
else
remove();
}
}
}
//-----------------------------------------------------------------------
// Generic magic weapon code. Called by useWandAction().
void MotionTask::useMagicWeaponAction() {
Actor *a = (Actor *)_object;
// Turn until facing the target
if (a->_currentFacing != _direction)
a->turn(_direction);
else {
// If the actors appearance becomes NULL, make sure this action
// no longer depends upon the animation
if ((_flags & kMfNextAnim) && a->_appearance == nullptr)
_flags &= ~kMfNextAnim;
// If the action counter has reached zero, get a spell and
// use it
if (actionCounter == 0) {
GameObject *magicWeapon;
magicWeapon = a->offensiveObject();
if (magicWeapon != nullptr && magicWeapon->IDChild() != Nothing) {
GameObject *spell;
SkillProto *spellProto;
spell = GameObject::objectAddress(magicWeapon->IDChild());
spellProto = (SkillProto *)spell->proto();
assert(spellProto->containmentSet() & ProtoObj::kIsSkill);
// use the spell
spellProto->implementAction(
spellProto->getSpellID(),
magicWeapon->thisID(),
_targetObj->thisID());
}
}
if (_flags & kMfNextAnim) {
// Run through the animation frames
if (!a->nextAnimationFrame()) {
if (actionCounter >= 0) actionCounter--;
} else
remove();
} else {
if (actionCounter > 0)
actionCounter--;
else
remove();
}
}
}
//-----------------------------------------------------------------------
// Generic defensive melee code. Called by twoHandedParryAction(),
// oneHandedParryAction() and shieldParryAction().
void MotionTask::defensiveMeleeAction() {
Actor *a = (Actor *)_object;
MotionTask *attackerMotion = _d.attacker->_moveTask;
// Determine if the blocking action has been initiated
if (!(_d.defenseFlags & kDfBlocking)) {
// If the attacker is not attacking, we're done
if (attackerMotion == nullptr
|| !attackerMotion->isMeleeAttack()) {
a->setInterruptablity(true);
remove();
return;
}
// turn towards attacker
if (a->_currentFacing != _direction)
a->turn(_direction);
// If the strike is about to land start the blocking motion
if (attackerMotion->framesUntilStrike() <= 1)
_d.defenseFlags |= kDfBlocking;
} else {
// If the actors appearance becomes NULL, make sure this action
// no longer depends upon the animation
if ((_flags & kMfNextAnim) && a->_appearance == nullptr)
_flags &= ~kMfNextAnim;
// Run through the animation frames
if (!(_flags & kMfNextAnim) || a->nextAnimationFrame()) {
// Wait for the attacker's attack
if (attackerMotion == nullptr
|| !attackerMotion->isMeleeAttack()) {
a->setInterruptablity(true);
remove();
}
}
}
}
//-----------------------------------------------------------------------
// Routine to update positions of all moving objects using MotionTasks
void MotionTask::updatePositions() {
TilePoint targetVector;
TilePoint fallVelocity, terminalVelocity(15, 15, 0);
TilePoint curLoc;
int16 targetDist;
StandingTileInfo sti;
for (Common::List<MotionTask *>::iterator it = g_vm->_mTaskList->_list.begin(); it != g_vm->_mTaskList->_list.end(); it = g_vm->_mTaskList->_nextMT) {
MotionTask *mt = *it;
GameObject *obj = mt->_object;
ProtoObj *proto = obj->proto();
Actor *a = (Actor *)obj;
bool moveTaskDone = false;
g_vm->_mTaskList->_nextMT = it;
g_vm->_mTaskList->_nextMT++;
if (!isWorld(obj->IDParent())) {
mt->remove();
continue;
}
// Determine if this motion should be skipped
if (interruptableMotionsPaused
&& isActor(obj)
&& a->isInterruptable())
continue;
if (obj->_data.location.z < -(proto->height >> 2))
mt->_flags |= kMfInWater;
else
mt->_flags &= ~kMfInWater;
switch (mt->_motionType) {
case kMotionTypeThrown:
case kMotionTypeShot:
mt->ballisticAction();
break;
case kMotionTypeWalk:
mt->walkAction();
break;
case kMotionTypeClimbUp:
mt->upLadderAction();
break;
case kMotionTypeClimbDown:
mt->downLadderAction();
break;
case kMotionTypeTalk:
if (mt->_flags & kMfReset) {
a->setAction(kActionStand, 0);
a->_cycleCount = g_vm->_rnd->getRandomNumber(3);
mt->_flags &= ~(kMfReset | kMfNextAnim);
}
if (a->_cycleCount == 0) {
a->setAction(kActionTalk, 0);
mt->_flags |= kMfNextAnim;
a->_cycleCount = -1;
} else if (mt->_flags & kMfNextAnim) {
if (a->nextAnimationFrame()) {
a->setAction(kActionStand, 0);
a->_cycleCount = g_vm->_rnd->getRandomNumber(3);
mt->_flags &= ~kMfNextAnim;
}
} else
a->_cycleCount--;
break;
case kMotionTypeLand:
case kMotionTypeLandBadly:
if (mt->_flags & kMfReset) {
int16 newAction = mt->_motionType == kMotionTypeLand
? kActionJumpUp
: kActionFallBadly;
if (!a->isActionAvailable(newAction)) {
if (mt->_prevMotionType == kMotionTypeWalk) {
mt->_motionType = mt->_prevMotionType;
if (mt->_flags & kMfPathFind) {
mt->changeTarget(
mt->_finalTarget,
(mt->_flags & kMfRequestRun) != 0);
} else {
mt->changeDirectTarget(
mt->_finalTarget,
(mt->_flags & kMfRequestRun) != 0);
}
g_vm->_mTaskList->_nextMT = it;
}
} else {
a->setAction(newAction, 0);
a->setInterruptablity(false);
mt->_flags &= ~kMfReset;
}
} else if (a->nextAnimationFrame() || (mt->_flags & kMfInWater)) {
if (mt->_prevMotionType == kMotionTypeWalk) {
mt->_motionType = mt->_prevMotionType;
if (mt->_flags & kMfPathFind) {
mt->changeTarget(
mt->_finalTarget,
(mt->_flags & kMfRequestRun) != 0);
} else {
mt->changeDirectTarget(
mt->_finalTarget,
(mt->_flags & kMfRequestRun) != 0);
}
g_vm->_mTaskList->_nextMT = it;
} else if (mt->freeFall(obj->_data.location, sti) == false)
moveTaskDone = true;
} else {
// If actor was running, go through an abreviated
// landing sequence by aborting the landing animation
// after the first frame.
if (mt->_prevMotionType == kMotionTypeWalk
&& mt->_flags & kMfRequestRun
&& mt->_runCount == 0
&& !(mt->_flags & kMfInWater)) {
mt->_motionType = mt->_prevMotionType;
if (mt->_flags & kMfPathFind) {
mt->changeTarget(
mt->_finalTarget,
(mt->_flags & kMfRequestRun) != 0);
} else {
mt->changeDirectTarget(
mt->_finalTarget,
(mt->_flags & kMfRequestRun) != 0);
}
g_vm->_mTaskList->_nextMT = it;
}
}
break;
case kMotionTypeJump:
if (mt->_flags & kMfReset) {
a->setAction(kActionJumpUp, 0);
a->setInterruptablity(false);
mt->_flags &= ~kMfReset;
} else if (a->nextAnimationFrame()) {
mt->_motionType = kMotionTypeThrown;
a->setAction(kActionFreeFall, 0);
}
break;
case kMotionTypeTurn:
mt->turnAction();
break;
case kMotionTypeGive:
mt->giveAction();
break;
case kMotionTypeRise:
if (a->_data.location.z < mt->_immediateLocation.z) {
a->_data.location.z++;
if (mt->_flags & kMfNextAnim)
a->nextAnimationFrame();
mt->_flags ^= kMfNextAnim;
} else {
targetVector = mt->_finalTarget - obj->_data.location;
targetDist = targetVector.quickHDistance();
if (targetDist > kTileUVSize) {
mt->_motionType = mt->_prevMotionType;
mt->_flags |= kMfReset;
g_vm->_mTaskList->_nextMT = it;
} else
moveTaskDone = true;
}
break;
case kMotionTypeWait:
if (mt->_flags & kMfReset) {
mt->actionCounter = 5;
mt->_flags &= ~kMfReset;
} else if (--mt->actionCounter == 0)
moveTaskDone = true;
break;
case kMotionTypeUseObject:
// This will be uninterrutable for 2 frames
a->setActionPoints(2);
mt->_o.directObject->use(a->thisID());
//g_vm->_mTaskList->_nextMT=mt;
moveTaskDone = true;
break;
case kMotionTypeUseObjectOnObject:
if (isWorld(mt->_o.indirectObject->IDParent())) {
if (
1
#ifdef THIS_SHOULD_BE_IN_TILEMODE
a->inUseRange(
mt->_o.indirectObject->getLocation(),
mt->_o.directObject)
#endif
) {
mt->_direction = (mt->_o.indirectObject->getLocation()
- a->getLocation()).quickDir();
if (a->_currentFacing != mt->_direction)
a->turn(mt->_direction);
else {
// The actor will now be uniterruptable
a->setActionPoints(2);
mt->_o.directObject->useOn(
a->thisID(),
mt->_o.indirectObject->thisID());
if (mt->_motionType == kMotionTypeUseObjectOnObject)
moveTaskDone = true;
else
g_vm->_mTaskList->_nextMT = it;
}
}
} else {
// The actor will now be uniterruptable
a->setActionPoints(2);
mt->_o.directObject->useOn(
a->thisID(),
mt->_o.indirectObject->thisID());
if (mt->_motionType == kMotionTypeUseObjectOnObject)
moveTaskDone = true;
else
g_vm->_mTaskList->_nextMT = it;
}
break;
case kMotionTypeUseObjectOnTAI:
if (mt->_flags & kMfReset) {
TilePoint actorLoc = a->getLocation(),
TAILoc;
TileRegion TAIReg;
ActiveItem *TAG = mt->_o.TAI->getGroup();
// Compute in points the region of the TAI
TAIReg.min.u = mt->_o.TAI->_data.instance.u << kTileUVShift;
TAIReg.min.v = mt->_o.TAI->_data.instance.v << kTileUVShift;
TAIReg.max.u = TAIReg.min.u
+ (TAG->_data.group.uSize << kTileUVShift);
TAIReg.max.v = TAIReg.min.v
+ (TAG->_data.group.vSize << kTileUVShift);
TAIReg.min.z = TAIReg.max.z = 0;
// Find the point on the TAI closest to the actor
TAILoc.u = clamp(TAIReg.min.u, actorLoc.u, TAIReg.max.u - 1);
TAILoc.v = clamp(TAIReg.min.v, actorLoc.v, TAIReg.max.v - 1);
TAILoc.z = actorLoc.z;
// Compute the direction from the actor to the TAI
mt->_direction = (TAILoc - actorLoc).quickDir();
mt->_flags &= ~kMfReset;
}
if (a->_currentFacing != mt->_direction)
a->turn(mt->_direction);
else {
// The actor will now be uniterruptable
a->setActionPoints(2);
mt->_o.directObject->useOn(a->thisID(), mt->_o.TAI);
if (mt->_motionType == kMotionTypeUseObjectOnTAI)
moveTaskDone = true;
else
g_vm->_mTaskList->_nextMT = it;
}
break;
case kMotionTypeUseObjectOnLocation:
if (mt->_flags & kMfReset) {
mt->_direction = (mt->_targetLoc - a->getLocation()).quickDir();
mt->_flags &= ~kMfReset;
}
if (a->_currentFacing != mt->_direction)
a->turn(mt->_direction);
else {
// The actor will now be uniterruptable
a->setActionPoints(2);
mt->_o.directObject->useOn(a->thisID(), mt->_targetLoc);
if (mt->_motionType == kMotionTypeUseObjectOnLocation)
moveTaskDone = true;
else
g_vm->_mTaskList->_nextMT = it;
}
break;
case kMotionTypeUseTAI:
if (mt->_flags & kMfReset) {
TilePoint actorLoc = a->getLocation(),
TAILoc;
TileRegion TAIReg;
ActiveItem *TAG = mt->_o.TAI->getGroup();
// Compute in points the region of the TAI
TAIReg.min.u = mt->_o.TAI->_data.instance.u << kTileUVShift;
TAIReg.min.v = mt->_o.TAI->_data.instance.v << kTileUVShift;
TAIReg.max.u = TAIReg.min.u
+ (TAG->_data.group.uSize << kTileUVShift);
TAIReg.max.v = TAIReg.min.v
+ (TAG->_data.group.vSize << kTileUVShift);
TAIReg.min.z = TAIReg.max.z = 0;
// Find the point on the TAI closest to the actor
TAILoc.u = clamp(TAIReg.min.u, actorLoc.u, TAIReg.max.u - 1);
TAILoc.v = clamp(TAIReg.min.v, actorLoc.v, TAIReg.max.v - 1);
TAILoc.z = actorLoc.z;
// Compute the direction from the actor to the TAI
mt->_direction = (TAILoc - actorLoc).quickDir();
mt->_flags &= ~kMfReset;
}
if (a->_currentFacing != mt->_direction)
a->turn(mt->_direction);
else {
// The actor will now be uniterruptable
a->setActionPoints(2);
mt->_o.TAI->use(a->thisID());
moveTaskDone = true;
}
break;
case kMotionTypeDropObject:
if (isWorld(mt->_targetLoc._context)) {
if (mt->_flags & kMfReset) {
mt->_direction = (mt->_targetLoc - a->getLocation()).quickDir();
mt->_flags &= ~kMfReset;
}
if (a->_currentFacing != mt->_direction)
a->turn(mt->_direction);
else {
// The actor will now be uniterruptable
a->setActionPoints(2);
mt->_o.directObject->drop(a->thisID(),
mt->_targetLoc,
mt->moveCount);
if (mt->_motionType == kMotionTypeDropObject)
moveTaskDone = true;
else
g_vm->_mTaskList->_nextMT = it;
}
} else {
// The actor will now be uniterruptable
a->setActionPoints(2);
mt->_o.directObject->drop(a->thisID(),
mt->_targetLoc,
mt->moveCount);
if (mt->_motionType == kMotionTypeDropObject)
moveTaskDone = true;
else
g_vm->_mTaskList->_nextMT = it;
}
CMassWeightIndicator::_bRedraw = true; // tell the mass/weight indicators to refresh
break;
case kMotionTypeDropObjectOnObject:
if (isWorld(mt->_o.indirectObject->IDParent())) {
mt->_direction = (mt->_o.indirectObject->getLocation()
- a->getLocation()).quickDir();
if (a->_currentFacing != mt->_direction)
a->turn(mt->_direction);
else {
// The actor will now be uniterruptable
a->setActionPoints(2);
mt->_o.directObject->dropOn(
a->thisID(),
mt->_o.indirectObject->thisID(),
mt->moveCount);
if (mt->_motionType == kMotionTypeDropObjectOnObject)
moveTaskDone = true;
else
g_vm->_mTaskList->_nextMT = it;
}
} else {
// The actor will now be uniterruptable
a->setActionPoints(2);
mt->_o.directObject->dropOn(
a->thisID(),
mt->_o.indirectObject->thisID(),
mt->moveCount);
if (mt->_motionType == kMotionTypeDropObjectOnObject)
moveTaskDone = true;
else
g_vm->_mTaskList->_nextMT = it;
}
CMassWeightIndicator::_bRedraw = true; // tell the mass/weight indicators to refresh
break;
case kMotionTypeDropObjectOnTAI:
if (mt->_flags & kMfReset) {
mt->_direction = (mt->_targetLoc - a->getLocation()).quickDir();
mt->_flags &= ~kMfReset;
}
if (a->_currentFacing != mt->_direction)
a->turn(mt->_direction);
else {
// The actor will now be uniterruptable
a->setActionPoints(2);
mt->_o.directObject->dropOn(
a->thisID(),
mt->_o.TAI,
mt->_targetLoc);
if (mt->_motionType == kMotionTypeDropObjectOnTAI)
moveTaskDone = true;
else
g_vm->_mTaskList->_nextMT = it;
}
break;
case kMotionTypeTwoHandedSwing:
mt->twoHandedSwingAction();
break;
case kMotionTypeOneHandedSwing:
mt->oneHandedSwingAction();
break;
case kMotionTypeFireBow:
mt->fireBowAction();
break;
case kMotionTypeCastSpell:
mt->castSpellAction();
break;
case kMotionTypeUseWand:
mt->useWandAction();
break;
case kMotionTypeTwoHandedParry:
mt->twoHandedParryAction();
break;
case kMotionTypeOneHandedParry:
mt->oneHandedParryAction();
break;
case kMotionTypeShieldParry:
mt->shieldParryAction();
break;
case kMotionTypeDodge:
mt->dodgeAction();
break;
case kMotionTypeAcceptHit:
mt->acceptHitAction();
break;
case kMotionTypeFallDown:
mt->fallDownAction();
break;
case kMotionTypeDie:
if (mt->_flags & kMfReset) {
if (a->isActionAvailable(kActionDie)) {
a->setAction(kActionDie, 0);
a->setInterruptablity(false);
mt->_flags &= ~kMfReset;
} else {
moveTaskDone = true;
a->setInterruptablity(true);
if (!a->hasEffect(kActorDisappearOnDeath)) {
a->setAction(kActionDead, 0);
a->die();
} else {
a->die();
a->dropInventory();
a->deleteObjectRecursive();
}
}
} else if (a->nextAnimationFrame()) {
moveTaskDone = true;
a->setInterruptablity(true);
if (!a->hasEffect(kActorDisappearOnDeath)) {
a->setAction(kActionDead, 0);
a->die();
} else {
a->die();
a->dropInventory();
a->deleteObjectRecursive();
}
}
break;
}
if (moveTaskDone) mt->remove();
}
}
//-----------------------------------------------------------------------
// Manages any object which has no supporting surface.
// Returns true if object is still falling.
bool MotionTask::freeFall(TilePoint &newPos, StandingTileInfo &sti) {
int16 tHeight;
TilePoint tPos;
uint8 objCrossSection;
tHeight = tileSlopeHeight(newPos, _object, &sti);
if (_object->_data.objectFlags & kObjectFloating) return false;
_velocity.u = (newPos.u - _object->_data.location.u) * 2 / 3;
_velocity.v = (newPos.v - _object->_data.location.v) * 2 / 3;
_velocity.z = (newPos.z - _object->_data.location.z) * 2 / 3;
// _velocity.z = 0;
// If terrain is HIGHER (or even sligtly lower) than we are
// currently at, then try climbing it.
if (tHeight >= newPos.z - kGravity * 4) {
supported:
if (_motionType != kMotionTypeWalk
|| tHeight <= newPos.z
|| !(_flags & kMfInWater)) {
if (tHeight > newPos.z + kMaxStepHeight) {
unstickObject(_object);
tHeight = tileSlopeHeight(newPos, _object, &sti);
}
newPos.z = tHeight;
// setObjectSurface( _object, sti );
return false;
} else {
_motionType = kMotionTypeRise;
_immediateLocation.z = tHeight;
_object->move(newPos);
return true;
}
}
// Otherwise, begin a fall sequence...
tPos = newPos;
// Attempt to solve cases where he gets stuck in falling,
// by checking the contact of what he's about to fall on.
if (tPos.z > tHeight) tPos.z--;
// See if we fell on something.
if (checkContact(_object, tPos) == kBlockageNone) {
falling:
if (_motionType != kMotionTypeWalk
|| newPos.z > kGravity * 4
|| tHeight >= 0) {
_motionType = kMotionTypeThrown;
// newPos = tPos;
_object->move(tPos);
return true;
} else {
newPos = tPos;
return false;
}
}
// If we fall on something, reduce velocity due to impact.
// Try a couple of probes to see if we can fall in
// other directions.
objCrossSection = _object->proto()->crossSection;
tPos.u += objCrossSection;
if (!checkBlocked(_object, tPos)
&& !checkContact(_object, tPos))
goto falling;
tPos.u -= objCrossSection * 2;
if (!checkBlocked(_object, tPos)
&& !checkContact(_object, tPos))
goto falling;
tPos.u += objCrossSection;
tPos.v += objCrossSection;
if (!checkBlocked(_object, tPos)
&& !checkContact(_object, tPos))
goto falling;
tPos.v -= objCrossSection * 2;
if (!checkBlocked(_object, tPos)
&& !checkContact(_object, tPos))
goto falling;
// There is no support for the object and there is no place to fall
// so cheat and pretend this whole mess never happened.
tPos = newPos;
tPos.u += objCrossSection;
tHeight = tileSlopeHeight(tPos, _object, &sti);
if (tHeight <= tPos.z + kMaxStepHeight
&& tHeight >= tPos.z - kGravity * 4) {
newPos = tPos;
goto supported;
}
tPos.u -= objCrossSection * 2;
tHeight = tileSlopeHeight(tPos, _object, &sti);
if (tHeight <= tPos.z + kMaxStepHeight
&& tHeight >= tPos.z - kGravity * 4) {
newPos = tPos;
goto supported;
}
tPos.u += objCrossSection;
tPos.v += objCrossSection;
tHeight = tileSlopeHeight(tPos, _object, &sti);
if (tHeight <= tPos.z + kMaxStepHeight
&& tHeight >= tPos.z - kGravity * 4) {
newPos = tPos;
goto supported;
}
tPos.v -= objCrossSection * 2;
tHeight = tileSlopeHeight(tPos, _object, &sti);
if (tHeight <= tPos.z + kMaxStepHeight
&& tHeight >= tPos.z - kGravity * 4) {
newPos = tPos;
goto supported;
}
// If we STILL cannot find support for the object, change its
// position and try again. This should be very rare.
newPos.z--;
_object->move(newPos);
unstickObject(_object);
newPos = _object->getLocation();
return true;
}
//-----------------------------------------------------------------------
// Calls the handling routine for each active motion task
void moveActors(int32 deltaTime) {
MotionTask::updatePositions();
}
//-----------------------------------------------------------------------
// Check the actor's area to see if he is intersecting ladder terrain, and
// if so, make him climb it.
bool checkLadder(Actor *a, const TilePoint &loc) {
TileRegion actorTileReg;
uint8 crossSection = a->proto()->crossSection,
height = a->proto()->height;
int16 mapNum = a->getMapNum();
TileInfo *ti;
TilePoint tileLoc;
StandingTileInfo sti = {nullptr, nullptr, {0, 0, 0}, 0};
actorTileReg.min.u = (loc.u - crossSection) >> kTileUVShift;
actorTileReg.min.v = (loc.v - crossSection) >> kTileUVShift;
actorTileReg.max.u = (loc.u + crossSection + kTileUVMask) >> kTileUVShift;
actorTileReg.max.v = (loc.v + crossSection + kTileUVMask) >> kTileUVShift;
actorTileReg.min.z = actorTileReg.max.z = 0;
TileIterator iter(mapNum, actorTileReg);
for (ti = iter.first(&tileLoc, &sti);
ti != nullptr;
ti = iter.next(&tileLoc, &sti)) {
if (!(ti->combinedTerrainMask() & kTerrainLadder)) continue;
if (sti.surfaceHeight + ti->attrs.terrainHeight < loc.z
|| sti.surfaceHeight > loc.z + height)
continue;
uint16 footPrintMask = 0xFFFF,
ladderMask;
TilePoint subTileLoc(
tileLoc.u << kTileSubShift,
tileLoc.v << kTileSubShift,
0);
TileRegion actorSubTileReg;
actorSubTileReg.min.u = (loc.u - crossSection) >> kSubTileShift;
actorSubTileReg.min.v = (loc.v - crossSection) >> kSubTileShift;
actorSubTileReg.max.u =
(loc.u + crossSection + kSubTileMask) >> kSubTileShift;
actorSubTileReg.max.v =
(loc.v + crossSection + kSubTileMask) >> kSubTileShift;
if (actorSubTileReg.min.u >= subTileLoc.u)
footPrintMask &=
uMinMasks[actorSubTileReg.min.u - subTileLoc.u];
if (actorSubTileReg.min.v >= subTileLoc.v)
footPrintMask &=
vMinMasks[actorSubTileReg.min.v - subTileLoc.v];
if (actorSubTileReg.max.u < subTileLoc.u + kTileSubSize)
footPrintMask &=
uMaxMasks[actorSubTileReg.max.u - subTileLoc.u];
if (actorSubTileReg.max.v < subTileLoc.v + kTileSubSize)
footPrintMask &=
vMaxMasks[actorSubTileReg.max.v - subTileLoc.v];
ladderMask = ti->attrs.fgdTerrain == kTerrNumLadder
? ti->attrs.terrainMask
: ~ti->attrs.terrainMask;
if (footPrintMask & ladderMask) {
if (!(~ladderMask & 0xF000)) {
a->_currentFacing = 7;
a->move(
TilePoint(
(tileLoc.u << kTileUVShift)
+ kTileUVSize
- crossSection,
(tileLoc.v << kTileUVShift) + kTileUVSize / 2,
loc.z));
} else if (!(~ladderMask & 0x000F)) {
a->_currentFacing = 3;
a->move(
TilePoint(
(tileLoc.u << kTileUVShift) + crossSection,
(tileLoc.v << kTileUVShift) + kTileUVSize / 2,
loc.z));
} else if (!(~ladderMask & 0x8888)) {
a->_currentFacing = 1;
a->move(
TilePoint(
(tileLoc.u << kTileUVShift) + kTileUVSize / 2,
(tileLoc.v << kTileUVShift)
+ kTileUVSize
- crossSection,
loc.z));
} else {
a->_currentFacing = 3;
a->move(
TilePoint(
(tileLoc.u << kTileUVShift) + kTileUVSize / 2,
(tileLoc.v << kTileUVShift) + crossSection,
loc.z));
}
if (loc.z
< tileSlopeHeight(a->getLocation(), a) + kMaxStepHeight)
MotionTask::upLadder(*a);
else
MotionTask::downLadder(*a);
return true;
}
}
return false;
}
void pauseInterruptableMotions() {
interruptableMotionsPaused = true;
}
void resumeInterruptableMotions() {
interruptableMotionsPaused = false;
}
/* ===================================================================== *
MotionTask list management functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Initialize the motion task list
void initMotionTasks() {
// Simply call the default MotionTaskList constructor
//new (g_vm->_mTaskList) MotionTaskList;
}
void saveMotionTasks(Common::OutSaveFile *outS) {
debugC(2, kDebugSaveload, "Saving MotionTasks");
outS->write("MOTN", 4);
CHUNK_BEGIN;
g_vm->_mTaskList->write(out);
CHUNK_END;
}
void loadMotionTasks(Common::InSaveFile *in, int32 chunkSize) {
debugC(2, kDebugSaveload, "Loading MotionTasks");
// If there is no saved data, simply call the default constructor
if (chunkSize == 0) {
//new (g_vm->_mTaskList) MotionTaskList;
return;
}
// Reconstruct g_vm->_mTaskList from archived data
g_vm->_mTaskList->read(in);
}
//-----------------------------------------------------------------------
// Cleanup the motion task list
void cleanupMotionTasks() {
// Simply call stackList's cleanup
g_vm->_mTaskList->cleanup();
}
} // end of namespace Saga2