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