822 lines
29 KiB
C++
822 lines
29 KiB
C++
/***************************************************************************
|
|
|
|
file : racecars.cpp
|
|
created : Sat Nov 23 09:05:23 CET 2002
|
|
copyright : (C) 2002 by Eric Espie
|
|
email : eric.espie@torcs.org
|
|
version : $Id$
|
|
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
/** @file
|
|
|
|
@author <a href=mailto:eric.espie@torcs.org>Eric Espie</a>
|
|
@version $Id$
|
|
*/
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <portability.h>
|
|
#include <raceman.h>
|
|
#include <network.h>
|
|
#include <robot.h>
|
|
#include <robottools.h>
|
|
#include <teammanager.h>
|
|
#include <replay.h>
|
|
|
|
#include "standardgame.h"
|
|
|
|
#include "racesituation.h"
|
|
#include "raceupdate.h"
|
|
#include "raceresults.h"
|
|
#include "racecars.h"
|
|
|
|
int ghostcarActive;
|
|
double ghostcarTimeOffset;
|
|
tReplayElt curGhostcarData, nextGhostcarData;
|
|
#ifdef THIRD_PARTY_SQLITE3
|
|
sqlite3_stmt *ghostcarBlob;
|
|
#endif
|
|
|
|
/* Compute Pit stop time */
|
|
void
|
|
ReCarsUpdateCarPitTime(tCarElt *car)
|
|
{
|
|
tSituation *s = ReInfo->s;
|
|
tReCarInfo *info = &(ReInfo->_reCarInfo[car->index]);
|
|
tCarPenalty *penalty;
|
|
int i;
|
|
|
|
// GfLogDebug("ReCarsUpdateCarPitTime(%s) : typ=%d, fuel=%f, rep=%d\n",
|
|
// car->_name, car->_pitStopType, car->_pitFuel, car->_pitRepair);
|
|
|
|
switch (car->_pitStopType) {
|
|
case RM_PIT_REPAIR:
|
|
info->totalPitTime = 2.0f + fabs((double)(car->_pitFuel)) / 8.0f + (tdble)(fabs((double)(car->_pitRepair))) * 0.007f;
|
|
car->_scheduledEventTime = s->currentTime + info->totalPitTime;
|
|
RePhysicsEngine().reconfigureCar(car);
|
|
|
|
for (i=0; i<4; i++) {
|
|
car->_tyreCondition(i) = 1.01f;
|
|
car->_tyreT_in(i) = 50.0f;
|
|
car->_tyreT_mid(i) = 50.0f;
|
|
car->_tyreT_out(i) = 50.0f;
|
|
}
|
|
GfLogInfo("%s in repair pit stop for %.1f s (refueling by %.1f l, repairing by %d).\n",
|
|
car->_name, info->totalPitTime, car->_pitFuel, car->_pitRepair);
|
|
break;
|
|
case RM_PIT_STOPANDGO:
|
|
penalty = GF_TAILQ_FIRST(&(car->_penaltyList));
|
|
if (penalty && penalty->penalty == RM_PENALTY_10SEC_STOPANDGO)
|
|
info->totalPitTime = 10.0;
|
|
else
|
|
info->totalPitTime = 0.0;
|
|
car->_scheduledEventTime = s->currentTime + info->totalPitTime;
|
|
|
|
// Prevent car->_state & RM_CAR_STATE_PIT from being true for a too short delay,
|
|
// in order for the penalty management to detect it.
|
|
if (car->_scheduledEventTime < s->currentTime + RCM_MAX_DT_SIMU)
|
|
car->_scheduledEventTime += RCM_MAX_DT_SIMU;
|
|
|
|
GfLogInfo("%s in Stop-and-Go pit stop for %.1f s.\n", car->_name, info->totalPitTime);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* Prepare to open the pit menu when back in the main updater (thread) */
|
|
static void
|
|
reCarsSchedulePitMenu(tCarElt *car)
|
|
{
|
|
// Do nothing if one car is already scheduled for the pit menu
|
|
// (this one will have to wait for the current one exiting from the menu)
|
|
if (ReInfo->_rePitRequester)
|
|
{
|
|
GfLogInfo("%s would like to pit, but the pit menu is already in use.\n", car->_name);
|
|
return;
|
|
}
|
|
|
|
// Otherwise, "post" a pit menu request for this car.
|
|
ReInfo->_rePitRequester = car;
|
|
}
|
|
|
|
|
|
static void
|
|
reCarsAddPenalty(tCarElt *car, int penalty)
|
|
{
|
|
char msg[64];
|
|
tCarPenalty *newPenalty;
|
|
|
|
if (penalty == RM_PENALTY_DRIVETHROUGH)
|
|
snprintf(msg, sizeof(msg), "%s Drive-Through penalty", car->_name);
|
|
else if (penalty == RM_PENALTY_STOPANDGO)
|
|
snprintf(msg, sizeof(msg), "%s Stop-and-Go penalty", car->_name);
|
|
else if (penalty == RM_PENALTY_10SEC_STOPANDGO)
|
|
snprintf(msg, sizeof(msg), "%s 10s Stop-and-Go penalty", car->_name);
|
|
else if (penalty == RM_PENALTY_DISQUALIFIED)
|
|
snprintf(msg, sizeof(msg), "%s disqualified", car->_name);
|
|
msg[sizeof(msg)-1] = 0; // Some snprintf implementations fail to do so.
|
|
|
|
ReSituation::self().setRaceMessage(msg, 5);
|
|
|
|
/* If disqualified, remove the car from the track */
|
|
if (penalty == RM_PENALTY_DISQUALIFIED)
|
|
{
|
|
car->_state |= RM_CAR_STATE_ELIMINATED;
|
|
return;
|
|
}
|
|
|
|
// Add the penalty in the list.
|
|
newPenalty = (tCarPenalty*)calloc(1, sizeof(tCarPenalty));
|
|
newPenalty->penalty = penalty;
|
|
newPenalty->lapToClear = car->_laps + 5;
|
|
GF_TAILQ_INSERT_TAIL(&(car->_penaltyList), newPenalty, link);
|
|
//GfLogDebug("reCarsAddPenalty(car #%d) : Added penalty %p\n", car->index, newPenalty);
|
|
}
|
|
|
|
static void
|
|
reCarsRemovePenalty(tCarElt *car, tCarPenalty *penalty)
|
|
{
|
|
GF_TAILQ_REMOVE(&(car->_penaltyList), penalty, link);
|
|
//GfLogDebug("reCarsRemovePenalty(car #%d) : Removed penalty %p\n",
|
|
// car->index, penalty);
|
|
FREEZ(penalty);
|
|
}
|
|
|
|
/* Compute the race rules and penalties */
|
|
static void
|
|
reCarsApplyRaceRules(tCarElt *car)
|
|
{
|
|
char msg[64];
|
|
tCarPenalty *penalty;
|
|
tTrack *track = ReInfo->track;
|
|
tRmCarRules *rules = &(ReInfo->rules[car->index]);
|
|
tTrackSeg *seg = RtTrackGetSeg(&(car->_trkPos));
|
|
tReCarInfo *info = &(ReInfo->_reCarInfo[car->index]);
|
|
tTrackSeg *prevSeg = RtTrackGetSeg(&(info->prevTrkPos));
|
|
static const float ctrlMsgColor[] = {0.0, 0.0, 1.0, 1.0};
|
|
|
|
// DNF cars which need too much time for the current lap, this is mainly to avoid
|
|
// that a "hanging" driver can stop the quali from finishing.
|
|
// Allowed time is longest pitstop possible + time for tracklength with speed???
|
|
// (currently fixed 10 [m/s]).
|
|
// For simplicity. Human driver is an exception to this rule, to allow explorers
|
|
// to enjoy the landscape.
|
|
// Also - don't remove cars that are currently being repaired in pits
|
|
// TODO: Make it configurable.
|
|
|
|
if ((car->_curLapTime > 84.5 + ReInfo->track->length/10.0) &&
|
|
!(car->_state & RM_CAR_STATE_PIT) &&
|
|
(car->_driverType != RM_DRV_HUMAN))
|
|
{
|
|
if (!(car->_state & RM_CAR_STATE_ELIMINATED))
|
|
GfLogInfo("%s eliminated (too long to finish the lap).\n", car->_name);
|
|
car->_state |= RM_CAR_STATE_ELIMINATED;
|
|
return;
|
|
}
|
|
|
|
// Stop here (no more rules) if not in "Pro" skill level.
|
|
if (car->_skillLevel < 3)
|
|
return;
|
|
|
|
// Stop here (no more rules) if "penalties" feature not enables for this race.
|
|
if (! (ReInfo->s->_features & RM_FEATURE_PENALTIES) )
|
|
return;
|
|
|
|
// Otherwise, update control board and do the referee job.
|
|
// 1) Update control board message about the current penalty if any.
|
|
// TODO: Optimization : Add penalty->timeStamp and car->ctrl.timeStamp fields
|
|
// to avoid doing this again and again as long as the penalty is not cleared
|
|
// whereas it's only needed when the penalty reaches the _penaltyList head.
|
|
penalty = GF_TAILQ_FIRST(&(car->_penaltyList));
|
|
if (penalty) {
|
|
switch (penalty->penalty) {
|
|
case RM_PENALTY_DRIVETHROUGH:
|
|
snprintf(car->ctrl.msg[3], RM_CMD_MAX_MSG_SIZE, "Drive-Through Penalty");
|
|
break;
|
|
case RM_PENALTY_STOPANDGO:
|
|
snprintf(car->ctrl.msg[3], RM_CMD_MAX_MSG_SIZE, "Stop-and-Go Penalty");
|
|
break;
|
|
case RM_PENALTY_10SEC_STOPANDGO:
|
|
snprintf(car->ctrl.msg[3], RM_CMD_MAX_MSG_SIZE, "10s Stop-and-Go Penalty");
|
|
break;
|
|
default:
|
|
*(car->ctrl.msg[3]) = 0;
|
|
break;
|
|
}
|
|
car->ctrl.msg[3][RM_CMD_MAX_MSG_SIZE - 1] = 0; // Some snprintf implementations fail to do so.
|
|
memcpy(car->ctrl.msgColor, ctrlMsgColor, sizeof(car->ctrl.msgColor));
|
|
} else {
|
|
// No penalty => no message.
|
|
*(car->ctrl.msg[3]) = 0;
|
|
}
|
|
|
|
// 2) Check if not too late for the 1st penalty if any.
|
|
if (penalty) {
|
|
if (car->_laps > penalty->lapToClear) {
|
|
// The penalty was not "executed" : too late to clear => disqualified (out of race)
|
|
reCarsAddPenalty(car, RM_PENALTY_DISQUALIFIED);
|
|
GfLogInfo("%s disqualified (penalty not executed after 5 laps).\n", car->_name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 3) Check if we can hopefuly clear the penalty because just entered the pit lane.
|
|
// (means that we enter the clearing process, but that it may fail ; nothing sure)
|
|
if (prevSeg->raceInfo & TR_PITSTART) {
|
|
|
|
//if (seg->raceInfo & TR_PIT)
|
|
// GfLogDebug("%s crossed pit lane entry.\n", car->_name);
|
|
if (penalty) {
|
|
// just entered the pit lane
|
|
if (seg->raceInfo & TR_PIT) {
|
|
switch (penalty->penalty) {
|
|
case RM_PENALTY_DRIVETHROUGH:
|
|
snprintf(msg, sizeof(msg), "%s Drive-Through penalty clearing", car->_name);
|
|
msg[sizeof(msg)-1] = 0; // Some snprintf implementations fail to do so.
|
|
ReSituation::self().setRaceMessage(msg, 5);
|
|
rules->ruleState |= RM_PNST_DRIVETHROUGH;
|
|
GfLogInfo("%s might get its Drive-Through penalty cleared.\n", car->_name);
|
|
break;
|
|
case RM_PENALTY_STOPANDGO:
|
|
case RM_PENALTY_10SEC_STOPANDGO:
|
|
snprintf(msg, sizeof(msg), "%s Stop-and-Go penalty clearing", car->_name);
|
|
msg[sizeof(msg)-1] = 0; // Some snprintf implementations fail to do so.
|
|
ReSituation::self().setRaceMessage(msg, 5);
|
|
rules->ruleState |= RM_PNST_STOPANDGO;
|
|
GfLogInfo("%s might get his Stop-and-Go Penalty cleared.\n", car->_name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4) If in pit lane for more than 1 segment :
|
|
} else if (prevSeg->raceInfo & TR_PIT) {
|
|
|
|
if (seg->raceInfo & TR_PIT) {
|
|
// 4a) Check if we can go on with clearing the penalty because stopped in pit.
|
|
if (car->_state & RM_CAR_STATE_PIT) {
|
|
//GfLogDebug("%s is pitting.\n", car->_name);
|
|
if (rules->ruleState & RM_PNST_STOPANDGO && car->_pitStopType == RM_PIT_STOPANDGO) {
|
|
GfLogInfo("%s Stop-and-Go accepted.\n", car->_name);
|
|
rules->ruleState |= RM_PNST_STOPANDGO_OK; // Stop-and-Go really done.
|
|
}
|
|
}
|
|
} else if (seg->raceInfo & TR_PITEND) {
|
|
//GfLogDebug("%s crossing pit lane exit.\n", car->_name);
|
|
// 4b) Check if the penalty can really and finally be removed because exiting pit lane
|
|
// and everything went well in the clearing process til then.
|
|
if (rules->ruleState & (RM_PNST_DRIVETHROUGH | RM_PNST_STOPANDGO_OK)) {
|
|
snprintf(msg, sizeof(msg), "%s penalty cleared", car->_name);
|
|
msg[sizeof(msg)-1] = 0; // Some snprintf implementations fail to do so.
|
|
ReSituation::self().setRaceMessage(msg, 5);
|
|
penalty = GF_TAILQ_FIRST(&(car->_penaltyList));
|
|
reCarsRemovePenalty(car, penalty);
|
|
GfLogInfo("%s %s penalty cleared.\n", car->_name,
|
|
(rules->ruleState & RM_PNST_DRIVETHROUGH) ? "Drive-Through" : "Stop-and-Go");
|
|
}
|
|
rules->ruleState = 0;
|
|
} else {
|
|
// 4c) Exiting pit lane the wrong way : add new stop and go penalty if possible.
|
|
//GfLogDebug("%s exiting pit lane by a side (bad).\n", car->_name);
|
|
if (!(rules->ruleState & RM_PNST_STOPANDGO)) {
|
|
reCarsAddPenalty(car, RM_PENALTY_STOPANDGO);
|
|
rules->ruleState = RM_PNST_STOPANDGO;
|
|
GfLogInfo("%s got a Stop-and-Go penalty (went out the pits at a wrong place).\n",
|
|
car->_name);
|
|
}
|
|
}
|
|
|
|
// 5) Still crossing pit lane exit (probably a long PITEND segment) : Nothing bad.
|
|
} else if (seg->raceInfo & TR_PITEND) {
|
|
|
|
rules->ruleState = 0;
|
|
|
|
// 6) Entering the pits at a wrong place, add new stop and go penalty if possible.
|
|
} else if (seg->raceInfo & TR_PIT) {
|
|
//GfLogDebug("%s entering pit lane by a side (bad).\n", car->_name);
|
|
if (!(rules->ruleState & RM_PNST_STOPANDGO)) {
|
|
reCarsAddPenalty(car, RM_PENALTY_STOPANDGO);
|
|
rules->ruleState = RM_PNST_STOPANDGO;
|
|
GfLogInfo("%s got a Stop-and-Go penalty (went in the pits at a wrong place).\n",
|
|
car->_name);
|
|
}
|
|
}
|
|
|
|
// 7) If too fast in a speed limited section, add new drive-through penalty if possible.
|
|
if (seg->raceInfo & TR_SPEEDLIMIT) {
|
|
if (!(rules->ruleState & (RM_PNST_OVERSPEED | RM_PNST_STOPANDGO))
|
|
&& car->_speed_x > track->pits.speedLimit) {
|
|
rules->ruleState |= RM_PNST_OVERSPEED;
|
|
reCarsAddPenalty(car, RM_PENALTY_DRIVETHROUGH);
|
|
GfLogInfo("%s got a Drive-Through penalty (too fast in the pits).\n", car->_name);
|
|
}
|
|
}
|
|
|
|
// Check for jumping starting lights
|
|
if (ReInfo->s->_raceState & RM_RACE_PRESTART && car->_speed_x > 1) {
|
|
if (!(rules->ruleState & (RM_PNST_STOPANDGO))) {
|
|
reCarsAddPenalty(car, RM_PENALTY_STOPANDGO);
|
|
rules->ruleState = RM_PNST_STOPANDGO;
|
|
GfLogInfo("%s got a Stop-and-Go penalty (jumped starting lights).\n",
|
|
car->_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ReCarsManageCar(tCarElt *car, bool& bestLapChanged)
|
|
{
|
|
char msg[64];
|
|
int i;
|
|
int xx;
|
|
tTrackSeg *sseg;
|
|
tdble wseg;
|
|
static const float ctrlMsgColor[] = {0.0, 0.0, 1.0, 1.0};
|
|
tSituation *s = ReInfo->s;
|
|
|
|
tReCarInfo *info = &(ReInfo->_reCarInfo[car->index]);
|
|
|
|
// Update top speeds.
|
|
if (car->_speed_x > car->_topSpeed)
|
|
car->_topSpeed = car->_speed_x;
|
|
|
|
// (practice and qualification only).
|
|
if (car->_speed_x > info->topSpd)
|
|
info->topSpd = car->_speed_x;
|
|
if (car->_speed_x < info->botSpd)
|
|
info->botSpd = car->_speed_x;
|
|
|
|
// Pitstop management.
|
|
if (car->_pit) {
|
|
|
|
// If the driver can ask for a pit, update control messages whether slot occupied or not.
|
|
if (car->ctrl.raceCmd & RM_CMD_PIT_ASKED) {
|
|
// Pit already occupied?
|
|
if (car->_pit->pitCarIndex == TR_PIT_STATE_FREE)
|
|
snprintf(car->ctrl.msg[2], RM_CMD_MAX_MSG_SIZE, "Can Pit");
|
|
else
|
|
snprintf(car->ctrl.msg[2], RM_CMD_MAX_MSG_SIZE, "Pit Occupied");
|
|
car->ctrl.msg[2][RM_CMD_MAX_MSG_SIZE-1] = 0; // Some snprintf implementations fail to do so.
|
|
memcpy(car->ctrl.msgColor, ctrlMsgColor, sizeof(car->ctrl.msgColor));
|
|
}
|
|
|
|
// If pitting, check if pitting delay over, and end up with pitting process if so.
|
|
if (car->_state & RM_CAR_STATE_PIT) {
|
|
car->ctrl.raceCmd &= ~RM_CMD_PIT_ASKED; // clear the flag.
|
|
// Note: Due to asynchronous behaviour of the main updater and the situation updater,
|
|
// we have to wait for car->_scheduledEventTime being set to smthg > 0.
|
|
if (car->_scheduledEventTime > 0.0) {
|
|
if (car->_scheduledEventTime < s->currentTime) {
|
|
car->_state &= ~RM_CAR_STATE_PIT;
|
|
car->_pit->pitCarIndex = TR_PIT_STATE_FREE;
|
|
snprintf(msg, sizeof(msg), "%s pit stop %.1f s", car->_name, info->totalPitTime);
|
|
msg[sizeof(msg)-1] = 0; // Some snprintf implementations fail to do so.
|
|
ReSituation::self().setRaceMessage(msg, 5);
|
|
GfLogInfo("%s exiting pit (%.1f s elapsed).\n", car->_name, info->totalPitTime);
|
|
} else {
|
|
snprintf(car->ctrl.msg[2], RM_CMD_MAX_MSG_SIZE, "In pits %.1f s",
|
|
s->currentTime - info->startPitTime);
|
|
car->ctrl.msg[2][RM_CMD_MAX_MSG_SIZE-1] = 0; // Some snprintf implementations fail to do so.
|
|
}
|
|
}
|
|
|
|
// If the driver asks for a pit, check if the car is in the right conditions
|
|
// (position, speed, ...) and start up pitting process if so.
|
|
} else if ((car->ctrl.raceCmd & RM_CMD_PIT_ASKED) &&
|
|
car->_pit->pitCarIndex == TR_PIT_STATE_FREE &&
|
|
(s->_maxDammage == 0 || car->_dammage <= s->_maxDammage)) {
|
|
snprintf(car->ctrl.msg[2], RM_CMD_MAX_MSG_SIZE, "Pit request");
|
|
car->ctrl.msg[2][RM_CMD_MAX_MSG_SIZE-1] = 0; // Some snprintf implementations fail to do so.
|
|
|
|
tdble lgFromStart = car->_trkPos.seg->lgfromstart;
|
|
|
|
switch (car->_trkPos.seg->type) {
|
|
case TR_STR:
|
|
lgFromStart += car->_trkPos.toStart;
|
|
break;
|
|
default:
|
|
lgFromStart += car->_trkPos.toStart * car->_trkPos.seg->radius;
|
|
break;
|
|
}
|
|
|
|
if ((lgFromStart > car->_pit->lmin) && (lgFromStart < car->_pit->lmax)) {
|
|
int side;
|
|
tdble toBorder;
|
|
if (ReInfo->track->pits.side == TR_RGT) {
|
|
side = TR_SIDE_RGT;
|
|
toBorder = car->_trkPos.toRight;
|
|
} else {
|
|
side = TR_SIDE_LFT;
|
|
toBorder = car->_trkPos.toLeft;
|
|
}
|
|
|
|
sseg = car->_trkPos.seg->side[side];
|
|
wseg = RtTrackGetWidth(sseg, car->_trkPos.toStart);
|
|
if (sseg->side[side]) {
|
|
sseg = sseg->side[side];
|
|
wseg += RtTrackGetWidth(sseg, car->_trkPos.toStart);
|
|
}
|
|
if (((toBorder + wseg) < (ReInfo->track->pits.width - car->_dimension_y / 2.0)) &&
|
|
(fabs(car->_speed_x) < 1.0) && (fabs(car->_speed_y) < 1.0))
|
|
{
|
|
// All conditions fullfilled => enter pitting process
|
|
car->_state |= RM_CAR_STATE_PIT;
|
|
car->_scheduledEventTime = 0.0; // Pit will really start when set to smthg > 0.
|
|
car->_nbPitStops++;
|
|
for (i = 0; i < car->_pit->freeCarIndex; i++) {
|
|
if (car->_pit->car[i] == car) {
|
|
car->_pit->pitCarIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
info->startPitTime = s->currentTime;
|
|
snprintf(msg, sizeof(msg), "%s in pits", car->_name);
|
|
msg[sizeof(msg)-1] = 0; // Some snprintf implementations fail to do so.
|
|
ReSituation::self().setRaceMessage(msg, 5);
|
|
GfLogInfo("%s entering in pit slot.\n", car->_name);
|
|
if (car->robot->rbPitCmd(car->robot->index, car, s) == ROB_PIT_MENU) {
|
|
// the pit cmd is modified by menu.
|
|
reCarsSchedulePitMenu(car);
|
|
} else {
|
|
ReCarsUpdateCarPitTime(car);
|
|
}
|
|
}
|
|
else
|
|
{ // The cars speed or offset is out of accepted range
|
|
// Show the user/developer/robot the reason of the issue
|
|
tTeamDriver* TeamDriver = RtTeamDriverByCar(car);
|
|
if (TeamDriver)
|
|
{
|
|
TeamDriver->StillToGo = 0.0;
|
|
TeamDriver->MoreOffset = 0.0;
|
|
TeamDriver->TooFastBy = 0.0;
|
|
}
|
|
|
|
float Offset = (float) ((toBorder + wseg) - (ReInfo->track->pits.width - car->_dimension_y / 2.0));
|
|
if (Offset >= 0.0)
|
|
{
|
|
// The car's position across the track is out of accepted range
|
|
snprintf(car->ctrl.msg[2], RM_CMD_MAX_MSG_SIZE, "Offset: %.02f",Offset);
|
|
car->ctrl.msg[2][RM_CMD_MAX_MSG_SIZE-1] = 0; // Some snprintf implementations fail to do so.
|
|
if (TeamDriver)
|
|
TeamDriver->MoreOffset = Offset;
|
|
}
|
|
|
|
float TooFastBy = MAX(fabs(car->_speed_x),fabs(car->_speed_y));
|
|
if (TooFastBy >= 1.0)
|
|
{
|
|
// The car's speed is out of accepted range
|
|
snprintf(car->ctrl.msg[2], RM_CMD_MAX_MSG_SIZE, "Speed: %.02f",TooFastBy);
|
|
car->ctrl.msg[2][RM_CMD_MAX_MSG_SIZE-1] = 0; // Some snprintf implementations fail to do so.
|
|
if (TeamDriver)
|
|
TeamDriver->TooFastBy = TooFastBy;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // The car's position along the track is out of accepted range
|
|
// Show the user/developer/robot the reason of the issue
|
|
tTeamDriver* TeamDriver = RtTeamDriverByCar(car);
|
|
if (TeamDriver)
|
|
{
|
|
TeamDriver->StillToGo = 0.0;
|
|
TeamDriver->MoreOffset = 0.0;
|
|
TeamDriver->TooFastBy = 0.0;
|
|
}
|
|
|
|
if (car->_pit->lmin > lgFromStart)
|
|
{
|
|
float StillToGo = car->_pit->lmin - lgFromStart;
|
|
snprintf(car->ctrl.msg[2], RM_CMD_MAX_MSG_SIZE, "Still to go: %0.2f m" ,StillToGo);
|
|
car->ctrl.msg[2][RM_CMD_MAX_MSG_SIZE-1] = 0; // Some snprintf implementations fail to do so.
|
|
if (TeamDriver)
|
|
TeamDriver->StillToGo = StillToGo;
|
|
}
|
|
else if (car->_pit->lmax < lgFromStart)
|
|
{
|
|
float StillToGo = lgFromStart - car->_pit->lmax;
|
|
snprintf(car->ctrl.msg[2], RM_CMD_MAX_MSG_SIZE, "Overrun: %0.2f m" ,StillToGo);
|
|
car->ctrl.msg[2][RM_CMD_MAX_MSG_SIZE-1] = 0; // Some snprintf implementations fail to do so.
|
|
if (TeamDriver)
|
|
TeamDriver->StillToGo = -StillToGo;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check if it is in a new sector */
|
|
while (true)
|
|
{
|
|
if (car->_currentSector < ReInfo->track->numberOfSectors - 1 && car->_laps > 0 && info->lapFlag == 0)
|
|
{
|
|
/* Must pass at least one sector before the finish */
|
|
if (RtGetDistFromStart(car) > ReInfo->track->sectors[car->_currentSector])
|
|
{
|
|
/* It is in a new sector : update split time */
|
|
car->_curSplitTime[car->_currentSector] = car->_curLapTime;
|
|
++car->_currentSector;
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Start Line Crossing */
|
|
if (info->prevTrkPos.seg != car->_trkPos.seg) {
|
|
|
|
if ((info->prevTrkPos.seg->raceInfo & TR_LAST)
|
|
&& (car->_trkPos.seg->raceInfo & TR_START)) {
|
|
|
|
if (info->lapFlag == 0) {
|
|
|
|
// If the car has not yet finished the race :
|
|
if (!(car->_state & RM_CAR_STATE_FINISH)) {
|
|
|
|
// 1 more lap completed
|
|
// (Note: lap with index 0 finishes when the car crosses the start line the 1st time,
|
|
// and is thus considered a real lap, whereas it is not).
|
|
car->_laps++;
|
|
|
|
/*if (NetGetNetwork())
|
|
NetGetNetwork()->SendLapStatusPacket(car);*/
|
|
|
|
car->_remainingLaps--;
|
|
if (car->_pos == 1 && s->currentTime < s->_totTime
|
|
&& s->_raceType == RM_TYPE_RACE)
|
|
{
|
|
/* First car passed finish time before the time ends: increase the number of laps for everyone */
|
|
for (xx = 0; xx < s->_ncars; ++xx)
|
|
++ReInfo->s->cars[xx]->_remainingLaps;
|
|
++s->_totLaps;
|
|
}
|
|
|
|
car->_currentSector = 0;
|
|
if (car->_laps > 1) {
|
|
car->_lastLapTime = s->currentTime - info->sTime;
|
|
if (car->_bestLapTime != 0) {
|
|
car->_deltaBestLapTime = car->_lastLapTime - car->_bestLapTime;
|
|
}
|
|
if ((car->_lastLapTime < car->_bestLapTime) || (car->_bestLapTime == 0)) {
|
|
car->_bestLapTime = car->_lastLapTime;
|
|
car->_bestLap = car->_laps - 1;
|
|
memcpy(car->_bestSplitTime, car->_curSplitTime, sizeof(double)*(ReInfo->track->numberOfSectors - 1) );
|
|
|
|
if (s->_raceType != RM_TYPE_RACE && s->_ncars > 1)
|
|
{
|
|
/* Best lap time is made better : update times behind leader */
|
|
bestLapChanged = true;
|
|
car->_timeBehindLeader = car->_bestLapTime - s->cars[0]->_bestLapTime;
|
|
if (car->_pos > 1)
|
|
{
|
|
car->_timeBehindPrev = car->_bestLapTime - s->cars[car->_pos - 1]->_bestLapTime;
|
|
}
|
|
else
|
|
{
|
|
/* New best time for the leader : update the differences */
|
|
for (xx = 1; xx < s->_ncars; ++xx)
|
|
{
|
|
if (s->cars[xx]->_bestLapTime > 0.0f)
|
|
s->cars[xx]->_timeBehindLeader = s->cars[xx]->_bestLapTime - car->_bestLapTime;
|
|
}
|
|
}
|
|
if (car->_pos + 1 < s->_ncars && s->cars[car->_pos+1]->_bestLapTime > 0.0f)
|
|
car->_timeBeforeNext = s->cars[car->_pos + 1]->_bestLapTime - car->_bestLapTime;
|
|
else
|
|
car->_timeBeforeNext = 0;
|
|
}
|
|
}
|
|
}
|
|
if (car->_laps > 0) {
|
|
car->_curTime += s->currentTime - info->sTime;
|
|
|
|
if (car->_pos != 1 && s->_raceType == RM_TYPE_RACE) {
|
|
car->_timeBehindLeader = car->_curTime - s->cars[0]->_curTime;
|
|
car->_lapsBehindLeader = s->cars[0]->_laps - car->_laps;
|
|
car->_timeBehindPrev = car->_curTime - s->cars[car->_pos - 2]->_curTime;
|
|
s->cars[car->_pos - 2]->_timeBeforeNext = car->_timeBehindPrev;
|
|
} else if (s->_raceType == RM_TYPE_RACE) {
|
|
car->_timeBehindLeader = 0;
|
|
car->_lapsBehindLeader = 0;
|
|
car->_timeBehindPrev = 0;
|
|
}
|
|
|
|
info->sTime = (tdble)s->currentTime;
|
|
|
|
if (ReInfo->s->_raceType == RM_TYPE_PRACTICE &&
|
|
(car->_laps > 1 || s->_totLaps == 0))
|
|
ReSavePracticeLap(car);
|
|
}
|
|
|
|
if (ReInfo->_displayMode == RM_DISP_MODE_NONE)
|
|
{
|
|
switch(s->_raceType)
|
|
{
|
|
case RM_TYPE_PRACTICE:
|
|
ReUpdatePracticeCurRes(car);
|
|
break;
|
|
case RM_TYPE_QUALIF:
|
|
ReUpdateQualifCurRes(car);
|
|
break;
|
|
case RM_TYPE_RACE:
|
|
ReUpdateRaceCurRes();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
info->topSpd = car->_speed_x;
|
|
info->botSpd = car->_speed_x;
|
|
if ((car->_remainingLaps < 0 && s->currentTime > s->_totTime) || (s->_raceState == RM_RACE_FINISHING)) {
|
|
car->_state |= RM_CAR_STATE_FINISH;
|
|
s->_raceState = RM_RACE_FINISHING;
|
|
if (ReInfo->s->_raceType == RM_TYPE_RACE) {
|
|
if (car->_pos == 1) {
|
|
snprintf(msg, sizeof(msg), "Winner %s", car->_name);
|
|
msg[sizeof(msg)-1] = 0; // Some snprintf implementations fail to do so.
|
|
ReSituation::self().setRaceMessage(msg, 10, /*big=*/true);
|
|
if (NetGetServer())
|
|
{
|
|
NetGetServer()->SetFinishTime(s->currentTime+FINISHDELAY);
|
|
}
|
|
} else {
|
|
const char *numSuffix = "th";
|
|
if (abs(12 - car->_pos) > 1) { /* leave suffix as 'th' for 11 to 13 */
|
|
switch (car->_pos % 10) {
|
|
case 1:
|
|
numSuffix = "st";
|
|
break;
|
|
case 2:
|
|
numSuffix = "nd";
|
|
break;
|
|
case 3:
|
|
numSuffix = "rd";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
snprintf(msg, sizeof(msg), "%s finished %d%s", car->_name, car->_pos, numSuffix);
|
|
msg[sizeof(msg)-1] = 0; // Some snprintf implementations fail to do so.
|
|
ReSituation::self().setRaceMessage(msg, 5);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Notify the UI when a lap is completed (by the leader)
|
|
// and race results have been updated.
|
|
if (car->_pos == 1)
|
|
ReUI().onLapCompleted(car->_laps - 1);
|
|
|
|
} else {
|
|
// Prevent infinite looping of cars around track,
|
|
// allowing one lap after finish for the first car, but no more
|
|
for (i = 0; i < s->_ncars; i++) {
|
|
s->cars[i]->_state |= RM_CAR_STATE_FINISH;
|
|
}
|
|
return;
|
|
}
|
|
|
|
#if 0 //def THIRD_PARTY_SQLITE3
|
|
// Re-read the best lap for ghostcar
|
|
if (replayDB != NULL && car->_bestLap) {
|
|
char command[200];
|
|
int result;
|
|
|
|
GfLogInfo("Re-reading best lap\n");
|
|
sprintf(command, "SELECT datablob FROM car0 where lap=%d", car->_bestLap);
|
|
result = sqlite3_prepare_v2(replayDB, command, -1, &ghostcarBlob, 0);
|
|
|
|
if (result) {
|
|
GfLogInfo("Unable to read ghostlap %d: %s\n", car->_bestLap, sqlite3_errmsg(replayDB));
|
|
} else {
|
|
// read the first 2 records
|
|
result = sqlite3_step(ghostcarBlob);
|
|
if (result == SQLITE_ROW) {
|
|
memcpy(&curGhostcarData, sqlite3_column_blob(ghostcarBlob, 0), sizeof(tReplayElt));
|
|
}
|
|
result = sqlite3_step(ghostcarBlob);
|
|
if (result == SQLITE_ROW) {
|
|
memcpy(&nextGhostcarData, sqlite3_column_blob(ghostcarBlob, 0), sizeof(tReplayElt));
|
|
}
|
|
|
|
ghostcarTimeOffset = s->currentTime - curGhostcarData.currentTime;
|
|
ghostcarActive = 1;
|
|
}
|
|
}
|
|
#endif
|
|
} else {
|
|
info->lapFlag--;
|
|
}
|
|
}
|
|
if ((info->prevTrkPos.seg->raceInfo & TR_START)
|
|
&& (car->_trkPos.seg->raceInfo & TR_LAST)) {
|
|
/* going backward through the start line */
|
|
info->lapFlag++;
|
|
}
|
|
} // Start Line Crossing
|
|
|
|
|
|
// Apply race rules (penalties if enabled).
|
|
reCarsApplyRaceRules(car);
|
|
|
|
// Update misc car info.
|
|
info->prevTrkPos = car->_trkPos;
|
|
car->_curLapTime = s->currentTime - info->sTime;
|
|
car->_distFromStartLine = car->_trkPos.seg->lgfromstart +
|
|
(car->_trkPos.seg->type == TR_STR ? car->_trkPos.toStart : car->_trkPos.toStart * car->_trkPos.seg->radius);
|
|
car->_distRaced = (car->_laps - 1) * ReInfo->track->length + car->_distFromStartLine;
|
|
}
|
|
|
|
void
|
|
ReCarsSortCars(void)
|
|
{
|
|
int i,j;
|
|
int xx;
|
|
tCarElt *car;
|
|
tSituation *s = ReInfo->s;
|
|
char msg[64];
|
|
|
|
// Check cars are driving the right way around track
|
|
for (i = 0; i < s->_ncars; i++) {
|
|
if (s->cars[i]->_prevFromStartLine < s->cars[i]->_distFromStartLine) {
|
|
s->cars[i]->_wrongWayTime = s->currentTime + 5.0;
|
|
}
|
|
|
|
s->cars[i]->_prevFromStartLine = s->cars[i]->_distFromStartLine;
|
|
|
|
if (s->cars[i]->_wrongWayTime < s->currentTime
|
|
&& s->cars[i]->_speed_xy > 10
|
|
&& s->cars[i]->_driverType == RM_DRV_HUMAN
|
|
&& s->cars[i]->_state != RM_CAR_STATE_ELIMINATED) {
|
|
snprintf(msg, sizeof(msg), "%s Wrong Way", s->cars[i]->_name);
|
|
msg[sizeof(msg)-1] = 0; // Some snprintf implementations fail to do so.
|
|
ReSituation::self().setRaceMessage(msg, 2);
|
|
// prevent flickering occuring by 'short timing', assuming > 10fps
|
|
s->cars[i]->_wrongWayTime = s->currentTime + 1.9;
|
|
}
|
|
}
|
|
|
|
int allfinish = (s->cars[0]->_state & RM_CAR_STATE_FINISH) ? 1 : 0;
|
|
for (i = 1; i < s->_ncars; i++) {
|
|
j = i;
|
|
while (j > 0) {
|
|
if (!(s->cars[j]->_state & RM_CAR_STATE_FINISH)) {
|
|
allfinish = 0;
|
|
if ((ReInfo->s->_raceType == RM_TYPE_RACE && s->cars[j]->_distRaced > s->cars[j-1]->_distRaced) ||
|
|
(ReInfo->s->_raceType != RM_TYPE_RACE && s->cars[j]->_bestLapTime > 0.0f && ( s->cars[j]->_bestLapTime < s->cars[j-1]->_bestLapTime ||
|
|
s->cars[j-1]->_bestLapTime <= 0.0f))) {
|
|
car = s->cars[j];
|
|
s->cars[j] = s->cars[j-1];
|
|
s->cars[j-1] = car;
|
|
s->cars[j]->_pos = j+1;
|
|
s->cars[j-1]->_pos = j;
|
|
if (s->_raceType != RM_TYPE_RACE)
|
|
{
|
|
if (j-1 > 0)
|
|
{
|
|
s->cars[j-1]->_timeBehindPrev = s->cars[j-1]->_bestLapTime - s->cars[j-2]->_bestLapTime;
|
|
}
|
|
else
|
|
{
|
|
s->cars[j-1]->_timeBehindPrev = 0;
|
|
for (xx = 1; xx < s->_ncars; ++xx)
|
|
{
|
|
/* New leader */
|
|
if (s->cars[xx]->_bestLapTime > 0.0f)
|
|
s->cars[xx]->_timeBehindLeader = s->cars[xx]->_bestLapTime - s->cars[0]->_bestLapTime;
|
|
}
|
|
}
|
|
if (s->cars[j]->_bestLapTime)
|
|
s->cars[j-1]->_timeBeforeNext = s->cars[j-1]->_bestLapTime - s->cars[j]->_bestLapTime;
|
|
else
|
|
s->cars[j-1]->_timeBeforeNext = 0;
|
|
s->cars[j]->_timeBehindPrev = s->cars[j]->_bestLapTime - s->cars[j-1]->_bestLapTime;
|
|
if (j+1 < s->_ncars && s->cars[j+1]->_bestLapTime > 0.0f)
|
|
s->cars[j]->_timeBeforeNext = s->cars[j]->_bestLapTime - s->cars[j+1]->_bestLapTime;
|
|
else
|
|
s->cars[j]->_timeBeforeNext = 0;
|
|
}
|
|
j--;
|
|
continue;
|
|
}
|
|
}
|
|
j = 0;
|
|
}
|
|
}
|
|
if (allfinish) {
|
|
ReInfo->s->_raceState = RM_RACE_ENDED;
|
|
//GfLogDebug("ReCarsSortCars: Race completed.\n");
|
|
}
|
|
}
|