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 <structures/StructureBase.h>
19 
20 #include <globals.h>
21 
22 #include <House.h>
23 #include <Game.h>
24 #include <Map.h>
25 #include <ScreenBorder.h>
26 #include <Explosion.h>
27 #include <SoundPlayer.h>
28 
29 #include <players/HumanPlayer.h>
30 
31 #include <misc/draw_util.h>
32 
33 #include <units/UnitBase.h>
34 
35 #include <GUI/ObjectInterfaces/DefaultStructureInterface.h>
36 
StructureBase(House * newOwner)37 StructureBase::StructureBase(House* newOwner) : ObjectBase(newOwner) {
38     StructureBase::init();
39 
40     repairing = false;
41     fogged = false;
42     degradeTimer = MILLI2CYCLES(15*1000);
43 }
44 
StructureBase(InputStream & stream)45 StructureBase::StructureBase(InputStream& stream): ObjectBase(stream) {
46     StructureBase::init();
47 
48     repairing = stream.readBool();
49     fogged = stream.readBool();
50     lastVisibleFrame = stream.readUint32();
51 
52     degradeTimer = stream.readSint32();
53 
54     size_t numSmoke = stream.readUint32();
55     for(size_t i=0;i<numSmoke; i++) {
56         smoke.push_back(StructureSmoke(stream));
57     }
58 }
59 
init()60 void StructureBase::init() {
61     aStructure = true;
62 
63     structureSize.x = 0;
64     structureSize.y = 0;
65 
66     justPlacedTimer = 0;
67 
68     lastVisibleFrame = curAnimFrame = 2;
69     animationCounter = 0;
70 
71     structureList.push_back(this);
72 }
73 
~StructureBase()74 StructureBase::~StructureBase() {
75     currentGameMap->removeObjectFromMap(getObjectID()); //no map point will reference now
76     currentGame->getObjectManager().removeObject(getObjectID());
77     structureList.remove(this);
78     owner->decrementStructures(itemID, location);
79 
80     removeFromSelectionLists();
81 
82     for(int i=0; i < NUMSELECTEDLISTS; i++) {
83         pLocalPlayer->getGroupList(i).erase(getObjectID());
84     }
85 }
86 
save(OutputStream & stream) const87 void StructureBase::save(OutputStream& stream) const {
88     ObjectBase::save(stream);
89 
90     stream.writeBool(repairing);
91     stream.writeBool(fogged);
92     stream.writeUint32(lastVisibleFrame);
93 
94     stream.writeSint32(degradeTimer);
95 
96     stream.writeUint32(smoke.size());
97     for(const StructureSmoke& structureSmoke : smoke) {
98         structureSmoke.save(stream);
99     }
100 }
101 
assignToMap(const Coord & pos)102 void StructureBase::assignToMap(const Coord& pos) {
103     bool bFoundNonConcreteTile = false;
104 
105     Coord temp;
106     for(int i = pos.x; i < pos.x + structureSize.x; i++) {
107         for(int j = pos.y; j < pos.y + structureSize.y; j++) {
108             if(currentGameMap->tileExists(i, j)) {
109                 Tile* pTile = currentGameMap->getTile(i,j);
110                 pTile->assignNonInfantryGroundObject(getObjectID());
111                 if(!pTile->isConcrete() && currentGame->getGameInitSettings().getGameOptions().concreteRequired && (currentGame->gameState != GameState::Start)) {
112                     bFoundNonConcreteTile = true;
113 
114                     if((itemID != Structure_Wall) && (itemID != Structure_ConstructionYard)) {
115                         setHealth(getHealth() - FixPoint(getMaxHealth())/(2*structureSize.x*structureSize.y));
116                     }
117                 }
118                 pTile->setType(Terrain_Rock);
119                 pTile->setOwner(getOwner()->getHouseID());
120 
121                 setVisible(VIS_ALL, true);
122                 setActive(true);
123                 setRespondable(true);
124             }
125         }
126     }
127 
128     currentGameMap->viewMap(getOwner()->getTeam(), pos, getViewRange());
129 
130     if(!bFoundNonConcreteTile && !currentGame->getGameInitSettings().getGameOptions().structuresDegradeOnConcrete) {
131         degradeTimer = -1;
132     }
133 }
134 
blitToScreen()135 void StructureBase::blitToScreen() {
136     int index = fogged ? lastVisibleFrame : curAnimFrame;
137     int indexX = index % numImagesX;
138     int indexY = index / numImagesX;
139 
140     SDL_Rect dest = calcSpriteDrawingRect(  graphic[currentZoomlevel],
141                                             screenborder->world2screenX(lround(realX)),
142                                             screenborder->world2screenY(lround(realY)),
143                                             numImagesX, numImagesY);
144     SDL_Rect source = calcSpriteSourceRect(graphic[currentZoomlevel],indexX,numImagesX,indexY,numImagesY);
145 
146     SDL_RenderCopy(renderer, graphic[currentZoomlevel], &source, &dest);
147 
148     if(!fogged) {
149         SDL_Texture** pSmokeSurface = pGFXManager->getObjPic(ObjPic_Smoke,getOwner()->getHouseID());
150         SDL_Rect smokeSource = calcSpriteSourceRect(pSmokeSurface[currentZoomlevel], 0, 3);
151         for(const StructureSmoke& structureSmoke : smoke) {
152             SDL_Rect smokeDest = calcSpriteDrawingRect( pSmokeSurface[currentZoomlevel],
153                                                         screenborder->world2screenX(structureSmoke.realPos.x),
154                                                         screenborder->world2screenY(structureSmoke.realPos.y),
155                                                         3, 1, HAlign::Center, VAlign::Bottom);
156             Uint32 cycleDiff = currentGame->getGameCycleCount() - structureSmoke.startGameCycle;
157 
158             Uint32 smokeFrame = (cycleDiff/25) % 4;
159             if(smokeFrame == 3) {
160                 smokeFrame = 1;
161             }
162 
163             smokeSource.x = smokeFrame * smokeSource.w;
164             SDL_RenderCopy(renderer, pSmokeSurface[currentZoomlevel], &smokeSource, &smokeDest);
165         }
166     }
167 }
168 
getInterfaceContainer()169 ObjectInterface* StructureBase::getInterfaceContainer() {
170     if((pLocalHouse == owner) || (debug == true)) {
171         return DefaultStructureInterface::create(objectID);
172     } else {
173         return DefaultObjectInterface::create(objectID);
174     }
175 }
176 
drawSelectionBox()177 void StructureBase::drawSelectionBox() {
178     SDL_Rect dest;
179     dest.x = screenborder->world2screenX(realX);
180     dest.y = screenborder->world2screenY(realY);
181     dest.w = getWidth(graphic[currentZoomlevel])/numImagesX;
182     dest.h = getHeight(graphic[currentZoomlevel])/numImagesY;
183 
184     //now draw the selection box thing, with parts at all corners of structure
185 
186     // top left bit
187     for(int i=0;i<=currentZoomlevel;i++) {
188         renderDrawHLine(renderer, dest.x+i, dest.y+i, dest.x+(currentZoomlevel+1)*3, COLOR_WHITE);
189         renderDrawVLine(renderer, dest.x+i, dest.y+i, dest.y+(currentZoomlevel+1)*3, COLOR_WHITE);
190     }
191 
192     // top right bit
193     for(int i=0;i<=currentZoomlevel;i++) {
194         renderDrawHLine(renderer, dest.x + dest.w-1 - i, dest.y+i, dest.x + dest.w-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
195         renderDrawVLine(renderer, dest.x + dest.w-1 - i, dest.y+i, dest.y+(currentZoomlevel+1)*3, COLOR_WHITE);
196     }
197 
198     // bottom left bit
199     for(int i=0;i<=currentZoomlevel;i++) {
200         renderDrawHLine(renderer, dest.x+i, dest.y + dest.h-1 - i, dest.x+(currentZoomlevel+1)*3, COLOR_WHITE);
201         renderDrawVLine(renderer, dest.x+i, dest.y + dest.h-1 - i, dest.y + dest.h-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
202     }
203 
204     // bottom right bit
205     for(int i=0;i<=currentZoomlevel;i++) {
206         renderDrawHLine(renderer, dest.x + dest.w-1 - i, dest.y + dest.h-1 - i, dest.x + dest.w-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
207         renderDrawVLine(renderer, dest.x + dest.w-1 - i, dest.y + dest.h-1 - i, dest.y + dest.h-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
208     }
209 
210     // health bar
211     for(int i=1;i<=currentZoomlevel+1;i++) {
212         renderDrawHLine(renderer, dest.x, dest.y-i-1, dest.x + (lround((getHealth()/getMaxHealth())*(world2zoomedWorld(TILESIZE)*structureSize.x - 1))), getHealthColor());
213     }
214 }
215 
drawOtherPlayerSelectionBox()216 void StructureBase::drawOtherPlayerSelectionBox() {
217     SDL_Rect dest;
218     dest.x = screenborder->world2screenX(realX) + (currentZoomlevel+1);
219     dest.y = screenborder->world2screenY(realY) + (currentZoomlevel+1);
220     dest.w = getWidth(graphic[currentZoomlevel])/numImagesX - 2*(currentZoomlevel+1);
221     dest.h = getHeight(graphic[currentZoomlevel])/numImagesY - 2*(currentZoomlevel+1);
222 
223     //now draw the selection box thing, with parts at all corners of structure
224 
225     // top left bit
226     for(int i=0;i<=currentZoomlevel;i++) {
227         renderDrawHLine(renderer, dest.x+i, dest.y+i, dest.x+(currentZoomlevel+1)*2, COLOR_LIGHTBLUE);
228         renderDrawVLine(renderer, dest.x+i, dest.y+i, dest.y+(currentZoomlevel+1)*2, COLOR_LIGHTBLUE);
229     }
230 
231     // top right bit
232     for(int i=0;i<=currentZoomlevel;i++) {
233         renderDrawHLine(renderer, dest.x + dest.w-1 - i, dest.y+i, dest.x + dest.w-1 - (currentZoomlevel+1)*2, COLOR_LIGHTBLUE);
234         renderDrawVLine(renderer, dest.x + dest.w-1 - i, dest.y+i, dest.y+(currentZoomlevel+1)*2, COLOR_LIGHTBLUE);
235     }
236 
237     // bottom left bit
238     for(int i=0;i<=currentZoomlevel;i++) {
239         renderDrawHLine(renderer, dest.x+i, dest.y + dest.h-1 - i, dest.x+(currentZoomlevel+1)*2, COLOR_LIGHTBLUE);
240         renderDrawVLine(renderer, dest.x+i, dest.y + dest.h-1 - i, dest.y + dest.h-1 - (currentZoomlevel+1)*2, COLOR_LIGHTBLUE);
241     }
242 
243     // bottom right bit
244     for(int i=0;i<=currentZoomlevel;i++) {
245         renderDrawHLine(renderer, dest.x + dest.w-1 - i, dest.y + dest.h-1 - i, dest.x + dest.w-1 - (currentZoomlevel+1)*2, COLOR_LIGHTBLUE);
246         renderDrawVLine(renderer, dest.x + dest.w-1 - i, dest.y + dest.h-1 - i, dest.y + dest.h-1 - (currentZoomlevel+1)*2, COLOR_LIGHTBLUE);
247     }
248 }
249 
drawGatheringPointLine()250 void StructureBase::drawGatheringPointLine() {
251     if(isABuilder() && (getItemID() != Structure_ConstructionYard) && destination.isValid() && (getOwner() == pLocalHouse)) {
252         Coord indicatorPosition = destination*TILESIZE + Coord(TILESIZE/2, TILESIZE/2);
253         Coord structurePosition = getCenterPoint();
254 
255         renderDrawLine( renderer,
256                         screenborder->world2screenX(structurePosition.x), screenborder->world2screenY(structurePosition.y),
257                         screenborder->world2screenX(indicatorPosition.x), screenborder->world2screenY(indicatorPosition.y),
258                         COLOR_HALF_TRANSPARENT);
259 
260 
261         SDL_Texture* pUIIndicator = pGFXManager->getUIGraphic(UI_Indicator);
262         SDL_Rect source = calcSpriteSourceRect(pUIIndicator, 0, 3);
263         SDL_Rect drawLocation = calcSpriteDrawingRect(  pUIIndicator,
264                                                         screenborder->world2screenX(indicatorPosition.x),
265                                                         screenborder->world2screenY(indicatorPosition.y),
266                                                         3, 1,
267                                                         HAlign::Center, VAlign::Center);
268 
269         // Render twice
270         SDL_RenderCopy(renderer, pUIIndicator, &source, &drawLocation);
271         SDL_RenderCopy(renderer, pUIIndicator, &source, &drawLocation);
272     }
273 }
274 
275 /**
276     Returns the center point of this structure
277     \return the center point in world coordinates
278 */
getCenterPoint() const279 Coord StructureBase::getCenterPoint() const {
280     return Coord( lround(realX + structureSize.x*TILESIZE/2),
281                   lround(realY + structureSize.y*TILESIZE/2));
282 }
283 
getClosestCenterPoint(const Coord & objectLocation) const284 Coord StructureBase::getClosestCenterPoint(const Coord& objectLocation) const {
285     return getClosestPoint(objectLocation) * TILESIZE + Coord(TILESIZE/2, TILESIZE/2);
286 }
287 
handleActionClick(int xPos,int yPos)288 void StructureBase::handleActionClick(int xPos, int yPos) {
289     if ((xPos < location.x) || (xPos >= (location.x + structureSize.x)) || (yPos < location.y) || (yPos >= (location.y + structureSize.y))) {
290         currentGame->getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_STRUCTURE_SETDEPLOYPOSITION,objectID, (Uint32) xPos, (Uint32) yPos));
291     } else {
292         currentGame->getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_STRUCTURE_SETDEPLOYPOSITION,objectID, (Uint32) NONE_ID, (Uint32) NONE_ID));
293     }
294 }
295 
handleRepairClick()296 void StructureBase::handleRepairClick() {
297     currentGame->getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_STRUCTURE_REPAIR,objectID));
298 }
299 
doSetDeployPosition(int xPos,int yPos)300 void StructureBase::doSetDeployPosition(int xPos, int yPos) {
301     setTarget(nullptr);
302     setDestination(xPos,yPos);
303     setForced(true);
304 }
305 
306 
doRepair()307 void StructureBase::doRepair() {
308     repairing = true;
309 }
310 
setDestination(int newX,int newY)311 void StructureBase::setDestination(int newX, int newY) {
312     if(currentGameMap->tileExists(newX, newY) || ((newX == INVALID_POS) && (newY == INVALID_POS))) {
313         destination.x = newX;
314         destination.y = newY;
315     }
316 }
317 
setJustPlaced()318 void StructureBase::setJustPlaced() {
319     justPlacedTimer = 6;
320     curAnimFrame = 0;
321     animationCounter = -STRUCTURE_ANIMATIONTIMER; // make first build animation double as long
322 }
323 
update()324 bool StructureBase::update() {
325     if(((currentGame->getGameCycleCount() + getObjectID()) % 512) == 0) {
326         currentGameMap->viewMap(owner->getTeam(), location, getViewRange());
327     }
328 
329     if(!fogged) {
330         lastVisibleFrame = curAnimFrame;
331     }
332 
333     // degrade
334     if((degradeTimer >= 0) && currentGame->getGameInitSettings().getGameOptions().concreteRequired && (owner->getPowerRequirement() > owner->getProducedPower())) {
335         degradeTimer--;
336         if(degradeTimer <= 0) {
337             degradeTimer = MILLI2CYCLES(15*1000);
338 
339             int damageMultiplyer = 1;
340             if(owner->getHouseID() == HOUSE_HARKONNEN || owner->getHouseID() == HOUSE_SARDAUKAR) {
341                 damageMultiplyer = 3;
342             } else if(owner->getHouseID() == HOUSE_ORDOS) {
343                 damageMultiplyer = 2;
344             } else if(owner->getHouseID() == HOUSE_MERCENARY) {
345                 damageMultiplyer = 5;
346             }
347 
348             if(getHealth() > getMaxHealth() / 2) {
349                 setHealth( getHealth() - FixPoint(damageMultiplyer * getMaxHealth())/100);
350             }
351         }
352     }
353 
354     updateStructureSpecificStuff();
355 
356     if(getHealth() <= 0) {
357         destroy();
358         return false;
359     }
360 
361     if(repairing) {
362         if(owner->getCredits() >= 5) {
363             // Original dune 2 is doing the repair calculation with fix-point math (multiply everything with 256).
364             // It is calculating what fraction 2 hitpoints of the maximum health would be.
365             int fraction = (2*256)/getMaxHealth();
366             FixPoint repairprice = FixPoint(fraction * currentGame->objectData.data[itemID][originalHouseID].price) / 256;
367 
368             // Original dune is always repairing 5 hitpoints (for the costs of 2) but we are only repairing 1/30th of that
369             const FixPoint repairHealth = FixPt(5,0)/FixPt(30,0);
370             owner->takeCredits(repairprice/30);
371             FixPoint newHealth = getHealth() + repairHealth;
372             if(newHealth >= getMaxHealth()) {
373                 setHealth(getMaxHealth());
374                 repairing = false;
375             } else {
376                 setHealth(newHealth);
377             }
378         } else {
379             repairing = false;
380         }
381     } else if(owner->isAI() && (getHealth() < getMaxHealth()/2)) {
382         doRepair();
383     }
384 
385     // check smoke
386     std::list<StructureSmoke>::iterator iter = smoke.begin();
387     while(iter != smoke.end()) {
388         if(currentGame->getGameCycleCount() - iter->startGameCycle >= MILLI2CYCLES(8*1000)) {
389             smoke.erase(iter++);
390         } else {
391             ++iter;
392         }
393     }
394 
395     // update animations
396     animationCounter++;
397     if(animationCounter > STRUCTURE_ANIMATIONTIMER) {
398         animationCounter = 0;
399         curAnimFrame++;
400         if((curAnimFrame < firstAnimFrame) || (curAnimFrame > lastAnimFrame)) {
401             curAnimFrame = firstAnimFrame;
402         }
403 
404         justPlacedTimer--;
405         if((justPlacedTimer > 0) && (justPlacedTimer % 2 == 0)) {
406             curAnimFrame = 0;
407         }
408     }
409 
410     return true;
411 }
412 
destroy()413 void StructureBase::destroy() {
414     int*    pDestroyedStructureTiles = nullptr;
415     int     DestroyedStructureTilesSizeY = 0;
416     static int DestroyedStructureTilesWall[] = { DestroyedStructure_Wall };
417     static int DestroyedStructureTiles1x1[] = { Destroyed1x1Structure };
418     static int DestroyedStructureTiles2x2[] = { Destroyed2x2Structure_TopLeft, Destroyed2x2Structure_TopRight,
419                                                 Destroyed2x2Structure_BottomLeft, Destroyed2x2Structure_BottomRight };
420     static int DestroyedStructureTiles3x2[] = { Destroyed3x2Structure_TopLeft, Destroyed3x2Structure_TopCenter, Destroyed3x2Structure_TopRight,
421                                                 Destroyed3x2Structure_BottomLeft, Destroyed3x2Structure_BottomCenter, Destroyed3x2Structure_BottomRight};
422     static int DestroyedStructureTiles3x3[] = { Destroyed3x3Structure_TopLeft, Destroyed3x3Structure_TopCenter, Destroyed3x3Structure_TopRight,
423                                                 Destroyed3x3Structure_CenterLeft, Destroyed3x3Structure_CenterCenter, Destroyed3x3Structure_CenterRight,
424                                                 Destroyed3x3Structure_BottomLeft, Destroyed3x3Structure_BottomCenter, Destroyed3x3Structure_BottomRight};
425 
426 
427     if(itemID == Structure_Wall) {
428         pDestroyedStructureTiles = DestroyedStructureTilesWall;
429         DestroyedStructureTilesSizeY = 1;
430     } else {
431         switch(structureSize.y) {
432             case 1: {
433                 pDestroyedStructureTiles = DestroyedStructureTiles1x1;
434                 DestroyedStructureTilesSizeY = 1;
435             } break;
436 
437             case 2: {
438                 if(structureSize.x == 2) {
439                     pDestroyedStructureTiles = DestroyedStructureTiles2x2;
440                     DestroyedStructureTilesSizeY = 2;
441                 } else if(structureSize.x == 3) {
442                     pDestroyedStructureTiles = DestroyedStructureTiles3x2;
443                     DestroyedStructureTilesSizeY = 3;
444                 } else {
445                     THROW(std::runtime_error, "StructureBase::destroy(): Invalid structure size");
446                 }
447             } break;
448 
449             case 3: {
450                 pDestroyedStructureTiles = DestroyedStructureTiles3x3;
451                 DestroyedStructureTilesSizeY = 3;
452             } break;
453 
454             default: {
455                 THROW(std::runtime_error, "StructureBase::destroy(): Invalid structure size");
456             } break;
457         }
458     }
459 
460     if(itemID != Structure_Wall) {
461         for(int j = 0; j < structureSize.y; j++) {
462             for(int i = 0; i < structureSize.x; i++) {
463                 Tile* pTile = currentGameMap->getTile(location.x + i, location.y + j);
464                 pTile->setDestroyedStructureTile(pDestroyedStructureTiles[DestroyedStructureTilesSizeY*j + i]);
465 
466                 Coord position((location.x+i)*TILESIZE + TILESIZE/2, (location.y+j)*TILESIZE + TILESIZE/2);
467                 Uint32 explosionID = currentGame->randomGen.getRandOf(2,Explosion_Large1,Explosion_Large2);
468                 currentGame->getExplosionList().push_back(new Explosion(explosionID, position, owner->getHouseID()) );
469 
470                 if(currentGame->randomGen.rand(1,100) <= getInfSpawnProp()) {
471                     UnitBase* pNewUnit = owner->createUnit(Unit_Soldier);
472                     pNewUnit->setHealth(pNewUnit->getMaxHealth()/2);
473                     pNewUnit->deploy(location + Coord(i,j));
474                 }
475             }
476         }
477     }
478 
479     if(isVisible(pLocalHouse->getTeam()))
480         soundPlayer->playSoundAt(Sound_ExplosionStructure, location);
481 
482 
483     delete this;
484 }
485 
getClosestPoint(const Coord & objectLocation) const486 Coord StructureBase::getClosestPoint(const Coord& objectLocation) const {
487     Coord closestPoint;
488 
489     // find the closest tile of a structure from a location
490     if(objectLocation.x <= location.x) {
491         // if we are left of the structure
492         // set destination, left most point
493         closestPoint.x = location.x;
494     } else if(objectLocation.x >= (location.x + structureSize.x-1)) {
495         //vica versa
496         closestPoint.x = location.x + structureSize.x-1;
497     } else {
498         //we are above or below at least one tile of the structure, closest path is straight
499         closestPoint.x = objectLocation.x;
500     }
501 
502     //same deal but with y
503     if(objectLocation.y <= location.y) {
504         closestPoint.y = location.y;
505     } else if(objectLocation.y >= (location.y + structureSize.y-1)) {
506         closestPoint.y = location.y + structureSize.y-1;
507     } else {
508         closestPoint.y = objectLocation.y;
509     }
510 
511     return closestPoint;
512 }
513