1 #include "CombatManager.h"
2 #include "LegacyCpp/WeaponDef.h"
3 #include "LegacyCpp/CommandQueue.h"
4 
cCombatManager(IAICallback * callback,cRAI * Global)5 cCombatManager::cCombatManager(IAICallback* callback, cRAI* Global)
6 {
7 	cb=callback;
8 	G=Global;
9 	l=G->l;
10 }
11 
~cCombatManager()12 cCombatManager::~cCombatManager()
13 {
14 }
15 
UnitIdle(const int & unit,UnitInfo * U)16 void cCombatManager::UnitIdle(const int& unit, UnitInfo *U)
17 {
18 //*l<<"(cui) -eID="<<U->enemyID;
19 	if( ValidateEnemy(unit,U) && CanAttack(U,U->E,GetEnemyPosition(U->enemyID,U->E)) == 0 )
20 		U->enemyID=-1;
21 	float3 fPos=cb->GetUnitPos(unit);
22 	if( U->enemyID == -1 )
23 		while( (U->enemyID=GetClosestEnemy(fPos,U)) > 0 && !ValidateEnemy(unit,U) ) {}
24 	float distance = -1;
25 	if( U->enemyID >= 0 )
26 	{
27 		distance = fPos.distance(GetEnemyPosition(U->enemyID,U->E));
28 		if( distance == 0 )
29 			distance = 1;
30 	}
31 	if( U->enemyID == -1 || (U->udrBL->task != TASK_ASSAULT && distance > 2.5*8*U->ud->losRadius) )
32 	{
33 		U->inCombat=false;
34 		G->UpdateEventAdd(1,0,unit,U);
35 		return;
36 	}
37 	else if( CommandDGun(unit,U) )
38 		return;
39 	else if( U->enemyEff == 0 || (U->udrBL->task != TASK_ASSAULT && distance > 1.75*U->enemyEff->BestRange ) || (U->ud->isCommander && cb->GetUnitHealth(unit)/U->ud->health <= 0.66 ) ) // || G->Enemies.find(U->enemyID)->second.ud->kamikazeDist > distance
40 	{
41 		float3 EPos = GetEnemyPosition(U->enemyID,U->E);
42 		CommandRun(unit,U,EPos);
43 		return;
44 	}
45 	else if( CommandCapture(unit,U,distance) || CommandManeuver(unit,U,distance) )
46 		return;
47 	else
48 	{
49 		float3 EPos = GetEnemyPosition(U->enemyID,U->E);
50 		Command c;
51 		if( U->ud->canAttack && (U->E->inLOS || U->E->inRadar) )
52 		{
53 			c.id = CMD_ATTACK;
54 			c.params.push_back(U->enemyID);
55 		}
56 		else if( U->ud->canAttack && (U->udr->IsBomber && U->E->posLocked) )
57 		{
58 			c.id = CMD_ATTACK;
59 			c.params.push_back(EPos.x);
60 			c.params.push_back(EPos.y);
61 			c.params.push_back(EPos.z);
62 		}
63 		else // cant see enemy or Mod Workaround: Combat Lords - cant be given attack orders
64 		{
65 			c.id = CMD_MOVE;
66 			EPos.x += -100.0 +rand()%201;
67 			EPos.z += -100.0 +rand()%201;
68 			G->CorrectPosition(EPos);
69 			c.params.push_back(EPos.x);
70 			c.params.push_back(EPos.y);
71 			c.params.push_back(EPos.z);
72 		}
73 
74 		cb->GiveOrder(unit, &c);
75 		G->UpdateEventAdd(1,int(GetNextUpdate(distance,U)),unit,U);
76 	}
77 }
78 
UnitDamaged(const int & unitID,UnitInfo * U,const int & attackerID,EnemyInfo * A,float3 & dir)79 void cCombatManager::UnitDamaged(const int& unitID, UnitInfo* U, const int& attackerID, EnemyInfo* A, float3& dir)
80 {
81 	ValidateEnemy(unitID,U,false);
82 	if( attackerID >= 0 && attackerID != U->enemyID )
83 	{
84 		float3 Pos = cb->GetUnitPos(unitID);
85 		float3 APos = GetEnemyPosition(attackerID,A);
86 		if( U->enemyID == -1 || Pos.distance(APos) < Pos.distance(GetEnemyPosition(U->enemyID,U->E)) )
87 			if( CanAttack(U, A, APos) != 0 && (U->group == 0 || U->group->Enemies.find(attackerID) != U->group->Enemies.end()) )
88 			{
89 				U->enemyID=attackerID;
90 				U->E = A;
91 				U->enemyEff = CanAttack(U, A, APos);
92 			}
93 	}
94 	if( U->inCombat )
95 	{
96 		if( U->ud->isCommander )
97 		{
98 			if( int(cb->GetCurrentUnitCommands(unitID)->size()) == 0 )
99 				UnitIdle(unitID,U);
100 			else if( cb->GetCurrentUnitCommands(unitID)->front().id != CMD_MOVE )
101 			{
102 				if( cb->GetUnitHealth(unitID)/U->ud->health <= 0.66 ||
103 					(cb->GetUnitHealth(unitID)/U->ud->health <= 0.9 && cb->GetCurrentUnitCommands(unitID)->front().id == CMD_CAPTURE) )
104 					UnitIdle(unitID,U);
105 			}
106 		}
107 		return;
108 	}
109 	if( U->BuildQ != 0 && U->BuildQ->RS != 0 )
110 		U->BuildQ->tryCount = 4; // If the project is destroyed too many times, give up on it
111 	U->inCombat=true;
112 	if( U->enemyID == -1 )
113 	{
114 		if( attackerID >= 0 )
115 		{
116 			float3 APos = GetEnemyPosition(attackerID,A);
117 			CommandRun(unitID,U,APos);
118 		}
119 		else
120 		{
121 			float3 EPos = cb->GetUnitPos(unitID);
122 			EPos.x += dir.x*700;
123 			EPos.z += dir.z*700;
124 			EPos.y = cb->GetElevation(EPos.x,EPos.z);
125 			CommandRun(unitID,U,EPos);
126 		}
127 	}
128 	else
129 		UnitIdle(unitID,U);
130 }
131 
CommandDGun(const int & unitID,UnitInfo * U)132 bool cCombatManager::CommandDGun(const int& unitID, UnitInfo *U)
133 {
134 	if( U->udr->DGun == 0 || cb->GetEnergy() < U->udr->DGun->energycost )
135 		return false;
136 
137 	float3 EPos = GetEnemyPosition(U->enemyID,U->E);
138 	float EDis = EPos.distance(cb->GetUnitPos(unitID));
139 	if( EDis > 1.05*U->udr->DGun->range )
140 		return false;
141 
142 	if( U->ud->isCommander )
143 	{
144 		if( U->E->ud != 0 && U->E->ud->isCommander )
145 		{
146 			CommandRun(unitID,U,EPos);
147 			return true;
148 		}
149 	}
150 	Command c;
151 	c.id=CMD_DGUN;
152 	c.params.push_back(EPos.x);
153 	c.params.push_back(EPos.y);
154 	c.params.push_back(EPos.z);
155 	cb->GiveOrder(unitID, &c);
156 	G->UpdateEventAdd(1,cb->GetCurrentFrame()+5,unitID,U);
157 	return true;
158 }
159 
CommandCapture(const int & unitID,UnitInfo * U,const float & EDis)160 bool cCombatManager::CommandCapture(const int& unitID, UnitInfo* U, const float& EDis)
161 {
162 	if( !U->ud->canCapture ) //|| EDis > 1.5*U->ud->buildDistance )
163 		return false;
164 
165 	if( U->ud->isCommander && cb->GetUnitHealth(unitID)/U->ud->health <= 0.9 )
166 		return false;
167 
168 	if( !U->E->inLOS || (!cb->IsUnitParalyzed(U->enemyID) && 1.5*U->ud->speed < U->E->ud->speed) )
169 		return false;
170 
171 	Command c;
172 	c.id = CMD_CAPTURE;
173 	c.params.push_back(U->enemyID);
174 	cb->GiveOrder(unitID, &c);
175 	return true;
176 }
177 /*
178 bool cCombatManager::CommandTrap(const int& unitID, UnitInfo* U, const float& EDis)
179 {
180 	if( !U->E->inLOS || U->ud->transportMass < U->E->ud->mass )
181 		return false;
182 	if( U->ud->transportCapacity == 0 )
183 		return false;
184 
185 	Command c;
186 	c.id = CMD_LOAD_UNITS;
187 	c.params.push_back(U->enemyID);
188 	cb->GiveOrder(unitID, &c);
189 	return true;
190 }
191 */
CommandManeuver(const int & unitID,UnitInfo * U,const float & EDis)192 bool cCombatManager::CommandManeuver(const int& unitID, UnitInfo *U, const float& EDis)
193 {
194 	if( U->ud->canfly || U->E->ud == 0 || !U->E->inLOS || U->enemyEff->BestRange <= 1.15*cb->GetUnitMaxRange(U->enemyID) || EDis > 3500.0 || int(G->UMobile.size()) > 60 )
195 		return false;
196 
197 	float3 Pos=cb->GetUnitPos(unitID);
198 	float3 EPos=GetEnemyPosition(U->enemyID,U->E);
199 
200 	if( U->ud->minWaterDepth < 0 && Pos.y <= 0 && U->udr->WeaponSeaEff.BestRange == 0 )
201 	{
202 		int iS=G->TM->GetSectorIndex(EPos);
203 		if( G->TM->IsSectorValid(iS) )
204 		{
205 			Pos = G->TM->GetClosestSector(G->TM->landSectorType,iS)->position;
206 			Pos.x+=128-rand()%256;
207 			Pos.z+=128-rand()%256;
208 			G->CorrectPosition(Pos);
209 			Command c;
210 			c.id = CMD_MOVE;
211 			c.params.push_back(Pos.x);
212 			c.params.push_back(Pos.y);
213 			c.params.push_back(Pos.z);
214 			cb->GiveOrder(unitID, &c);
215 			G->UpdateEventAdd(1,int(GetNextUpdate(EDis,U)),unitID,U);
216 			return true;
217 		}
218 	}
219 	if( EDis < 0.70*U->enemyEff->BestRange || EDis > U->enemyEff->BestRange )
220 	{
221 		float distanceAway=(0.87*U->enemyEff->BestRange-EDis);
222 		Pos.x+=(Pos.x-EPos.x)*(distanceAway/EDis);
223 		Pos.z+=(Pos.z-EPos.z)*(distanceAway/EDis);
224 		G->CorrectPosition(Pos);
225 
226 		if( !G->TM->CanMoveToPos(U->area,Pos) )
227 			return false;
228 
229 		Command c;
230 		c.id = CMD_MOVE;
231 		c.params.push_back(Pos.x);
232 		c.params.push_back(cb->GetElevation(Pos.x,Pos.z));
233 		c.params.push_back(Pos.z);
234 		cb->GiveOrder(unitID, &c);
235 		G->UpdateEventAdd(1,int(GetNextUpdate(EDis,U)),unitID,U);
236 		return true;
237 	}
238 	return false;
239 }
240 
CommandRun(const int & unitID,UnitInfo * U,float3 & EPos)241 void cCombatManager::CommandRun(const int& unitID, UnitInfo *U, float3& EPos)
242 {
243 	float3 Pos=cb->GetUnitPos(unitID);
244 	Pos.x+=Pos.x-EPos.x;
245 	Pos.z+=Pos.z-EPos.z;
246 	G->CorrectPosition(Pos);
247 	Command c;
248 	c.id = CMD_MOVE;
249 	c.params.push_back(Pos.x);
250 	c.params.push_back(Pos.y);
251 	c.params.push_back(Pos.z);
252 	cb->GiveOrder(unitID, &c);
253 	G->UpdateEventAdd(1,cb->GetCurrentFrame()+210,unitID,U);
254 }
255 
GetClosestEnemy(float3 Pos,UnitInfo * U)256 int cCombatManager::GetClosestEnemy(float3 Pos, UnitInfo* U)
257 {
258 	U->enemyID=-1;
259 	// these two function need improvement, for now I'll just use a short cut
260 	if( !G->UM->ActiveAttackOrders() && U->udrBL->task != TASK_SUICIDE )
261 		return GetClosestThreat(Pos, U);
262 	sWeaponEfficiency* weTemp;
263 	float distance,fTemp;
264 	float3 fE;
265 	distance=0.0f;
266 	for( map<int,EnemyInfo>::iterator E=G->Enemies.begin(); E!=G->Enemies.end(); ++E )
267 	{
268 		fE=GetEnemyPosition(E->first,&E->second);
269 		if( (weTemp = CanAttack(U,&E->second,fE)) != 0 )
270 		{
271 			fTemp=Pos.distance(fE);
272 			if( U->enemyID == -1 || fTemp < distance )
273 			{
274 				U->enemyID=E->first;
275 				U->E = &E->second;
276 				U->enemyEff = weTemp;
277 				distance=fTemp;
278 			}
279 		}
280 	}
281 	if( U->enemyID != -1 && U->group != 0 )
282 		G->UM->GroupAddEnemy(U->enemyID,U->E,U->group);
283 	return U->enemyID;
284 }
285 
GetClosestThreat(float3 Pos,UnitInfo * U)286 int cCombatManager::GetClosestThreat(float3 Pos, UnitInfo* U)
287 {
288 	sWeaponEfficiency* weTemp;
289 	float distance,fTemp;
290 	distance=0.0f;
291 	float3 fE;
292 	set<int> deletion;
293 	for( map<int,EnemyInfo*>::iterator E=G->EThreat.begin(); E!=G->EThreat.end(); ++E )
294 	{
295 		fE=GetEnemyPosition(E->first,E->second);
296 		if( E->second->baseThreatFrame > cb->GetCurrentFrame()+3600 ||
297 			(E->second->baseThreatFrame > cb->GetCurrentFrame()+1200 && G->UImmobile.find(E->second->baseThreatID) == G->UImmobile.end() ) ||
298 			(E->second->ud != 0 && G->UImmobile.find(E->second->baseThreatID) != G->UImmobile.end() && 1.3*E->second->ud->maxWeaponRange < fE.distance(cb->GetUnitPos(E->second->baseThreatID)) ) )
299 		{
300 			E->second->baseThreatID = -1;
301 			E->second->baseThreatFrame = -1;
302 			deletion.insert(E->first);
303 		}
304 		else if( (weTemp = CanAttack(U,E->second,fE)) != 0 )
305 		{
306 			fTemp=Pos.distance(fE);
307 			if( U->enemyID == -1 || fTemp < distance )
308 			{
309 				U->enemyID=E->first;
310 				U->E = E->second;
311 				U->enemyEff = weTemp;
312 				distance=fTemp;
313 			}
314 		}
315 	}
316 	while( int(deletion.size()) > 0 )
317 	{
318 		if( !G->UM->ActiveAttackOrders() )
319 		{
320 			EnemyInfo* E = G->EThreat.find(*deletion.begin())->second;
321 			while( int(E->attackGroups.size()) > 0 )
322 				G->UM->GroupRemoveEnemy(*deletion.begin(),E,*E->attackGroups.begin());
323 		}
324 		G->EThreat.erase(*deletion.begin());
325 		deletion.erase(*deletion.begin());
326 	}
327 	if( U->enemyID != -1 && U->group != 0 )
328 		G->UM->GroupAddEnemy(U->enemyID,U->E,U->group);
329 	return U->enemyID;
330 }
331 
GetEnemyPosition(const int & enemyID,EnemyInfo * E)332 float3 cCombatManager::GetEnemyPosition(const int& enemyID, EnemyInfo* E)
333 {
334 	if( E->posLocked || (!E->inLOS && !E->inRadar) )
335 		return E->position;
336 	return cb->GetUnitPos(enemyID);
337 }
338 
GetNextUpdate(const float & Distance,UnitInfo * U)339 float cCombatManager::GetNextUpdate(const float &Distance, UnitInfo* U)
340 {
341 	if( U->ud->speed == 0.0 )
342 		return cb->GetCurrentFrame()+90.0;
343 	float fFrame=30.0*((Distance-U->enemyEff->BestRange)/(5.0*U->ud->speed));
344 	if( int(G->UMobile.size()) > 45 )
345 		fFrame*=3;
346 	if( fFrame > 90.0 )
347 		return cb->GetCurrentFrame()+fFrame;
348 	return cb->GetCurrentFrame()+90.0;
349 }
350 
CanAttack(UnitInfo * U,EnemyInfo * E,const float3 & EPos)351 sWeaponEfficiency* cCombatManager::CanAttack(UnitInfo* U, EnemyInfo *E, const float3& EPos)
352 {
353 	if( !G->TM->CanMoveToPos(U->area,EPos) )
354 		return 0;
355 	float fElevation=cb->GetElevation(EPos.x,EPos.z);
356 	if( EPos.y < 0.0 && U->udr->WeaponSeaEff.BestRange > 0 )
357 		return &U->udr->WeaponSeaEff;
358 	if( EPos.y-fElevation>50.0 && U->udr->WeaponAirEff.BestRange > 0 )
359 		return &U->udr->WeaponAirEff;
360 	if( EPos.y-fElevation<=50.0 && EPos.y >= -15.0 && U->udr->WeaponLandEff.BestRange > 0 )
361 		return &U->udr->WeaponLandEff;
362 	return 0;
363 }
364 
ValidateEnemy(const int & unitID,UnitInfo * U,bool IdleIfInvalid)365 bool cCombatManager::ValidateEnemy(const int& unitID, UnitInfo* U, bool IdleIfInvalid)
366 {
367 	if( U->enemyID == -1 || G->Enemies.find(U->enemyID) == G->Enemies.end() )
368 	{	// old enemy target that doesn't exist
369 		U->enemyID=-1;
370 		if( IdleIfInvalid )
371 			G->UpdateEventAdd(1,cb->GetCurrentFrame()+90,unitID,U);
372 		return false;
373 	}
374 	float3 EPos = cb->GetUnitPos(U->enemyID);
375 	if( U->group == 0 )
376 	{	// keeping variables up-to-date, this is event-driven for groups
377 		U->E = &G->Enemies.find(U->enemyID)->second;
378 		U->enemyEff = CanAttack(U,U->E,EPos);
379 	}
380 	if( cb->GetUnitDef(U->enemyID) != 0 && cb->GetUnitAllyTeam(unitID) == cb->GetUnitAllyTeam(U->enemyID) )
381 	{	// an enemy ID was reused by an ally team
382 		if( U->E->inLOS || U->E->inRadar ) // ! Work Around:  Spring-Version(v0.72b1-0.76b1)
383 		{	// OR an ally captures an enemy unit & no events were sent
384 			*l<<"\nWARNING: ValidateEnemy(eID="<<U->enemyID<<"): an ally has captured an enemy unit";
385 		}
386 		G->EnemyDestroyed(U->enemyID,-1);
387 		U->enemyID=-1;
388 		return false;
389 	}
390 	if( EPos.x > 0 || EPos.z > 0 || EPos.y > 0 ) // Position is valid
391 	{
392 		if( !U->E->inLOS && !U->E->inRadar ) // ! Work Around:  Spring-Version(v0.72b1-0.76b1)
393 		{
394 			if( cb->GetUnitDef(U->enemyID) != 0 )
395 			{
396 				*l<<"\nWARNING: ValidateEnemy(eID="<<U->enemyID<<"): incorrect LOS status";
397 				G->EnemyEnterLOS(U->enemyID);
398 			}
399 			else
400 			{
401 				*l<<"\nWARNING: ValidateEnemy(eID="<<U->enemyID<<"): incorrect radar status";
402 				G->EnemyEnterRadar(U->enemyID);
403 			}
404 		}
405 		return true;
406 	}
407 	if( !U->E->inLOS && !U->E->inRadar && cb->GetUnitPos(unitID).distance2D(U->E->position) > 300.0f )
408 		return true;
409 	G->EnemyRemove(U->enemyID,U->E);
410 	U->enemyID=-1;
411 	if( IdleIfInvalid )
412 		G->UpdateEventAdd(1,cb->GetCurrentFrame()+90,unitID,U);
413 	return false;
414 }
415