NS/releases/3.05/source/mod/AvHBalanceAnalysis.cpp

407 lines
14 KiB
C++
Raw Normal View History

//======== (C) Copyright 2002 Charles G. Cleveland All rights reserved. =========
//
// The copyright to the contents herein is the property of Charles G. Cleveland.
// The contents may be used and/or copied only with the written permission of
// Charles G. Cleveland, or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose:
//
// $Workfile: AvHBalanceAnalysis.cpp $
// $Date: $
//
//-------------------------------------------------------------------------------
// $Log: $
//===============================================================================
#include "util/nowarnings.h"
#include "mod/AvHBalanceAnalysis.h"
#include "mod/AvHBalance.h"
#include "mod/AvHAlienWeaponConstants.h"
#include "mod/AvHMarineWeaponConstants.h"
#include "mod/AvHPlayerUpgrade.h"
#include "math.h"
#ifdef AVH_CLIENT
#include "mod/AvHHud.h"
extern AvHHud gHUD;
#endif
AvHEconomy::AvHEconomy(const AvHTechNodes& inTechNodes, int inInitialResources, int inInitialTowers, bool inMarine, MessageIDListType inTargetTechList)
{
mTechNodes = inTechNodes;
mInitialResources = inInitialResources;
mInitialTowers = inInitialTowers;
mMarine = inMarine;
mTargetTech = inTargetTechList;
mResultTime = mResultTowers = mResultResources = 0;
}
void AvHEconomy::CalculateFastestTime()
{
int theMinNodeAmount = 0;
float theMinTime = 10000;
// 7 nodes is realistic map maximum
for(int i = 0; i < 7; i++)
{
this->Simulate(i);
float theResultTime = (float)this->GetResultTime();
if(theResultTime < theMinTime)
{
theMinTime = theResultTime;
theMinNodeAmount = i;
}
}
// Use the minimum results
this->Simulate(theMinNodeAmount);
}
int AvHEconomy::GetBuildNodeCost() const
{
int theCost = 0;
if(this->mMarine)
{
theCost = BALANCE_IVAR(kResourceTowerCost);
}
else
{
theCost = BALANCE_IVAR(kAlienResourceTowerCost);
}
return theCost;
}
int AvHEconomy::GetResultTime() const
{
return this->mResultTime;
}
int AvHEconomy::GetResultTowers() const
{
return this->mResultTowers;
}
int AvHEconomy::GetResultResources() const
{
return this->mResultResources;
}
// Assumes nodes build instantly, doesn't account for travel time, assumes there is always an available node to be built
void AvHEconomy::Simulate(int inBuildXNodes)
{
int theResourceInjectionInterval = BALANCE_IVAR(kFuncResourceInjectionTime);
int theBuildNodeInterval = BALANCE_IVAR(kBalanceFuncResourceTime);
// Start simulating
int theTimeOfLastNode = 0;
bool theIsDone = false;
int theNumNodes = this->mInitialTowers;
float theTimeOfNextResearchComplete = 0;
// Initialize results
this->mResultTowers = this->mInitialTowers;
this->mResultResources = this->mInitialResources;
this->mResultTime = 0;
// Use copy of tech nodes
AvHTechNodes theTechNodes = this->mTechNodes;
MessageIDListType theTargetTech = this->mTargetTech;
if(theResourceInjectionInterval > 0)
{
// While not done
while(!theIsDone)
{
AvHMessageID theTargetTechID = (*theTargetTech.begin());
// If we have more nodes to build and we have enough resources
if(inBuildXNodes > 0)
{
// Build the node (increment num nodes, decrement resources)
int theNodeCost = this->GetBuildNodeCost();
if(this->mResultResources >= theNodeCost)
{
if(this->mResultTime > (theTimeOfLastNode + theBuildNodeInterval))
{
theTimeOfLastNode = this->mResultTime;
this->mResultResources -= theNodeCost;
this->mResultTowers++;
inBuildXNodes--;
}
}
}
// Find bottleneck technology
AvHMessageID theBottleNeck = theTechNodes.GetNextMessageNeededFor(theTargetTechID);
if(theTimeOfNextResearchComplete == 0)
{
// If we have enough resources to research/buy it
int theCost = 0;
float theTime = 0.0f;
bool theIsResearchable = false;
if(theTechNodes.GetResearchInfo(theBottleNeck, theIsResearchable, theCost, theTime))
{
// Pay for it
if(this->mResultResources >= theCost)
{
// Remember when we'll be done with it
this->mResultResources -= theCost;
theTimeOfNextResearchComplete = this->mResultTime + theTime;
}
}
else
{
this->mResultResources = -1;
theIsDone = true;
}
}
// Is our bottleneck technology done?
else if(this->mResultTime >= theTimeOfNextResearchComplete)
{
// Set it researched
theTechNodes.SetResearchDone(theBottleNeck, true);
// Is this technology our final tech?
if(theBottleNeck == theTargetTechID)
{
MessageIDListType::iterator theFindIter = std::find(theTargetTech.begin(), theTargetTech.end(), theTargetTechID);
if(theFindIter != theTargetTech.end())
{
theTargetTech.erase(theFindIter);
}
}
if(theTargetTech.size() == 0)
{
// We're done
theIsDone = true;
}
else
{
theTimeOfNextResearchComplete = 0;
}
}
// Add resources from current towers to our resources
this->mResultResources += this->mResultTowers*BALANCE_IVAR(kFuncResourceInjectionAmount);
// Increment current time by interval
this->mResultTime += theResourceInjectionInterval;
}
}
}
void GetSimulatedAnalysisFor(MessageIDListType inMessageIDList, bool inMarine, StringList& outList, string inNote)
{
outList.push_back(inNote);
int theStartingResources = inMarine ? BALANCE_IVAR(kNumInitialMarinePoints) : BALANCE_IVAR(kNumInitialAlienPoints);
// Get tech nodes
const AvHTechNodes& theTechNodes = gHUD.GetTechNodes();
AvHEconomy theEconomy(theTechNodes, theStartingResources, 1, inMarine, inMessageIDList);
theEconomy.CalculateFastestTime();
// Add message to log
int theResultTime = theEconomy.GetResultTime();
char theTimeString[128];
sprintf(theTimeString, "%d:%.2d", theResultTime/60, theResultTime%60);
int theNodesBuilt = theEconomy.GetResultTowers() - 1;
int theResultResources = theEconomy.GetResultResources();
string theLogMessage = " " + MakeStringFromInt(theNodesBuilt) + " nodes built -> Time: " + string(theTimeString) + " Resources: " + MakeStringFromInt(theResultResources);
outList.push_back(theLogMessage);
}
void GetSimulatedAnalysisFor(AvHMessageID inMessageID, bool inMarine, StringList& outList, string inNote)
{
MessageIDListType theMessageIDList;
theMessageIDList.push_back(inMessageID);
GetSimulatedAnalysisFor(theMessageIDList, inMarine, outList, inNote);
}
//void GetHiveAnalysis(StringList& outList)
//{
// // Assume gorges are all building res towers
//
// // Find time that one gorge has enough to build hive
//
// // Add hive build time
//}
int SimulateDamage(AvHUser3 inUser3, int inUser4, float inDamage, int inNumHives)
{
int theNumBullets = 0;
// Initialize health and armor
float theArmorLevel = (float)(AvHPlayerUpgrade::GetMaxArmorLevel(inUser4, inUser3));
float theHealth = (float)(AvHPlayerUpgrade::GetMaxHealth(inUser4, inUser3));
bool theDone = false;
// While lifeform is still alive
if(inDamage > 0)
{
while(!theDone)
{
// Simulate bullet hit
float theHealthToRemove = AvHPlayerUpgrade::CalculateDamageLessArmor(inUser3, inUser4, inDamage, theArmorLevel, NS_DMG_NORMAL, inNumHives);
theHealth -= theHealthToRemove;
// Increment number bullets
theNumBullets++;
// We're dead when our health cast to an int is 0 (for HL reasons, see CBaseEntity::TakeDamage for more info)
if(((int)theHealth) <= 0)
{
theDone = true;
}
}
}
else
{
theNumBullets = -1;
}
return theNumBullets;
}
void SimulateWeaponVsLifeform(float inDamage, string& inPrependString, string& inLifeformName, AvHUser3 inLifeform, StringList& outList)
{
for(int theNumHives = 1; theNumHives <= 3; theNumHives++)
//int theNumHives = 1;
{
for(int i = 0; i < 4; i++)
{
float theDamage = inDamage;
switch(i)
{
case 1:
theDamage *= (1.0f + (float)BALANCE_FVAR(kWeaponDamageLevelOne));
break;
case 2:
theDamage *= (1.0f + (float)BALANCE_FVAR(kWeaponDamageLevelTwo));
break;
case 3:
theDamage *= (1.0f + (float)BALANCE_FVAR(kWeaponDamageLevelThree));
break;
}
string theResult = string(inPrependString + " L" + MakeStringFromInt(i) + " (" + MakeStringFromFloat(theDamage) + ") vs. " + MakeStringFromInt(theNumHives) + "-hive " + inLifeformName + ": ");
theResult += MakeStringFromInt(SimulateDamage(inLifeform, MASK_NONE, theDamage, theNumHives));
theResult += "/" + MakeStringFromInt(SimulateDamage(inLifeform, MASK_UPGRADE_1, theDamage, theNumHives));
theResult += "/" + MakeStringFromInt(SimulateDamage(inLifeform, MASK_UPGRADE_1 | MASK_UPGRADE_10, theDamage, theNumHives));
theResult += "/" + MakeStringFromInt(SimulateDamage(inLifeform, MASK_UPGRADE_1 | MASK_UPGRADE_11, theDamage, theNumHives));
outList.push_back(theResult);
}
}
}
void SimulateWeaponVsLifeforms(float inDamage, string& inPrependString, StringList& outList)
{
SimulateWeaponVsLifeform(inDamage, inPrependString, string("Skulk"), AVH_USER3_ALIEN_PLAYER1, outList);
SimulateWeaponVsLifeform(inDamage, inPrependString, string("Gorge"), AVH_USER3_ALIEN_PLAYER2, outList);
SimulateWeaponVsLifeform(inDamage, inPrependString, string("Lerk"), AVH_USER3_ALIEN_PLAYER3, outList);
SimulateWeaponVsLifeform(inDamage, inPrependString, string("Fade"), AVH_USER3_ALIEN_PLAYER4, outList);
SimulateWeaponVsLifeform(inDamage, inPrependString, string("Onos"), AVH_USER3_ALIEN_PLAYER5, outList);
SimulateWeaponVsLifeform(inDamage, inPrependString, string("Marine"), AVH_USER3_MARINE_PLAYER, outList);
}
void AvHBAComputeAnalysis(StringList& outList)
{
// Num bile bombs to kill a resource tower
int theBileBombDamage = BALANCE_IVAR(kBileBombDamage);
int theResourceTowerHealth = BALANCE_IVAR(kResourceTowerHealth);
int theNumShots = (int)(ceil((float)theResourceTowerHealth/theBileBombDamage));
outList.push_back("# bile bombs to kill resource tower: " + MakeStringFromInt(theNumShots));
// Bite vs. resource tower
int theBiteDamage = BALANCE_IVAR(kBiteDamage);
theNumShots = (int)(ceil((float)theResourceTowerHealth/theBiteDamage));
float theTime = theNumShots*BALANCE_FVAR(kBiteROF);
outList.push_back("# bites to kill resource tower: " + MakeStringFromInt(theNumShots) + " (time: " + MakeStringFromFloat(theTime) + ")");
// Shotgun vs. hive
int theSGDamage = BALANCE_IVAR(kSGDamage);
int theNumSGPellets = BALANCE_IVAR(kSGBulletsPerShot);
int theHiveHealth = BALANCE_IVAR(kHiveHealth);
theNumShots = (int)(ceil((float)theHiveHealth/((float)theSGDamage*theNumSGPellets)));
outList.push_back("# shotgun blasts to kill hive: " + MakeStringFromInt(theNumShots));
// Grenades vs. alien resource tower (blast damage)
int theGGDamage = 2*BALANCE_IVAR(kGrenadeDamage);
int theAlienTowerHealth = BALANCE_IVAR(kAlienResourceTowerHealth);
theNumShots = (int)(ceil((float)theAlienTowerHealth/theGGDamage));
outList.push_back("# grenades to kill alien resource tower: " + MakeStringFromInt(theNumShots));
// Machine gun vs. alien res tower
int theMGDamage = BALANCE_IVAR(kMGDamage);
theNumShots = (int)(ceil((float)theAlienTowerHealth/theMGDamage));
outList.push_back("# mg bullets to kill alien resource tower: " + MakeStringFromInt(theNumShots));
GetSimulatedAnalysisFor(BUILD_HMG, true, outList, "First HMG:");
GetSimulatedAnalysisFor(BUILD_GRENADE_GUN, true, outList, "First GL:");
GetSimulatedAnalysisFor(BUILD_JETPACK, true, outList, "First jetpack:");
GetSimulatedAnalysisFor(BUILD_HEAVY, true, outList, "First heavy armor:");
GetSimulatedAnalysisFor(BUILD_SIEGE, true, outList, "First ASC:");
GetSimulatedAnalysisFor(RESEARCH_MOTIONTRACK, true, outList, "Motion-tracking:");
MessageIDListType theTech;
theTech.push_back(BUILD_PHASEGATE);
theTech.push_back(BUILD_PHASEGATE);
GetSimulatedAnalysisFor(theTech, true, outList, "Two phase gates:");
theTech.clear();
theTech.push_back(RESEARCH_ARMOR_THREE);
theTech.push_back(RESEARCH_WEAPONS_THREE);
GetSimulatedAnalysisFor(theTech, true, outList, "Level 3 armor + weapons:");
theTech.clear();
for(int i = 0; i < BALANCE_IVAR(kBalanceAverageTeamSize); i++)
{
theTech.push_back(BUILD_SHOTGUN);
}
GetSimulatedAnalysisFor(theTech, true, outList, "Full squad of shotguns:");
theTech.clear();
for(i = 0; i < BALANCE_IVAR(kBalanceAverageTeamSize); i++)
{
theTech.push_back(BUILD_HMG);
}
GetSimulatedAnalysisFor(theTech, true, outList, "Full squad of HMGs:");
theTech.clear();
//GetSimulatedAnalysisFor(ALIEN_BUILD_HIVE, false, outList, "Second hive:");
//GetSimulatedAnalysisFor(ALIEN_LIFEFORM_FIVE, false, outList, "First Onos:");
// GetHiveAnalysis(outList);
//
// GetMGSkulkAnalysis(outList);
// HMG vs. hive
SimulateWeaponVsLifeforms((float)BALANCE_IVAR(kMGDamage), string("mg"), outList);
SimulateWeaponVsLifeforms((float)BALANCE_IVAR(kHGDamage), string("pistol"), outList);
SimulateWeaponVsLifeforms((float)BALANCE_IVAR(kSGDamage), string("shotty"), outList);
SimulateWeaponVsLifeforms((float)BALANCE_IVAR(kHMGDamage), string("hmg"), outList);
SimulateWeaponVsLifeforms((float)BALANCE_IVAR(kMineDamage), string("mine"), outList);
SimulateWeaponVsLifeforms((float)BALANCE_IVAR(kGrenadeDamage), string("gl"), outList);
SimulateWeaponVsLifeforms((float)BALANCE_IVAR(kBiteDamage), string("bite"), outList);
// outList.push_back("MG vs. Skulk: 1/2/3/4");
// outList.push_back("MG vs. Skulk (cara 1): 2/3/4/5");
// outList.push_back("MG vs. Skulk (cara 2): 3/4/5/6");
outList.push_back("");
}