void SortTable (SItemTableCtx &Ctx, const TArray<CItemType *> &List, SItemTypeList *retSorted) { int i; // Loop over all items that match and add them to // a sorted table. retSorted->DeleteAll(); for (i = 0; i < List.GetCount(); i++) { CItemType *pType = List[i]; // Add with sort key char szBuffer[1024]; wsprintf(szBuffer, "%02d%s%02d%s", pType->GetLevel(), g_szTypeCode[GetItemType(pType)], GetItemFreq(pType), pType->GetNounPhrase().GetASCIIZPointer()); retSorted->Insert(CString(szBuffer), pType); } }
bool CWeaponBenchmarkCtx::InitArmorTable (void) // InitArmorTable // // Initializes the armor table, which lists armor by level. { int i; // If we've already initialized the table, then nothing to do. if (m_ArmorTable.GetCount() != 0) return true; // Create table for (i = 0; i < g_pUniverse->GetItemTypeCount(); i++) { CItemType *pType = g_pUniverse->GetItemType(i); // Skip non-armor if (!pType->IsArmor() || pType->IsVirtual()) continue; // Add to our list, indexed by level. TArray<CItemType *> *pTable = m_ArmorTable.SetAt(pType->GetLevel()); pTable->Insert(pType); } // Done return true; }
void GenerateArmorTable (CUniverse &Universe, CXMLElement *pCmdLine) { int i; printf("ARMOR TABLE\n\n"); printf("Level\tArmor\tCost\tWeight\tHP\tLasr\tImpc\tPart\tBlst\tIon\tThrm\tPosi\tPlsm\tAnti\tNano\tGrav\tSing\tDacd\tDstl\tDlgt\tDfir\n"); for (i = 0; i < Universe.GetItemTypeCount(); i++) { CItemType *pItem = Universe.GetItemType(i); CArmorClass *pArmor = pItem->GetArmorClass(); if (pArmor == NULL) continue; CItem Item(pItem, 1); CString sName = pItem->GetName(NULL); int iHP = pArmor->GetMaxHP(CItemCtx(&Item)); printf("%d\t%s\t%d\t%d\t%d\t", pItem->GetLevel(), sName.GetASCIIZPointer(), Item.GetValue(true), Item.GetMassKg(), iHP); // For each damage type, compute the adjusted hit points of the armor int iDamage; for (iDamage = damageLaser; iDamage < damageCount; iDamage++) { printf("%d", pArmor->GetDamageAdj(CItemCtx(Item), (DamageTypes)iDamage)); if (iDamage != damageCount - 1) printf("\t"); } printf("\n"); } printf("ARMOR TABLE DAMAGE ADJUSTMENTS\n\n"); printf("Level\tArmor\tHP\tLasr\tImpc\tPart\tBlst\tIon\tThrm\tPosi\tPlsm\tAnti\tNano\tGrav\tSing\tDacd\tDstl\tDlgt\tDfir\n"); for (i = 0; i < Universe.GetItemTypeCount(); i++) { CItemType *pItem = Universe.GetItemType(i); CArmorClass *pArmor = pItem->GetArmorClass(); if (pArmor == NULL) continue; CString sName = pItem->GetName(NULL); CItem Item(pItem, 1); int iHP = pArmor->GetMaxHP(CItemCtx(&Item)); printf("%d\t%s\t%d\t", pItem->GetLevel(), sName.GetASCIIZPointer(), iHP); // For each damage type, compute the adjusted hit points of the armor int iDamage; for (iDamage = damageLaser; iDamage < damageCount; iDamage++) { int iAdj = pArmor->GetDamageAdj(CItemCtx(Item), (DamageTypes)iDamage); if (iAdj == 0) printf("----"); else printf("%d", iHP * 100 / iAdj); if (iDamage != damageCount - 1) printf("\t"); } printf("\n"); } }
bool CSpaceObject::GetRefuelItemAndPrice (CSpaceObject *pObjToRefuel, CItemType **retpItemType, int *retiPrice) // GetRefuelItemAndPrice // // Returns the appropriate item to use for refueling (based on the trading // directives). { int i; if (IsAbandoned()) return false; // See if we have an override CTradingDesc *pTradeOverride = GetTradeDescOverride(); if (pTradeOverride && pTradeOverride->GetRefuelItemAndPrice(this, pObjToRefuel, 0, retpItemType, retiPrice)) return true; // Otherwise, ask our design type CDesignType *pType = GetType(); if (pType == NULL) return false; CTradingDesc *pTrade = pType->GetTradingDesc(); if (pTrade && pTrade->GetRefuelItemAndPrice(this, pObjToRefuel, 0, retpItemType, retiPrice)) return true; // For compatibility, any ship prior to version 23 has a default. // [For API Version 23 and above, ships must have a <Trade> descriptor.] if (pType->GetAPIVersion() < 23 && pType->GetType() == designShipClass) { // Get the ship CShip *pShipToRefuel = pObjToRefuel->AsShip(); if (pShipToRefuel == NULL) return false; // Find the highest-level item that can be used by the ship int iBestLevel = 0; int iBestPrice = 0; CItemType *pBestItem = NULL; for (i = 0; i < g_pUniverse->GetItemTypeCount(); i++) { CItemType *pType = g_pUniverse->GetItemType(i); CItem Item(pType, 1); if (pShipToRefuel->IsFuelCompatible(Item)) { if (pBestItem == NULL || pType->GetLevel() > iBestPrice) { // Compute the price, because if we don't sell it, then we // skip it. // // NOTE: Unlike selling, we allow 0 prices because some // stations give fuel for free. int iPrice = CTradingDesc::CalcPriceForService(serviceRefuel, this, Item, 1, 0); if (iPrice >= 0) { pBestItem = pType; iBestLevel = pType->GetLevel(); iBestPrice = iPrice; } } } } if (pBestItem == NULL) return false; // Done if (retpItemType) *retpItemType = pBestItem; if (retiPrice) *retiPrice = iBestPrice; return true; } // Otherwise, we do not refuel return false; }
void GenerateShieldStats (CUniverse &Universe, CXMLElement *pCmdLine) { int i; CString sUNID = pCmdLine->GetAttribute(CONSTLIT("unid")); DWORD dwUNID = strToInt(sUNID, 0, NULL); CItemType *pItem = Universe.FindItemType(dwUNID); if (pItem == NULL) { CItemCriteria Crit; CItem::InitCriteriaAll(&Crit); CItem Item = CItem::CreateItemByName(sUNID, Crit); pItem = Item.GetType(); if (pItem == NULL) { printf("ERROR: Unknown item '%s'\n", sUNID.GetASCIIZPointer()); return; } } if (pItem->GetCategory() != itemcatShields) { printf("ERROR: Item '%s' is not a shield generator\n", pItem->GetNounPhrase().GetASCIIZPointer()); return; } bool bVerbose = pCmdLine->GetAttributeBool(CONSTLIT("verbose")); bool bEval = pCmdLine->GetAttributeBool(CONSTLIT("eval")); // Get the stats for the shield Metric rHP = (Metric)pItem->GetDataFieldInteger(FIELD_HP); Metric rHPRegenPerTick = pItem->GetDataFieldInteger(FIELD_REGEN) / 1000.0; int iDamageAdj[damageCount]; CString sDamageAdj = pItem->GetDataField(CONSTLIT("damageAdj")); char *pPos = sDamageAdj.GetASCIIZPointer(); int iCount = 0; while (iCount < damageCount) { iDamageAdj[iCount] = strParseInt(pPos, 0, &pPos, NULL); if (*pPos != '\0') pPos++; iCount++; } // Print header printf("%s\n\n", pItem->GetNounPhrase().GetASCIIZPointer()); // Loop over all weapons and sort them by level and then name CSymbolTable List(FALSE, TRUE); for (i = 0; i < Universe.GetItemTypeCount(); i++) { CItemType *pWeapon = Universe.GetItemType(i); if (pWeapon->GetCategory() != itemcatWeapon) continue; CString sLevel = (pWeapon->GetLevel() < 10 ? strPatternSubst(CONSTLIT("0%d"), pWeapon->GetLevel()) : strFromInt(pWeapon->GetLevel(), FALSE)); CString sSortName = strPatternSubst(CONSTLIT("%s%s"), sLevel, pWeapon->GetNounPhrase()); List.AddEntry(sSortName, (CObject *)pWeapon); } // Loop over sorted list and output data for (i = 0; i < List.GetCount(); i++) { CItemType *pWeapon = (CItemType *)List.GetValue(i); // Get the data for the weapon int iFireDelay = pWeapon->GetDataFieldInteger(CONSTLIT("fireDelay")); Metric rAverageDamage = pWeapon->GetDataFieldInteger(CONSTLIT("averageDamage")) / 1000.0; int iDamageType = pWeapon->GetDataFieldInteger(CONSTLIT("damageType")); if (iDamageType < 0 || iDamageType >= damageCount) iDamageType = 0; // Adjust damage for type rAverageDamage = rAverageDamage * (iDamageAdj[iDamageType] / 100.0); if (rAverageDamage < 1.0) rAverageDamage = 0.0; // Calculate how many shots it would take to pierce through the shields char szBuffer[256]; Metric rShotsToDeplete; Metric rRegenPerShot = rHPRegenPerTick * (Metric)iFireDelay; if (rRegenPerShot >= rAverageDamage) { rShotsToDeplete = 1000000.0; lstrcpy(szBuffer, "ineffective"); } else { Metric rDrainPerShot = rAverageDamage - rRegenPerShot; rShotsToDeplete = rHP / rDrainPerShot; sprintf(szBuffer, "%.2f", rShotsToDeplete); } // See if this weapon is overpowered or underpowered char szEval[256]; if (bEval) { lstrcpy(szEval, "\t"); if (pWeapon->GetLevel() < pItem->GetLevel()) { if (rShotsToDeplete <= 10.0) lstrcpy(szEval, "\tOVERpowered"); } else { if (rShotsToDeplete > 20.0) lstrcpy(szEval, "\tUNDERpowered"); } } else lstrcpy(szEval, ""); // Print table if (bVerbose) { printf("%s\t%s\t%s\t(%d ticks; %.2f damage; %.2f regen/shot)%s\n", RomanNumeral(pWeapon->GetLevel()), pWeapon->GetNounPhrase().GetASCIIZPointer(), szBuffer, iFireDelay, rAverageDamage, rRegenPerShot, szEval); } else { printf("%s\t%s\t%s%s\n", RomanNumeral(pWeapon->GetLevel()), pWeapon->GetNounPhrase().GetASCIIZPointer(), szBuffer, szEval); } } }
void CRandomItems::InitTable (void) // InitTable // // Initializes the m_Table array. // // We assume that m_Criteria, m_sLevelFrequency, m_iLevel, and m_iLevelCurve are properly initialized. { int i; // Start by allocating an array large enough to hold // all item types in the universe ItemEntryStruct *pTable = new ItemEntryStruct [g_pUniverse->GetItemTypeCount()]; int iTableSize = 0; // Figure out if we should use level curves or level frequency bool bUseLevelFrequency = !m_sLevelFrequency.IsBlank(); // Iterate over every item type and add it to the table if // it matches the given criteria for (i = 0; i < g_pUniverse->GetItemTypeCount(); i++) { CItemType *pType = g_pUniverse->GetItemType(i); CItem Item(pType, 1); // Skip if the item doesn't match the categories if (!Item.MatchesCriteria(m_Criteria)) continue; // Skip if this item is not found randomly if (pType->GetFrequency() == ftNotRandom) continue; // Adjust score based on level, either the level curve // or the level frequency string. int iScore; if (bUseLevelFrequency) { iScore = 1000 * GetFrequencyByLevel(m_sLevelFrequency, pType->GetLevel()) / ftCommon; } else { // Skip if the item is not within the level curve if ((pType->GetLevel() < m_iLevel - m_iLevelCurve) || (pType->GetLevel() > m_iLevel + m_iLevelCurve)) continue; // If we get this far then the item perfectly matches // and we need to add it to our table. First, however, we need // to calculate a score. // // The score is number that represents how common the item // is in the table. Later we normalize the score to be a probability int iLevelDelta = pType->GetLevel() - m_iLevel; switch (iLevelDelta) { case 0: iScore = 1000; break; case 1: case -1: iScore = 500; break; case 2: case -2: iScore = 200; break; default: iScore = 50; } } // Adjust score based on item frequency iScore = iScore * pType->GetFrequency() * 10 / (ftCommon * 10); // If we have a score of 0 then we skip this item if (iScore == 0) continue; // Add the item to the table pTable[iTableSize].pType = pType; pTable[iTableSize].iChance = iScore; iTableSize++; } // We must have items if (iTableSize == 0) { m_iCount = 0; m_Table = NULL; return; } // Add up the total score of all items int iTotalScore = 0; for (i = 0; i < iTableSize; i++) iTotalScore += pTable[i].iChance; // Compute the chance int iTotalChance = 0; for (i = 0; i < iTableSize; i++) { int iScore = pTable[i].iChance; pTable[i].iChance = (iScore * 1000) / iTotalScore; pTable[i].iRemainder = (iScore * 1000) % iTotalScore; iTotalChance += pTable[i].iChance; } // Distribute the remaining chance points while (iTotalChance < 1000) { // Look for the entry with the biggest remainder int iBestRemainder = 0; int iBestEntry = -1; for (i = 0; i < iTableSize; i++) if (pTable[i].iRemainder > iBestRemainder) { iBestRemainder = pTable[i].iRemainder; iBestEntry = i; } pTable[iBestEntry].iChance++; pTable[iBestEntry].iRemainder = 0; iTotalChance++; } // Count the number of entries that we've got m_iCount = 0; for (i = 0; i < iTableSize; i++) if (pTable[i].iChance > 0) m_iCount++; // Now loop over the entire table and add it to the // random entry generator m_Table = new SEntry [m_iCount]; int j = 0; for (i = 0; i < iTableSize; i++) if (pTable[i].iChance > 0) { m_Table[j].pType = pTable[i].pType; m_Table[j].iProbability = pTable[i].iChance; j++; } // Done delete [] pTable; }
void CUIHelper::CreateClassInfoItem (const CItem &Item, int x, int y, int cxWidth, DWORD dwOptions, const CString &sExtraDesc, int *retcyHeight, IAnimatron **retpInfo) const // CreateClassInfoItem // // Creates an item info animation { const CVisualPalette &VI = m_HI.GetVisuals(); const CG16bitFont &MediumFont = VI.GetFont(fontMedium); const CG16bitFont &MediumBoldFont = VI.GetFont(fontMediumBold); const CG16bitFont &SubTitleFont = VI.GetFont(fontSubTitle); // Handle some edge conditions CItemType *pType = Item.GetType(); if (pType == NULL) { if (retcyHeight) *retcyHeight = 0; CAniSequencer::Create(CVector(x, y), (CAniSequencer **)retpInfo); return; } // Figure out some dimensions and metrics. Everything is relative to x, y. bool bRightAlign = ((dwOptions & OPTION_ITEM_RIGHT_ALIGN) ? true : false); int cxIcon = SMALL_ICON_WIDTH; int cyIcon = SMALL_ICON_HEIGHT; int xIcon = (bRightAlign ? -cxIcon : 0); int yIcon = 0; int cxText = cxWidth - (cxIcon + ITEM_INFO_SPACING_HORZ); int xText = (bRightAlign ? -cxWidth : cxIcon + ITEM_INFO_SPACING_HORZ); int yText = 0; // Create a sequencer to hold all the controls CAniSequencer *pRoot; CAniSequencer::Create(CVector(x, y), &pRoot); // Create a small item icon const CObjectImageArray &Image = pType->GetImage(); RECT rcImage = Image.GetImageRect(); if (!Image.IsEmpty()) { CG16bitImage *pIcon = new CG16bitImage; pIcon->CreateFromImageTransformed(Image.GetImage(), rcImage.left, rcImage.top, RectWidth(rcImage), RectHeight(rcImage), (Metric)SMALL_ICON_WIDTH / RectWidth(rcImage), (Metric)SMALL_ICON_HEIGHT / RectHeight(rcImage), 0.0); IAnimatron *pImageFrame = new CAniRect; pImageFrame->SetPropertyVector(PROP_POSITION, CVector(xIcon, yIcon)); pImageFrame->SetPropertyVector(PROP_SCALE, CVector(SMALL_ICON_WIDTH, SMALL_ICON_HEIGHT)); pImageFrame->SetFillMethod(new CAniImageFill(pIcon, true)); pRoot->AddTrack(pImageFrame, 0); } // Create text int cyText = 0; IAnimatron *pName = new CAniText; pName->SetPropertyVector(PROP_POSITION, CVector(xText, yText + cyText)); pName->SetPropertyVector(PROP_SCALE, CVector(cxText, 1000)); pName->SetPropertyColor(PROP_COLOR, VI.GetColor(colorTextDialogInput)); pName->SetPropertyFont(PROP_FONT, &MediumBoldFont); pName->SetPropertyString(PROP_TEXT, pType->GetNounPhrase(nounActual)); if (bRightAlign) pName->SetPropertyString(PROP_TEXT_ALIGN_HORZ, ALIGN_RIGHT); pRoot->AddTrack(pName, 0); cyText += MediumBoldFont.GetHeight(); // Add the damage type adjustment CItemDataAnimatron *pDamageDesc = new CItemDataAnimatron(VI, Item); if (!pDamageDesc->IsEmpty()) { pDamageDesc->SetPropertyVector(PROP_POSITION, CVector(xText, yText + cyText)); pDamageDesc->SetPropertyVector(PROP_SCALE, CVector(cxText, 1000)); if (bRightAlign) pDamageDesc->SetPropertyString(PROP_TEXT_ALIGN_HORZ, ALIGN_RIGHT); pRoot->AddTrack(pDamageDesc, 0); RECT rcRect; pDamageDesc->GetSpacingRect(&rcRect); cyText += RectHeight(rcRect); } else delete pDamageDesc; // Add the reference text CItemCtx ItemCtx; CString sRef = pType->GetReference(ItemCtx, -1, CItemType::FLAG_ACTUAL_ITEM); if (sRef.IsBlank()) sRef = strPatternSubst(CONSTLIT("Level %s%s"), strLevel(pType->GetLevel()), sExtraDesc); else sRef = strPatternSubst(CONSTLIT("Level %s — %s%s"), strLevel(pType->GetLevel()), sRef, sExtraDesc); IAnimatron *pRef = new CAniText; pRef->SetPropertyVector(PROP_POSITION, CVector(xText, yText + cyText)); pRef->SetPropertyVector(PROP_SCALE, CVector(cxText, 1000)); pRef->SetPropertyColor(PROP_COLOR, VI.GetColor(colorTextDialogLabel)); pRef->SetPropertyFont(PROP_FONT, &MediumFont); pRef->SetPropertyString(PROP_TEXT, sRef); if (bRightAlign) pRef->SetPropertyString(PROP_TEXT_ALIGN_HORZ, ALIGN_RIGHT); pRoot->AddTrack(pRef, 0); RECT rcRef; pRef->GetSpacingRect(&rcRef); cyText += RectHeight(rcRef); // Done if (retcyHeight) *retcyHeight = Max(cyText, SMALL_ICON_HEIGHT); *retpInfo = pRoot; }
void GenerateItemTable (CUniverse &Universe, CXMLElement *pCmdLine) { ALERROR error; int i, j; // Compute the criteria CItemCriteria Crit; CString sCriteria; if (pCmdLine->FindAttribute(CRITERIA_ATTRIB, &sCriteria)) CItem::ParseCriteria(sCriteria, &Crit); else CItem::InitCriteriaAll(&Crit); // Generate a table CSymbolTable Table(FALSE, TRUE); // Loop over all items that match and add them to // a sorted table. for (j = 0; j < Universe.GetItemTypeCount(); j++) { CItemType *pType = Universe.GetItemType(j); CItem Item(pType, 1); if (!Item.MatchesCriteria(Crit)) continue; // Figure out the sort order char szBuffer[1024]; wsprintf(szBuffer, "%02d%s%02d%s", pType->GetLevel(), g_szTypeCode[GetItemType(pType)], GetItemFreq(pType), pType->GetNounPhrase().GetASCIIZPointer()); Table.AddEntry(CString(szBuffer), (CObject *)pType); } // If we need to output total count, then load the table CSymbolTable TotalCount(TRUE, TRUE); if (pCmdLine->GetAttributeBool(FIELD_TOTAL_COUNT)) { if (error = LoadTotalCount(TOTAL_COUNT_FILENAME, TotalCount)) return; } // If we've got any entries in the table, output now if (Table.GetCount()) { // Generate a list of columns to display CStringArray Cols; Cols.AppendString(FIELD_LEVEL); Cols.AppendString(FIELD_TYPE); Cols.AppendString(FIELD_FREQUENCY); Cols.AppendString(FIELD_NAME); // More columns from command-line if (pCmdLine->GetAttributeBool(FIELD_AVERAGE_COUNT)) Cols.AppendString(FIELD_AVERAGE_COUNT); if (pCmdLine->GetAttributeBool(FIELD_BALANCE)) Cols.AppendString(FIELD_BALANCE); if (pCmdLine->GetAttributeBool(FIELD_COST)) Cols.AppendString(FIELD_COST); if (pCmdLine->GetAttributeBool(FIELD_INSTALL_COST)) Cols.AppendString(FIELD_INSTALL_COST); if (pCmdLine->GetAttributeBool(FIELD_MASS)) Cols.AppendString(FIELD_MASS); if (pCmdLine->GetAttributeBool(FIELD_TOTAL_COUNT)) Cols.AppendString(FIELD_TOTAL_COUNT); if (pCmdLine->GetAttributeBool(FIELD_REFERENCE)) Cols.AppendString(FIELD_REFERENCE); if (pCmdLine->GetAttributeBool(FIELD_HP)) Cols.AppendString(FIELD_HP); if (pCmdLine->GetAttributeBool(FIELD_HP_BONUS)) Cols.AppendString(FIELD_HP_BONUS); if (pCmdLine->GetAttributeBool(FIELD_REGEN)) Cols.AppendString(FIELD_REGEN); if (pCmdLine->GetAttributeBool(FIELD_FIRE_DELAY)) Cols.AppendString(FIELD_FIRE_DELAY); if (pCmdLine->GetAttributeBool(FIELD_THRUST)) Cols.AppendString(FIELD_THRUST); if (pCmdLine->GetAttributeBool(FIELD_POWER)) Cols.AppendString(FIELD_POWER); if (pCmdLine->GetAttributeBool(FIELD_POWER_PER_SHOT)) Cols.AppendString(FIELD_POWER_PER_SHOT); if (pCmdLine->GetAttributeBool(FIELD_AVERAGE_DAMAGE)) Cols.AppendString(FIELD_AVERAGE_DAMAGE); if (pCmdLine->GetAttributeBool(FIELD_MAX_SPEED)) Cols.AppendString(FIELD_MAX_SPEED); // Output the header for (j = 0; j < Cols.GetCount(); j++) { if (j != 0) printf("\t"); printf(Cols.GetStringValue(j).GetASCIIZPointer()); } printf("\n"); // Output each row for (i = 0; i < Table.GetCount(); i++) { CItemType *pType = (CItemType *)Table.GetValue(i); for (j = 0; j < Cols.GetCount(); j++) { if (j != 0) printf("\t"); CString sField = Cols.GetStringValue(j); CString sValue = pType->GetDataField(sField); if (strEquals(sField, FIELD_AVERAGE_DAMAGE) || strEquals(sField, FIELD_POWER_PER_SHOT)) printf("%.2f", strToInt(sValue, 0, NULL) / 1000.0); else if (strEquals(sField, FIELD_POWER)) printf("%.1f", strToInt(sValue, 0, NULL) / 1000.0); else if (strEquals(sField, FIELD_TOTAL_COUNT)) { double rCount = 0.0; CString sKey = strFromInt(pType->GetUNID(), FALSE); EntryInfo *pEntry; if (TotalCount.Lookup(sKey, (CObject **)&pEntry) == NOERROR) rCount = pEntry->rTotalCount; printf("%.2f", rCount); } else printf(sValue.GetASCIIZPointer()); } printf("\n"); } printf("\n"); } else printf("No entries match criteria.\n"); }
void CPlayerGameStats::GenerateGameStats (CGameStats &Stats, CSpaceObject *pPlayerShip, bool bGameOver) const // GenerateGameStats // // Generates a stats for everything we track { int j; CShip *pShip = (pPlayerShip ? pPlayerShip->AsShip() : NULL); if (pShip == NULL) return; CPlayerShipController *pPlayer = (CPlayerShipController *)pShip->GetController(); if (pPlayer == NULL) return; CSovereign *pPlayerSovereign = g_pUniverse->FindSovereign(g_PlayerSovereignUNID); if (pPlayerSovereign == NULL) return; // Base stats Stats.Insert(CONSTLIT("Genome"), strCapitalize(GetGenomeName(pPlayer->GetPlayerGenome()))); Stats.Insert(CONSTLIT("Score"), strFormatInteger(CalcEndGameScore(), -1, FORMAT_THOUSAND_SEPARATOR | FORMAT_UNSIGNED)); Stats.Insert(CONSTLIT("Ship class"), pShip->GetNounPhrase(0)); CTimeSpan Time = GetPlayTime(); if (!Time.IsBlank()) Stats.Insert(CONSTLIT("Time played"), Time.Format(NULL_STR)); #ifdef REAL_TIME Time = GetGameTime(); if (!Time.IsBlank()) Stats.Insert(CONSTLIT("Time elapsed in game"), Time.Format(NULL_STR)); #endif // Some combat stats CString sDestroyed = GetStat(ENEMY_SHIPS_DESTROYED_STAT); if (!sDestroyed.IsBlank()) Stats.Insert(CONSTLIT("Enemy ships destroyed"), sDestroyed, CONSTLIT("combat")); sDestroyed = GetStat(FRIENDLY_SHIPS_DESTROYED_STAT); if (!sDestroyed.IsBlank()) Stats.Insert(CONSTLIT("Friendly ships destroyed"), sDestroyed, CONSTLIT("combat")); sDestroyed = GetStat(ENEMY_STATIONS_DESTROYED_STAT); if (!sDestroyed.IsBlank()) Stats.Insert(CONSTLIT("Enemy stations destroyed"), sDestroyed, CONSTLIT("combat")); sDestroyed = GetStat(FRIENDLY_STATIONS_DESTROYED_STAT); if (!sDestroyed.IsBlank()) Stats.Insert(CONSTLIT("Friendly stations destroyed"), sDestroyed, CONSTLIT("combat")); // Add stat for every station destroyed CStatCounterArray CounterArray; CMapIterator i; m_StationStats.Reset(i); while (m_StationStats.HasMore(i)) { SStationTypeStats *pStats; DWORD dwUNID = m_StationStats.GetNext(i, &pStats); CStationType *pType = g_pUniverse->FindStationType(dwUNID); if (pType == NULL) continue; CString sName = pType->GetNounPhrase(0); CString sSort = strPatternSubst(CONSTLIT("%03d%s"), 100 - pType->GetLevel(), sName); if (pType->GetSovereign()->IsEnemy(pPlayerSovereign)) CounterArray.Insert(sName, pStats->iDestroyed, CONSTLIT("Enemy stations destroyed"), sSort); else CounterArray.Insert(sName, pStats->iDestroyed, CONSTLIT("Friendly stations destroyed"), sSort); } CounterArray.GenerateGameStats(Stats); // Add stat for every ship class destroyed CounterArray.DeleteAll(); m_ShipStats.Reset(i); while (m_ShipStats.HasMore(i)) { SShipClassStats *pStats; DWORD dwUNID = m_ShipStats.GetNext(i, &pStats); CShipClass *pClass = g_pUniverse->FindShipClass(dwUNID); if (pClass == NULL) continue; CString sName = pClass->GetNounPhrase(0); CString sSort = strPatternSubst(CONSTLIT("%09d%s"), 100000000 - pClass->GetScore(), sName); if (pStats->iEnemyDestroyed > 0) CounterArray.Insert(sName, pStats->iEnemyDestroyed, CONSTLIT("Enemy ships destroyed"), sSort); if (pStats->iFriendDestroyed > 0) CounterArray.Insert(sName, pStats->iFriendDestroyed, CONSTLIT("Friendly ships destroyed"), sSort); } CounterArray.GenerateGameStats(Stats); // Add stat for every weapon fired m_ItemStats.Reset(i); while (m_ItemStats.HasMore(i)) { SItemTypeStats *pStats; DWORD dwUNID = m_ItemStats.GetNext(i, &pStats); CItemType *pItemType = g_pUniverse->FindItemType(dwUNID); if (pItemType == NULL) continue; CString sName = pItemType->GetNounPhrase(nounShort); CString sSort = strPatternSubst(CONSTLIT("%03d%s"), 100 - pItemType->GetLevel(), sName); // Installed items if (pStats->dwFirstInstalled != INVALID_TIME) Stats.Insert(sName, NULL_STR, CONSTLIT("Items installed"), sSort); if (pStats->iCountFired > 0) Stats.Insert(sName, strFormatInteger(pStats->iCountFired, -1, FORMAT_THOUSAND_SEPARATOR | FORMAT_UNSIGNED), CONSTLIT("Weapons fired"), sSort); } // Stats for player equipment (but only if the game is done) if (bGameOver) { TSortMap<CString, CItem> InstalledItems; // First we generate a sorted list of installed items // (We do this in case there are multiple of the same device/armor so that // we can coalesce them together into a single line). CItemListManipulator ItemList(pShip->GetItemList()); ItemList.ResetCursor(); while (ItemList.MoveCursorForward()) { const CItem &Item(ItemList.GetItemAtCursor()); if (Item.IsInstalled()) { CString sEnhancement = Item.GetEnhancedDesc(pShip); CString sItemName = Item.GetNounPhrase(nounActual | nounCountOnly | nounShort); CString sLine = (sEnhancement.IsBlank() ? sItemName : strPatternSubst(CONSTLIT("%s [%s]"), sItemName, sEnhancement)); bool bInserted; CItem *pEntry = InstalledItems.SetAt(sLine, &bInserted); if (bInserted) { *pEntry = Item; pEntry->SetCount(1); } else pEntry->SetCount(pEntry->GetCount() + 1); } } // Now add all the installed items to the stats for (j = 0; j < InstalledItems.GetCount(); j++) { // Redo the line now that we know the proper count CString sEnhancement = InstalledItems[j].GetEnhancedDesc(pShip); CString sItemName = InstalledItems[j].GetNounPhrase(nounActual | nounCountOnly); CString sLine = (sEnhancement.IsBlank() ? sItemName : strPatternSubst(CONSTLIT("%s [%s]"), sItemName, sEnhancement)); // Compute the sort order int iOrder; switch (InstalledItems[j].GetType()->GetCategory()) { case itemcatWeapon: iOrder = 0; break; case itemcatLauncher: iOrder = 1; break; case itemcatShields: iOrder = 2; break; case itemcatArmor: iOrder = 3; break; case itemcatReactor: iOrder = 4; break; case itemcatDrive: iOrder = 5; break; default: iOrder = 6; break; } CString sSort = strPatternSubst(CONSTLIT("%d%03d%s"), iOrder, 100 - InstalledItems[j].GetType()->GetLevel(), sLine); Stats.Insert(sLine, NULL_STR, CONSTLIT("Final equipment"), sSort); } // Add the remaining items ItemList.ResetCursor(); while (ItemList.MoveCursorForward()) { const CItem &Item(ItemList.GetItemAtCursor()); if (!Item.IsInstalled()) { CString sEnhancement = Item.GetEnhancedDesc(pShip); CString sItemName = Item.GetNounPhrase(nounActual | nounCountOnly); CString sLine = (sEnhancement.IsBlank() ? sItemName : strPatternSubst(CONSTLIT("%s [%s]"), sItemName, sEnhancement)); CString sSort = strPatternSubst(CONSTLIT("%03d%s"), 100 - Item.GetType()->GetLevel(), sLine); Stats.Insert(sLine, NULL_STR, CONSTLIT("Final items"), sSort); } } } }