1 /*
2 * This file is part of Dune Legacy.
3 *
4 * Dune Legacy is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * Dune Legacy is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with Dune Legacy. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18
19 #include <players/QuantBot.h>
20
21 #include <Game.h>
22 #include <GameInitSettings.h>
23 #include <Map.h>
24 #include <sand.h>
25 #include <House.h>
26
27 #include <structures/StructureBase.h>
28 #include <structures/BuilderBase.h>
29 #include <structures/StarPort.h>
30 #include <structures/ConstructionYard.h>
31 #include <structures/RepairYard.h>
32 #include <structures/Palace.h>
33 #include <units/UnitBase.h>
34 #include <units/GroundUnit.h>
35 #include <units/MCV.h>
36 #include <units/Harvester.h>
37 #include <units/Saboteur.h>
38 #include <units/Devastator.h>
39
40 #include <algorithm>
41 #include <string>
42
43 #define AIUPDATEINTERVAL 50
44
45
46
47 /**
48 TODO
49
50 New list from Dec 2016
51 - Some harvesters getting 'stuck' by base when 100% full
52 - rocket launchers are firing on units too close again...
53 - unit rally points need to be adjusted for unit producers
54 - add in writing of game log to a repository
55
56 - fix game performance when toomany units
57
58
59 New list from May 2016
60 - units should move at start
61 - fix single player campaign crash
62 - fix unit allocation bug - atredes only building light tanks
63
64
65 == Building Placement ==
66
67
68 ia) build concrete when no placement locations are available == in progress, bugs exist ==
69 iii) increase favourability of being near other buildings == 50% done ==
70
71 1. Refinerys near spice == tried but failed ==
72 4. Repair yards factories, & Turrets near enemy == 50% done ==
73 5. All buildings away from enemy other that silos and turrets
74
75
76 == buildings ==
77 i) stop repair when just on yellow (at 50%) == 50% done, still broken for some buildings as goes into yellow health ==
78 ii) silo build broken == fixed ==
79
80
81 building algo still leaving gaps
82 increase alignment score when sides match
83
84 == Units ==
85 ii) units that get stuck in buildings should be transported to squadcenter =%80=
86 vii) fix attack timer =%80=
87 viii) when attack timer exceeds a certain value then all fing units are set to area guard
88
89 2) harvester return distance bug been introduced.= in progress ==
90
91 3) carryalls sit over units hovering bug introduced.... fix scramble units and defend + manual carryall = 50% =
92
93 4) theres a bug in on increment and decrement units...
94
95 5) turn off force move to rally point after attacked = 50% =
96 6) reduce turret building when lacking a military = 50% =
97
98 7) remove turrets from nuke target calculation =50%=
99 8) adjust turret placement algo to include points for proximitry to base centre =50%=
100
101
102
103 1. Harvesters deploy away from enemy
104 5. fix gun turret & gun for rocket turret
105
106 x. Improve squad management
107
108 == New work ==
109 1. Add them with some logic =50%=
110 2. fix force ratio optimisation algorithm,
111 need to make it based off kill / death ratio instead of just losses =50%=
112 3. create a retreate mechanism = 50% = still need to add retreat timer, say 1 retreat per minute, max
113 - fix rally point and ybut deploy logic
114
115
116 2. Make carryalls and ornithopers easier to hit
117
118 ====> FIX WORM CRASH GAME BUG
119
120 **/
121
122
123
QuantBot(House * associatedHouse,const std::string & playername,Difficulty difficulty)124 QuantBot::QuantBot(House* associatedHouse, const std::string& playername, Difficulty difficulty)
125 : Player(associatedHouse, playername), difficulty(difficulty) {
126
127 buildTimer = getRandomGen().rand(0,3) * 50;
128
129 attackTimer = MILLI2CYCLES(10000);
130 retreatTimer = MILLI2CYCLES(60000);
131
132 // Different AI logic for Campaign. Assumption is if player is loading they are playing a campaign game
133 if((currentGame->gameType == GameType::Campaign) || (currentGame->gameType == GameType::LoadSavegame) || (currentGame->gameType == GameType::Skirmish)) {
134 gameMode = GameMode::Campaign;
135 }else{
136 gameMode = GameMode::Custom;
137 }
138
139
140 if(gameMode == GameMode::Campaign){
141 // Wait a while if it is a campaign game
142
143 switch(currentGame->techLevel){
144 case 6: {
145 attackTimer = MILLI2CYCLES(540000);
146 }break;
147
148 case 7: {
149 attackTimer = MILLI2CYCLES(600000);
150 }break;
151
152 case 8: {
153 attackTimer = MILLI2CYCLES(720000);
154 }break;
155
156 default: {
157 attackTimer = MILLI2CYCLES(480000);
158 }
159
160 }
161 }
162 }
163
164
QuantBot(InputStream & stream,House * associatedHouse)165 QuantBot::QuantBot(InputStream& stream, House* associatedHouse) : Player(stream, associatedHouse) {
166 QuantBot::init();
167
168 difficulty = static_cast<Difficulty>(stream.readUint8());
169 gameMode = static_cast<GameMode>(stream.readUint8());
170 buildTimer = stream.readSint32();
171 attackTimer = stream.readSint32();
172 retreatTimer = stream.readSint32();
173
174 for (Uint32 i = ItemID_FirstID; i <= Structure_LastID; i++ ){
175 initialItemCount[i] = stream.readUint32();
176 }
177 initialMilitaryValue = stream.readSint32();
178 militaryValueLimit = stream.readSint32();
179 harvesterLimit = stream.readSint32();
180 campaignAIAttackFlag = stream.readBool();
181
182 squadRallyLocation.x = stream.readSint32();
183 squadRallyLocation.y = stream.readSint32();
184 squadRetreatLocation.x = stream.readSint32();
185 squadRetreatLocation.y = stream.readSint32();
186
187 // Need to add in a building array for when people save and load
188 // So that it keeps the count of buildings that should be on the map.
189 Uint32 NumPlaceLocations = stream.readUint32();
190 for(Uint32 i = 0; i < NumPlaceLocations; i++) {
191 Sint32 x = stream.readSint32();
192 Sint32 y = stream.readSint32();
193
194 placeLocations.push_back(Coord(x,y));
195 }
196 }
197
198
init()199 void QuantBot::init() {
200 }
201
202
~QuantBot()203 QuantBot::~QuantBot() {
204 }
205
save(OutputStream & stream) const206 void QuantBot::save(OutputStream& stream) const {
207 Player::save(stream);
208
209 stream.writeUint8(static_cast<Uint8>(difficulty));
210 stream.writeUint8(static_cast<Uint8>(gameMode));
211 stream.writeSint32(buildTimer);
212 stream.writeSint32(attackTimer);
213 stream.writeSint32(retreatTimer);
214
215 for (Uint32 i = ItemID_FirstID; i <= Structure_LastID; i++ ){
216 stream.writeUint32(initialItemCount[i]);
217 }
218 stream.writeSint32(initialMilitaryValue);
219 stream.writeSint32(militaryValueLimit);
220 stream.writeSint32(harvesterLimit);
221 stream.writeBool(campaignAIAttackFlag);
222
223 stream.writeSint32(squadRallyLocation.x);
224 stream.writeSint32(squadRallyLocation.y);
225 stream.writeSint32(squadRetreatLocation.x);
226 stream.writeSint32(squadRetreatLocation.y);
227
228 stream.writeUint32(placeLocations.size());
229 for(const Coord& placeLocation : placeLocations) {
230 stream.writeSint32(placeLocation.x);
231 stream.writeSint32(placeLocation.y);
232 }
233
234 }
235
236
update()237 void QuantBot::update() {
238 if(getGameCylceCount() == 0) {
239 // The game just started and we gather some
240 // Count the items once initially
241
242 // First count all the objects we have
243 for (int i = ItemID_FirstID; i <= ItemID_LastID; i++ ) {
244 initialItemCount[i] = getHouse()->getNumItems(i);
245 logDebug("Initial: Item: %d Count: %d", i, initialItemCount[i]);
246 }
247
248 if((initialItemCount[Structure_RepairYard] == 0) && gameMode == GameMode::Campaign && currentGame->techLevel > 4) {
249 initialItemCount[Structure_RepairYard] = 1;
250 if(initialItemCount[Structure_Radar] == 0){
251 initialItemCount[Structure_Radar] = 1;
252 }
253
254 if(initialItemCount[Structure_LightFactory] == 0){
255 initialItemCount[Structure_LightFactory] = 1;
256 }
257
258 logDebug("Allow Campaign AI one Repair Yard");
259 }
260
261 // Calculate the total military value of the player
262 initialMilitaryValue = 0;
263 for(Uint32 i = Unit_FirstID; i <= Unit_LastID; i++){
264 if(i != Unit_Carryall && i != Unit_Harvester){
265 // Used for campaign mode.
266 initialMilitaryValue += initialItemCount[i] * currentGame->objectData.data[i][getHouse()->getHouseID()].price;
267 }
268 }
269
270 switch(gameMode) {
271 case GameMode::Campaign: {
272
273 switch(difficulty) {
274 case Difficulty::Easy: {
275 harvesterLimit = initialItemCount[Structure_Refinery];
276 if(currentGame->techLevel == 8){
277 militaryValueLimit = 4000;
278
279 } else {
280 militaryValueLimit = initialMilitaryValue;
281 }
282
283 logDebug("Easy Campaign ");
284 } break;
285
286 case Difficulty::Medium: {
287 harvesterLimit = 2 * initialItemCount[Structure_Refinery];
288 militaryValueLimit = lround(initialMilitaryValue * FixPt(1,2));
289 if(militaryValueLimit < 4000 && currentGame->techLevel == 8) {
290 militaryValueLimit = 4000;
291 }
292
293 logDebug("Medium Campaign ");
294 } break;
295
296 case Difficulty::Hard: {
297 if(currentGame->techLevel == 8) {
298 harvesterLimit = 3;
299 initialItemCount[Structure_Refinery] = 2;
300 militaryValueLimit = 5000;
301 } else {
302 harvesterLimit = 2 * initialItemCount[Structure_Refinery];
303 militaryValueLimit = lround(initialMilitaryValue * FixPt(1,5));
304 }
305
306 logDebug("Hard Campaign ");
307 } break;
308
309 case Difficulty::Brutal: {
310 harvesterLimit = (currentGameMap->getSizeX() * currentGameMap->getSizeY() / 512);
311 militaryValueLimit = 25000;
312
313 logDebug("Brutal Campaign ");
314 } break;
315
316 case Difficulty::Defend: {
317 harvesterLimit = 2 * initialItemCount[Structure_Refinery];
318 militaryValueLimit = lround(initialMilitaryValue * FixPt(1,2));
319
320 logDebug("Defensive Campaign ");
321 } break;
322 }
323
324 } break;
325
326 case GameMode::Custom: {
327
328 switch(difficulty) {
329 case Difficulty::Brutal: {
330 harvesterLimit = 60;
331 militaryValueLimit = 100000;
332 //logDebug("BUILD BRUTAL SKIRM ");
333 } break;
334
335 case Difficulty::Easy: {
336 harvesterLimit = (currentGameMap->getSizeX() * currentGameMap->getSizeY() / 2048);
337
338 militaryValueLimit = 10000;
339 //logDebug("BUILD EASY SKIRM ");
340 } break;
341
342 case Difficulty::Medium: {
343 harvesterLimit = (currentGameMap->getSizeX() * currentGameMap->getSizeY() / 1024);
344 militaryValueLimit = 25000;
345 //logDebug("BUILD MEDIUM SKIRM ");
346 } break;
347
348 case Difficulty::Hard: {
349 harvesterLimit = (currentGameMap->getSizeX() * currentGameMap->getSizeY() / 512);
350 militaryValueLimit = 50000;
351 //logDebug("BUILD HARD SKIRM ");
352 } break;
353
354 case Difficulty::Defend: {
355 harvesterLimit = (currentGameMap->getSizeX() * currentGameMap->getSizeY() / 1024);
356 militaryValueLimit = 25000;
357 //logDebug("BUILD MEDIUM SKIRM ");
358 } break;
359 }
360
361 // what is this useful for?
362 if((currentGameMap->getSizeX() * currentGameMap->getSizeY() / 512) < harvesterLimit && difficulty != Difficulty::Brutal) {
363 harvesterLimit = currentGameMap->getSizeX() * currentGameMap->getSizeY() / 512;
364 }
365
366 } break;
367
368 }
369 }
370
371
372 if((getGameCylceCount() + getHouse()->getHouseID()) % AIUPDATEINTERVAL != 0) {
373 // we are not updating this AI player this cycle
374 return;
375 }
376
377 // Calculate the total military value of the player
378 int militaryValue = 0;
379 for(Uint32 i = Unit_FirstID; i <= Unit_LastID; i++){
380 if(i != Unit_Carryall && i != Unit_Harvester){
381 militaryValue += getHouse()->getNumItems(i) * currentGame->objectData.data[i][getHouse()->getHouseID()].price;
382 }
383 }
384 //logDebug("Military Value %d Initial Military Value %d", militaryValue, initialMilitaryValue);
385
386 checkAllUnits();
387
388 if(buildTimer <= 0) {
389 build(militaryValue);
390 } else {
391 buildTimer -= AIUPDATEINTERVAL;
392 }
393
394 if(attackTimer <= 0) {
395 attack(militaryValue);
396 } else if (attackTimer > MILLI2CYCLES(100000) ) {
397 // If we have taken substantial losses then retreat
398 attackTimer = MILLI2CYCLES(90000);
399
400 if(retreatTimer < 0){
401 retreatAllUnits();
402 }
403 } else {
404 attackTimer -= AIUPDATEINTERVAL;
405 retreatTimer -= AIUPDATEINTERVAL;
406 }
407 }
408
409
onIncrementStructures(int itemID)410 void QuantBot::onIncrementStructures(int itemID) {
411 }
412
413
onDecrementStructures(int itemID,const Coord & location)414 void QuantBot::onDecrementStructures(int itemID, const Coord& location) {
415 }
416
417
418 /// When we take losses we should hold off from attacking for longer...
onDecrementUnits(int itemID)419 void QuantBot::onDecrementUnits(int itemID) {
420 if(itemID != Unit_Trooper && itemID != Unit_Infantry) {
421 attackTimer += MILLI2CYCLES(currentGame->objectData.data[itemID][getHouse()->getHouseID()].price * 30 / (static_cast<Uint8>(difficulty)+1) );
422 //logDebug("loss ");
423 }
424 }
425
426
427 /// When we get kills we should re-attack sooner...
onIncrementUnitKills(int itemID)428 void QuantBot::onIncrementUnitKills(int itemID) {
429 if(itemID != Unit_Trooper && itemID != Unit_Infantry) {
430 attackTimer -= MILLI2CYCLES(currentGame->objectData.data[itemID][getHouse()->getHouseID()].price * 15);
431 //logDebug("kill ");
432 }
433 }
434
onDamage(const ObjectBase * pObject,int damage,Uint32 damagerID)435 void QuantBot::onDamage(const ObjectBase* pObject, int damage, Uint32 damagerID) {
436 const ObjectBase* pDamager = getObject(damagerID);
437
438 if(pDamager == nullptr || pDamager->getOwner() == getHouse() || pObject->getItemID() == Unit_Sandworm) {
439 return;
440 }
441
442 // If the human has attacked us then its time to start fighting back... unless its an attack on a special unit
443 // Don't trigger with fremen or saboteur
444 bool bPossiblyOwnFremen = (pObject->getOwner()->getHouseID() == HOUSE_ATREIDES) && (pObject->getItemID() == Unit_Trooper) && (currentGame->techLevel > 7);
445 if(gameMode == GameMode::Campaign && !pDamager->getOwner()->isAI() && !campaignAIAttackFlag && !bPossiblyOwnFremen && (pObject->getItemID() != Unit_Saboteur)) {
446 campaignAIAttackFlag = true;
447 } else if (pObject->isAStructure()) {
448 doRepair(pObject);
449 // no point scrambling to defend a missile
450 if(pDamager->getItemID() != Structure_Palace) {
451 int numStructureDefenders = 0;
452 switch(difficulty) {
453 case Difficulty::Defend: numStructureDefenders = 4; break;
454 case Difficulty::Easy: numStructureDefenders = 6; break;
455 case Difficulty::Medium: numStructureDefenders = 10; break;
456 case Difficulty::Hard: numStructureDefenders = 20; break;
457 case Difficulty::Brutal: numStructureDefenders = std::numeric_limits<int>::max(); break;
458 }
459 scrambleUnitsAndDefend(pDamager, numStructureDefenders);
460 }
461
462 } else if(pObject->isAGroundUnit()){
463 Coord squadCenterLocation = findSquadCenter(pObject->getOwner()->getHouseID());
464
465 const GroundUnit* pUnit = dynamic_cast<const GroundUnit*>(pObject);
466
467 if(pUnit == nullptr) {
468 return;
469 }
470
471 if(pUnit->isAwaitingPickup()) {
472 return;
473 }
474
475 // Stop him dead in his tracks if he's going to rally point
476 if(pUnit->wasForced() && (pUnit->getItemID() != Unit_Harvester)) {
477 doMove2Pos(pUnit,
478 pUnit->getCenterPoint().x,
479 pUnit->getCenterPoint().y,
480 false);
481 }
482
483 if (pUnit->getItemID() == Unit_Harvester) {
484 // Always keep Harvesters away from harm
485 // Defend the harvester!
486 const Harvester* pHarvester = dynamic_cast<const Harvester*>(pUnit);
487 if(pHarvester->isActive() && (!pHarvester->isReturning()) && pHarvester->getAmountOfSpice() > 0) {
488 int numHarvesterDefenders = 0;
489 switch(difficulty) {
490 case Difficulty::Defend: numHarvesterDefenders = 2; break;
491 case Difficulty::Easy: numHarvesterDefenders = 3; break;
492 case Difficulty::Medium: numHarvesterDefenders = 5; break;
493 case Difficulty::Hard: numHarvesterDefenders = 10; break;
494 case Difficulty::Brutal: numHarvesterDefenders = std::numeric_limits<int>::max(); break;
495 }
496 scrambleUnitsAndDefend(pDamager, numHarvesterDefenders);
497 doReturn(pHarvester);
498 }
499 } else if ((pUnit->getItemID() == Unit_Launcher || pUnit->getItemID() == Unit_Deviator)
500 && (difficulty == Difficulty::Hard || difficulty == Difficulty::Brutal) ) {
501 // Always keep Launchers away from harm
502
503 doSetAttackMode(pUnit, AREAGUARD);
504 doMove2Pos(pUnit, squadCenterLocation.x, squadCenterLocation.y, true);
505
506 } else if( (currentGame->techLevel > 3)
507 && (pUnit->getItemID() == Unit_Quad)
508 && !pDamager->isInfantry()
509 && (pDamager->getItemID() != Unit_RaiderTrike)
510 && (pDamager->getItemID() != Unit_Trike)
511 && (pDamager->getItemID() != Unit_Quad)) {
512 // We want out quads as raiders
513 // Quads flee from every unit except trikes, infantry and other quads (but only if quads are not our main vehicle for that techlevel)
514 doSetAttackMode(pUnit, AREAGUARD);
515 doMove2Pos(pUnit, squadCenterLocation.x, squadCenterLocation.y, true);
516 } else if( (currentGame->techLevel > 3)
517 && (pUnit->getItemID() == Unit_RaiderTrike)
518 && (pUnit->getItemID() == Unit_Trike)
519 && !pDamager->isInfantry()
520 && (pDamager->getItemID() != Unit_RaiderTrike)
521 && (pDamager->getItemID() != Unit_Trike)) {
522 // Quads flee from every unit except infantry and other trikes (but only if trikes are not our main vehicle for that techlevel)
523 // We want to use our light vehicles as raiders.
524 // This means they are free to engage other light military units
525 // but should run away from tanks
526
527 doSetAttackMode(pUnit, AREAGUARD);
528 doMove2Pos(pUnit, squadCenterLocation.x, squadCenterLocation.y, true);
529
530 }
531
532 // If the unit is at 60% health or less and is not being forced to move anywhere
533 // repair them, if they are eligible to be repaired
534 if(difficulty == Difficulty::Brutal) {
535 if((pUnit->getHealth() * 100) / pUnit->getMaxHealth() < 60
536 && !pUnit->isInfantry()
537 && pUnit->isVisible()) {
538
539 if(getHouse()->hasRepairYard()){
540 doRepair(pUnit);
541 } else if(gameMode == GameMode::Custom && pUnit->getItemID() != Unit_Devastator && squadRetreatLocation.isValid()){
542 doSetAttackMode(pUnit, RETREAT);
543 }
544 }
545 }
546 }
547 }
548
findMcvPlaceLocation(const MCV * pMCV)549 Coord QuantBot::findMcvPlaceLocation(const MCV* pMCV) {
550 Coord bestLocation = findPlaceLocation(Structure_ConstructionYard);
551
552 if(bestLocation == Coord::Invalid()) {
553 logDebug("No MCV deploy location adjacent to existing base structures was found, move to full search | ");
554
555 int bestLocationScore = 1000;
556
557 // Don't place on the very edge of the map
558 for(int placeLocationX = 1; placeLocationX < getMap().getSizeX() -1; placeLocationX++) {
559 for(int placeLocationY = 1; placeLocationY < getMap().getSizeY() -1; placeLocationY++) {
560 Coord placeLocation = Coord::Invalid();
561 placeLocation.x = placeLocationX;
562 placeLocation.y = placeLocationY;
563
564 if(getMap().okayToPlaceStructure(placeLocationX, placeLocationY, 2, 2, false, nullptr)) {
565 int locationScore = lround(blockDistance(pMCV->getLocation(), placeLocation));
566 if(locationScore < bestLocationScore){
567 bestLocationScore = locationScore;
568 bestLocation.x = placeLocationX;
569 bestLocation.y = placeLocationY;
570 }
571 }
572 }
573 }
574 }
575
576 return bestLocation;
577 }
578
findPlaceLocation(Uint32 itemID)579 Coord QuantBot::findPlaceLocation(Uint32 itemID) {
580 // Will over allocate space for small maps so its not clean
581 // But should allow Richard to compile
582 int buildLocationScore[128][128] = {{0}};
583
584 int bestLocationX = -1;
585 int bestLocationY = -1;
586 int bestLocationScore = - 10000;
587 int newSizeX = getStructureSize(itemID).x;
588 int newSizeY = getStructureSize(itemID).y;
589 Coord bestLocation = Coord::Invalid();
590
591 for(const StructureBase* pStructureExisting : getStructureList()) {
592 if(pStructureExisting->getOwner() == getHouse()) {
593
594 int existingStartX = pStructureExisting->getX();
595 int existingStartY = pStructureExisting->getY();
596
597 int existingSizeX = pStructureExisting->getStructureSizeX();
598 int existingSizeY = pStructureExisting->getStructureSizeY();
599
600 int existingEndX = existingStartX + existingSizeX;
601 int existingEndY = existingStartY + existingSizeY;
602
603 squadRallyLocation = findSquadRallyLocation();
604
605 bool existingIsBuilder = (pStructureExisting->getItemID() == Structure_HeavyFactory
606 || pStructureExisting->getItemID() == Structure_RepairYard
607 || pStructureExisting->getItemID() == Structure_LightFactory
608 || pStructureExisting->getItemID() == Structure_WOR
609 || pStructureExisting->getItemID() == Structure_Barracks
610 || pStructureExisting->getItemID() == Structure_StarPort);
611
612 bool sizeMatchX = (existingSizeX == newSizeX);
613 bool sizeMatchY = (existingSizeY == newSizeY);
614
615
616 for(int placeLocationX = existingStartX - newSizeX; placeLocationX <= existingEndX; placeLocationX++){
617 for(int placeLocationY = existingStartY - newSizeY; placeLocationY <= existingEndY; placeLocationY++){
618 if(getMap().tileExists(placeLocationX,placeLocationY)){
619 if(getMap().okayToPlaceStructure(placeLocationX, placeLocationY, newSizeX, newSizeY,
620 false, (itemID == Structure_ConstructionYard) ? nullptr : getHouse())) {
621
622 int placeLocationEndX = placeLocationX + newSizeX;
623 int placeLocationEndY = placeLocationY + newSizeY;
624
625 bool alignedX = (placeLocationX == existingStartX && sizeMatchX);
626 bool alignedY = (placeLocationY == existingStartY && sizeMatchY);
627
628 // bool placeGapExists = (placeLocationEndX < existingStartX || placeLocationX > existingEndX || placeLocationEndY < existingStartY || placeLocationY > existingEndY);
629
630 // How many free spaces the building will have if placed
631 for(int i = placeLocationX-1; i <= placeLocationEndX; i++) {
632 for(int j = placeLocationY-1; j <= placeLocationEndY; j++) {
633 if(getMap().tileExists(i,j) && (getMap().getSizeX() > i) && (0 <= i) && (getMap().getSizeY() > j) && (0 <= j)) {
634 // Penalise if near edge of map
635 if((i == 0) || (i == getMap().getSizeX() - 1) || (j == 0) || (j == getMap().getSizeY() - 1)) {
636 buildLocationScore[placeLocationX][placeLocationY] -= 10;
637 }
638
639 if(getMap().getTile(i,j)->hasAStructure()) {
640 // If one of our buildings is nearby favour the location
641 // if it is someone elses building don't favour it
642 if(getMap().getTile(i,j)->getOwner() == getHouse()->getHouseID()){
643 buildLocationScore[placeLocationX][placeLocationY]+=3;
644 } else{
645 buildLocationScore[placeLocationX][placeLocationY]-=10;
646 }
647 } else if(!getMap().getTile(i,j)->isRock()){
648 // square isn't rock, favour it
649 buildLocationScore[placeLocationX][placeLocationY]+=1;
650 } else if(getMap().getTile(i,j)->hasAGroundObject()){
651 if(getMap().getTile(i,j)->getOwner() != getHouse()->getHouseID()){
652 // try not to build next to units which aren't yours
653 buildLocationScore[placeLocationX][placeLocationY]-=100;
654 } else if(itemID != Structure_RocketTurret){
655 buildLocationScore[placeLocationX][placeLocationY]-=20;
656 }
657 }
658 } else {
659 // penalise if on edge of map
660 buildLocationScore[placeLocationX][placeLocationY]-=200;
661 }
662 }
663 }
664
665 //encourage structure alignment
666 if(alignedX) {
667 buildLocationScore[placeLocationX][placeLocationX] += 10;
668 }
669
670 if(alignedY) {
671 buildLocationScore[placeLocationX][placeLocationY] += 10;
672 }
673
674 // Add building specific scores
675 if(existingIsBuilder || itemID == Structure_GunTurret || itemID == Structure_RocketTurret){
676 buildLocationScore[placeLocationX][placeLocationY] -= lround(blockDistance(squadRallyLocation, Coord(placeLocationX,placeLocationY))/2);
677
678 buildLocationScore[placeLocationX][placeLocationY] -= lround(blockDistance(findBaseCentre(getHouse()->getHouseID()), Coord(placeLocationX,placeLocationY)));
679 }
680
681 // Pick this location if it has the best score
682 if (buildLocationScore[placeLocationX][placeLocationY] > bestLocationScore) {
683 bestLocationScore = buildLocationScore[placeLocationX][placeLocationY];
684 bestLocationX = placeLocationX;
685 bestLocationY = placeLocationY;
686 //logDebug("Build location for item:%d x:%d y:%d score:%d", itemID, bestLocationX, bestLocationY, bestLocationScore);
687 }
688 }
689 }
690 }
691 }
692 }
693 }
694
695
696 if (bestLocationScore != -10000){
697 bestLocation = Coord(bestLocationX, bestLocationY);
698 }
699
700 return bestLocation;
701 }
702
703
build(int militaryValue)704 void QuantBot::build(int militaryValue) {
705 int houseID = getHouse()->getHouseID();
706 auto& data = currentGame->objectData.data;
707
708 int itemCount[Num_ItemID];
709 for (int i = ItemID_FirstID; i <= ItemID_LastID; i++ ) {
710 itemCount[i] = getHouse()->getNumItems(i);
711 }
712
713 int activeHeavyFactoryCount = 0;
714 int activeRepairYardCount = 0;
715
716 // Let's try just running this once...
717 if(squadRallyLocation.isInvalid()) {
718 if(gameMode == GameMode::Campaign) {
719 squadRallyLocation = findSquadRallyLocation();
720 squadRetreatLocation = findSquadRetreatLocation();
721 } else {
722 retreatAllUnits();
723 }
724 }
725
726 // Next add in the objects we are building
727 for(const StructureBase* pStructure : getStructureList()) {
728 if(pStructure->getOwner() == getHouse()) {
729 if(pStructure->isABuilder()) {
730 const BuilderBase* pBuilder = dynamic_cast<const BuilderBase*>(pStructure);
731 if(pBuilder->getProductionQueueSize() > 0){
732 itemCount[pBuilder->getCurrentProducedItem()]++;
733 if(pBuilder->getItemID() == Structure_HeavyFactory){
734 activeHeavyFactoryCount++;
735 }
736 }
737 } else if(pStructure->getItemID() == Structure_RepairYard) {
738 const RepairYard* pRepairYard= dynamic_cast<const RepairYard*>(pStructure);
739 if(!pRepairYard->isFree()) {
740 activeRepairYardCount++;
741 }
742
743 }
744
745 // Set unit deployment position
746 if(pStructure->getItemID() == Structure_Barracks
747 || pStructure->getItemID() == Structure_WOR
748 || pStructure->getItemID() == Structure_LightFactory
749 || pStructure->getItemID() == Structure_HeavyFactory
750 || pStructure->getItemID() == Structure_RepairYard
751 || pStructure->getItemID() == Structure_StarPort) {
752 doSetDeployPosition(pStructure, squadRallyLocation.x, squadRallyLocation.y);
753 }
754 }
755
756
757 }
758
759 int money = getHouse()->getCredits();
760
761 if(militaryValue > 0 || getHouse()->getNumStructures() > 0) {
762 logDebug( " att: %d crdt: %d mVal: %d/%d built: %d kill: %d loss: %d hvstr: %d/%d",
763 attackTimer, getHouse()->getCredits(), militaryValueLimit, militaryValue, getHouse()->getUnitBuiltValue(),
764 getHouse()->getKillValue(), getHouse()->getLossValue(), getHouse()->getNumItems(Unit_Harvester), harvesterLimit);
765 }
766
767
768 // Second attempt at unit prioritisation
769 // This algorithm calculates damage dealt over units lost value for each unit type
770 // referred to as damage loss ratio (dlr)
771 // It then prioritises the build of units with a higher dlr
772 FixPoint dlrTank = getHouse()->getNumItemDamageInflicted(Unit_Tank) / FixPoint((1 + getHouse()->getNumLostItems(Unit_Tank)) * data[Unit_Tank][houseID].price);
773 FixPoint dlrSiege = getHouse()->getNumItemDamageInflicted(Unit_SiegeTank) / FixPoint((1 + getHouse()->getNumLostItems(Unit_SiegeTank)) * data[Unit_SiegeTank][houseID].price);
774 int numSpecialUnitsDamageInflicted = getHouse()->getNumItemDamageInflicted(Unit_Devastator) + getHouse()->getNumItemDamageInflicted(Unit_SonicTank) + getHouse()->getNumItemDamageInflicted(Unit_Deviator);
775 int weightedNumLostSpecialUnits = (getHouse()->getNumLostItems(Unit_Devastator) * data[Unit_Devastator][houseID].price)
776 + (getHouse()->getNumLostItems(Unit_SonicTank) * data[Unit_SonicTank][houseID].price)
777 + (getHouse()->getNumLostItems(Unit_Deviator) * data[Unit_Deviator][houseID].price)
778 + 700; // middle ground 1 for special units
779 FixPoint dlrSpecial = FixPoint(numSpecialUnitsDamageInflicted) / FixPoint(weightedNumLostSpecialUnits);
780 FixPoint dlrLauncher = getHouse()->getNumItemDamageInflicted(Unit_Launcher) / FixPoint((1 + getHouse()->getNumLostItems(Unit_Launcher)) * data[Unit_Launcher][houseID].price);
781 FixPoint dlrOrnithopter = getHouse()->getNumItemDamageInflicted(Unit_Ornithopter) / FixPoint((1 + getHouse()->getNumLostItems(Unit_Ornithopter)) * data[Unit_Ornithopter][houseID].price);
782
783 Sint32 totalDamage = getHouse()->getNumItemDamageInflicted(Unit_Tank)
784 + getHouse()->getNumItemDamageInflicted(Unit_SiegeTank)
785 + getHouse()->getNumItemDamageInflicted(Unit_Devastator)
786 + getHouse()->getNumItemDamageInflicted(Unit_Launcher)
787 + getHouse()->getNumItemDamageInflicted(Unit_Ornithopter);
788
789 // Harkonnen can't build ornithopers
790 if(houseID == HOUSE_HARKONNEN){
791 dlrOrnithopter = 0;
792 }
793
794 // Ordos can't build Launchers
795 if(houseID == HOUSE_ORDOS){
796 dlrLauncher = 0;
797 }
798
799 // Sonic tanks can get into negative damage territory
800 if(dlrSpecial < 0){
801 dlrSpecial = 0;
802 }
803
804 FixPoint dlrTotal = dlrTank + dlrSiege + dlrSpecial + dlrLauncher + dlrOrnithopter;
805
806 if(dlrTotal < 0){
807 dlrTotal = 0;
808 }
809
810 logDebug("Dmg: %d DLR: %f", totalDamage, dlrTotal.toFloat());
811
812 /// Calculate ratios of launcher, special and light tanks. Remainder will be tank
813 FixPoint launcherPercent = dlrLauncher / dlrTotal;
814 FixPoint specialPercent = dlrSpecial / dlrTotal;
815 FixPoint siegePercent = dlrSiege / dlrTotal;
816 FixPoint ornithopterPercent = dlrOrnithopter / dlrTotal;
817 FixPoint tankPercent = dlrTank / dlrTotal;
818
819 // If we haven't done much damage just keep all ratios at optimised defaults
820 // These ratios are based on end game stats over a number of AI test runs to see
821 // Which units perform. By and large launchers and siege tanks have the best damage to loss ratio
822 if(totalDamage < 3000){
823 switch (houseID) {
824 case HOUSE_HARKONNEN:
825 launcherPercent = FixPt(0,5);
826 specialPercent = FixPt(0,15);
827 siegePercent = FixPt(0,35);
828 ornithopterPercent = FixPt(0,0);
829 break;
830
831 case HOUSE_ORDOS:
832 launcherPercent = FixPt(0,0); // Don't have these
833 specialPercent = FixPt(0,25);
834 siegePercent = FixPt(0,75);
835 ornithopterPercent = FixPt(0,05);
836 break;
837
838 default:
839 launcherPercent = FixPt(0,40);
840 specialPercent = FixPt(0,10);
841 siegePercent = FixPt(0,35);
842 ornithopterPercent = FixPt(0,15);
843
844 break;
845 }
846 }
847
848 // lets analyse damage inflicted
849
850 logDebug(" Tank: %d/%d %f Siege: %d/%d %f Special: %d/%d %f Launch: %d/%d %f Orni: %d/%d %f",
851 getHouse()->getNumItemDamageInflicted(Unit_Tank), getHouse()->getNumLostItems(Unit_Tank) * 300, tankPercent.toDouble(),
852 getHouse()->getNumItemDamageInflicted(Unit_SiegeTank), getHouse()->getNumLostItems(Unit_SiegeTank) * 600, siegePercent.toDouble(),
853 getHouse()->getNumItemDamageInflicted(Unit_SonicTank) + getHouse()->getNumItemDamageInflicted(Unit_Devastator) + getHouse()->getNumItemDamageInflicted(Unit_Deviator),
854 getHouse()->getNumLostItems(Unit_SonicTank) * 600 + getHouse()->getNumLostItems(Unit_Devastator) * 800 + getHouse()->getNumLostItems(Unit_Deviator) * 750,
855 specialPercent.toDouble(),
856 getHouse()->getNumItemDamageInflicted(Unit_Launcher), getHouse()->getNumLostItems(Unit_Launcher) * 450, launcherPercent.toDouble(),
857 getHouse()->getNumItemDamageInflicted(Unit_Ornithopter), getHouse()->getNumLostItems(Unit_Ornithopter) * data[Unit_Ornithopter][houseID].price, ornithopterPercent.toDouble()
858 );
859
860
861 // End of adaptive unit prioritisation algorithm
862
863
864 for(const StructureBase* pStructure : getStructureList()) {
865 if(pStructure->getOwner() == getHouse()) {
866 if((pStructure->isRepairing() == false)
867 && (pStructure->getHealth() < pStructure->getMaxHealth())
868 && (!getGameInitSettings().getGameOptions().concreteRequired
869 || pStructure->getItemID() == Structure_Palace) // Palace repairs for free
870 && (pStructure->getItemID() != Structure_Refinery
871 && pStructure->getItemID() != Structure_Silo
872 && pStructure->getItemID() != Structure_Radar
873 && pStructure->getItemID() != Structure_WindTrap))
874 {
875 doRepair(pStructure);
876 } else if( (pStructure->isRepairing() == false)
877 && (pStructure->getHealth() < pStructure->getMaxHealth() * FixPt(0,45))
878 && !getGameInitSettings().getGameOptions().concreteRequired
879 && money > 1000) {
880 doRepair(pStructure);
881 } else if( (pStructure->isRepairing() == false) && money > 5000){
882 // Repair if we are rich
883 doRepair(pStructure);
884 } else if(pStructure->getItemID() == Structure_RocketTurret) {
885 if(!getGameInitSettings().getGameOptions().structuresDegradeOnConcrete || pStructure->hasATarget()) {
886 doRepair(pStructure);
887 }
888 }
889
890 // Special weapon launch logic
891 if(pStructure->getItemID() == Structure_Palace) {
892
893 const Palace* pPalace = dynamic_cast<const Palace*>(pStructure);
894
895 if(pPalace != nullptr){
896 if(pPalace->isSpecialWeaponReady()){
897
898 if(houseID != HOUSE_HARKONNEN && houseID != HOUSE_SARDAUKAR) {
899 doSpecialWeapon(pPalace);
900 } else {
901 int enemyHouseID = -1;
902 int enemyHouseBuildingCount = 0;
903
904 for(int i = 0; i < NUM_HOUSES; i++) {
905 if(getHouse(i) != nullptr) {
906 if(getHouse(i)->getTeam() != getHouse()->getTeam() && getHouse(i)->getNumStructures() > enemyHouseBuildingCount) {
907 enemyHouseBuildingCount = getHouse(i)->getNumStructures();
908 enemyHouseID = i;
909 }
910 }
911 }
912
913 if((enemyHouseID != -1) && (houseID == HOUSE_HARKONNEN || houseID == HOUSE_SARDAUKAR)) {
914 Coord target = findBaseCentre(enemyHouseID);
915 doLaunchDeathhand(pPalace, target.x, target.y);
916 }
917 }
918 }
919 }
920 }
921
922
923 /* First attempt of unit prioritisation
924 We this algorithm prioritises units with the lowest loss ratio
925 The idea is if a unit is less likely to die the AI should have
926 a higher ratio of that unit in its army
927
928 At the moment it takes in special, light tanks and launchers
929 The default is siege tanks otherwise
930
931
932 int launcherLosses = getHouse()->getNumLostItems(Unit_Launcher)
933 * data[Unit_Devastator][houseID].price;
934
935 int specialLosses = getHouse()->getNumLostItems(Unit_SonicTank)
936 * data[Unit_SonicTank][houseID].price + getHouse()->getNumLostItems(Unit_Deviator)
937 * data[Unit_Deviator][houseID].price + getHouse()->getNumLostItems(Unit_Devastator)
938 * data[Unit_Devastator][houseID].price;
939
940 int lightLosses = getHouse()->getNumLostItems(Unit_Tank)
941 * data[Unit_Tank][houseID].price;
942
943 int siegeLosses = getHouse()->getNumLostItems(Unit_SiegeTank)
944 * data[Unit_SiegeTank][houseID].price;
945
946 int ornithopterLosses = getHouse()->getNumLostItems(Unit_Ornithopter)
947 * data[Unit_Ornithopter][houseID].price;
948
949 int totalLosses = launcherLosses + specialLosses + lightLosses + siegeLosses + ornithopterLosses;
950
951
952
953 //Effectively I'm solving a simultaneous equation
954 //There's probably an easier way involving matrices but this works
955
956
957
958 FixPoint launcherWeight = FixPoint((totalLosses - launcherLosses) + 1) / (launcherLosses+1);
959 FixPoint specialWeight = FixPoint((totalLosses - specialLosses) + 1) / (specialLosses+1);
960 FixPoint lightWeight = FixPoint((totalLosses - lightLosses) + 1) / (lightLosses+1);
961 FixPoint siegeWeight = FixPoint((totalLosses - siegeLosses) + 1) / (siegeLosses+1);
962 FixPoint ornithopterWeight = FixPoint((totalLosses - ornithopterLosses) + 1) / (ornithopterLosses+1);
963
964 FixPoint totalWeight = launcherWeight + specialWeight + lightWeight + siegeWeight + ornithopterWeight;
965
966 // Apply house specific logic
967 if(houseID == HOUSE_HARKONNEN){
968 totalWeight -= ornithopterWeight;
969 }
970
971 if(houseID == HOUSE_ATREIDES){
972 totalWeight -= specialWeight;
973 }
974
975
976 if(houseID == HOUSE_ORDOS){
977 totalWeight -= launcherWeight;
978 }
979
980 /// Calculate ratios of launcher, special and light tanks. Remainder will be tank
981 FixPoint launcherPercent = launcherWeight / totalWeight;
982 FixPoint specialPercent = specialWeight / totalWeight;
983 FixPoint siegePercent = siegeWeight / totalWeight;
984 FixPoint ornithopterPercent = ornithopterWeight / totalWeight;
985
986 */
987
988 // End of unit ratio optimisation algorithm
989
990 const BuilderBase* pBuilder = dynamic_cast<const BuilderBase*>(pStructure);
991 if(pBuilder != nullptr) {
992 switch (pStructure->getItemID()) {
993
994 case Structure_LightFactory: {
995 if(!pBuilder->isUpgrading()
996 && gameMode == GameMode::Campaign
997 && money > 1000
998 && ((itemCount[Structure_HeavyFactory] == 0) || militaryValue < militaryValueLimit * FixPt(0,30))
999 && pBuilder->getProductionQueueSize() < 1
1000 && pBuilder->getBuildListSize() > 0
1001 && militaryValue < militaryValueLimit) {
1002
1003 if(pBuilder->getCurrentUpgradeLevel() < pBuilder->getMaxUpgradeLevel() && getHouse()->getCredits() > 1500){
1004 doUpgrade(pBuilder);
1005 } else if(!getHouse()->isGroundUnitLimitReached()) {
1006 Uint32 itemID = NONE_ID;
1007
1008 if(pBuilder->isAvailableToBuild(Unit_RaiderTrike)) {
1009 itemID = Unit_RaiderTrike;
1010 } else if(pBuilder->isAvailableToBuild(Unit_Quad)) {
1011 itemID = Unit_Quad;
1012 } else if(pBuilder->isAvailableToBuild(Unit_Trike)) {
1013 itemID = Unit_Trike;
1014 }
1015
1016 if(itemID != NONE_ID){
1017 doProduceItem(pBuilder, itemID);
1018 itemCount[itemID]++;
1019 }
1020 }
1021 }
1022 } break;
1023
1024 case Structure_WOR: {
1025 if(!pBuilder->isUpgrading()
1026 && pBuilder->isAvailableToBuild(Unit_Trooper)
1027 && gameMode == GameMode::Campaign
1028 && money > 1000
1029 && ((itemCount[Structure_HeavyFactory] == 0) || militaryValue < militaryValueLimit * FixPt(0,30))
1030 && pBuilder->getProductionQueueSize() < 1
1031 && pBuilder->getBuildListSize() > 0
1032 && !getHouse()->isInfantryUnitLimitReached()
1033 && militaryValue < militaryValueLimit) {
1034
1035 doProduceItem(pBuilder, Unit_Trooper);
1036 itemCount[Unit_Trooper]++;
1037 }
1038 } break;
1039
1040 case Structure_Barracks: {
1041 if(!pBuilder->isUpgrading()
1042 && pBuilder->isAvailableToBuild(Unit_Soldier)
1043 && gameMode == GameMode::Campaign
1044 && ((itemCount[Structure_HeavyFactory] == 0) || militaryValue < militaryValueLimit * FixPt(0,30))
1045 && itemCount[Structure_WOR == 0]
1046 && money > 1000
1047 && pBuilder->getProductionQueueSize() < 1
1048 && pBuilder->getBuildListSize() > 0
1049 && !getHouse()->isInfantryUnitLimitReached()
1050 && militaryValue < militaryValueLimit){
1051
1052 doProduceItem(pBuilder, Unit_Soldier);
1053 itemCount[Unit_Soldier]++;
1054 }
1055 } break;
1056
1057 case Structure_HighTechFactory: {
1058 int ornithopterValue = data[Unit_Ornithopter][houseID].price * itemCount[Unit_Ornithopter];
1059
1060 if(pBuilder->isAvailableToBuild(Unit_Carryall)
1061 && itemCount[Unit_Carryall] < (militaryValue + itemCount[Unit_Harvester] * 500) / 3000
1062 && (pBuilder->getProductionQueueSize() < 1)
1063 && money > 1000
1064 && !getHouse()->isAirUnitLimitReached()){
1065 doProduceItem(pBuilder, Unit_Carryall);
1066 itemCount[Unit_Carryall]++;
1067 } else if((money > 500) && (pBuilder->isUpgrading() == false) && (pBuilder->getCurrentUpgradeLevel() < pBuilder->getMaxUpgradeLevel())) {
1068 if (pBuilder->getHealth() >= pBuilder->getMaxHealth()) {
1069 doUpgrade(pBuilder);
1070 } else {
1071 doRepair(pBuilder);
1072 }
1073 } else if( pBuilder->isAvailableToBuild(Unit_Ornithopter)
1074 && (militaryValue * ornithopterPercent > ornithopterValue)
1075 && (pBuilder->getProductionQueueSize() < 1)
1076 && !getHouse()->isAirUnitLimitReached()
1077 && money > 1200){
1078 // Current value and what percentage of military we want used to determine
1079 // whether to build an additional unit.
1080 doProduceItem(pBuilder, Unit_Ornithopter);
1081 itemCount[Unit_Ornithopter]++;
1082 money -= data[Unit_Ornithopter][houseID].price;
1083 militaryValue += data[Unit_Ornithopter][houseID].price;
1084 }
1085 } break;
1086
1087 case Structure_HeavyFactory: {
1088 // only if the factory isn't busy
1089 if((pBuilder->isUpgrading() == false) && (pBuilder->getProductionQueueSize() < 1) && (pBuilder->getBuildListSize() > 0)) {
1090 // we need a construction yard. Build an MCV if we don't have a starport
1091 if( (difficulty == Difficulty::Hard || difficulty == Difficulty::Brutal)
1092 && itemCount[Unit_MCV] + itemCount[Structure_ConstructionYard] + itemCount[Structure_StarPort] < 1
1093 && pBuilder->isAvailableToBuild(Unit_MCV)
1094 && !getHouse()->isGroundUnitLimitReached()) {
1095 doProduceItem(pBuilder, Unit_MCV);
1096 itemCount[Unit_MCV]++;
1097 } else if(gameMode == GameMode::Custom && (itemCount[Structure_ConstructionYard] + itemCount[Unit_MCV] )*3500 < getHouse()->getCredits()
1098 && pBuilder->isAvailableToBuild(Unit_MCV)
1099 && itemCount[Structure_ConstructionYard] + itemCount[Unit_MCV] < 10
1100 && !getHouse()->isGroundUnitLimitReached()
1101 && militaryValue * 2 > militaryValueLimit){
1102 // If we are really rich, like in all against Atriedes
1103 doProduceItem(pBuilder, Unit_MCV);
1104 itemCount[Unit_MCV]++;
1105 } else if(gameMode == GameMode::Custom
1106 && (itemCount[Structure_ConstructionYard] + itemCount[Unit_MCV] ) * 10000 < getHouse()->getCredits()
1107 && !getHouse()->isGroundUnitLimitReached()
1108 && pBuilder->isAvailableToBuild(Unit_MCV)) {
1109 // If we are kind of rich make a backup construction yard to spend the excess money
1110 doProduceItem(pBuilder, Unit_MCV);
1111 itemCount[Unit_MCV]++;
1112 } else if(gameMode == GameMode::Custom
1113 && pBuilder->isAvailableToBuild(Unit_Harvester)
1114 && !getHouse()->isGroundUnitLimitReached()
1115 && itemCount[Unit_Harvester] < militaryValue / 1000
1116 && itemCount[Unit_Harvester] < harvesterLimit ) {
1117 // In case we get given lots of money, it will eventually run out so we need to be prepared
1118 doProduceItem(pBuilder, Unit_Harvester);
1119 itemCount[Unit_Harvester]++;
1120 } else if(itemCount[Unit_Harvester] < harvesterLimit
1121 && pBuilder->isAvailableToBuild(Unit_Harvester)
1122 && !getHouse()->isGroundUnitLimitReached()
1123 && (money < 2500 || gameMode == GameMode::Campaign)) {
1124 //logDebug("*Building a Harvester.",
1125 //itemCount[Unit_Harvester], harvesterLimit, money);
1126 doProduceItem(pBuilder, Unit_Harvester);
1127 itemCount[Unit_Harvester]++;
1128 } else if((money > 500) && (pBuilder->isUpgrading() == false) && (pBuilder->getCurrentUpgradeLevel() < pBuilder->getMaxUpgradeLevel())) {
1129 if (pBuilder->getHealth() >= pBuilder->getMaxHealth()){
1130 doUpgrade(pBuilder);
1131 } else {
1132 doRepair(pBuilder);
1133 }
1134 } else if(money > 1200 && militaryValue < militaryValueLimit && !getHouse()->isGroundUnitLimitReached()) {
1135 // TODO: This entire section needs to be refactored to make it more generic
1136 // Limit enemy military units based on difficulty
1137
1138 // Calculate current value of units
1139 int launcherValue = data[Unit_Launcher][houseID].price * itemCount[Unit_Launcher];
1140 int specialValue = data[Unit_Devastator][houseID].price * itemCount[Unit_Devastator]
1141 + data[Unit_Deviator][houseID].price * itemCount[Unit_Deviator]
1142 + data[Unit_SonicTank][houseID].price * itemCount[Unit_SonicTank];
1143 int siegeValue = data[Unit_SiegeTank][houseID].price * itemCount[Unit_SiegeTank];
1144
1145
1146 /// Use current value and what percentage of military we want to determine
1147 /// whether to build an additional unit.
1148 if( pBuilder->isAvailableToBuild(Unit_Launcher) && (militaryValue * launcherPercent > launcherValue)) {
1149 doProduceItem(pBuilder, Unit_Launcher);
1150 itemCount[Unit_Launcher]++;
1151 money -= data[Unit_Launcher][houseID].price;
1152 militaryValue += data[Unit_Launcher][houseID].price;
1153 } else if( pBuilder->isAvailableToBuild(Unit_Devastator) && (militaryValue * specialPercent > specialValue)) {
1154 doProduceItem(pBuilder, Unit_Devastator);
1155 itemCount[Unit_Devastator]++;
1156 money -= data[Unit_Devastator][houseID].price;
1157 militaryValue += data[Unit_Devastator][houseID].price;
1158 } else if( pBuilder->isAvailableToBuild(Unit_SonicTank) && (militaryValue * specialPercent > specialValue)) {
1159 doProduceItem(pBuilder, Unit_SonicTank);
1160 itemCount[Unit_SonicTank]++;
1161 money -= data[Unit_SonicTank][houseID].price;
1162 militaryValue += data[Unit_SonicTank][houseID].price;
1163 } else if( pBuilder->isAvailableToBuild(Unit_Deviator) && (militaryValue * specialPercent > specialValue)) {
1164 doProduceItem(pBuilder, Unit_Deviator);
1165 itemCount[Unit_Deviator]++;
1166 money -= data[Unit_Deviator][houseID].price;
1167 militaryValue += data[Unit_Deviator][houseID].price;
1168 } else if( pBuilder->isAvailableToBuild(Unit_SiegeTank) && (militaryValue * siegePercent > siegeValue)) {
1169 doProduceItem(pBuilder, Unit_SiegeTank);
1170 itemCount[Unit_SiegeTank]++;
1171 money -= data[Unit_Tank][houseID].price;
1172 militaryValue += data[Unit_SiegeTank][houseID].price;
1173 } else if(pBuilder->isAvailableToBuild(Unit_Tank)) {
1174 // Tanks for all else
1175 doProduceItem(pBuilder, Unit_Tank);
1176 itemCount[Unit_Tank]++;
1177 money -= data[Unit_Tank][houseID].price;
1178 militaryValue += data[Unit_Tank][houseID].price;
1179 }
1180 }
1181 }
1182
1183 } break;
1184
1185 case Structure_StarPort: {
1186 const StarPort* pStarPort = dynamic_cast<const StarPort*>(pBuilder);
1187 if(pStarPort->okToOrder()) {
1188 const Choam& choam = getHouse()->getChoam();
1189
1190 // We need a construction yard!!
1191 if((difficulty == Difficulty::Hard || difficulty == Difficulty::Brutal)
1192 && pStarPort->isAvailableToBuild(Unit_MCV)
1193 && choam.getNumAvailable(Unit_MCV) > 0
1194 && itemCount[Structure_ConstructionYard] + itemCount[Unit_MCV] < 1) {
1195 doProduceItem(pBuilder, Unit_MCV);
1196 itemCount[Unit_MCV]++;
1197 money = money - choam.getPrice(Unit_MCV);
1198 }
1199
1200 if(money >= choam.getPrice(Unit_Carryall)
1201 && choam.getNumAvailable(Unit_Carryall) > 0
1202 && itemCount[Unit_Carryall] == 0) {
1203 doProduceItem(pBuilder, Unit_Carryall);
1204 itemCount[Unit_Carryall]++;
1205 money = money - choam.getPrice(Unit_Carryall);
1206 } else if(militaryValue > (itemCount[Unit_Harvester]*200)) {
1207 while (money > choam.getPrice(Unit_Harvester) && choam.getNumAvailable(Unit_Harvester) > 0 && itemCount[Unit_Harvester] < harvesterLimit){
1208 doProduceItem(pBuilder, Unit_Harvester);
1209 itemCount[Unit_Harvester]++;
1210 money = money - choam.getPrice(Unit_Harvester);
1211 }
1212
1213 int itemCountUnits = itemCount[Unit_Tank] + itemCount[Unit_SiegeTank] + itemCount[Unit_Launcher] + itemCount[Unit_Quad] + itemCount[Unit_Harvester];
1214 while (money > choam.getPrice(Unit_Carryall) && choam.getNumAvailable(Unit_Carryall) > 0 && itemCount[Unit_Carryall] < itemCountUnits / 5) {
1215 doProduceItem(pBuilder, Unit_Carryall);
1216 itemCount[Unit_Carryall]++;
1217 money = money - choam.getPrice(Unit_Carryall);
1218 }
1219 }
1220
1221 if (money > choam.getPrice(Unit_Carryall) && choam.getNumAvailable(Unit_Carryall) > 0 && itemCount[Unit_Carryall] == 0) {
1222 // Get at least one Carryall
1223 doProduceItem(pBuilder, Unit_Carryall);
1224 itemCount[Unit_Carryall]++;
1225 money = money - choam.getPrice(Unit_Carryall);
1226 }
1227
1228 if(militaryValue < militaryValueLimit && itemCount[Unit_Carryall] > 0 ) {
1229 while (money > choam.getPrice(Unit_SiegeTank) && choam.getNumAvailable(Unit_SiegeTank) > 0
1230 && choam.isCheap(Unit_SiegeTank) && militaryValue < militaryValueLimit) {
1231 doProduceItem(pBuilder, Unit_SiegeTank);
1232 itemCount[Unit_SiegeTank]++;
1233 money = money - choam.getPrice(Unit_SiegeTank);
1234 militaryValue += data[Unit_SiegeTank][houseID].price;
1235 }
1236
1237 while (money > choam.getPrice(Unit_Tank) && choam.getNumAvailable(Unit_Tank) > 0
1238 && choam.isCheap(Unit_Tank) && militaryValue < militaryValueLimit) {
1239 doProduceItem(pBuilder, Unit_Tank);
1240 itemCount[Unit_Tank]++;
1241 money = money - choam.getPrice(Unit_Tank);
1242 militaryValue += data[Unit_Tank][houseID].price;
1243 }
1244
1245 while (money > choam.getPrice(Unit_Launcher) && choam.getNumAvailable(Unit_Launcher) > 0
1246 && choam.isCheap(Unit_Launcher) && militaryValue < militaryValueLimit && militaryValue > 1000) {
1247 doProduceItem(pBuilder, Unit_Launcher);
1248 itemCount[Unit_Launcher]++;
1249 money = money - choam.getPrice(Unit_Launcher);
1250 militaryValue += data[Unit_Launcher][houseID].price;
1251 }
1252
1253 while (money > choam.getPrice(Unit_Quad) && choam.getNumAvailable(Unit_Quad) > 0
1254 && choam.isCheap(Unit_Quad) && militaryValue * 10 < militaryValueLimit) {
1255 doProduceItem(pBuilder, Unit_Quad);
1256 itemCount[Unit_Quad]++;
1257 money = money - choam.getPrice(Unit_Quad);
1258 militaryValue += data[Unit_Quad][houseID].price;
1259 }
1260
1261 while (money > choam.getPrice(Unit_Trike) && choam.getNumAvailable(Unit_Trike) > 0
1262 && choam.isCheap(Unit_Trike) && militaryValue * 10 < militaryValueLimit) {
1263 doProduceItem(pBuilder, Unit_Trike);
1264 itemCount[Unit_Trike]++;
1265 money = money - choam.getPrice(Unit_Trike);
1266 militaryValue += data[Unit_Trike][houseID].price;
1267 }
1268 }
1269
1270 doPlaceOrder(pStarPort);
1271 }
1272
1273 } break;
1274
1275 case Structure_ConstructionYard: {
1276
1277 // If rocket turrets don't need power then let's build some for defense
1278 int rocketTurretValue = itemCount[Structure_RocketTurret] * 250;
1279
1280 if(getGameInitSettings().getGameOptions().rocketTurretsNeedPower) {
1281 rocketTurretValue = 1000000; // If rocket turrets need power we don't want to build them
1282 }
1283
1284 const ConstructionYard* pConstYard = dynamic_cast<const ConstructionYard*>(pBuilder);
1285
1286 if(!pBuilder->isUpgrading() && getHouse()->getCredits() > 100 && (pBuilder->getProductionQueueSize() < 1) && pBuilder->getBuildListSize()) {
1287
1288 // Campaign Build order, iterate through the buildings, if the number that exist
1289 // is less than the number that should exist, then build the one that is missing
1290
1291 if(gameMode == GameMode::Campaign && difficulty != Difficulty::Brutal) {
1292 //logDebug("GameMode Campaign.. ");
1293
1294 for(int i = Structure_FirstID; i <= Structure_LastID; i++){
1295 if(itemCount[i] < initialItemCount[i]
1296 && pBuilder->isAvailableToBuild(i)
1297 && findPlaceLocation(i).isValid()
1298 && !pBuilder->isUpgrading()
1299 && pBuilder->getProductionQueueSize() < 1) {
1300
1301 logDebug("***CampAI Build itemID: %o structure count: %o, initial count: %o", i, itemCount[i], initialItemCount[i]);
1302 doProduceItem(pBuilder, i);
1303 itemCount[i]++;
1304 }
1305 }
1306
1307 // If Campaign AI can't build military, let it build up its cash reserves and defenses
1308
1309 if(pStructure->getHealth() < pStructure->getMaxHealth()) {
1310 doRepair(pBuilder);
1311 } else if(pBuilder->getCurrentUpgradeLevel() < pBuilder->getMaxUpgradeLevel()
1312 && !pBuilder->isUpgrading()
1313 && itemCount[Unit_Harvester] >= harvesterLimit) {
1314
1315 doUpgrade(pBuilder);
1316 logDebug("***CampAI Upgrade builder");
1317 } else if((getHouse()->getProducedPower() < getHouse()->getPowerRequirement())
1318 && pBuilder->isAvailableToBuild(Structure_WindTrap)
1319 && findPlaceLocation(Structure_WindTrap).isValid()
1320 && pBuilder->getProductionQueueSize() == 0){
1321
1322 doProduceItem(pBuilder, Structure_WindTrap);
1323 itemCount[Structure_WindTrap]++;
1324
1325 logDebug("***CampAI Build A new Windtrap increasing count to: %d", itemCount[Structure_WindTrap]);
1326 } else if((getHouse()->getCapacity() < getHouse()->getStoredCredits() + 2000)
1327 && pBuilder->isAvailableToBuild(Structure_Silo)
1328 && findPlaceLocation(Structure_Silo).isValid()
1329 && pBuilder->getProductionQueueSize() == 0){
1330
1331 doProduceItem(pBuilder, Structure_Silo);
1332 itemCount[Structure_Silo]++;
1333
1334 logDebug("***CampAI Build A new Silo increasing count to: %d", itemCount[Structure_Silo]);
1335 } else if (money > 3000
1336 && pBuilder->isAvailableToBuild(Structure_RocketTurret)
1337 && findPlaceLocation(Structure_RocketTurret).isValid()
1338 && pBuilder->getProductionQueueSize() == 0
1339 && (itemCount[Structure_RocketTurret] <
1340 (itemCount[Structure_Silo] + itemCount[Structure_Refinery]) * 2)){
1341
1342 doProduceItem(pBuilder, Structure_RocketTurret);
1343 itemCount[Structure_RocketTurret]++;
1344
1345 logDebug("***CampAI Build A new Rocket turret increasing count to: %d", itemCount[Structure_RocketTurret]);
1346 }
1347
1348 buildTimer = getRandomGen().rand(0,3)*5;
1349 } else {
1350 // custom AI starts here:
1351
1352 Uint32 itemID = NONE_ID;
1353
1354 if(itemCount[Structure_WindTrap] == 0 && pBuilder->isAvailableToBuild(Structure_WindTrap)) {
1355 itemID = Structure_WindTrap;
1356 itemCount[Structure_WindTrap]++;
1357 } else if((itemCount[Structure_Refinery] == 0 || itemCount[Structure_Refinery] < itemCount[Unit_Harvester] / 2) && pBuilder->isAvailableToBuild(Structure_Refinery)) {
1358 itemID = Structure_Refinery;
1359 itemCount[Unit_Harvester]++;
1360 itemCount[Structure_Refinery]++;
1361 } else if(itemCount[Structure_Refinery] < 6 - (money / 2000) && pBuilder->isAvailableToBuild(Structure_Refinery)) {
1362 itemID = Structure_Refinery;
1363 itemCount[Unit_Harvester]++;
1364 itemCount[Structure_Refinery]++;
1365 } else if(itemCount[Structure_StarPort] == 0 && pBuilder->isAvailableToBuild(Structure_StarPort) && findPlaceLocation(Structure_StarPort).isValid()) {
1366 itemID = Structure_StarPort;
1367 } else if(itemCount[Unit_Harvester] < (harvesterLimit / 3) && money < 2000
1368 && ((itemCount[Structure_Refinery] < harvesterLimit / 4
1369 && itemCount[Structure_Refinery] < 8)
1370 || itemCount[Structure_HeavyFactory] > 0)
1371 && pBuilder->isAvailableToBuild(Structure_Refinery)) {
1372 // Focus on the economy
1373 itemID = Structure_Refinery;
1374 itemCount[Unit_Harvester]++;
1375 } else if(itemCount[Unit_Harvester] < harvesterLimit / 2 && money < 1200 && pBuilder->isAvailableToBuild(Structure_Refinery)) {
1376 itemID = Structure_Refinery;
1377 itemCount[Unit_Harvester]++;
1378 itemCount[Structure_Refinery]++;
1379 } else if(itemCount[Structure_LightFactory] == 0 && pBuilder->isAvailableToBuild(Structure_LightFactory)) {
1380 itemID = Structure_LightFactory;
1381 } else if(itemCount[Structure_Radar] == 0 && pBuilder->isAvailableToBuild(Structure_Radar)) {
1382 itemID = Structure_Radar;
1383 } else if(itemCount[Structure_HeavyFactory] == 0) {
1384 if(pBuilder->isAvailableToBuild(Structure_HeavyFactory)) {
1385 itemID = Structure_HeavyFactory;
1386 }
1387 } else if(itemCount[Structure_RepairYard] == 0 && pBuilder->isAvailableToBuild(Structure_RepairYard)) {
1388 itemID = Structure_RepairYard;
1389 } else if(money < 2000 && itemCount[Unit_Harvester] < harvesterLimit && pBuilder->isAvailableToBuild(Structure_Refinery)) {
1390 // Focus on the economy
1391 itemID = Structure_Refinery;
1392 itemCount[Unit_Harvester]++;
1393 itemCount[Structure_Refinery]++;
1394 } else if(itemCount[Structure_HighTechFactory] == 0){
1395 if(pBuilder->isAvailableToBuild(Structure_HighTechFactory)) {
1396 itemID = Structure_HighTechFactory;
1397 }
1398 } else if(itemCount[Structure_IX] == 0) {
1399 // Let's trial special units
1400 if(pBuilder->isAvailableToBuild(Structure_IX)) {
1401 itemID = Structure_IX;
1402 }
1403 } else if(pBuilder->isAvailableToBuild(Structure_RepairYard) && money > 500
1404 && (itemCount[Structure_RepairYard] <= activeRepairYardCount
1405 || itemCount[Structure_RepairYard] * 6000 < militaryValue)) {
1406 // If we have a lot of troops get some repair facilities
1407 itemID = Structure_RepairYard;
1408 //logDebug("Build Repair... active: %d total: %d", activeRepairYardCount, getHouse()->getNumItems(Structure_RepairYard));
1409
1410 } else if(pBuilder->isAvailableToBuild(Structure_HeavyFactory) && money > 500
1411 && (itemCount[Structure_HeavyFactory] <= activeHeavyFactoryCount || money > itemCount[Structure_HeavyFactory]*4000)) {
1412 // If we have a lot of money get more heavy factories
1413 itemID = Structure_HeavyFactory;
1414 logDebug("Build Factory... active: %d total: %d", activeHeavyFactoryCount, getHouse()->getNumItems(Structure_HeavyFactory));
1415 } else if(itemCount[Structure_Refinery] * FixPt(3,5) < itemCount[Unit_Harvester] && pBuilder->isAvailableToBuild(Structure_Refinery)) {
1416 itemID = Structure_Refinery;
1417 } else if (getHouse()->getStoredCredits() + 2000 > (itemCount[Structure_Refinery] + itemCount[Structure_Silo]) * 1000 && pBuilder->isAvailableToBuild(Structure_Silo)){
1418 // We are running out of spice storage capacity
1419 itemID = Structure_Silo;
1420 } else if(money > 1200
1421 && pBuilder->isAvailableToBuild(Structure_Palace)
1422 && getGameInitSettings().getGameOptions().onlyOnePalace
1423 && itemCount[Structure_Palace] == 0) {
1424 // Let's build one palace if its available
1425 itemID = Structure_Palace;
1426 } else if(money > 1200
1427 && pBuilder->getCurrentUpgradeLevel() < pBuilder->getMaxUpgradeLevel()
1428 && !getGameInitSettings().getGameOptions().rocketTurretsNeedPower) {
1429 // First off we need to upgrade the construction yard
1430 doUpgrade(pBuilder);
1431 } else if(money > 1200
1432 && rocketTurretValue < militaryValueLimit * FixPt(0,10) + itemCount[Structure_Palace] * 750 + itemCount[Structure_Refinery] * 250
1433 && rocketTurretValue < militaryValue * FixPt(0,25) + itemCount[Structure_Palace] * 750 + itemCount[Structure_Refinery] * 250
1434 && pBuilder->isAvailableToBuild(Structure_RocketTurret)) {
1435 // Lets build turrets based on our military value limit, palaces and silo's
1436 itemID = Structure_RocketTurret;
1437 } else if(money > militaryValueLimit - militaryValue) {
1438 // Here are our luxury items:
1439 // - Rocket Turrets
1440 // - Palaces
1441 // Need to balance saving credits with expenditure on palaces and turrets
1442
1443 //logDebug("Build Luxury.. money: %d mildecifict: %d", money, militaryValueLimit - militaryValue);
1444 if(pBuilder->isAvailableToBuild(Structure_Palace)
1445 && !getGameInitSettings().getGameOptions().onlyOnePalace
1446 && itemCount[Structure_Palace] * 1250 < rocketTurretValue
1447 && money > itemCount[Structure_Palace] * 500){
1448 itemID = Structure_Palace;
1449 } else if(pBuilder->isAvailableToBuild(Structure_RocketTurret) && money > rocketTurretValue) {
1450 itemID = Structure_RocketTurret;
1451 }
1452 }
1453
1454 // TODO: Build concrete if we have bad building spots
1455 if(pBuilder->isAvailableToBuild(itemID) && findPlaceLocation(itemID).isValid() && itemID != NONE_ID) {
1456 doProduceItem(pBuilder, itemID);
1457 itemCount[itemID]++;
1458 }/*else if(pBuilder->isAvailableToBuild(Structure_Slab1) && findPlaceLocation(Structure_Slab1).isValid()){
1459 doProduceItem(pBuilder, Structure_Slab1);
1460 }*/
1461
1462 }
1463 }
1464
1465 if(pBuilder->isWaitingToPlace()) {
1466 Coord location = findPlaceLocation(pBuilder->getCurrentProducedItem());
1467
1468 if(location.isValid()){
1469 doPlaceStructure(pConstYard, location.x, location.y);
1470 } else{
1471 doCancelItem(pConstYard, pBuilder->getCurrentProducedItem());
1472 }
1473 }
1474 } break;
1475 }
1476 }
1477 }
1478 }
1479
1480 buildTimer = getRandomGen().rand(0,3)*5;
1481 }
1482
1483
scrambleUnitsAndDefend(const ObjectBase * pIntruder,int numUnits)1484 void QuantBot::scrambleUnitsAndDefend(const ObjectBase* pIntruder, int numUnits) {
1485 for(const UnitBase* pUnit : getUnitList()) {
1486 if(pUnit->isRespondable() && (pUnit->getOwner() == getHouse())) {
1487 if(!pUnit->hasATarget() && !pUnit->wasForced()) {
1488 Uint32 itemID = pUnit->getItemID();
1489 if((itemID != Unit_Harvester) && (pUnit->getItemID() != Unit_MCV) && (pUnit->getItemID() != Unit_Carryall)
1490 && (pUnit->getItemID() != Unit_Frigate) && (pUnit->getItemID() != Unit_Saboteur) && (pUnit->getItemID() != Unit_Sandworm)) {
1491
1492 doSetAttackMode(pUnit, AREAGUARD);
1493
1494 if(pUnit->getItemID() == Unit_Launcher || pUnit->getItemID() == Unit_Deviator) {
1495 //doAttackObject(pUnit, pIntruder, true);
1496 } else {
1497 doAttackObject(pUnit, pIntruder, true);
1498 }
1499
1500 if(pUnit->isVisible()
1501 && blockDistance(pUnit->getLocation(), pUnit->getDestination()) >= 10
1502 && pUnit->isAGroundUnit()
1503 && pUnit->getHealth() / pUnit->getMaxHealth() > BADLYDAMAGEDRATIO) {
1504
1505 const GroundUnit* pGroundUnit = dynamic_cast<const GroundUnit*>(pUnit);
1506
1507 if(getGameInitSettings().getGameOptions().manualCarryallDrops && pGroundUnit->getItemID() != Unit_Deviator && pGroundUnit->getItemID() != Unit_Launcher) {
1508 doRequestCarryallDrop(pGroundUnit); //do request carryall to defend unit
1509 }
1510 }
1511
1512 if(--numUnits == 0) {
1513 break;
1514 }
1515 }
1516 }
1517 }
1518 }
1519 }
1520
1521
attack(int militaryValue)1522 void QuantBot::attack(int militaryValue) {
1523
1524 /// Logic to make Brutal AI attack more often
1525 /// not using this atm
1526 /*
1527 int tempLim = militaryValueLimit;
1528 if(tempLim > 60000) {
1529 tempLim = 60000;
1530 }
1531
1532 FixPoint strength = (FixPoint(militaryValue) + 1) / (FixPoint(tempLim)) + FixPt(0,03);
1533
1534 FixPoint newAttack = 15000 / strength;
1535
1536 if(newAttack > 100000){
1537 newAttack = 100000;
1538 }
1539 */
1540
1541 // overwriting existing logic for the time being
1542 attackTimer = MILLI2CYCLES(90000);
1543
1544 // only attack if we have 35% of maximum military power on max sized map. Required military power scales down accordingly
1545 if(militaryValue < militaryValueLimit * FixPt(0,35) * currentGameMap->getSizeX() * currentGameMap->getSizeY() / 16384 && militaryValue < 20000) {
1546 return;
1547 }
1548
1549 // In campaign mode don't attack if the attack trigger isn't set
1550 // And don't attack with less than 40% of your limit
1551 if((!campaignAIAttackFlag || militaryValue < militaryValueLimit * FixPt(0,80)) && gameMode == GameMode::Campaign) {
1552 return;
1553 }
1554
1555 int militaryValueToAttackWith = militaryValue;
1556 switch(difficulty) {
1557 case Difficulty::Defend: {
1558 return;
1559 } break;
1560
1561 case Difficulty::Easy: {
1562 militaryValueToAttackWith = militaryValue/5;
1563 } break;
1564
1565 case Difficulty::Medium: {
1566 militaryValueToAttackWith = militaryValue/3;
1567 } break;
1568
1569 case Difficulty::Hard: {
1570 militaryValueToAttackWith = 2*militaryValue/3;
1571 } break;
1572
1573 case Difficulty::Brutal: {
1574 militaryValueToAttackWith = militaryValue;
1575 } break;
1576 }
1577
1578 logDebug( "Attack: house: %d dif: %d mStr: %d mLim: %d attackTimer: %d",
1579 getHouse()->getHouseID(), static_cast<Uint8>(difficulty), militaryValue, militaryValueLimit, attackTimer);
1580
1581 Coord squadCenterLocation = findSquadCenter(getHouse()->getHouseID());
1582
1583 for(const UnitBase *pUnit : getUnitList()) {
1584 if (pUnit->isRespondable()
1585 && (pUnit->getOwner() == getHouse())
1586 && pUnit->isActive()
1587 && (pUnit->getAttackMode() == AREAGUARD)
1588 && pUnit->getItemID() != Unit_Harvester
1589 && pUnit->getItemID() != Unit_MCV
1590 && pUnit->getItemID() != Unit_Carryall
1591 && (pUnit->getItemID() != Unit_Ornithopter || getHouse()->getNumItems(Unit_Ornithopter) > 15)
1592 && (pUnit->getItemID() != Unit_Deviator || getHouse()->getNumItems(Unit_Deviator) > 10)
1593 && pUnit->getHealth() / pUnit->getMaxHealth() > FixPt(0,6)
1594 // Only units within the squad should hunt, safety in numbers
1595 && blockDistance(pUnit->getLocation(), squadCenterLocation) < FixPoint::sqrt(getHouse()->getNumUnits()
1596 - getHouse()->getNumItems(Unit_Harvester)
1597 - getHouse()->getNumItems(Unit_Carryall)
1598 - getHouse()->getNumItems(Unit_Ornithopter)
1599 - getHouse()->getNumItems(Unit_Sandworm)
1600 - getHouse()->getNumItems(Unit_MCV)) + 6)
1601 {
1602 doSetAttackMode(pUnit, HUNT);
1603 militaryValueToAttackWith -= currentGame->objectData.data[pUnit->getItemID()][getHouse()->getHouseID()].price;
1604 if(militaryValueToAttackWith < 0) {
1605 break;
1606 }
1607 }
1608 }
1609
1610 }
1611
1612
findSquadRallyLocation()1613 Coord QuantBot::findSquadRallyLocation() {
1614 int buildingCount = 0;
1615 int totalX = 0;
1616 int totalY = 0;
1617
1618 int enemyBuildingCount = 0;
1619 int enemyTotalX = 0;
1620 int enemyTotalY = 0;
1621
1622 for(const StructureBase* pCurrentStructure : getStructureList()) {
1623 if(pCurrentStructure->getOwner()->getHouseID() == getHouse()->getHouseID()) {
1624 // Lets find the center of mass of our squad
1625 buildingCount++;
1626 totalX += pCurrentStructure->getX();
1627 totalY += pCurrentStructure->getY();
1628 } else if(pCurrentStructure->getOwner()->getTeam() != getHouse()->getTeam()) {
1629 enemyBuildingCount++;
1630 enemyTotalX += pCurrentStructure->getX();
1631 enemyTotalY += pCurrentStructure->getY();
1632 }
1633 }
1634
1635 Coord baseCentreLocation = Coord::Invalid();
1636 if(enemyBuildingCount > 0 && buildingCount > 0) {
1637 baseCentreLocation.x = lround((totalX / buildingCount) * FixPt(0,75) + (enemyTotalX / enemyBuildingCount) * FixPt(0,25));
1638 baseCentreLocation.y = lround((totalY / buildingCount) * FixPt(0,75) + (enemyTotalY / enemyBuildingCount) * FixPt(0,25));
1639 }
1640
1641 //logDebug("Squad rally location: %d, %d", baseCentreLocation.x , baseCentreLocation.y );
1642
1643 return baseCentreLocation;
1644 }
1645
findSquadRetreatLocation()1646 Coord QuantBot::findSquadRetreatLocation() {
1647 Coord newSquadRetreatLocation = Coord::Invalid();
1648
1649 FixPoint closestDistance = FixPt_MAX;
1650 for(const StructureBase* pStructure : getStructureList()) {
1651 // if it is our building, check to see if it is closer to the squad rally point then we are
1652 if(pStructure->getOwner()->getHouseID() == getHouse()->getHouseID()) {
1653 Coord closestStructurePoint = pStructure->getClosestPoint(squadRallyLocation);
1654 FixPoint structureDistance = blockDistance(squadRallyLocation, closestStructurePoint);
1655
1656 if(structureDistance < closestDistance) {
1657 closestDistance = structureDistance;
1658 newSquadRetreatLocation = closestStructurePoint;
1659 }
1660 }
1661 }
1662
1663 return newSquadRetreatLocation;
1664 }
1665
findBaseCentre(int houseID)1666 Coord QuantBot::findBaseCentre(int houseID) {
1667 int buildingCount = 0;
1668 int totalX = 0;
1669 int totalY = 0;
1670
1671 for(const StructureBase* pCurrentStructure : getStructureList()) {
1672 if(pCurrentStructure->getOwner()->getHouseID() == houseID && pCurrentStructure->getStructureSizeX() != 1) {
1673 // Lets find the center of mass of our squad
1674 buildingCount++;
1675 totalX += pCurrentStructure->getX();
1676 totalY += pCurrentStructure->getY();
1677 }
1678 }
1679
1680 Coord baseCentreLocation = Coord::Invalid();
1681
1682 if(buildingCount > 0) {
1683 baseCentreLocation.x = totalX / buildingCount;
1684 baseCentreLocation.y = totalY / buildingCount;
1685 }
1686
1687 return baseCentreLocation;
1688 }
1689
1690
findSquadCenter(int houseID)1691 Coord QuantBot::findSquadCenter(int houseID) {
1692 int squadSize = 0;
1693
1694 int totalX = 0;
1695 int totalY = 0;
1696
1697 for(const UnitBase* pCurrentUnit : getUnitList()) {
1698 if(pCurrentUnit->getOwner()->getHouseID() == houseID
1699 && pCurrentUnit->getItemID() != Unit_Carryall
1700 && pCurrentUnit->getItemID() != Unit_Harvester
1701 && pCurrentUnit->getItemID() != Unit_Frigate
1702 && pCurrentUnit->getItemID() != Unit_MCV
1703
1704 // Stop freeman making tanks roll forward
1705 && !(currentGame->techLevel > 6 && pCurrentUnit->getItemID() == Unit_Trooper)
1706 && pCurrentUnit->getItemID() != Unit_Saboteur
1707 && pCurrentUnit->getItemID() != Unit_Sandworm
1708
1709 // Don't let troops moving to rally point contribute
1710 && pCurrentUnit->getAttackMode() != RETREAT
1711 && pCurrentUnit->getDestination().x != squadRallyLocation.x
1712 && pCurrentUnit->getDestination().y != squadRallyLocation.y) {
1713
1714 // Lets find the center of mass of our squad
1715 squadSize++;
1716 totalX += pCurrentUnit->getX();
1717 totalY += pCurrentUnit->getY();
1718 }
1719
1720 }
1721
1722 Coord squadCenterLocation = Coord::Invalid();
1723
1724 if(squadSize > 0) {
1725 squadCenterLocation.x = totalX / squadSize;
1726 squadCenterLocation.y = totalY / squadSize;
1727 }
1728
1729 return squadCenterLocation;
1730 }
1731
1732 /**
1733 Set a rally / retreat location for all our military units.
1734 This should be near our base but within it
1735 The retreat mode causes all our military units to move
1736 to this squad rally location
1737
1738 */
retreatAllUnits()1739 void QuantBot::retreatAllUnits() {
1740
1741 // Set the new squad rally location
1742 squadRallyLocation = findSquadRallyLocation();
1743 squadRetreatLocation = findSquadRetreatLocation();
1744
1745 // set attck timer down a bit
1746 retreatTimer = MILLI2CYCLES(90000);
1747
1748 // If no base exists yet, there is no retreat location
1749 if(squadRallyLocation.isValid() && squadRetreatLocation.isValid()) {
1750 for(const UnitBase* pUnit : getUnitList()) {
1751 if(pUnit->getOwner() == getHouse()
1752 && pUnit->getItemID() != Unit_Carryall
1753 && pUnit->getItemID() != Unit_Sandworm
1754 && pUnit->getItemID() != Unit_Harvester
1755 && pUnit->getItemID() != Unit_MCV
1756 && pUnit->getItemID() != Unit_Frigate){
1757
1758 doSetAttackMode(pUnit, RETREAT);
1759 }
1760 }
1761 }
1762 }
1763
1764
1765 /**
1766 In dune it is best to mass military units in one location.
1767 This function determines a squad leader by finding the unit with the most central location
1768 Amongst all of a players units.
1769
1770 Rocket launchers and Ornithopters are excluded from having this role as on the
1771 battle field these units should always have other supporting units to work with
1772
1773 */
checkAllUnits()1774 void QuantBot::checkAllUnits() {
1775 Coord squadCenterLocation = findSquadCenter(getHouse()->getHouseID());
1776
1777 for(const UnitBase* pUnit : getUnitList()) {
1778 if(pUnit->getOwner() == getHouse()) {
1779 switch(pUnit->getItemID()) {
1780 case Unit_MCV: {
1781 const MCV* pMCV = dynamic_cast<const MCV*>(pUnit);
1782 if(pMCV != nullptr) {
1783 //logDebug("MCV: forced: %d moving: %d canDeploy: %d",
1784 //pMCV->wasForced(), pMCV->isMoving(), pMCV->canDeploy());
1785
1786 if (pMCV->canDeploy() && !pMCV->wasForced() && !pMCV->isMoving()) {
1787 //logDebug("MCV: Deployed");
1788 doDeploy(pMCV);
1789 } else if(!pMCV->isMoving() && !pMCV->wasForced()) {
1790 Coord pos = findMcvPlaceLocation(pMCV);
1791 doMove2Pos(pMCV, pos.x, pos.y, true);
1792 /*
1793 if(getHouse()->getNumItems(Unit_Carryall) > 0){
1794 doRequestCarryallDrop(pMCV);
1795 }*/
1796 }
1797 }
1798 } break;
1799
1800 case Unit_Harvester: {
1801 const Harvester* pHarvester = dynamic_cast<const Harvester*>(pUnit);
1802 if(getHouse()->getCredits() < 1000 && pHarvester != nullptr && pHarvester->isActive()
1803 && (pHarvester->getAmountOfSpice() >= HARVESTERMAXSPICE/2) && getHouse()->getNumItems(Structure_HeavyFactory) == 0) {
1804 doReturn(pHarvester);
1805 }
1806 } break;
1807
1808 case Unit_Carryall: {
1809 } break;
1810
1811 case Unit_Frigate: {
1812 } break;
1813
1814 case Unit_Sandworm: {
1815 } break;
1816
1817 default: {
1818
1819 int squadRadius = lround(FixPoint::sqrt(getHouse()->getNumUnits()
1820 - getHouse()->getNumItems(Unit_Harvester)
1821 - getHouse()->getNumItems(Unit_Carryall)
1822 - getHouse()->getNumItems(Unit_Ornithopter)
1823 - getHouse()->getNumItems(Unit_Sandworm)
1824 - getHouse()->getNumItems(Unit_MCV))) + 1;
1825
1826 if(pUnit->getOwner()->getHouseID() != pUnit->getOriginalHouseID()) {
1827 // If its a devastator and its not ours, blow it up!!
1828 if(pUnit->getItemID() == Unit_Devastator){
1829 const Devastator* pDevastator = dynamic_cast<const Devastator*>(pUnit);
1830 if(pDevastator != nullptr) {
1831
1832 doStartDevastate(pDevastator);
1833 doSetAttackMode(pDevastator, HUNT);
1834 }
1835 } else if(pUnit->getItemID() == Unit_Ornithopter) {
1836 if(pUnit->getAttackMode() != HUNT){
1837 doSetAttackMode(pUnit, HUNT);
1838 }
1839 } else if(pUnit->getItemID() == Unit_Harvester) {
1840 const Harvester* pHarvester = dynamic_cast<const Harvester*>(pUnit);
1841 if(pHarvester != nullptr && pHarvester->getAmountOfSpice() >= HARVESTERMAXSPICE/5) {
1842 doReturn(pHarvester);
1843 } else {
1844 doMove2Pos(pUnit, squadCenterLocation.x, squadCenterLocation.y, true );
1845 }
1846 } else {
1847 // Send deviated unit to squad centre
1848 if(pUnit->getAttackMode() != AREAGUARD) {
1849 doSetAttackMode(pUnit, AREAGUARD);
1850 }
1851
1852 if(blockDistance(pUnit->getLocation(), squadCenterLocation) > squadRadius - 1) {
1853 doMove2Pos(pUnit, squadCenterLocation.x, squadCenterLocation.y, true );
1854 }
1855 }
1856 } else if((pUnit->getItemID() == Unit_Launcher || pUnit->getItemID() == Unit_Deviator || pUnit->getItemID() == Unit_SonicTank)
1857 && pUnit->hasATarget() && (difficulty == Difficulty::Hard || difficulty == Difficulty::Brutal)) {
1858 // Special logic to keep launchers away from harm
1859 if(pUnit->getTarget() != nullptr){
1860 if(blockDistance(pUnit->getLocation(), pUnit->getTarget()->getLocation()) <= 5 && pUnit->getTarget()->getItemID() != Unit_Ornithopter) {
1861 doSetAttackMode(pUnit, AREAGUARD);
1862 doMove2Pos(pUnit, squadCenterLocation.x, squadCenterLocation.y, true );
1863 }
1864 }
1865 } else if(pUnit->getAttackMode() != HUNT && !pUnit->hasATarget() && !pUnit->wasForced()) {
1866 if(pUnit->getAttackMode() == AREAGUARD && squadCenterLocation.isValid() && (gameMode != GameMode::Campaign)) {
1867 if(blockDistance(pUnit->getLocation(), squadCenterLocation) > squadRadius) {
1868 if(!pUnit->hasATarget()){
1869 doMove2Pos(pUnit, squadCenterLocation.x, squadCenterLocation.y, false );
1870 }
1871 }
1872 } else if (pUnit->getAttackMode() == RETREAT) {
1873 if(blockDistance(pUnit->getLocation(), squadRetreatLocation) > squadRadius + 2 && !pUnit->wasForced()) {
1874 if(pUnit->getHealth() < pUnit->getMaxHealth()) {
1875 doRepair(pUnit);
1876 }
1877 doMove2Pos(pUnit, squadRetreatLocation.x, squadRetreatLocation.y, true );
1878 } else {
1879 // We have finished retreating back to the rally point
1880 doSetAttackMode(pUnit, AREAGUARD);
1881 }
1882 } else if (pUnit->getAttackMode() == GUARD
1883 && ((pUnit->getDestination() != squadRallyLocation) || (blockDistance(pUnit->getLocation(),squadRallyLocation) <= squadRadius))) {
1884 // A newly deployed unit has reached the rally point, or has been diverted => Change it to area guard
1885 doSetAttackMode(pUnit, AREAGUARD);
1886 }
1887 } else if (pUnit->getAttackMode() == HUNT
1888 && attackTimer > MILLI2CYCLES(250000)
1889 && pUnit->getItemID() != Unit_Trooper
1890 && pUnit->getItemID() != Unit_Saboteur
1891 && pUnit->getItemID() != Unit_Sandworm){
1892 doSetAttackMode(pUnit, AREAGUARD);
1893 }
1894 } break;
1895 }
1896 }
1897 }
1898 }
1899