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 source file. */
12 //
13 //      (c) Copyright 1998-2019 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 //Wyrmgus start
47 #include "commands.h"
48 //Wyrmgus end
49 #include "iolib.h"
50 #include "map/map.h"
51 #include "map/map_layer.h"
52 #include "map/tileset.h"
53 #include "missile.h"
54 #include "pathfinder.h"
55 #include "player.h"
56 #include "script.h"
57 #include "settings.h"
58 #include "sound.h"
59 #include "spells.h"
60 #include "ui/ui.h"
61 #include "unit/unit.h"
62 #include "unit/unit_find.h"
63 #include "unit/unittype.h"
64 #include "video.h"
65 
66 /*----------------------------------------------------------------------------
67 --  Defines
68 ----------------------------------------------------------------------------*/
69 
70 #define WEAK_TARGET      2  /// Weak target, could be changed
71 #define MOVE_TO_TARGET   4  /// Move to target state
72 #define ATTACK_TARGET    5  /// Attack target state
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 	//Wyrmgus start
91 	/*
92 	if (unit.Type->Animations && unit.Type->Animations->RangedAttack && unit.IsAttackRanged(order.GetGoal(), order.GetGoalPos())) {
93 		UnitShowAnimation(unit, unit.Type->Animations->RangedAttack);
94 	} else {
95 		if (!unit.Type->Animations || !unit.Type->Animations->Attack) {
96 			order.OnAnimationAttack(unit);
97 			return;
98 		}
99 		UnitShowAnimation(unit, unit.Type->Animations->Attack);
100 	}
101 	*/
102 	if (unit.GetAnimations() && unit.GetAnimations()->RangedAttack && unit.IsAttackRanged(order.GetGoal(), order.GetGoalPos(), order.GetGoalMapLayer())) {
103 		UnitShowAnimation(unit, unit.GetAnimations()->RangedAttack);
104 	} else {
105 		if (!unit.GetAnimations() || !unit.GetAnimations()->Attack) {
106 			order.OnAnimationAttack(unit);
107 			return;
108 		}
109 		UnitShowAnimation(unit, unit.GetAnimations()->Attack);
110 	}
111 	//Wyrmgus end
112 }
113 
NewActionAttack(const CUnit & attacker,CUnit & target)114 /* static */ COrder *COrder::NewActionAttack(const CUnit &attacker, CUnit &target)
115 {
116 	COrder_Attack *order = new COrder_Attack(false);
117 
118 	order->goalPos = target.tilePos + target.GetHalfTileSize();
119 	//Wyrmgus start
120 	order->MapLayer = target.MapLayer->ID;
121 	//Wyrmgus end
122 	// Removed, Dying handled by action routine.
123 	order->SetGoal(&target);
124 	order->Range = attacker.GetModifiedVariable(ATTACKRANGE_INDEX);
125 	order->MinRange = attacker.Type->MinAttackRange;
126 	if (attacker.Player->AiEnabled && attacker.Variable[SPEED_INDEX].Value > target.Variable[SPEED_INDEX].Value && attacker.GetModifiedVariable(ATTACKRANGE_INDEX) > target.GetModifiedVariable(ATTACKRANGE_INDEX)) { //makes fast AI ranged units move away from slower targets that have smaller range
127 		order->MinRange = attacker.GetModifiedVariable(ATTACKRANGE_INDEX);
128 	}
129 
130 	//Wyrmgus start
131 	if (!attacker.Type->BoolFlag[HIDDENOWNERSHIP_INDEX].value && !target.Type->BoolFlag[HIDDENOWNERSHIP_INDEX].value && !target.IsEnemy(attacker) && (target.Player->Type == PlayerComputer) && (attacker.Player->Type == PlayerComputer || attacker.Player->Type == PlayerPerson)) {
132 		target.Player->SetDiplomacyEnemyWith(*attacker.Player);
133 		attacker.Player->SetDiplomacyEnemyWith(*target.Player);
134 		if (target.Player->IsSharedVision(*attacker.Player)) {
135 			CommandSharedVision(target.Player->Index, false, attacker.Player->Index);
136 		}
137 	}
138 	//Wyrmgus end
139 
140 	return order;
141 }
142 
143 //Wyrmgus start
144 ///* static */ COrder *COrder::NewActionAttack(const CUnit &attacker, const Vec2i &dest)
NewActionAttack(const CUnit & attacker,const Vec2i & dest,int z)145 /* static */ COrder *COrder::NewActionAttack(const CUnit &attacker, const Vec2i &dest, int z)
146 //Wyrmgus end
147 {
148 	Assert(Map.Info.IsPointOnMap(dest, z));
149 
150 	COrder_Attack *order = new COrder_Attack(false);
151 
152 	//Wyrmgus start
153 //	if (Map.WallOnMap(dest) && Map.Field(dest)->playerInfo.IsExplored(*attacker.Player)) {
154 	if (Map.WallOnMap(dest, z) && Map.Field(dest, z)->playerInfo.IsTeamExplored(*attacker.Player)) {
155 	//Wyrmgus end
156 		// FIXME: look into action_attack.cpp about this ugly problem
157 		order->goalPos = dest;
158 		order->MapLayer = z;
159 		order->Range = attacker.GetModifiedVariable(ATTACKRANGE_INDEX);
160 		order->MinRange = attacker.Type->MinAttackRange;
161 	} else {
162 		order->goalPos = dest;
163 		//Wyrmgus start
164 		order->MapLayer = z;
165 		//Wyrmgus end
166 	}
167 	return order;
168 }
169 
170 //Wyrmgus start
171 ///* static */ COrder *COrder::NewActionAttackGround(const CUnit &attacker, const Vec2i &dest)
NewActionAttackGround(const CUnit & attacker,const Vec2i & dest,int z)172 /* static */ COrder *COrder::NewActionAttackGround(const CUnit &attacker, const Vec2i &dest, int z)
173 //Wyrmgus end
174 {
175 	COrder_Attack *order = new COrder_Attack(true);
176 
177 	order->goalPos = dest;
178 	//Wyrmgus start
179 	order->MapLayer = z;
180 	order->Range = attacker.GetModifiedVariable(ATTACKRANGE_INDEX);
181 	order->MinRange = attacker.Type->MinAttackRange;
182 
183 	return order;
184 }
185 
186 
Save(CFile & file,const CUnit & unit) const187 /* virtual */ void COrder_Attack::Save(CFile &file, const CUnit &unit) const
188 {
189 	Assert(Action == UnitActionAttack || Action == UnitActionAttackGround);
190 
191 	if (Action == UnitActionAttack) {
192 		file.printf("{\"action-attack\",");
193 	} else {
194 		file.printf("{\"action-attack-ground\",");
195 	}
196 	file.printf(" \"range\", %d,", this->Range);
197 	file.printf(" \"min-range\", %d,", this->MinRange);
198 
199 	if (this->Finished) {
200 		file.printf(" \"finished\", ");
201 	}
202 	if (this->HasGoal()) {
203 		file.printf(" \"goal\", \"%s\",", UnitReference(this->GetGoal()).c_str());
204 	}
205 	file.printf(" \"tile\", {%d, %d},", this->goalPos.x, this->goalPos.y);
206 	//Wyrmgus start
207 	file.printf(" \"map-layer\", %d,", this->MapLayer);
208 	//Wyrmgus end
209 
210 	file.printf(" \"state\", %d", this->State);
211 	file.printf("}");
212 }
213 
214 
ParseSpecificData(lua_State * l,int & j,const char * value,const CUnit & unit)215 /* virtual */ bool COrder_Attack::ParseSpecificData(lua_State *l, int &j, const char *value, const CUnit &unit)
216 {
217 	if (!strcmp(value, "state")) {
218 		++j;
219 		this->State = LuaToNumber(l, -1, j + 1);
220 	} else if (!strcmp(value, "min-range")) {
221 		++j;
222 		this->MinRange = LuaToNumber(l, -1, j + 1);
223 	} else if (!strcmp(value, "range")) {
224 		++j;
225 		this->Range = LuaToNumber(l, -1, j + 1);
226 	} else if (!strcmp(value, "tile")) {
227 		++j;
228 		lua_rawgeti(l, -1, j + 1);
229 		CclGetPos(l, &this->goalPos.x , &this->goalPos.y);
230 		lua_pop(l, 1);
231 	//Wyrmgus start
232 	} else if (!strcmp(value, "map-layer")) {
233 		++j;
234 		this->MapLayer = LuaToNumber(l, -1, j + 1);
235 	//Wyrmgus end
236 	} else {
237 		return false;
238 	}
239 	return true;
240 }
241 
IsValid() const242 /* virtual */ bool COrder_Attack::IsValid() const
243 {
244 	if (Action == UnitActionAttack) {
245 		if (this->HasGoal()) {
246 			return this->GetGoal()->IsAliveOnMap();
247 		} else {
248 			return Map.Info.IsPointOnMap(this->goalPos, this->MapLayer);
249 		}
250 	} else {
251 		Assert(Action == UnitActionAttackGround);
252 		return Map.Info.IsPointOnMap(this->goalPos, this->MapLayer);
253 	}
254 }
255 
Show(const CViewport & vp,const PixelPos & lastScreenPos) const256 /* virtual */ PixelPos COrder_Attack::Show(const CViewport &vp, const PixelPos &lastScreenPos) const
257 {
258 	PixelPos targetPos;
259 
260 	if (this->HasGoal()) {
261 		if (this->GetGoal()->MapLayer != UI.CurrentMapLayer) {
262 			return lastScreenPos;
263 		}
264 		targetPos = vp.MapToScreenPixelPos(this->GetGoal()->GetMapPixelPosCenter());
265 	} else {
266 		if (this->MapLayer != UI.CurrentMapLayer->ID) {
267 			return lastScreenPos;
268 		}
269 		targetPos = vp.TilePosToScreen_Center(this->goalPos);
270 	}
271 	if (Preference.ShowPathlines) {
272 		Video.FillCircleClip(ColorRed, lastScreenPos, 2);
273 		Video.DrawLineClip(ColorRed, lastScreenPos, targetPos);
274 		Video.FillCircleClip(IsWeakTargetSelected() ? ColorBlue : ColorRed, targetPos, 3);
275 	}
276 	return targetPos;
277 }
278 
UpdatePathFinderData(PathFinderInput & input)279 /* virtual */ void COrder_Attack::UpdatePathFinderData(PathFinderInput &input)
280 {
281 	Vec2i tileSize;
282 	if (this->HasGoal()) {
283 		CUnit *goal = this->GetGoal();
284 		tileSize = goal->GetTileSize();
285 		//Wyrmgus start
286 //		input.SetGoal(goal->tilePos, tileSize);
287 		input.SetGoal(goal->tilePos, tileSize, goal->MapLayer->ID);
288 		//Wyrmgus end
289 	} else {
290 		tileSize.x = 0;
291 		tileSize.y = 0;
292 		//Wyrmgus start
293 //		input.SetGoal(this->goalPos, tileSize);
294 		input.SetGoal(this->goalPos, tileSize, this->MapLayer);
295 		//Wyrmgus end
296 	}
297 
298 	input.SetMinRange(this->MinRange);
299 	int distance = this->Range;
300 	//Wyrmgus start
301 //	if (GameSettings.Inside) {
302 //		CheckObstaclesBetweenTiles(input.GetUnitPos(), this->HasGoal() ? this->GetGoal()->tilePos : this->goalPos, MapFieldRocks | MapFieldForest, &distance);
303 //	}
304 	if (Map.IsLayerUnderground(this->MapLayer) && input.GetUnit()->GetModifiedVariable(ATTACKRANGE_INDEX) > 1) {
305 		if (!CheckObstaclesBetweenTiles(input.GetUnitPos(), this->HasGoal() ? this->GetGoal()->tilePos : this->goalPos, MapFieldAirUnpassable, this->MapLayer)) {
306 			distance = 1;
307 		}
308 	}
309 	//Wyrmgus end
310 	input.SetMaxRange(distance);
311 }
312 
OnAnimationAttack(CUnit & unit)313 /* virtual */ void COrder_Attack::OnAnimationAttack(CUnit &unit)
314 {
315 	//Wyrmgus start
316 //	Assert(unit.Type->CanAttack);
317 	if (!unit.CanAttack(false)) {
318 		return;
319 	}
320 	//Wyrmgus end
321 
322 	//Wyrmgus start
323 //	FireMissile(unit, this->GetGoal(), this->goalPos);
324 	FireMissile(unit, this->GetGoal(), this->goalPos, this->MapLayer);
325 	//Wyrmgus end
326 	UnHideUnit(unit); // unit is invisible until attacks
327 	unit.StepCount = 0;
328 }
329 
OnAiHitUnit(CUnit & unit,CUnit * attacker,int)330 /* virtual */ bool COrder_Attack::OnAiHitUnit(CUnit &unit, CUnit *attacker, int /*damage*/)
331 {
332 	CUnit *goal = this->GetGoal();
333 
334 	if (goal) {
335 		if (goal->IsAlive() == false) {
336 			this->ClearGoal();
337 			this->goalPos = goal->tilePos;
338 			this->MapLayer = goal->MapLayer->ID;
339 			return false;
340 		}
341 		if (goal == attacker) {
342 			return true;
343 		}
344 		//Wyrmgus start
345 //		if (goal->CurrentAction() == UnitActionAttack) {
346 		if (goal->CurrentAction() == UnitActionAttack && unit.MapDistanceTo(*goal) <= unit.GetModifiedVariable(ATTACKRANGE_INDEX)) {
347 		//Wyrmgus end
348 			const COrder_Attack &order = *static_cast<COrder_Attack *>(goal->CurrentOrder());
349 			if (order.GetGoal() == &unit) {
350 				//we already fight with one of attackers;
351 				return true;
352 			}
353 		}
354 	}
355 	return false;
356 }
357 
358 
359 
IsWeakTargetSelected() const360 bool COrder_Attack::IsWeakTargetSelected() const
361 {
362 	return (this->State & WEAK_TARGET) != 0;
363 }
364 
365 /**
366 **  Check for dead goal.
367 **
368 **  @warning  The caller must check, if he likes the restored SavedOrder!
369 **
370 **  @todo     If a unit enters an building, than the attack choose an
371 **            other goal, perhaps it is better to wait for the goal?
372 **
373 **  @param unit  Unit using the goal.
374 **
375 **  @return      true if order have changed, false else.
376 */
CheckForDeadGoal(CUnit & unit)377 bool COrder_Attack::CheckForDeadGoal(CUnit &unit)
378 {
379 	CUnit *goal = this->GetGoal();
380 
381 	// Position or valid target, it is ok.
382 	if (!goal || goal->IsVisibleAsGoal(*unit.Player)) {
383 		return false;
384 	}
385 
386 	// Goal could be destroyed or unseen
387 	// So, cannot use type.
388 	this->goalPos = goal->tilePos;
389 	this->MapLayer = goal->MapLayer->ID;
390 	this->MinRange = 0;
391 	this->Range = 0;
392 	this->ClearGoal();
393 
394 	// If we have a saved order continue this saved order.
395 	if (unit.RestoreOrder()) {
396 		return true;
397 	}
398 	return false;
399 }
400 
401 /**
402 **  Change invalid target for new target in range.
403 **
404 **  @param unit  Unit to check if goal is in range
405 **
406 **  @return      true if order(action) have changed, false else (if goal change return false).
407 */
CheckForTargetInRange(CUnit & unit)408 bool COrder_Attack::CheckForTargetInRange(CUnit &unit)
409 {
410 	// Target is dead?
411 	if (CheckForDeadGoal(unit)) {
412 		return true;
413 	}
414 
415 	// No goal: if meeting enemy attack it.
416 	if (!this->HasGoal()
417 		&& this->Action != UnitActionAttackGround
418 		//Wyrmgus start
419 //		&& !Map.WallOnMap(this->goalPos)) {
420 		&& !Map.WallOnMap(this->goalPos, this->MapLayer)) {
421 		//Wyrmgus end
422 		CUnit *goal = AttackUnitsInReactRange(unit);
423 
424 		if (goal) {
425 			//Wyrmgus start
426 //			COrder *savedOrder = COrder::NewActionAttack(unit, this->goalPos);
427 			COrder *savedOrder = COrder::NewActionAttack(unit, this->goalPos, this->MapLayer);
428 			//Wyrmgus end
429 
430 			if (unit.CanStoreOrder(savedOrder) == false) {
431 				delete savedOrder;
432 				savedOrder = nullptr;
433 			} else {
434 				unit.SavedOrder = savedOrder;
435 			}
436 			this->SetGoal(goal);
437 			this->MinRange = unit.Type->MinAttackRange;
438 			if (unit.Player->AiEnabled && unit.Variable[SPEED_INDEX].Value > goal->Variable[SPEED_INDEX].Value && unit.GetModifiedVariable(ATTACKRANGE_INDEX) > goal->GetModifiedVariable(ATTACKRANGE_INDEX)) { //makes fast AI ranged units move away from slower targets that have smaller range
439 				this->MinRange = unit.GetModifiedVariable(ATTACKRANGE_INDEX);
440 			}
441 			this->Range = unit.GetModifiedVariable(ATTACKRANGE_INDEX);
442 			this->goalPos = goal->tilePos;
443 			this->MapLayer = goal->MapLayer->ID;
444 			this->State |= WEAK_TARGET; // weak target
445 		}
446 		// Have a weak target, try a better target.
447 	} else if (this->HasGoal() && (this->State & WEAK_TARGET || unit.Player->AiEnabled)) {
448 		CUnit *goal = this->GetGoal();
449 		CUnit *newTarget = AttackUnitsInReactRange(unit);
450 
451 		if (newTarget && ThreatCalculate(unit, *newTarget) < ThreatCalculate(unit, *goal)) {
452 			COrder *savedOrder = nullptr;
453 			if (unit.CanStoreOrder(this)) {
454 				savedOrder = this->Clone();
455 			}
456 			if (savedOrder != nullptr) {
457 				unit.SavedOrder = savedOrder;
458 			}
459 			this->SetGoal(newTarget);
460 			this->goalPos = newTarget->tilePos;
461 			this->MapLayer = newTarget->MapLayer->ID;
462 
463 			//set the MinRange here as well, so that hit-and-run attacks will be conducted properly
464 			this->MinRange = unit.Type->MinAttackRange;
465 			if (unit.Player->AiEnabled && unit.Variable[SPEED_INDEX].Value > newTarget->Variable[SPEED_INDEX].Value && unit.GetModifiedVariable(ATTACKRANGE_INDEX) > newTarget->GetModifiedVariable(ATTACKRANGE_INDEX)) { //makes fast AI ranged units move away from slower targets that have smaller range
466 				this->MinRange = unit.GetModifiedVariable(ATTACKRANGE_INDEX);
467 			}
468 		}
469 	}
470 
471 	Assert(!unit.Type->BoolFlag[VANISHES_INDEX].value && !unit.Destroyed && !unit.Removed);
472 	return false;
473 }
474 
475 /**
476 **  Controls moving a unit to its target when attacking
477 **
478 **  @param unit  Unit that is attacking and moving
479 */
MoveToTarget(CUnit & unit)480 void COrder_Attack::MoveToTarget(CUnit &unit)
481 {
482 	Assert(!unit.Type->BoolFlag[VANISHES_INDEX].value && !unit.Destroyed && !unit.Removed);
483 	Assert(unit.CurrentOrder() == this);
484 	Assert(unit.CanMove());
485 	Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos, this->MapLayer));
486 
487 	//Wyrmgus start
488 	//if is on a moving raft and target is now within range, stop the raft
489 	if ((unit.MapLayer->Field(unit.tilePos)->Flags & MapFieldBridge) && !unit.Type->BoolFlag[BRIDGE_INDEX].value && unit.Type->UnitType == UnitTypeLand) {
490 		std::vector<CUnit *> table;
491 		Select(unit.tilePos, unit.tilePos, table, unit.MapLayer->ID);
492 		for (size_t i = 0; i != table.size(); ++i) {
493 			if (!table[i]->Removed && table[i]->Type->BoolFlag[BRIDGE_INDEX].value && table[i]->CanMove()) {
494 				if (table[i]->CurrentAction() == UnitActionMove) {
495 					if ((this->GetGoal() && unit.MapDistanceTo(*this->GetGoal()) <= unit.GetModifiedVariable(ATTACKRANGE_INDEX)) || (!this->HasGoal() && unit.MapDistanceTo(this->goalPos, this->MapLayer) <= unit.GetModifiedVariable(ATTACKRANGE_INDEX))) {
496 						if (!Map.IsLayerUnderground(this->MapLayer) || CheckObstaclesBetweenTiles(unit.tilePos, goalPos, MapFieldAirUnpassable, MapLayer)) {
497 							CommandStopUnit(*table[i]);
498 						}
499 					}
500 				}
501 			}
502 		}
503 	}
504 	//Wyrmgus end
505 
506 	int err = DoActionMove(unit);
507 
508 	if (unit.Anim.Unbreakable) {
509 		return;
510 	}
511 
512 	// Look if we have reached the target.
513 	if (err == 0 && !this->HasGoal()) {
514 		// Check if we're in range when attacking a location and we are waiting
515 		if (unit.MapDistanceTo(this->goalPos, this->MapLayer) <= unit.GetModifiedVariable(ATTACKRANGE_INDEX)) {
516 			//Wyrmgus start
517 //			if (!GameSettings.Inside || CheckObstaclesBetweenTiles(unit.tilePos, goalPos, MapFieldRocks | MapFieldForest)) {
518 			if (!Map.IsLayerUnderground(MapLayer) || CheckObstaclesBetweenTiles(unit.tilePos, goalPos, MapFieldAirUnpassable, MapLayer)) {
519 			//Wyrmgus end
520 				err = PF_REACHED;
521 			}
522 		}
523 	}
524 	if (err >= 0) {
525 		if (CheckForTargetInRange(unit)) {
526 			return;
527 		}
528 		return;
529 	}
530 	if (err == PF_REACHED) {
531 		CUnit *goal = this->GetGoal();
532 		// Have reached target? FIXME: could use the new return code?
533 		if (goal && unit.MapDistanceTo(*goal) <= unit.GetModifiedVariable(ATTACKRANGE_INDEX)) {
534 			//Wyrmgus start
535 //			if (!GameSettings.Inside || CheckObstaclesBetweenTiles(unit.tilePos, goalPos, MapFieldRocks | MapFieldForest)) {
536 			if (!Map.IsLayerUnderground(this->MapLayer) || CheckObstaclesBetweenTiles(unit.tilePos, goalPos, MapFieldAirUnpassable, MapLayer)) {
537 			//Wyrmgus end
538 				// Reached another unit, now attacking it
539 				unsigned char oldDir = unit.Direction;
540 				//Wyrmgus start
541 //				const Vec2i dir = goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos;
542 				const Vec2i dir = PixelSize(PixelSize(goal->tilePos) * Map.GetMapLayerPixelTileSize(this->MapLayer)) + goal->GetHalfTilePixelSize() - PixelSize(PixelSize(unit.tilePos) * Map.GetMapLayerPixelTileSize(this->MapLayer)) - unit.GetHalfTilePixelSize();
543 				//Wyrmgus end
544 				UnitHeadingFromDeltaXY(unit, dir);
545 				if (unit.Type->BoolFlag[SIDEATTACK_INDEX].value) {
546 					unsigned char leftTurn = (unit.Direction - 2 * NextDirection) % (NextDirection * 8);
547 					unsigned char rightTurn = (unit.Direction + 2 * NextDirection) % (NextDirection * 8);
548 					if (abs(leftTurn - oldDir) < abs(rightTurn - oldDir)) {
549 						unit.Direction = leftTurn;
550 					} else {
551 						unit.Direction = rightTurn;
552 					}
553 					UnitUpdateHeading(unit);
554 				}
555 				this->State++;
556 				return;
557 			}
558 		}
559 		// Attacking wall or ground.
560 		if (((goal && goal->Type && goal->Type->BoolFlag[WALL_INDEX].value)
561 			//Wyrmgus start
562 //			 || (!goal && (Map.WallOnMap(this->goalPos) || this->Action == UnitActionAttackGround)))
563 			 || (!goal && (this->Action == UnitActionAttackGround || Map.WallOnMap(this->goalPos, this->MapLayer))))
564 			//Wyrmgus end
565 			&& unit.MapDistanceTo(this->goalPos, this->MapLayer) <= unit.GetModifiedVariable(ATTACKRANGE_INDEX)) {
566 			//Wyrmgus start
567 //			if (!GameSettings.Inside || CheckObstaclesBetweenTiles(unit.tilePos, goalPos, MapFieldRocks | MapFieldForest)) {
568 			if (!Map.IsLayerUnderground(this->MapLayer) || CheckObstaclesBetweenTiles(unit.tilePos, goalPos, MapFieldAirUnpassable, MapLayer)) {
569 			//Wyrmgus end
570 				// Reached wall or ground, now attacking it
571 				unsigned char oldDir = unit.Direction;
572 				UnitHeadingFromDeltaXY(unit, this->goalPos - unit.tilePos);
573 				if (unit.Type->BoolFlag[SIDEATTACK_INDEX].value) {
574 					unsigned char leftTurn = (unit.Direction - 2 * NextDirection) % (NextDirection * 8);
575 					unsigned char rightTurn = (unit.Direction + 2 * NextDirection) % (NextDirection * 8);
576 					if (abs(leftTurn - oldDir) < abs(rightTurn - oldDir)) {
577 						unit.Direction = leftTurn;
578 					} else {
579 						unit.Direction = rightTurn;
580 					}
581 					UnitUpdateHeading(unit);
582 				}
583 				this->State &= WEAK_TARGET;
584 				this->State |= ATTACK_TARGET;
585 				return;
586 			}
587 		}
588 	}
589 	// Unreachable.
590 
591 	if (err == PF_UNREACHABLE) {
592 		//Wyrmgus start
593 		//if is unreachable and is on a raft, see if the raft can move closer to the enemy
594 		if ((unit.MapLayer->Field(unit.tilePos)->Flags & MapFieldBridge) && !unit.Type->BoolFlag[BRIDGE_INDEX].value && unit.Type->UnitType == UnitTypeLand) {
595 			std::vector<CUnit *> table;
596 			Select(unit.tilePos, unit.tilePos, table, unit.MapLayer->ID);
597 			for (size_t i = 0; i != table.size(); ++i) {
598 				if (!table[i]->Removed && table[i]->Type->BoolFlag[BRIDGE_INDEX].value && table[i]->CanMove()) {
599 					if (table[i]->CurrentAction() == UnitActionStill) {
600 						CommandStopUnit(*table[i]);
601 						CommandMove(*table[i], this->HasGoal() ? this->GetGoal()->tilePos : this->goalPos, FlushCommands, this->HasGoal() ? this->GetGoal()->MapLayer->ID : this->MapLayer);
602 					}
603 					return;
604 				}
605 			}
606 		}
607 		//Wyrmgus end
608 		if (!this->HasGoal()) {
609 			// When attack-moving we have to allow a bigger range
610 			this->Range++;
611 			unit.Wait = 5;
612 			return;
613 		} else {
614 			this->ClearGoal();
615 		}
616 	}
617 
618 	// Return to old task?
619 	if (!unit.RestoreOrder()) {
620 		this->Finished = true;
621 	}
622 }
623 
624 /**
625 **  Handle attacking the target.
626 **
627 **  @param unit  Unit, for that the attack is handled.
628 */
AttackTarget(CUnit & unit)629 void COrder_Attack::AttackTarget(CUnit &unit)
630 {
631 	Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos, this->MapLayer));
632 
633 	AnimateActionAttack(unit, *this);
634 	if (unit.Anim.Unbreakable) {
635 		return;
636 	}
637 
638 	//Wyrmgus start
639 //	if (!this->HasGoal() && (this->Action == UnitActionAttackGround || Map.WallOnMap(this->goalPos))) {
640 	if (!this->HasGoal() && (this->Action == UnitActionAttackGround || Map.WallOnMap(this->goalPos, this->MapLayer))) {
641 	//Wyrmgus end
642 		return;
643 	}
644 
645 	// Target is dead ? Change order ?
646 	if (CheckForDeadGoal(unit)) {
647 		return;
648 	}
649 	CUnit *goal = this->GetGoal();
650 	bool dead = !goal || goal->IsAlive() == false;
651 
652 	// No target choose one.
653 	if (!goal) {
654 		goal = AttackUnitsInReactRange(unit);
655 
656 		// No new goal, continue way to destination.
657 		if (!goal) {
658 			// Return to old task ?
659 			if (unit.RestoreOrder()) {
660 				return;
661 			}
662 			this->State = MOVE_TO_TARGET;
663 			return;
664 		}
665 		// Save current command to come back.
666 		//Wyrmgus start
667 //		COrder *savedOrder = COrder::NewActionAttack(unit, this->goalPos);
668 		COrder *savedOrder = COrder::NewActionAttack(unit, this->goalPos, this->MapLayer);
669 		//Wyrmgus end
670 
671 		if (unit.CanStoreOrder(savedOrder) == false) {
672 			delete savedOrder;
673 			savedOrder = nullptr;
674 		} else {
675 			unit.SavedOrder = savedOrder;
676 		}
677 		this->SetGoal(goal);
678 		this->goalPos = goal->tilePos;
679 		this->MapLayer = goal->MapLayer->ID;
680 		this->MinRange = unit.Type->MinAttackRange;
681 		if (unit.Player->AiEnabled && unit.Variable[SPEED_INDEX].Value > goal->Variable[SPEED_INDEX].Value && unit.GetModifiedVariable(ATTACKRANGE_INDEX) > goal->GetModifiedVariable(ATTACKRANGE_INDEX)) { //makes fast AI ranged units move away from slower targets that have smaller range
682 			this->MinRange = unit.GetModifiedVariable(ATTACKRANGE_INDEX);
683 		}
684 		this->Range = unit.GetModifiedVariable(ATTACKRANGE_INDEX);
685 		this->State |= WEAK_TARGET;
686 
687 		// Have a weak target, try a better target.
688 		// FIXME: if out of range also try another target quick
689 	} else {
690 		if ((this->State & WEAK_TARGET)) {
691 			CUnit *newTarget = AttackUnitsInReactRange(unit);
692 			if (newTarget && ThreatCalculate(unit, *newTarget) < ThreatCalculate(unit, *goal)) {
693 				if (unit.CanStoreOrder(this)) {
694 					unit.SavedOrder = this->Clone();
695 				}
696 				goal = newTarget;
697 				this->SetGoal(newTarget);
698 				this->goalPos = newTarget->tilePos;
699 				this->MapLayer = newTarget->MapLayer->ID;
700 				this->MinRange = unit.Type->MinAttackRange;
701 				if (unit.Player->AiEnabled && unit.Variable[SPEED_INDEX].Value > newTarget->Variable[SPEED_INDEX].Value && unit.GetModifiedVariable(ATTACKRANGE_INDEX) > newTarget->GetModifiedVariable(ATTACKRANGE_INDEX)) { //makes fast AI ranged units move away from slower targets that have smaller range
702 					this->MinRange = unit.GetModifiedVariable(ATTACKRANGE_INDEX);
703 				}
704 				this->State = MOVE_TO_TARGET;
705 			}
706 		}
707 	}
708 
709 	// Still near to target, if not goto target.
710 	const int dist = unit.MapDistanceTo(*goal);
711 	if (dist > unit.GetModifiedVariable(ATTACKRANGE_INDEX)
712 		//Wyrmgus start
713 //		|| (GameSettings.Inside && CheckObstaclesBetweenTiles(unit.tilePos, goal->tilePos, MapFieldRocks | MapFieldForest) == false)) {
714 		|| (Map.IsLayerUnderground(this->MapLayer) && CheckObstaclesBetweenTiles(unit.tilePos, goal->tilePos, MapFieldAirUnpassable, MapLayer) == false)) {
715 		//Wyrmgus end
716 		//Wyrmgus start
717 		// towers don't chase after goal
718 		/*
719 		if (unit.CanMove()) {
720 			if (unit.CanStoreOrder(this)) {
721 				if (dead) {
722 					//Wyrmgus start
723 //					unit.SavedOrder = COrder::NewActionAttack(unit, this->goalPos);
724 					unit.SavedOrder = COrder::NewActionAttack(unit, this->goalPos, this->MapLayer);
725 					//Wyrmgus end
726 				} else {
727 					unit.SavedOrder = this->Clone();
728 				}
729 			}
730 		}
731 		*/
732 		//Wyrmgus end
733 		unit.Frame = 0;
734 		this->State &= WEAK_TARGET;
735 		this->State |= MOVE_TO_TARGET;
736 	}
737 	if (
738 		dist < unit.Type->MinAttackRange
739 		|| (unit.Player->AiEnabled && dist < unit.GetModifiedVariable(ATTACKRANGE_INDEX) && unit.Variable[SPEED_INDEX].Value > goal->Variable[SPEED_INDEX].Value && unit.GetModifiedVariable(ATTACKRANGE_INDEX) > goal->GetModifiedVariable(ATTACKRANGE_INDEX)) //makes fast AI ranged units move away from slower targets that have smaller range
740 	) {
741 		this->State = MOVE_TO_TARGET;
742 	}
743 
744 	// Turn always to target
745 	if (goal) {
746 		//Wyrmgus start
747 //		const Vec2i dir = goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos;
748 		const Vec2i dir = PixelSize(PixelSize(goal->tilePos) * Map.GetMapLayerPixelTileSize(this->MapLayer)) + goal->GetHalfTilePixelSize() - PixelSize(PixelSize(unit.tilePos) * Map.GetMapLayerPixelTileSize(this->MapLayer)) - unit.GetHalfTilePixelSize();
749 		//Wyrmgus end
750 		unsigned char oldDir = unit.Direction;
751 		UnitHeadingFromDeltaXY(unit, dir);
752 		if (unit.Type->BoolFlag[SIDEATTACK_INDEX].value) {
753 			unsigned char leftTurn = (unit.Direction - 2 * NextDirection) % (NextDirection * 8);
754 			unsigned char rightTurn = (unit.Direction + 2 * NextDirection) % (NextDirection * 8);
755 			if (abs(leftTurn - oldDir) < abs(rightTurn - oldDir)) {
756 				unit.Direction = leftTurn;
757 			} else {
758 				unit.Direction = rightTurn;
759 			}
760 			UnitUpdateHeading(unit);
761 		}
762 	}
763 }
764 
765 /**
766 **  Unit attacks!
767 **
768 **  if (SubAction & WEAK_TARGET) is true the goal is a weak goal.
769 **  This means the unit AI (little AI) could choose a new better goal.
770 **
771 **  @todo  Lets do some tries to reach the target.
772 **         If target place is not reachable, choose better goal to reduce
773 **         the pathfinder load.
774 **
775 **  @param unit  Unit, for that the attack is handled.
776 */
Execute(CUnit & unit)777 /* virtual */ void COrder_Attack::Execute(CUnit &unit)
778 {
779 	Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos, this->MapLayer));
780 
781 	if (unit.Wait) {
782 		if (!unit.Waiting) {
783 			unit.Waiting = 1;
784 			unit.WaitBackup = unit.Anim;
785 		}
786 		//Wyrmgus start
787 //		UnitShowAnimation(unit, unit.Type->Animations->Still);
788 		UnitShowAnimation(unit, unit.GetAnimations()->Still);
789 		//Wyrmgus end
790 		unit.Wait--;
791 		return;
792 	}
793 	if (unit.Waiting) {
794 		unit.Anim = unit.WaitBackup;
795 		unit.Waiting = 0;
796 	}
797 
798 	//Wyrmgus start
799 	if (!unit.CanAttack(true) && !this->HasGoal()) { //if unit is a transporter that can't attack, return false if the original target no longer exists
800 		this->Finished = true;
801 		return;
802 	}
803 	//Wyrmgus end
804 
805 	switch (this->State) {
806 		case 0: { // First entry
807 			// did Order change ?
808 			if (CheckForTargetInRange(unit)) {
809 				return;
810 			}
811 			// Can we already attack ?
812 			if (this->HasGoal()) {
813 				CUnit &goal = *this->GetGoal();
814 				const int dist = goal.MapDistanceTo(unit);
815 
816 				if (unit.Type->MinAttackRange < dist &&
817 					dist <= unit.GetModifiedVariable(ATTACKRANGE_INDEX)
818 					//Wyrmgus start
819 					&& !(unit.Player->AiEnabled && dist < unit.GetModifiedVariable(ATTACKRANGE_INDEX) && unit.Variable[SPEED_INDEX].Value > goal.Variable[SPEED_INDEX].Value && unit.GetModifiedVariable(ATTACKRANGE_INDEX) > goal.GetModifiedVariable(ATTACKRANGE_INDEX)) //makes fast AI ranged units move away from slower targets that have smaller range
820 				) {
821 					//Wyrmgus end
822 					//Wyrmgus start
823 //					if (!GameSettings.Inside || CheckObstaclesBetweenTiles(unit.tilePos, goalPos, MapFieldRocks | MapFieldForest)) {
824 					if (!Map.IsLayerUnderground(this->MapLayer) || CheckObstaclesBetweenTiles(unit.tilePos, goalPos, MapFieldAirUnpassable, MapLayer)) {
825 					//Wyrmgus end
826 						//Wyrmgus start
827 //						const Vec2i dir = goal.tilePos + goal.Type->GetHalfTileSize() - unit.tilePos;
828 						const Vec2i dir = PixelSize(PixelSize(goal.tilePos) * Map.GetMapLayerPixelTileSize(goal.MapLayer->ID)) + goal.GetHalfTilePixelSize() - PixelSize(PixelSize(unit.tilePos) * Map.GetMapLayerPixelTileSize(unit.MapLayer->ID)) - unit.GetHalfTilePixelSize();
829 						//Wyrmgus end
830 						unsigned char oldDir = unit.Direction;
831 						UnitHeadingFromDeltaXY(unit, dir);
832 						if (unit.Type->BoolFlag[SIDEATTACK_INDEX].value) {
833 							unsigned char leftTurn = (unit.Direction - 2 * NextDirection) % (NextDirection * 8);
834 							unsigned char rightTurn = (unit.Direction + 2 * NextDirection) % (NextDirection * 8);
835 							if (abs(leftTurn - oldDir) < abs(rightTurn - oldDir)) {
836 								unit.Direction = leftTurn;
837 							} else {
838 								unit.Direction = rightTurn;
839 							}
840 							UnitUpdateHeading(unit);
841 						}
842 						this->State |= ATTACK_TARGET;
843 						AttackTarget(unit);
844 						return;
845 					}
846 				}
847 			//Wyrmgus start
848 			// add instance for attack ground without moving
849 			} else if (this->Action == UnitActionAttackGround && unit.MapDistanceTo(this->goalPos, this->MapLayer) <= unit.GetModifiedVariable(ATTACKRANGE_INDEX) && unit.Type->MinAttackRange < unit.MapDistanceTo(this->goalPos, this->MapLayer)) {
850 				if (!Map.IsLayerUnderground(this->MapLayer) || CheckObstaclesBetweenTiles(unit.tilePos, goalPos, MapFieldAirUnpassable, MapLayer)) {
851 					// Reached wall or ground, now attacking it
852 					unsigned char oldDir = unit.Direction;
853 					UnitHeadingFromDeltaXY(unit, this->goalPos - unit.tilePos);
854 					if (unit.Type->BoolFlag[SIDEATTACK_INDEX].value) {
855 						unsigned char leftTurn = (unit.Direction - 2 * NextDirection) % (NextDirection * 8);
856 						unsigned char rightTurn = (unit.Direction + 2 * NextDirection) % (NextDirection * 8);
857 						if (abs(leftTurn - oldDir) < abs(rightTurn - oldDir)) {
858 							unit.Direction = leftTurn;
859 						} else {
860 							unit.Direction = rightTurn;
861 						}
862 						UnitUpdateHeading(unit);
863 					}
864 					this->State &= WEAK_TARGET;
865 					this->State |= ATTACK_TARGET;
866 					return;
867 				}
868 			//Wyrmgus end
869 			}
870 			this->State = MOVE_TO_TARGET;
871 			// FIXME: should use a reachable place to reduce pathfinder time.
872 		}
873 		// FALL THROUGH
874 		case MOVE_TO_TARGET:
875 		case MOVE_TO_TARGET + WEAK_TARGET:
876 			if (!unit.CanMove()) {
877 				this->Finished = true;
878 				return;
879 			}
880 			MoveToTarget(unit);
881 			break;
882 
883 		case ATTACK_TARGET:
884 		case ATTACK_TARGET + WEAK_TARGET:
885 			AttackTarget(unit);
886 			break;
887 
888 		case WEAK_TARGET:
889 			DebugPrint("FIXME: wrong entry.\n");
890 			break;
891 	}
892 }
893 
894 //@}
895