/*************************************************************************** file : raceengine.cpp created : Sat Nov 23 09:05:23 CET 2002 copyright : (C) 2002 by Eric EspiƩ email : eric.espie@torcs.org version : $Id: raceengine.cpp,v 1.18 2006/02/22 22:10:14 berniw Exp $ ***************************************************************************/ /*************************************************************************** * * * 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 Eric Espie @version $Id: raceengine.cpp,v 1.18 2006/02/22 22:10:14 berniw Exp $ */ #include #include #include #include #include #include #include #include "racemain.h" #include "racegl.h" #include "raceinit.h" #include "raceresults.h" #include "raceengine.h" static char buf[1024]; static double msgDisp; static double bigMsgDisp; tRmInfo *ReInfo = 0; static void ReRaceRules(tCarElt *car); /* Compute Pit stop time */ static void ReUpdtPitTime(tCarElt *car) { tSituation *s = ReInfo->s; tReCarInfo *info = &(ReInfo->_reCarInfo[car->index]); int i; switch (car->_pitStopType) { case RM_PIT_REPAIR: info->totalPitTime = 2.0f + fabs(car->_pitFuel) / 8.0f + (tdble)(fabs(car->_pitRepair)) * 0.007f; car->_scheduledEventTime = s->currentTime + info->totalPitTime; ReInfo->_reSimItf.reconfig(car); for (i=0; i<4; i++) { car->_tyreCondition(i) = 1.01; car->_tyreT_in(i) = 50.0; car->_tyreT_mid(i) = 50.0; car->_tyreT_out(i) = 50.0; } break; case RM_PIT_STOPANDGO: info->totalPitTime = 0.0; car->_scheduledEventTime = s->currentTime; break; } } /* Return from interactive pit information */ static void ReUpdtPitCmd(void *pvcar) { tCarElt *car = (tCarElt*)pvcar; ReUpdtPitTime(car); //ReStart(); /* resynchro */ GfuiScreenActivate(ReInfo->_reGameScreen); } static void ReRaceMsgUpdate(void) { if (ReInfo->_reCurTime > msgDisp) { ReSetRaceMsg(""); } if (ReInfo->_reCurTime > bigMsgDisp) { ReSetRaceBigMsg(""); } } static void ReRaceMsgSet(char *msg, double life) { ReSetRaceMsg(msg); msgDisp = ReInfo->_reCurTime + life; } static void ReRaceBigMsgSet(char *msg, double life) { ReSetRaceBigMsg(msg); bigMsgDisp = ReInfo->_reCurTime + life; } static void ReManage(tCarElt *car) { int i, pitok; tTrackSeg *sseg; tdble wseg; static float color[] = {0.0, 0.0, 1.0, 1.0}; tSituation *s = ReInfo->s; tReCarInfo *info = &(ReInfo->_reCarInfo[car->index]); if (car->_speed_x > car->_topSpeed) { car->_topSpeed = car->_speed_x; } // For practice and qualif. if (car->_speed_x > info->topSpd) { info->topSpd = car->_speed_x; } if (car->_speed_x < info->botSpd) { info->botSpd = car->_speed_x; } // Pitstop. if (car->_pit) { if (car->ctrl.raceCmd & RM_CMD_PIT_ASKED) { // Pit already occupied? if (car->_pit->pitCarIndex == TR_PIT_STATE_FREE) { sprintf(car->ctrl.msg[2], "Can Pit"); } else { sprintf(car->ctrl.msg[2], "Pit Occupied"); } memcpy(car->ctrl.msgColor, color, sizeof(car->ctrl.msgColor)); } if (car->_state & RM_CAR_STATE_PIT) { car->ctrl.raceCmd &= ~RM_CMD_PIT_ASKED; // clear the flag. if (car->_scheduledEventTime < s->currentTime) { car->_state &= ~RM_CAR_STATE_PIT; car->_pit->pitCarIndex = TR_PIT_STATE_FREE; sprintf(buf, "%s pit stop %.1fs", car->_name, info->totalPitTime); ReRaceMsgSet(buf, 5); } else { sprintf(car->ctrl.msg[2], "in pits %.1fs", s->currentTime - info->startPitTime); } } else if ((car->ctrl.raceCmd & RM_CMD_PIT_ASKED) && car->_pit->pitCarIndex == TR_PIT_STATE_FREE && (s->_maxDammage == 0 || car->_dammage <= s->_maxDammage)) { 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)) { pitok = 0; 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)) { pitok = 1; } if (pitok) { car->_state |= RM_CAR_STATE_PIT; 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; sprintf(buf, "%s in pits", car->_name); ReRaceMsgSet(buf, 5); if (car->robot->rbPitCmd(car->robot->index, car, s) == ROB_PIT_MENU) { // the pit cmd is modified by menu. ReStop(); RmPitMenuStart(car, (void*)car, ReUpdtPitCmd); } else { ReUpdtPitTime(car); } } } } } /* 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 ((car->_state & RM_CAR_STATE_FINISH) == 0) { car->_laps++; car->_remainingLaps--; if (car->_laps > 1) { car->_lastLapTime = s->currentTime - info->sTime; car->_curTime += car->_lastLapTime; if (car->_bestLapTime != 0) { car->_deltaBestLapTime = car->_lastLapTime - car->_bestLapTime; } if ((car->_lastLapTime < car->_bestLapTime) || (car->_bestLapTime == 0)) { car->_bestLapTime = car->_lastLapTime; } if (car->_pos != 1) { 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 { car->_timeBehindLeader = 0; car->_lapsBehindLeader = 0; car->_timeBehindPrev = 0; } info->sTime = s->currentTime; switch (ReInfo->s->_raceType) { case RM_TYPE_PRACTICE: if (ReInfo->_displayMode == RM_DISP_MODE_NONE) { ReInfo->_refreshDisplay = 1; char *t1, *t2; t1 = GfTime2Str(car->_lastLapTime, 0); t2 = GfTime2Str(car->_bestLapTime, 0); sprintf(buf,"lap: %02d time: %s best: %s top spd: %.2f min spd: %.2f damage: %d", car->_laps - 1, t1, t2, info->topSpd * 3.6, info->botSpd * 3.6, car->_dammage); ReResScreenAddText(buf); free(t1); free(t2); } /* save the lap result */ ReSavePracticeLap(car); break; case RM_TYPE_QUALIF: if (ReInfo->_displayMode == RM_DISP_MODE_NONE) { ReUpdateQualifCurRes(car); } break; } } else { if ((ReInfo->_displayMode == RM_DISP_MODE_NONE) && (ReInfo->s->_raceType == RM_TYPE_QUALIF)) { ReUpdateQualifCurRes(car); } } info->topSpd = car->_speed_x; info->botSpd = car->_speed_x; if ((car->_remainingLaps < 0) || (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) { sprintf(buf, "Winner %s", car->_name); ReRaceBigMsgSet(buf, 10); } else { 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; } } sprintf(buf, "%s Finished %d%s", car->_name, car->_pos, numSuffix); ReRaceMsgSet(buf, 5); } } } } else { /* prevent infinite looping of cars around track, allow one lap after finish for the first car */ for (i = 0; i < s->_ncars; i++) { s->cars[i]->_state |= RM_CAR_STATE_FINISH; } return; } } else { info->lapFlag--; } } if ((info->prevTrkPos.seg->raceInfo & TR_START) && (car->_trkPos.seg->raceInfo & TR_LAST)) { /* going backward through the start line */ info->lapFlag++; } } ReRaceRules(car); 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; } static void ReSortCars(void) { int i,j; tCarElt *car; int allfinish; tSituation *s = ReInfo->s; if ((s->cars[0]->_state & RM_CAR_STATE_FINISH) == 0) { allfinish = 0; } else { allfinish = 1; } for (i = 1; i < s->_ncars; i++) { j = i; while (j > 0) { if ((s->cars[j]->_state & RM_CAR_STATE_FINISH) == 0) { allfinish = 0; if (s->cars[j]->_distRaced > s->cars[j-1]->_distRaced) { 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; j--; continue; } } j = 0; } } if (allfinish) { ReInfo->s->_raceState = RM_RACE_ENDED; } } /* Compute the race rules and penalties */ static void ReRaceRules(tCarElt *car) { 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 float color[] = {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. // TODO: Make it configurable. if ((car->_curLapTime > 84.5 + ReInfo->track->length/10.0) && (car->_driverType != RM_DRV_HUMAN)) { car->_state |= RM_CAR_STATE_ELIMINATED; return; } if (car->_skillLevel < 3) { /* only for the pros */ return; } penalty = GF_TAILQ_FIRST(&(car->_penaltyList)); if (penalty) { if (car->_laps > penalty->lapToClear) { /* too late to clear the penalty, out of race */ car->_state |= RM_CAR_STATE_ELIMINATED; return; } switch (penalty->penalty) { case RM_PENALTY_DRIVETHROUGH: sprintf(car->ctrl.msg[3], "Drive Through Penalty"); break; case RM_PENALTY_STOPANDGO: sprintf(car->ctrl.msg[3], "Stop And Go Penalty"); break; default: *(car->ctrl.msg[3]) = 0; break; } memcpy(car->ctrl.msgColor, color, sizeof(car->ctrl.msgColor)); } if (prevSeg->raceInfo & TR_PITSTART) { /* just entered the pit lane */ if (seg->raceInfo & TR_PIT) { /* may be a penalty can be cleaned up */ if (penalty) { switch (penalty->penalty) { case RM_PENALTY_DRIVETHROUGH: sprintf(buf, "%s DRIVE THROUGH PENALTY CLEANING", car->_name); ReRaceMsgSet(buf, 5); rules->ruleState |= RM_PNST_DRIVETHROUGH; break; case RM_PENALTY_STOPANDGO: sprintf(buf, "%s STOP&GO PENALTY CLEANING", car->_name); ReRaceMsgSet(buf, 5); rules->ruleState |= RM_PNST_STOPANDGO; break; } } } } else if (prevSeg->raceInfo & TR_PIT) { if (seg->raceInfo & TR_PIT) { /* the car stopped in pits */ if (car->_state & RM_CAR_STATE_PIT) { if (rules->ruleState & RM_PNST_DRIVETHROUGH) { /* it's not more a drive through */ rules->ruleState &= ~RM_PNST_DRIVETHROUGH; } else if (rules->ruleState & RM_PNST_STOPANDGO) { rules->ruleState |= RM_PNST_STOPANDGO_OK; } } else { if(rules->ruleState & RM_PNST_STOPANDGO_OK && car->_pitStopType != RM_PIT_STOPANDGO) { rules->ruleState &= ~ ( RM_PNST_STOPANDGO | RM_PNST_STOPANDGO_OK ); } } } else if (seg->raceInfo & TR_PITEND) { /* went out of the pit lane, check if the current penalty is cleared */ if (rules->ruleState & (RM_PNST_DRIVETHROUGH | RM_PNST_STOPANDGO_OK)) { /* clear the penalty */ sprintf(buf, "%s penalty cleared", car->_name); ReRaceMsgSet(buf, 5); penalty = GF_TAILQ_FIRST(&(car->_penaltyList)); GF_TAILQ_REMOVE(&(car->_penaltyList), penalty, link); FREEZ(penalty); } rules->ruleState = 0; } else { /* went out of the pit lane illegally... */ /* it's a new stop and go... */ if (!(rules->ruleState & RM_PNST_STNGO)) { sprintf(buf, "%s STOP&GO PENALTY", car->_name); ReRaceMsgSet(buf, 5); penalty = (tCarPenalty*)calloc(1, sizeof(tCarPenalty)); penalty->penalty = RM_PENALTY_STOPANDGO; penalty->lapToClear = car->_laps + 5; GF_TAILQ_INSERT_TAIL(&(car->_penaltyList), penalty, link); rules->ruleState = RM_PNST_STNGO; } } } else if (seg->raceInfo & TR_PITEND) { rules->ruleState = 0; } else if (seg->raceInfo & TR_PIT) { /* entrered the pits not from the pit entry... */ /* it's a new stop and go... */ if (!(rules->ruleState & RM_PNST_STNGO)) { sprintf(buf, "%s STOP&GO PENALTY", car->_name); ReRaceMsgSet(buf, 5); penalty = (tCarPenalty*)calloc(1, sizeof(tCarPenalty)); penalty->penalty = RM_PENALTY_STOPANDGO; penalty->lapToClear = car->_laps + 5; GF_TAILQ_INSERT_TAIL(&(car->_penaltyList), penalty, link); rules->ruleState = RM_PNST_STNGO; } } if (seg->raceInfo & TR_SPEEDLIMIT) { if (!(rules->ruleState & (RM_PNST_SPD | RM_PNST_STNGO)) && (car->_speed_x > track->pits.speedLimit)) { sprintf(buf, "%s DRIVE THROUGH PENALTY", car->_name); ReRaceMsgSet(buf, 5); rules->ruleState |= RM_PNST_SPD; penalty = (tCarPenalty*)calloc(1, sizeof(tCarPenalty)); penalty->penalty = RM_PENALTY_DRIVETHROUGH; penalty->lapToClear = car->_laps + 5; GF_TAILQ_INSERT_TAIL(&(car->_penaltyList), penalty, link); } } } static void ReOneStep(double deltaTimeIncrement) { int i; tRobotItf *robot; tSituation *s = ReInfo->s; if (floor(s->currentTime) == -2.0) { ReRaceBigMsgSet("Ready", 1.0); } else if (floor(s->currentTime) == -1.0) { ReRaceBigMsgSet("Set", 1.0); } else if (floor(s->currentTime) == 0.0) { ReRaceBigMsgSet("Go", 1.0); } ReInfo->_reCurTime += deltaTimeIncrement * ReInfo->_reTimeMult; /* "Real" time */ s->currentTime += deltaTimeIncrement; /* Simulated time */ if (s->currentTime < 0) { /* no simu yet */ ReInfo->s->_raceState = RM_RACE_PRESTART; } else if (ReInfo->s->_raceState == RM_RACE_PRESTART) { ReInfo->s->_raceState = RM_RACE_RUNNING; s->currentTime = 0.0; /* resynchronize */ ReInfo->_reLastTime = 0.0; } START_PROFILE("rbDrive*"); if ((s->currentTime - ReInfo->_reLastTime) >= RCM_MAX_DT_ROBOTS) { s->deltaTime = s->currentTime - ReInfo->_reLastTime; for (i = 0; i < s->_ncars; i++) { if ((s->cars[i]->_state & RM_CAR_STATE_NO_SIMU) == 0) { robot = s->cars[i]->robot; robot->rbDrive(robot->index, s->cars[i], s); } } ReInfo->_reLastTime = s->currentTime; } STOP_PROFILE("rbDrive*"); START_PROFILE("_reSimItf.update*"); ReInfo->_reSimItf.update(s, deltaTimeIncrement, -1); for (i = 0; i < s->_ncars; i++) { ReManage(s->cars[i]); } STOP_PROFILE("_reSimItf.update*"); ReRaceMsgUpdate(); ReSortCars(); } void ReStart(void) { ReInfo->_reRunning = 1; ReInfo->_reCurTime = GfTimeClock() - RCM_MAX_DT_SIMU; } void ReStop(void) { ReInfo->_reRunning = 0; } static void reCapture(void) { unsigned char *img; int sw, sh, vw, vh; tRmMovieCapture *capture = &(ReInfo->movieCapture); GfScrGetSize(&sw, &sh, &vw, &vh); img = (unsigned char*)malloc(vw * vh * 3); if (img == NULL) { return; } glPixelStorei(GL_PACK_ROW_LENGTH, 0); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadBuffer(GL_FRONT); glReadPixels((sw-vw)/2, (sh-vh)/2, vw, vh, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*)img); sprintf(buf, "%s/torcs-%4.4d-%8.8d.png", capture->outputBase, capture->currentCapture, capture->currentFrame++); GfImgWritePng(img, buf, vw, vh); free(img); } int ReUpdate(void) { double t; tRmMovieCapture *capture; START_PROFILE("ReUpdate"); ReInfo->_refreshDisplay = 0; switch (ReInfo->_displayMode) { case RM_DISP_MODE_NORMAL: t = GfTimeClock(); START_PROFILE("ReOneStep*"); while (ReInfo->_reRunning && ((t - ReInfo->_reCurTime) > RCM_MAX_DT_SIMU)) { ReOneStep(RCM_MAX_DT_SIMU); } STOP_PROFILE("ReOneStep*"); GfuiDisplay(); ReInfo->_reGraphicItf.refresh(ReInfo->s); glutPostRedisplay(); /* Callback -> reDisplay */ break; case RM_DISP_MODE_NONE: ReOneStep(RCM_MAX_DT_SIMU); if (ReInfo->_refreshDisplay) { GfuiDisplay(); } glutPostRedisplay(); /* Callback -> reDisplay */ break; case RM_DISP_MODE_CAPTURE: capture = &(ReInfo->movieCapture); while ((ReInfo->_reCurTime - capture->lastFrame) < capture->deltaFrame) { ReOneStep(capture->deltaSimu); } capture->lastFrame = ReInfo->_reCurTime; GfuiDisplay(); ReInfo->_reGraphicItf.refresh(ReInfo->s); reCapture(); glutPostRedisplay(); /* Callback -> reDisplay */ break; } STOP_PROFILE("ReUpdate"); return RM_ASYNC; } void ReTimeMod (void *vcmd) { long cmd = (long)vcmd; switch ((int)cmd) { case 0: ReInfo->_reTimeMult *= 2.0; if (ReInfo->_reTimeMult > 64.0) { ReInfo->_reTimeMult = 64.0; } break; case 1: ReInfo->_reTimeMult *= 0.5; if (ReInfo->_reTimeMult < 0.25) { ReInfo->_reTimeMult = 0.25; } break; case 2: default: ReInfo->_reTimeMult = 1.0; break; } sprintf(buf, "Time x%.2f", 1.0 / ReInfo->_reTimeMult); ReRaceMsgSet(buf, 5); }