mirror of
https://github.com/OpenDriver2/REDRIVER2.git
synced 2024-11-22 10:22:48 +01:00
3802 lines
80 KiB
C
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(¤tRoadInfo, currentRoadId))
|
|
{
|
|
int widthInLanes;
|
|
int laneNo;
|
|
int count;
|
|
|
|
widthInLanes = ROAD_WIDTH_IN_LANES(¤tRoadInfo);
|
|
|
|
currentLaneDir = ROAD_LANE_DIR(¤tRoadInfo, 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(¤tRoadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(¤tRoadInfo, count))
|
|
{
|
|
test42 = ROAD_LANE_DIR(¤tRoadInfo, 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(¤tRoadInfo);
|
|
laneNo = widthInLanes;
|
|
|
|
while (count < widthInLanes)
|
|
{
|
|
if (ROAD_IS_AI_LANE(¤tRoadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(¤tRoadInfo, count))
|
|
{
|
|
test555 = ROAD_LANE_DIR(¤tRoadInfo, 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(¤tRoadInfo) > 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(¤tPos, lbody))
|
|
{
|
|
newSlot = CreateStationaryCivCar(dir2NextRow + (Random2(0) * 0x10001 >> (laneNo) & 0x3ffU) - 512, 0, 0, (LONGVECTOR4 *)¤tPos, 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 *)¤tPos, 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;
|
|
} |