mirror of
https://github.com/unknownworlds/NS.git
synced 2024-11-26 14:30:56 +00:00
dc79b89150
git-svn-id: https://unknownworlds.svn.cloudforge.com/ns1@220 67975925-1194-0748-b3d5-c16f83f1a3a1
407 lines
No EOL
14 KiB
C++
407 lines
No EOL
14 KiB
C++
//======== (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("");
|
|
} |