1 // _________ __ __
2 // / _____// |_____________ _/ |______ ____ __ __ ______
3 // \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
4 // / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
5 // /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
6 // \/ \/ \//_____/ \/
7 // ______________________ ______________________
8 // T H E W A R B E G I N S
9 // Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name action_attack.cpp - The attack action. */
12 //
13 // (c) Copyright 1998-2015 by Lutz Sammer, Jimmy Salmon and Andrettin
14 //
15 // This program is free software; you can redistribute it and/or modify
16 // it under the terms of the GNU General Public License as published by
17 // the Free Software Foundation; only version 2 of the License.
18 //
19 // This program is distributed in the hope that it will be useful,
20 // but WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 // GNU General Public License for more details.
23 //
24 // You should have received a copy of the GNU General Public License
25 // along with this program; if not, write to the Free Software
26 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 // 02111-1307, USA.
28 //
29
30 //@{
31
32 /**
33 ** @todo FIXME: I should rewrite this action, if only the
34 ** new orders are supported.
35 */
36
37 /*----------------------------------------------------------------------------
38 -- Includes
39 ----------------------------------------------------------------------------*/
40
41 #include "stratagus.h"
42
43 #include "action/action_attack.h"
44
45 #include "animation.h"
46 #include "iolib.h"
47 #include "map.h"
48 #include "missile.h"
49 #include "pathfinder.h"
50 #include "player.h"
51 #include "script.h"
52 #include "settings.h"
53 #include "sound.h"
54 #include "spells.h"
55 #include "tileset.h"
56 #include "ui.h"
57 #include "unit.h"
58 #include "unit_find.h"
59 #include "unittype.h"
60 #include "video.h"
61
62 /*----------------------------------------------------------------------------
63 -- Defines
64 ----------------------------------------------------------------------------*/
65
66 #define FIRST_ENTRY 0
67 #define AUTO_TARGETING 1 /// Targets will be selected by small (unit's) AI
68 #define MOVE_TO_TARGET 2 /// Move to target state
69 #define ATTACK_TARGET 4 /// Attack target state
70 #define MOVE_TO_ATTACKPOS 8 /// Move to position for attack if target is too close
71
72 #define RESTORE_ONLY false /// Do not finish this order, only restore saved
73
74 /*----------------------------------------------------------------------------
75 -- Functions
76 ----------------------------------------------------------------------------*/
77
78 /**
79 ** Animate unit attack!
80 **
81 ** @param unit Unit, for that the attack animation is played.
82 **
83 ** @todo manage correctly unit with no animation attack.
84 */
AnimateActionAttack(CUnit & unit,COrder & order)85 void AnimateActionAttack(CUnit &unit, COrder &order)
86 {
87 // No animation.
88 // So direct fire missile.
89 // FIXME : wait a little.
90 if (unit.Type->Animations && unit.Type->Animations->RangedAttack && unit.IsAttackRanged(order.GetGoal(), order.GetGoalPos())) {
91 UnitShowAnimation(unit, unit.Type->Animations->RangedAttack);
92 } else {
93 if (!unit.Type->Animations || !unit.Type->Animations->Attack) {
94 order.OnAnimationAttack(unit);
95 return;
96 }
97 UnitShowAnimation(unit, unit.Type->Animations->Attack);
98 }
99 }
100
NewActionAttack(const CUnit & attacker,CUnit & target)101 /* static */ COrder *COrder::NewActionAttack(const CUnit &attacker, CUnit &target)
102 {
103 COrder_Attack *order = new COrder_Attack(false);
104
105 order->goalPos = target.tilePos + target.Type->GetHalfTileSize();
106 // Removed, Dying handled by action routine.
107 order->SetGoal(&target);
108 order->Range = attacker.Stats->Variables[ATTACKRANGE_INDEX].Max;
109 order->MinRange = attacker.Type->MinAttackRange;
110 if (attacker.Type->BoolFlag[SKIRMISHER_INDEX].value) {
111 order->SkirmishRange = order->Range;
112 }
113
114 return order;
115 }
116
NewActionAttack(const CUnit & attacker,const Vec2i & dest)117 /* static */ COrder *COrder::NewActionAttack(const CUnit &attacker, const Vec2i &dest)
118 {
119 Assert(Map.Info.IsPointOnMap(dest));
120
121 COrder_Attack *order = new COrder_Attack(false);
122
123 if (Map.WallOnMap(dest) && Map.Field(dest)->playerInfo.IsExplored(*attacker.Player)) {
124 // FIXME: look into action_attack.cpp about this ugly problem
125 order->goalPos = dest;
126 order->Range = attacker.Stats->Variables[ATTACKRANGE_INDEX].Max;
127 order->MinRange = attacker.Type->MinAttackRange;
128 } else {
129 order->goalPos = dest;
130 order->attackMovePos = dest;
131 order->State = AUTO_TARGETING;
132 }
133 if (attacker.Type->BoolFlag[SKIRMISHER_INDEX].value) {
134 order->SkirmishRange = attacker.Stats->Variables[ATTACKRANGE_INDEX].Max;
135 }
136
137 return order;
138 }
139
NewActionAttackGround(const CUnit & attacker,const Vec2i & dest)140 /* static */ COrder *COrder::NewActionAttackGround(const CUnit &attacker, const Vec2i &dest)
141 {
142 COrder_Attack *order = new COrder_Attack(true);
143
144 order->goalPos = dest;
145 order->Range = attacker.Stats->Variables[ATTACKRANGE_INDEX].Max;
146 order->MinRange = attacker.Type->MinAttackRange;
147 if (attacker.Type->BoolFlag[SKIRMISHER_INDEX].value) {
148 order->SkirmishRange = order->Range;
149 }
150
151 return order;
152 }
153
154
Save(CFile & file,const CUnit & unit) const155 /* virtual */ void COrder_Attack::Save(CFile &file, const CUnit &unit) const
156 {
157 Assert(Action == UnitActionAttack || Action == UnitActionAttackGround);
158
159 if (Action == UnitActionAttack) {
160 file.printf("{\"action-attack\",");
161 } else {
162 file.printf("{\"action-attack-ground\",");
163 }
164 file.printf(" \"range\", %d,", this->Range);
165 file.printf(" \"min-range\", %d,", this->MinRange);
166
167 if (this->Finished) {
168 file.printf(" \"finished\", ");
169 }
170 if (this->HasGoal()) {
171 file.printf(" \"goal\", \"%s\",", UnitReference(this->GetGoal()).c_str());
172 }
173 file.printf(" \"tile\", {%d, %d},", this->goalPos.x, this->goalPos.y);
174 file.printf(" \"amove-tile\", {%d, %d},", this->attackMovePos.x, this->attackMovePos.y);
175 file.printf(" \"state\", %d", this->State);
176 file.printf("}");
177 }
178
179
ParseSpecificData(lua_State * l,int & j,const char * value,const CUnit & unit)180 /* virtual */ bool COrder_Attack::ParseSpecificData(lua_State *l, int &j, const char *value, const CUnit &unit)
181 {
182 if (!strcmp(value, "state")) {
183 ++j;
184 this->State = LuaToNumber(l, -1, j + 1);
185 } else if (!strcmp(value, "min-range")) {
186 ++j;
187 this->MinRange = LuaToNumber(l, -1, j + 1);
188 } else if (!strcmp(value, "range")) {
189 ++j;
190 this->Range = LuaToNumber(l, -1, j + 1);
191 if (unit.Type->BoolFlag[SKIRMISHER_INDEX].value) {
192 this->SkirmishRange = this->Range;
193 }
194 } else if (!strcmp(value, "tile")) {
195 ++j;
196 lua_rawgeti(l, -1, j + 1);
197 CclGetPos(l, &this->goalPos.x, &this->goalPos.y);
198 lua_pop(l, 1);
199
200 } else if (!strcmp(value, "amove-tile")) {
201 ++j;
202 lua_rawgeti(l, -1, j + 1);
203 CclGetPos(l, &this->attackMovePos.x, &this->attackMovePos.y);
204 lua_pop(l, 1);
205
206 } else {
207 return false;
208 }
209 return true;
210 }
211
IsValid() const212 /* virtual */ bool COrder_Attack::IsValid() const
213 {
214 if (Action == UnitActionAttack) {
215 if (this->HasGoal()) {
216 return this->GetGoal()->IsAliveOnMap();
217 } else {
218 return Map.Info.IsPointOnMap(this->goalPos);
219 }
220 } else {
221 Assert(Action == UnitActionAttackGround);
222 return Map.Info.IsPointOnMap(this->goalPos);
223 }
224 }
225
Show(const CViewport & vp,const PixelPos & lastScreenPos) const226 /* virtual */ PixelPos COrder_Attack::Show(const CViewport &vp, const PixelPos &lastScreenPos) const
227 {
228 PixelPos targetPos;
229 PixelPos orderedPos;
230 bool isAttackMove = IsAutoTargeting() ? true : false;
231
232 targetPos = this->HasGoal() ? vp.MapToScreenPixelPos(this->GetGoal()->GetMapPixelPosCenter())
233 : IsMovingToAttackPos() ? vp.TilePosToScreen_Center(this->attackMovePos)
234 : vp.TilePosToScreen_Center(this->goalPos);
235
236 orderedPos = isAttackMove ? vp.TilePosToScreen_Center(this->attackMovePos)
237 : targetPos;
238
239 Uint32 color = isAttackMove ? ColorOrange : ColorRed;
240 Video.FillCircleClip(color, lastScreenPos, 2);
241 Video.DrawLineClip(ColorRed, lastScreenPos, orderedPos);
242 Video.FillCircleClip(color, orderedPos, 3);
243
244 if (isAttackMove && this->HasGoal()) {
245 Video.DrawLineClip(ColorOrange, lastScreenPos, targetPos);
246 Video.FillCircleClip(ColorOrange, targetPos, 3);
247 }
248 #ifdef DEBUG
249 if (IsMovingToAttackPos()) {
250 Video.DrawLineClip(ColorGreen, lastScreenPos, vp.TilePosToScreen_Center(this->goalPos));
251 Video.FillCircleClip(ColorRed, vp.TilePosToScreen_Center(this->goalPos), 3);
252 }
253 #endif
254
255 return isAttackMove ? orderedPos : targetPos;
256 }
257
UpdatePathFinderData(PathFinderInput & input)258 /* virtual */ void COrder_Attack::UpdatePathFinderData(PathFinderInput &input)
259 {
260 Vec2i tileSize;
261 if (this->HasGoal() && !IsMovingToAttackPos()) {
262 CUnit *goal = this->GetGoal();
263 tileSize.x = goal->Type->TileWidth;
264 tileSize.y = goal->Type->TileHeight;
265 input.SetGoal(goal->tilePos, tileSize);
266 } else {
267 tileSize.x = 0;
268 tileSize.y = 0;
269 input.SetGoal(this->goalPos, tileSize);
270 }
271
272 int distance = this->Range;
273 if (GameSettings.Inside) {
274 CheckObstaclesBetweenTiles(input.GetUnitPos(), this->HasGoal() ? this->GetGoal()->tilePos : this->goalPos, MapFieldRocks | MapFieldForest, &distance);
275 }
276 input.SetMaxRange(distance);
277 if (!this->SkirmishRange || Distance(input.GetUnitPos(), input.GetGoalPos()) < this->SkirmishRange) {
278 input.SetMinRange(this->MinRange);
279 } else {
280 input.SetMinRange(std::max<int>(this->SkirmishRange, this->MinRange));
281 }
282 }
283
OnAnimationAttack(CUnit & unit)284 /* virtual */ void COrder_Attack::OnAnimationAttack(CUnit &unit)
285 {
286 Assert(unit.Type->CanAttack);
287
288 FireMissile(unit, this->GetGoal(), this->goalPos);
289 UnHideUnit(unit); // unit is invisible until attacks
290 }
291
OnAiHitUnit(CUnit & unit,CUnit * attacker,int)292 /* virtual */ bool COrder_Attack::OnAiHitUnit(CUnit &unit, CUnit *attacker, int /*damage*/)
293 {
294 CUnit *goal = this->GetGoal();
295
296 if (goal) {
297 if (goal->IsAlive() == false) {
298 this->ClearGoal();
299 this->goalPos = goal->tilePos;
300 return false;
301 }
302 if (goal == attacker) {
303 return true;
304 }
305 if (goal->CurrentAction() == UnitActionAttack) {
306 const COrder_Attack &order = *static_cast<COrder_Attack *>(goal->CurrentOrder());
307 if (order.GetGoal() == &unit) {
308 //we already fight with one of attackers;
309 return true;
310 }
311 }
312 }
313 return false;
314 }
315
IsWeakTargetSelected() const316 bool COrder_Attack::IsWeakTargetSelected() const
317 {
318 return (this->State & AUTO_TARGETING) != 0;
319 }
IsAutoTargeting() const320 bool COrder_Attack::IsAutoTargeting() const
321 {
322 return (this->State & AUTO_TARGETING) != 0;
323 }
IsMovingToAttackPos() const324 bool COrder_Attack::IsMovingToAttackPos() const
325 {
326 return (this->State & MOVE_TO_ATTACKPOS) != 0;
327 }
IsAttackGroundOrWall() const328 bool COrder_Attack::IsAttackGroundOrWall() const
329 {
330 /// FIXME: Check if need to add this: (goal && goal->Type && goal->Type->BoolFlag[WALL_INDEX].value)
331 return (this->Action == UnitActionAttackGround || Map.WallOnMap(this->goalPos) ? true : false);
332 }
333
BestTarget(const CUnit & unit,CUnit * const target1,CUnit * const target2) const334 CUnit *const COrder_Attack::BestTarget(const CUnit &unit, CUnit *const target1, CUnit *const target2) const
335 {
336 Assert(target1 != NULL);
337 Assert(target2 != NULL);
338
339 return (Preference.SimplifiedAutoTargeting
340 ? ((TargetPriorityCalculate(&unit, target1) > TargetPriorityCalculate(&unit, target2)) ? target1 : target2)
341 : ((ThreatCalculate(unit, *target1) < ThreatCalculate(unit, *target2)) ? target1 : target2));
342 }
343
344 /**
345 ** Try to change goal from outside, when in the auto attack mode
346 **
347 ** @param unit
348 ** @param target Target that offered as the new current goal
349 **
350 */
OfferNewTarget(const CUnit & unit,CUnit * const target)351 void COrder_Attack::OfferNewTarget(const CUnit &unit, CUnit *const target)
352 {
353 Assert(target != NULL);
354 Assert(this->IsAutoTargeting() || unit.Player->AiEnabled);
355
356 /// if attacker cant't move (stand_ground, building, in a bunker or transport)
357 const bool immobile = (this->Action == UnitActionStandGround || unit.Removed || !unit.CanMove()) ? true : false;
358 if (immobile && !InAttackRange(unit, *target)) {
359 return;
360 }
361 CUnit *best = (this->offeredTarget != NULL && this->offeredTarget->IsVisibleAsGoal(*unit.Player))
362 ? BestTarget(unit, this->offeredTarget, target)
363 : target;
364 if (this->offeredTarget != NULL) {
365 this->offeredTarget.Reset();
366 }
367 this->offeredTarget = best;
368 }
369
370 /**
371 ** Check for dead/valid goal.
372 **
373 **
374 ** @todo If a unit enters an building, than the attack choose an
375 ** other goal, perhaps it is better to wait for the goal?
376 **
377 ** @param unit Unit using the goal.
378 **
379 ** @return true if target is valid, false else.
380 */
CheckIfGoalValid(CUnit & unit)381 bool COrder_Attack::CheckIfGoalValid(CUnit &unit)
382 {
383 CUnit *goal = this->GetGoal();
384
385 // Wall was destroyed
386 if (!goal && this->State & ATTACK_TARGET && this->Action != UnitActionAttackGround && !Map.WallOnMap(this->goalPos)) {
387 return false;
388 }
389 // Position or valid target, it is ok.
390 if (!goal || goal->IsVisibleAsGoal(*unit.Player)) {
391 return true;
392 }
393
394 // Goal could be destroyed or unseen
395 // So, cannot use type.
396 this->goalPos = goal->tilePos;
397 this->MinRange = 0;
398 this->Range = 0;
399 this->ClearGoal();
400 return false;
401 }
402
403 /**
404 ** Turn unit to Target or position on map for attack.
405 **
406 ** @param unit Unit to turn.
407 ** @param target Turn to this Target. If NULL then turn to goalPos.
408 **
409 */
TurnToTarget(CUnit & unit,const CUnit * target)410 void COrder_Attack::TurnToTarget(CUnit &unit, const CUnit *target)
411 {
412 const Vec2i dir = target ? (target->tilePos + target->Type->GetHalfTileSize() - unit.tilePos)
413 : (this->goalPos - unit.tilePos);
414 const unsigned char oldDir = unit.Direction;
415
416 UnitHeadingFromDeltaXY(unit, dir);
417 if (unit.Type->BoolFlag[SIDEATTACK_INDEX].value) {
418 unsigned char leftTurn = (unit.Direction - 2 * NextDirection) % (NextDirection * 8);
419 unsigned char rightTurn = (unit.Direction + 2 * NextDirection) % (NextDirection * 8);
420 if (abs(leftTurn - oldDir) < abs(rightTurn - oldDir)) {
421 unit.Direction = leftTurn;
422 } else {
423 unit.Direction = rightTurn;
424 }
425 UnitUpdateHeading(unit);
426 }
427 }
428
429 /**
430 ** Set target for attack in auto-attack mode.
431 ** Also if there is no active target Attack-Move action will be saved.
432 **
433 ** @param unit Attacker.
434 ** @param target Turn to this Target. If NULL then turn to goalPos.
435 **
436 */
SetAutoTarget(CUnit & unit,CUnit * target)437 void COrder_Attack::SetAutoTarget(CUnit &unit, CUnit *target)
438 {
439 if (this->HasGoal()) {
440 this->ClearGoal();
441 }
442 this->SetGoal(target);
443 this->goalPos = target->tilePos;
444 this->Range = unit.Stats->Variables[ATTACKRANGE_INDEX].Max;
445 this->MinRange = unit.Type->MinAttackRange;
446 if (unit.Type->BoolFlag[SKIRMISHER_INDEX].value) {
447 this->SkirmishRange = this->Range;
448 }
449 // Set threshold value only for aggressive units (Prevent to change target)
450 if (!Preference.SimplifiedAutoTargeting && target->IsAgressive())
451 {
452 unit.Threshold = 30;
453 }
454 }
455
456 /**
457 ** Select target in auto attack mode
458 **
459 ** return true if we have a target, false if can't find any
460 **/
AutoSelectTarget(CUnit & unit)461 bool COrder_Attack::AutoSelectTarget(CUnit &unit)
462 {
463 // if unit can't attack, or if unit is not bunkered and removed - exit, no targets
464 if (unit.Type->CanAttack == false
465 || (unit.Removed
466 && (unit.Container == NULL || unit.Container->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value == false))) {
467 this->offeredTarget.Reset();
468 return false;
469 }
470 CUnit *goal = this->GetGoal();
471 CUnit *newTarget = NULL;
472 if (unit.Selected)
473 {
474 DebugPrint("UnderAttack counter: %d \n" _C_ unit.UnderAttack);
475 }
476
477 /// if attacker cant't move (stand_ground, building, in a bunker or transport)
478 const bool immobile = (this->Action == UnitActionStandGround || unit.Removed || !unit.CanMove()) ? true : false;
479 if (immobile) {
480 newTarget = AttackUnitsInRange(unit); // search for enemies only in attack range
481 } else {
482 newTarget = AttackUnitsInReactRange(unit); // search for enemies in reaction range
483 }
484 /// If we have target offered from outside - try it
485 if (this->offeredTarget != NULL) {
486 if (this->offeredTarget->IsVisibleAsGoal(*unit.Player)
487 && (!immobile || InAttackRange(unit, *this->offeredTarget))) {
488
489 newTarget = newTarget ? BestTarget(unit, this->offeredTarget, newTarget) : &(*this->offeredTarget);
490 }
491 this->offeredTarget.Reset();
492 }
493 const bool attackedByGoal = (goal && goal->CurrentOrder()->GetGoal() == &unit)
494 && InAttackRange(*goal, unit) ? true : false;
495 if (goal /// if goal is Valid
496 && goal->IsVisibleAsGoal(*unit.Player)
497 && CanTarget(*unit.Type, *goal->Type)
498 && (immobile ? InAttackRange(unit, *goal) : (attackedByGoal ? true : InReactRange(unit, *goal)))
499 && !(unit.UnderAttack && !goal->IsAgressive())) {
500
501 if (newTarget && newTarget != goal) {
502 /// Do not switch to non aggresive targets while UnderAttack counter is active
503 if (unit.UnderAttack && !newTarget->IsAgressive()) {
504 return true;
505 }
506 if (Preference.SimplifiedAutoTargeting) {
507 const int goal_priority = TargetPriorityCalculate(&unit, goal);
508 const int newTarget_priority = TargetPriorityCalculate(&unit, newTarget);
509
510 if ((newTarget_priority & AT_PRIORITY_MASK_HI) > (goal_priority & AT_PRIORITY_MASK_HI)) {
511 if (goal_priority & AT_ATTACKED_BY_FACTOR) { /// if unit under attack by current goal
512 if (InAttackRange(unit, *newTarget)) {
513 SetAutoTarget(unit, newTarget);
514 }
515 } else {
516 SetAutoTarget(unit, newTarget);
517 }
518 } else if (!immobile
519 && (!InAttackRange(unit, *goal) && newTarget_priority > goal_priority)) {
520 SetAutoTarget(unit, newTarget);
521 }
522 } else {
523 if (unit.Threshold == 0 && ThreatCalculate(unit, *newTarget) < ThreatCalculate(unit, *goal)) {
524 SetAutoTarget(unit, newTarget);
525 }
526 }
527 }
528 } else {
529 if (goal) {
530 this->ClearGoal();
531 }
532 if (newTarget && !(unit.UnderAttack && !newTarget->IsAgressive())) {
533 SetAutoTarget(unit, newTarget);
534 } else {
535 return false;
536 }
537 }
538 return true;
539 }
540
541 /**
542 ** Restore action/order when current action is finished
543 **
544 ** @param unit
545 ** @param canBeFinished False if ony restore order/action needed
546 **
547 ** @return false if order/action restored, true else (if order finished).
548 */
EndActionAttack(CUnit & unit,const bool canBeFinished=true)549 bool COrder_Attack::EndActionAttack(CUnit &unit, const bool canBeFinished = true)
550 {
551 /// Restore saved order only when UnderAttack counter is expired
552 if ((unit.UnderAttack && IsAutoTargeting()) || !unit.RestoreOrder()) {
553 if (IsAutoTargeting() && this->goalPos != this->attackMovePos) {
554 this->goalPos = this->attackMovePos;
555 this->Range = 0;
556 this->MinRange = 0;
557 this->State = AUTO_TARGETING;
558 return false;
559 }
560 this->Finished = canBeFinished ? true : false;
561 return true;
562 }
563 return false;
564 }
565
566 /**
567 ** Move unit to randomly selected position in MinAttackRange away from current goal.
568 **
569 ** @param unit Unit ot move
570 */
MoveToBetterPos(CUnit & unit)571 void COrder_Attack::MoveToBetterPos(CUnit &unit)
572 {
573 CUnit *goal = this->GetGoal();
574
575 /// Save current goalPos if target is ground or wall
576 if (!goal && IsAttackGroundOrWall()) {
577 if (IsMovingToAttackPos()) {
578 this->goalPos = this->attackMovePos;
579 } else {
580 this->attackMovePos = this->goalPos;
581 }
582 }
583 this->goalPos = goal ? GetRndPosInDirection(unit.tilePos, *goal, true, unit.Type->MinAttackRange, 3)
584 : GetRndPosInDirection(unit.tilePos, this->goalPos, true, unit.Type->MinAttackRange, 3);
585 this->Range = 0;
586 this->MinRange = 0;
587 unit.Frame = 0;
588 this->State &= AUTO_TARGETING;
589 this->State |= MOVE_TO_ATTACKPOS;
590 }
591
592 /**
593 ** Change invalid target for new target in range.
594 **
595 ** @param unit Unit to check if goal is in range
596 **
597 ** @return true if order(action) have changed, false else (if goal change return false).
598 */
CheckForTargetInRange(CUnit & unit)599 bool COrder_Attack::CheckForTargetInRange(CUnit &unit)
600 {
601
602 if (!this->HasGoal() && IsAttackGroundOrWall()) {
603 return false;
604 }
605
606 if (!CheckIfGoalValid(unit)) {
607 EndActionAttack(unit);
608 return true;
609 }
610
611 if (IsAutoTargeting() || unit.Player->AiEnabled) {
612 const bool hadGoal = this->HasGoal();
613 if (!AutoSelectTarget(unit) && hadGoal) {
614 EndActionAttack(unit, RESTORE_ONLY);
615 return true;
616 }
617 }
618
619 Assert(!unit.Type->BoolFlag[VANISHES_INDEX].value && !unit.Destroyed && !unit.Removed);
620 return false;
621 }
622
623 /**
624 ** Check if current target is closer to unit more than MinAttackRange
625 **
626 ** @param unit Unit that is attacking and moving
627 */
IsTargetTooClose(const CUnit & unit) const628 bool COrder_Attack::IsTargetTooClose(const CUnit &unit) const
629 {
630 /// Calculate distance to goal or map tile if attack ground/wall
631 if (!this->HasGoal() && !IsAttackGroundOrWall()) {
632 return false;
633 }
634 const int distance = this->HasGoal() ? unit.MapDistanceTo(*this->GetGoal())
635 : IsMovingToAttackPos() ? unit.MapDistanceTo(this->attackMovePos)
636 : unit.MapDistanceTo(this->goalPos);
637 const bool tooClose = (distance < unit.Type->MinAttackRange) ? true : false;
638 return tooClose;
639 }
640
641 /**
642 ** Controls moving a unit to position if its target is closer than MinAttackRange when attacking
643 **
644 ** @param unit Unit that is attacking and moving
645 ** @param pfReturn Current path finder status. Using to find new attack pos if current is unreachable.
646 */
MoveToAttackPos(CUnit & unit,const int pfReturn)647 void COrder_Attack::MoveToAttackPos(CUnit &unit, const int pfReturn)
648 {
649 Assert(IsMovingToAttackPos());
650
651 if (CheckForTargetInRange(unit)) {
652 return;
653 }
654 CUnit *goal = this->GetGoal();
655 /// When attack ground and moving to attack position, the target tile pos is stored in attackMovePos
656 const bool inAttackRange = goal ? InAttackRange(unit, *goal)
657 : InAttackRange(unit, this->attackMovePos);
658 if (!IsTargetTooClose(unit)) {
659 /// We have to restore original goalPos value
660 if (goal) {
661 this->goalPos = goal->tilePos;
662 } else {
663 this->goalPos = this->attackMovePos;
664 this->attackMovePos = Vec2i(-1,-1);
665 }
666 TurnToTarget(unit, goal);
667 this->State &= AUTO_TARGETING;
668 this->State |= inAttackRange ? ATTACK_TARGET : MOVE_TO_TARGET;
669 if (this->State & MOVE_TO_TARGET) {
670 unit.Frame = 0;
671 }
672 } else if (pfReturn < 0) {
673 MoveToBetterPos(unit);
674 }
675 }
676
677 /**
678 ** Controls moving a unit to its target when attacking
679 **
680 ** @param unit Unit that is attacking and moving
681 */
MoveToTarget(CUnit & unit)682 void COrder_Attack::MoveToTarget(CUnit &unit)
683 {
684 Assert(!unit.Type->BoolFlag[VANISHES_INDEX].value && !unit.Destroyed && !unit.Removed);
685 Assert(unit.CurrentOrder() == this);
686 Assert(unit.CanMove());
687 Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos));
688
689 bool needToSearchBetterPos = (IsTargetTooClose(unit) && !IsMovingToAttackPos()) ? true : false;
690 if (needToSearchBetterPos && !unit.Anim.Unbreakable) {
691 MoveToBetterPos(unit);
692 needToSearchBetterPos = false;
693 }
694
695 int err = DoActionMove(unit);
696
697 if (needToSearchBetterPos) {
698 MoveToBetterPos(unit);
699 return;
700 }
701 /// Order may be set as finished by outside code while playing animation.
702 /// In this case we must not execute code of MoveToTarget
703 if (unit.Anim.Unbreakable || this->Finished) {
704 return;
705 }
706 if (IsMovingToAttackPos()) {
707 MoveToAttackPos(unit, err);
708 return;
709 }
710
711 // Look if we have reached the target.
712 if (err == 0 && !this->HasGoal()) {
713 // Check if we're in range when attacking a location and we are waiting
714 if (InAttackRange(unit, this->goalPos)) {
715 err = PF_REACHED;
716 }
717 }
718 CUnit *goal = this->GetGoal();
719 // Waiting or on the way
720 if (err >= 0) {
721 if (!CheckForTargetInRange(unit) && (IsAutoTargeting() || unit.Player->AiEnabled)) {
722 CUnit *currGoal = this->GetGoal();
723 if (currGoal && goal != currGoal) {
724 if (InAttackRange(unit, *currGoal)) {
725 TurnToTarget(unit, currGoal);
726 this->State &= AUTO_TARGETING;
727 this->State |= ATTACK_TARGET ;
728 } else {
729 unit.Frame = 0;
730 this->State &= AUTO_TARGETING;
731 this->State |= MOVE_TO_TARGET;
732 }
733 }
734 }
735 return;
736 }
737 if (err == PF_REACHED) {
738 // Have reached target? FIXME: could use the new return code?
739 if (goal && InAttackRange(unit, *goal)) {
740 // Reached another unit, now attacking it
741 TurnToTarget(unit, goal);
742 this->State &= AUTO_TARGETING;
743 this->State |= ATTACK_TARGET;
744 return;
745 }
746 // Attacking wall or ground.
747 if (((goal && goal->Type && goal->Type->BoolFlag[WALL_INDEX].value)
748 || (!goal && IsAttackGroundOrWall()))
749 && InAttackRange(unit, this->goalPos)) {
750
751 // Reached wall or ground, now attacking it
752 TurnToTarget(unit, NULL);
753 this->State &= AUTO_TARGETING;
754 this->State |= ATTACK_TARGET;
755 return;
756 }
757 }
758 // Unreachable.
759 // FIXME: look at this
760 if (err == PF_UNREACHABLE) {
761 if (!this->HasGoal()) {
762 // When attack-moving we have to allow a bigger range (PF)
763 this->Range++;
764 unit.Wait = 5;
765 return;
766 }
767 this->ClearGoal();
768 }
769 EndActionAttack(unit);
770 return;
771 }
772
773 /**
774 ** Handle attacking the target.
775 **
776 ** @param unit Unit, for that the attack is handled.
777 */
AttackTarget(CUnit & unit)778 void COrder_Attack::AttackTarget(CUnit &unit)
779 {
780 Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos));
781
782 AnimateActionAttack(unit, *this);
783 /// Order may be set as finished by outside code while playing attack animation.
784 /// In this case we must not execute code of AttackTarget
785 if (unit.Anim.Unbreakable || this->Finished) {
786 return;
787 }
788 if (!this->HasGoal() && IsAttackGroundOrWall()) {
789 return;
790 }
791
792 if (!CheckIfGoalValid(unit)) {
793 EndActionAttack(unit);
794 return;
795 }
796
797 if (IsAutoTargeting() || unit.Player->AiEnabled) {
798 if (!AutoSelectTarget(unit)) {
799 EndActionAttack(unit, RESTORE_ONLY);
800 return;
801 }
802 }
803 CUnit *goal = this->GetGoal();
804 if (goal && !InAttackRange(unit, *goal)) {
805 unit.Frame = 0;
806 this->State &= AUTO_TARGETING;
807 this->State |= MOVE_TO_TARGET;
808 }
809 TurnToTarget(unit, goal);
810 }
811
812 /**
813 ** Unit attacks!
814 **
815 ** if (SubAction & AUTO_TARGETING) is true the goal is a weak goal.
816 ** This means the unit AI (little AI) could choose a new better goal.
817 **
818 ** @todo Lets do some tries to reach the target.
819 ** If target place is not reachable, choose better goal to reduce
820 ** the pathfinder load.
821 **
822 ** @param unit Unit, for that the attack is handled.
823 */
Execute(CUnit & unit)824 /* virtual */ void COrder_Attack::Execute(CUnit &unit)
825 {
826 Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos));
827
828 if (unit.Wait) {
829 if (!unit.Waiting) {
830 unit.Waiting = 1;
831 unit.WaitBackup = unit.Anim;
832 }
833 UnitShowAnimation(unit, unit.Type->Animations->Still);
834 unit.Wait--;
835 return;
836 }
837 if (unit.Waiting) {
838 unit.Anim = unit.WaitBackup;
839 unit.Waiting = 0;
840 }
841
842 if (this->State != ATTACK_TARGET && unit.CanStoreOrder(this) && AutoCast(unit)) {
843 this->Finished = true;
844 return;
845 }
846
847 switch (this->State) {
848 case FIRST_ENTRY:
849 case AUTO_TARGETING:
850
851 if (CheckForTargetInRange(unit)) {
852 return;
853 }
854 // Can we already attack ?
855 if ((this->HasGoal() && InAttackRange(unit, *this->GetGoal()))
856 || (IsAttackGroundOrWall() && InAttackRange(unit, this->goalPos))) {
857
858 TurnToTarget(unit, this->GetGoal());
859 this->State |= ATTACK_TARGET;
860 AttackTarget(unit);
861 return;
862 }
863 this->State |= MOVE_TO_TARGET;
864 // FIXME: should use a reachable place to reduce pathfinder time.
865
866 // FALL THROUGH
867 case MOVE_TO_TARGET:
868 case MOVE_TO_TARGET + AUTO_TARGETING:
869 case MOVE_TO_ATTACKPOS:
870 case MOVE_TO_ATTACKPOS + AUTO_TARGETING:
871 if (!unit.CanMove()) {
872 this->Finished = true;
873 return;
874 }
875 MoveToTarget(unit);
876 break;
877
878 case ATTACK_TARGET:
879 case ATTACK_TARGET + AUTO_TARGETING:
880 AttackTarget(unit);
881 break;
882 }
883 }
884
885 //@}
886