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