📄 attackhandler.cpp
字号:
#include "AttackHandler.h"
#define K_MEANS_ELEVATION 40
#define IDLE_GROUP_ID 0
#define STUCK_GROUP_ID 1
#define AIR_GROUP_ID 2
#define GROUND_GROUP_ID_START 1000
#define SAFE_SPOT_DISTANCE 300
//#define SAFE_SPOT_DISTANCE_SLACK 700
#define KMEANS_ENEMY_MAX_K 32
#define KMEANS_BASE_MAX_K 32
#define KMEANS_MINIMUM_LINE_LENGTH (8 * THREATRES)
CR_BIND(CAttackHandler, (NULL));
CR_REG_METADATA(CAttackHandler, (
CR_MEMBER(ai),
CR_MEMBER(units),
CR_MEMBER(stuckUnits),
CR_MEMBER(unarmedAirUnits),
CR_MEMBER(armedAirUnits),
CR_MEMBER(airIsAttacking),
CR_MEMBER(airPatrolOrdersGiven),
CR_MEMBER(airTarget),
CR_MEMBER(newGroupID),
CR_MEMBER(attackGroups),
// CR_MEMBER(defenseGroups),
CR_MEMBER(kMeansBase),
CR_MEMBER(kMeansK),
CR_MEMBER(kMeansEnemyBase),
CR_MEMBER(kMeansEnemyK),
CR_RESERVED(16)
));
CAttackHandler::CAttackHandler(AIClasses* ai) {
this->ai = ai;
if (ai) {
// setting a position to the middle of the map
float mapWidth = ai->cb->GetMapWidth() * 8.0f;
float mapHeight = ai->cb->GetMapHeight() * 8.0f;
newGroupID = GROUND_GROUP_ID_START;
this->kMeansK = 1;
this->kMeansBase.push_back(float3(mapWidth / 2.0f, K_MEANS_ELEVATION, mapHeight / 2.0f));
this->kMeansEnemyK = 1;
this->kMeansEnemyBase.push_back(float3(mapWidth / 2.0f, K_MEANS_ELEVATION, mapHeight / 2.0f));
UpdateKMeans();
}
airIsAttacking = false;
airPatrolOrdersGiven = false;
airTarget = -1;
}
CAttackHandler::~CAttackHandler(void) {
}
void CAttackHandler::AddUnit(int unitID) {
if ((ai->MyUnits[unitID]->def())->canfly) {
// the groupID of this "group" is 0, to separate them from other idle units
ai->MyUnits[unitID]->groupID = AIR_GROUP_ID;
// this might be a new unit with the same id as an older dead unit
ai->MyUnits[unitID]->stuckCounter = 0;
// do some checking then essentially add it to defense group
if ((ai->MyUnits[unitID]->def())->weapons.size() == 0) {
unarmedAirUnits.push_back(unitID);
} else {
armedAirUnits.push_back(unitID);
}
// patrol orders need to be updated
airPatrolOrdersGiven = false;
}
else {
// the groupID of this "group" is 0, to separate them from other idle units
ai->MyUnits[unitID]->groupID = IDLE_GROUP_ID;
// this might be a new unit with the same id as an older dead unit
ai->MyUnits[unitID]->stuckCounter = 0;
// do some checking then essentially add it to defense group
units.push_back(unitID);
// TODO: not that good
this->PlaceIdleUnit(unitID);
}
}
void CAttackHandler::UnitDestroyed(int unitID) {
int attackGroupID = ai->MyUnits[unitID]->groupID;
// if it's in the defense group
if (attackGroupID == IDLE_GROUP_ID) {
bool found_dead_unit_in_attackHandler = false;
for (list<int>::iterator it = units.begin(); it != units.end(); it++) {
if (*it == unitID) {
units.erase(it);
found_dead_unit_in_attackHandler = true;
break;
}
}
assert(found_dead_unit_in_attackHandler);
}
else if (attackGroupID >= GROUND_GROUP_ID_START) {
// unit in an attack-group died
bool foundGroup = false;
bool removedDeadUnit = false;
list<CAttackGroup>::iterator it;
for (it = attackGroups.begin(); it != attackGroups.end(); it++) {
if (it->GetGroupID() == attackGroupID) {
removedDeadUnit = it->RemoveUnit(unitID);
foundGroup = true;
break;
}
}
assert(foundGroup);
assert(removedDeadUnit);
// check if the group is now empty
if (it->Size() == 0) {
attackGroups.erase(it);
}
}
else if (attackGroupID == AIR_GROUP_ID) {
// unit in air-group died
bool armed = true;
list<int>::iterator unarmedAirIt = unarmedAirUnits.begin();
list<int>::iterator armedAirIt = armedAirUnits.begin();
for (; unarmedAirIt != unarmedAirUnits.end(); unarmedAirIt++) {
if (unitID == *unarmedAirIt) {
unarmedAirUnits.erase(unarmedAirIt);
armed = false;
break;
}
}
if (armed) {
for (; armedAirIt != armedAirUnits.end(); armedAirIt++) {
if (unitID == *armedAirIt) {
armedAirUnits.erase(armedAirIt);
break;
}
}
}
}
else {
// unit in stuck-units group
bool found_dead_in_stuck_units = false;
list<pair<int, float3> >::iterator it;
for (it = stuckUnits.begin(); it != stuckUnits.end(); it++) {
if (it->first == unitID) {
stuckUnits.erase(it);
found_dead_in_stuck_units = true;
break;
}
}
assert(found_dead_in_stuck_units);
}
}
bool CAttackHandler::PlaceIdleUnit(int unit) {
if (ai->cb->GetUnitDef(unit) != NULL) {
float3 moo = FindUnsafeArea(ai->cb->GetUnitPos(unit));
if (moo != ZEROVECTOR && moo != ERRORVECTOR) {
ai->MyUnits[unit]->Move(moo);
}
}
return false;
}
// is it ok to build at CBS from this position?
bool CAttackHandler::IsSafeBuildSpot(float3 mypos) {
// TODO: get a subset of the k-means, then
// iterate the lines along the path that they
// make with a min distance slightly lower than
// the area radius definition
mypos = mypos;
return false;
}
// returns a safe spot from k-means, adjacent to myPos, safety params are (0..1).
// change to: decide on the random float 0...1 first, then find it (easier)
float3 CAttackHandler::FindSafeSpot(float3 myPos, float minSafety, float maxSafety) {
// find a safe spot
myPos = myPos;
int startIndex = int(minSafety * this->kMeansK);
if (startIndex < 0)
startIndex = 0;
// if (startIndex >= kMeansK)
// startIndex = kMeansK - 1;
int endIndex = int(maxSafety * this->kMeansK);
if (endIndex < 0)
startIndex = 0;
// if (endIndex >= kMeansK)
// endIndex = kMeansK - 1;
if (startIndex > endIndex)
startIndex = endIndex;
char text[512];
if (kMeansK <= 1 || startIndex == endIndex) {
if (startIndex >= kMeansK)
startIndex = kMeansK - 1;
float3 pos = kMeansBase[startIndex] + float3((RANDINT % SAFE_SPOT_DISTANCE), 0, (RANDINT % SAFE_SPOT_DISTANCE));
pos.y = ai->cb->GetElevation(pos.x, pos.z);
sprintf(text, "AH::FSA1 minS: %3.2f, maxS: %3.2f,", minSafety, maxSafety);
return pos;
}
assert(startIndex < endIndex);
assert(startIndex < kMeansK);
assert(endIndex <= kMeansK);
// get a subset of the k-means
vector<float3> subset;
for (int i = startIndex; i < endIndex; i++) {
// subset[i] = kMeansBase[startIndex + i];
assert(i < kMeansK);
subset.push_back(kMeansBase[i]);
}
// then find a position on one of the lines between those points (pather)
int whichPath;
if (subset.size() > 1) {
whichPath = (RANDINT % (int) subset.size());
} else {
whichPath = 0;
}
assert(whichPath < (int) subset.size());
assert(subset.size() > 0);
if ((whichPath + 1) < (int) subset.size() && subset[whichPath].distance2D(subset[whichPath + 1]) > KMEANS_MINIMUM_LINE_LENGTH) {
vector<float3> posPath;
//TODO: implement one in pathfinder without radius (or unit ID)
// if (size > (int)kMeansBase.size())
// size = kMeansBase.size();
float dist = ai->pather->MakePath(&posPath, &subset[whichPath], &subset[whichPath + 1], THREATRES * 8);
float3 res;
if (dist > 0) {
// L("attackhandler:findsafespot #1 dist > 0 from path, using res from pather. dist:" << dist);
int whichPos = RANDINT % (int) posPath.size();
res = posPath[whichPos];
} else {
// L("attackhandler:findsafespot #2 dist == 0 from path, using first point. dist:" << dist);
res = subset[whichPath];
}
sprintf(text, "AH::FSA-2 path:minS: %3.2f, maxS: %3.2f, pos:x: %f5.1 y: %f5.1 z: %f5.1", minSafety, maxSafety, res.x, res.y, res.z);
return res;
}
else {
assert(whichPath < (int) subset.size());
float3 res = subset[whichPath];
sprintf(text, "AH::FSA-3 minS: %f, maxS: %f, pos:x: %f y: %f z: %f", minSafety, maxSafety, res.x, res.y, res.z);
return res;
}
}
float3 CAttackHandler::FindSafeArea(float3 pos) {
if (this->DistanceToBase(pos) < SAFE_SPOT_DISTANCE)
return pos;
float min = 0.6;
float max = 0.95;
float3 safe = this->FindSafeSpot(pos, min, max);
// HACK
safe += pos;
safe /= 2;
return safe;
}
float3 CAttackHandler::FindVerySafeArea(float3 pos) {
float min = 0.9;
float max = 1.0;
return (this->FindSafeSpot(pos, min, max));
}
float3 CAttackHandler::FindUnsafeArea(float3 pos) {
float min = 0.1;
float max = 0.3;
return (this->FindSafeSpot(pos, min, max));
}
float CAttackHandler::DistanceToBase(float3 pos) {
float closestDistance = FLT_MAX;
for (int i = 0; i < this->kMeansK; i++) {
float3 mean = this->kMeansBase[i];
float distance = pos.distance2D(mean);
closestDistance = min(distance, closestDistance);
}
return closestDistance;
}
float3 CAttackHandler::GetClosestBaseSpot(float3 pos) {
float closestDistance = FLT_MAX;
int index = 0;
for (int i = 0; i < this->kMeansK; i++) {
float3 mean = this->kMeansBase[i];
float distance = pos.distance2D(mean);
if (distance < closestDistance) {
closestDistance = distance;
index = i;
}
}
return kMeansBase[index];
}
vector<float3> CAttackHandler::KMeansIteration(vector<float3> means, vector<float3> unitPositions, int newK) {
assert(newK > 0 && means.size() > 0);
int numUnits = unitPositions.size();
// change the number of means according to newK
int oldK = means.size();
means.resize(newK);
// add a new means, just use one of the positions
float3 newMeansPosition = unitPositions[0];
newMeansPosition.y = ai->cb->GetElevation(newMeansPosition.x, newMeansPosition.z) + K_MEANS_ELEVATION;
for (int i = oldK; i < newK; i++) {
means[i] = newMeansPosition;
}
// check all positions and assign them to means, complexity n*k for one iteration
vector<int> unitsClosestMeanID;
unitsClosestMeanID.resize(numUnits, -1);
vector<int> numUnitsAssignedToMean;
numUnitsAssignedToMean.resize(newK, 0);
for (int i = 0; i < numUnits; i++) {
float3 unitPos = unitPositions.at(i);
float closestDistance = FLT_MAX;
int closestIndex = -1;
for (int m = 0; m < newK; m++) {
float3 mean = means[m];
float distance = unitPos.distance2D(mean);
if (distance < closestDistance) {
closestDistance = distance;
closestIndex = m;
}
}
// position i is closest to the mean at closestIndex
unitsClosestMeanID[i] = closestIndex;
numUnitsAssignedToMean[closestIndex]++;
}
// change the means according to which positions are assigned to them
// use meanAverage for indexes with 0 pos'es assigned
// make a new means list
vector<float3> newMeans;
newMeans.resize(newK, float3(0, 0, 0));
for (int i = 0; i < numUnits; i++) {
int meanIndex = unitsClosestMeanID[i];
// don't divide by 0
float num = max(1, numUnitsAssignedToMean[meanIndex]);
newMeans[meanIndex] += unitPositions[i] / num;
}
// do a check and see if there are any empty means and set the height
for (int i = 0; i < newK; i++) {
// if a newmean is unchanged, set it to the new means pos instead of (0, 0, 0)
if (newMeans[i] == float3(0, 0, 0)) {
newMeans[i] = newMeansPosition;
}
else {
// get the proper elevation for the y-coord
newMeans[i].y = ai->cb->GetElevation(newMeans[i].x, newMeans[i].z) + K_MEANS_ELEVATION;
}
}
return newMeans;
}
void CAttackHandler::UpdateKMeans(void) {
// we want local variable definitions
{
// get positions of all friendly units and put them in a vector (completed buildings only)
int numFriendlies = 0;
vector<float3> friendlyPositions;
int friendlies[MAX_UNITS];
numFriendlies = ai->cb->GetFriendlyUnits(friendlies);
for (int i = 0; i < numFriendlies; i++) {
int unit = friendlies[i];
CUNIT* u = ai->MyUnits[unit];
// its a building, it has hp, and its mine (0)
if (this->UnitBuildingFilter(u->def()) && this->UnitReadyFilter(unit) && u->owner() == 0) {
friendlyPositions.push_back(u->pos());
}
}
// hack to make it at least 1 unit, should only happen when you have no base
if (friendlyPositions.size() < 1) {
// it has to be a proper position, unless there are no proper positions
if (numFriendlies > 0 && ai->cb->GetUnitDef(friendlies[0]) && ai->MyUnits[friendlies[0]]->owner() == 0) {
friendlyPositions.push_back(ai->cb->GetUnitPos(friendlies[0]));
}
else {
// when everything is dead
friendlyPositions.push_back(float3(RANDINT % (ai->cb->GetMapWidth() * 8), 1000, RANDINT % (ai->cb->GetMapHeight() * 8)));
}
}
// calculate a new K. change the formula to adjust max K, needs to be 1 minimum.
this->kMeansK = int(min((float) (KMEANS_BASE_MAX_K), 1.0f + sqrtf((float)numFriendlies + 0.01f)));
// iterate k-means algo over these positions and move the means
this->kMeansBase = KMeansIteration(this->kMeansBase, friendlyPositions, this->kMeansK);
}
// update enemy position k-means
// get positions of all enemy units and put them in a vector (completed buildings only)
int numEnemies = 0;
vector<float3> enemyPositions;
int enemies[MAX_UNITS];
numEnemies = ai->cheat->GetEnemyUnits(enemies);
for (int i = 0; i < numEnemies; i++) {
const UnitDef* ud = ai->cheat->GetUnitDef(enemies[i]);
if (this->UnitBuildingFilter(ud)) {
// if (this->UnitBuildingFilter(ud) && this->UnitReadyFilter(unit)) {
enemyPositions.push_back(ai->cheat->GetUnitPos(enemies[i]));
}
}
// hack to make it at least 1 unit, should only happen when you have no base
if (enemyPositions.size() < 1) {
// it has to be a proper position, unless there are no proper positions
if (numEnemies > 0 && ai->cheat->GetUnitDef(enemies[0])) {
enemyPositions.push_back(ai->cheat->GetUnitPos(enemies[0]));
}
else {
// when everything is dead
enemyPositions.push_back(float3(RANDINT % (ai->cb->GetMapWidth() * 8), 1000, RANDINT % (ai->cb->GetMapHeight() * 8)));
}
}
// calculate a new K. change the formula to adjust max K, needs to be 1 minimum
this->kMeansEnemyK = int(min(float(KMEANS_ENEMY_MAX_K), 1.0f + sqrtf((float) numEnemies + 0.01f)));
// iterate k-means algo over these positions and move the means
this->kMeansEnemyBase = KMeansIteration(this->kMeansEnemyBase, enemyPositions, this->kMeansEnemyK);
// base k-means and enemy base k-means are updated
// approach: add up (max - distance) to enemies
vector<float> proximity;
proximity.resize(kMeansK, 0.0000001f);
const float mapDiagonal = sqrt(pow((float) ai->cb->GetMapHeight() * 8,2) + pow((float) ai->cb->GetMapWidth() * 8, 2) + 1.0f);
for (int f = 0; f < kMeansK; f++) {
for (int e = 0; e < kMeansEnemyK; e++) {
proximity[f] += mapDiagonal - kMeansBase[f].distance2D(kMeansEnemyBase[e]);
}
}
// sort kMeans by the proximity score
float3 tempPos;
float temp;
for (int i = 1; i < kMeansK; i++) {
for (int j = 0; j < i; j++) {
// switch
if (proximity[i] > proximity[j]) {
tempPos = kMeansBase[i];
kMeansBase[i] = kMeansBase[j];
kMeansBase[j] = tempPos;
temp = proximity[i];
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -