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, ¢erPoint, &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, ¢erPoint, &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