/*============================================================================= Name : Stats.c Purpose : Stats deals with collecting fighting stats for the various ships Created 4/1/1998 by gshaw Copyright Relic Entertainment, Inc. All rights reserved. =============================================================================*/ #include #include #include "types.h" #include "spaceobj.h" #include "universe.h" #include "univupdate.h" #include "file.h" #include "tweak.h" #include "stats.h" #include "fastmath.h" #include "singleplayer.h" #include "crc32.h" #include "researchapi.h" #include "netcheck.h" #define STATLOG_FILENAME "statlog.txt" #define CONSIDER_SHIPS_EVEN_PERC_HP_LEFT 0.15f #define REFRESH 5 real32 statsOverkillfactor = 1.0f; real32 statsOverkillfactorSqr = 1.0f; TypeOfFormation showStatsFightF[2]; TacticsType showStatsFightT[2]; bool ShowFancyFights = FALSE; static CommandToDo *attackcommands[2]; FightStats FightStatsTable[NUM_SHIPS_TO_GATHER_STATS_FOR][NUM_SHIPS_TO_GATHER_STATS_FOR]; FightStatsSum FightStatsColumnSum[NUM_SHIPS_TO_GATHER_STATS_FOR]; FightStatsSum FightStatsRowSum[NUM_SHIPS_TO_GATHER_STATS_FOR]; // R1,R2,P1-3 #define NUM_RACES_TO_GATHER_STATS_FOR 5 sdword StatIndexRaceOffsets[NUM_RACES_TO_GATHER_STATS_FOR] = { 0, TOTAL_STD_SHIPS, TOTAL_STD_SHIPS+TOTAL_STD_SHIPS, TOTAL_STD_SHIPS+TOTAL_STD_SHIPS+TOTAL_P1_SHIPS, TOTAL_STD_SHIPS+TOTAL_STD_SHIPS+TOTAL_P1_SHIPS+TOTAL_P2_SHIPS }; static sbyte CalcFightStatsForRaces[NUM_RACES_TO_GATHER_STATS_FOR][NUM_RACES_TO_GATHER_STATS_FOR] = { // R1 R2 P1 P2 P3 { TRUE, TRUE, TRUE, TRUE, TRUE }, // R1 { TRUE, TRUE, TRUE, TRUE, TRUE }, // R2 { TRUE, TRUE, TRUE, FALSE,FALSE}, // P1 { TRUE, TRUE, FALSE,TRUE, FALSE}, // P2 { TRUE, TRUE, FALSE,FALSE,TRUE }, // P3 }; static sbyte R1CalcFightStatsFor[TOTAL_STD_SHIPS] = { /* R1 */ TRUE, //AdvanceSupportFrigate TRUE, //AttackBomber TRUE, //Carrier TRUE, //CloakedFighter FALSE, //CloakGenerator TRUE, //DDDFrigate FALSE, //DefenseFighter FALSE, //DFGFrigate FALSE, //GravWellGenerator TRUE, //HeavyCorvette TRUE, //HeavyCruiser TRUE, //HeavyDefender TRUE, //HeavyInterceptor TRUE, //IonCannonFrigate TRUE, //LightCorvette FALSE, //LightDefender TRUE, //LightInterceptor TRUE, //MinelayerCorvette TRUE, //MissileDestroyer TRUE, //Mothership TRUE, //MultiGunCorvette FALSE, //Probe FALSE, //ProximitySensor TRUE, //RepairCorvette FALSE, //ResearchShip FALSE, //ResourceCollector FALSE, //ResourceController FALSE, //SalCapCorvette FALSE, //SensorArray TRUE, //StandardDestroyer TRUE, //StandardFrigate FALSE, //Drone FALSE, //TargetDrone FALSE, //HeadShotAsteroid FALSE //CryoTray }; static sbyte R2CalcFightStatsFor[TOTAL_STD_SHIPS] = { /* R2 */ TRUE, //AdvanceSupportFrigate TRUE, //AttackBomber TRUE, //Carrier FALSE, //CloakedFighter FALSE, //CloakGenerator FALSE, //DDDFrigate FALSE, //DefenseFighter FALSE, //DFGFrigate FALSE, //GravWellGenerator TRUE, //HeavyCorvette TRUE, //HeavyCruiser TRUE, //HeavyDefender TRUE, //HeavyInterceptor TRUE, //IonCannonFrigate TRUE, //LightCorvette FALSE, //LightDefender TRUE, //LightInterceptor TRUE, //MinelayerCorvette TRUE, //MissileDestroyer TRUE, //Mothership TRUE, //MultiGunCorvette FALSE, //Probe FALSE, //ProximitySensor TRUE, //RepairCorvette FALSE, //ResearchShip FALSE, //ResourceCollector FALSE, //ResourceController FALSE, //SalCapCorvette FALSE, //SensorArray TRUE, //StandardDestroyer TRUE, //StandardFrigate FALSE, //Drone FALSE, //TargetDrone FALSE, //HeadShotAsteroid FALSE }; static sbyte P1CalcFightStatsFor[TOTAL_P1_SHIPS] = { /* P1 */ TRUE, //P1Fighter TRUE, //P1IonArrayFrigate TRUE, //P1MissileCorvette TRUE, //P1Mothership TRUE //P1StandardCorvette }; static sbyte P2CalcFightStatsFor[TOTAL_P2_SHIPS] = { /* P2 */ TRUE, //P2AdvanceSwarmer FALSE, //P2FuelPod TRUE, //P2Mothership TRUE, //P2MultiBeamFrigate TRUE, //P2Swarmer }; static sbyte P3CalcFightStatsFor[TOTAL_P3_SHIPS] = { /* P3 */ TRUE, //P3Destroyer TRUE, //P3Frigate TRUE, //P3Megaship }; static sbyte *RaceCalcFightStatsFor[NUM_RACES_TO_GATHER_STATS_FOR] = { R1CalcFightStatsFor, R2CalcFightStatsFor, P1CalcFightStatsFor, P2CalcFightStatsFor, P3CalcFightStatsFor }; bool ForceTotalRefresh = FALSE; static void statsTest(void); static void StatsForRaceCB(char *directory,char *field,void *dataToFillIn); static void StatsForCB(char *directory,char *field,void *dataToFillIn); scriptEntry GatherStatsScriptTable[] = { makeEntry(ForceTotalRefresh,scriptSetBool), {"StatsForRace", StatsForRaceCB, NULL}, {"StatsFor", StatsForCB, NULL}, endEntry }; static void StatsForRaceCB(char *directory,char *field,void *dataToFillIn) { char *param; ShipRace race; sbyte val; sdword i; param = strtok(field, ", \t"); dbgAssert(param); race = StrToShipRace(param); dbgAssert(race >= 0 && race < NUM_RACES_TO_GATHER_STATS_FOR); for (i=0;i= 0 && race < NUM_RACES_TO_GATHER_STATS_FOR); shiptype = StrToShipType(shipstr); dbgAssert(shiptype >= 0 && shiptype < TOTAL_NUM_SHIPS); val = FALSE; if (strncmp(truestr,"TRUE",4) == 0) { val = TRUE; } else if (strncmp(truestr,"REFRESH",7) == 0) { val = REFRESH; } RaceCalcFightStatsFor[race][shiptype - FirstShipTypeOfRace[race]] = val; } void statLog(char *format, ...) { char buffer[200]; va_list argList; va_start(argList, format); //get first arg vsprintf(buffer, format, argList); //prepare output string va_end(argList); dbgMessage("\nSTATLOG:"); logfileLog(STATLOG_FILENAME,buffer); dbgMessage(buffer); } sdword ConvertShipRaceTypeToStatIndex(ShipType shiptype,ShipRace shiprace) { sdword offset; if ((shiprace < 0) || (shiprace >= NUM_RACES_TO_GATHER_STATS_FOR)) { return -1; } if ((shiptype < 0) || (shiptype >= TOTAL_NUM_SHIPS)) { return -1; } offset = shiptype - FirstShipTypeOfRace[shiprace]; if ((offset < 0) || (offset >= NumShipTypesInRace[shiprace])) { return -1; } return (offset + StatIndexRaceOffsets[shiprace]); } void ConvertStatIndexToShipRaceType(sdword index,ShipType *shiptype,ShipRace *shiprace) { sdword j; sdword offset; for (j=NUM_RACES_TO_GATHER_STATS_FOR-1;j>=0;j--) { if (index >= StatIndexRaceOffsets[j]) { offset = index - StatIndexRaceOffsets[j]; dbgAssert(offset >= 0); dbgAssert(offset < NumShipTypesInRace[j]); *shiprace = j; *shiptype = (ShipType) (offset + FirstShipTypeOfRace[j]); return; } } dbgFatalf(DBG_Loc,"Bad stats index %d",index); } ShipStaticInfo *ConvertStatIndexToShipStatic(sdword i) { ShipType shiptype; ShipRace shiprace; ShipStaticInfo *shipstatic; ConvertStatIndexToShipRaceType(i,&shiptype,&shiprace); shipstatic = GetShipStaticInfo(shiptype,shiprace); if (bitTest(shipstatic->staticheader.infoFlags, IF_InfoLoaded)) { return shipstatic; } else { return NULL; } } #if 0 void GetPrecombatStats(FightStats *fightStats) { ShipStaticInfo *shipstatic0 = GetShipStaticInfoValidate(fightStats->shiptype[0],fightStats->shiprace[0]); ShipStaticInfo *shipstatic1 = GetShipStaticInfoValidate(fightStats->shiptype[1],fightStats->shiprace[1]); sdword RU0; sdword RU1; dbgAssert(shipstatic0->shiptype == fightStats->shiptype[0]); dbgAssert(shipstatic1->shiptype == fightStats->shiptype[1]); // Get # of ships for players 0 and 1 to fight RU0 = shipstatic0->buildCost; RU1 = shipstatic1->buildCost; if (RU0 == RU1) { fightStats->numShips[0] = (sword)MIN_SHIPS_TO_FIGHT; fightStats->numShips[1] = (sword)MIN_SHIPS_TO_FIGHT; } else { sdword maxRU; sdword minRU; sdword maxRUplayer; sdword numMinRUShips; sdword numMaxRUShips; if (RU0 > RU1) { maxRU = RU0; minRU = RU1; maxRUplayer = 0; } else { maxRU = RU1; minRU = RU0; maxRUplayer = 1; } numMinRUShips = maxRU / minRU; numMaxRUShips = 1; if (numMinRUShips < MIN_SHIPS_TO_FIGHT) { numMinRUShips = MIN_SHIPS_TO_FIGHT; numMaxRUShips = (numMinRUShips * minRU) / maxRU; } if (maxRUplayer == 0) { fightStats->numShips[0] = (sword)numMaxRUShips; fightStats->numShips[1] = (sword)numMinRUShips; } else { fightStats->numShips[0] = (sword)numMinRUShips; fightStats->numShips[1] = (sword)numMaxRUShips; } } if (fightStats->numShips[0] > cdLimitCaps[fightStats->shiptype[0]]) { fightStats->numShips[0] = cdLimitCaps[fightStats->shiptype[0]]; } if (fightStats->numShips[1] > cdLimitCaps[fightStats->shiptype[1]]) { fightStats->numShips[1] = cdLimitCaps[fightStats->shiptype[1]]; } dbgAssert(fightStats->numShips[0] > 0); dbgAssert(fightStats->numShips[1] > 0); dbgMessagef("\n%s %s Vs %s %s\n",ShipRaceToStr(fightStats->shiprace[0]),ShipTypeToStr(fightStats->shiptype[0]),ShipRaceToStr(fightStats->shiprace[1]),ShipTypeToStr(fightStats->shiptype[1])); dbgMessagef("\n%d %d",fightStats->numShips[0],fightStats->numShips[1]); #if 0 fightStats->numRUs[0] = fightStats->numShips[0] * RU0; fightStats->numRUs[1] = fightStats->numShips[1] * RU1; fightStats->totalHP[0] = fightStats->numShips[0] * shipstatic0->maxhealth; fightStats->totalHP[1] = fightStats->numShips[1] * shipstatic1->maxhealth; #endif } #endif void SetupShipsForFight(FightStats *fightStats) { sdword i,j; SelectCommand *selection[2]; sdword numShips; vector position[2]; real32 facerotation[2] = { DEG_TO_RAD(0.0f), DEG_TO_RAD(180.0f) }; Ship *ship; vecSet(position[0],0.0f,0.0f,0.0f); vecSet(position[1],FACEOFF_DISTANCE,0.0f,0.0f); unitCapDisable(); for (i=0;i<2;i++) { numShips = (sdword)fightStats->numShips[i]; dbgAssert(numShips > 0); selection[i] = memAlloc(sizeofSelectCommand(numShips),"fightselection",0); selection[i]->numShips = numShips; for (j=0;jShipPtr[j] = ship = univAddShip(fightStats->shiptype[i],fightStats->shiprace[i],&position[i],&universe.players[i],0); univRotateObjYaw((SpaceObjRot *)ship,facerotation[i]); } if (showStatsFancyFight) { if ((showStatsFightF[i] >= 0) && (showStatsFightF[i] < NO_FORMATION)) clFormation(&universe.mainCommandLayer,selection[i],showStatsFightF[i]); clSetTactics(&universe.mainCommandLayer,selection[i],showStatsFightT[i]); } else { clFormation(&universe.mainCommandLayer,selection[i],BROAD_FORMATION); } } clPresetShipsToPosition(&universe.mainCommandLayer); universeSwitchToPlayer(0); if (AreAllShipsAttackCapable(selection[0])) { attackcommands[0] = clAttackThese(&universe.mainCommandLayer,selection[0],(AttackCommand *)selection[1]); dbgAssert(attackcommands[0] != NULL); } else { statLog("Warning: Ships %s %s could not attack",ShipRaceToStr(fightStats->shiprace[0]),ShipTypeToStr(fightStats->shiptype[0])); } if (AreAllShipsAttackCapable(selection[1])) { attackcommands[1] = clAttackThese(&universe.mainCommandLayer,selection[1],(AttackCommand *)selection[0]); dbgAssert(attackcommands[1] != NULL); } else { statLog("Warning: Ships %s %s could not attack",ShipRaceToStr(fightStats->shiprace[1]),ShipTypeToStr(fightStats->shiptype[1])); } memFree(selection[0]); memFree(selection[1]); } bool AreShipsDoneFighting() { // keep going until an attack is finished if (!CommandInCommandLayer(&universe.mainCommandLayer,attackcommands[0])) { return TRUE; } if (!CommandInCommandLayer(&universe.mainCommandLayer,attackcommands[1])) { return TRUE; } // or the time expires if (universe.totaltimeelapsed >= STATFIGHT_TIMEOUT_TIME) { return TRUE; } return FALSE; } void HaveShipsFight() { universe.totaltimeelapsed = 0.0f; universe.univUpdateCounter = 0; for (;;) { univUpdate(UNIVERSE_UPDATE_PERIOD); if (AreShipsDoneFighting()) { break; } } } void CalculateRUKillRatios(FightStats *fightStats) { sdword i; ShipStaticInfo *shipstatic[2]; for (i=0;i<2;i++) { shipstatic[i] = GetShipStaticInfoValidate(fightStats->shiptype[i],fightStats->shiprace[i]); } fightStats->fracKillratio = ((real32)fightStats->numShips[1]) / ((real32)fightStats->numShips[0]); fightStats->fracRUratio = ((real32)(fightStats->numShips[1] * shipstatic[1]->buildCost)) / ((real32)(fightStats->numShips[0] * shipstatic[0]->buildCost)); statLog(" >%d Vs %d RUratio Killratio %3.3f %3.3f\n",fightStats->numShips[0],fightStats->numShips[1],fightStats->fracRUratio,fightStats->fracKillratio); } #if 0 void CalculateRatios(FightStats *fightStats) { sdword i; ShipStaticInfo *shipstatic[2]; sdword numShipsDied[2]; real32 numFracShipsDied[2]; for (i=0;i<2;i++) { shipstatic[i] = GetShipStaticInfoValidate(fightStats->shiptype[i],fightStats->shiprace[i]); fightStats->numFracShipsAfter[i] = ((real32)fightStats->totalHPAfter[i]) / shipstatic[i]->maxhealth; numShipsDied[i] = fightStats->numShips[i] - fightStats->numShipsAfter[i]; numFracShipsDied[i] = ((real32)fightStats->numShips[i]) - fightStats->numFracShipsAfter[i]; } #if 0 fightStats->RUratio = ((real32)(numShipsDied[1] * shipstatic[1]->buildCost)) / ((real32)(numShipsDied[0] * shipstatic[0]->buildCost)); fightStats->Killratio = ((real32)numShipsDied[1]) / ((real32)numShipsDied[0]); #endif if (numFracShipsDied[0] == 0.0f) { fightStats->fracRUratio = MAX_RU_RATIO; fightStats->fracKillratio = MAX_KILL_RATIO; } else { fightStats->fracRUratio = ((real32)(numFracShipsDied[1] * shipstatic[1]->buildCost)) / ((real32)(numFracShipsDied[0] * shipstatic[0]->buildCost)); fightStats->fracKillratio = numFracShipsDied[1] / numFracShipsDied[0]; } if (fightStats->fracRUratio > MAX_RU_RATIO) { fightStats->fracRUratio = MAX_RU_RATIO; } if (fightStats->fracKillratio > MAX_KILL_RATIO) { fightStats->fracKillratio = MAX_KILL_RATIO; } } #endif #if 0 void PrintFightStats(FightStats *fightStats) { filehandle statlogfileFH = fileOpen(STATLOG_FILENAME, FF_AppendMode | FF_TextMode); FILE *statlogfile; dbgAssert(!fileUsingBigfile(statlogfileFH)); statlogfile = fileStream(statlogfileFH); if (statlogfile) { fprintf(statlogfile,"STATS for %s %s Vs %s %s\n",ShipRaceToStr(fightStats->shiprace[0]),ShipTypeToStr(fightStats->shiptype[0]),ShipRaceToStr(fightStats->shiprace[1]),ShipTypeToStr(fightStats->shiptype[1])); fprintf(statlogfile,"Before: %3d %3d\n",fightStats->numShips[0],fightStats->numShips[1]); // fprintf(statlogfile,"After : %3d %3d\n",fightStats->numShipsAfter[0],fightStats->numShipsAfter[1]); fprintf(statlogfile,"After: %3.3f %3.3f\n",fightStats->numFracShipsAfter[0],fightStats->numFracShipsAfter[1]); // fprintf(statlogfile,"RU,Kill: %3.3f %3.3f\n",fightStats->RUratio,fightStats->Killratio); fprintf(statlogfile,"RU,Kill: %3.3f %3.3f %3.3f\n",fightStats->fracRUratio,fightStats->fracKillratio,fightStats->battleTime); fileClose(statlogfileFH); } } #endif void GetPostCombatStats(FightStats *fightStats) { Node *curnode = universe.mainCommandLayer.todolist.head; CommandToDo *command; sdword numCommands = universe.mainCommandLayer.todolist.num; SelectCommand *selection; sdword numShips; sdword i; uword playerindex; fightStats->numShipsAfter[0] = 0; fightStats->numShipsAfter[1] = 0; fightStats->totalHPAfter[0] = 0; fightStats->totalHPAfter[1] = 0; // Go through command layer, and see what ships are left: while (curnode != NULL) { command = (CommandToDo *)listGetStructOfNode(curnode); if ((command == attackcommands[0]) || (command == attackcommands[1])) { selection = command->selection; numShips = selection->numShips; dbgAssert(numShips > 0); playerindex = selection->ShipPtr[0]->playerowner->playerIndex; dbgAssert(playerindex < 2); fightStats->numShipsAfter[playerindex] = (uword)numShips; for (i=0;itotalHPAfter[playerindex] += selection->ShipPtr[i]->health; } } curnode = curnode->next; } fightStats->battleTime = universe.totaltimeelapsed; #if 0 CalculateRatios(fightStats); PrintFightStats(fightStats); #endif universeReset(); univupdateReset(); singlePlayerInit(); // for hyperspace // clReset(&universe.mainCommandLayer); // ccReset(&universe.mainCameraCommand); // univupdateCloseAllObjectsAndMissionSpheres(); // universe.totaltimeelapsed = 0.0f; // universe.univUpdateCounter = 0; } void GatherFightStatsFor(sdword i,sdword j,bool actuallyDoFight) { FightStats *fightStats = &FightStatsTable[i][j]; sdword *binsearchtable = NULL; sdword bottom,top; sdword trynum; sdword search,found,verytop; sdword powerfulship,nonpowerfulship; ShipType shiptype; ShipRace shiprace; ConvertStatIndexToShipRaceType(i,&shiptype,&shiprace); fightStats->shiptype[0] = shiptype; fightStats->shiprace[0] = shiprace; ConvertStatIndexToShipRaceType(j,&shiptype,&shiprace); fightStats->shiptype[1] = shiptype; fightStats->shiprace[1] = shiprace; if (!actuallyDoFight) { // later handle this case } statLog("STATS for %s %s Vs %s %s\n",ShipRaceToStr(fightStats->shiprace[0]),ShipTypeToStr(fightStats->shiptype[0]),ShipRaceToStr(fightStats->shiprace[1]),ShipTypeToStr(fightStats->shiptype[1])); // Have first fight 1 on 1, to determine who is more powerful: fightStats->numShips[0] = 1; fightStats->numShips[1] = 1; if (i == j) { CalculateRUKillRatios(fightStats); return; } SetupShipsForFight(fightStats); HaveShipsFight(); GetPostCombatStats(fightStats); if ((fightStats->numShipsAfter[0] | fightStats->numShipsAfter[1]) == 0) { // simultaneous kill: CalculateRUKillRatios(fightStats); statLog(" Simultaneous kill\n"); return; } if ((fightStats->numShipsAfter[0] == 1) && (fightStats->numShipsAfter[1] == 1)) { // neither ship could kill the other ship: fightStats->fracRUratio = 0.0f; fightStats->fracKillratio = 0.0f; statLog(" Stalemate\n"); return; } if (fightStats->numShipsAfter[0] == 1) { // ship 0 is more powerful real32 hpleftperc = ((real32)fightStats->totalHPAfter[0]) / ((real32)GetShipStaticInfoValidate(fightStats->shiptype[0],fightStats->shiprace[0])->maxhealth); if (hpleftperc < CONSIDER_SHIPS_EVEN_PERC_HP_LEFT) { // ship 0 isn't that much more powerful. Consider these ships even CalculateRUKillRatios(fightStats); statLog(" Ships roughly equal 0\n"); return; } powerfulship = 0; } else { // ship 1 is more powerful real32 hpleftperc = ((real32)fightStats->totalHPAfter[1]) / ((real32)GetShipStaticInfoValidate(fightStats->shiptype[1],fightStats->shiprace[1])->maxhealth); if (hpleftperc < CONSIDER_SHIPS_EVEN_PERC_HP_LEFT) { // ship 1 isn't that much more powerful. Consider these ships even CalculateRUKillRatios(fightStats); statLog(" Ships roughly equal 1\n"); return; } powerfulship = 1; } nonpowerfulship = 1-powerfulship; bottom = 1; top = cdLimitCaps[fightStats->shiptype[nonpowerfulship]]; if (top == -1) { top = cdClassCaps[GetShipStaticInfoValidate(fightStats->shiptype[nonpowerfulship],fightStats->shiprace[nonpowerfulship])->shipclass]; } if (top >= MAX_SHIPS_TO_EVER_CONSIDER) { top = MAX_SHIPS_TO_EVER_CONSIDER; } dbgAssert(top >= 1); verytop = top; binsearchtable = memAlloc(sizeof(udword)*(top+1),"binsearchtable",0); memset(binsearchtable,0,sizeof(udword)*(top+1)); while (top >= bottom) { trynum = (bottom+top)>>1; if (binsearchtable[trynum]) { statLog(" Cache hit\n"); // decision in cache if (binsearchtable[trynum] < 0) { // trynum is too small bottom = trynum+1; } else { // trynum is too big top = trynum-1; } } else { // decision not in cache, so execute fight fightStats->numShips[powerfulship] = 1; fightStats->numShips[nonpowerfulship] = (uword)trynum; statLog(" Binsearch %d %d\n",fightStats->numShips[0],fightStats->numShips[1]); SetupShipsForFight(fightStats); HaveShipsFight(); GetPostCombatStats(fightStats); if (fightStats->numShipsAfter[powerfulship] == 1) { // powerful ship hasn't been defeated. Need more ships. binsearchtable[trynum] = -1; // trynum is too small bottom = trynum+1; } else { binsearchtable[trynum] = 1; // trynum is too big top = trynum-1; } } } for (found=-1,search=0;search<=verytop;search++) { if (binsearchtable[search] > 0) { // lowest number of ships needed to kill found = search; break; } } memFree(binsearchtable); // at this point trynum contains # of ships needed to kill powerfulship fightStats->numShips[powerfulship] = 1; fightStats->numShips[nonpowerfulship] = (found >= 0) ? found : trynum; CalculateRUKillRatios(fightStats); } #define tswap(a,b,abtype) \ { \ abtype typetemp; \ typetemp = a; \ a = b; \ b = typetemp; \ } void ReciprocateFightStats(FightStats *fightStats) { tswap(fightStats->shiptype[0],fightStats->shiptype[1],ShipType) tswap(fightStats->shiprace[0],fightStats->shiprace[1],ShipRace) tswap(fightStats->numShips[0],fightStats->numShips[1],sword) tswap(fightStats->numShipsAfter[0],fightStats->numShipsAfter[1],sword) tswap(fightStats->totalHPAfter[0],fightStats->totalHPAfter[1],real32) // tswap(fightStats->numFracShipsAfter[0],fightStats->numFracShipsAfter[1],sword) if (fightStats->fracRUratio != 0.0f) { fightStats->fracRUratio = 1.0f / fightStats->fracRUratio; } if (fightStats->fracKillratio != 0.0f) { fightStats->fracKillratio = 1.0f / fightStats->fracKillratio; } } /*============================================================================= FancyFight Stuff =============================================================================*/ typedef struct { sdword repeat; sbyte statindex[2]; sbyte num[2]; TypeOfFormation formation[2]; TacticsType tactics[2]; } FancyFightEntry; #define MAX_NUM_FANCYFIGHTENTRYS 100 sdword numFancyFightEntrys = 0; FancyFightEntry fancyFightEntry[MAX_NUM_FANCYFIGHTENTRYS]; sdword currentFancyFightEntry = 0; sdword currentFancyFightRepeatFight = 0; bool ShowFancyFightsShipsFighting = FALSE; void SetFlightEntrysCB(char *directory,char *field,void *dataToFillIn); void SetFlightEntrysPreLoadCB(char *directory,char *field,void *dataToFillIn); void SetInfoNeededForShipAndRelatedStaticInfo(ShipType type,ShipRace race,bool8 dataToFillIn); void universeFlagNothingNeeded(void); scriptEntry FancyFightScriptTable[] = { makeEntry(ShowFancyFights,scriptSetBool), { "DoStatsFor", SetFlightEntrysCB, NULL }, endEntry }; scriptEntry FancyFightPreLoadScriptTable[] = { { "DoStatsFor", SetFlightEntrysPreLoadCB, NULL }, endEntry }; void SetFlightEntrysCB(char *directory,char *field,void *dataToFillIn) { sdword repeat; sdword num[2]; char racestr[2][20]; char shipstr[2][50]; char formationstr[2][50]; char tacticsstr[2][50]; RemoveCommasFromString(field); sscanf(field,"%d %d %s %s %s %s %d %s %s %s %s",&repeat,&num[0],racestr[0],shipstr[0],formationstr[0],tacticsstr[0], &num[1],racestr[1],shipstr[1],formationstr[1],tacticsstr[1]); dbgAssert(numFancyFightEntrys < MAX_NUM_FANCYFIGHTENTRYS); fancyFightEntry[numFancyFightEntrys].repeat = repeat; fancyFightEntry[numFancyFightEntrys].statindex[0] = ConvertShipRaceTypeToStatIndex(StrToShipType(shipstr[0]),StrToShipRace(racestr[0])); fancyFightEntry[numFancyFightEntrys].statindex[1] = ConvertShipRaceTypeToStatIndex(StrToShipType(shipstr[1]),StrToShipRace(racestr[1])); fancyFightEntry[numFancyFightEntrys].num[0] = num[0]; fancyFightEntry[numFancyFightEntrys].num[1] = num[1]; fancyFightEntry[numFancyFightEntrys].formation[0] = StrToTypeOfFormation(formationstr[0]); fancyFightEntry[numFancyFightEntrys].formation[1] = StrToTypeOfFormation(formationstr[1]); fancyFightEntry[numFancyFightEntrys].tactics[0] = StrToTacticsType(tacticsstr[0]); fancyFightEntry[numFancyFightEntrys].tactics[1] = StrToTacticsType(tacticsstr[1]); numFancyFightEntrys++; } void SetFlightEntrysPreLoadCB(char *directory,char *field,void *dataToFillIn) { sdword repeat; sdword num[2]; char racestr[2][20]; char shipstr[2][50]; char formationstr[2][50]; char tacticsstr[2][50]; RemoveCommasFromString(field); sscanf(field,"%d %d %s %s %s %s %d %s %s %s %s",&repeat,&num[0],racestr[0],shipstr[0],formationstr[0],tacticsstr[0], &num[1],racestr[1],shipstr[1],formationstr[1],tacticsstr[1]); SetInfoNeededForShipAndRelatedStaticInfo(StrToShipType(shipstr[0]),StrToShipRace(racestr[0]),TRUE); SetInfoNeededForShipAndRelatedStaticInfo(StrToShipType(shipstr[1]),StrToShipRace(racestr[1]),TRUE); } void FancyFightPreLoad(void) { universeFlagNothingNeeded(); scriptSet(NULL,showStatsFancyFightScriptFile,FancyFightPreLoadScriptTable); } void FancyFightPrepareFightStats(FightStats *fightStats) { FancyFightEntry *ffe = &fancyFightEntry[currentFancyFightEntry]; ShipType shiptype; ShipRace shiprace; sdword i = ffe->statindex[0]; sdword j = ffe->statindex[1]; dbgAssert(i >= 0); dbgAssert(i < NUM_SHIPS_TO_GATHER_STATS_FOR); dbgAssert(j >= 0); dbgAssert(j < NUM_SHIPS_TO_GATHER_STATS_FOR); ConvertStatIndexToShipRaceType(i,&shiptype,&shiprace); fightStats->shiptype[0] = shiptype; fightStats->shiprace[0] = shiprace; ConvertStatIndexToShipRaceType(j,&shiptype,&shiprace); fightStats->shiptype[1] = shiptype; fightStats->shiprace[1] = shiprace; fightStats->numShips[0] = ffe->num[0]; fightStats->numShips[1] = ffe->num[1]; showStatsFightF[0] = ffe->formation[0]; showStatsFightF[1] = ffe->formation[1]; showStatsFightT[0] = ffe->tactics[0]; showStatsFightT[1] = ffe->tactics[1]; } void PrintFancyFightStats(FightStats *fightStats) { statLog("STATS for %s %s Vs %s %s\n",ShipRaceToStr(fightStats->shiprace[0]),ShipTypeToStr(fightStats->shiptype[0]),ShipRaceToStr(fightStats->shiprace[1]),ShipTypeToStr(fightStats->shiptype[1])); statLog("Before: %3d %3d\n",fightStats->numShips[0],fightStats->numShips[1]); statLog("After : %3d %3d\n",fightStats->numShipsAfter[0],fightStats->numShipsAfter[1]); } void statsShowFancyFight(sdword i,sdword j) { FightStats fightStats; // initialize fancyfight stats ShowFancyFights = FALSE; // set defaults numFancyFightEntrys = 0; currentFancyFightEntry = 0; currentFancyFightRepeatFight = 0; scriptSet(NULL,showStatsFancyFightScriptFile,FancyFightScriptTable); logfileClear(STATLOG_FILENAME); statLog("GATHERING FANCYFIGHT STATS\n"); dbgAssert(universe.numPlayers >= 2); // need at least 2 players to gather stats if (!ShowFancyFights) { for (currentFancyFightEntry=0;currentFancyFightEntry= fancyFightEntry[currentFancyFightEntry].repeat) { currentFancyFightRepeatFight = 0; currentFancyFightEntry++; if (currentFancyFightEntry >= numFancyFightEntrys) { // all done! ShowFancyFights = FALSE; } } } } } void statsShowFight(sdword i,sdword j) { FightStats *fightStats; dbgAssert(i >= 0); dbgAssert(i < NUM_SHIPS_TO_GATHER_STATS_FOR); dbgAssert(j >= 0); dbgAssert(j < NUM_SHIPS_TO_GATHER_STATS_FOR); dbgAssert(universe.numPlayers >= 2); // need at least 2 players to gather stats fightStats = &FightStatsTable[i][j]; if ((fightStats->numShips[0] > 0) && (fightStats->numShips[1] > 0)) { SetupShipsForFight(fightStats); } else { fightStats->numShips[0] = 1; fightStats->numShips[1] = 1; SetupShipsForFight(fightStats); } } void CalculateOverallSums(void) { sdword i,j; for (i=0;i= 2); // need at least 2 players to gather stats logfileClear(STATLOG_FILENAME); if ((ForceTotalRefresh) || (statfilesize != fileSizeGet("statfile.bin",0))) { memset(&FightStatsTable[0][0],0,statfilesize); } else { fileLoad("statfile.bin",&FightStatsTable[0][0],0); RefreshClearFightStatsTable(); } for (racei=0;raceifracRUratio,fightStats->fracKillratio); } } } } } void statsPrintTable(void) { ShipRace racei,racej; filehandle tablefileFH = fileOpen("stattable.txt", FF_IgnoreBIG | FF_WriteMode | FF_TextMode); FILE *tablefile; dbgAssert(!fileUsingBigfile(tablefileFH)); tablefile = fileStream(tablefileFH); dbgAssert(tablefile); for (racei=0;raceishiptype,thisShip->shiprace); sdword j = ConvertShipRaceTypeToStatIndex(againstShip->shiptype,againstShip->shiprace); if ((i < 0) || (j < 0)) { return NULL; } return &FightStatsTable[i][j]; } real32 statsGetShipRURatingAgainstShip(ShipStaticInfo *thisShip,ShipStaticInfo *againstShip) { FightStats *fightStats = getFightStatsFromShipStatics(thisShip,againstShip); if (fightStats == NULL) { return 0.0f; } return fightStats->fracRUratio; } real32 statsGetShipKillRatingAgainstShip(ShipStaticInfo *thisShip,ShipStaticInfo *againstShip) { FightStats *fightStats = getFightStatsFromShipStatics(thisShip,againstShip); if (fightStats == NULL) { return 0.0f; } return fightStats->fracKillratio; } void statsSetOverkillfactor(real32 factor) { statsOverkillfactor = factor; statsOverkillfactorSqr = factor*factor; } real32 statsGetOverallRURating(ShipStaticInfo *shipstatic) { sdword i = ConvertShipRaceTypeToStatIndex(shipstatic->shiptype,shipstatic->shiprace); if (i < 0) { return 0.0f; } return FightStatsColumnSum[i].RUratio; } real32 statsGetOverallKillRating(ShipStaticInfo *shipstatic) { sdword i = ConvertShipRaceTypeToStatIndex(shipstatic->shiptype,shipstatic->shiprace); if (i < 0) { return 0.0f; } return FightStatsColumnSum[i].Killratio; } bool ShipConstraintsNoneCB(Ship *ship) { return TRUE; } bool statShipConstraintsNoneCB(ShipStaticInfo *shipstatic) { return TRUE; } bool statShipConstraintsFrigatesOrWorseCB(ShipStaticInfo *shipstatic) { if (shipstatic->shipclass >= CLASS_Frigate) { return TRUE; } else { return FALSE; } } bool statShipConstraintsFrigatesOrBetterCB(ShipStaticInfo *shipstatic) { if (shipstatic->shipclass <= CLASS_Frigate) { return TRUE; } else { return FALSE; } } /*----------------------------------------------------------------------------- Name : statShipConstraintsFightingShipsCB Description : Returns true if the staticinfo passed is a ship that can be considered a "fighting ship" (i.e. has guns, can attack effectively, etc.) Inputs : shipstatic - the staticinfo to check Outputs : Return : TRUE if the ship is a fighting ship ----------------------------------------------------------------------------*/ bool statShipConstraintsFightingShipsCB(ShipStaticInfo *shipstatic) { switch (shipstatic->shipclass) { case CLASS_Mothership: case CLASS_Carrier: return FALSE; case CLASS_HeavyCruiser: case CLASS_Destroyer: return TRUE; break; case CLASS_Frigate: if ((shipstatic->shiptype == ResourceController) || (shipstatic->shiptype == DFGFrigate) || (shipstatic->shiptype == AdvanceSupportFrigate)) { return FALSE; } else { return TRUE; // all other Frigates are considered fighting ships } break; case CLASS_Corvette: if ((shipstatic->shiptype == RepairCorvette) || (shipstatic->shiptype == SalCapCorvette)) { return FALSE; //salcap corvette doesn't attack in a conventional manner } else { return TRUE; } case CLASS_Fighter: if (shipstatic->shiptype == DefenseFighter) { return FALSE; } else { return TRUE; } case CLASS_Resource: case CLASS_NonCombat: default: return FALSE; } return FALSE; } /*----------------------------------------------------------------------------- Name : statShipConstraintsCarrierFightingShipsCB Description : Returns true if the staticinfo passed is a ship that can be considered a "fighting ship" (i.e. has guns, can attack effectively, etc.) and that can be built by a Carrier Inputs : shipstatic - the staticinfo to check Outputs : Return : TRUE if the ship is a fighting ship ----------------------------------------------------------------------------*/ bool statShipConstraintsCarrierFightingShipsCB(ShipStaticInfo *shipstatic) { switch (shipstatic->shipclass) { case CLASS_Mothership: case CLASS_Carrier: case CLASS_HeavyCruiser: case CLASS_Destroyer: return FALSE; break; case CLASS_Frigate: if ((shipstatic->shiptype == ResourceController) || (shipstatic->shiptype == DFGFrigate) || (shipstatic->shiptype == AdvanceSupportFrigate)) { return FALSE; } else { return TRUE; // all other Frigates are considered fighting ships } break; case CLASS_Corvette: if ((shipstatic->shiptype == RepairCorvette) || (shipstatic->shiptype == SalCapCorvette)) { return FALSE; //salcap corvette doesn't attack in a conventional manner } else { return TRUE; } case CLASS_Fighter: if (shipstatic->shiptype == DefenseFighter) { return FALSE; } else { return TRUE; } case CLASS_Resource: case CLASS_NonCombat: default: return FALSE; } return FALSE; } real32 statsGetKillRatingAgainstFleet(ShipStaticInfo *shipstatic,SelectCommand *fleet) { sdword numShips = fleet->numShips; sdword i; real32 totalkills = 0.0f; dbgAssert(numShips > 0); // kill rating against fleet is reciprical of (sum of recipricals of kill ratings) for (i=0;iShipPtr[i]->staticinfo,shipstatic); } return (1.0f / totalkills); } real32 statsGetKillRatingAgainstFleetStatic(ShipStaticInfo *shipstatic,SelectCommandStatic *fleetStatic) { sdword numShips = fleetStatic->numShips; sdword i; real32 totalkills = 0; dbgAssert(numShips > 0); for (i=0;iShipStaticPtr[i],shipstatic); } return (1.0f / totalkills); } real32 statsGetRURatingAgainstFleet(ShipStaticInfo *shipstatic,SelectCommand *fleet) { sdword numShips = fleet->numShips; sdword i; real32 totalrurating = 0; dbgAssert(numShips > 0); for (i=0;iShipPtr[i]->staticinfo,shipstatic); } return (1.0f / totalrurating); } real32 statsGetRURatingAgainstFleetStatic(ShipStaticInfo *shipstatic,SelectCommandStatic *fleetStatic) { sdword numShips = fleetStatic->numShips; sdword i; real32 totalrurating = 0; dbgAssert(numShips > 0); for (i=0;iShipStaticPtr[i],shipstatic); } return (1.0f / totalrurating); } Ship *statsGetMostDangerousShipNonStatConstraints(SelectCommand *selection,ShipConstraintsCB constraintsCB) { sdword numShips = selection->numShips; sdword i; Ship *ship; Ship *maxship = NULL; real32 maxKillratio = -1.0f; real32 Killratio; for (i=0;iShipPtr[i]; dbgAssert(ship->objtype == OBJ_ShipType); if (constraintsCB(ship)) { Killratio = statsGetOverallKillRating(ship->staticinfo); if (Killratio > maxKillratio) { maxKillratio = Killratio; maxship = ship; } } } return maxship; } ShipStaticInfo *statsGetMostDangerousShipStaticConstraints(SelectCommandStatic *selection,statShipConstraintsCB constraintsCB) { sdword numShips = selection->numShips; sdword i; ShipStaticInfo *shipstatic; ShipStaticInfo *maxshipstatic = NULL; real32 maxKillratio = -1.0f; real32 Killratio; for (i=0;iShipStaticPtr[i]; if (constraintsCB(shipstatic)) { Killratio = statsGetOverallKillRating(shipstatic); if (Killratio > maxKillratio) { maxKillratio = Killratio; maxshipstatic = shipstatic; } } } return maxshipstatic; } ShipStaticInfo *statsBestShipToBuyToKillShip(ShipRace shipRace,statShipConstraintsCB constraintsCB,ShipStaticInfo *targetstatic) { sdword i; ShipStaticInfo *shipstatic; ShipStaticInfo *maxshipstatic = NULL; real32 maxRUratio = -1.0f; real32 RUratio; sdword raceiNumShips = NumShipTypesInRace[shipRace]; sbyte *raceiCalcFightStatsFor = RaceCalcFightStatsFor[shipRace]; sdword raceiStatIndexOffset = StatIndexRaceOffsets[shipRace]; sdword indexi; for (i=0;ishiprace == shipRace); RUratio = statsGetShipRURatingAgainstShip(shipstatic,targetstatic); if (RUratio > maxRUratio) { maxRUratio = RUratio; maxshipstatic = shipstatic; } } } } return maxshipstatic; } ShipStaticInfo *statsBestShipToBuyToKillFleet(ShipRace shipRace,statShipConstraintsCB constraintsCB,SelectCommand *targetFleet) { sdword i; sdword indexi; ShipStaticInfo *shipstatic; ShipStaticInfo *maxshipstatic = NULL; real32 maxRUratio = -1.0f; real32 RUratio; sdword raceiNumShips = NumShipTypesInRace[shipRace]; sbyte *raceiCalcFightStatsFor = RaceCalcFightStatsFor[shipRace]; sdword raceiStatIndexOffset = StatIndexRaceOffsets[shipRace]; for (i=0;ishiprace == shipRace); RUratio = statsGetRURatingAgainstFleet(shipstatic,targetFleet); if (RUratio > maxRUratio) { maxRUratio = RUratio; maxshipstatic = shipstatic; } } } } return maxshipstatic; } ShipStaticInfo *statsBestShipToBuyToKillFleetStatic(ShipRace shipRace,statShipConstraintsCB constraintsCB,SelectCommandStatic *targetFleetStatic) { sdword i; ShipStaticInfo *shipstatic; ShipStaticInfo *maxshipstatic = NULL; real32 maxRUratio = -1.0f; real32 RUratio; sdword raceiNumShips = NumShipTypesInRace[shipRace]; sbyte *raceiCalcFightStatsFor = RaceCalcFightStatsFor[shipRace]; sdword raceiStatIndexOffset = StatIndexRaceOffsets[shipRace]; sdword indexi; for (i=0;i maxRUratio) { maxRUratio = RUratio; maxshipstatic = shipstatic; } } } } return maxshipstatic; } Ship *statsBestShipToUseToKillShip(SelectCommand *freeShips,ShipStaticInfo *targetstatic) { sdword i; Ship *ship; Ship *maxship = NULL; real32 maxKillratio = -1.0f; real32 Killratio; sdword numShips = freeShips->numShips; for (i=0;iShipPtr[i]; dbgAssert(ship->objtype == OBJ_ShipType); Killratio = statsGetShipKillRatingAgainstShip(ship->staticinfo,targetstatic); if (Killratio > maxKillratio) { maxKillratio = Killratio; maxship = ship; } } return maxship; } Ship *statsBestShipToUseToKillFleet(SelectCommand *freeShips,SelectCommand *targetFleet) { sdword i; Ship *ship; Ship *maxship = NULL; real32 maxKillratio = -1.0f; real32 Killratio; sdword numShips = freeShips->numShips; for (i=0;iShipPtr[i]; dbgAssert(ship->objtype == OBJ_ShipType); Killratio = statsGetKillRatingAgainstFleet(ship->staticinfo,targetFleet); if (Killratio > maxKillratio) { maxKillratio = Killratio; maxship = ship; } } return maxship; } sdword statsNumShipsNeededToKillTarget(ShipStaticInfo *shipstatic,ShipStaticInfo *targetstatic) { real32 killratio = statsGetShipKillRatingAgainstShip(shipstatic,targetstatic); real32 num; if (killratio >= 1) { num = 1.0f; // can kill >= 1 target with just 1 ship, so we only need 1 } else { num = 1.0f / killratio; } return ((sdword)(num * statsOverkillfactor)); } sdword statsNumShipsNeededToKillFleet(ShipStaticInfo *shipstatic,SelectCommand *targetFleet) { real32 killratio = statsGetKillRatingAgainstFleet(shipstatic,targetFleet); real32 num; if (killratio == 0.0f) { return 0; } else if (killratio >= 1) { num = 1.0f; // can kill >= 1 target with just 1 ship, so we only need 1 } else { num = 1.0f / killratio; } return (((sdword)(num * statsOverkillfactor))); } sdword statsNumShipsNeededToKillFleetStatic(ShipStaticInfo *shipstatic,SelectCommandStatic *targetFleetStatic) { real32 killratio = statsGetKillRatingAgainstFleetStatic(shipstatic,targetFleetStatic); real32 num; if (killratio == 0.0f) { return 0; } else if (killratio >= 1) { num = 1.0f; // can kill >= 1 target with just 1 ship, so we only need 1 } else { num = 1.0f / killratio; } return (((sdword)(num * statsOverkillfactor))); } // strength of fleet2 against fleet1, e.g. fleet2/fleet1 real32 statsGetRelativeFleetStrengths(SelectCommand *fleet1,SelectCommand *fleet2) { sdword numShips1 = fleet1->numShips; sdword numShips2 = fleet2->numShips; sdword j; real32 totalstr1 = 0.0f; real32 totalstr2 = 0.0f; dbgAssert(numShips1 > 0); dbgAssert(numShips2 > 0); #if 0 for (i=0;iShipPtr[i]->staticinfo,fleet2); } #endif for (j=0;jShipPtr[j]->staticinfo,fleet1); } return totalstr2; } // strength of ship against fleet strength, e.g. targetstatic strength / fleet1 strength real32 statsGetRelativeFleetStrengthAgainstShip(SelectCommand *fleet1,ShipStaticInfo *targetstatic) { sdword numShips1 = fleet1->numShips; real32 totalstr1 = 0.0f; real32 totalstr2; dbgAssert(numShips1 > 0); #if 0 for (i=0;iShipPtr[i]->staticinfo,targetstatic); } #endif totalstr2 = statsGetKillRatingAgainstFleet(targetstatic,fleet1); return totalstr2; } SelectCommand *statsBestShipsToUseToKillTarget(SelectCommand *freeships,ShipStaticInfo *targetstatic,bool *goodEnough) { SelectCommand *freeshipsleft; SelectCommand *useships; Ship *bestship; Ship *ship; sdword num; real32 fleetstr; sdword i; if (freeships->numShips == 0) { *goodEnough = FALSE; return NULL; } freeshipsleft = selectDupSelection(freeships); useships = memAlloc(sizeofSelectCommand(freeships->numShips),"statsuseships",0); useships->numShips = 0; repeat: bestship = statsBestShipToUseToKillShip(freeshipsleft,targetstatic); num = statsNumShipsNeededToKillTarget(bestship->staticinfo,targetstatic); clRemoveShipFromSelection(freeshipsleft,bestship); useships->ShipPtr[useships->numShips++] = bestship; num--; while (num > 0) { // find another ship of type bestship->staticinfo for (i=0;inumShips;i++) { ship = freeshipsleft->ShipPtr[i]; if (ship->staticinfo == bestship->staticinfo) { // found it, remove it from freeshipsleft freeshipsleft->numShips--; freeshipsleft->ShipPtr[i] = freeshipsleft->ShipPtr[freeshipsleft->numShips]; // add it to useships useships->ShipPtr[useships->numShips++] = ship; goto foundit; } } // didn't find any more ships, so break out of loop break; foundit: num--; } // now check fleet strengths: // want useships strength relative to targetstatic fleetstr = 1.0f / statsGetRelativeFleetStrengthAgainstShip(useships,targetstatic); if (fleetstr < statsOverkillfactor) { // not good enough, so lets exit and not repeat if we don't have any free ships if (freeshipsleft->numShips == 0) { // use all ships dbgAssert(useships->numShips == freeships->numShips); memFree(freeshipsleft); *goodEnough = FALSE; return useships; } goto repeat; } memFree(freeshipsleft); *goodEnough = TRUE; return useships; } SelectCommand *statsBestShipsToUseToKillFleet(SelectCommand *freeships,SelectCommand *targetFleet,bool *goodEnough) { SelectCommand *freeshipsleft; SelectCommand *useships; Ship *bestship; Ship *ship; sdword num; real32 fleetstr; sdword i; if (freeships->numShips == 0) { *goodEnough = FALSE; return NULL; } freeshipsleft = selectDupSelection(freeships); useships = memAlloc(sizeofSelectCommand(freeships->numShips),"statsuseships",0); useships->numShips = 0; repeat: bestship = statsBestShipToUseToKillFleet(freeshipsleft,targetFleet); num = statsNumShipsNeededToKillFleet(bestship->staticinfo,targetFleet); clRemoveShipFromSelection(freeshipsleft,bestship); useships->ShipPtr[useships->numShips++] = bestship; num--; while (num > 0) { // find another ship of type bestship->staticinfo for (i=0;inumShips;i++) { ship = freeshipsleft->ShipPtr[i]; if (ship->staticinfo == bestship->staticinfo) { // found it, remove it from freeshipsleft freeshipsleft->numShips--; freeshipsleft->ShipPtr[i] = freeshipsleft->ShipPtr[freeshipsleft->numShips]; // add it to useships useships->ShipPtr[useships->numShips++] = ship; goto foundit; } } // didn't find any more ships, so break out of loop break; foundit: num--; } // now check fleet strengths: // want useships strength relative to targetFleet fleetstr = 1.0f / statsGetRelativeFleetStrengths(useships,targetFleet); if (fleetstr < statsOverkillfactor) { // not good enough, so lets exit and not repeat if we don't have any free ships if (freeshipsleft->numShips == 0) { // use all ships dbgAssert(useships->numShips == freeships->numShips); memFree(freeshipsleft); *goodEnough = FALSE; return useships; } goto repeat; } memFree(freeshipsleft); *goodEnough = TRUE; return useships; } SelectCommand *statsGetMostEfficientShipsToKill(ShipStaticInfo *killer,SelectCommand *fleet,real32 efficiencyFactor) { SelectCommand *returnsel; sdword i; if (fleet->numShips <= 0) { return NULL; } returnsel = selectDupSelection(fleet); for (i=0;inumShips; ) { if (statsGetShipRURatingAgainstShip(killer,returnsel->ShipPtr[i]->staticinfo) <= efficiencyFactor) { // killer ship sucks against this ship, so let's remove it returnsel->numShips--; returnsel->ShipPtr[i] = returnsel->ShipPtr[returnsel->numShips]; continue; // we must check same index again because we put last array entry here } i++; } if (returnsel->numShips == 0) { memFree(returnsel); return NULL; } return returnsel; } SelectCommand *CreateTestSelectionOfShips(ShipRace shiprace,ShipType shiptype,sdword numShips) { SelectCommand *selection; vector position = { 0.0f, 0.0f, 0.0f }; sdword i; selection = memAlloc(sizeofSelectCommand(numShips),"createshipsel",0); selection->numShips = numShips; for (i=0;iShipPtr[i] = univCreateShip(shiptype,shiprace,&position,&universe.players[0],0); } return selection; } void FreeTestSelectionOfShips(SelectCommand *selection) { sdword numShips = selection->numShips; sdword i; for (i=0;iShipPtr[i]); memFree(selection->ShipPtr[i]); } memFree(selection); } static void statsTest(void) { #ifndef HW_Release #ifdef gshaw #if 0 ShipType shiptype1,shiptype2; ShipRace shiprace1,shiprace2; ShipStaticInfo *shipstatic1,*shipstatic2; real32 killratio; real32 ruratio; sdword num; SelectCommand *selection1,*selection2,*selection3,*selection4,*selection5; SelectCommand *fleet1,*fleet2,*fleet3; real32 ratio1,ratio2,ratio3,ratio4; SelectCommand *bestsel; sdword numneeded; Ship *ship; bool goodEnough; statsSetOverkillfactor(1.2f); shiptype2 = IonCannonFrigate; shiprace2 = R1; shipstatic2 = GetShipStaticInfoValidate(shiptype2,shiprace2); shiptype1 = StandardFrigate; shiprace1 = R1; shipstatic1 = GetShipStaticInfoValidate(shiptype1,shiprace1); killratio = statsGetShipRURatingAgainstShip(shipstatic1,shipstatic2); ruratio = statsGetShipKillRatingAgainstShip(shipstatic1,shipstatic2); shiptype1 = StandardFrigate; shiprace1 = R1; shipstatic1 = GetShipStaticInfoValidate(shiptype1,shiprace1); killratio = statsGetOverallKillRating(shipstatic1); ruratio = statsGetOverallRURating(shipstatic1); shiptype1 = StandardFrigate; shiprace1 = R1; shipstatic1 = GetShipStaticInfoValidate(shiptype1,shiprace1); shipstatic2 = statsBestShipToBuyToKillShip(R1,statShipConstraintsNoneCB,shipstatic1); shipstatic2 = statsBestShipToBuyToKillShip(R1,statShipConstraintsFrigatesOrWorseCB,shipstatic1); num = statsNumShipsNeededToKillTarget(shipstatic2,shipstatic1); if (!multiPlayerGame) // game will get out of sync due to test ship creation { selection1 = CreateTestSelectionOfShips(R1,HeavyDefender,10); selection2 = CreateTestSelectionOfShips(R1,LightCorvette,5); selection3 = CreateTestSelectionOfShips(R1,IonCannonFrigate,1); selection4 = CreateTestSelectionOfShips(R1,HeavyDefender,10); selection5 = CreateTestSelectionOfShips(R1,LightInterceptor,22); fleet1 = selectMergeTwoSelections(selection1,selection2, NO_DEALLOC); fleet2 = selectMergeTwoSelections(selection3,selection4, NO_DEALLOC); ratio2 = statsGetRelativeFleetStrengths(selection2,selection3); ratio3 = statsGetRelativeFleetStrengths(selection2,selection4); ratio4 = statsGetRelativeFleetStrengths(selection1,selection4); ratio1 = statsGetRelativeFleetStrengths(fleet1,fleet2); bestsel = statsBestShipsToUseToKillTarget(fleet1,shipstatic1,&goodEnough); memFree(bestsel); shipstatic1 = statsBestShipToBuyToKillFleet(R1,statShipConstraintsFrigatesOrWorseCB,fleet2); numneeded = statsNumShipsNeededToKillFleet(shipstatic1,fleet2); ratio1 = statsGetRelativeFleetStrengths(fleet2,selection5); ship = statsGetMostDangerousShip(fleet2); fleet3 = selectMergeTwoSelections(fleet1,selection5, NO_DEALLOC); bestsel = statsBestShipsToUseToKillFleet(fleet3,fleet2,&goodEnough); memFree(bestsel); ratio1 = statsGetRelativeFleetStrengths(fleet3,fleet2); FreeTestSelectionOfShips(selection1); FreeTestSelectionOfShips(selection2); FreeTestSelectionOfShips(selection3); FreeTestSelectionOfShips(selection4); FreeTestSelectionOfShips(selection5); memFree(fleet1); memFree(fleet2); memFree(fleet3); } #endif #endif #endif } /*============================================================================= Cheat detection: =============================================================================*/ /*----------------------------------------------------------------------------- Name : statsGetStatChecksum Description : Returns a checksum of the game statistics Inputs : Outputs : Return : The checksum of the game statistics ----------------------------------------------------------------------------*/ udword statsGetStatChecksum(void) { udword i, intCheck; udword shiptotals,classtotals; for (i=0, intCheck = 0;i