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 #include <House.h>
19 
20 #include <globals.h>
21 #include <sand.h>
22 
23 #include <FileClasses/TextManager.h>
24 
25 #include <players/PlayerFactory.h>
26 #include <players/AIPlayer.h>
27 #include <players/QuantBot.h>
28 #include <players/HumanPlayer.h>
29 
30 
31 #include <Game.h>
32 #include <GameInterface.h>
33 #include <Map.h>
34 #include <SoundPlayer.h>
35 
36 #include <structures/StructureBase.h>
37 #include <structures/BuilderBase.h>
38 #include <structures/Refinery.h>
39 #include <structures/ConstructionYard.h>
40 #include <units/Carryall.h>
41 #include <units/Harvester.h>
42 
43 #include <misc/exceptions.h>
44 #include <misc/format.h>
45 
46 #include <algorithm>
47 
48 
House(int newHouse,int newCredits,int maxUnits,Uint8 team,int quota)49 House::House(int newHouse, int newCredits, int maxUnits, Uint8 team, int quota) : choam(this) {
50     House::init();
51 
52     houseID = ((newHouse >= 0) && (newHouse < NUM_HOUSES)) ? newHouse :  0;
53     this->team = team;
54 
55     storedCredits = 0;
56     startingCredits = newCredits;
57     oldCredits = lround(storedCredits+startingCredits);
58 
59     this->maxUnits = maxUnits;
60     this->quota = quota;
61 
62     unitBuiltValue = 0;
63     structureBuiltValue = 0;
64     militaryValue = 0;
65     killValue = 0;
66     lossValue = 0;
67     numBuiltUnits = 0;
68     numBuiltStructures = 0;
69     destroyedValue = 0;
70     numDestroyedUnits = 0;
71     numDestroyedStructures = 0;
72     harvestedSpice = 0;
73     producedPower = 0;
74     powerUsageTimer = 0;
75 }
76 
77 
78 
79 
House(InputStream & stream)80 House::House(InputStream& stream) : choam(this) {
81     House::init();
82 
83     houseID = stream.readUint8();
84     team = stream.readUint8();
85 
86     storedCredits = stream.readFixPoint();
87     startingCredits = stream.readFixPoint();
88     oldCredits = lround(storedCredits+startingCredits);
89     maxUnits = stream.readSint32();
90     quota = stream.readSint32();
91 
92     unitBuiltValue = stream.readUint32();
93     structureBuiltValue = stream.readUint32();
94     militaryValue = stream.readUint32();
95     killValue = stream.readUint32();
96     lossValue = stream.readUint32();
97     numBuiltUnits = stream.readUint32();
98     numBuiltStructures = stream.readUint32();
99     destroyedValue = stream.readUint32();
100     numDestroyedUnits = stream.readUint32();
101     numDestroyedStructures = stream.readUint32();
102     harvestedSpice = stream.readFixPoint();
103     producedPower = stream.readSint32();
104     powerUsageTimer = stream.readSint32();
105 
106     choam.load(stream);
107 
108     Uint32 numPlayers = stream.readUint32();
109     for(Uint32 i = 0; i < numPlayers; i++) {
110         std::string playerclass = stream.readString();
111         const PlayerFactory::PlayerData* pPlayerData = PlayerFactory::getByPlayerClass(playerclass);
112         if(pPlayerData == nullptr) {
113             SDL_Log("Warning: Cannot load player '%s'", playerclass.c_str());
114         } else {
115             addPlayer(std::shared_ptr<Player>(pPlayerData->load(stream,this)));
116         }
117     }
118 }
119 
120 
121 
122 
init()123 void House::init() {
124     ai = true;
125 
126     numUnits = 0;
127     numStructures = 0;
128     for(int i=0;i<Num_ItemID;i++) {
129         numItem[i] = 0;
130         numItemBuilt[i] = 0;
131         numItemKills[i] = 0;
132         numItemLosses[i] = 0;
133         numItemDamageInflicted[i] = 0;
134     }
135 
136     capacity = 0;
137     powerRequirement = 0;
138 }
139 
140 
141 
142 
~House()143 House::~House() {
144 }
145 
146 
147 
148 
save(OutputStream & stream) const149 void House::save(OutputStream& stream) const {
150     stream.writeUint8(houseID);
151     stream.writeUint8(team);
152 
153     stream.writeFixPoint(storedCredits);
154     stream.writeFixPoint(startingCredits);
155     stream.writeSint32(maxUnits);
156     stream.writeSint32(quota);
157 
158     stream.writeUint32(unitBuiltValue);
159     stream.writeUint32(structureBuiltValue);
160     stream.writeUint32(militaryValue);
161     stream.writeUint32(killValue);
162     stream.writeUint32(lossValue);
163     stream.writeUint32(numBuiltUnits);
164     stream.writeUint32(numBuiltStructures);
165     stream.writeUint32(destroyedValue);
166     stream.writeUint32(numDestroyedUnits);
167     stream.writeUint32(numDestroyedStructures);
168     stream.writeFixPoint(harvestedSpice);
169     stream.writeSint32(producedPower);
170     stream.writeSint32(powerUsageTimer);
171 
172     choam.save(stream);
173 
174     stream.writeUint32(players.size());
175     for(const std::shared_ptr<Player>& pPlayer : players) {
176         stream.writeString(pPlayer->getPlayerclass());
177         pPlayer->save(stream);
178     }
179 }
180 
181 
182 
183 
addPlayer(std::shared_ptr<Player> newPlayer)184 void House::addPlayer(std::shared_ptr<Player> newPlayer) {
185     if(dynamic_cast<HumanPlayer*>(newPlayer.get()) != nullptr && players.empty()) {
186         ai = false;
187     } else {
188         ai = true;
189     }
190 
191     players.push_back(newPlayer);
192 
193     Uint8 newPlayerID = static_cast<Uint8>((houseID << 4) | players.size());
194     newPlayer->playerID = newPlayerID;
195 
196     currentGame->registerPlayer(newPlayer.get());
197 }
198 
199 
setProducedPower(int newPower)200 void House::setProducedPower(int newPower) {
201     producedPower = newPower;
202 }
203 
204 
addCredits(FixPoint newCredits,bool wasRefined)205 void House::addCredits(FixPoint newCredits, bool wasRefined) {
206     if(newCredits > 0) {
207         if(wasRefined == true) {
208             harvestedSpice += newCredits;
209         }
210 
211         storedCredits += newCredits;
212         if(this == pLocalHouse) {
213             if(((currentGame->winFlags & WINLOSEFLAGS_QUOTA) != 0) && (quota != 0)) {
214                 if(storedCredits >= quota) {
215                     win();
216                 }
217             }
218         }
219     }
220 }
221 
222 
223 
224 
returnCredits(FixPoint newCredits)225 void House::returnCredits(FixPoint newCredits) {
226     if(newCredits > 0) {
227         FixPoint leftCapacity = capacity - storedCredits;
228         if(newCredits <= leftCapacity) {
229             addCredits(newCredits, false);
230         } else {
231             addCredits(leftCapacity, false);
232             startingCredits += (newCredits - leftCapacity);
233         }
234     }
235 }
236 
237 
238 
239 
takeCredits(FixPoint amount)240 FixPoint House::takeCredits(FixPoint amount) {
241     FixPoint taken = 0;
242 
243     if(getCredits() >= 1) {
244         if(storedCredits > amount) {
245             taken = amount;
246             storedCredits -= amount;
247         } else {
248             taken = storedCredits;
249             storedCredits = 0;
250 
251             if(startingCredits > (amount - taken)) {
252                 startingCredits -= (amount - taken);
253                 taken = amount;
254             } else {
255                 taken += startingCredits;
256                 startingCredits = 0;
257             }
258         }
259     }
260 
261     return taken;   //the amount that was actually withdrawn
262 }
263 
264 
265 
266 
printStat() const267 void House::printStat() const {
268     SDL_Log("House %s: (Number of Units: %d, Number of Structures: %d)",getHouseNameByNumber( (HOUSETYPE) getHouseID()).c_str(),numUnits,numStructures);
269     SDL_Log("Barracks: %d\t\tWORs: %d", numItem[Structure_Barracks],numItem[Structure_WOR]);
270     SDL_Log("Light Factories: %d\tHeavy Factories: %d",numItem[Structure_LightFactory],numItem[Structure_HeavyFactory]);
271     SDL_Log("IXs: %d\t\t\tPalaces: %d",numItem[Structure_IX],numItem[Structure_Palace]);
272     SDL_Log("Repair Yards: %d\t\tHigh-Tech Factories: %d",numItem[Structure_RepairYard],numItem[Structure_HighTechFactory]);
273     SDL_Log("Refineries: %d\t\tStarports: %d",numItem[Structure_Refinery],numItem[Structure_StarPort]);
274     SDL_Log("Walls: %d\t\tRocket Turrets: %d",numItem[Structure_Wall],numItem[Structure_RocketTurret]);
275     SDL_Log("Gun Turrets: %d\t\tConstruction Yards: %d",numItem[Structure_GunTurret],numItem[Structure_ConstructionYard]);
276     SDL_Log("Windtraps: %d\t\tRadars: %d",numItem[Structure_WindTrap],numItem[Structure_Radar]);
277     SDL_Log("Silos: %d",numItem[Structure_Silo]);
278     SDL_Log("Carryalls: %d\t\tFrigates: %d",numItem[Unit_Carryall],numItem[Unit_Frigate]);
279     SDL_Log("Devastators: %d\t\tDeviators: %d",numItem[Unit_Devastator],numItem[Unit_Deviator]);
280     SDL_Log("Soldiers: %d\t\tTrooper: %d",numItem[Unit_Soldier],numItem[Unit_Trooper]);
281     SDL_Log("Saboteur: %d\t\tSandworms: %d",numItem[Unit_Saboteur],numItem[Unit_Sandworm]);
282     SDL_Log("Quads: %d\t\tTrikes: %d",numItem[Unit_Quad],numItem[Unit_Trike]);
283     SDL_Log("Raiders: %d\t\tTanks: %d",numItem[Unit_RaiderTrike],numItem[Unit_Tank]);
284     SDL_Log("Siege Tanks : %d\t\tSonic Tanks: %d",numItem[Unit_SiegeTank],numItem[Unit_SonicTank]);
285     SDL_Log("Harvesters: %d\t\tMCVs: %d",numItem[Unit_Harvester],numItem[Unit_MCV]);
286     SDL_Log("Ornithopters: %d\t\tRocket Launchers: %d",numItem[Unit_Ornithopter],numItem[Unit_Launcher]);
287 }
288 
289 
290 
291 
updateBuildLists()292 void House::updateBuildLists() {
293     for(StructureBase* pStructure : structureList) {
294         if(pStructure->isABuilder() && (pStructure->getOwner() == this)) {
295             static_cast<BuilderBase*>(pStructure)->updateBuildList();
296         }
297     }
298 }
299 
300 
301 
302 
update()303 void House::update() {
304     if (oldCredits != getCredits()) {
305         if((this == pLocalHouse) && (getCredits() > 0)) {
306             soundPlayer->playSound(Sound_CreditsTick);
307         }
308         oldCredits = getCredits();
309     }
310 
311     if(storedCredits > capacity) {
312         --storedCredits;
313         if(storedCredits < 0) {
314          storedCredits = 0;
315         }
316 
317         if(this == pLocalHouse) {
318             currentGame->addToNewsTicker(_("@DUNE.ENG|145#As insufficient spice storage is available, spice is lost."));
319         }
320     }
321 
322     powerUsageTimer--;
323     if(powerUsageTimer <= 0) {
324         powerUsageTimer = MILLI2CYCLES(15*1000);
325         takeCredits(FixPoint(getPowerRequirement()) / 32);
326     }
327 
328     choam.update();
329 
330     for(std::shared_ptr<Player>& pPlayer : players) {
331         pPlayer->update();
332     }
333 }
334 
335 
336 
337 
incrementUnits(int itemID)338 void House::incrementUnits(int itemID) {
339     numUnits++;
340     numItem[itemID]++;
341 
342     if(itemID != Unit_Saboteur
343        && itemID != Unit_Frigate
344        && itemID != Unit_Carryall
345        && itemID != Unit_MCV
346        && itemID != Unit_Harvester
347        && itemID != Unit_Sandworm) {
348 
349             militaryValue += currentGame->objectData.data[itemID][houseID].price;
350     }
351 
352 }
353 
354 
355 
decrementUnits(int itemID)356 void House::decrementUnits(int itemID) {
357     numUnits--;
358     numItemLosses[itemID]++;
359 
360     if(itemID == Unit_Harvester) {
361         decrementHarvesters();
362     } else {
363         numItem[itemID]--;
364     }
365 
366     for(std::shared_ptr<Player>& pPlayer : players) {
367         pPlayer->onDecrementUnits(itemID);
368     }
369 
370     if(itemID != Unit_Saboteur
371        && itemID != Unit_Frigate
372        && itemID != Unit_Carryall
373        && itemID != Unit_MCV
374        && itemID != Unit_Harvester
375        && itemID != Unit_Sandworm) {
376 
377             lossValue += currentGame->objectData.data[itemID][houseID].price;
378     }
379 
380     if (!isAlive())
381         lose();
382 
383 
384 }
385 
386 
387 
388 
incrementStructures(int itemID)389 void House::incrementStructures(int itemID) {
390     numStructures++;
391     numItem[itemID]++;
392 
393     // change power requirements
394     int currentItemPower = currentGame->objectData.data[itemID][houseID].power;
395     if(currentItemPower >= 0) {
396         powerRequirement += currentItemPower;
397     }
398 
399     // change spice capacity
400     capacity += currentGame->objectData.data[itemID][houseID].capacity;
401 
402     if(currentGame->gameState != GameState::Loading) {
403         // do not check selection lists if we are loading
404         updateBuildLists();
405     }
406 
407     for(std::shared_ptr<Player>& pPlayer : players) {
408         pPlayer->onIncrementStructures(itemID);
409     }
410 }
411 
412 
413 
414 
decrementStructures(int itemID,const Coord & location)415 void House::decrementStructures(int itemID, const Coord& location) {
416     numStructures--;
417     numItem[itemID]--;
418     numItemLosses[itemID]++;
419 
420     // change power requirements
421     int currentItemPower = currentGame->objectData.data[itemID][houseID].power;
422     if(currentItemPower >= 0) {
423         powerRequirement -= currentItemPower;
424     }
425 
426     // change spice capacity
427     capacity -= currentGame->objectData.data[itemID][houseID].capacity;
428 
429     if(currentGame->gameState != GameState::Loading) {
430         // do not check selection lists if we are loading
431         updateBuildLists();
432     }
433 
434     if (!isAlive())
435         lose();
436 
437     for(std::shared_ptr<Player>& pPlayer : players) {
438         pPlayer->onDecrementStructures(itemID, location);
439     }
440 }
441 
442 
443 
444 
noteDamageLocation(ObjectBase * pObject,int damage,Uint32 damagerID)445 void House::noteDamageLocation(ObjectBase* pObject, int damage, Uint32 damagerID) {
446     for(std::shared_ptr<Player>& pPlayer : players) {
447         pPlayer->onDamage(pObject, damage, damagerID);
448     }
449 }
450 
451 
452 /**
453     This method informs this house that a new unit or structure was built
454     \param itemID   the ID of the enemy unit or structure
455 */
informWasBuilt(Uint32 itemID)456 void House::informWasBuilt(Uint32 itemID) {
457     if(isStructure(itemID)) {
458         structureBuiltValue += currentGame->objectData.data[itemID][houseID].price; // Removed divide by 100
459         numBuiltStructures++;
460     } else {
461         unitBuiltValue += currentGame->objectData.data[itemID][houseID].price; // Removed divide by 100
462         numBuiltUnits++;
463     }
464 
465     numItemBuilt[itemID]++;
466 }
467 
468 
469 
470 /**
471     This method informs this house that one of its units has killed an enemy unit or structure
472     \param itemID   the ID of the enemy unit or structure
473 */
informHasKilled(Uint32 itemID)474 void House::informHasKilled(Uint32 itemID) {
475     destroyedValue += std::max(currentGame->objectData.data[itemID][houseID].price/100, 1);
476     if(isStructure(itemID)) {
477         numDestroyedStructures++;
478     } else {
479         numDestroyedUnits++;
480 
481         if(itemID != Unit_Saboteur
482            && itemID != Unit_Frigate
483            && itemID != Unit_Carryall
484            && itemID != Unit_MCV
485            && itemID != Unit_Harvester
486            && itemID != Unit_Sandworm) {
487 
488                 killValue += currentGame->objectData.data[itemID][houseID].price;
489 
490         }
491 
492     }
493 
494     numItemKills[itemID]++;
495 
496 
497     for(std::shared_ptr<Player>& pPlayer : players) {
498         pPlayer->onIncrementUnitKills(itemID);
499     }
500 }
501 
502 /**
503  This method informs this house that one of its units has damaged an enemy unit or structure
504  \param itemID   the ID of the enemy unit or structure
505  */
506 
informHasDamaged(Uint32 itemID,Uint32 damage)507 void House::informHasDamaged(Uint32 itemID, Uint32 damage) {
508     numItemDamageInflicted[itemID] += damage;
509 }
510 
511 
win()512 void House::win() {
513     if(getTeam() == pLocalHouse->getTeam()) {
514         currentGame->setGameWon();
515     } else {
516         currentGame->setGameLost();
517     }
518 }
519 
520 
521 
522 
lose(bool bSilent)523 void House::lose(bool bSilent) {
524     if(!bSilent) {
525         currentGame->addToNewsTicker(fmt::sprintf(_("House '%s' has been defeated."), getHouseNameByNumber( (HOUSETYPE) getHouseID())));
526     }
527 
528     if((getTeam() == pLocalHouse->getTeam()) && ((currentGame->winFlags & WINLOSEFLAGS_HUMAN_HAS_BUILDINGS) != 0)) {
529 
530         bool finished = true;
531 
532         for(int i=0; i < NUM_HOUSES; i++) {
533             House* pHouse = currentGame->getHouse(i);
534             if(pHouse != nullptr && pHouse->isAlive() && pHouse->getTeam() == pLocalHouse->getTeam()) {
535                 finished = false;
536             }
537         }
538 
539         if(finished) {
540             // pLocalHouse is destroyed and this is a game finish condition
541             if((currentGame->loseFlags & WINLOSEFLAGS_HUMAN_HAS_BUILDINGS) != 0) {
542                 // house has won
543                 currentGame->setGameWon();
544             } else {
545                 // house has lost
546                 currentGame->setGameLost();
547             }
548         }
549 
550     } else if((currentGame->winFlags & WINLOSEFLAGS_AI_NO_BUILDINGS) != 0) {
551         //if the only players left are on the thisPlayers team, pLocalHouse has won
552         bool finished = true;
553 
554         for(int i=0; i < NUM_HOUSES; i++) {
555             House* pHouse = currentGame->getHouse(i);
556             if(pHouse != nullptr && pHouse->isAlive() && pHouse->getTeam() != 0 && pHouse->getTeam() != pLocalHouse->getTeam()) {
557                 finished = false;
558             }
559         }
560 
561         if(finished) {
562             // all AI players are destroyed and this is a game finish condition
563             if((currentGame->loseFlags & WINLOSEFLAGS_AI_NO_BUILDINGS) != 0) {
564                 // house has won
565                 currentGame->setGameWon();
566             } else {
567                 // house has lost
568                 currentGame->setGameLost();
569             }
570         }
571     }
572 }
573 
574 
575 
576 
freeHarvester(int xPos,int yPos)577 void House::freeHarvester(int xPos, int yPos) {
578     if(currentGameMap->tileExists(xPos, yPos)
579         && currentGameMap->getTile(xPos, yPos)->hasAGroundObject()
580         && (currentGameMap->getTile(xPos, yPos)->getGroundObject()->getItemID() == Structure_Refinery))
581     {
582         Refinery* refinery = static_cast<Refinery*>(currentGameMap->getTile(xPos, yPos)->getGroundObject());
583         Coord closestPos = currentGameMap->findClosestEdgePoint(refinery->getLocation() + Coord(2,0), Coord(1,1));
584 
585         Carryall* carryall = static_cast<Carryall*>(createUnit(Unit_Carryall));
586         Harvester* harvester = static_cast<Harvester*>(createUnit(Unit_Harvester));
587         harvester->setAmountOfSpice(5);
588         carryall->setOwned(false);
589         carryall->giveCargo(harvester);
590         carryall->deploy(closestPos);
591         carryall->setDropOfferer(true);
592 
593         if (closestPos.x == 0)
594             carryall->setAngle(RIGHT);
595         else if (closestPos.x == currentGameMap->getSizeX()-1)
596             carryall->setAngle(LEFT);
597         else if (closestPos.y == 0)
598             carryall->setAngle(DOWN);
599         else if (closestPos.y == currentGameMap->getSizeY()-1)
600             carryall->setAngle(UP);
601 
602         harvester->setTarget(refinery);
603         harvester->setActive(false);
604         carryall->setTarget(refinery);
605     }
606 }
607 
608 
609 
610 
placeStructure(Uint32 builderID,int itemID,int xPos,int yPos,bool bForcePlacing)611 StructureBase* House::placeStructure(Uint32 builderID, int itemID, int xPos, int yPos, bool bForcePlacing) {
612     if(!currentGameMap->tileExists(xPos,yPos)) {
613         return nullptr;
614     }
615 
616     BuilderBase* pBuilder = (builderID == NONE_ID) ? nullptr : dynamic_cast<BuilderBase*>(currentGame->getObjectManager().getObject(builderID));
617 
618     if(currentGame->getGameInitSettings().getGameOptions().onlyOnePalace && pBuilder != nullptr && itemID == Structure_Palace && getNumItems(Structure_Palace) > 0) {
619         if(this == pLocalHouse && pBuilder->isSelected()) {
620             currentGame->currentCursorMode = Game::CursorMode_Normal;
621         }
622         return nullptr;
623     }
624 
625     switch (itemID) {
626         case (Structure_Slab1): {
627             // Slabs are no normal buildings
628             currentGameMap->getTile(xPos, yPos)->setType(Terrain_Slab);
629             currentGameMap->getTile(xPos, yPos)->setOwner(getHouseID());
630             currentGameMap->viewMap(getTeam(), xPos, yPos, currentGame->objectData.data[Structure_Slab1][houseID].viewrange);
631     //      currentGameMap->getTile(xPos, yPos)->clearTerrain();
632 
633             if(pBuilder != nullptr) {
634                 pBuilder->unSetWaitingToPlace();
635 
636                 if(this == pLocalHouse) {
637                     if(pBuilder->isSelected()) {
638                         currentGame->currentCursorMode = Game::CursorMode_Normal;
639                     }
640 
641                     pLocalPlayer->onPlaceStructure(nullptr);
642                 }
643             }
644 
645             return nullptr;
646 
647         } break;
648 
649         case (Structure_Slab4): {
650             // Slabs are no normal buildings
651             int i,j;
652             for(i = xPos; i < xPos + 2; i++) {
653                 for(j = yPos; j < yPos + 2; j++) {
654                     if (currentGameMap->tileExists(i, j)) {
655                         Tile* pTile = currentGameMap->getTile(i, j);
656 
657                         if (!pTile->hasAGroundObject() && pTile->isRock() && !pTile->isMountain()) {
658                             pTile->setType(Terrain_Slab);
659                             pTile->setOwner(getHouseID());
660                             currentGameMap->viewMap(getTeam(), i, j, currentGame->objectData.data[Structure_Slab4][houseID].viewrange);
661                             //pTile->clearTerrain();
662                         }
663                     }
664                 }
665             }
666 
667             if(pBuilder != nullptr) {
668                 pBuilder->unSetWaitingToPlace();
669 
670                 if(this == pLocalHouse) {
671                     if(pBuilder->isSelected()) {
672                         currentGame->currentCursorMode = Game::CursorMode_Normal;
673                     }
674 
675                     pLocalPlayer->onPlaceStructure(nullptr);
676                 }
677             }
678 
679             return nullptr;
680 
681         } break;
682 
683         default: {
684             ObjectBase* newObject = ObjectBase::createObject(itemID,this);
685             StructureBase* newStructure = dynamic_cast<StructureBase*>(newObject);
686             if(newStructure == nullptr) {
687                 delete newStructure;
688                 THROW(std::runtime_error, "Cannot create structure with itemID %d!", itemID);
689             }
690 
691             if(bForcePlacing == false) {
692                 // check if there is already something on this tile
693                 for(int i=0;i<newStructure->getStructureSizeX();i++) {
694                     for(int j=0;j<newStructure->getStructureSizeY();j++) {
695                         if((currentGameMap->tileExists(xPos+i, yPos+j) == false) || (currentGameMap->getTile(xPos+i, yPos+j)->hasAGroundObject() == true)) {
696                             delete newStructure;
697                             return nullptr;
698                         }
699                     }
700                 }
701             }
702 
703             for(int i=0;i<newStructure->getStructureSizeX();i++) {
704                 for(int j=0;j<newStructure->getStructureSizeY();j++) {
705                     if(currentGameMap->tileExists(xPos+i, yPos+j)) {
706                         currentGameMap->getTile(xPos+i, yPos+j)->clearTerrain();
707                     }
708                 }
709             }
710 
711             newStructure->setLocation(xPos, yPos);
712 
713             if ((builderID != NONE_ID) && (itemID != Structure_Wall)) {
714                 newStructure->setJustPlaced();
715             }
716 
717             // at the beginning of the game the first refinery gets a harvester for free (brought by a carryall)
718             if((itemID == Structure_Refinery) && ( ((currentGame->gameState == GameState::Start) && (numItem[Unit_Harvester] <= 0)) || (builderID != NONE_ID)) ) {
719                 freeHarvester(xPos, yPos);
720             }
721 
722             // if this structure was built by a construction yard this construction yard must be informed
723             if(pBuilder != nullptr) {
724                 pBuilder->unSetWaitingToPlace();
725 
726                 if(itemID == Structure_Palace) {
727                     // cancel all other palaces
728                     for(StructureBase* pStructure : structureList) {
729                         if(pStructure->getOwner() == this && pStructure->getItemID() == Structure_ConstructionYard) {
730                             ConstructionYard* pConstructionYard = static_cast<ConstructionYard*>(pStructure);
731                             if(pBuilder != pConstructionYard) {
732                                 pConstructionYard->doCancelItem(Structure_Palace, false);
733                             }
734                         }
735                     }
736                 }
737 
738                 if (this == pLocalHouse) {
739                     if(pBuilder->isSelected()) {
740                         currentGame->currentCursorMode = Game::CursorMode_Normal;
741                     }
742                     pLocalPlayer->onPlaceStructure(newStructure);
743                 }
744 
745                 // only if we were constructed by construction yard
746                 // => inform house of the building
747                 pBuilder->getOwner()->informWasBuilt(itemID);
748             }
749 
750             if(newStructure->isABuilder()) {
751                 static_cast<BuilderBase*>(newStructure)->updateBuildList();
752             }
753 
754             return newStructure;
755 
756         } break;
757     }
758 
759     return nullptr;
760 }
761 
762 
763 
764 
createUnit(int itemID)765 UnitBase* House::createUnit(int itemID) {
766     ObjectBase* newObject = ObjectBase::createObject(itemID,this);
767     UnitBase* newUnit = dynamic_cast<UnitBase*>(newObject);
768 
769     if(newUnit == nullptr) {
770         delete newObject;
771         THROW(std::runtime_error, "Cannot create unit with itemID %d!", itemID);
772     }
773 
774     return newUnit;
775 }
776 
777 
778 
779 
placeUnit(int itemID,int xPos,int yPos)780 UnitBase* House::placeUnit(int itemID, int xPos, int yPos) {
781     UnitBase* newUnit = nullptr;
782     if(currentGameMap->tileExists(xPos, yPos) == true) {
783         Tile* pTile = currentGameMap->getTile(xPos,yPos);
784 
785         if(itemID == Unit_Saboteur || itemID == Unit_Soldier || itemID == Unit_Trooper) {
786             if((pTile->hasANonInfantryGroundObject() == true) || (pTile->infantryNotFull() == false)) {
787                 // infantry units can not placed on non-infantry units or structures (or the tile is already full of infantry units)
788                 return nullptr;
789             }
790         } else {
791             if(pTile->hasAGroundObject() == true) {
792                 // non-infantry units can not placed on a tile where already some other unit or structure is placed on
793                 return nullptr;
794             }
795         }
796 
797         newUnit = createUnit(itemID);
798     }
799 
800     if (newUnit) {
801         Coord pos = Coord(xPos, yPos);
802         if (newUnit->canPass(xPos, yPos)) {
803             newUnit->deploy(pos);
804         } else {
805             newUnit->setVisible(VIS_ALL, false);
806             newUnit->destroy();
807             newUnit = nullptr;
808         }
809     }
810 
811     return newUnit;
812 }
813 
814 
815 /**
816     This method returns the center of the base of this house.
817     \return the coordinate of the center in tile coordinates
818 */
getCenterOfMainBase() const819 Coord House::getCenterOfMainBase() const {
820     Coord center;
821     int numStructures = 0;
822     for(const StructureBase* pStructure : structureList) {
823         if(pStructure->getOwner() == this) {
824             center += pStructure->getLocation();
825             numStructures++;
826         }
827     }
828 
829     return center / numStructures;
830 }
831 
832 
833 /**
834     This method returns the position of the strongest unit
835     \return the coordinate of the strongest unit
836 */
getStrongestUnitPosition() const837 Coord House::getStrongestUnitPosition() const {
838     Coord strongestUnitPosition = Coord::Invalid();
839     Sint32 strongestUnitCost = 0;
840     for(const UnitBase* pUnit : unitList) {
841         if(pUnit->getOwner() == this) {
842             Sint32 currentCost = currentGame->objectData.data[pUnit->getItemID()][houseID].price;
843 
844             if(currentCost > strongestUnitCost) {
845                 strongestUnitPosition = pUnit->getLocation();
846                 strongestUnitCost = currentCost;
847             }
848         }
849     }
850 
851     return strongestUnitPosition;
852 }
853 
854 
855 
856 
decrementHarvesters()857 void House::decrementHarvesters() {
858     numItem[Unit_Harvester]--;
859 
860     if(numItem[Unit_Harvester] <= 0) {
861         numItem[Unit_Harvester] = 0;
862 
863         if(numItem[Structure_Refinery]) {
864             Coord   closestPos;
865             FixPoint    closestDistance = FixPt_MAX;
866             StructureBase *pClosestRefinery = nullptr;
867 
868             for(StructureBase* pStructure : structureList) {
869                 if((pStructure->getItemID() == Structure_Refinery) && (pStructure->getOwner() == this) && (pStructure->getHealth() > 0)) {
870                     Coord pos = pStructure->getLocation();
871 
872                     Coord closestPoint = pStructure->getClosestPoint(pos);
873                     FixPoint refineryDistance = blockDistance(pos, closestPoint);
874                     if(!pClosestRefinery || (refineryDistance < closestDistance)) {
875                             closestDistance = refineryDistance;
876                             pClosestRefinery = pStructure;
877                             closestPos = pos;
878                     }
879                 }
880             }
881 
882             if(pClosestRefinery && (currentGame->gameState == GameState::Running)) {
883                 freeHarvester(pClosestRefinery->getLocation());
884             }
885         }
886     }
887 }
888