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