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