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