REDRIVER2/src_rebuild/Game/C/civ_ai.c
2022-02-03 23:57:39 +03:00

3802 lines
80 KiB
C

#include "driver2.h"
#include "civ_ai.h"
#include "system.h"
#include "handling.h"
#include "dr2roads.h"
#include "cosmetic.h"
#include "mission.h"
#include "cop_ai.h"
#include "denting.h"
#include "cars.h"
#include "camera.h"
#include "cutscene.h"
#include "replays.h"
#include "convert.h"
#include "spool.h"
#include "players.h"
#include "gamesnd.h"
#include "sound.h"
#include "bcollide.h"
#include "leadai.h"
#include "main.h"
#include "pedest.h"
#include "objcoll.h"
#include "overlay.h"
#include "cutrecorder.h"
const u_char speedLimits[3] = { 56, 97, 138 };
struct
{
int NumPingedIn;
int OffRoad;
int NotDrivable;
int TooShortStr;
int NearEndStr;
int TooShortCrv;
int NearEndCrv;
int TooCloseNuddaCar;
int TooClosePlayer;
int InvalidRegion;
} civPingTest;
char modelRandomList[] = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 1, 0, 4 };
u_char reservedSlots[MAX_CARS] = { 0 };
int distFurthestCivCarSq = 0;
char furthestCivID = 0;
char makeLimoPullOver = 0;
int frameStart = 0;
char limoId = 0; // Seems to be unused
char playerNum = 0;
char PingOutCivsOnly = 0;
char cookieCount = 0;
int roadSeg = 0;
int testNumPingedOut = 0;
int currentAngle = 0;
int closeEncounter = 3;
char junctionLightsPhase[2];
int test42 = 0;
int test123 = 0;
int test555 = 0;
// check path node validty in cars
#define IS_NODE_VALID(cp, node) \
((node) >= cp->ai.c.targetRoute && (node) <= cp->ai.c.targetRoute+12)
#define GET_NEXT_NODE(cp, relNode)\
(relNode + 1 > cp->ai.c.targetRoute+12 ? relNode - 12 : relNode + 1)
#define GET_NODE_ID(cp, node) int((node) - cp->ai.c.targetRoute)
#ifdef _DEBUG
#define CIV_STATE_SET_CONFUSED(cp) \
printInfo("CIV confused: at %s, %d\n", __FUNCTION__, __LINE__);\
cp->ai.c.thrustState = 3; cp->ai.c.ctrlState = 7;
#else
#define CIV_STATE_SET_CONFUSED(cp) \
cp->ai.c.thrustState = 3; cp->ai.c.ctrlState = 7;
#endif
// [D] [T]
int InitCar(CAR_DATA* cp, int direction, LONGVECTOR4* startPos, unsigned char control, int model, int palette, char* extraData)
{
VECTOR tmpStart;
ClearMem((char*)cp, sizeof(CAR_DATA));
cp->wasOnGround = 1;
cp->id = CAR_INDEX(cp);
if (startPos == NULL)
return 0;
cp->ap.model = model;
cp->lowDetail = -1;
cp->ap.qy = 0;
cp->ap.qw = 0;
cp->ap.carCos = &car_cosmetics[model];
tmpStart.vx = (*startPos)[0];
tmpStart.vy = (*startPos)[1];
tmpStart.vz = (*startPos)[2];
tmpStart.vy = MapHeight(&tmpStart) - cp->ap.carCos->wheelDisp[0].vy;
// not valid request
if (control == CONTROL_TYPE_NONE)
return 1;
InitCarPhysics(cp, (LONGVECTOR4 *)&tmpStart, direction);
cp->ap.palette = palette;
cp->controlType = control;
switch (control)
{
case CONTROL_TYPE_PLAYER:
case CONTROL_TYPE_CUTSCENE:
// player car or cutscene car
cp->ai.padid = extraData;
player[cp->id].worldCentreCarId = cp->id;
cp->hndType = 0;
break;
case CONTROL_TYPE_CIV_AI:
cp->hndType = 1;
if (extraData == NULL)
{
cp->controlFlags = 0;
cp->ap.palette = 0;
}
else
{
cp->controlFlags = ((EXTRA_CIV_DATA*)extraData)->controlFlags;
cp->ap.palette = ((EXTRA_CIV_DATA*)extraData)->palette;
}
InitCivState(cp, (EXTRA_CIV_DATA*)extraData);
break;
case CONTROL_TYPE_PURSUER_AI:
InitCopState(cp, extraData);
cp->ap.palette = 0;
numCopCars++;
break;
case CONTROL_TYPE_LEAD_AI:
// free roamer lead car
InitLead(cp);
cp->hndType = 5;
break;
}
CreateDentableCar(cp);
DentCar(cp);
return 1;
}
// [D] [T]
CAR_DATA* FindClosestCar(int x, int y, int z, int* distToCarSq)
{
u_int distSq;
CAR_DATA* lcp;
u_int retDistSq;
CAR_DATA* retCar;
int dx; // $a0
int dz; // $v1
retCar = NULL;
retDistSq = INT_MAX;
lcp = car_data;
do {
if (lcp->controlType != CONTROL_TYPE_NONE)
{
if (ABS(y - lcp->hd.where.t[1]) < 800)
{
dx = x - lcp->hd.where.t[0];
dz = z - lcp->hd.where.t[2];
if (ABS(dx) < 4096 && ABS(dz) < 4096)
{
distSq = dx * dx + dz * dz;
if (distSq < retDistSq)
{
retDistSq = distSq;
retCar = lcp;
}
}
}
}
lcp++;
} while (lcp < &car_data[MAX_CARS]);
if (distToCarSq)
*distToCarSq = retDistSq;
return retCar;
}
// [D] [T]
int NotTravellingAlongCurve(int x, int z, int dir, DRIVER2_CURVE* cv)
{
int curveDir;
int cvDiff;
curveDir = ratan2(x - cv->Midx, z - cv->Midz);
cvDiff = DIFF_ANGLES(curveDir, dir); //((dir - curveDir) + 2048U & 0xfff) - 2048U;
return (cvDiff < 1) * 2048;
}
// [D] [T]
void CivCarFX(CAR_DATA* cp)
{
if (cp->ai.c.thrustState != 3)
{
if (cp->ai.c.turnNode != -1)
AddIndicatorLight(cp, cp->ai.c.turnDir);
if (cp->ai.c.changeLaneIndicateCount != 0 && cp->ai.c.turnNode == -1)
AddIndicatorLight(cp, cp->ai.c.changeLane);
}
if (cp->ai.c.brakeLight != 0)
AddBrakeLight(cp);
}
// [D] [T]
int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist, CIV_ROUTE_ENTRY* oldNode)
{
u_int junctionFlags;
DRIVER2_JUNCTION* jn;
DRIVER2_ROAD_INFO currentRoadInfo;
DRIVER2_ROAD_INFO roadInfo;
int currentRoadId = 0;
int tmpNewRoad[2];
int newExit = 0;
int tmpNewLane[2];
int laneFit[2];
short validExitIdx[4];
int newLane;
int newRoad;
int numExits;
int leftLane;
int rightLane;
int oldOppDir;
int oppDir;
int turnDir;
int currentLaneDir;
currentRoadId = cp->ai.c.currentRoad;
if (GetSurfaceRoadInfo(&currentRoadInfo, currentRoadId))
{
int widthInLanes;
int laneNo;
int count;
widthInLanes = ROAD_WIDTH_IN_LANES(&currentRoadInfo);
currentLaneDir = ROAD_LANE_DIR(&currentRoadInfo, cp->ai.c.currentLane);
if (currentRoadInfo.straight)
{
oldOppDir = (currentRoadInfo.straight->angle - oldNode->dir) + 1024U & 2048;
}
else
{
int dx, dz;
dx = oldNode->x - currentRoadInfo.curve->Midx;
dz = oldNode->z - currentRoadInfo.curve->Midz;
oldOppDir = DIFF_ANGLES(ratan2(dx, dz), oldNode->dir); // (((oldNode->dir - ratan2(dx, dz)) + 2048U & 0xfff) - 2048);
oldOppDir = (oldOppDir < 1) * 2048;
}
// first road is picked from road direction
tmpNewRoad[0] = ((short*)currentRoadInfo.ConnectIdx)[(oldOppDir > 0) * 2];
// and second picked from lane direction
tmpNewRoad[1] = ((short*)currentRoadInfo.ConnectIdx)[(currentLaneDir > 0) ? 3 : 1];
count = widthInLanes; // counter
laneNo = widthInLanes; // bestLane
do
{
count--;
if (ROAD_IS_AI_LANE(&currentRoadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(&currentRoadInfo, count))
{
test42 = ROAD_LANE_DIR(&currentRoadInfo, count);
laneNo = count;
}
} while (count >= 0);
if (currentLaneDir == 0)
leftLane = laneNo & 0xff;
else
rightLane = laneNo & 0xff;
// ifhas fast lane, use second lane
count = ROAD_HAS_FAST_LANES(&currentRoadInfo);
laneNo = widthInLanes;
while (count < widthInLanes)
{
if (ROAD_IS_AI_LANE(&currentRoadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(&currentRoadInfo, count))
{
test555 = ROAD_LANE_DIR(&currentRoadInfo, count) ^ 1;
laneNo = count;
if (test555 == 0)
{
if (currentLaneDir != 0)
break;
}
else
{
if (currentLaneDir == 0)
break;
}
}
laneNo = widthInLanes;
count++;
}
if (currentLaneDir != 0)
leftLane = laneNo & 0xff;
else
rightLane = laneNo & 0xff;
}
newLane = -1;
if (IS_JUNCTION_SURFACE(tmpNewRoad[0]))
{
int bestExit;
int exitFrom;
int exitCnt;
int rnd;
numExits = 0;
cp->ai.c.changeLaneCount = 0;
jn = GET_JUNCTION(tmpNewRoad[0]);
exitFrom = -1; // best exit id
// check if road is valid for this junction
// by determining connection with junction
exitCnt = 0;
while(exitCnt < 4)
{
if(jn->ExitIdx[exitCnt] == currentRoadId)
{
exitFrom = exitCnt;
break;
}
exitCnt++;
}
if (exitFrom == -1)
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
// check directions of exits
//printWarning("Checking directions, exitFrom: %d, \n", exitFrom);
exitCnt = 0;
do {
int exitSurfId;
int exitIdx = (exitFrom + exitCnt + 1) % 4;
int valid;
valid = 0;
exitSurfId = jn->ExitIdx[exitIdx];
if (exitSurfId != -1)
{
int exitDir;
exitDir = ((exitIdx + 4) - exitFrom) % 4;
//exitDir = exitDir - (exitDir / 4) * 4;
if (exitDir == 1)
*turnAngle = -1024; // left
else if (exitDir == 2)
*turnAngle = 0; // forward
else if (exitDir == 3)
*turnAngle = 1024; // right
else
*turnAngle = 0; // forward again?
test123 = 666;
test555 = 666;
test42 = 666;
// current node direction and new direction
turnDir = oldNode->dir + *turnAngle;
if (GetSurfaceRoadInfo(&roadInfo, exitSurfId))
{
int turnAng;
int dx, dz;
int laneCount;
laneCount = ROAD_WIDTH_IN_LANES(&roadInfo);
if(roadInfo.straight)
{
oppDir = (roadInfo.straight->angle - turnDir) + 1024U & 2048;
}
else
{
dx = oldNode->x - roadInfo.curve->Midx;
dz = oldNode->z - roadInfo.curve->Midz;
oppDir = DIFF_ANGLES(ratan2(dx, dz), turnDir);// ((turnDir - ratan2(dx, dz)) + 2048U & 0xfff) - 2048; // [A]
oppDir = (oppDir < 1) * 2048;
}
turnAng = *turnAngle;
if (oppDir == 0)
turnAng = -turnAng;
if (turnAng == 0) // going forward
{
if (oppDir != oldOppDir) // next road is flipped
newLane = laneCount - (cp->ai.c.currentLane + 1);
else
newLane = cp->ai.c.currentLane;
// goes on invalid lane?
// [A] bug fix with exitDir == 0
if (oppDir == 0 && exitDir == 0 || !ROAD_IS_AI_LANE(&roadInfo, newLane))
newLane = -1;
}
else if (turnAng == -1024) // going left
{
int count;
//printInfo("check left\n");
count = ROAD_HAS_FAST_LANES(&roadInfo); // lane counter
newLane = laneCount;
// check if allowed to go on any of lanes
while (count < laneCount)
{
if (ROAD_IS_AI_LANE(&roadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, count))
{
test555 = (ROAD_LANE_DIR(&roadInfo, count) ^ 1) & 1;
newLane = count;
if (test555 == 0)
{
if (oppDir != 0)
break;
}
else
{
if (oppDir == 0)
break;
}
}
count++;
newLane = laneCount;
}
}
else if (turnAng == 1024)
{
int count;
count = laneCount; // lane counter
newLane = laneCount;
do
{
if (ROAD_IS_AI_LANE(&roadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, count))
{
test42 = (ROAD_LANE_DIR(&roadInfo, count) ^ 1) & 1;
if (test42 == 0)
{
if (oppDir != 0)
newLane = count;
}
else
{
if (oppDir == 0)
newLane = count;
}
}
count--;
} while (count >= 0);
}
// validate lane
if (turnAng == 0 || newLane >= 0 && newLane < laneCount)
{
valid = ROAD_IS_AI_LANE(&roadInfo, newLane) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, newLane);
}
}
}
if (valid)
{
validExitIdx[exitCnt] = exitIdx;
numExits++;
}
else
{
validExitIdx[exitCnt] = 42;
}
// only need 3 cycles because we don't want make U-turns
exitCnt++;
} while (exitCnt < 3);
if (leftLane != rightLane && numExits != 1 && ROAD_LANES_COUNT(&currentRoadInfo) > 1)
{
if (cp->ai.c.currentLane == leftLane)
{
if (validExitIdx[2] != 42)
numExits--;
validExitIdx[2] = 42;
}
else if (cp->ai.c.currentLane == rightLane)
{
if (validExitIdx[0] != 42)
numExits--;
validExitIdx[0] = 42;
}
else if (validExitIdx[1] != 42)
{
if (validExitIdx[0] != 42)
numExits--;
if (validExitIdx[2] != 42)
numExits--;
validExitIdx[2] = 42;
validExitIdx[0] = 42;
}
}
if (numExits == 0)
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
// randomize exit selection
rnd = Random2(0) % 3;
do
{
bestExit = validExitIdx[rnd % 3];
rnd++;
} while (bestExit == 42);
newRoad = jn->ExitIdx[bestExit];
if (turnAngle != NULL)
{
int invExit;
invExit = (bestExit + 4 - exitFrom) % 4;
//invExit = invExit - (invExit / 4) * 4;
if (invExit == 1)
*turnAngle = -1024;
else if (invExit > 1 && invExit != 2 && invExit == 3)
*turnAngle = 1024;
else
*turnAngle = 0;
test42 = exitFrom;
test555 = bestExit;
}
junctionFlags = jn->flags;
// manage states
if (cp->ai.c.ctrlState != 8)
{
cp->ai.c.ctrlState = 0;
if (junctionFlags & 1)
{
// wait for traffic light
cp->ai.c.trafficLightPhaseId = (exitFrom & 1);
cp->ai.c.ctrlState = 1;
if (junctionLightsPhase[exitFrom & 1] == 3)
{
if (*turnAngle != 0)
cp->ai.c.ctrlNode = oldNode;
else
cp->ai.c.ctrlState = 6;
}
else
cp->ai.c.ctrlNode = oldNode;
}
else
{
int yield = 0;
if (exitFrom == 0 || exitFrom == 2)
yield = 1;
if (junctionFlags & 2)
{
if (yield || *turnAngle != 0)
{
cp->ai.c.ctrlNode = oldNode;
cp->ai.c.ctrlState = 4;
}
else
cp->ai.c.ctrlState = 6;
}
else
{
if (!yield || *turnAngle != 0)
{
cp->ai.c.ctrlNode = oldNode;
cp->ai.c.ctrlState = 4;
}
else
cp->ai.c.ctrlState = 6;
}
}
}
if(!GetSurfaceRoadInfo(&roadInfo, newRoad))
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
turnDir = oldNode->dir + *turnAngle;
{
int numLanes;
int turnAng;
numLanes = ROAD_WIDTH_IN_LANES(&roadInfo);
turnAng = *turnAngle;
if (roadInfo.straight)
{
oppDir = (turnDir - roadInfo.straight->angle) + 1024 & 2048;
}
else
{
int dx, dz;
dx = oldNode->x - roadInfo.curve->Midx;
dz = oldNode->z - roadInfo.curve->Midz;
oppDir = DIFF_ANGLES(ratan2(dx, dz), turnDir); // (((turnDir - ratan2(dx, dz)) + 2048U & 0xfff) - 2048);
oppDir = (oppDir < 1) * 2048;
}
if (turnAng == 0)
{
//printWarning("car %d check forward lane\n", cp->id);
if (oppDir == oldOppDir)
newLane = cp->ai.c.currentLane;
else
newLane = numLanes - (cp->ai.c.currentLane + 1);
if (ROAD_LANE_DIR(&roadInfo, newLane))
{
if (oppDir == 0)
newLane = -1;
}
else
{
if (oppDir != 0)
newLane = -1;
}
if (!ROAD_IS_AI_LANE(&roadInfo, newLane))
newLane = -1;
}
else if (turnAng == -1024)
{
int count;
count = ROAD_HAS_FAST_LANES(&roadInfo);
newLane = numLanes;
// check if allowed to go on any of lanes
while (count < numLanes)
{
if (ROAD_IS_AI_LANE(&roadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, count))
{
test555 = (ROAD_LANE_DIR(&roadInfo, count) ^ 1) & 1;
newLane = count;
if (test555 == 0)
{
if (oppDir != 0)
break;
}
else
{
if (oppDir == 0)
break;
}
}
count++;
newLane = numLanes;
}
//printWarning("car %d check left lane, chosen %d\n", cp->id, newLane);
}
else if (turnAng == 1024)
{
int count;
count = numLanes;
newLane = numLanes;
do
{
if (ROAD_IS_AI_LANE(&roadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, count))
{
test42 = (ROAD_LANE_DIR(&roadInfo, count) ^ 1) & 1;
if (test42 == 0)
{
if (oppDir != 0)
newLane = count;
}
else
{
if (oppDir == 0)
newLane = count;
}
}
count--;
} while (count >= 0);
//printWarning("car %d check right lane, chosen %d\n", cp->id, newLane);
}
if (*turnAngle != 0)
{
if (numLanes - 1 == newLane)
{
// [A] might be incorrect
if (ROAD_LANE_DIR(&roadInfo, newLane) == ROAD_LANE_DIR(&roadInfo, newLane - 1))
newLane = newLane - 1;
}
else if (newLane == 0)
{
// [A] might be incorrect
if (ROAD_LANE_DIR(&roadInfo, 0) == ROAD_LANE_DIR(&roadInfo, newLane + 1))
newLane = newLane + 1;
}
}
}
}
else
{
// road continuation using connections
int roadCnt;
if (turnAngle == NULL)
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
*turnAngle = 0;
newExit = -1;
numExits = 0;
if (tmpNewRoad[0] != -1)
numExits++;
if (tmpNewRoad[1] != -1)
numExits++;
// check the connections
// determine new lanes on possible new roads based on the old node position
for (roadCnt = 0; roadCnt < 2; roadCnt++)
{
if (tmpNewRoad[roadCnt] != -1)
{
int dx, dz;
int numLanes;
numLanes = 0;
if (GetSurfaceRoadInfo(&roadInfo, tmpNewRoad[roadCnt]))
{
numLanes = ROAD_WIDTH_IN_LANES(&roadInfo);
// determine new lane by old node position
if(roadInfo.straight)
{
dx = (oldNode->x - roadInfo.straight->Midx);
dz = (oldNode->z - roadInfo.straight->Midz);
tmpNewLane[roadCnt] = ROAD_LANES_COUNT(&roadInfo)
- (FIXEDH(dx * RCOS(roadInfo.straight->angle) - dz * RSIN(roadInfo.straight->angle)) + 512 >> 9);
}
else
{
dx = oldNode->x - roadInfo.curve->Midx;
dz = oldNode->z - roadInfo.curve->Midz;
tmpNewLane[roadCnt] = (SquareRoot0(dx * dx + dz * dz) >> 9) - roadInfo.curve->inside * 2;
}
}
// [A] if there are more than one connections we check the bounds strictly
if(numExits > 1)
{
if (tmpNewLane[roadCnt] < 0 || tmpNewLane[roadCnt] >= numLanes)
{
laneFit[roadCnt] = 666;
numExits--; // there has to be one exit
continue;
}
}
// fit new lane
newLane = tmpNewLane[roadCnt];
if (newLane < 0)
laneFit[roadCnt] = 0;
else if (newLane <= numLanes - 1)
laneFit[roadCnt] = 0;
else
laneFit[roadCnt] = newLane - numLanes - 1;
// clamp lane
if (tmpNewLane[roadCnt] >= numLanes - 1)
tmpNewLane[roadCnt] = numLanes - 1;
if (tmpNewLane[roadCnt] < 0)
tmpNewLane[roadCnt] = 0;
// set this a s correct exit
newExit = roadCnt;
}
else
{
laneFit[roadCnt] = 666;
}
}
if (newExit == -1 || laneFit[newExit] == 666)
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
newExit = (ABS(laneFit[0]) < ABS(laneFit[1])) ^ 1;
newRoad = tmpNewRoad[newExit];
newLane = tmpNewLane[newExit];
if (cp->ai.c.ctrlState != 7)
{
// [A] removed old lane fitting code
if (laneFit[newExit] != 0)
{
cp->ai.c.turnNode = GET_NODE_ID(cp, GET_NEXT_NODE(cp, oldNode));
cp->ai.c.turnDir = ROAD_LANE_DIR(&roadInfo, laneFit[newExit]);
}
}
}
if (newLane < 0)
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
// determine distance and speed limit
if (startDist != NULL)
{
if (GetSurfaceRoadInfo(&roadInfo, newRoad))
{
if(roadInfo.straight)
{
if (ROAD_LANE_DIR(&roadInfo, newLane))
*startDist = roadInfo.straight->length;
else
*startDist = 0;
}
else
{
if (ROAD_LANE_DIR(&roadInfo, newLane))
*startDist = roadInfo.curve->end - roadInfo.curve->start & 0xfff;
else
*startDist = 0;
}
cp->ai.c.maxSpeed = speedLimits[ROAD_SPEED_LIMIT(&roadInfo)];
}
}
if (newRoad < 0)
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
cp->ai.c.currentLane = newLane;
return newRoad;
}
// [D] [T]
void InitNodeList(CAR_DATA* cp, EXTRA_CIV_DATA* extraData)
{
int dx; // $s1
int dz; // $s2
int i;
DRIVER2_CURVE* curve;
DRIVER2_STRAIGHT* straight;
int surfInd;
CIV_ROUTE_ENTRY* cr;
cr = cp->ai.c.targetRoute;
for (i = 0; i < 13; i++)
{
cr->pathType = 127;
cr->x = 0;
cr->z = 0;
cr++;
}
cr = &cp->ai.c.targetRoute[0];
cr->pathType = 1;
cr->x = cp->hd.where.t[0];
cr->z = cp->hd.where.t[2];
surfInd = cp->ai.c.currentRoad;
if (IS_STRAIGHT_SURFACE(surfInd))
{
int theta; // $s0
int laneDist; // $s1
straight = GET_STRAIGHT(surfInd);
dx = cp->hd.where.t[0] - straight->Midx;
dz = cp->hd.where.t[2] - straight->Midz;
theta = straight->angle - ratan2(dx, dz);
cr->distAlongSegment = (straight->length / 2) + FIXEDH(RCOS(theta) * SquareRoot0(dx * dx + dz * dz));
laneDist = ROAD_LANES_COUNT(straight) * 512 + FIXEDH(-dx * RCOS(straight->angle) + dz * RSIN(straight->angle));
cp->ai.c.currentLane = laneDist / 512;
// calc all dirs
if (ROAD_LANE_DIR(straight, cp->ai.c.currentLane))
cr->dir = straight->angle + 2048U & 0xfff;
else
cr->dir = straight->angle;
}
else if (IS_CURVED_SURFACE(surfInd))
{
if (extraData == NULL)
{
curve = GET_CURVE(surfInd);
cr->distAlongSegment = (ratan2(cr->x - curve->Midx, cr->z - curve->Midz) & 0xfff) - curve->start;
if (curve->inside < 10)
cr->distAlongSegment &= 0xf80;
else if (curve->inside < 20)
cr->distAlongSegment &= 0xfc0;
else
cr->distAlongSegment &= 0xfe0;
if (ROAD_LANE_DIR(curve, cp->ai.c.currentLane) == 0)
cr->dir = (cr->distAlongSegment + curve->start) + 1024;
else
cr->dir = (cr->distAlongSegment + curve->start) - 1024;
}
else
{
cr->distAlongSegment = extraData->distAlongSegment;
cr->dir = extraData->angle;
}
}
if (extraData != NULL)
cr->distAlongSegment = extraData->distAlongSegment;
}
// [D] [T]
int GetNodePos(DRIVER2_STRAIGHT* straight, DRIVER2_JUNCTION* junction, DRIVER2_CURVE* curve, int distAlongPath, CAR_DATA* cp, int* x, int* z, int laneNo)
{
unsigned char oldLane;
unsigned char changeLaneCount;
int angle;
int distFromCentre;
int sideShift;
int radius;
if (cp)
{
oldLane = cp->ai.c.oldLane;
changeLaneCount = cp->ai.c.changeLaneCount;
}
else
{
oldLane = 0;
changeLaneCount = 0;
}
if (straight)
{
angle = straight->angle;
distFromCentre = distAlongPath - (straight->length / 2);
if (angle < 2048)
{
if (laneNo <= oldLane)
test42 = -changeLaneCount * 128;
else
test42 = changeLaneCount * 128;
sideShift = (ROAD_LANES_COUNT(straight) * 512 - (laneNo * 512 + 256)) + test42;
*x = straight->Midx + FIXEDH(distFromCentre * RSIN(angle)) +
FIXEDH(sideShift * RCOS(angle));
*z = straight->Midz + FIXEDH(distFromCentre * RCOS(angle)) -
FIXEDH(sideShift * RSIN(angle));
}
else if (cp)
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
}
else if (curve)
{
angle = distAlongPath + curve->start;
if (oldLane <= laneNo)
test42 = -changeLaneCount * 128;
else
test42 = changeLaneCount * 128;
radius = curve->inside * 1024 + laneNo * 512 + 256 + test42;
*x = curve->Midx + FIXEDH(radius * RSIN(angle));
*z = curve->Midz + FIXEDH(radius * RCOS(angle));
}
else if (junction)
{
// do nothing really
}
else if (cp)
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
if (cp && changeLaneCount > 0)
cp->ai.c.changeLaneCount--;
return 0;
}
// [D] [T]
int CheckChangeLanes(DRIVER2_STRAIGHT* straight, DRIVER2_CURVE* curve, int distAlongSegment, CAR_DATA* cp, int tryToPark)
{
int oldLane;
int currentLane;
int newLane;
int trials;
CAR_COSMETICS* car_cos;
int dx;
int dz;
u_int theta;
int length;
CAR_DATA* lcp;
int oldLaneDir;
VECTOR pos;
u_char canProceed;
u_char turnRight;
CDATA2D cd[2];
canProceed = 0;
oldLane = cp->ai.c.currentLane;
currentLane = oldLane;
if (cp->ai.c.ctrlState != 8 && cp->ai.c.changeLaneIndicateCount == 0)
{
int roadWidthInLanes;
int segLen;
if (straight == NULL)
{
roadWidthInLanes = ROAD_WIDTH_IN_LANES(curve);
segLen = curve->end - curve->start & 0xfff;
}
else
{
roadWidthInLanes = ROAD_WIDTH_IN_LANES(straight);
segLen = straight->length;
}
newLane = currentLane + (Random2((int)straight) >> 7 & 2U) + 0xff & 0xff;
if (tryToPark)
{
if (oldLane == 1)
newLane = 0;
else if (roadWidthInLanes - 2 == currentLane)
newLane = roadWidthInLanes - 1;
// [A] don't park near the road ends
if(distAlongSegment < 1024 && distAlongSegment > segLen - 1024)
return currentLane;
}
for (trials = 0; trials < 2; trials++)
{
if (trials == 1)
newLane = currentLane * 2 - newLane & 0xff;
if (newLane == 0xff || roadWidthInLanes <= newLane)
continue;
//travelAlongRoad = (currentLane ^ 1) & 1;
if (gCurrentMissionNumber == 33 && makeLimoPullOver)
{
// bypass checks
}
else if (tryToPark)
{
int allowedToDrive;
allowedToDrive = straight && ROAD_IS_AI_LANE(straight, newLane) || curve && ROAD_IS_AI_LANE(curve, newLane);
if (!allowedToDrive)
continue;
}
else
{
int allowedToPark;
int allowedToDrive;
// collect the lanes.
allowedToPark = straight && ROAD_IS_PARKING_ALLOWED_AT(straight, newLane) || curve && ROAD_IS_PARKING_ALLOWED_AT(curve, newLane);
allowedToDrive = straight && ROAD_IS_AI_LANE(straight, newLane) || curve && ROAD_IS_AI_LANE(curve, newLane);
if (!allowedToDrive || allowedToPark)
continue;
}
// don't allow to change lane which in wrong direction
if (straight != NULL)
{
oldLaneDir = ROAD_LANE_DIR(straight, oldLane);
if (oldLaneDir != ROAD_LANE_DIR(straight, newLane))
continue;
}
else if (curve != NULL)
{
oldLaneDir = ROAD_LANE_DIR(curve, oldLane);
if (oldLaneDir != ROAD_LANE_DIR(curve, newLane))
continue;
}
if (cp->ai.c.ctrlNode == NULL && cp->ai.c.turnNode == -1)
{
canProceed = true;
turnRight = oldLaneDir == 0;
if (currentLane < newLane)
turnRight = !turnRight;
if (turnRight)
theta = cp->hd.direction + 1024;
else
theta = cp->hd.direction - 1024;
cd[0].x.vx = cp->hd.oBox.location.vx + FIXEDH(RSIN(theta) * 512);
cd[0].x.vz = cp->hd.oBox.location.vz + FIXEDH(RCOS(theta) * 512);
cd[0].length[0] = cp->ap.carCos->colBox.vz + 93;
cd[0].length[1] = cp->ap.carCos->colBox.vx;
cd[0].theta = cp->hd.direction;
lcp = &car_data[MAX_CARS-1];
while (lcp >= car_data)
{
if (lcp != cp && lcp->controlType != CONTROL_TYPE_NONE)
{
car_cos = lcp->ap.carCos;
cd[1].length[0] = car_cos->colBox.vz + 93;
cd[1].length[1] = car_cos->colBox.vx;
cd[1].x.vx = lcp->hd.oBox.location.vx;
cd[1].x.vz = lcp->hd.oBox.location.vz;
cd[1].theta = lcp->hd.direction;
length = ((cd[0].length[0] + cd[1].length[0]) * 3) / 2;
dx = cd[0].x.vx - cd[1].x.vx;
dz = cd[0].x.vz - cd[1].x.vz;
if (ABS(dx) < length && ABS(dz) < length)
{
if (bcollided2d(cd))
{
canProceed = false;
break;
}
}
}
lcp--;
}
if (canProceed)
break;
}
}
if (canProceed)
{
cp->ai.c.changeLaneIndicateCount = 161;
cp->ai.c.oldLane = oldLane;
cp->ai.c.changeLaneCount = 4;
cp->ai.c.changeLane = turnRight;
currentLane = newLane;
}
}
return currentLane;
}
int makeNextNodeCtrlNode = -1;
// [D] [T]
int CreateNewNode(CAR_DATA * cp)
{
CIV_ROUTE_ENTRY* start;
CIV_ROUTE_ENTRY* newNode;
CIV_ROUTE_ENTRY* retNode;
int oldRoad;
int cr;
DRIVER2_ROAD_INFO roadInfo;
CIV_ROUTE_ENTRY tempNode;
int turnAngle;
int startDist;
int travelDir;
int segmentEnd;
int cornerAngle;
start = cp->ai.c.pnode;
turnAngle = 0;
// validate two next nodes
if (IS_NODE_VALID(cp, GET_NEXT_NODE(cp, start)) &&
IS_NODE_VALID(cp, GET_NEXT_NODE(cp, start + 1)))
{
do {
newNode = retNode = GET_NEXT_NODE(cp, start);
if (retNode->pathType == 127)
{
oldRoad = cp->ai.c.currentRoad;
if (cp->id == makeNextNodeCtrlNode)
{
cp->ai.c.ctrlNode = newNode;
makeNextNodeCtrlNode = -1;
}
// stop, dammmm
if (!GetSurfaceRoadInfo(&roadInfo, oldRoad))
break;
if (ROAD_LANE_DIR(&roadInfo, cp->ai.c.currentLane) == 0)
travelDir = 1;
else
travelDir = -1;
if (roadInfo.straight)
{
segmentEnd = roadInfo.straight->length;
newNode->distAlongSegment = start->distAlongSegment + travelDir * 1024;
newNode->dir = start->dir;
}
else
{
int dist;
segmentEnd = roadInfo.curve->end - roadInfo.curve->start & 0xfff;
if (roadInfo.curve->inside < 10)
dist = travelDir << 7;
else if (roadInfo.curve->inside < 20)
dist = travelDir << 6;
else
dist = travelDir << 5;
newNode->distAlongSegment = start->distAlongSegment + dist;
newNode->dir = newNode->distAlongSegment + roadInfo.curve->start + travelDir * 1024 & 0xfff;
}
// check travelling direction and position on segment
// advance road if needed
if ((travelDir > 0 && segmentEnd < newNode->distAlongSegment) ||
(travelDir < 0 && newNode->distAlongSegment < 0))
{
if ((travelDir > 0 && segmentEnd <= start->distAlongSegment) ||
(travelDir < 0 && start->distAlongSegment < 1))
{
cp->ai.c.currentRoad = GetNextRoadInfo(cp, 1, &turnAngle, &startDist, start);
newNode->dir = start->dir + turnAngle & 0xfff;
newNode->distAlongSegment = startDist;
if (cp->ai.c.currentRoad == -1)
break;
}
else
{
if (travelDir < 1)
newNode->distAlongSegment = 0;
else
newNode->distAlongSegment = segmentEnd;
}
}
cr = cp->ai.c.currentRoad;
if (GetSurfaceRoadInfo(&roadInfo, cr))
{
// this node is unused
tempNode.dir = newNode->dir;
tempNode.pathType = newNode->pathType;
tempNode.distAlongSegment = newNode->distAlongSegment;
tempNode.x = newNode->x;
tempNode.z = newNode->z;
if (oldRoad != cr && turnAngle == 0)
{
if (ROAD_LANE_DIR(&roadInfo, cp->ai.c.currentLane) == 0)
travelDir = 1;
else
travelDir = -1;
if (roadInfo.curve)
{
int dist;
if (roadInfo.curve->inside < 10)
dist = travelDir << 7;
else if (roadInfo.curve->inside < 20)
dist = travelDir << 6;
else
dist = travelDir << 5;
newNode->distAlongSegment = newNode->distAlongSegment + dist;
}
else
{
newNode->distAlongSegment = newNode->distAlongSegment + travelDir * 1024;
}
}
// Caine's Cash limo can be stopped by player in police car
if (gCurrentMissionNumber == 33 && cp->ap.model == 4)
{
int dx, dz;
CAR_DATA* playerCar = &car_data[player[0].playerCarId];
dx = playerCar->hd.where.t[0] - cp->hd.where.t[0];
dz = playerCar->hd.where.t[1] - cp->hd.where.t[1];
// check if siren is on and we near the car
if (CarHasSiren(playerCar->ap.model) && player[0].horn.on &&
dx * dx + dz * dz < 16000000)
{
makeLimoPullOver = 1;
}
}
// try parking it
if ((Random2(0) + cp->id * 32 & 0xf1) == 0xf1 || makeLimoPullOver)
{
int tryToPark;
int newLane;
tryToPark = 0;
if (numParkedCars < maxParkedCars)
tryToPark = (Random2(0) & 2U) != 0;
// apply our Caine's Cash logic
if (gCurrentMissionNumber == 33 && cp->ap.model == 4 && limoId == cp->id) // [A] limoId was skipped, bringing it back.
{
tryToPark = makeLimoPullOver;
}
newLane = CheckChangeLanes(roadInfo.straight, roadInfo.curve, newNode->distAlongSegment, cp, tryToPark);
// try change lanes or park
if (newLane != cp->ai.c.currentLane)
{
cp->ai.c.currentLane = newLane;
if (tryToPark)
{
if (makeLimoPullOver || cp->ai.c.ctrlState == 0 && ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, newLane))
{
makeNextNodeCtrlNode = cp->id;
cp->ai.c.ctrlState = 8;
cp->ai.c.ctrlNode = newNode; // [A]
cp->ai.c.changeLaneCount = 0;
}
}
}
}
tempNode.x = newNode->x;
tempNode.z = newNode->z;
tempNode.dir = newNode->dir;
tempNode.pathType = newNode->pathType;
tempNode.distAlongSegment = newNode->distAlongSegment;
GetNodePos(roadInfo.straight, NULL, roadInfo.curve, newNode->distAlongSegment, cp, &tempNode.x, &tempNode.z, cp->ai.c.currentLane);
if (turnAngle == -1024)
{
int cornerAngle;
int segLength;
int tmp;
int dx;
int dz;
dx = tempNode.x - start->x;
dz = tempNode.z - start->z;
segLength = (SquareRoot0(dx * dx + dz * dz) - (cp->hd.wheel_speed / 170));
cornerAngle = DIFF_ANGLES(start->dir, ratan2(dx, dz)); //((ratan2(dx, dz) - start->dir) + 2048U & 0xfff) - 2048;
tmp = FIXEDH(segLength * RCOS(cornerAngle));
retNode = newNode;
if (tmp > 0)
{
newNode->x = start->x + FIXEDH(tmp * RSIN(start->dir));
newNode->z = start->z + FIXEDH(tmp * RCOS(start->dir));
newNode->pathType = 1;
newNode->dir = start->dir;
retNode = GET_NEXT_NODE(cp, newNode);
}
tmp = FIXEDH(segLength * RSIN(cornerAngle));
newNode = retNode;
if (tmp < 0)
{
retNode->x = tempNode.x + FIXEDH(tmp * RSIN(tempNode.dir));
retNode->z = tempNode.z + FIXEDH(tmp * RCOS(tempNode.dir));
retNode->pathType = 1;
retNode->dir = tempNode.dir;
newNode = GET_NEXT_NODE(cp, retNode);
}
}
// push into empty node
if (newNode->pathType == 127)
{
newNode->x = tempNode.x;
newNode->z = tempNode.z;
newNode->dir = tempNode.dir;
newNode->pathType = tempNode.pathType;
newNode->distAlongSegment = tempNode.distAlongSegment;
if (ABS(tempNode.x) < 600000)
{
if (turnAngle)
{
cp->ai.c.turnNode = GET_NODE_ID(cp, newNode);
cp->ai.c.turnDir = (turnAngle == 1024);
}
newNode->pathType = 1;
return 1;
}
}
}
break;
}
//if (newNode >= end) // [A] i don't think that's ever needed now, probably have done better macro than OG devs
// newNode = start - 12;
start = newNode;
} while (newNode != cp->ai.c.pnode);
}
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
// [D] [T]
int InitCivState(CAR_DATA * cp, EXTRA_CIV_DATA * extraData)
{
CIV_STATE* cs = &cp->ai.c;
cp->controlType = CONTROL_TYPE_CIV_AI;
if (extraData == NULL)
cp->ai.c.thrustState = 0;
else
cp->ai.c.thrustState = extraData->thrustState;
// not moving in that state
if (cp->ai.c.thrustState == 3)
{
if (extraData == NULL)
cp->ai.c.ctrlState = 0;
else
cp->ai.c.ctrlState = extraData->ctrlState;
return 1;
}
if (extraData == NULL)
cs->currentRoad = GetSurfaceIndex((VECTOR*)cp->hd.where.t);
else
cs->currentRoad = extraData->surfInd;
// init road and nodes
if (cs->currentRoad > -1)
{
DRIVER2_ROAD_INFO roadInfo;
cp->ai.c.currentNode = 0;
cp->ai.c.pnode = NULL;
cp->ai.c.ctrlNode = NULL;
cp->ai.c.turnNode = -1;
if(GetSurfaceRoadInfo(&roadInfo, cs->currentRoad))
{
cp->ai.c.maxSpeed = speedLimits[ROAD_SPEED_LIMIT(&roadInfo)];
InitNodeList(cp, extraData);
cp->ai.c.pnode = &cp->ai.c.targetRoute[0];
cp->hd.where.t[0] = cp->ai.c.pnode->x;
cp->hd.where.t[2] = cp->ai.c.pnode->z;
return 1;
}
}
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
// [D] [T]
int PingOutCar(CAR_DATA * cp)
{
testNumPingedOut++;
if (cp->controlType == CONTROL_TYPE_CIV_AI)
{
if (cp->controlFlags & CONTROL_FLAG_COP)
numCopCars--;
numCivCars--;
if (cp->ai.c.thrustState == 3 && cp->ai.c.ctrlState == 5)
numParkedCars--;
}
else if (PingOutCivsOnly != 0 && valid_region(cp->hd.where.t[0], cp->hd.where.t[2]))
{
return 0;
}
// inform target that car was pinged out
if (cp->inform)
*cp->inform ^= TARGET_FLAG_CAR_PINGED_IN;
cp->inform = NULL;
ClearMem((char*)cp, sizeof(CAR_DATA));
cp->controlType = CONTROL_TYPE_NONE;
return 1;
}
// [D] [T]
// used when secret car is requested
int PingOutAllSpecialCivCars(void)
{
CAR_DATA* lcp;
lcp = car_data;
do
{
if (lcp->controlType == CONTROL_TYPE_CIV_AI && MissionHeader->residentModels[lcp->ap.model] > 4)
PingOutCar(lcp);
lcp++;
}
while (lcp < &car_data[MAX_CARS]);
return 1;
}
// [D] [T]
// used by cutscenes
int PingOutAllCivCarsAndCopCars(void)
{
CAR_DATA* cp;
cp = car_data;
do {
if (cp->controlType == CONTROL_TYPE_CIV_AI || cp->controlType == CONTROL_TYPE_PURSUER_AI)
PingOutCar(cp);
cp++;
} while (cp < &car_data[MAX_CARS]);
return 1;
}
// [D] [T]
// called by civ car.
// checks distance from player and removes car if too far
int CheckPingOut(CAR_DATA * cp)
{
int dx, dz;
int dist;
dx = player[0].spoolXZ->vx - cp->hd.where.t[0];
dz = player[0].spoolXZ->vz - cp->hd.where.t[2];
dist = dx * dx + dz * dz;
if (dist > 24000 * 24000 && PingOutCar(cp))
return 1;
if (requestStationaryCivCar == 1 && distFurthestCivCarSq < dist)
{
furthestCivID = cp->id;
distFurthestCivCarSq = dist;
}
return 1;
}
// [D] [T]
void SetUpTrafficLightPhase(void)
{
junctionLightsPhase[0] = TrafficLightCycle(0);
junctionLightsPhase[1] = TrafficLightCycle(1);
}
// [D] [T]
int TrafficLightCycle(int exit)
{
int timeCnt;
timeCnt = CameraCnt - frameStart & 0x1ff;
if (exit == 0 || exit == 2)
{
if (timeCnt < 256)
return 1;
timeCnt -= 256;
}
else if (timeCnt > 255)
return 1;
if (timeCnt < 150)
return 3;
else if (timeCnt > 199)
return 1;
return 2;
}
// [D] [T]
void InitCivCars(void)
{
PingBufferPos = 0;
cookieCount = 0;
requestRoadblock = 0;
makeLimoPullOver = 0;
frameStart = CameraCnt;
roadblockDelay = roadblockDelayDiff[gCopDifficultyLevel] + (Random2(0) & 0xff);
PingOutCivsOnly = 0;
roadblockCount = roadblockDelay;
// [A] clear out other values
distFurthestCivCarSq = 0;
furthestCivID = 0;
makeLimoPullOver = 0;
limoId = 0;
playerNum = 0;
roadSeg = 0;
testNumPingedOut = 0;
currentAngle = 0;
closeEncounter = 3;
}
const int EVENT_CAR_SPEED = 71;
const int DistanceTriggerCarMoves = 5000;
// [D] [T] [A]
int CreateCivCarWotDrivesABitThenStops(int direction, LONGVECTOR4* startPos, LONGVECTOR4* stopPos, unsigned char internalModel, int palette)
{
unsigned char* slot;
CAR_DATA* carCnt;
CAR_DATA* pNewCar;
CIV_ROUTE_ENTRY* stopNode; // $a0
CIV_ROUTE_ENTRY* spareNode; // $a1
EXTRA_CIV_DATA civDat;
ClearMem((char*)&civDat, sizeof(civDat));
carCnt = car_data;
slot = reservedSlots;
pNewCar = NULL;
// find free slot
do {
if (carCnt->controlType == CONTROL_TYPE_NONE && *slot == 0)
{
pNewCar = carCnt;
break;
}
carCnt++;
slot++;
} while (carCnt < &car_data[MAX_CARS]);
if (pNewCar == NULL)
return -1;
civDat.thrustState = 3;
civDat.ctrlState = 8;
civDat.controlFlags = 0;
civDat.palette = palette;
civDat.angle = direction;
InitCar(pNewCar, direction, startPos, 2, internalModel, 0, (char*)&civDat);
pNewCar->ai.c.ctrlState = 8;
pNewCar->ai.c.maxSpeed = EVENT_CAR_SPEED;
pNewCar->st.n.linearVelocity[1] = 0;
pNewCar->st.n.linearVelocity[0] = EVENT_CAR_SPEED * RSIN(direction);
pNewCar->st.n.linearVelocity[2] = EVENT_CAR_SPEED * RCOS(direction);
pNewCar->ai.c.velRatio = (EVENT_CAR_SPEED * ONE) / (DistanceTriggerCarMoves - pNewCar->ap.carCos->colBox.vz * 3);
pNewCar->ai.c.targetRoute[0].x = (*startPos)[0];
pNewCar->ai.c.targetRoute[0].z = (*startPos)[2];
stopNode = &pNewCar->ai.c.targetRoute[1];
pNewCar->ai.c.ctrlNode = stopNode;
stopNode->pathType = 1;
stopNode->dir = direction;
stopNode->distAlongSegment = 0;
stopNode->x = (*startPos)[0] + FIXEDH(DistanceTriggerCarMoves * RSIN(direction));
stopNode->z = (*startPos)[2] + FIXEDH(DistanceTriggerCarMoves * RCOS(direction));
spareNode = &pNewCar->ai.c.targetRoute[2];
spareNode->pathType = 1;
spareNode->dir = direction;
spareNode->distAlongSegment = 0;
spareNode->x = (*startPos)[0] + FIXEDH(DistanceTriggerCarMoves * RSIN(direction) * 3);
spareNode->z = (*startPos)[2] + FIXEDH(DistanceTriggerCarMoves * RCOS(direction) * 3);
numCivCars++;
return pNewCar->id;
}
// [D] [T]
int CreateStationaryCivCar(int direction, long orientX, long orientZ, LONGVECTOR4* startPos, int externalModel, int palette, int controlFlags)
{
unsigned char* slot;
CAR_DATA* newCar;
CAR_DATA* carCnt;
int model;
EXTRA_CIV_DATA civDat;
LONGQUATERNION tmpQ;
model = -1;
if (MissionHeader->residentModels[0] == externalModel)
model = 0;
else if (MissionHeader->residentModels[1] == externalModel)
model = 1;
else if (MissionHeader->residentModels[2] == externalModel)
model = 2;
else if (MissionHeader->residentModels[3] == externalModel)
model = 3;
else if (MissionHeader->residentModels[4] == externalModel)
model = 4;
if (model != -1)
{
if (externalModel > 4 && specModelValid == 0)
{
return -1;
}
// pick free slot
newCar = NULL;
carCnt = car_data;
slot = reservedSlots;
do {
if (carCnt->controlType == CONTROL_TYPE_NONE && *slot == 0)
{
newCar = carCnt;
break;
}
carCnt++;
slot++;
} while (carCnt < &car_data[MAX_CARS]);
if (newCar)
{
int dx, dz;
civDat.thrustState = 3;
civDat.ctrlState = 7;
if (controlFlags & CONTROL_FLAG_COP)
{
requestCopCar = 0;
numCopCars++;
cop_respawn_timer = gCopRespawnTime;
}
civDat.controlFlags = controlFlags | CONTROL_FLAG_WAS_PARKED;
if (gCurrentMissionNumber != 32 && externalModel == 0)
civDat.controlFlags |= CONTROL_FLAG_COP_SLEEPING;
civDat.palette = palette;
civDat.angle = direction;
InitCar(newCar, direction, startPos, 2, model, 0, (char*)&civDat);
dx = RSIN(orientZ / 2);
dz = RCOS(orientZ / 2);
tmpQ[0] = FIXEDH(newCar->st.n.orientation[1] * dx + newCar->st.n.orientation[0] * dz);
tmpQ[1] = FIXEDH(newCar->st.n.orientation[1] * dz - newCar->st.n.orientation[0] * dx);
tmpQ[2] = FIXEDH(newCar->st.n.orientation[3] * dx + newCar->st.n.orientation[2] * dz);
tmpQ[3] = FIXEDH(newCar->st.n.orientation[3] * dz - newCar->st.n.orientation[2] * dx);
dx = RSIN(orientX / 2);
dz = RCOS(orientX / 2);
newCar->st.n.orientation[0] = FIXEDH(tmpQ[3] * dx + tmpQ[0] * dz);
newCar->st.n.orientation[1] = FIXEDH(tmpQ[2] * dx + tmpQ[1] * dz);
newCar->st.n.orientation[2] = FIXEDH(tmpQ[2] * dz - tmpQ[1] * dx);
newCar->st.n.orientation[3] = FIXEDH(tmpQ[3] * dz - tmpQ[0] * dx);
numCivCars++;
return newCar->id;
}
}
return -1;
}
VECTOR baseLoc;
VECTOR randomLoc;
int dx = 0; // offset 0xAAB40
int dy = 0; // offset 0xAAB44
int dz = 0; // offset 0xAAB48
#define PINGIN_DIST_WANTED_MULT (10)
#define PINGIN_DIST_MULT (8)
// [D] [T] [A] - some register is not properly decompiled
int PingInCivCar(int minPingInDist)
{
int model;
CAR_DATA* carCnt;
CAR_DATA* newCar;
//DRIVER2_CURVE* curve;
//DRIVER2_STRAIGHT* straight;
DRIVER2_ROAD_INFO roadInfo;
EXTRA_CIV_DATA civDat;
u_char possibleLanes[12];
// carDistLanes removed as it's unused
LONGVECTOR4 pos;
int distSq;
int dir;
int curveLength;
int tryPingInParkedCars;
int count;
int lbody;
int lane;
int i;
u_char cookieCountStart;
u_int retDistSq;
unsigned char* slot;
//straight = NULL;
//curve = NULL;
civDat.distAlongSegment = -5;
lane = -1;
dir = 0xffffff;
tryPingInParkedCars = 0;
PingOutCivsOnly = 1;
_CutRec_HandleCarRequest();
if (requestCopCar == 0 && numParkedCars < maxParkedCars && (gCurrentMissionNumber != 33 || numCivCars != 0))
{
tryPingInParkedCars = 1;
}
if (NumPlayers == 2)
playerNum = CameraCnt & 1;
else
playerNum = 0;
if (MissionHeader->type & 0x4)
{
PingOutCivsOnly = 1;
return 0;
}
if (gInGameCutsceneActive != 0)
{
PingOutCivsOnly = 1;
return 0;
}
if (numCivCars >= maxCivCars - 1 && gInGameChaseActive == 0)
{
PingOutCivsOnly = 1;
return 0;
}
/*if (NumPlayers > 1) // [A] allow pinging in cars in multiplayer game such as Cops and Robbers
{
PingOutCivsOnly = 1;
return 0;
}*/
newCar = NULL;
ClearMem((char*)&civDat, sizeof(civDat));
baseLoc.vx = player[playerNum].spoolXZ->vx;
baseLoc.vz = player[playerNum].spoolXZ->vz;
// allow to use ping buffer sometimes
if (IsPingInfoAvailable())
{
// rely on pings provided by cutscenes/chases
int angle;
int dx, dz;
int newCarId;
newCarId = GetPingInfo(&cookieCount);
if (newCarId == -1)
return 0;
//if (newCarId > MAX_CARS - 1)
// return 0;
newCar = &car_data[newCarId];
// ping out old car
if (newCar->controlType != CONTROL_TYPE_NONE)
{
if (PingOutCar(newCar) == 0)
return 0;
}
if (requestCopCar != 0)
{
angle = (cookieCount * ONE) / 56;
dx = RSIN(angle) * PINGIN_DIST_WANTED_MULT;
dz = RCOS(angle) * PINGIN_DIST_WANTED_MULT;
}
else
{
angle = (cookieCount * ONE) / 44;
dx = RSIN(angle) * PINGIN_DIST_MULT;
dz = RCOS(angle) * PINGIN_DIST_MULT;
}
randomLoc.vx = baseLoc.vx + FIXEDH(dx) * 2048;
randomLoc.vz = baseLoc.vz + FIXEDH(dz) * 2048;
roadSeg = RoadInCell(&randomLoc);
}
else
{
// randomized pings
int angle;
int dx, dz;
const int maxCookies = requestCopCar ? 55 : 43;
if (requestCopCar == 0 && cookieCount > 43)
cookieCount -= 25;
cookieCountStart = cookieCount;
// find a free slot
carCnt = car_data;
slot = reservedSlots;
do {
if (carCnt->controlType == CONTROL_TYPE_NONE && *slot == 0)
{
newCar = carCnt;
break;
}
carCnt++;
slot++;
} while (carCnt < &car_data[MAX_CARS]);
if (newCar == NULL)
{
PingOutCivsOnly = 1;
return 0;
}
do {
if (cookieCount < maxCookies)
cookieCount++;
else
cookieCount = 0;
if (cookieCount == cookieCountStart)
{
break;
}
if (requestCopCar != 0)
{
angle = (cookieCount * ONE) / 56;
dx = RSIN(angle) * PINGIN_DIST_WANTED_MULT;
dz = RCOS(angle) * PINGIN_DIST_WANTED_MULT;
}
else
{
angle = (cookieCount * ONE) / 44;
dx = RSIN(angle) * PINGIN_DIST_MULT;
dz = RCOS(angle) * PINGIN_DIST_MULT;
}
// [A] make limo ping in closer and in right direction
if (minPingInDist == 666)
{
randomLoc.vx = baseLoc.vx + FIXEDH(dx) * 1024;
randomLoc.vz = baseLoc.vz + FIXEDH(dz) * 1024;
}
else
{
randomLoc.vx = baseLoc.vx + FIXEDH(dx) * 2048;
randomLoc.vz = baseLoc.vz + FIXEDH(dz) * 2048;
}
roadSeg = RoadInCell(&randomLoc);
} while (!IS_STRAIGHT_SURFACE(roadSeg) && !IS_CURVED_SURFACE(roadSeg));
}
// wtf there were before? car wasn't set to 'confused' state
if (!GetSurfaceRoadInfo(&roadInfo, roadSeg))
{
civPingTest.OffRoad++;
//CIV_STATE_SET_CONFUSED(newCar);
return 0;
}
{
volatile int numPossibleLanes;
volatile int numLanes;
volatile int allowedToPark;
if (ROAD_LANES_COUNT(&roadInfo) == 0) // BAD ROAD
{
//CIV_STATE_SET_CONFUSED(newCar);
return 0;
}
numLanes = ROAD_WIDTH_IN_LANES(&roadInfo);
numPossibleLanes = 0;
// Caine's Cash limo spawned always on lane 1. Don't allow it parked!
if (minPingInDist == 666)
{
lane = 1;
}
else
{
for (i = 0; i < numLanes; i++)
{
// collect the lanes.
allowedToPark = ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, i);
// this is closest to OG decompiled. Works different!
//if ((
// ((tryPingInParkedCars && allowedToPark))) ||
// ((ROAD_IS_AI_LANE(straight, i) && (((i != 0 || ((straight->NumLanes & 0x40U) == 0)) && (((straight->NumLanes & 0xffffff0f) * 2 - 1 != i || ((straight->NumLanes & 0x80U) == 0))))))))
// pick only non-parkable driveable lanes if parked cars not requested
if (tryPingInParkedCars && allowedToPark || ROAD_IS_AI_LANE(&roadInfo, i) && !allowedToPark)
possibleLanes[numPossibleLanes++] = i;
}
if (numPossibleLanes == 0)
return 0;
lane = possibleLanes[(Random2(0) >> 8) % numPossibleLanes];
}
// check if need to make a parked car
if (ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, lane))
{
civDat.thrustState = 3;
civDat.ctrlState = 7;
// set to drive off
if (ROAD_IS_AI_LANE(&roadInfo, lane))
civDat.ctrlState = 5;
}
else
{
// Car is not active. Permanently parked
if (ROAD_IS_AI_LANE(&roadInfo, lane) == 0)
{
civPingTest.NotDrivable++;
return 0;
}
civDat.thrustState = 0;
civDat.ctrlState = 0;
}
}
// [A] what's model is this?
if (gInGameCutsceneActive == 0 && gInGameChaseActive == 0 && (Random2(0) & 0x40) == 0)
{
modelRandomList[12] = 3;
}
// seems like this mission check (Get a cop car) is a bug fix
// choose to spawn parked cops
if (gCurrentMissionNumber == 32 || civDat.thrustState != 3)
{
modelRandomList[12] = 0;
}
// check if special car is loaded and add it to random list
if (specModelValid == 0 || allowSpecSpooling == 0 || MissionHeader->residentModels[4] == 12)
{
modelRandomList[15] = 0;
modelRandomList[14] = 1;
}
else
{
modelRandomList[15] = 4;
modelRandomList[14] = 1;
if ((Random2(0) & 0x100) != 0)
modelRandomList[14] = 4;
}
// another change for Caine's compound
if (gCurrentMissionNumber == 7 || gCurrentMissionNumber == 33)
{
modelRandomList[9] = 0;
modelRandomList[8] = 0;
modelRandomList[11] = 1;
modelRandomList[10] = 1;
}
else
{
modelRandomList[11] = 2;
modelRandomList[10] = 2;
modelRandomList[9] = 2;
modelRandomList[8] = 2;
}
civDat.controlFlags = 0;
if (requestCopCar)
{
model = 3;
civDat.controlFlags = CONTROL_FLAG_COP;
}
else
{
model = modelRandomList[Random2(0) & 0xf];
}
// force spawn limo nearby in Caine's Cash
if (minPingInDist == 666)
model = 4;
// select car color palette
if (MissionHeader->residentModels[model] == 0 || MissionHeader->residentModels[model] > 4)
{
civDat.palette = 0;
}
else if (player[0].playerType == 1 && car_data[player[0].playerCarId].ap.model == model)
{
civDat.palette = Random2(0) % 5; // [A] was % 4; use previously unused palette slot
// if player got the same color we better select other
if (car_data[player[0].playerCarId].ap.palette <= civDat.palette)
civDat.palette++;
}
else
{
civDat.palette = Random2(0) % 6; // [A] was % 5; use previously unused palette slot
}
lbody = car_cosmetics[model].colBox.vz;
// check bounds
if (IS_STRAIGHT_SURFACE(roadSeg))
{
int theta;
int minDistAlong;
int scDist;
if (requestCopCar == 0)
{
scDist = lbody * 2;
minDistAlong = lbody * 3;
}
else
{
scDist = lbody / 2;
minDistAlong = 0;
}
if (roadInfo.straight->length <= (scDist + lbody) * 2) // don't spawn outside straight
return 0;
dx = randomLoc.vx - roadInfo.straight->Midx;
dz = randomLoc.vz - roadInfo.straight->Midz;
theta = roadInfo.straight->angle - ratan2(dx, dz);
civDat.distAlongSegment = (roadInfo.straight->length / 2) + FIXEDH(RCOS(theta) * SquareRoot0(dx * dx + dz * dz));
if (requestCopCar == 0)
{
if (civDat.distAlongSegment < minDistAlong)
civDat.distAlongSegment = minDistAlong;
if (roadInfo.straight->length - civDat.distAlongSegment < minDistAlong)
civDat.distAlongSegment = roadInfo.straight->length - minDistAlong;
}
if (ROAD_LANE_DIR(roadInfo.straight, lane) == 0)
dir = roadInfo.straight->angle;
else
dir = roadInfo.straight->angle + 2048U & 0xfff;
}
else if (IS_CURVED_SURFACE(roadSeg))
{
int minDistAlong;
int segmentLen;
currentAngle = ratan2(randomLoc.vx - roadInfo.curve->Midx, randomLoc.vz - roadInfo.curve->Midz);
minDistAlong = 0;
if (requestCopCar == 0)
{
if (roadInfo.curve->inside < 10)
minDistAlong = 128;
else if (roadInfo.curve->inside < 20)
minDistAlong = 64;
else
minDistAlong = 32;
}
segmentLen = (roadInfo.curve->end - roadInfo.curve->start) - minDistAlong & 0xfff;
if (roadInfo.curve->inside < 10)
civDat.distAlongSegment = (currentAngle & 0xfffU) - roadInfo.curve->start & 0xf80;
else if (roadInfo.curve->inside < 20)
civDat.distAlongSegment = (currentAngle & 0xfffU) - roadInfo.curve->start & 0xfc0;
else
civDat.distAlongSegment = (currentAngle & 0xfffU) - roadInfo.curve->start & 0xfe0;
if (civDat.distAlongSegment <= minDistAlong)
civDat.distAlongSegment = minDistAlong;
if (civDat.distAlongSegment >= segmentLen)
civDat.distAlongSegment = segmentLen;
if (ROAD_LANE_DIR(roadInfo.curve, lane) == 0)
dir = civDat.distAlongSegment + roadInfo.curve->start + 1024;
else
dir = civDat.distAlongSegment + roadInfo.curve->start - 1024;
curveLength = ((roadInfo.curve->end - roadInfo.curve->start & 0xfffU) * roadInfo.curve->inside * 11) / 7;
if (lbody * 6 > curveLength) // don't spawn outside curve
return 0;
}
// too much parked cars?
if (civDat.thrustState == 3 && civDat.ctrlState == 5 && numParkedCars >= maxParkedCars)
{
return 0;
}
if (dir == 0xffffff || lane < 0 || civDat.distAlongSegment < 0)
{
//CIV_STATE_SET_CONFUSED(newCar);
return 0;
}
GetNodePos(roadInfo.straight, NULL, roadInfo.curve, civDat.distAlongSegment, newCar, &newCar->ai.c.targetRoute[0].x, &newCar->ai.c.targetRoute[0].z, lane);
retDistSq = INT_MAX;
pos[0] = newCar->ai.c.targetRoute[0].x;
pos[2] = newCar->ai.c.targetRoute[0].z;
pos[1] = randomLoc.vy;
count = 0;
carCnt = car_data;
while (carCnt < &car_data[MAX_CARS])
{
if (carCnt->controlType != CONTROL_TYPE_NONE)
{
int dx, dy, dz;
u_int sqDist;
dy = randomLoc.vy - carCnt->hd.where.t[1];
dy = ABS(dy);
if (dy < 800)
{
dx = pos[0] - carCnt->hd.where.t[0];
dz = pos[2] - carCnt->hd.where.t[2];
sqDist = (dx * dx + dz * dz);
if (ABS(dx) < 4096 && ABS(dz) < 4096 && sqDist < retDistSq)
{
retDistSq = sqDist;
}
// check if this car's route is too close to others
if (ABS(dx) < 0x7000 && ABS(dz) < 0x7000 && sqDist < 9000000)
{
count++;
if (count > closeEncounter)
return 0;
}
}
}
carCnt++;
}
//if (retDistSq != 56) // [A] This was decompiled wrong. Can't get the meaning for it
distSq = retDistSq;
if (distSq < (lbody * lbody * 8 * 8))
return 0;
civDat.surfInd = roadSeg;
if (roadSeg < 0)
{
//CIV_STATE_SET_CONFUSED(newCar); // [A] not needed really before InitCar happens
return 0;
}
civDat.angle = dir;
InitCar(newCar, dir, &pos, 2, model, 0, (char*)&civDat);
// set the lane
newCar->ai.c.currentLane = lane;
if (minPingInDist == 666)
limoId = newCar->id;
if (newCar->ai.c.ctrlState == 5)
{
numParkedCars++;
// parked car is going to unpark
if (newCar->ai.c.thrustState == 3)
newCar->controlFlags |= CONTROL_FLAG_WAS_PARKED;
}
// cop car request done
if (newCar->controlFlags & CONTROL_FLAG_COP)
{
requestCopCar = 0;
numCopCars++;
}
numCivCars++;
// init Cop
if (newCar->controlFlags & CONTROL_FLAG_COP)
PassiveCopTasks(newCar);
PingOutCivsOnly = 0;
// [A] REDRIVER2 always stores pings
_CutRec_StorePingInfo(cookieCount, newCar->id);
return newCar->id + 1;
}
// [D] [T]
void AttemptUnPark(CAR_DATA * cp)
{
unsigned char oldLane;
DRIVER2_ROAD_INFO roadInfo;
InitCivState(cp, NULL);
oldLane = cp->ai.c.currentLane;
if(GetSurfaceRoadInfo(&roadInfo, cp->ai.c.currentRoad))
{
cp->ai.c.currentLane = CheckChangeLanes(roadInfo.straight, roadInfo.curve, cp->ai.c.targetRoute[0].distAlongSegment, cp, 0);
if (oldLane == cp->ai.c.currentLane || ROAD_IS_AI_LANE(&roadInfo, cp->ai.c.currentLane) == 0)
{
// shut off. Unknown intention
CIV_STATE_SET_CONFUSED(cp);
}
}
}
int sideMul = 10;
int collDat = 0;
int carnum = 0;
const int newAccel = 2000;
// [D] [T]
int CivAccelTrafficRules(CAR_DATA * cp, int* distToNode)
{
CAR_COSMETICS* car_cos;
CAR_DATA* lcp;
int tangent;
int normal;
int lbody;
int wbody;
car_cos = cp->ap.carCos;
lbody = car_cos->colBox.vz;
wbody = car_cos->colBox.vx;
// why only first car must die?
// P.S. it's the only one with the music
if (cp->id == 1 && cp->ai.c.carMustDie == 1)
{
cp->ai.c.thrustState = 3;
cp->ai.c.ctrlState = 9;
cp->ai.c.carMustDie = 0;
}
switch (cp->ai.c.thrustState)
{
case 0:
{
cp->ai.c.brakeLight = 0;
if (cp->ai.c.ctrlNode)
{
int properVel;
int brakeDist;
if (!IS_NODE_VALID(cp, cp->ai.c.ctrlNode))
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
properVel = cp->hd.wheel_speed;
brakeDist = (properVel * FIXEDH(properVel)) / (newAccel * 2);
if (ABS(brakeDist) > *distToNode)
{
properVel -= 120000;
brakeDist = *distToNode - lbody * 3;
if (brakeDist < 0)
{
if (lbody * 3 - *distToNode > 2)
{
properVel /= *distToNode - lbody * 3;
}
}
else if (brakeDist > 2)
{
properVel /= *distToNode - lbody * 3;
}
cp->ai.c.velRatio = properVel;
cp->ai.c.thrustState = 1;
}
}
if (FIXEDH(cp->hd.wheel_speed) > cp->ai.c.maxSpeed)
return newAccel >> 2;
return newAccel;
}
case 1:
{
int properVel;
int distToEnd;
int accelRatio;
if (cp->ai.c.ctrlState == 5 || cp->ai.c.ctrlState == 8)
distToEnd = 100;
else
distToEnd = lbody * 3;
cp->ai.c.brakeLight = 1;
if (cp->ai.c.ctrlNode != NULL && cp->ai.c.ctrlNode->pathType != 127)
{
if (cp->ai.c.ctrlState == 1 && junctionLightsPhase[cp->ai.c.trafficLightPhaseId] == 3)
{
cp->ai.c.thrustState = 0;
cp->ai.c.ctrlNode = NULL;
return newAccel;
}
if (*distToNode < distToEnd + 32) // [A] add some number to make Havana cops not getting stuck while yielding
{
if (cp->ai.c.ctrlState == 6)
{
cp->ai.c.thrustState = 0;
cp->ai.c.ctrlNode = NULL;
return newAccel;
}
accelRatio = (-cp->hd.wheel_speed) / 4;
cp->ai.c.thrustState = 3;
}
else
{
if (cp->ai.c.ctrlState == 6)
{
properVel = (*distToNode - distToEnd) * cp->ai.c.velRatio + 70000;
}
else if (distToEnd < *distToNode)
{
properVel = cp->ai.c.velRatio * ((*distToNode - distToEnd) + 100);
}
else
{
properVel = 0;
}
accelRatio = ((properVel - cp->hd.wheel_speed) * newAccel) / 15;
}
if (IS_NODE_VALID(cp, cp->ai.c.ctrlNode))
{
if (accelRatio <= newAccel)
{
if (accelRatio < newAccel * -2)
return newAccel * -2;
else
return accelRatio;
}
return newAccel;
}
}
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
case 3:
{
break;
}
case 5:
case 6:
{
int distToObstacle;
int checkObstDist;
int carDir;
int dx, dz;
cp->ai.c.brakeLight = 1;
if (cp->ai.c.ctrlState == 4)
checkObstDist = 2048;
else
checkObstDist = 512;
carDir = cp->hd.direction;
distToObstacle = 0x7fffff;
lcp = &car_data[MAX_CARS-1];
while (lcp >= car_data)
{
if (lcp->ai.c.thrustState != 3 &&
lcp->ai.c.ctrlState != 4 && // [A] don't check cars that are yielding
lcp != cp &&
lcp->controlType != CONTROL_TYPE_NONE)
{
dx = lcp->hd.where.t[0] - cp->hd.where.t[0];
dz = lcp->hd.where.t[2] - cp->hd.where.t[2];
tangent = FIXEDH(dx * RSIN(carDir) + dz * RCOS(carDir));
normal = FIXEDH(dx * RCOS(carDir) - dz * RSIN(carDir));
if (tangent > 0)
{
// keep it 6. Does work alright
if (ABS(normal) < wbody * sideMul * 6 && tangent < distToObstacle)
{
distToObstacle = tangent;
}
}
}
lcp--;
}
if (distToObstacle <= checkObstDist)
{
int speed;
speed = (-cp->hd.wheel_speed) / 4; // is that a brake dist?
if (speed <= newAccel)
{
if (speed < newAccel * -2)
return newAccel * -2;
else
return speed;
}
return newAccel;
}
cp->ai.c.ctrlState = 0;
cp->ai.c.thrustState = 0;
cp->ai.c.ctrlNode = 0;
return newAccel;
}
default:
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
}
// switch lights
switch (cp->ai.c.ctrlState)
{
case 1:
if (junctionLightsPhase[cp->ai.c.trafficLightPhaseId] == 3)
cp->ai.c.thrustState = 0;
case 2:
cp->ai.c.brakeLight = 1;
return 0;
case 3:
cp->ai.c.thrustState = 5;
cp->ai.c.brakeLight = 1;
break;
case 4:
cp->ai.c.thrustState = 6;
cp->ai.c.brakeLight = 1;
break;
default:
cp->ai.c.brakeLight = 0;
break;
}
return 0;
}
int brakeLength[MAX_CARS];
int CAR_PAUSE_START = 100;
static CAR_DATA(*horncarflag[2]) = { 0 };
static u_char hornchanflag[2] = { 0 };
// [D] [T]
void SetUpCivCollFlags(void)
{
CAR_COSMETICS* car_cos;
CAR_DATA* cp1;
SVECTOR boxDisp;
CAR_DATA* cp0;
int carLength[2];
int i;
int brake;
int dx;
int extraLength;
int boxOverlap;
CDATA2D cd[2];
ClearMem((char*)brakeLength, sizeof(brakeLength));
cp0 = &car_data[MAX_CARS - 1];
while (cp0 >= car_data)
{
if (cp0->controlType == CONTROL_TYPE_CIV_AI)
{
extraLength = FIXEDH(cp0->hd.wheel_speed);
if (cp0->wheel_angle < 61)
extraLength *= 13;
else
extraLength *= 4;
extraLength = ABS(extraLength);
car_cos = cp0->ap.carCos;
carLength[0] = car_cos->colBox.vz;
cd[0].length[0] = carLength[0] + 93 + extraLength;
cd[0].length[1] = car_cos->colBox.vx;
cd[0].theta = cp0->hd.direction;
gte_SetRotMatrix(&cp0->hd.where.m);
gte_SetTransMatrix(&cp0->hd.where.m);
boxDisp.vx = -car_cos->cog.vx;
boxDisp.vy = -car_cos->cog.vy;
boxDisp.vz = (extraLength - car_cos->cog.vz) + 93;
gte_ldv0(&boxDisp);
gte_rtv0tr();
gte_stlvnl(&cd[0].x);
cp1 = &car_data[MAX_CARS];
while (cp1 >= car_data)
{
if (cp1->controlType != CONTROL_TYPE_NONE && cp1 != cp0)
{
car_cos = cp1->ap.carCos;
if (CAR_INDEX(cp1) == TANNER_COLLIDER_CARID)
{
if (player[0].playerType != 2)
{
cp1--;
continue;
}
cd[1].length[0] = 60;
cd[1].length[1] = 60;
cd[1].x.vx = player[0].pos[0];
cd[1].x.vz = player[0].pos[2];
cd[1].theta = player[0].dir;
}
else
{
cd[1].length[0] = car_cos->colBox.vz;
cd[1].length[1] = car_cos->colBox.vx;
cd[1].x.vx = cp1->hd.oBox.location.vx;
cd[1].x.vy = cp1->hd.oBox.location.vy;
cd[1].x.vz = cp1->hd.oBox.location.vz;
cd[1].theta = cp1->hd.direction;
}
dx = ((cd[0].length[0] + cd[1].length[0]) * 3) / 2;
if (cd[0].x.vx - cd[1].x.vx < 0)
{
if (cd[1].x.vx - cd[0].x.vx >= dx)
{
cp1--;
continue;
}
}
else if (dx <= cd[0].x.vx - cd[1].x.vx)
{
cp1--;
continue;
}
if (cd[0].x.vz - cd[1].x.vz < 0)
{
if (cd[1].x.vz - cd[0].x.vz >= dx)
{
cp1--;
continue;
}
}
else if (dx <= cd[0].x.vz - cd[1].x.vz)
{
cp1--;
continue;
}
// check height difference
if (CAR_INDEX(cp1) == CAMERA_COLLIDER_CARID)
{
if (ABS(player[0].pos[1] - cp0->hd.where.t[1]) >= 500)
{
cp1--;
continue;
}
}
else if (ABS(cp1->hd.where.t[1] - cp0->hd.where.t[1]) >= 500 && ABS(player[0].pos[1] - cp0->hd.where.t[1]) >= 500)
{
cp1--;
continue;
}
// do overlap test between boxes
if (!bcollided2d(cd, &boxOverlap))
{
cp1--;
continue;
}
brake = (cd[0].length[0] - carLength[0]) - boxOverlap;
if (brake < 1)
brake = 1;
if (brakeLength[cp0->id] == 0 || brake < brakeLength[cp0->id])
brakeLength[cp0->id] = brake;
// don't do anything further when it tries to park
if (cp0->ai.c.thrustState == 3)
{
cp1--;
continue;
}
if (CAR_INDEX(cp1) == TANNER_COLLIDER_CARID)
cp0->ai.c.carPauseCnt = CAR_PAUSE_START;
// do horns
// horn to player and chased cars (except Steal the Ambulance)
if (cp0->ai.c.thrustState != 3 &&
(cp1->controlType == CONTROL_TYPE_PLAYER || cp1->controlType == CONTROL_TYPE_CUTSCENE && gCurrentMissionNumber != 26 && ProxyBar.active == 0 ||
CAR_INDEX(cp1) == TANNER_COLLIDER_CARID))
{
int dont;
int rnd;
rnd = Random2(0);
dont = 0;
for (i = 0; i < 2; i++)
{
if (horncarflag[i] == cp0)
{
dont = 1;
break;
}
}
if (dont)
{
cp1--;
continue;
}
for (i = 0; i < 2; i++)
{
if (hornchanflag[i] == 0)
{
int sample;
hornchanflag[i] = GetFreeChannel();
SpuSetVoiceAR(hornchanflag[i], 27);
if (cp0->ap.model == 4)
sample = ResidentModelsBodge();
else if (cp0->ap.model < 3)
sample = cp0->ap.model;
else
sample = cp0->ap.model - 1;
// [A] use tracking sound
Start3DTrackingSound(hornchanflag[i], SOUND_BANK_CARS, sample * 3 + 2,
(VECTOR*)cp0->hd.where.t,
(LONGVECTOR3*)cp0->st.n.linearVelocity);
SetChannelVolume(hornchanflag[i], -2000, 0);
horncarflag[i] = cp0;
channels[hornchanflag[i]].time += rnd - (rnd / 30) * 30;
break;
}
}
}
}
cp1--;
}
}
cp0--;
}
// clear on timeout
for (i = 0; i < 2; i++)
{
if (hornchanflag[i] != 0 && channels[hornchanflag[i]].time == 0)
{
horncarflag[i] = NULL;
hornchanflag[i] = 0;
SpuSetVoiceAR(0, 35);
}
}
}
// [D] [T]
int CivAccel(CAR_DATA * cp)
{
CIV_ROUTE_ENTRY* node;
int distToNode;
int ret;
node = cp->ai.c.ctrlNode;
carnum = CAR_INDEX(cp);
if (node == NULL || cp->ai.c.thrustState == 3)
{
distToNode = -1;
}
else
{
int dx, dz;
dx = cp->hd.where.t[0] - node->x;
dz = cp->hd.where.t[2] - node->z;
distToNode = SquareRoot0(dx * dx + dz * dz);
}
if (cp->ai.c.thrustState != 3 && node && node->pathType == 127)
{
distToNode = -distToNode;
}
ret = CivAccelTrafficRules(cp, &distToNode);
if (cp->ai.c.thrustState != 3)
{
int lbd2;
int lbody;
int tmpret;
int brakeDist;
collDat = brakeLength[cp->id];
lbody = cp->ap.carCos->colBox.vz;
// brake in front of obstacles
if (collDat != 0)
{
int sf, c1, c2;
sf = lbody / 2;
if (collDat < lbody * 2)
{
if (/*lbody * 2 < collDat &&*/ sf <= collDat) // [A] ancient bug fix
{
sf = sf - lbody * 2;
tmpret = ((collDat + lbody * -2) * -100) / sf + 100;
}
else if (sf < collDat && (lbody / 4) <= collDat)
{
c2 = (lbody / 4) - sf;
tmpret = ((collDat - sf) * -300) / c2 + 400;
}
else
{
tmpret = 100;
}
}
else
{
tmpret = 0;
}
tmpret = (newAccel * tmpret) / 100;
brakeDist = (-cp->hd.wheel_speed) / 4;
if (tmpret >= brakeDist)
{
if (brakeDist < tmpret * -2)
tmpret *= -2;
else
tmpret = brakeDist;
}
if (ret > tmpret)
ret = tmpret;
}
// stop car if interrupted by pedestrian Tanner
if (cp->ai.c.carPauseCnt > 0 && collDat == 0)
{
int spd;
spd = cp->hd.wheel_speed;
if (ABS(FIXEDH(spd)) < 3)
{
ret = (-spd) / 4;
cp->ai.c.carPauseCnt -= 1;
}
}
}
return ret;
}
// [D] [T]
int CivFindStation(CAR_DATA * cp)
{
int loop;
int square;
CIV_ROUTE_ENTRY* currentNode;
CIV_ROUTE_ENTRY* retNode;
CIV_ROUTE_ENTRY* rep;
int dz;
int dx;
int sz;
int sx;
int cx;
int cz;
int stepsize;
int i;
cx = cp->hd.where.t[0];
cz = cp->hd.where.t[2];
loop = cp->ai.c.currentNode - 1;
currentNode = cp->ai.c.targetRoute;
rep = currentNode;
while (loop >= 0)
{
currentNode = rep = GET_NEXT_NODE(cp, rep);
loop--;
}
do {
retNode = GET_NEXT_NODE(cp, currentNode);
if (retNode == NULL)
break;
sx = currentNode->x;
sz = currentNode->z;
dx = retNode->x - sx;
dz = retNode->z - sz;
square = dx * dx + dz * dz;
if (square < 0)
break;
square = SquareRoot0(square);
if (square > 0)
{
stepsize = ((cx - sx) * dx + (cz - sz) * dz) / square;
if (stepsize < square)
{
cp->ai.c.pnode = currentNode;
return stepsize;
}
}
currentNode = retNode;
cp->ai.c.currentNode = GET_NODE_ID(cp, currentNode);
i = 1;
do
{
if (GET_NEXT_NODE(cp, retNode + i)->pathType == 127)
CreateNewNode(cp);
i++;
} while (i < 4);
} while (true);
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
// [D] [T]
int CivFindPointOnPath(CAR_DATA * cp, int station, VECTOR * ppoint)
{
int stepSize;
CIV_ROUTE_ENTRY* start;
CIV_ROUTE_ENTRY* currentNode;
CIV_ROUTE_ENTRY* retNode;
int dx;
int dz;
int sx;
int sz;
start = cp->ai.c.pnode;
if (start)
{
currentNode = start;
do {
retNode = currentNode + 1;
if (cp->ai.c.targetRoute + 13 <= retNode) // [A] there was reflections bug?
retNode = currentNode - 12;
if (retNode == NULL || retNode && retNode->pathType == 127)
{
ppoint->vx = currentNode->x;
ppoint->vz = currentNode->z;
return 1;
}
sx = currentNode->x;
sz = currentNode->z;
dx = retNode->x - sx;
dz = retNode->z - sz;
stepSize = SquareRoot0(dx * dx + dz * dz);
// advance
if (station < stepSize)
{
if (station < 0)
station = 0;
if (stepSize < 4096)
{
ppoint->vx = sx + (dx * station) / stepSize;
ppoint->vz = sz + (dz * station) / stepSize;
}
else
{
stepSize /= 16;
ppoint->vx = sx + FIXEDH(((dx * 256) / stepSize) * station);
ppoint->vz = sz + FIXEDH(((dz * 256) / stepSize) * station);
}
return 1;
}
station -= stepSize;
currentNode = retNode;
} while (retNode != start);
}
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
const int checkFrames = 20;
const int maxSteer = 512;
// [D] [T] [A] - removed copying of car. Still works okay
int CivSteerAngle(CAR_DATA* cp)
{
CIV_ROUTE_ENTRY* startNode;
short lbody;
CIV_ROUTE_ENTRY* retNode;
CIV_ROUTE_ENTRY* crLoc;
VECTOR locPath;
VECTOR pathPoint;
int i;
pathPoint.vx = 0;
pathPoint.vy = 0;
pathPoint.vz = 0;
startNode = cp->ai.c.pnode;
lbody = cp->ap.carCos->colBox.vz;
// we need 3 nodes, try making them in each different frame
i = 0;
do
{
if (GET_NEXT_NODE(cp, startNode + i)->pathType == 127)
{
if (CreateNewNode(cp) == 0)
{
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
}
i++;
} while (i < 4);
if (startNode->pathType != 127)
{
retNode = GET_NEXT_NODE(cp, cp->ai.c.pnode);
if (retNode->pathType != 127)
{
int station;
station = CivFindStation(cp);
crLoc = cp->ai.c.pnode;
if (crLoc->pathType != 127)
{
retNode = GET_NEXT_NODE(cp, cp->ai.c.pnode);
if (retNode->pathType != 127)
{
CIV_ROUTE_ENTRY* cr;
int ret;
int dx, dz;
CivFindPointOnPath(cp, station + FIXEDH(cp->hd.wheel_speed) * checkFrames + lbody, &pathPoint);
dx = pathPoint.vx - cp->hd.where.t[0];
dz = pathPoint.vz - cp->hd.where.t[2];
ret = DIFF_ANGLES(cp->hd.direction, ratan2(dx, dz)); // ((ratan2(dx, dz) - cp->hd.direction) + 2048U & 0xfff) - 2048;
if (ret < -maxSteer)
ret = -maxSteer;
else if (ret > maxSteer)
ret = maxSteer;
// invalidate old nodes
cr = startNode;
while (cr != cp->ai.c.pnode)
{
cr->pathType = 127;
// invalidate current turn node
if (cp->ai.c.turnNode == GET_NODE_ID(cp, cr))
cp->ai.c.turnNode = -1;
cr = GET_NEXT_NODE(cp, cr);
}
return ret;
}
}
}
}
CIV_STATE_SET_CONFUSED(cp);
return 0;
}
// [D] [T]
void CreateRoadblock(void)
{
int laneNo;
CAR_COSMETICS* car_cos;
CAR_DATA* cp;
int distAlongSegment;
DRIVER2_CURVE* crv;
DRIVER2_STRAIGHT* str;
VECTOR startPos;
VECTOR endPos;
VECTOR currentPos;
int numLanes;
int externalCopModel;
int noMoreCars;
int angle;
int dir;
int lbody;
int delta;
int dir2NextRow;
int newSlot;
CAR_DATA* newCar;
crv = NULL;
str = NULL;
noMoreCars = 0;
distAlongSegment = -5;
lbody = car_cosmetics[3].colBox.vz;
externalCopModel = MissionHeader->residentModels[3];
// [A] use player instead of car
dir = player[0].dir;
baseLoc.vx = player[0].pos[0];
baseLoc.vy = player[0].pos[1];
baseLoc.vz = player[0].pos[2];
currentPos.vy = baseLoc.vy;
angle = 0;
// scan for roads
do {
int dx, dz;
if (requestCopCar != 0)
{
dx = RSIN(dir + angle) * PINGIN_DIST_WANTED_MULT;
dz = RCOS(dir + angle) * PINGIN_DIST_WANTED_MULT;
}
else
{
dx = RSIN(dir + angle) * PINGIN_DIST_MULT;
dz = RCOS(dir + angle) * PINGIN_DIST_MULT;
}
roadblockLoc.vx = baseLoc.vx + FIXEDH(dx) * 2048;
roadblockLoc.vz = baseLoc.vz + FIXEDH(dz) * 2048;
roadSeg = RoadInCell(&roadblockLoc);
if (IS_STRAIGHT_SURFACE(roadSeg) || IS_CURVED_SURFACE(roadSeg))
break;
if (requestCopCar != 0)
{
dx = RSIN(dir - angle) * PINGIN_DIST_WANTED_MULT;
dz = RCOS(dir - angle) * PINGIN_DIST_WANTED_MULT;
}
else
{
dx = RSIN(dir - angle) * PINGIN_DIST_MULT;
dz = RCOS(dir - angle) * PINGIN_DIST_MULT;
}
roadblockLoc.vx = baseLoc.vx + FIXEDH(dx) * 2048;
roadblockLoc.vz = baseLoc.vz + FIXEDH(dz) * 2048;
roadSeg = RoadInCell(&roadblockLoc);
if (IS_STRAIGHT_SURFACE(roadSeg) || IS_CURVED_SURFACE(roadSeg))
break;
// FIXME: there must be some computed constant
if (requestCopCar != 0)
angle += 73;
else
angle += 93;
} while (angle < 2048);
// no suitable road found
if (!IS_STRAIGHT_SURFACE(roadSeg) && !IS_CURVED_SURFACE(roadSeg))
return;
if (IS_STRAIGHT_SURFACE(roadSeg))
{
int segmentLen;
str = GET_STRAIGHT(roadSeg);
dx = roadblockLoc.vx - str->Midx;
dz = roadblockLoc.vz - str->Midz;
segmentLen = str->length;
distAlongSegment = (str->length / 2) + FIXEDH(RCOS(str->angle - ratan2(dx, dz)) * SquareRoot0(dx * dx + dz * dz));
if (segmentLen < lbody * 6)
return;
if (distAlongSegment < lbody * 3)
distAlongSegment = lbody * 3;
if ((segmentLen - distAlongSegment) < lbody * 3)
distAlongSegment = segmentLen - lbody * 3;
numLanes = ROAD_WIDTH_IN_LANES(str);
}
else if(IS_CURVED_SURFACE(roadSeg))
{
int theta;
int segmentLen;
int minDistAlong;
crv = GET_CURVE(roadSeg);
// check if not outside the curve
if (((crv->end - crv->start & 0xfffU) * crv->inside * 11) / 7 < lbody * 6)
return;
currentAngle = ratan2(roadblockLoc.vx - crv->Midx, roadblockLoc.vz - crv->Midz);
if(crv->inside < 10)
minDistAlong = 128;
else if (crv->inside < 20)
minDistAlong = 64;
else
minDistAlong = 32;
theta = (currentAngle & 0xfffU) - crv->start;
if(crv->inside < 10)
distAlongSegment = theta & 0xf80;
else if (crv->inside < 20)
distAlongSegment = theta & 0xfc0;
else
distAlongSegment = theta & 0xfe0;
segmentLen = (crv->end - crv->start) - minDistAlong & 0xfff;
if (distAlongSegment >= segmentLen)
distAlongSegment = segmentLen;
numLanes = ROAD_WIDTH_IN_LANES(crv);
}
GetNodePos(str, NULL, crv, distAlongSegment, NULL, (int*)&startPos.vx, (int*)&startPos.vz, 0);
GetNodePos(str, NULL, crv, distAlongSegment, NULL, (int*)&endPos, (int*)&endPos.vz, numLanes - 1);
dir2NextRow = ratan2(endPos.vx - startPos.vx, endPos.vz - startPos.vz);
delta = 256;
while (delta < numLanes * 512)
{
laneNo = (delta >> 9);
currentPos.vx = startPos.vx + FIXEDH(delta * RSIN(dir2NextRow));
currentPos.vz = startPos.vz + FIXEDH(delta * RCOS(dir2NextRow));
if((str && ROAD_IS_AI_LANE(str, laneNo) || crv && ROAD_IS_AI_LANE(crv, laneNo)) &&
CellEmpty(&currentPos, lbody))
{
newSlot = CreateStationaryCivCar(dir2NextRow + (Random2(0) * 0x10001 >> (laneNo) & 0x3ffU) - 512, 0, 0, (LONGVECTOR4 *)&currentPos, externalCopModel, 0, 2);
if (newSlot == -1)
{
noMoreCars = 1;
break;
}
newCar = &car_data[newSlot];
car_cos = newCar->ap.carCos;
if (QuickBuildingCollisionCheck((VECTOR*)newCar->hd.where.t, newCar->hd.direction, car_cos->colBox.vz, car_cos->colBox.vx, 0x14) != 0)
{
PingOutCar(newCar);
}
cp = &car_data[MAX_CARS - 1];
do {
if (cp->controlType != CONTROL_TYPE_NONE && !IS_ROADBLOCK_CAR(cp))
{
dx = cp->hd.where.t[0] - currentPos.vx;
dz = cp->hd.where.t[2] - currentPos.vz;
if (dx * dx + dz * dz < 360000)
{
PingOutCar(cp);
}
}
cp--;
} while (cp >= car_data);
}
delta += 1024;
}
// try making additional cars
if (!noMoreCars && gCopDifficultyLevel != 0)
{
int faceDir;
int deltaAngle;
int theta;
int numSpareCars;
int numSpots;
int count;
int numCarsToAdd;
int px, pz;
if (ratan2(baseLoc.vx - startPos.vx, baseLoc.vz - startPos.vz) - dir2NextRow & 0xfff < 2048)
deltaAngle = -1024;
else
deltaAngle = 1024;
if (gCopDifficultyLevel == 1)
faceDir = dir2NextRow - deltaAngle;
else
faceDir = dir2NextRow;
theta = dir2NextRow + deltaAngle;
px = startPos.vx + FIXEDH(RSIN(theta) * 1500);
pz = startPos.vz + FIXEDH(RCOS(theta) * 1500);
numSpareCars = (maxCivCars - numCivCars) + 2;
numSpots = numLanes / 2 - 1;
numCarsToAdd = numSpots;
if (numCarsToAdd > numSpareCars)
numCarsToAdd = numSpareCars;
count = 0;
while (count < numCarsToAdd)
{
delta = ((numSpots * (count * 2 + 1)) / (numCarsToAdd * 2)) * 1024 + 768;
currentPos.vx = px + FIXEDH(delta * RSIN(dir2NextRow));
currentPos.vz = pz + FIXEDH(delta * RCOS(dir2NextRow));
test42 = delta;
newSlot = CreateStationaryCivCar(faceDir + (Random2(0) * 0x10001 >> (delta >> 9 & 0x1fU) & 0x3ffU) - 512, 0, 0, (LONGVECTOR4 *)&currentPos, externalCopModel, 0, 2);
if (newSlot == -1)
break;
newCar = &car_data[newSlot];
car_cos = newCar->ap.carCos;
if (QuickBuildingCollisionCheck((VECTOR*)newCar->hd.where.t, newCar->hd.direction, car_cos->colBox.vz, car_cos->colBox.vx, 0x14) != 0)
{
PingOutCar(newCar);
}
cp = &car_data[MAX_CARS - 1];
do {
if (cp->controlType != CONTROL_TYPE_NONE && !IS_ROADBLOCK_CAR(cp))
{
dx = cp->hd.where.t[0] - currentPos.vx;
dz = cp->hd.where.t[2] - currentPos.vz;
if (dx * dx + dz * dz < 360000)
{
PingOutCar(cp);
}
}
cp--;
} while (cp >= car_data);
count++;
}
}
requestRoadblock = 0;
roadblockDelay = roadblockDelayDiff[gCopDifficultyLevel] + (Random2(0) & 0xff);
roadblockCount = roadblockDelay;
PlaceRoadBlockCops();
}
// [D] [T]
int CivControl(CAR_DATA* cp)
{
int thrust;
int steer;
CheckPingOut(cp);
if (cp->controlType == CONTROL_TYPE_CIV_AI)
{
if (cp->ai.c.changeLaneIndicateCount != 0)
cp->ai.c.changeLaneIndicateCount--;
if (cp->ai.c.ctrlState == 5 && cp->ai.c.thrustState == 3 &&
cp->totalDamage < 4 && (cp->ap.model != 3 && (Random2(0) + (25 - cp->id) * 16 & 0xff8) == 0xf00))
{
AttemptUnPark(cp);
}
steer = cp->wheel_angle;
if (cp->ai.c.thrustState != 3)
steer = CivSteerAngle(cp);
thrust = CivAccel(cp) - MAX(ABS(steer), 4) * 3; // [A] reduce acceleration when steering is applied
if (thrust < 0 && cp->hd.wheel_speed < 5)
thrust = 0;
cp->wheel_angle = steer;
cp->thrust = thrust;
#if 0
{
//maxCivCars = 2;
//maxCopCars = 0;
extern void Debug_AddLine(VECTOR & pointA, VECTOR & pointB, CVECTOR & color);
extern void Debug_AddLineOfs(VECTOR & pointA, VECTOR & pointB, VECTOR & ofs, CVECTOR & color);
CVECTOR ggcv = { 0, 250, 0 };
CVECTOR bbcv = { 0, 0, 250 };
CVECTOR rrcv = { 250, 0, 0 };
CVECTOR yycv = { 250, 250, 0 };
VECTOR _zero = { 0 };
VECTOR _up = { 0, 120, 0 };
VECTOR _up1 = { 0, 20, 0 };
VECTOR _up2 = { 0, 40, 0 };
VECTOR _up3 = { 0, 60, 0 };
VECTOR _up4 = { 0, 80, 0 };
// show current road
/*
if (cp->ai.c.currentRoad != -1)
{
DRIVER2_STRAIGHT* straight = GET_STRAIGHT(cp->ai.c.currentRoad);
int angle = straight->angle;
int distFromCentreA = -straight->length / 2;
int distFromCentreB = straight->length / 2;
int laneNo = cp->ai.c.currentLane;
test42 = 0 * 0x80;
sideShift = ((straight->NumLanes & 0xf) * 0x200 - (laneNo * 0x200 + 0x100)) + test42;
int straightX1 = straight->Midx + FIXED(distFromCentreA * RSIN(angle)) + FIXED(sideShift * RCOS(angle));
int straightZ1 = (straight->Midz + FIXED(distFromCentreA * RCOS(angle))) - FIXED(sideShift * RSIN(angle));
int straightX2 = straight->Midx + FIXED(distFromCentreB * RSIN(angle)) + FIXED(sideShift * RCOS(angle));
int straightZ2 = (straight->Midz + FIXED(distFromCentreB * RCOS(angle))) - FIXED(sideShift * RSIN(angle));
VECTOR roadA = { straightX1, cp->hd.where.t[1], straightZ1 };
VECTOR roadB = { straightX2, cp->hd.where.t[1], straightZ2 };
Debug_AddLine(roadA, roadB, rrcv);
}*/
CIV_ROUTE_ENTRY* pn = cp->ai.c.pnode;
if (pn)
{
for (int i = 0; i < 13; i++)
{
if (pn->pathType != 127)
{
int sx, cx;
sx = rsin(pn->dir);
cx = rcos(pn->dir);
VECTOR ofs = { sx / 16, 0, cx / 16 };
VECTOR b1p = { pn->x, cp->hd.where.t[1], pn->z };
if (pn->pathType == 2)
{
ofs.vy = 1000;
}
//Debug_AddLineOfs(_zero, _up, b1p, rrcv);
Debug_AddLineOfs(_zero, ofs, b1p, rrcv);
}
pn = GET_NEXT_NODE(cp, pn);
}
}
// current node - YELLOW
if (cp->ai.c.currentNode > -1 && cp->ai.c.currentNode < 13)
{
CIV_ROUTE_ENTRY& currentNode = cp->ai.c.targetRoute[cp->ai.c.currentNode];
VECTOR b1p = { currentNode.x, cp->hd.where.t[1], currentNode.z };
Debug_AddLineOfs(_zero, _up1, b1p, yycv);
}
// turn node - RED
if (cp->ai.c.turnNode > -1 && cp->ai.c.turnNode < 13)
{
CIV_ROUTE_ENTRY& turnNode = cp->ai.c.targetRoute[cp->ai.c.turnNode];
VECTOR b1p = { turnNode.x, cp->hd.where.t[1], turnNode.z };
Debug_AddLineOfs(_up1, _up2, b1p, rrcv);
}
// control node - GREEN
if (cp->ai.c.ctrlNode)
{
VECTOR b1p = { cp->ai.c.ctrlNode->x, cp->hd.where.t[1], cp->ai.c.ctrlNode->z };
Debug_AddLineOfs(_up2, _up3, b1p, ggcv);
}
// previous node - BLUE
if (cp->ai.c.pnode)
{
VECTOR b1p = { cp->ai.c.pnode->x, cp->hd.where.t[1], cp->ai.c.pnode->z };
Debug_AddLineOfs(_up3, _up4, b1p, bbcv);
}
}
#endif
}
// watch for the player
if (cp->controlFlags & CONTROL_FLAG_COP)
PassiveCopTasks(cp);
return 1;
}