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 <units/UnitBase.h>
19 
20 #include <globals.h>
21 
22 #include <FileClasses/GFXManager.h>
23 
24 #include <SoundPlayer.h>
25 #include <Map.h>
26 #include <Bullet.h>
27 #include <ScreenBorder.h>
28 #include <House.h>
29 
30 #include <players/HumanPlayer.h>
31 
32 #include <misc/draw_util.h>
33 
34 #include <AStarSearch.h>
35 
36 #include <GUI/ObjectInterfaces/UnitInterface.h>
37 
38 #include <structures/Refinery.h>
39 #include <structures/RepairYard.h>
40 #include <units/Harvester.h>
41 
42 #define SMOKEDELAY 30
43 #define UNITIDLETIMER (GAMESPEED_DEFAULT *  315)  // about every 5s
44 
UnitBase(House * newOwner)45 UnitBase::UnitBase(House* newOwner) : ObjectBase(newOwner) {
46 
47     UnitBase::init();
48 
49     drawnAngle = currentGame->randomGen.rand(0, 7);
50     angle = drawnAngle;
51 
52     goingToRepairYard = false;
53     pickedUp = false;
54     bFollow = false;
55     guardPoint = Coord::Invalid();
56     attackPos = Coord::Invalid();
57 
58     moving = false;
59     turning = false;
60     justStoppedMoving = false;
61     xSpeed = 0;
62     ySpeed = 0;
63     bumpyOffsetX = 0;
64     bumpyOffsetY = 0;
65 
66     targetDistance = 0;
67     targetAngle = INVALID;
68 
69     noCloserPointCount = 0;
70     nextSpotFound = false;
71     nextSpotAngle = drawnAngle;
72     recalculatePathTimer = 0;
73     nextSpot = Coord::Invalid();
74 
75     findTargetTimer = 0;
76     primaryWeaponTimer = 0;
77     secondaryWeaponTimer = INVALID;
78 
79     deviationTimer = INVALID;
80 }
81 
UnitBase(InputStream & stream)82 UnitBase::UnitBase(InputStream& stream) : ObjectBase(stream) {
83 
84     UnitBase::init();
85 
86     stream.readBools(&goingToRepairYard, &pickedUp, &bFollow);
87     guardPoint.x = stream.readSint32();
88     guardPoint.y = stream.readSint32();
89     attackPos.x = stream.readSint32();
90     attackPos.y = stream.readSint32();
91 
92     stream.readBools(&moving, &turning, &justStoppedMoving);
93     xSpeed = stream.readFixPoint();
94     ySpeed = stream.readFixPoint();
95     bumpyOffsetX = stream.readFixPoint();
96     bumpyOffsetY = stream.readFixPoint();
97 
98     targetDistance = stream.readFixPoint();
99     targetAngle = stream.readSint8();
100 
101     noCloserPointCount = stream.readUint8();
102     nextSpotFound = stream.readBool();
103     nextSpotAngle = stream.readSint8();
104     recalculatePathTimer = stream.readSint32();
105     nextSpot.x = stream.readSint32();
106     nextSpot.y = stream.readSint32();
107     int numPathNodes = stream.readUint32();
108     for(int i=0;i<numPathNodes; i++) {
109         Sint32 x = stream.readSint32();
110         Sint32 y = stream.readSint32();
111         pathList.push_back(Coord(x,y));
112     }
113 
114     findTargetTimer = stream.readSint32();
115     primaryWeaponTimer = stream.readSint32();
116     secondaryWeaponTimer = stream.readSint32();
117 
118     deviationTimer = stream.readSint32();
119 }
120 
init()121 void UnitBase::init() {
122     aUnit = true;
123     canAttackStuff = true;
124 
125     tracked = false;
126     turreted = false;
127     numWeapons = 0;
128 
129     drawnFrame = 0;
130 
131     unitList.push_back(this);
132 }
133 
~UnitBase()134 UnitBase::~UnitBase() {
135     pathList.clear();
136     removeFromSelectionLists();
137 
138     for(int i=0; i < NUMSELECTEDLISTS; i++) {
139         pLocalPlayer->getGroupList(i).erase(objectID);
140     }
141 
142     currentGame->getObjectManager().removeObject(objectID);
143 }
144 
145 
save(OutputStream & stream) const146 void UnitBase::save(OutputStream& stream) const {
147 
148     ObjectBase::save(stream);
149 
150     stream.writeBools(goingToRepairYard, pickedUp, bFollow);
151     stream.writeSint32(guardPoint.x);
152     stream.writeSint32(guardPoint.y);
153     stream.writeSint32(attackPos.x);
154     stream.writeSint32(attackPos.y);
155 
156     stream.writeBools(moving, turning, justStoppedMoving);
157     stream.writeFixPoint(xSpeed);
158     stream.writeFixPoint(ySpeed);
159     stream.writeFixPoint(bumpyOffsetX);
160     stream.writeFixPoint(bumpyOffsetY);
161 
162     stream.writeFixPoint(targetDistance);
163     stream.writeSint8(targetAngle);
164 
165     stream.writeUint8(noCloserPointCount);
166     stream.writeBool(nextSpotFound);
167     stream.writeSint8(nextSpotAngle);
168     stream.writeSint32(recalculatePathTimer);
169     stream.writeSint32(nextSpot.x);
170     stream.writeSint32(nextSpot.y);
171     stream.writeUint32(pathList.size());
172     for(const Coord& coord : pathList) {
173         stream.writeSint32(coord.x);
174         stream.writeSint32(coord.y);
175     }
176 
177     stream.writeSint32(findTargetTimer);
178     stream.writeSint32(primaryWeaponTimer);
179     stream.writeSint32(secondaryWeaponTimer);
180 
181     stream.writeSint32(deviationTimer);
182 }
183 
attack()184 void UnitBase::attack() {
185 
186     if(numWeapons) {
187         Coord targetCenterPoint;
188         Coord centerPoint = getCenterPoint();
189         bool bAirBullet;
190 
191         ObjectBase* pObject = target.getObjPointer();
192         if(pObject != nullptr) {
193             targetCenterPoint = pObject->getClosestCenterPoint(location);
194             bAirBullet = pObject->isAFlyingUnit();
195         } else {
196             targetCenterPoint = currentGameMap->getTile(attackPos)->getCenterPoint();
197             bAirBullet = false;
198         }
199 
200         int currentBulletType = bulletType;
201         Sint32 currentWeaponDamage = currentGame->objectData.data[itemID][originalHouseID].weapondamage;
202 
203         if(getItemID() == Unit_Trooper && !bAirBullet) {
204             // Troopers change weapon type depending on distance
205 
206             FixPoint distance = distanceFrom(centerPoint, targetCenterPoint);
207             if(distance <= 2*TILESIZE) {
208                 currentBulletType = Bullet_ShellSmall;
209                 currentWeaponDamage--;
210             }
211         } else if(getItemID() == Unit_Launcher && bAirBullet){
212             // Launchers change weapon type when targeting flying units
213             currentBulletType = Bullet_TurretRocket;
214 
215         }
216 
217         if(primaryWeaponTimer == 0) {
218             bulletList.push_back( new Bullet( objectID, &centerPoint, &targetCenterPoint, currentBulletType, currentWeaponDamage, bAirBullet) );
219             if(pObject != nullptr) {
220                 currentGameMap->viewMap(pObject->getOwner()->getTeam(), location, 2);
221             }
222             playAttackSound();
223             primaryWeaponTimer = getWeaponReloadTime();
224 
225             secondaryWeaponTimer = 15;
226 
227             if(attackPos && getItemID() != Unit_SonicTank && currentGameMap->getTile(attackPos)->isSpiceBloom()) {
228                 setDestination(location);
229                 forced = false;
230                 attackPos.invalidate();
231             }
232 
233             // shorten deviation time
234             if(deviationTimer > 0) {
235                 deviationTimer = std::max(0,deviationTimer - MILLI2CYCLES(20*1000));
236             }
237         }
238 
239         if((numWeapons == 2) && (secondaryWeaponTimer == 0) && (isBadlyDamaged() == false)) {
240             bulletList.push_back( new Bullet( objectID, &centerPoint, &targetCenterPoint, currentBulletType, currentWeaponDamage, bAirBullet) );
241             if(pObject != nullptr) {
242                 currentGameMap->viewMap(pObject->getOwner()->getTeam(), location, 2);
243             }
244             playAttackSound();
245             secondaryWeaponTimer = -1;
246 
247             if(attackPos && getItemID() != Unit_SonicTank && currentGameMap->getTile(attackPos)->isSpiceBloom()) {
248                 setDestination(location);
249                 forced = false;
250                 attackPos.invalidate();
251             }
252 
253             // shorten deviation time
254             if(deviationTimer > 0) {
255                 deviationTimer = std::max(0,deviationTimer - MILLI2CYCLES(20*1000));
256             }
257         }
258     }
259 }
260 
blitToScreen()261 void UnitBase::blitToScreen() {
262     int x = screenborder->world2screenX(realX);
263     int y = screenborder->world2screenY(realY);
264 
265     SDL_Texture* pUnitGraphic = graphic[currentZoomlevel];
266     SDL_Rect source = calcSpriteSourceRect(pUnitGraphic, drawnAngle, numImagesX, drawnFrame, numImagesY);
267     SDL_Rect dest = calcSpriteDrawingRect( pUnitGraphic, x, y, numImagesX, numImagesY, HAlign::Center, VAlign::Center);
268 
269     SDL_RenderCopy(renderer, pUnitGraphic, &source, &dest);
270 
271     if(isBadlyDamaged()) {
272         drawSmoke(x, y);
273     }
274 }
275 
getInterfaceContainer()276 ObjectInterface* UnitBase::getInterfaceContainer() {
277     if((pLocalHouse == owner && isRespondable()) || (debug == true)) {
278         return UnitInterface::create(objectID);
279     } else {
280         return DefaultObjectInterface::create(objectID);
281     }
282 }
283 
getCurrentAttackAngle() const284 int UnitBase::getCurrentAttackAngle() const {
285     return drawnAngle;
286 }
287 
deploy(const Coord & newLocation)288 void UnitBase::deploy(const Coord& newLocation) {
289 
290     if(currentGameMap->tileExists(newLocation)) {
291         setLocation(newLocation);
292 
293         if (guardPoint.isInvalid())
294             guardPoint = location;
295         setDestination(guardPoint);
296         pickedUp = false;
297         setRespondable(true);
298         setActive(true);
299         setVisible(VIS_ALL, true);
300         setForced(false);
301 
302         // Deployment logic to hopefully stop units freezing
303         if (getAttackMode() == CARRYALLREQUESTED || getAttackMode() == HUNT) {
304             if(getItemID() == Unit_Harvester) {
305                 doSetAttackMode(HARVEST);
306             } else {
307                 doSetAttackMode(GUARD);
308             }
309         }
310 
311         if(isAGroundUnit() && (getItemID() != Unit_Sandworm)) {
312             if(currentGameMap->getTile(location)->isSpiceBloom()) {
313                 setHealth(0);
314                 setVisible(VIS_ALL, false);
315                 currentGameMap->getTile(location)->triggerSpiceBloom(getOwner());
316             } else if(currentGameMap->getTile(location)->isSpecialBloom()){
317                 currentGameMap->getTile(location)->triggerSpecialBloom(getOwner());
318             }
319         }
320 
321         if(pLocalHouse == getOwner()) {
322             pLocalPlayer->onUnitDeployed(this);
323         }
324     }
325 }
326 
destroy()327 void UnitBase::destroy() {
328 
329     setTarget(nullptr);
330     currentGameMap->removeObjectFromMap(getObjectID()); //no map point will reference now
331     currentGame->getObjectManager().removeObject(objectID);
332 
333     currentGame->getHouse(originalHouseID)->decrementUnits(itemID);
334 
335     unitList.remove(this);
336 
337     if(isVisible()) {
338         if(currentGame->randomGen.rand(1,100) <= getInfSpawnProp()) {
339             UnitBase* pNewUnit = currentGame->getHouse(originalHouseID)->createUnit(Unit_Soldier);
340             pNewUnit->setHealth(pNewUnit->getMaxHealth()/2);
341             pNewUnit->deploy(location);
342 
343             if(owner->getHouseID() != originalHouseID) {
344                 // deviation is inherited
345                 pNewUnit->owner = owner;
346                 pNewUnit->graphic = pGFXManager->getObjPic(pNewUnit->graphicID,owner->getHouseID());
347                 pNewUnit->deviationTimer = deviationTimer;
348             }
349         }
350     }
351 
352     delete this;
353 }
354 
deviate(House * newOwner)355 void UnitBase::deviate(House* newOwner) {
356 
357     if(newOwner->getHouseID() == originalHouseID) {
358         quitDeviation();
359     } else {
360         removeFromSelectionLists();
361         setTarget(nullptr);
362         setGuardPoint(location);
363         setDestination(location);
364         clearPath();
365         doSetAttackMode(GUARD);
366         owner = newOwner;
367 
368         graphic = pGFXManager->getObjPic(graphicID,getOwner()->getHouseID());
369         deviationTimer = DEVIATIONTIME;
370     }
371 
372     // Adding this in as a surrogate for damage inflicted upon deviation.. Still not sure what the best value
373     // should be... going in with a 25% of the units value unless its a devastator which we can destruct or an ornithoper
374     // which is likely to get killed
375     if(getItemID() == Unit_Devastator || getItemID() == Unit_Ornithopter){
376         newOwner->informHasDamaged(Unit_Deviator, currentGame->objectData.data[getItemID()][newOwner->getHouseID()].price);
377     } else{
378         newOwner->informHasDamaged(Unit_Deviator, currentGame->objectData.data[getItemID()][newOwner->getHouseID()].price / 5);
379     }
380 
381 
382 
383 }
384 
drawSelectionBox()385 void UnitBase::drawSelectionBox() {
386 
387     SDL_Texture* selectionBox = nullptr;
388 
389     switch(currentZoomlevel) {
390         case 0:     selectionBox = pGFXManager->getUIGraphic(UI_SelectionBox_Zoomlevel0);   break;
391         case 1:     selectionBox = pGFXManager->getUIGraphic(UI_SelectionBox_Zoomlevel1);   break;
392         case 2:
393         default:    selectionBox = pGFXManager->getUIGraphic(UI_SelectionBox_Zoomlevel2);   break;
394     }
395 
396     SDL_Rect dest = calcDrawingRect(selectionBox, screenborder->world2screenX(realX), screenborder->world2screenY(realY), HAlign::Center, VAlign::Center);
397     SDL_RenderCopy(renderer, selectionBox, nullptr, &dest);
398 
399     int x = screenborder->world2screenX(realX) - getWidth(selectionBox)/2;
400     int y = screenborder->world2screenY(realY) - getHeight(selectionBox)/2;
401     for(int i=1;i<=currentZoomlevel+1;i++) {
402         renderDrawHLine(renderer, x+1, y-i, x+1 + (lround((getHealth()/getMaxHealth())*(getWidth(selectionBox)-3))), getHealthColor());
403     }
404 }
405 
drawOtherPlayerSelectionBox()406 void UnitBase::drawOtherPlayerSelectionBox() {
407     SDL_Texture* selectionBox = nullptr;
408 
409     switch(currentZoomlevel) {
410         case 0:     selectionBox = pGFXManager->getUIGraphic(UI_OtherPlayerSelectionBox_Zoomlevel0);   break;
411         case 1:     selectionBox = pGFXManager->getUIGraphic(UI_OtherPlayerSelectionBox_Zoomlevel1);   break;
412         case 2:
413         default:    selectionBox = pGFXManager->getUIGraphic(UI_OtherPlayerSelectionBox_Zoomlevel2);   break;
414     }
415 
416     SDL_Rect dest = calcDrawingRect(selectionBox, screenborder->world2screenX(realX), screenborder->world2screenY(realY), HAlign::Center, VAlign::Center);
417     SDL_RenderCopy(renderer, selectionBox, nullptr, &dest);
418 }
419 
420 
releaseTarget()421 void UnitBase::releaseTarget() {
422     if(forced == true) {
423         guardPoint = location;
424     }
425     setDestination(guardPoint);
426 
427     findTargetTimer = 0;
428     setForced(false);
429     setTarget(nullptr);
430 }
431 
engageTarget()432 void UnitBase::engageTarget() {
433 
434     if(target && (target.getObjPointer() == nullptr)) {
435         // the target does not exist anymore
436         releaseTarget();
437         return;
438     }
439 
440     if(target && (target.getObjPointer()->isActive() == false)) {
441         // the target changed its state to inactive
442         releaseTarget();
443         return;
444     }
445 
446     if(target && !targetFriendly && !canAttack(target.getObjPointer())) {
447         // the (non-friendly) target cannot be attacked anymore
448         releaseTarget();
449         return;
450     }
451 
452     if(target && !targetFriendly && !forced && !isInAttackRange(target.getObjPointer())) {
453         // the (non-friendly) target left the attack mode range (and we were not forced to attack it)
454         releaseTarget();
455         return;
456     }
457 
458     if(target) {
459         // we have a target unit or structure
460 
461         Coord targetLocation = target.getObjPointer()->getClosestPoint(location);
462 
463         if(destination != targetLocation) {
464             // the location of the target has moved
465             // => recalculate path
466             clearPath();
467         }
468 
469         targetDistance = blockDistance(location, targetLocation);
470 
471         Sint8 newTargetAngle = destinationDrawnAngle(location, targetLocation);
472 
473         if(bFollow) {
474             // we are following someone
475             setDestination(targetLocation);
476             return;
477         }
478 
479         if(targetDistance > getWeaponRange()) {
480             // we are not in attack range
481             if(target.getObjPointer()->isAFlyingUnit()) {
482                 // we are not following this air unit
483                 releaseTarget();
484                 return;
485             } else {
486                 // follow the target
487                 setDestination(targetLocation);
488                 return;
489             }
490         }
491 
492         // we are in attack range
493 
494         if(targetFriendly && !forced) {
495             // the target is friendly and we only attack these if were forced to do so
496             return;
497         }
498 
499         if(goingToRepairYard) {
500             // we are going to the repair yard
501             // => we do not need to change the destination
502             targetAngle = INVALID;
503         } else if(attackMode == CAPTURE) {
504             // we want to capture the target building
505             setDestination(targetLocation);
506             targetAngle = INVALID;
507         } else if(isTracked() && target.getObjPointer()->isInfantry() && !targetFriendly && currentGameMap->tileExists(targetLocation) && !currentGameMap->getTile(targetLocation)->isMountain() && forced) {
508             // we squash the infantry unit because we are forced to
509             setDestination(targetLocation);
510             targetAngle = INVALID;
511         } else {
512             // we decide to fire on the target thus we can stop moving
513             setDestination(location);
514             targetAngle = newTargetAngle;
515         }
516 
517         if(getCurrentAttackAngle() == newTargetAngle) {
518             attack();
519         }
520 
521     } else if(attackPos) {
522         // we attack a position
523 
524         targetDistance = blockDistance(location, attackPos);
525 
526         Sint8 newTargetAngle = destinationDrawnAngle(location, attackPos);
527 
528         if(targetDistance <= getWeaponRange()) {
529             // we are in weapon range thus we can stop moving
530             setDestination(location);
531             targetAngle = newTargetAngle;
532 
533             if(getCurrentAttackAngle() == newTargetAngle) {
534                 attack();
535             }
536         } else {
537             targetAngle = INVALID;
538         }
539     }
540 }
541 
move()542 void UnitBase::move() {
543 
544     if(moving && !justStoppedMoving) {
545         if((isBadlyDamaged() == false) || isAFlyingUnit()) {
546             realX += xSpeed;
547             realY += ySpeed;
548         } else {
549             realX += xSpeed/2;
550             realY += ySpeed/2;
551         }
552 
553         // check if vehicle is on the first half of the way
554         FixPoint fromDistanceX;
555         FixPoint fromDistanceY;
556         FixPoint toDistanceX;
557         FixPoint toDistanceY;
558         if(location != nextSpot) {
559             // check if vehicle is half way out of old tile
560 
561             fromDistanceX = FixPoint::abs(location.x*TILESIZE - (realX-bumpyOffsetX) + TILESIZE/2);
562             fromDistanceY = FixPoint::abs(location.y*TILESIZE - (realY-bumpyOffsetY) + TILESIZE/2);
563             toDistanceX = FixPoint::abs(nextSpot.x*TILESIZE - (realX-bumpyOffsetX) + TILESIZE/2);
564             toDistanceY = FixPoint::abs(nextSpot.y*TILESIZE - (realY-bumpyOffsetY) + TILESIZE/2);
565 
566             if((fromDistanceX >= TILESIZE/2) || (fromDistanceY >= TILESIZE/2)) {
567                 // let something else go in
568                 unassignFromMap(location);
569                 oldLocation = location;
570                 location = nextSpot;
571 
572                 if(isAFlyingUnit() == false && itemID != Unit_Sandworm) {
573                     currentGameMap->viewMap(owner->getTeam(), location, getViewRange());
574                 }
575             }
576 
577         } else {
578             // if vehicle is out of old tile
579 
580             fromDistanceX = FixPoint::abs(oldLocation.x*TILESIZE - (realX-bumpyOffsetX) + TILESIZE/2);
581             fromDistanceY = FixPoint::abs(oldLocation.y*TILESIZE - (realY-bumpyOffsetY) + TILESIZE/2);
582             toDistanceX = FixPoint::abs(location.x*TILESIZE - (realX-bumpyOffsetX) + TILESIZE/2);
583             toDistanceY = FixPoint::abs(location.y*TILESIZE - (realY-bumpyOffsetY) + TILESIZE/2);
584 
585             if ((fromDistanceX >= TILESIZE) || (fromDistanceY >= TILESIZE)) {
586 
587                 if(forced && (location == destination) && !target) {
588                     setForced(false);
589                     if(getAttackMode() == CARRYALLREQUESTED) {
590                         doSetAttackMode(GUARD);
591                     }
592                 }
593 
594                 moving = false;
595                 justStoppedMoving = true;
596                 realX = location.x * TILESIZE + TILESIZE/2;
597                 realY = location.y * TILESIZE + TILESIZE/2;
598                 bumpyOffsetX = 0;
599                 bumpyOffsetY = 0;
600 
601                 oldLocation.invalidate();
602             }
603 
604         }
605 
606         bumpyMovementOnRock(fromDistanceX, fromDistanceY, toDistanceX, toDistanceY);
607 
608     } else {
609         justStoppedMoving = false;
610     }
611 
612     checkPos();
613 }
614 
bumpyMovementOnRock(FixPoint fromDistanceX,FixPoint fromDistanceY,FixPoint toDistanceX,FixPoint toDistanceY)615 void UnitBase::bumpyMovementOnRock(FixPoint fromDistanceX, FixPoint fromDistanceY, FixPoint toDistanceX, FixPoint toDistanceY) {
616 
617     if(hasBumpyMovementOnRock() && ((currentGameMap->getTile(location)->getType() == Terrain_Rock)
618                                     || (currentGameMap->getTile(location)->getType() == Terrain_Mountain)
619                                     || (currentGameMap->getTile(location)->getType() == Terrain_ThickSpice))) {
620         // bumping effect
621 
622         const FixPoint epsilon = FixPt(0,005);
623         const FixPoint bumpyOffset = FixPt(2,5);
624         const FixPoint absXSpeed = FixPoint::abs(xSpeed);
625         const FixPoint absYSpeed = FixPoint::abs(ySpeed);
626 
627 
628         if((FixPoint::abs(xSpeed) >= epsilon) && (FixPoint::abs(fromDistanceX - absXSpeed) < absXSpeed/2)) { realY -= bumpyOffset; bumpyOffsetY -= bumpyOffset; }
629         if((FixPoint::abs(ySpeed) >= epsilon) && (FixPoint::abs(fromDistanceY - absYSpeed) < absYSpeed/2)) { realX += bumpyOffset; bumpyOffsetX += bumpyOffset; }
630 
631         if((FixPoint::abs(xSpeed) >= epsilon) && (FixPoint::abs(fromDistanceX - 4*absXSpeed) < absXSpeed/2)) { realY += bumpyOffset; bumpyOffsetY += bumpyOffset; }
632         if((FixPoint::abs(ySpeed) >= epsilon) && (FixPoint::abs(fromDistanceY - 4*absYSpeed) < absYSpeed/2)) { realX -= bumpyOffset; bumpyOffsetX -= bumpyOffset; }
633 
634 
635         if((FixPoint::abs(xSpeed) >= epsilon) && (FixPoint::abs(fromDistanceX - 10*absXSpeed) < absXSpeed/2)) { realY -= bumpyOffset; bumpyOffsetY -= bumpyOffset; }
636         if((FixPoint::abs(ySpeed) >= epsilon) && (FixPoint::abs(fromDistanceY - 20*absYSpeed) < absYSpeed/2)) { realX += bumpyOffset; bumpyOffsetX += bumpyOffset; }
637 
638         if((FixPoint::abs(xSpeed) >= epsilon) && (FixPoint::abs(fromDistanceX - 14*absXSpeed) < absXSpeed/2)) { realY += bumpyOffset; bumpyOffsetY += bumpyOffset; }
639         if((FixPoint::abs(ySpeed) >= epsilon) && (FixPoint::abs(fromDistanceY - 14*absYSpeed) < absYSpeed/2)) { realX -= bumpyOffset; bumpyOffsetX -= bumpyOffset; }
640 
641 
642         if((FixPoint::abs(xSpeed) >= epsilon) && (FixPoint::abs(toDistanceX - absXSpeed) < absXSpeed/2)) { realY -= bumpyOffset; bumpyOffsetY -= bumpyOffset; }
643         if((FixPoint::abs(ySpeed) >= epsilon) && (FixPoint::abs(toDistanceY - absYSpeed) < absYSpeed/2)) { realX += bumpyOffset; bumpyOffsetX += bumpyOffset; }
644 
645         if((FixPoint::abs(xSpeed) >= epsilon) && (FixPoint::abs(toDistanceX - 4*absXSpeed) < absXSpeed/2)) { realY += bumpyOffset; bumpyOffsetY += bumpyOffset; }
646         if((FixPoint::abs(ySpeed) >= epsilon) && (FixPoint::abs(toDistanceY - 4*absYSpeed) < absYSpeed/2)) { realX -= bumpyOffset; bumpyOffsetX -= bumpyOffset; }
647 
648         if((FixPoint::abs(xSpeed) >= epsilon) && (FixPoint::abs(toDistanceX - 10*absXSpeed) < absXSpeed/2)) { realY -= bumpyOffset; bumpyOffsetY -= bumpyOffset; }
649         if((FixPoint::abs(ySpeed) >= epsilon) && (FixPoint::abs(toDistanceY - 10*absYSpeed) < absYSpeed/2)) { realX += bumpyOffset; bumpyOffsetX += bumpyOffset; }
650 
651         if((FixPoint::abs(xSpeed) >= epsilon) && (FixPoint::abs(toDistanceX - 14*absXSpeed) < absXSpeed/2)) { realY += bumpyOffset; bumpyOffsetY += bumpyOffset; }
652         if((FixPoint::abs(ySpeed) >= epsilon) && (FixPoint::abs(toDistanceY - 14*absYSpeed) < absYSpeed/2)) { realX -= bumpyOffset; bumpyOffsetX -= bumpyOffset; }
653 
654     }
655 }
656 
navigate()657 void UnitBase::navigate() {
658 
659     if(isAFlyingUnit() || (((currentGame->getGameCycleCount() + getObjectID()*1337) % 5) == 0)) {
660         // navigation is only performed every 5th frame
661 
662         if(!moving && !justStoppedMoving) {
663             if(location != destination) {
664                 if(nextSpotFound == false)  {
665 
666                     if(pathList.empty() && (recalculatePathTimer == 0)) {
667                         recalculatePathTimer = 100;
668 
669                         if(!SearchPathWithAStar() && (++noCloserPointCount >= 3)
670                             && (location != oldLocation))
671                         {   //try searching for a path a number of times then give up
672                             if (target.getObjPointer() != nullptr && targetFriendly
673                                 && (target.getObjPointer()->getItemID() != Structure_RepairYard)
674                                 && ((target.getObjPointer()->getItemID() != Structure_Refinery)
675                                 || (getItemID() != Unit_Harvester))) {
676                                 setTarget(nullptr);
677                             }
678 
679                             /// This method will transport units if they get stuck inside a base
680                             /// This often happens after an AI get nuked and has a hole in their base
681                             if(getOwner()->hasCarryalls()
682                                && this->isAGroundUnit()
683                                && (currentGame->getGameInitSettings().getGameOptions().manualCarryallDrops || getOwner()->isAI())
684                                && blockDistance(location, destination) >= MIN_CARRYALL_LIFT_DISTANCE ) {
685                                static_cast<GroundUnit*>(this)->requestCarryall();
686                             } else if(  getOwner()->isAI()
687                                         && (getItemID() == Unit_Harvester)
688                                         && !static_cast<Harvester*>(this)->isReturning()
689                                         && blockDistance(location, destination) >= 2) {
690                                 // try getting back to a refinery
691                                 static_cast<Harvester*>(this)->doReturn();
692                             } else {
693                                 setDestination(location);   //can't get any closer, give up
694                                 forced = false;
695                             }
696                         }
697                     }
698 
699                     if(!pathList.empty()) {
700                         nextSpot = pathList.front();
701                         pathList.pop_front();
702                         nextSpotFound = true;
703                         recalculatePathTimer = 0;
704                         noCloserPointCount = 0;
705                     }
706                 } else {
707                     int tempAngle = currentGameMap->getPosAngle(location, nextSpot);
708                     if(tempAngle != INVALID) {
709                         nextSpotAngle = tempAngle;
710                     }
711 
712                     if(!canPass(nextSpot.x, nextSpot.y)) {
713                         clearPath();
714                     } else {
715                         if (drawnAngle == nextSpotAngle)    {
716                             moving = true;
717                             nextSpotFound = false;
718 
719                             assignToMap(nextSpot);
720                             angle = drawnAngle;
721                             setSpeeds();
722                         }
723                     }
724                 }
725             } else if(!target && attackPos.isInvalid()) {
726                 if(((currentGame->getGameCycleCount() + getObjectID()*1337) % MILLI2CYCLES(UNITIDLETIMER)) == 0) {
727                     idleAction();
728                 }
729             }
730         }
731     }
732 }
733 
idleAction()734 void UnitBase::idleAction() {
735     //not moving and not wanting to go anywhere, do some random turning
736     if(isAGroundUnit() && (getItemID() != Unit_Harvester) && (getAttackMode() == GUARD)) {
737         // we might turn this cylce with 20% chance
738         if(currentGame->randomGen.rand(0, 4) == 0) {
739             // choose a random one of the eight possible angles
740             nextSpotAngle = currentGame->randomGen.rand(0, 7);
741         }
742     }
743 }
744 
handleActionClick(int xPos,int yPos)745 void UnitBase::handleActionClick(int xPos, int yPos) {
746     if(respondable) {
747         if(currentGameMap->tileExists(xPos, yPos)) {
748             if(currentGameMap->getTile(xPos,yPos)->hasAnObject()) {
749                 // attack unit/structure or move to structure
750                 ObjectBase* tempTarget = currentGameMap->getTile(xPos,yPos)->getObject();
751 
752                 if(tempTarget->getOwner()->getTeam() != getOwner()->getTeam()) {
753                     // attack
754                     currentGame->getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_UNIT_ATTACKOBJECT,objectID,tempTarget->getObjectID()));
755                 } else {
756                     // move to object/structure
757                     currentGame->getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_UNIT_MOVE2OBJECT,objectID,tempTarget->getObjectID()));
758                 }
759             } else {
760                 // move this unit
761                 currentGame->getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_UNIT_MOVE2POS,objectID,(Uint32) xPos, (Uint32) yPos, (Uint32) true));
762             }
763         }
764     }
765 }
766 
handleAttackClick(int xPos,int yPos)767 void UnitBase::handleAttackClick(int xPos, int yPos) {
768     if(respondable) {
769         if(currentGameMap->tileExists(xPos, yPos)) {
770             if(currentGameMap->getTile(xPos,yPos)->hasAnObject()) {
771                 // attack unit/structure or move to structure
772                 ObjectBase* tempTarget = currentGameMap->getTile(xPos,yPos)->getObject();
773 
774                 currentGame->getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_UNIT_ATTACKOBJECT,objectID,tempTarget->getObjectID()));
775             } else {
776                 // attack pos
777                 currentGame->getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_UNIT_ATTACKPOS,objectID,(Uint32) xPos, (Uint32) yPos, (Uint32) true));
778             }
779         }
780     }
781 
782 }
783 
handleMoveClick(int xPos,int yPos)784 void UnitBase::handleMoveClick(int xPos, int yPos) {
785     if(respondable) {
786         if(currentGameMap->tileExists(xPos, yPos)) {
787             // move to pos
788             currentGame->getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_UNIT_MOVE2POS,objectID,(Uint32) xPos, (Uint32) yPos, (Uint32) true));
789         }
790     }
791 }
792 
handleSetAttackModeClick(ATTACKMODE newAttackMode)793 void UnitBase::handleSetAttackModeClick(ATTACKMODE newAttackMode) {
794     currentGame->getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_UNIT_SETMODE,objectID,(Uint32) newAttackMode));
795 }
796 
797 /**
798     User action
799     Request a Carryall to drop at target location
800 **/
handleRequestCarryallDropClick(int xPos,int yPos)801 void UnitBase::handleRequestCarryallDropClick(int xPos, int yPos) {
802     if(respondable) {
803         if(currentGameMap->tileExists(xPos, yPos)) {
804             currentGame->getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_UNIT_REQUESTCARRYALLDROP, objectID, (Uint32) xPos, (Uint32) yPos));
805         }
806     }
807 }
808 
809 
810 
doMove2Pos(int xPos,int yPos,bool bForced)811 void UnitBase::doMove2Pos(int xPos, int yPos, bool bForced) {
812     if(attackMode == CAPTURE || attackMode == HUNT) {
813         doSetAttackMode(GUARD);
814     }
815 
816     if(currentGameMap->tileExists(xPos, yPos)) {
817         if((xPos != destination.x) || (yPos != destination.y)) {
818             clearPath();
819             findTargetTimer = 0;
820         }
821 
822         setTarget(nullptr);
823         setDestination(xPos,yPos);
824         setForced(bForced);
825         setGuardPoint(xPos,yPos);
826     } else {
827         setTarget(nullptr);
828         setDestination(location);
829         setForced(bForced);
830         setGuardPoint(location);
831     }
832 }
833 
doMove2Pos(const Coord & coord,bool bForced)834 void UnitBase::doMove2Pos(const Coord& coord, bool bForced) {
835     doMove2Pos(coord.x, coord.y, bForced);
836 }
837 
doMove2Object(const ObjectBase * pTargetObject)838 void UnitBase::doMove2Object(const ObjectBase* pTargetObject) {
839     if(pTargetObject->getObjectID() == getObjectID()) {
840         return;
841     }
842 
843     if(attackMode == CAPTURE || attackMode == HUNT) {
844         doSetAttackMode(GUARD);
845     }
846 
847     setDestination(INVALID_POS,INVALID_POS);
848     setTarget(pTargetObject);
849     setForced(true);
850 
851     bFollow = true;
852 
853     clearPath();
854     findTargetTimer = 0;
855 }
856 
doMove2Object(Uint32 targetObjectID)857 void UnitBase::doMove2Object(Uint32 targetObjectID) {
858     ObjectBase* pObject = currentGame->getObjectManager().getObject(targetObjectID);
859 
860     if(pObject == nullptr) {
861         return;
862     }
863 
864     doMove2Object(pObject);
865 }
866 
doAttackPos(int xPos,int yPos,bool bForced)867 void UnitBase::doAttackPos(int xPos, int yPos, bool bForced) {
868     if(!currentGameMap->tileExists(xPos, yPos)) {
869         return;
870     }
871 
872     if(attackMode == CAPTURE) {
873         doSetAttackMode(GUARD);
874     }
875 
876     setDestination(xPos,yPos);
877     setTarget(nullptr);
878     setForced(bForced);
879     attackPos.x = xPos;
880     attackPos.y = yPos;
881 
882     clearPath();
883     findTargetTimer = 0;
884 }
885 
doAttackObject(const ObjectBase * pTargetObject,bool bForced)886 void UnitBase::doAttackObject(const ObjectBase* pTargetObject, bool bForced) {
887     if(pTargetObject->getObjectID() == getObjectID() || (!canAttack() && getItemID() != Unit_Harvester)) {
888         return;
889     }
890 
891     if(attackMode == CAPTURE) {
892         doSetAttackMode(GUARD);
893     }
894 
895     setDestination(INVALID_POS,INVALID_POS);
896 
897     setTarget(pTargetObject);
898     // hack to make it possible to attack own repair yard
899     if(goingToRepairYard && target && (target.getObjPointer()->getItemID() == Structure_RepairYard)) {
900         static_cast<RepairYard*>(target.getObjPointer())->unBook();
901         goingToRepairYard = false;
902     }
903 
904 
905     setForced(bForced);
906 
907     clearPath();
908     findTargetTimer = 0;
909 }
910 
doAttackObject(Uint32 TargetObjectID,bool bForced)911 void UnitBase::doAttackObject(Uint32 TargetObjectID, bool bForced) {
912     ObjectBase* pObject = currentGame->getObjectManager().getObject(TargetObjectID);
913 
914     if(pObject == nullptr) {
915         return;
916     }
917 
918     doAttackObject(pObject, bForced);
919 }
920 
doSetAttackMode(ATTACKMODE newAttackMode)921 void UnitBase::doSetAttackMode(ATTACKMODE newAttackMode) {
922     if((newAttackMode >= 0) && (newAttackMode < ATTACKMODE_MAX)) {
923         attackMode = newAttackMode;
924     }
925 
926     if(attackMode == GUARD || attackMode == STOP) {
927         if(moving && !justStoppedMoving) {
928             doMove2Pos(nextSpot, false);
929         } else {
930             doMove2Pos(location, false);
931         }
932     }
933 }
934 
handleDamage(int damage,Uint32 damagerID,House * damagerOwner)935 void UnitBase::handleDamage(int damage, Uint32 damagerID, House* damagerOwner) {
936     // shorten deviation time
937     if(deviationTimer > 0) {
938         deviationTimer = std::max(0,deviationTimer - MILLI2CYCLES(damage*20*1000));
939     }
940 
941     ObjectBase::handleDamage(damage, damagerID, damagerOwner);
942 
943     ObjectBase* pDamager = currentGame->getObjectManager().getObject(damagerID);
944 
945     if(pDamager != nullptr){
946 
947         if(attackMode == HUNT && !forced) {
948             ObjectBase* pDamager = currentGame->getObjectManager().getObject(damagerID);
949             if(canAttack(pDamager)) {
950                 if(!target || target.getObjPointer() == nullptr || !isInWeaponRange(target.getObjPointer())) {
951                     // no target or target not on weapon range => switch target
952                     doAttackObject(pDamager, false);
953                 }
954 
955             }
956         }
957 
958         /*
959          This method records the damage taken so that QuantBot can use it to know how effective different unit
960          classes are during the current game so that it can adjust its unit build ratios
961         */
962 
963         // If you damaged your own unit then, the damage should be treated as negative.
964         if(damagerOwner == getOwner()){
965             damage *= -1;
966         }
967 
968         damagerOwner->informHasDamaged(pDamager->getItemID(), damage);
969     }
970 }
971 
isInGuardRange(const ObjectBase * pObject) const972 bool UnitBase::isInGuardRange(const ObjectBase* pObject) const  {
973     int checkRange;
974     switch(attackMode) {
975         case GUARD: {
976             checkRange = getWeaponRange();
977         } break;
978 
979         case AREAGUARD: {
980             checkRange = getAreaGuardRange();
981         } break;
982 
983         case AMBUSH: {
984             checkRange = getViewRange();
985         } break;
986 
987         case HUNT: {
988             return true;
989         } break;
990 
991         case CARRYALLREQUESTED: {
992             return false;
993         } break;
994 
995         case RETREAT: {
996             return false;
997         } break;
998 
999         case STOP:
1000         default: {
1001             return false;
1002         } break;
1003     }
1004 
1005     if(getItemID() == Unit_Sandworm) {
1006         checkRange = getViewRange();
1007     }
1008 
1009     return (blockDistance(guardPoint*TILESIZE + Coord(TILESIZE/2, TILESIZE/2), pObject->getCenterPoint()) <= checkRange*TILESIZE);
1010 }
1011 
isInAttackRange(const ObjectBase * pObject) const1012 bool UnitBase::isInAttackRange(const ObjectBase* pObject) const {
1013     int checkRange;
1014     switch(attackMode) {
1015         case GUARD: {
1016             checkRange = getWeaponRange();
1017         } break;
1018 
1019         case AREAGUARD: {
1020             checkRange = getAreaGuardRange() + getWeaponRange() + 1;
1021         } break;
1022 
1023         case AMBUSH: {
1024             checkRange = getViewRange() + 1;
1025         } break;
1026 
1027         case HUNT: {
1028             return true;
1029         } break;
1030 
1031         case CARRYALLREQUESTED: {
1032             return false;
1033         } break;
1034 
1035         case RETREAT: {
1036             return false;
1037         } break;
1038 
1039         case STOP:
1040         default: {
1041             return false;
1042         } break;
1043     }
1044 
1045     if(getItemID() == Unit_Sandworm) {
1046         checkRange = getViewRange() + 1;
1047     }
1048 
1049     return (blockDistance(guardPoint*TILESIZE + Coord(TILESIZE/2, TILESIZE/2), pObject->getCenterPoint()) <= checkRange*TILESIZE);
1050 }
1051 
isInWeaponRange(const ObjectBase * object) const1052 bool UnitBase::isInWeaponRange(const ObjectBase* object) const {
1053     if(object == nullptr) {
1054         return false;
1055     }
1056 
1057     Coord targetLocation = target.getObjPointer()->getClosestPoint(location);
1058 
1059     return (blockDistance(location, targetLocation) <= getWeaponRange());
1060 }
1061 
1062 
setAngle(int newAngle)1063 void UnitBase::setAngle(int newAngle) {
1064     if(!moving && !justStoppedMoving) {
1065         newAngle = newAngle % NUM_ANGLES;
1066         angle = drawnAngle = newAngle;
1067         clearPath();
1068     }
1069 }
1070 
setGettingRepaired()1071 void UnitBase::setGettingRepaired() {
1072     if(target.getObjPointer() != nullptr && (target.getObjPointer()->getItemID() == Structure_RepairYard)) {
1073         if(selected) {
1074             removeFromSelectionLists();
1075         }
1076 
1077         currentGameMap->removeObjectFromMap(getObjectID());
1078 
1079         static_cast<RepairYard*>(target.getObjPointer())->assignUnit(this);
1080 
1081         respondable = false;
1082         setActive(false);
1083         setVisible(VIS_ALL, false);
1084         goingToRepairYard = false;
1085         badlyDamaged = false;
1086 
1087         setTarget(nullptr);
1088         //setLocation(INVALID_POS, INVALID_POS);
1089         setDestination(location);
1090         nextSpotAngle = DOWN;
1091     }
1092 }
1093 
setGuardPoint(int newX,int newY)1094 void UnitBase::setGuardPoint(int newX, int newY) {
1095     if(currentGameMap->tileExists(newX, newY) || ((newX == INVALID_POS) && (newY == INVALID_POS))) {
1096         guardPoint.x = newX;
1097         guardPoint.y = newY;
1098 
1099         if((getItemID() == Unit_Harvester) && guardPoint.isValid()) {
1100             if(currentGameMap->getTile(newX, newY)->hasSpice()) {
1101                 if(attackMode == STOP) {
1102                     attackMode = GUARD;
1103                 }
1104             } else {
1105                 if(attackMode != STOP) {
1106                     attackMode = STOP;
1107                 }
1108             }
1109         }
1110     }
1111 }
1112 
setLocation(int xPos,int yPos)1113 void UnitBase::setLocation(int xPos, int yPos) {
1114 
1115     if((xPos == INVALID_POS) && (yPos == INVALID_POS)) {
1116         ObjectBase::setLocation(xPos, yPos);
1117     } else if (currentGameMap->tileExists(xPos, yPos)) {
1118         ObjectBase::setLocation(xPos, yPos);
1119         realX += TILESIZE/2;
1120         realY += TILESIZE/2;
1121         bumpyOffsetX = 0;
1122         bumpyOffsetY = 0;
1123     }
1124 
1125     moving = false;
1126     pickedUp = false;
1127     setTarget(nullptr);
1128 
1129     clearPath();
1130 }
1131 
setPickedUp(UnitBase * newCarrier)1132 void UnitBase::setPickedUp(UnitBase* newCarrier) {
1133     if(selected) {
1134         removeFromSelectionLists();
1135     }
1136 
1137     currentGameMap->removeObjectFromMap(getObjectID());
1138 
1139     if(goingToRepairYard) {
1140         static_cast<RepairYard*>(target.getObjPointer())->unBook();
1141     }
1142 
1143     if(getItemID() == Unit_Harvester) {
1144         Harvester* harvester = static_cast<Harvester*>(this);
1145         if(harvester->isReturning() && target && (target.getObjPointer()!= nullptr) && (target.getObjPointer()->getItemID() == Structure_Refinery)) {
1146             static_cast<Refinery*>(target.getObjPointer())->unBook();
1147         }
1148     }
1149 
1150     target.pointTo(newCarrier);
1151 
1152     goingToRepairYard = false;
1153     forced = false;
1154     moving = false;
1155     pickedUp = true;
1156     respondable = false;
1157     setActive(false);
1158     setVisible(VIS_ALL, false);
1159 
1160     clearPath();
1161 }
1162 
getMaxSpeed() const1163 FixPoint UnitBase::getMaxSpeed() const {
1164     return currentGame->objectData.data[itemID][originalHouseID].maxspeed;
1165 }
1166 
setSpeeds()1167 void UnitBase::setSpeeds() {
1168     FixPoint speed = getMaxSpeed();
1169 
1170     if(!isAFlyingUnit()) {
1171         speed += speed*(1 - getTerrainDifficulty((TERRAINTYPE) currentGameMap->getTile(location)->getType()));
1172         if(isBadlyDamaged()) {
1173             speed *= HEAVILYDAMAGEDSPEEDMULTIPLIER;
1174         }
1175     }
1176 
1177     switch(drawnAngle){
1178         case LEFT:      xSpeed = -speed;                    ySpeed = 0;         break;
1179         case LEFTUP:    xSpeed = -speed*DIAGONALSPEEDCONST; ySpeed = xSpeed;    break;
1180         case UP:        xSpeed = 0;                         ySpeed = -speed;    break;
1181         case RIGHTUP:   xSpeed = speed*DIAGONALSPEEDCONST;  ySpeed = -xSpeed;   break;
1182         case RIGHT:     xSpeed = speed;                     ySpeed = 0;         break;
1183         case RIGHTDOWN: xSpeed = speed*DIAGONALSPEEDCONST;  ySpeed = xSpeed;    break;
1184         case DOWN:      xSpeed = 0;                         ySpeed = speed;     break;
1185         case LEFTDOWN:  xSpeed = -speed*DIAGONALSPEEDCONST; ySpeed = -xSpeed;   break;
1186     }
1187 }
1188 
setTarget(const ObjectBase * newTarget)1189 void UnitBase::setTarget(const ObjectBase* newTarget) {
1190     attackPos.invalidate();
1191     bFollow = false;
1192     targetAngle = INVALID;
1193 
1194     if(goingToRepairYard && target && (target.getObjPointer()->getItemID() == Structure_RepairYard)) {
1195         static_cast<RepairYard*>(target.getObjPointer())->unBook();
1196         goingToRepairYard = false;
1197     }
1198 
1199     ObjectBase::setTarget(newTarget);
1200 
1201     if(target.getObjPointer() != nullptr
1202         && (target.getObjPointer()->getOwner() == getOwner())
1203         && (target.getObjPointer()->getItemID() == Structure_RepairYard)) {
1204         static_cast<RepairYard*>(target.getObjPointer())->book();
1205         goingToRepairYard = true;
1206     }
1207 }
1208 
targeting()1209 void UnitBase::targeting() {
1210     if(findTargetTimer == 0) {
1211 
1212         if(attackMode != STOP && attackMode != CARRYALLREQUESTED) {
1213 
1214             // lets add a bit of logic to make units recalibrate their nearest target if the target isn't in weapon range
1215             if(target && !attackPos && !forced &&(attackMode == GUARD || attackMode == AREAGUARD || attackMode == HUNT)){
1216                 if(!isInWeaponRange(target.getObjPointer())){
1217                     const ObjectBase* pNewTarget = findTarget();
1218 
1219                     if(pNewTarget != nullptr) {
1220 
1221                         doAttackObject(pNewTarget, false);
1222 
1223                         findTargetTimer = 500;
1224                     }
1225                 }
1226             }
1227 
1228 
1229             if(!target && !attackPos && !moving && !justStoppedMoving && !forced) {
1230                 // we have no target, we have stopped moving and we weren't forced to do anything else
1231 
1232                 const ObjectBase* pNewTarget = findTarget();
1233 
1234                 if(pNewTarget != nullptr && isInGuardRange(pNewTarget)) {
1235                     // we have found a new target => attack it
1236                     if(attackMode == AMBUSH) {
1237                         doSetAttackMode(HUNT);
1238                     }
1239                     doAttackObject(pNewTarget, false);
1240 
1241                     if(getItemID() == Unit_Sandworm) {
1242                         doSetAttackMode(HUNT);
1243                     }
1244                 } else if(attackMode == HUNT) {
1245                     setGuardPoint(location);
1246                     doSetAttackMode(GUARD);
1247                 }
1248 
1249                 // reset target timer
1250                 findTargetTimer = MILLI2CYCLES(2*1000);
1251             }
1252         }
1253 
1254     }
1255 
1256     engageTarget();
1257 }
1258 
turn()1259 void UnitBase::turn() {
1260     if(!moving && !justStoppedMoving) {
1261         int wantedAngle = INVALID;
1262 
1263         // if we have to decide between moving and shooting we opt for moving
1264         if(nextSpotAngle != INVALID) {
1265             wantedAngle = nextSpotAngle;
1266         } else if(targetAngle != INVALID) {
1267             wantedAngle = targetAngle;
1268         }
1269 
1270         if(wantedAngle != INVALID) {
1271             FixPoint angleLeft = 0;
1272             FixPoint angleRight = 0;
1273 
1274             if(angle > wantedAngle) {
1275                 angleRight = angle - wantedAngle;
1276                 angleLeft = FixPoint::abs(8-angle)+wantedAngle;
1277             } else if (angle < wantedAngle) {
1278                 angleRight = FixPoint::abs(8-wantedAngle) + angle;
1279                 angleLeft = wantedAngle - angle;
1280             }
1281 
1282             if(angleLeft <= angleRight) {
1283                 turnLeft();
1284             } else {
1285                 turnRight();
1286             }
1287         }
1288     }
1289 }
1290 
turnLeft()1291 void UnitBase::turnLeft() {
1292     angle += currentGame->objectData.data[itemID][originalHouseID].turnspeed;
1293     if(angle >= FixPt(7,5)) {
1294         drawnAngle = lround(angle) - NUM_ANGLES;
1295         angle -= NUM_ANGLES;
1296     } else {
1297         drawnAngle = lround(angle);
1298     }
1299 }
1300 
turnRight()1301 void UnitBase::turnRight() {
1302     angle -= currentGame->objectData.data[itemID][originalHouseID].turnspeed;
1303     if(angle <= FixPt(-0,5)) {
1304         drawnAngle = lround(angle) + NUM_ANGLES;
1305         angle += NUM_ANGLES;
1306     } else {
1307         drawnAngle = lround(angle);
1308     }
1309 }
1310 
quitDeviation()1311 void UnitBase::quitDeviation() {
1312     if(wasDeviated()) {
1313         // revert back to real owner
1314         removeFromSelectionLists();
1315         setTarget(nullptr);
1316         setGuardPoint(location);
1317         setDestination(location);
1318         owner = currentGame->getHouse(originalHouseID);
1319         graphic = pGFXManager->getObjPic(graphicID,getOwner()->getHouseID());
1320         deviationTimer = INVALID;
1321     }
1322 }
1323 
update()1324 bool UnitBase::update() {
1325     if(active) {
1326         targeting();
1327         navigate();
1328         move();
1329         if(active) {
1330             turn();
1331         }
1332     }
1333 
1334     if(getHealth() <= 0) {
1335         destroy();
1336         return false;
1337     }
1338 
1339 
1340 
1341 
1342     if(recalculatePathTimer > 0) recalculatePathTimer--;
1343     if(findTargetTimer > 0) findTargetTimer--;
1344     if(primaryWeaponTimer > 0) primaryWeaponTimer--;
1345     if(secondaryWeaponTimer > 0) secondaryWeaponTimer--;
1346     if(deviationTimer != INVALID) {
1347         if(--deviationTimer <= 0) {
1348             quitDeviation();
1349         }
1350     }
1351 
1352     return true;
1353 }
1354 
canPass(int xPos,int yPos) const1355 bool UnitBase::canPass(int xPos, int yPos) const {
1356     if(!currentGameMap->tileExists(xPos, yPos)) {
1357         return false;
1358     }
1359 
1360     Tile* pTile = currentGameMap->getTile(xPos, yPos);
1361 
1362     if(pTile->isMountain()) {
1363         return false;
1364     }
1365 
1366     if(pTile->hasAGroundObject()) {
1367         ObjectBase *pObject = pTile->getGroundObject();
1368 
1369         if( (pObject != nullptr)
1370             && (pObject->getObjectID() == target.getObjectID())
1371             && targetFriendly
1372             && pObject->isAStructure()
1373             && (pObject->getOwner()->getTeam() == owner->getTeam())
1374             && pObject->isVisible(getOwner()->getTeam()))
1375         {
1376             // are we entering a repair yard?
1377             return (goingToRepairYard && (pObject->getItemID() == Structure_RepairYard) && static_cast<const RepairYard*>(pObject)->isFree());
1378         } else {
1379             return false;
1380         }
1381     }
1382 
1383     return true;
1384 }
1385 
SearchPathWithAStar()1386 bool UnitBase::SearchPathWithAStar() {
1387     Coord destinationCoord;
1388 
1389     if(target && target.getObjPointer() != nullptr) {
1390         if(itemID == Unit_Carryall && target.getObjPointer()->getItemID() == Structure_Refinery) {
1391             destinationCoord = target.getObjPointer()->getLocation() + Coord(2,0);
1392         } else if(itemID == Unit_Frigate && target.getObjPointer()->getItemID() == Structure_StarPort) {
1393             destinationCoord = target.getObjPointer()->getLocation() + Coord(1,1);
1394         } else {
1395             destinationCoord = target.getObjPointer()->getClosestPoint(location);
1396         }
1397     } else {
1398         destinationCoord = destination;
1399     }
1400 
1401     AStarSearch pathfinder(currentGameMap, this, location, destinationCoord);
1402     pathList = pathfinder.getFoundPath();
1403 
1404     if(pathList.empty() == true) {
1405         nextSpotFound = false;
1406         return false;
1407     } else {
1408         return true;
1409     }
1410 }
1411 
drawSmoke(int x,int y)1412 void UnitBase::drawSmoke(int x, int y) {
1413     int frame = ((currentGame->getGameCycleCount() + (getObjectID() * 10)) / SMOKEDELAY) % (2*2);
1414     if(frame == 3) {
1415         frame = 1;
1416     }
1417 
1418     SDL_Texture** smoke = pGFXManager->getObjPic(ObjPic_Smoke,getOwner()->getHouseID());
1419 
1420     SDL_Rect dest = calcSpriteDrawingRect(smoke[currentZoomlevel], x, y, 3, 1, HAlign::Center, VAlign::Bottom);
1421     SDL_Rect source = calcSpriteSourceRect(smoke[currentZoomlevel], frame, 3);
1422 
1423     SDL_RenderCopy(renderer, smoke[currentZoomlevel], &source, &dest);
1424 }
1425 
playAttackSound()1426 void UnitBase::playAttackSound() {
1427 }
1428