warzone2100/src/move.cpp

2412 lines
66 KiB
C++

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2013 Warzone 2100 Project
Warzone 2100 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 2 of the License, or
(at your option) any later version.
Warzone 2100 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
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* Move.c
*
* Routines for moving units about the map
*
*/
#include "lib/framework/frame.h"
#include "lib/framework/trig.h"
#include "lib/framework/math_ext.h"
#include "lib/gamelib/gtime.h"
#include "lib/gamelib/animobj.h"
#include "lib/netplay/netplay.h"
#include "lib/sound/audio.h"
#include "lib/sound/audio_id.h"
#include "console.h"
#include "move.h"
#include "objects.h"
#include "visibility.h"
#include "map.h"
#include "fpath.h"
#include "loop.h"
#include "geometry.h"
#include "anim_id.h"
#include "action.h"
#include "display3d.h"
#include "order.h"
#include "astar.h"
#include "combat.h"
#include "mapgrid.h"
#include "display.h" // needed for widgetsOn flag.
#include "effects.h"
#include "power.h"
#include "scores.h"
#include "multiplay.h"
#include "multigifts.h"
#include "random.h"
#include "mission.h"
#include "drive.h"
#include "qtscript.h"
/* max and min vtol heights above terrain */
#define VTOL_HEIGHT_MIN 250
#define VTOL_HEIGHT_LEVEL 300
#define VTOL_HEIGHT_MAX 350
// Maximum size of an object for collision
#define OBJ_MAXRADIUS (TILE_UNITS * 4)
// how long a shuffle can propagate before they all stop
#define MOVE_SHUFFLETIME 10000
// Length of time a droid has to be stationery to be considered blocked
#define BLOCK_TIME 6000
#define SHUFFLE_BLOCK_TIME 2000
// How long a droid has to be stationary before stopping trying to move
#define BLOCK_PAUSETIME 1500
#define BLOCK_PAUSERELEASE 500
// How far a droid has to move before it is no longer 'stationary'
#define BLOCK_DIST 64
// How far a droid has to rotate before it is no longer 'stationary'
#define BLOCK_DIR 90
// How far out from an obstruction to start avoiding it
#define AVOID_DIST (TILE_UNITS*2)
// Speed to approach a final way point, if possible.
#define MIN_END_SPEED 60
// distance from final way point to start slowing
#define END_SPEED_RANGE (3 * TILE_UNITS)
// how long to pause after firing a FOM_NO weapon
#define FOM_MOVEPAUSE 1500
// distance to consider droids for a shuffle
#define SHUFFLE_DIST (3*TILE_UNITS/2)
// how far to move for a shuffle
#define SHUFFLE_MOVE (2*TILE_UNITS/2)
/// Extra precision added to movement calculations.
#define EXTRA_BITS 8
#define EXTRA_PRECISION (1 << EXTRA_BITS)
/* Function prototypes */
static void moveUpdatePersonModel(DROID *psDroid, SDWORD speed, uint16_t direction);
const char *moveDescription(MOVE_STATUS status)
{
switch (status)
{
case MOVEINACTIVE : return "Inactive";
case MOVENAVIGATE : return "Navigate";
case MOVETURN : return "Turn";
case MOVEPAUSE : return "Pause";
case MOVEPOINTTOPOINT : return "P2P";
case MOVETURNTOTARGET : return "Turn2target";
case MOVEHOVER : return "Hover";
case MOVEDRIVE : return "Drive";
case MOVEWAITROUTE : return "Waitroute";
case MOVESHUFFLE : return "Shuffle";
}
return "Error"; // satisfy compiler
}
/** Set a target location in world coordinates for a droid to move to
* @return true if the routing was successful, if false then the calling code
* should not try to route here again for a while
* @todo Document what "should not try to route here again for a while" means.
*/
static bool moveDroidToBase(DROID *psDroid, UDWORD x, UDWORD y, bool bFormation, FPATH_MOVETYPE moveType)
{
FPATH_RETVAL retVal = FPR_OK;
CHECK_DROID(psDroid);
// in multiPlayer make Transporter move like the vtols
if ((psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER) && game.maxPlayers == 0)
{
fpathSetDirectRoute(psDroid, x, y);
psDroid->sMove.Status = MOVENAVIGATE;
psDroid->sMove.pathIndex = 0;
return true;
}
// NOTE: While Vtols can fly, then can't go through things, like the transporter.
else if ((game.maxPlayers > 0 && (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)))
{
fpathSetDirectRoute(psDroid, x, y);
retVal = FPR_OK;
}
else
{
retVal = fpathDroidRoute(psDroid, x, y, moveType);
}
if ( retVal == FPR_OK )
{
// bit of a hack this - john
// if astar doesn't have a complete route, it returns a route to the nearest clear tile.
// the location of the clear tile is in DestinationX,DestinationY.
// reset x,y to this position so the formation gets set up correctly
x = psDroid->sMove.destination.x;
y = psDroid->sMove.destination.y;
objTrace(psDroid->id, "unit %d: path ok - base Speed %u, speed %d, target(%u|%d, %u|%d)",
(int)psDroid->id, psDroid->baseSpeed, psDroid->sMove.speed, x, map_coord(x), y, map_coord(y));
psDroid->sMove.Status = MOVENAVIGATE;
psDroid->sMove.pathIndex = 0;
}
else if (retVal == FPR_WAIT)
{
// the route will be calculated by the path-finding thread
psDroid->sMove.Status = MOVEWAITROUTE;
psDroid->sMove.destination.x = x;
psDroid->sMove.destination.y = y;
}
else // if (retVal == FPR_FAILED)
{
objTrace(psDroid->id, "Path to (%d, %d) failed for droid %d", (int)x, (int)y, (int)psDroid->id);
psDroid->sMove.Status = MOVEINACTIVE;
actionDroid(psDroid, DACTION_SULK);
return(false);
}
CHECK_DROID(psDroid);
return true;
}
/** Move a droid to a location, joining a formation
* @see moveDroidToBase() for the parameter and return value specification
*/
bool moveDroidTo(DROID* psDroid, UDWORD x, UDWORD y, FPATH_MOVETYPE moveType)
{
return moveDroidToBase(psDroid, x, y, true, moveType);
}
/** Move a droid to a location, not joining a formation
* @see moveDroidToBase() for the parameter and return value specification
*/
bool moveDroidToNoFormation(DROID* psDroid, UDWORD x, UDWORD y, FPATH_MOVETYPE moveType)
{
ASSERT(x > 0 && y > 0, "Bad movement position");
return moveDroidToBase(psDroid, x, y, false, moveType);
}
/** Move a droid directly to a location.
* @note This is (or should be) used for VTOLs only.
*/
void moveDroidToDirect(DROID* psDroid, UDWORD x, UDWORD y)
{
ASSERT( psDroid != NULL && isVtolDroid(psDroid),
"moveUnitToDirect: only valid for a vtol unit" );
fpathSetDirectRoute(psDroid, x, y);
psDroid->sMove.Status = MOVENAVIGATE;
psDroid->sMove.pathIndex = 0;
}
/** Turn a droid towards a given location.
*/
void moveTurnDroid(DROID *psDroid, UDWORD x, UDWORD y)
{
uint16_t moveDir = calcDirection(psDroid->pos.x, psDroid->pos.y, x, y);
if (psDroid->rot.direction != moveDir)
{
psDroid->sMove.target.x = x;
psDroid->sMove.target.y = y;
psDroid->sMove.Status = MOVETURNTOTARGET;
}
}
// Tell a droid to move out the way for a shuffle
static void moveShuffleDroid(DROID *psDroid, Vector2i s)
{
SDWORD mx, my;
bool frontClear = true, leftClear = true, rightClear = true;
SDWORD lvx,lvy, rvx,rvy, svx,svy;
SDWORD shuffleMove;
CHECK_DROID(psDroid);
uint16_t shuffleDir = iAtan2(s);
int32_t shuffleMag = iHypot(s);
if (shuffleMag == 0)
{
return;
}
shuffleMove = SHUFFLE_MOVE;
// calculate the possible movement vectors
svx = s.x * shuffleMove / shuffleMag; // Straight in the direction of s.
svy = s.y * shuffleMove / shuffleMag;
lvx = -svy; // 90° to the... right?
lvy = svx;
rvx = svy; // 90° to the... left?
rvy = -svx;
// check for blocking tiles
if (fpathBlockingTile(map_coord((SDWORD)psDroid->pos.x + lvx),
map_coord((SDWORD)psDroid->pos.y + lvy), getPropulsionStats(psDroid)->propulsionType))
{
leftClear = false;
}
else if (fpathBlockingTile(map_coord((SDWORD)psDroid->pos.x + rvx),
map_coord((SDWORD)psDroid->pos.y + rvy), getPropulsionStats(psDroid)->propulsionType))
{
rightClear = false;
}
else if (fpathBlockingTile(map_coord((SDWORD)psDroid->pos.x + svx),
map_coord((SDWORD)psDroid->pos.y + svy), getPropulsionStats(psDroid)->propulsionType))
{
frontClear = false;
}
// find any droids that could block the shuffle
static GridList gridList; // static to avoid allocations.
gridList = gridStartIterate(psDroid->pos.x, psDroid->pos.y, SHUFFLE_DIST);
for (GridIterator gi = gridList.begin(); gi != gridList.end(); ++gi)
{
DROID *psCurr = castDroid(*gi);
if (psCurr == NULL || psCurr->died || psCurr == psDroid)
{
continue;
}
uint16_t droidDir = iAtan2(removeZ(psCurr->pos - psDroid->pos));
int diff = angleDelta(shuffleDir - droidDir);
if (diff > -DEG(135) && diff < -DEG(45))
{
leftClear = false;
}
else if (diff > DEG(45) && diff < DEG(135))
{
rightClear = false;
}
}
// calculate a target
if (leftClear)
{
mx = lvx;
my = lvy;
}
else if (rightClear)
{
mx = rvx;
my = rvy;
}
else if (frontClear)
{
mx = svx;
my = svy;
}
else
{
// nowhere to shuffle to, quit
return;
}
// check the location for vtols
Vector2i tar = removeZ(psDroid->pos) + Vector2i(mx, my);
if (isVtolDroid(psDroid))
{
actionVTOLLandingPos(psDroid, &tar);
}
// set up the move state
if (psDroid->sMove.Status != MOVESHUFFLE)
{
psDroid->sMove.shuffleStart = gameTime;
}
psDroid->sMove.Status = MOVESHUFFLE;
psDroid->sMove.src = removeZ(psDroid->pos);
psDroid->sMove.target = tar;
psDroid->sMove.numPoints = 0;
psDroid->sMove.pathIndex = 0;
CHECK_DROID(psDroid);
}
/** Stop a droid from moving.
*/
void moveStopDroid(DROID *psDroid)
{
PROPULSION_STATS *psPropStats;
CHECK_DROID(psDroid);
psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
ASSERT( psPropStats != NULL,
"moveUpdateUnit: invalid propulsion stats pointer" );
if ( psPropStats->propulsionType == PROPULSION_TYPE_LIFT )
{
psDroid->sMove.Status = MOVEHOVER;
}
else
{
psDroid->sMove.Status = MOVEINACTIVE;
}
}
/** Stops a droid dead in its tracks.
* Doesn't allow for any little skidding bits.
* @param psDroid the droid to stop from moving
*/
void moveReallyStopDroid(DROID *psDroid)
{
CHECK_DROID(psDroid);
psDroid->sMove.Status = MOVEINACTIVE;
psDroid->sMove.speed = 0;
}
#define PITCH_LIMIT 150
/* Get pitch and roll from direction and tile data */
void updateDroidOrientation(DROID *psDroid)
{
int32_t hx0, hx1, hy0, hy1;
int newPitch, deltaPitch, pitchLimit;
int32_t dzdx, dzdy, dzdv, dzdw;
const int d = 20;
int32_t vX, vY;
if(psDroid->droidType == DROID_PERSON || cyborgDroid(psDroid) || psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER
|| isFlying(psDroid))
{
/* The ground doesn't affect the pitch/roll of these droids*/
return;
}
// Find the height of 4 points around the droid.
// hy0
// hx0 * hx1 (* = droid)
// hy1
hx1 = map_Height(psDroid->pos.x + d, psDroid->pos.y);
hx0 = map_Height(MAX(0, psDroid->pos.x - d), psDroid->pos.y);
hy1 = map_Height(psDroid->pos.x, psDroid->pos.y + d);
hy0 = map_Height(psDroid->pos.x, MAX(0, psDroid->pos.y - d));
//update height in case were in the bottom of a trough
psDroid->pos.z = MAX(psDroid->pos.z, (hx0 + hx1)/2);
psDroid->pos.z = MAX(psDroid->pos.z, (hy0 + hy1)/2);
// Vector of length 65536 pointing in direction droid is facing.
vX = iSin(psDroid->rot.direction);
vY = iCos(psDroid->rot.direction);
// Calculate pitch of ground.
dzdx = hx1 - hx0; // 2*d*∂z(x, y)/∂x of ground
dzdy = hy1 - hy0; // 2*d*∂z(x, y)/∂y of ground
dzdv = dzdx*vX + dzdy*vY; // 2*d*∂z(x, y)/∂v << 16 of ground, where v is the direction the droid is facing.
newPitch = iAtan2(dzdv, (2*d) << 16); // pitch = atan(∂z(x, y)/∂v)/2π << 16
deltaPitch = angleDelta(newPitch - psDroid->rot.pitch);
// Limit the rate the front comes down to simulate momentum
pitchLimit = gameTimeAdjustedIncrement(DEG(PITCH_LIMIT));
deltaPitch = MAX(deltaPitch, -pitchLimit);
// Update pitch.
psDroid->rot.pitch += deltaPitch;
// Calculate and update roll of ground (not taking pitch into account, but good enough).
dzdw = dzdx * vY - dzdy * vX; // 2*d*∂z(x, y)/∂w << 16 of ground, where w is at right angles to the direction the droid is facing.
psDroid->rot.roll = iAtan2(dzdw, (2*d) << 16); // pitch = atan(∂z(x, y)/∂w)/2π << 16
}
struct BLOCKING_CALLBACK_DATA
{
PROPULSION_TYPE propulsionType;
bool blocking;
Vector2i src;
Vector2i dst;
};
static bool moveBlockingTileCallback(Vector2i pos, int32_t dist, void *data_)
{
BLOCKING_CALLBACK_DATA *data = (BLOCKING_CALLBACK_DATA *)data_;
data->blocking |= pos != data->src && pos != data->dst && fpathBlockingTile(map_coord(pos.x), map_coord(pos.y), data->propulsionType);
return !data->blocking;
}
// Returns -1 - distance if the direct path to the waypoint is blocked, otherwise returns the distance to the waypoint.
static int32_t moveDirectPathToWaypoint(DROID *psDroid, unsigned positionIndex)
{
Vector2i src = removeZ(psDroid->pos);
Vector2i dst = psDroid->sMove.asPath[positionIndex];
Vector2i delta = dst - src;
int32_t dist = iHypot(delta);
BLOCKING_CALLBACK_DATA data;
data.propulsionType = getPropulsionStats(psDroid)->propulsionType;
data.blocking = false;
data.src = src;
data.dst = dst;
rayCast(src, dst, &moveBlockingTileCallback, &data);
return data.blocking? -1 - dist : dist;
}
// Returns true if still able to find the path.
static bool moveBestTarget(DROID *psDroid)
{
int positionIndex = std::max(psDroid->sMove.pathIndex - 1, 0);
int32_t dist = moveDirectPathToWaypoint(psDroid, positionIndex);
if (dist >= 0)
{
// Look ahead in the path.
while (dist >= 0 && dist < TILE_UNITS*5)
{
++positionIndex;
if (positionIndex >= psDroid->sMove.numPoints)
{
dist = -1;
break; // Reached end of path.
}
dist = moveDirectPathToWaypoint(psDroid, positionIndex);
}
if (dist < 0)
{
--positionIndex;
}
}
else
{
// Lost sight of path, backtrack.
while (dist < 0 && dist >= -TILE_UNITS*7 && positionIndex > 0)
{
--positionIndex;
dist = moveDirectPathToWaypoint(psDroid, positionIndex);
}
if (dist < 0)
{
return false; // Couldn't find path, and backtracking didn't help.
}
}
psDroid->sMove.pathIndex = positionIndex + 1;
psDroid->sMove.src = removeZ(psDroid->pos);
psDroid->sMove.target = psDroid->sMove.asPath[positionIndex];
return true;
}
/* Get the next target point from the route */
static bool moveNextTarget(DROID *psDroid)
{
CHECK_DROID(psDroid);
// See if there is anything left in the move list
if (psDroid->sMove.pathIndex == psDroid->sMove.numPoints)
{
return false;
}
if (psDroid->sMove.pathIndex == 0)
{
psDroid->sMove.src = removeZ(psDroid->pos);
}
else
{
psDroid->sMove.src = psDroid->sMove.asPath[psDroid->sMove.pathIndex - 1];
}
psDroid->sMove.target = psDroid->sMove.asPath[psDroid->sMove.pathIndex];
++psDroid->sMove.pathIndex;
CHECK_DROID(psDroid);
return true;
}
// Watermelon:fix these magic number...the collision radius should be based on pie imd radius not some static int's...
static int mvPersRad = 20, mvCybRad = 30, mvSmRad = 40, mvMedRad = 50, mvLgRad = 60;
// Get the radius of a base object for collision
static SDWORD moveObjRadius(const BASE_OBJECT* psObj)
{
switch (psObj->type)
{
case OBJ_DROID:
{
const DROID* psDroid = (const DROID*)psObj;
if (psDroid->droidType == DROID_PERSON)
{
return mvPersRad;
}
else if (cyborgDroid(psDroid))
{
return mvCybRad;
}
else
{
const BODY_STATS* psBdyStats = &asBodyStats[psDroid->asBits[COMP_BODY]];
switch (psBdyStats->size)
{
case SIZE_LIGHT:
return mvSmRad;
case SIZE_MEDIUM:
return mvMedRad;
case SIZE_HEAVY:
return mvLgRad;
case SIZE_SUPER_HEAVY:
return 130;
default:
return psDroid->sDisplay.imd->radius;
}
}
break;
}
case OBJ_STRUCTURE:
return psObj->sDisplay.imd->radius / 2;
case OBJ_FEATURE:
return psObj->sDisplay.imd->radius / 2;
default:
ASSERT(false, "unknown object type");
return 0;
}
}
// see if a Droid has run over a person
static void moveCheckSquished(DROID *psDroid, int32_t emx, int32_t emy)
{
int32_t rad, radSq, objR, xdiff, ydiff, distSq;
const int32_t droidR = moveObjRadius((BASE_OBJECT *)psDroid);
const int32_t mx = gameTimeAdjustedAverage(emx, EXTRA_PRECISION);
const int32_t my = gameTimeAdjustedAverage(emy, EXTRA_PRECISION);
static GridList gridList; // static to avoid allocations.
gridList = gridStartIterate(psDroid->pos.x, psDroid->pos.y, OBJ_MAXRADIUS);
for (GridIterator gi = gridList.begin(); gi != gridList.end(); ++gi)
{
BASE_OBJECT *psObj = *gi;
if (psObj->type != OBJ_DROID || ((DROID *)psObj)->droidType != DROID_PERSON)
{
// ignore everything but people
continue;
}
ASSERT(psObj->type == OBJ_DROID && ((DROID *)psObj)->droidType == DROID_PERSON, "squished - eerk");
objR = moveObjRadius(psObj);
rad = droidR + objR;
radSq = rad*rad;
xdiff = psDroid->pos.x + mx - psObj->pos.x;
ydiff = psDroid->pos.y + my - psObj->pos.y;
distSq = xdiff*xdiff + ydiff*ydiff;
if (((2*radSq)/3) > distSq)
{
if ((psDroid->player != psObj->player) && !aiCheckAlliances(psDroid->player, psObj->player))
{
// run over a bloke - kill him
destroyDroid((DROID *)psObj, gameTime);
scoreUpdateVar(WD_BARBARIANS_MOWED_DOWN);
}
}
}
}
// See if the droid has been stopped long enough to give up on the move
static bool moveBlocked(DROID *psDroid)
{
SDWORD xdiff,ydiff, diffSq;
UDWORD blockTime;
if (psDroid->sMove.bumpTime == 0 || psDroid->sMove.bumpTime > gameTime)
{
// no bump - can't be blocked
return false;
}
// See if the block can be cancelled
if (abs(angleDelta(psDroid->rot.direction - psDroid->sMove.bumpDir)) > DEG(BLOCK_DIR))
{
// Move on, clear the bump
psDroid->sMove.bumpTime = 0;
psDroid->sMove.lastBump = 0;
return false;
}
xdiff = (SDWORD)psDroid->pos.x - (SDWORD)psDroid->sMove.bumpX;
ydiff = (SDWORD)psDroid->pos.y - (SDWORD)psDroid->sMove.bumpY;
diffSq = xdiff*xdiff + ydiff*ydiff;
if (diffSq > BLOCK_DIST*BLOCK_DIST)
{
// Move on, clear the bump
psDroid->sMove.bumpTime = 0;
psDroid->sMove.lastBump = 0;
return false;
}
if (psDroid->sMove.Status == MOVESHUFFLE)
{
blockTime = SHUFFLE_BLOCK_TIME;
}
else
{
blockTime = BLOCK_TIME;
}
if (gameTime - psDroid->sMove.bumpTime > blockTime)
{
// Stopped long enough - blocked
psDroid->sMove.bumpTime = 0;
psDroid->sMove.lastBump = 0;
if (!isHumanPlayer(psDroid->player) && bMultiPlayer)
{
psDroid->lastFrustratedTime = gameTime;
objTrace(psDroid->id, "FRUSTRATED");
}
else
{
objTrace(psDroid->id, "BLOCKED");
}
// if the unit cannot see the next way point - reroute it's got stuck
if ((bMultiPlayer || psDroid->player == selectedPlayer || psDroid->lastFrustratedTime == gameTime)
&& psDroid->sMove.pathIndex != psDroid->sMove.numPoints)
{
objTrace(psDroid->id, "Trying to reroute to (%d,%d)", psDroid->sMove.destination.x, psDroid->sMove.destination.y);
moveDroidTo(psDroid, psDroid->sMove.destination.x, psDroid->sMove.destination.y);
return false;
}
return true;
}
return false;
}
// Calculate the actual movement to slide around
static void moveCalcSlideVector(DROID *psDroid, int32_t objX, int32_t objY, int32_t *pMx, int32_t *pMy)
{
int32_t dirX, dirY, dirMagSq, dotRes;
const int32_t mx = *pMx;
const int32_t my = *pMy;
// Calculate the vector to the obstruction
const int32_t obstX = psDroid->pos.x - objX;
const int32_t obstY = psDroid->pos.y - objY;
// if the target dir is the same, don't need to slide
if (obstX*mx + obstY*my >= 0)
{
return;
}
// Choose the tangent vector to this on the same side as the target
dotRes = obstY * mx - obstX * my;
if (dotRes >= 0)
{
dirX = obstY;
dirY = -obstX;
}
else
{
dirX = -obstY;
dirY = obstX;
dotRes = -dotRes;
}
dirMagSq = MAX(1, dirX * dirX + dirY * dirY);
// Calculate the component of the movement in the direction of the tangent vector
*pMx = (int64_t)dirX * dotRes / dirMagSq;
*pMy = (int64_t)dirY * dotRes / dirMagSq;
}
static void moveOpenGates(DROID *psDroid, Vector2i tile)
{
// is the new tile a gate?
if (!worldOnMap(tile.x, tile.y))
{
return;
}
MAPTILE *psTile = mapTile(tile);
if (!isFlying(psDroid) && psTile && psTile->psObject && psTile->psObject->type == OBJ_STRUCTURE && aiCheckAlliances(psTile->psObject->player, psDroid->player))
{
requestOpenGate((STRUCTURE *)psTile->psObject); // If it's a friendly gate, open it. (It would be impolite to open an enemy gate.)
}
}
static void moveOpenGates(DROID *psDroid)
{
Vector2i pos = removeZ(psDroid->pos) + iSinCosR(psDroid->sMove.moveDir, psDroid->sMove.speed * SAS_OPEN_SPEED / GAME_TICKS_PER_SEC);
moveOpenGates(psDroid, map_coord(pos));
}
// see if a droid has run into a blocking tile
// TODO See if this function can be simplified.
static void moveCalcBlockingSlide(DROID *psDroid, int32_t *pmx, int32_t *pmy, uint16_t tarDir, uint16_t *pSlideDir)
{
PROPULSION_TYPE propulsion = getPropulsionStats(psDroid)->propulsionType;
SDWORD horizX,horizY, vertX,vertY;
uint16_t slideDir;
// calculate the new coords and see if they are on a different tile
const int32_t mx = gameTimeAdjustedAverage(*pmx, EXTRA_PRECISION);
const int32_t my = gameTimeAdjustedAverage(*pmy, EXTRA_PRECISION);
const int32_t tx = map_coord(psDroid->pos.x);
const int32_t ty = map_coord(psDroid->pos.y);
const int32_t nx = psDroid->pos.x + mx;
const int32_t ny = psDroid->pos.y + my;
const int32_t ntx = map_coord(nx);
const int32_t nty = map_coord(ny);
const int32_t blkCX = world_coord(ntx) + TILE_UNITS/2;
const int32_t blkCY = world_coord(nty) + TILE_UNITS/2;
CHECK_DROID(psDroid);
// is the new tile a gate?
moveOpenGates(psDroid, Vector2i(ntx, nty));
// is the new tile blocking?
if (!fpathBlockingTile(ntx, nty, propulsion))
{
// not blocking, don't change the move vector
return;
}
// if the droid is shuffling - just stop
if (psDroid->sMove.Status == MOVESHUFFLE)
{
objTrace(psDroid->id, "Was shuffling, now stopped");
psDroid->sMove.Status = MOVEINACTIVE;
}
// note the bump time and position if necessary
if (!isVtolDroid(psDroid) &&
psDroid->sMove.bumpTime == 0)
{
psDroid->sMove.bumpTime = gameTime;
psDroid->sMove.lastBump = 0;
psDroid->sMove.pauseTime = 0;
psDroid->sMove.bumpX = psDroid->pos.x;
psDroid->sMove.bumpY = psDroid->pos.y;
psDroid->sMove.bumpDir = psDroid->rot.direction;
}
if (tx != ntx && ty != nty)
{
// moved diagonally
// figure out where the other two possible blocking tiles are
horizX = mx < 0 ? ntx + 1 : ntx - 1;
horizY = nty;
vertX = ntx;
vertY = my < 0 ? nty + 1 : nty - 1;
if (fpathBlockingTile(horizX, horizY, propulsion) && fpathBlockingTile(vertX, vertY, propulsion))
{
// in a corner - choose an arbitrary slide
if (gameRand(2) == 0)
{
*pmx = 0;
*pmy = -*pmy;
}
else
{
*pmx = -*pmx;
*pmy = 0;
}
}
else if (fpathBlockingTile(horizX, horizY, propulsion))
{
*pmy = 0;
}
else if (fpathBlockingTile(vertX, vertY, propulsion))
{
*pmx = 0;
}
else
{
moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
}
}
else if (tx != ntx)
{
// moved horizontally - see which half of the tile were in
if ((psDroid->pos.y & TILE_MASK) > TILE_UNITS/2)
{
// top half
if (fpathBlockingTile(ntx, nty + 1, propulsion))
{
*pmx = 0;
}
else
{
moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
}
}
else
{
// bottom half
if (fpathBlockingTile(ntx, nty - 1, propulsion))
{
*pmx = 0;
}
else
{
moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
}
}
}
else if (ty != nty)
{
// moved vertically
if ((psDroid->pos.x & TILE_MASK) > TILE_UNITS/2)
{
// top half
if (fpathBlockingTile(ntx + 1, nty, propulsion))
{
*pmy = 0;
}
else
{
moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
}
}
else
{
// bottom half
if (fpathBlockingTile(ntx - 1, nty, propulsion))
{
*pmy = 0;
}
else
{
moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
}
}
}
else // if (tx == ntx && ty == nty)
{
// on a blocking tile - see if we need to jump off
int intx = psDroid->pos.x & TILE_MASK;
int inty = psDroid->pos.y & TILE_MASK;
bool bJumped = false;
int jumpx = psDroid->pos.x;
int jumpy = psDroid->pos.y;
if (intx < TILE_UNITS/2)
{
if (inty < TILE_UNITS/2)
{
// top left
if ((mx < 0) && fpathBlockingTile(tx - 1, ty, propulsion))
{
bJumped = true;
jumpy = (jumpy & ~TILE_MASK) -1;
}
if ((my < 0) && fpathBlockingTile(tx, ty - 1, propulsion))
{
bJumped = true;
jumpx = (jumpx & ~TILE_MASK) -1;
}
}
else
{
// bottom left
if ((mx < 0) && fpathBlockingTile(tx - 1, ty, propulsion))
{
bJumped = true;
jumpy = (jumpy & ~TILE_MASK) + TILE_UNITS;
}
if ((my >= 0) && fpathBlockingTile(tx, ty + 1, propulsion))
{
bJumped = true;
jumpx = (jumpx & ~TILE_MASK) -1;
}
}
}
else
{
if (inty < TILE_UNITS/2)
{
// top right
if ((mx >= 0) && fpathBlockingTile(tx + 1, ty, propulsion))
{
bJumped = true;
jumpy = (jumpy & ~TILE_MASK) -1;
}
if ((my < 0) && fpathBlockingTile(tx, ty - 1, propulsion))
{
bJumped = true;
jumpx = (jumpx & ~TILE_MASK) + TILE_UNITS;
}
}
else
{
// bottom right
if ((mx >= 0) && fpathBlockingTile(tx + 1, ty, propulsion))
{
bJumped = true;
jumpy = (jumpy & ~TILE_MASK) + TILE_UNITS;
}
if ((my >= 0) && fpathBlockingTile(tx, ty + 1, propulsion))
{
bJumped = true;
jumpx = (jumpx & ~TILE_MASK) + TILE_UNITS;
}
}
}
if (bJumped)
{
psDroid->pos.x = MAX(0, jumpx);
psDroid->pos.y = MAX(0, jumpy);
*pmx = 0;
*pmy = 0;
}
else
{
moveCalcSlideVector(psDroid, blkCX,blkCY, pmx,pmy);
}
}
slideDir = iAtan2(*pmx, *pmy);
if (ntx != tx)
{
// hit a horizontal block
if ((tarDir < DEG(90) || tarDir > DEG(270)) &&
(slideDir >= DEG(90) && slideDir <= DEG(270)))
{
slideDir = tarDir;
}
else if ((tarDir >= DEG(90) && tarDir <= DEG(270)) &&
(slideDir < DEG(90) || slideDir > DEG(270)))
{
slideDir = tarDir;
}
}
if (nty != ty)
{
// hit a vertical block
if ((tarDir < DEG(180)) &&
(slideDir >= DEG(180)))
{
slideDir = tarDir;
}
else if ((tarDir >= DEG(180)) &&
(slideDir < DEG(180)))
{
slideDir = tarDir;
}
}
*pSlideDir = slideDir;
CHECK_DROID(psDroid);
}
// see if a droid has run into another droid
// Only consider stationery droids
static void moveCalcDroidSlide(DROID *psDroid, int *pmx, int *pmy)
{
int32_t droidR, rad, radSq, objR, xdiff, ydiff, distSq, spmx, spmy;
bool bLegs;
CHECK_DROID(psDroid);
bLegs = false;
if (psDroid->droidType == DROID_PERSON || cyborgDroid(psDroid))
{
bLegs = true;
}
spmx = gameTimeAdjustedAverage(*pmx, EXTRA_PRECISION);
spmy = gameTimeAdjustedAverage(*pmy, EXTRA_PRECISION);
droidR = moveObjRadius((BASE_OBJECT *)psDroid);
BASE_OBJECT *psObst = NULL;
static GridList gridList; // static to avoid allocations.
gridList = gridStartIterate(psDroid->pos.x, psDroid->pos.y, OBJ_MAXRADIUS);
for (GridIterator gi = gridList.begin(); gi != gridList.end(); ++gi)
{
BASE_OBJECT *psObj = *gi;
if (psObj->died)
{
continue;
}
if (psObj->type == OBJ_DROID)
{
if (((DROID *)psObj)->droidType == DROID_TRANSPORTER || ((DROID *)psObj)->droidType == DROID_SUPERTRANSPORTER)
{
// ignore transporters
continue;
}
if (!bLegs && ((DROID *)psObj)->droidType == DROID_PERSON)
{
// everything else doesn't avoid people
continue;
}
if (psObj->player == psDroid->player
&& psDroid->lastFrustratedTime > 0
&& gameTime - psDroid->lastFrustratedTime < FRUSTRATED_TIME)
{
continue; // clip straight through own units when sufficient frustrated -- using cheat codes!
}
}
else
{
// ignore anything that isn't a droid
continue;
}
objR = moveObjRadius(psObj);
rad = droidR + objR;
radSq = rad*rad;
xdiff = psDroid->pos.x + spmx - psObj->pos.x;
ydiff = psDroid->pos.y + spmy - psObj->pos.y;
distSq = xdiff * xdiff + ydiff * ydiff;
if (xdiff * spmx + ydiff * spmy >= 0)
{
// object behind
continue;
}
if (radSq > distSq)
{
if (psObst != NULL)
{
// hit more than one droid - stop
*pmx = 0;
*pmy = 0;
psObst = NULL;
break;
}
else
{
psObst = psObj;
// note the bump time and position if necessary
if (psDroid->sMove.bumpTime == 0)
{
psDroid->sMove.bumpTime = gameTime;
psDroid->sMove.lastBump = 0;
psDroid->sMove.pauseTime = 0;
psDroid->sMove.bumpX = psDroid->pos.x;
psDroid->sMove.bumpY = psDroid->pos.y;
psDroid->sMove.bumpDir = psDroid->rot.direction;
}
else
{
psDroid->sMove.lastBump = (UWORD)(gameTime - psDroid->sMove.bumpTime);
}
// tell inactive droids to get out the way
if (psObst->type == OBJ_DROID)
{
DROID *psShuffleDroid = (DROID *)psObst;
if (aiCheckAlliances(psObst->player, psDroid->player)
&& psShuffleDroid->action != DACTION_WAITDURINGREARM
&& psShuffleDroid->sMove.Status == MOVEINACTIVE)
{
moveShuffleDroid(psShuffleDroid, psDroid->sMove.target - removeZ(psDroid->pos));
}
}
}
}
}
if (psObst != NULL)
{
// Try to slide round it
moveCalcSlideVector(psDroid, psObst->pos.x, psObst->pos.y, pmx, pmy);
}
CHECK_DROID(psDroid);
}
// get an obstacle avoidance vector
static Vector2i moveGetObstacleVector(DROID *psDroid, Vector2i dest)
{
int32_t numObst = 0, distTot = 0;
Vector2i dir(0, 0);
PROPULSION_STATS * psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
ASSERT(psPropStats, "invalid propulsion stats pointer");
int ourMaxSpeed = psPropStats->maxSpeed;
int ourRadius = moveObjRadius(psDroid);
if (ourMaxSpeed == 0)
{
return dest; // No point deciding which way to go, if we can't move...
}
// scan the neighbours for obstacles
static GridList gridList; // static to avoid allocations.
gridList = gridStartIterate(psDroid->pos.x, psDroid->pos.y, AVOID_DIST);
for (GridIterator gi = gridList.begin(); gi != gridList.end(); ++gi)
{
if (*gi == psDroid)
{
continue; // Don't try to avoid ourselves.
}
DROID *psObstacle = castDroid(*gi);
if (psObstacle == NULL)
{
// Object wrong type to worry about.
continue;
}
// vtol droids only avoid each other and don't affect ground droids
if (isVtolDroid(psDroid) != isVtolDroid(psObstacle))
{
continue;
}
if ((psObstacle->droidType == DROID_TRANSPORTER || psObstacle->droidType == DROID_SUPERTRANSPORTER) ||
(psObstacle->droidType == DROID_PERSON &&
psObstacle->player != psDroid->player))
{
// don't avoid people on the other side - run over them
continue;
}
PROPULSION_STATS *obstaclePropStats = asPropulsionStats + psObstacle->asBits[COMP_PROPULSION];
int obstacleMaxSpeed = obstaclePropStats->maxSpeed;
int obstacleRadius = moveObjRadius(psObstacle);
int totalRadius = ourRadius + obstacleRadius;
// Try to guess where the obstacle will be when we get close.
// Velocity guess 1: Guess the velocity the droid is actually moving at.
Vector2i obstVelocityGuess1 = iSinCosR(psObstacle->sMove.moveDir, psObstacle->sMove.speed);
// Velocity guess 2: Guess the velocity the droid wants to move at.
Vector2i obstTargetDiff = psObstacle->sMove.target - psObstacle->pos;
Vector2i obstVelocityGuess2 = iSinCosR(iAtan2(obstTargetDiff), obstacleMaxSpeed * std::min(iHypot(obstTargetDiff), AVOID_DIST)/AVOID_DIST);
if (moveBlocked(psObstacle))
{
obstVelocityGuess2 = Vector2i(0, 0); // This obstacle isn't going anywhere, even if it wants to.
//obstVelocityGuess2 = -obstVelocityGuess2;
}
// Guess the average of the two guesses.
Vector2i obstVelocityGuess = (obstVelocityGuess1 + obstVelocityGuess2) / 2;
// Find the guessed obstacle speed and direction, clamped to half our speed.
int obstSpeedGuess = std::min(iHypot(obstVelocityGuess), ourMaxSpeed/2);
uint16_t obstDirectionGuess = iAtan2(obstVelocityGuess);
// Position of obstacle relative to us.
Vector2i diff = removeZ(psObstacle->pos - psDroid->pos);
// Find very approximate position of obstacle relative to us when we get close, based on our guesses.
Vector2i deltaDiff = iSinCosR(obstDirectionGuess, (int64_t)std::max(iHypot(diff) - totalRadius*2/3, 0)*obstSpeedGuess / ourMaxSpeed);
if (!fpathBlockingTile(map_coord(psObstacle->pos.x + deltaDiff.x), map_coord(psObstacle->pos.y + deltaDiff.y), obstaclePropStats->propulsionType)) // Don't assume obstacle can go through cliffs.
{
diff += deltaDiff;
}
if (diff * dest < 0)
{
// object behind
continue;
}
int centreDist = std::max(iHypot(diff), 1);
int dist = std::max(centreDist - totalRadius, 1);
dir += diff * 65536 / (centreDist * dist);
distTot += 65536 / dist;
numObst += 1;
}
if (dir == Vector2i(0, 0) || numObst == 0)
{
return dest;
}
dir = Vector2i(dir.x / numObst, dir.y / numObst);
distTot /= numObst;
// Create the avoid vector
Vector2i o(dir.y, -dir.x);
Vector2i avoid = dest*o < 0? -o : o;
// Normalise dest and avoid.
dest = dest * 32768/(iHypot(dest) + 1);
avoid = avoid * 32768/(iHypot(avoid) + 1);
// combine the avoid vector and the target vector
int ratio = std::min(distTot * ourRadius/2, 65536);
return dest * (65536 - ratio) + avoid * ratio;
}
/*!
* Get a direction for a droid to avoid obstacles etc.
* \param psDroid Which droid to examine
* \return The normalised direction vector
*/
static uint16_t moveGetDirection(DROID *psDroid)
{
Vector2i src = removeZ(psDroid->pos); // Do not want precice precision here, would overflow.
Vector2i target = psDroid->sMove.target;
Vector2i dest = target - src;
// Transporters don't need to avoid obstacles, but everyone else should
if (psDroid->droidType != DROID_TRANSPORTER && psDroid->droidType != DROID_SUPERTRANSPORTER)
{
dest = moveGetObstacleVector(psDroid, dest);
}
return iAtan2(dest);
}
// Check if a droid has got to a way point
static bool moveReachedWayPoint(DROID *psDroid)
{
// Calculate the vector to the droid
const Vector2i droid = removeZ(psDroid->pos) - psDroid->sMove.target;
const bool last = psDroid->sMove.pathIndex == psDroid->sMove.numPoints;
int sqprecision = last ? ((TILE_UNITS / 4) * (TILE_UNITS / 4)) : ((TILE_UNITS / 2) * (TILE_UNITS / 2));
if (last && psDroid->sMove.bumpTime != 0)
{
// Make waypoint tolerance 1 tile after 0 seconds, 2 tiles after 3 seconds, X tiles after (X + 1)² seconds.
sqprecision = (gameTime - psDroid->sMove.bumpTime + GAME_TICKS_PER_SEC)*(TILE_UNITS*TILE_UNITS / GAME_TICKS_PER_SEC);
}
// Else check current waypoint
return droid*droid < sqprecision;
}
#define MAX_SPEED_PITCH 60
/** Calculate the new speed for a droid based on factors like pitch.
* @todo Remove hack for steep slopes not properly marked as blocking on some maps.
*/
SDWORD moveCalcDroidSpeed(DROID *psDroid)
{
const uint16_t maxPitch = DEG(MAX_SPEED_PITCH);
UDWORD mapX, mapY;
int speed, pitch;
WEAPON_STATS *psWStats;
CHECK_DROID(psDroid);
// NOTE: This screws up since the transporter is offscreen still (on a mission!), and we are trying to find terrainType of a tile (that is offscreen!)
if (psDroid->droidType == DROID_TRANSPORTER && missionIsOffworld())
{
PROPULSION_STATS *propulsion = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
speed = propulsion->maxSpeed;
}
else
{
mapX = map_coord(psDroid->pos.x);
mapY = map_coord(psDroid->pos.y);
speed = calcDroidSpeed(psDroid->baseSpeed, terrainType(mapTile(mapX,mapY)), psDroid->asBits[COMP_PROPULSION], getDroidEffectiveLevel(psDroid));
}
// now offset the speed for the slope of the droid
pitch = angleDelta(psDroid->rot.pitch);
speed = (maxPitch - pitch) * speed / maxPitch;
if (speed <= 10)
{
// Very nasty hack to deal with buggy maps, where some cliffs are
// not properly marked as being cliffs, but too steep to drive over.
// This confuses the heck out of the path-finding code! - Per
speed = 10;
}
// stop droids that have just fired a no fire while moving weapon
if (psDroid->numWeaps > 0)
{
if (psDroid->asWeaps[0].nStat > 0 && psDroid->asWeaps[0].lastFired + FOM_MOVEPAUSE > gameTime)
{
psWStats = asWeaponStats + psDroid->asWeaps[0].nStat;
if (!psWStats->fireOnMove)
{
speed = 0;
}
}
}
// slow down shuffling VTOLs
if (isVtolDroid(psDroid) &&
(psDroid->sMove.Status == MOVESHUFFLE) &&
(speed > MIN_END_SPEED))
{
speed = MIN_END_SPEED;
}
CHECK_DROID(psDroid);
return speed;
}
/** Determine whether a droid has stopped moving.
* @return true if the droid doesn't move, false if it's moving.
*/
static bool moveDroidStopped(DROID* psDroid, SDWORD speed)
{
if (psDroid->sMove.Status == MOVEINACTIVE && speed == 0 && psDroid->sMove.speed == 0)
{
return true;
}
else
{
return false;
}
}
// Direction is target direction.
static void moveUpdateDroidDirection(DROID *psDroid, SDWORD *pSpeed, uint16_t direction,
uint16_t iSpinAngle, int iSpinSpeed, int iTurnSpeed, uint16_t *pDroidDir)
{
*pDroidDir = psDroid->rot.direction;
// don't move if in MOVEPAUSE state
if (psDroid->sMove.Status == MOVEPAUSE)
{
return;
}
int diff = angleDelta(direction - *pDroidDir);
// Turn while moving - slow down speed depending on target angle so that we can turn faster
*pSpeed = std::max<int>(*pSpeed * (iSpinAngle - abs(diff)) / iSpinAngle, 0);
// iTurnSpeed is turn speed at max velocity, increase turn speed up to iSpinSpeed when slowing down
int turnSpeed = std::min<int>(iTurnSpeed + int64_t(iSpinSpeed - iTurnSpeed) * abs(diff) / iSpinAngle, iSpinSpeed);
// Calculate the maximum change in direction
int maxChange = gameTimeAdjustedAverage(turnSpeed);
// Move *pDroidDir towards target, by at most maxChange.
*pDroidDir += clip(diff, -maxChange, maxChange);
}
// Calculate current speed perpendicular to droids direction
static int moveCalcPerpSpeed(DROID *psDroid, uint16_t iDroidDir, SDWORD iSkidDecel)
{
int adiff = angleDelta(iDroidDir - psDroid->sMove.moveDir);
int perpSpeed = iSinR(abs(adiff), psDroid->sMove.speed);
// decelerate the perpendicular speed
perpSpeed = MAX(0, perpSpeed - gameTimeAdjustedAverage(iSkidDecel));
return perpSpeed;
}
static void moveCombineNormalAndPerpSpeeds(DROID *psDroid, int fNormalSpeed, int fPerpSpeed, uint16_t iDroidDir)
{
int16_t adiff;
int relDir;
int finalSpeed;
/* set current direction */
psDroid->rot.direction = iDroidDir;
/* set normal speed and direction if perpendicular speed is zero */
if (fPerpSpeed == 0)
{
psDroid->sMove.speed = fNormalSpeed;
psDroid->sMove.moveDir = iDroidDir;
return;
}
finalSpeed = iHypot(fNormalSpeed, fPerpSpeed);
// calculate the angle between the droid facing and movement direction
relDir = iAtan2(fPerpSpeed, fNormalSpeed);
// choose the finalDir on the same side as the old movement direction
adiff = angleDelta(iDroidDir - psDroid->sMove.moveDir);
psDroid->sMove.moveDir = adiff < 0 ? iDroidDir + relDir : iDroidDir - relDir; // Cast wrapping intended.
psDroid->sMove.speed = finalSpeed;
}
// Calculate the current speed in the droids normal direction
static int moveCalcNormalSpeed(DROID *psDroid, int fSpeed, uint16_t iDroidDir, SDWORD iAccel, SDWORD iDecel)
{
uint16_t adiff;
int normalSpeed;
adiff = (uint16_t)(iDroidDir - psDroid->sMove.moveDir); // Cast wrapping intended.
normalSpeed = iCosR(adiff, psDroid->sMove.speed);
if (normalSpeed < fSpeed)
{
// accelerate
normalSpeed += gameTimeAdjustedAverage(iAccel);
if (normalSpeed > fSpeed)
{
normalSpeed = fSpeed;
}
}
else
{
// decelerate
normalSpeed -= gameTimeAdjustedAverage(iDecel);
if (normalSpeed < fSpeed)
{
normalSpeed = fSpeed;
}
}
return normalSpeed;
}
static void moveGetDroidPosDiffs(DROID *psDroid, int32_t *pDX, int32_t *pDY)
{
int32_t move = psDroid->sMove.speed * EXTRA_PRECISION; // high precision
*pDX = iSinR(psDroid->sMove.moveDir, move);
*pDY = iCosR(psDroid->sMove.moveDir, move);
}
// see if the droid is close to the final way point
static void moveCheckFinalWaypoint( DROID *psDroid, SDWORD *pSpeed )
{
int minEndSpeed = (*pSpeed + 2)/3;
minEndSpeed = std::min(minEndSpeed, MIN_END_SPEED);
// don't do this for VTOLs doing attack runs
if (isVtolDroid(psDroid) && (psDroid->action == DACTION_VTOLATTACK))
{
return;
}
if (psDroid->sMove.Status != MOVESHUFFLE &&
psDroid->sMove.pathIndex == psDroid->sMove.numPoints)
{
Vector2i diff = removeZ(psDroid->pos) - psDroid->sMove.target;
int distSq = diff*diff;
if (distSq < END_SPEED_RANGE*END_SPEED_RANGE)
{
*pSpeed = (*pSpeed - minEndSpeed) * distSq / (END_SPEED_RANGE*END_SPEED_RANGE) + minEndSpeed;
}
}
}
static void moveUpdateDroidPos(DROID *psDroid, int32_t dx, int32_t dy)
{
Position newPos; // high precision coordinates (unusable for squared calculations)
CHECK_DROID(psDroid);
if (psDroid->sMove.Status == MOVEPAUSE || isDead((BASE_OBJECT *)psDroid))
{
// don't actually move if the move is paused
return;
}
psDroid->pos.x += gameTimeAdjustedAverage(dx, EXTRA_PRECISION);
psDroid->pos.y += gameTimeAdjustedAverage(dy, EXTRA_PRECISION);
/* impact if about to go off map else update coordinates */
if ( worldOnMap( psDroid->pos.x, psDroid->pos.y ) == false )
{
/* transporter going off-world will trigger next map, and is ok */
ASSERT(psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER, "droid trying to move off the map!");
if (psDroid->droidType != DROID_TRANSPORTER && psDroid->droidType != DROID_SUPERTRANSPORTER)
{
/* dreadful last-ditch crash-avoiding hack - sort this! - GJ */
destroyDroid(psDroid, gameTime);
return;
}
}
// lovely hack to keep transporters just on the map
// two weeks to go and the hacks just get better !!!
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
{
if (psDroid->pos.x == 0)
{
psDroid->pos.x = 1;
}
if (psDroid->pos.y == 0)
{
psDroid->pos.y = 1;
}
}
CHECK_DROID(psDroid);
}
/* Update a tracked droids position and speed given target values */
static void moveUpdateGroundModel(DROID *psDroid, SDWORD speed, uint16_t direction)
{
int fPerpSpeed, fNormalSpeed;
uint16_t iDroidDir;
uint16_t slideDir;
PROPULSION_STATS *psPropStats;
int32_t spinSpeed, spinAngle, turnSpeed, dx, dy, bx, by;
CHECK_DROID(psDroid);
// nothing to do if the droid is stopped
if ( moveDroidStopped( psDroid, speed ) == true )
{
return;
}
psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
spinSpeed = psDroid->baseSpeed * psPropStats->spinSpeed;
turnSpeed = psDroid->baseSpeed * psPropStats->turnSpeed;
spinAngle = DEG(psPropStats->spinAngle);
moveCheckFinalWaypoint( psDroid, &speed );
moveUpdateDroidDirection(psDroid, &speed, direction, spinAngle, spinSpeed, turnSpeed, &iDroidDir);
fNormalSpeed = moveCalcNormalSpeed(psDroid, speed, iDroidDir, psPropStats->acceleration, psPropStats->deceleration);
fPerpSpeed = moveCalcPerpSpeed(psDroid, iDroidDir, psPropStats->skidDeceleration);
moveCombineNormalAndPerpSpeeds(psDroid, fNormalSpeed, fPerpSpeed, iDroidDir);
moveGetDroidPosDiffs(psDroid, &dx, &dy);
moveOpenGates(psDroid);
moveCheckSquished(psDroid, dx,dy);
moveCalcDroidSlide(psDroid, &dx, &dy);
bx = dx;
by = dy;
moveCalcBlockingSlide(psDroid, &bx, &by, direction, &slideDir);
if (bx != dx || by != dy)
{
moveUpdateDroidDirection(psDroid, &speed, slideDir, spinAngle, psDroid->baseSpeed*DEG(1), psDroid->baseSpeed*DEG(1)/3, &iDroidDir);
psDroid->rot.direction = iDroidDir;
}
moveUpdateDroidPos(psDroid, bx, by);
//set the droid height here so other routines can use it
psDroid->pos.z = map_Height(psDroid->pos.x, psDroid->pos.y);//jps 21july96
updateDroidOrientation(psDroid);
}
/* Update a persons position and speed given target values */
static void moveUpdatePersonModel(DROID *psDroid, SDWORD speed, uint16_t direction)
{
int fPerpSpeed, fNormalSpeed;
int32_t spinSpeed, turnSpeed, dx, dy;
uint16_t iDroidDir;
uint16_t slideDir;
PROPULSION_STATS *psPropStats;
CHECK_DROID(psDroid);
// nothing to do if the droid is stopped
if ( moveDroidStopped( psDroid, speed ) == true )
{
if ( psDroid->droidType == DROID_PERSON &&
psDroid->order.type != DORDER_RUNBURN &&
(psDroid->action == DACTION_ATTACK ||
psDroid->action == DACTION_ROTATETOATTACK) )
{
/* remove previous anim */
if ( psDroid->psCurAnim != NULL &&
psDroid->psCurAnim->psAnim->uwID != ID_ANIM_DROIDFIRE )
{
const bool bRet = animObj_Remove(psDroid->psCurAnim, psDroid->psCurAnim->psAnim->uwID);
ASSERT(bRet, "animObj_Remove failed");
psDroid->psCurAnim = NULL;
}
/* add firing anim */
if ( psDroid->psCurAnim == NULL )
{
psDroid->psCurAnim = animObj_Add( psDroid, ID_ANIM_DROIDFIRE, 0, 0 );
}
else
{
psDroid->psCurAnim->bVisible = true;
}
return;
}
/* don't show move animations if inactive */
if ( psDroid->psCurAnim != NULL )
{
psDroid->psCurAnim->bVisible = false;
}
return;
}
psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
spinSpeed = psDroid->baseSpeed * psPropStats->spinSpeed;
turnSpeed = psDroid->baseSpeed * psPropStats->turnSpeed;
moveUpdateDroidDirection(psDroid, &speed, direction, DEG(psPropStats->spinAngle), spinSpeed, turnSpeed, &iDroidDir);
fNormalSpeed = moveCalcNormalSpeed(psDroid, speed, iDroidDir, psPropStats->acceleration, psPropStats->deceleration);
/* people don't skid at the moment so set zero perpendicular speed */
fPerpSpeed = 0;
moveCombineNormalAndPerpSpeeds(psDroid, fNormalSpeed, fPerpSpeed, iDroidDir);
moveGetDroidPosDiffs( psDroid, &dx, &dy );
moveOpenGates(psDroid);
moveCalcDroidSlide(psDroid, &dx,&dy);
moveCalcBlockingSlide(psDroid, &dx, &dy, direction, &slideDir);
moveUpdateDroidPos( psDroid, dx, dy );
//set the droid height here so other routines can use it
psDroid->pos.z = map_Height(psDroid->pos.x, psDroid->pos.y);//jps 21july96
/* update anim if moving and not on fire */
if ( psDroid->droidType == DROID_PERSON && speed != 0 &&
psDroid->order.type != DORDER_RUNBURN )
{
/* remove previous anim */
if ( psDroid->psCurAnim != NULL &&
(psDroid->psCurAnim->psAnim->uwID != ID_ANIM_DROIDRUN ||
psDroid->psCurAnim->psAnim->uwID != ID_ANIM_DROIDRUN) )
{
const bool bRet = animObj_Remove(psDroid->psCurAnim, psDroid->psCurAnim->psAnim->uwID);
ASSERT(bRet, "animObj_Remove failed" );
psDroid->psCurAnim = NULL;
}
/* if no anim currently attached, get one */
if ( psDroid->psCurAnim == NULL )
{
// Only add the animation if the droid is on screen, saves memory and time.
if(clipXY(psDroid->pos.x,psDroid->pos.y)) {
debug( LOG_NEVER, "Added person run anim\n" );
psDroid->psCurAnim = animObj_Add( (BASE_OBJECT *) psDroid,
ID_ANIM_DROIDRUN, 0, 0 );
}
} else {
// If the droid went off screen then remove the animation, saves memory and time.
if(!clipXY(psDroid->pos.x,psDroid->pos.y)) {
const bool bRet = animObj_Remove(psDroid->psCurAnim, psDroid->psCurAnim->psAnim->uwID);
ASSERT(bRet, "animObj_Remove failed");
psDroid->psCurAnim = NULL;
debug( LOG_NEVER, "Removed person run anim\n" );
}
}
}
/* show anim */
if ( psDroid->psCurAnim != NULL )
{
psDroid->psCurAnim->bVisible = true;
}
CHECK_DROID(psDroid);
}
#define VTOL_VERTICAL_SPEED (((psDroid->baseSpeed / 4) > 60) ? ((SDWORD)psDroid->baseSpeed / 4) : 60)
/* primitive 'bang-bang' vtol height controller */
static void moveAdjustVtolHeight(DROID * psDroid, int32_t iMapHeight)
{
int32_t iMinHeight, iMaxHeight, iLevelHeight;
if ((psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER) && !bMultiPlayer)
{
iMinHeight = 2*VTOL_HEIGHT_MIN;
iLevelHeight = 2*VTOL_HEIGHT_LEVEL;
iMaxHeight = 2*VTOL_HEIGHT_MAX;
}
else
{
iMinHeight = VTOL_HEIGHT_MIN;
iLevelHeight = VTOL_HEIGHT_LEVEL;
iMaxHeight = VTOL_HEIGHT_MAX;
}
if ( psDroid->pos.z >= (iMapHeight+iMaxHeight) )
{
psDroid->sMove.iVertSpeed = (SWORD)-VTOL_VERTICAL_SPEED;
}
else if ( psDroid->pos.z < (iMapHeight+iMinHeight) )
{
psDroid->sMove.iVertSpeed = (SWORD)VTOL_VERTICAL_SPEED;
}
else if ( (psDroid->pos.z < iLevelHeight) &&
(psDroid->sMove.iVertSpeed < 0 ) )
{
psDroid->sMove.iVertSpeed = 0;
}
else if ( (psDroid->pos.z > iLevelHeight) &&
(psDroid->sMove.iVertSpeed > 0 ) )
{
psDroid->sMove.iVertSpeed = 0;
}
}
// set a vtol to be hovering in the air
void moveMakeVtolHover( DROID *psDroid )
{
psDroid->sMove.Status = MOVEHOVER;
psDroid->pos.z = map_Height(psDroid->pos.x,psDroid->pos.y) + VTOL_HEIGHT_LEVEL;
}
static void moveUpdateVtolModel(DROID *psDroid, SDWORD speed, uint16_t direction)
{
int fPerpSpeed, fNormalSpeed;
uint16_t iDroidDir;
uint16_t slideDir;
int32_t spinSpeed, turnSpeed, iMapZ, iSpinSpeed, iTurnSpeed, dx, dy;
uint16_t targetRoll;
PROPULSION_STATS *psPropStats;
CHECK_DROID(psDroid);
// nothing to do if the droid is stopped
if ( moveDroidStopped( psDroid, speed ) == true )
{
return;
}
psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
spinSpeed = DEG(psPropStats->spinSpeed);
turnSpeed = DEG(psPropStats->turnSpeed);
moveCheckFinalWaypoint( psDroid, &speed );
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
{
moveUpdateDroidDirection(psDroid, &speed, direction, DEG(psPropStats->spinAngle), spinSpeed, turnSpeed, &iDroidDir);
}
else
{
iSpinSpeed = std::max<int>(psDroid->baseSpeed*DEG(1)/2, spinSpeed);
iTurnSpeed = std::max<int>(psDroid->baseSpeed*DEG(1)/8, turnSpeed);
moveUpdateDroidDirection(psDroid, &speed, direction, DEG(psPropStats->spinAngle), iSpinSpeed, iTurnSpeed, &iDroidDir);
}
fNormalSpeed = moveCalcNormalSpeed(psDroid, speed, iDroidDir, psPropStats->acceleration, psPropStats->deceleration);
fPerpSpeed = moveCalcPerpSpeed(psDroid, iDroidDir, psPropStats->skidDeceleration);
moveCombineNormalAndPerpSpeeds(psDroid, fNormalSpeed, fPerpSpeed, iDroidDir);
moveGetDroidPosDiffs( psDroid, &dx, &dy );
/* set slide blocking tile for map edge */
if ( psDroid->droidType != DROID_TRANSPORTER && psDroid->droidType != DROID_SUPERTRANSPORTER)
{
moveCalcBlockingSlide(psDroid, &dx, &dy, direction, &slideDir);
}
moveUpdateDroidPos( psDroid, dx, dy );
/* update vtol orientation */
targetRoll = clip(4 * angleDelta(psDroid->sMove.moveDir - psDroid->rot.direction), -DEG(60), DEG(60));
psDroid->rot.roll = psDroid->rot.roll + (uint16_t)gameTimeAdjustedIncrement(3 * angleDelta(targetRoll - psDroid->rot.roll));
/* do vertical movement - only if on the map */
if (worldOnMap(psDroid->pos.x, psDroid->pos.y))
{
iMapZ = map_Height(psDroid->pos.x, psDroid->pos.y);
psDroid->pos.z = MAX(iMapZ, psDroid->pos.z + gameTimeAdjustedIncrement(psDroid->sMove.iVertSpeed));
moveAdjustVtolHeight(psDroid, iMapZ);
}
}
static void moveUpdateCyborgModel(DROID *psDroid, SDWORD moveSpeed, uint16_t moveDir, UBYTE oldStatus)
{
CHECK_DROID(psDroid);
// nothing to do if the droid is stopped
if (moveDroidStopped(psDroid, moveSpeed) == true)
{
if (psDroid->psCurAnim != NULL)
{
if (!animObj_Remove(psDroid->psCurAnim, psDroid->psCurAnim->uwID))
{
debug(LOG_NEVER, "couldn't remove walk anim");
}
psDroid->psCurAnim = NULL;
}
return;
}
if (psDroid->psCurAnim == NULL)
{
// Only add the animation if the droid is on screen, saves memory and time.
if (clipXY(psDroid->pos.x, psDroid->pos.y))
{
if (psDroid->droidType == DROID_CYBORG_SUPER)
{
psDroid->psCurAnim = animObj_Add(psDroid, ID_ANIM_SUPERCYBORG_RUN, 0, 0);
}
else if (cyborgDroid(psDroid))
{
psDroid->psCurAnim = animObj_Add(psDroid, ID_ANIM_CYBORG_RUN, 0, 0);
}
}
}
// If the droid went off screen then remove the animation, saves memory and time
else if (!clipXY(psDroid->pos.x, psDroid->pos.y))
{
const bool bRet = animObj_Remove(psDroid->psCurAnim, psDroid->psCurAnim->psAnim->uwID);
ASSERT(bRet, "animObj_Remove failed");
psDroid->psCurAnim = NULL;
}
/* use baba person movement */
moveUpdatePersonModel(psDroid, moveSpeed, moveDir);
psDroid->rot.pitch = 0;
psDroid->rot.roll = 0;
}
static void moveDescending( DROID *psDroid )
{
int32_t iMapHeight = map_Height( psDroid->pos.x, psDroid->pos.y);
psDroid->sMove.speed = 0;
if ( psDroid->pos.z > iMapHeight)
{
/* descending */
psDroid->sMove.iVertSpeed = (SWORD)-VTOL_VERTICAL_SPEED;
}
else
{
/* on floor - stop */
psDroid->pos.z = iMapHeight;
psDroid->sMove.iVertSpeed = 0;
/* reset move state */
psDroid->sMove.Status = MOVEINACTIVE;
/* conform to terrain */
updateDroidOrientation(psDroid);
}
}
bool moveCheckDroidMovingAndVisible( void *psObj )
{
DROID *psDroid = (DROID*)psObj;
if ( psDroid == NULL )
{
return false;
}
/* check for dead, not moving or invisible to player */
if ( psDroid->died || moveDroidStopped( psDroid, 0 ) ||
((psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER) && psDroid->order.type == DORDER_NONE) ||
!(psDroid->visible[selectedPlayer]) )
{
psDroid->iAudioID = NO_SOUND;
return false;
}
return true;
}
static void movePlayDroidMoveAudio( DROID *psDroid )
{
SDWORD iAudioID = NO_SOUND;
PROPULSION_TYPES *psPropType;
UBYTE iPropType = 0;
ASSERT( psDroid != NULL,
"movePlayUnitMoveAudio: unit pointer invalid\n" );
if ( (psDroid != NULL) &&
(psDroid->visible[selectedPlayer]) )
{
iPropType = asPropulsionStats[(psDroid)->asBits[COMP_PROPULSION]].propulsionType;
psPropType = &asPropulsionTypes[iPropType];
/* play specific wheeled and transporter or stats-specified noises */
if ( iPropType == PROPULSION_TYPE_WHEELED && psDroid->droidType != DROID_CONSTRUCT )
{
iAudioID = ID_SOUND_TREAD;
}
else if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
{
iAudioID = ID_SOUND_BLIMP_FLIGHT;
}
else if (iPropType == PROPULSION_TYPE_LEGGED && cyborgDroid(psDroid))
{
iAudioID = ID_SOUND_CYBORG_MOVE;
}
else
{
iAudioID = psPropType->moveID;
}
if ( iAudioID != NO_SOUND )
{
if ( audio_PlayObjDynamicTrack( psDroid, iAudioID,
moveCheckDroidMovingAndVisible ) )
{
psDroid->iAudioID = iAudioID;
}
}
}
}
static bool moveDroidStartCallback( void *psObj )
{
DROID *psDroid = (DROID*)psObj;
if ( psDroid == NULL )
{
return false;
}
movePlayDroidMoveAudio( psDroid );
return true;
}
static void movePlayAudio( DROID *psDroid, bool bStarted, bool bStoppedBefore, SDWORD iMoveSpeed )
{
UBYTE propType;
PROPULSION_STATS *psPropStats;
PROPULSION_TYPES *psPropType;
bool bStoppedNow;
SDWORD iAudioID = NO_SOUND;
AUDIO_CALLBACK pAudioCallback = NULL;
/* get prop stats */
psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
ASSERT( psPropStats != NULL,
"moveUpdateUnit: invalid propulsion stats pointer" );
propType = psPropStats->propulsionType;
psPropType = &asPropulsionTypes[propType];
/* get current droid motion status */
bStoppedNow = moveDroidStopped( psDroid, iMoveSpeed );
if ( bStarted )
{
/* play start audio */
if ((propType == PROPULSION_TYPE_WHEELED && psDroid->droidType != DROID_CONSTRUCT)
|| psPropType->startID == NO_SOUND)
{
movePlayDroidMoveAudio( psDroid );
return;
}
else if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
{
iAudioID = ID_SOUND_BLIMP_TAKE_OFF;
}
else
{
iAudioID = psPropType->startID;
}
pAudioCallback = moveDroidStartCallback;
}
else if ( !bStoppedBefore && bStoppedNow &&
(psPropType->shutDownID != NO_SOUND) )
{
/* play stop audio */
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
{
iAudioID = ID_SOUND_BLIMP_LAND;
}
else if ( propType != PROPULSION_TYPE_WHEELED || psDroid->droidType == DROID_CONSTRUCT )
{
iAudioID = psPropType->shutDownID;
}
}
else if (!bStoppedBefore && !bStoppedNow && psDroid->iAudioID == NO_SOUND)
{
/* play move audio */
movePlayDroidMoveAudio( psDroid );
return;
}
if ( (iAudioID != NO_SOUND) &&
(psDroid->visible[selectedPlayer]) )
{
if ( audio_PlayObjDynamicTrack( psDroid, iAudioID,
pAudioCallback ) )
{
psDroid->iAudioID = iAudioID;
}
}
}
static bool pickupOilDrum(int toPlayer, int fromPlayer)
{
addPower(toPlayer, OILDRUM_POWER); // give power
if (toPlayer == selectedPlayer)
{
CONPRINTF(ConsoleString, (ConsoleString, _("You found %u power in an oil drum."), OILDRUM_POWER));
}
return true;
}
// called when a droid moves to a new tile.
// use to pick up oil, etc..
static void checkLocalFeatures(DROID *psDroid)
{
// NOTE: Why not do this for AI units also?
if ((!isHumanPlayer(psDroid->player) && psDroid->order.type != DORDER_RECOVER) || isVtolDroid(psDroid)) // VTOLs can't pick up features!
{
return;
}
// scan the neighbours
#define DROIDDIST ((TILE_UNITS*5)/2)
static GridList gridList; // static to avoid allocations.
gridList = gridStartIterate(psDroid->pos.x, psDroid->pos.y, DROIDDIST);
for (GridIterator gi = gridList.begin(); gi != gridList.end(); ++gi)
{
BASE_OBJECT *psObj = *gi;
bool pickedUp = false;
if (psObj->type == OBJ_FEATURE && !psObj->died)
{
switch (((FEATURE *)psObj)->psStats->subType)
{
case FEAT_OIL_DRUM:
pickedUp = pickupOilDrum(psDroid->player, psObj->player);
triggerEventPickup((FEATURE *)psObj, psDroid);
break;
case FEAT_GEN_ARTE:
pickedUp = pickupArtefact(psDroid->player, psObj->player);
triggerEventPickup((FEATURE *)psObj, psDroid);
break;
default:
break;
}
}
if (!pickedUp)
{
// Object is not a living oil drum or artefact.
continue;
}
turnOffMultiMsg(true);
removeFeature((FEATURE *)psObj); // remove artifact+.
turnOffMultiMsg(false);
}
}
/* Frame update for the movement of a tracked droid */
void moveUpdateDroid(DROID *psDroid)
{
UDWORD oldx, oldy;
UBYTE oldStatus = psDroid->sMove.Status;
SDWORD moveSpeed;
uint16_t moveDir;
PROPULSION_STATS *psPropStats;
Vector3i pos;
bool bStarted = false, bStopped;
CHECK_DROID(psDroid);
psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
ASSERT_OR_RETURN(, psPropStats != NULL, "Invalid propulsion stats pointer");
// If the droid has been attacked by an EMP weapon, it is temporarily disabled
if (psDroid->lastHitWeapon == WSC_EMP)
{
if (gameTime - psDroid->timeLastHit < EMP_DISABLE_TIME)
{
// Get out without updating
return;
}
}
/* save current motion status of droid */
bStopped = moveDroidStopped( psDroid, 0 );
moveSpeed = 0;
moveDir = psDroid->rot.direction;
switch (psDroid->sMove.Status)
{
case MOVEINACTIVE:
if ( (psDroid->droidType == DROID_PERSON) &&
(psDroid->psCurAnim != NULL) &&
(psDroid->psCurAnim->psAnim->uwID == ID_ANIM_DROIDRUN) )
{
psDroid->psCurAnim->bVisible = false;
}
break;
case MOVESHUFFLE:
if (moveReachedWayPoint(psDroid) || (psDroid->sMove.shuffleStart + MOVE_SHUFFLETIME) < gameTime)
{
if (psPropStats->propulsionType == PROPULSION_TYPE_LIFT)
{
psDroid->sMove.Status = MOVEHOVER;
}
else
{
psDroid->sMove.Status = MOVEINACTIVE;
}
}
else
{
// Calculate a target vector
moveDir = moveGetDirection(psDroid);
moveSpeed = moveCalcDroidSpeed(psDroid);
}
break;
case MOVEWAITROUTE:
moveDroidTo(psDroid, psDroid->sMove.destination.x, psDroid->sMove.destination.y);
moveSpeed = MAX(0, psDroid->sMove.speed - 1);
if (psDroid->sMove.Status != MOVENAVIGATE)
{
break;
}
// No break.
case MOVENAVIGATE:
// Get the next control point
if (!moveNextTarget(psDroid))
{
// No more waypoints - finish
if ( psPropStats->propulsionType == PROPULSION_TYPE_LIFT )
{
psDroid->sMove.Status = MOVEHOVER;
}
else
{
psDroid->sMove.Status = MOVEINACTIVE;
}
break;
}
if (isVtolDroid(psDroid))
{
psDroid->rot.pitch = 0;
}
psDroid->sMove.Status = MOVEPOINTTOPOINT;
psDroid->sMove.bumpTime = 0;
moveSpeed = MAX(0, psDroid->sMove.speed - 1);
/* save started status for movePlayAudio */
if (psDroid->sMove.speed == 0)
{
bStarted = true;
}
// No break.
case MOVEPOINTTOPOINT:
case MOVEPAUSE:
// moving between two way points
if (psDroid->sMove.numPoints == 0)
{
debug(LOG_WARNING, "No path to follow, but psDroid->sMove.Status = %d", psDroid->sMove.Status);
}
else
{
ASSERT_OR_RETURN(, psDroid->sMove.asPath != NULL, "NULL path of non-zero length!");
}
// Get the best control point.
if (psDroid->sMove.numPoints == 0 || !moveBestTarget(psDroid))
{
// Got stuck somewhere, can't find the path.
moveDroidTo(psDroid, psDroid->sMove.destination.x, psDroid->sMove.destination.y);
}
// See if the target point has been reached
if (moveReachedWayPoint(psDroid))
{
// Got there - move onto the next waypoint
if (!moveNextTarget(psDroid))
{
// No more waypoints - finish
if ( psPropStats->propulsionType == PROPULSION_TYPE_LIFT )
{
// check the location for vtols
Vector2i tar = removeZ(psDroid->pos);
if (psDroid->order.type != DORDER_PATROL && psDroid->order.type != DORDER_CIRCLE // Not doing an order which means we never land (which means we might want to land).
&& psDroid->action != DACTION_MOVETOREARM && psDroid->action != DACTION_MOVETOREARMPOINT
&& actionVTOLLandingPos(psDroid, &tar) // Can find a sensible place to land.
&& map_coord(tar) != map_coord(psDroid->sMove.destination)) // We're not at the right place to land.
{
psDroid->sMove.destination = tar;
moveDroidTo(psDroid, psDroid->sMove.destination.x, psDroid->sMove.destination.y);
}
else
{
psDroid->sMove.Status = MOVEHOVER;
}
}
else
{
psDroid->sMove.Status = MOVETURN;
}
objTrace(psDroid->id, "Arrived at destination!");
break;
}
}
moveDir = moveGetDirection(psDroid);
moveSpeed = moveCalcDroidSpeed(psDroid);
if ((psDroid->sMove.bumpTime != 0) &&
(psDroid->sMove.pauseTime + psDroid->sMove.bumpTime + BLOCK_PAUSETIME < gameTime))
{
if (psDroid->sMove.Status == MOVEPOINTTOPOINT)
{
psDroid->sMove.Status = MOVEPAUSE;
}
else
{
psDroid->sMove.Status = MOVEPOINTTOPOINT;
}
psDroid->sMove.pauseTime = (UWORD)(gameTime - psDroid->sMove.bumpTime);
}
if ((psDroid->sMove.Status == MOVEPAUSE) &&
(psDroid->sMove.bumpTime != 0) &&
(psDroid->sMove.lastBump > psDroid->sMove.pauseTime) &&
(psDroid->sMove.lastBump + psDroid->sMove.bumpTime + BLOCK_PAUSERELEASE < gameTime))
{
psDroid->sMove.Status = MOVEPOINTTOPOINT;
}
break;
case MOVETURN:
// Turn the droid to it's final facing
if ( psPropStats->propulsionType == PROPULSION_TYPE_LIFT )
{
psDroid->sMove.Status = MOVEPOINTTOPOINT;
}
else
{
psDroid->sMove.Status = MOVEINACTIVE;
}
break;
case MOVETURNTOTARGET:
moveSpeed = 0;
moveDir = iAtan2(psDroid->sMove.target - removeZ(psDroid->pos));
break;
case MOVEHOVER:
moveDescending(psDroid);
break;
// Driven around by the player.
case MOVEDRIVE:
driveSetDroidMove(psDroid);
moveSpeed = driveGetMoveSpeed(); //psDroid->sMove.speed;
moveDir = DEG(driveGetMoveDir()); //psDroid->sMove.dir;
break;
default:
ASSERT( false, "moveUpdateUnit: unknown move state" );
break;
}
// Update the movement model for the droid
oldx = psDroid->pos.x;
oldy = psDroid->pos.y;
if ( psDroid->droidType == DROID_PERSON )
{
moveUpdatePersonModel(psDroid, moveSpeed, moveDir);
}
else if (cyborgDroid(psDroid))
{
moveUpdateCyborgModel(psDroid, moveSpeed, moveDir, oldStatus);
}
else if ( psPropStats->propulsionType == PROPULSION_TYPE_LIFT )
{
moveUpdateVtolModel(psDroid, moveSpeed, moveDir);
}
else
{
moveUpdateGroundModel(psDroid, moveSpeed, moveDir);
}
if (map_coord(oldx) != map_coord(psDroid->pos.x)
|| map_coord(oldy) != map_coord(psDroid->pos.y))
{
visTilesUpdate((BASE_OBJECT *)psDroid);
// object moved from one tile to next, check to see if droid is near stuff.(oil)
checkLocalFeatures(psDroid);
triggerEventDroidMoved(psDroid, oldx, oldy);
}
// See if it's got blocked
if ( (psPropStats->propulsionType != PROPULSION_TYPE_LIFT) && moveBlocked(psDroid) )
{
objTrace(psDroid->id, "status: id %d blocked", (int)psDroid->id);
psDroid->sMove.Status = MOVETURN;
}
// // If were in drive mode and the droid is a follower then stop it when it gets within
// // range of the driver.
// if(driveIsFollower(psDroid)) {
// if(DoFollowRangeCheck) {
// if(driveInDriverRange(psDroid)) {
// psDroid->sMove.Status = MOVEINACTIVE;
//// ClearFollowRangeCheck = true;
// } else {
// AllInRange = false;
// }
// }
// }
/* If it's sitting in water then it's got to go with the flow! */
if (worldOnMap(psDroid->pos.x, psDroid->pos.y) && terrainType(mapTile(map_coord(psDroid->pos.x), map_coord(psDroid->pos.y))) == TER_WATER)
{
updateDroidOrientation(psDroid);
}
if (psDroid->sMove.Status == MOVETURNTOTARGET && psDroid->rot.direction == moveDir)
{
if (psPropStats->propulsionType == PROPULSION_TYPE_LIFT)
{
psDroid->sMove.Status = MOVEPOINTTOPOINT;
}
else
{
psDroid->sMove.Status = MOVEINACTIVE;
}
objTrace(psDroid->id, "MOVETURNTOTARGET complete");
}
if (psDroid->periodicalDamageStart != 0 && psDroid->droidType != DROID_PERSON && psDroid->visible[selectedPlayer])
{
pos.x = psDroid->pos.x + (18-rand()%36);
pos.z = psDroid->pos.y + (18-rand()%36);
pos.y = psDroid->pos.z + (psDroid->sDisplay.imd->max.y / 3);
addEffect(&pos, EFFECT_EXPLOSION, EXPLOSION_TYPE_SMALL, false, NULL, 0, gameTime - deltaGameTime + 1);
}
movePlayAudio( psDroid, bStarted, bStopped, moveSpeed );
ASSERT(droidOnMap(psDroid), "%s moved off map (%u, %u)->(%u, %u)", droidGetName(psDroid), oldx, oldy, (UDWORD)psDroid->pos.x, (UDWORD)psDroid->pos.y);
CHECK_DROID(psDroid);
}