From bf841969a7b25506058effec4757c36dc3706607 Mon Sep 17 00:00:00 2001
From: Andrettin <andre.ng@live.com>
Date: Wed, 25 Nov 2015 13:13:17 +0100
Subject: [PATCH] Units can now (optionally) have a RangedAttack animation
 specified

---
 doc/scripts/unittype.html    |  1 +
 src/action/action_attack.cpp | 12 ++++++++----
 src/animation/animation.cpp  |  3 +++
 src/include/animation.h      |  4 +++-
 src/include/unit.h           |  2 ++
 src/missile/missile.cpp      |  5 ++++-
 src/sound/unitsound.cpp      |  1 +
 src/unit/unit.cpp            | 31 +++++++++++++++++++++++++++++++
 8 files changed, 53 insertions(+), 6 deletions(-)

diff --git a/doc/scripts/unittype.html b/doc/scripts/unittype.html
index 20191efc3..e20c05199 100644
--- a/doc/scripts/unittype.html
+++ b/doc/scripts/unittype.html
@@ -74,6 +74,7 @@ animation.</dd>
     <li>Still</li>
     <li>Move</li>
     <li>Attack</li>
+    <li>RangedAttack</li>
     <li>Repair</li>
     <li>Train</li>
     <li>Research</li>
diff --git a/src/action/action_attack.cpp b/src/action/action_attack.cpp
index 0c1b8eb0d..84c1b5216 100644
--- a/src/action/action_attack.cpp
+++ b/src/action/action_attack.cpp
@@ -83,11 +83,15 @@ void AnimateActionAttack(CUnit &unit, COrder &order)
 	//  No animation.
 	//  So direct fire missile.
 	//  FIXME : wait a little.
-	if (!unit.Type->Animations || !unit.Type->Animations->Attack) {
-		order.OnAnimationAttack(unit);
-		return;
+	if (unit.Type->Animations && unit.Type->Animations->RangedAttack && unit.IsAttackRanged(order.GetGoal(), order.GetGoalPos())) {
+		UnitShowAnimation(unit, unit.Type->Animations->RangedAttack);
+	} else {
+		if (!unit.Type->Animations || !unit.Type->Animations->Attack) {
+			order.OnAnimationAttack(unit);
+			return;
+		}
+		UnitShowAnimation(unit, unit.Type->Animations->Attack);
 	}
-	UnitShowAnimation(unit, unit.Type->Animations->Attack);
 }
 
 /* static */ COrder *COrder::NewActionAttack(const CUnit &attacker, CUnit &target)
diff --git a/src/animation/animation.cpp b/src/animation/animation.cpp
index 3da5c59b0..a3c68a7fa 100644
--- a/src/animation/animation.cpp
+++ b/src/animation/animation.cpp
@@ -703,6 +703,8 @@ static int CclDefineAnimations(lua_State *l)
 			}
 		} else if (!strcmp(value, "Attack")) {
 			anims->Attack = ParseAnimation(l, -1);
+		} else if (!strcmp(value, "RangedAttack")) {
+			anims->RangedAttack = ParseAnimation(l, -1);
 		} else if (!strcmp(value, "SpellCast")) {
 			anims->SpellCast = ParseAnimation(l, -1);
 		} else if (!strcmp(value, "Move")) {
@@ -732,6 +734,7 @@ static int CclDefineAnimations(lua_State *l)
 		AddAnimationToArray(anims->Death[i]);
 	}
 	AddAnimationToArray(anims->Attack);
+	AddAnimationToArray(anims->RangedAttack);
 	AddAnimationToArray(anims->SpellCast);
 	AddAnimationToArray(anims->Move);
 	AddAnimationToArray(anims->Repair);
diff --git a/src/include/animation.h b/src/include/animation.h
index 7518fe19c..aa064a0c7 100644
--- a/src/include/animation.h
+++ b/src/include/animation.h
@@ -107,7 +107,7 @@ public:
 class CAnimations
 {
 public:
-	CAnimations() : Attack(NULL), Build(NULL), Move(NULL), Repair(NULL),
+	CAnimations() : Attack(NULL), RangedAttack(NULL), Build(NULL), Move(NULL), Repair(NULL),
 		Research(NULL), SpellCast(NULL), Start(NULL), Still(NULL),
 		Train(NULL), Upgrade(NULL)
 	{
@@ -118,6 +118,7 @@ public:
 	~CAnimations()
 	{
 		delete Attack;
+		delete RangedAttack;
 		delete Build;
 		for (int i = 0; i < ANIMATIONS_DEATHTYPES + 1; ++i) {
 			delete Death[i];
@@ -141,6 +142,7 @@ public:
 
 public:
 	CAnimation *Attack;
+	CAnimation *RangedAttack;
 	CAnimation *Build;
 	CAnimation *Death[ANIMATIONS_DEATHTYPES + 1];
 	CAnimation *Harvest[MaxCosts];
diff --git a/src/include/unit.h b/src/include/unit.h
index 7d2d1cdcf..bffdb768d 100644
--- a/src/include/unit.h
+++ b/src/include/unit.h
@@ -286,6 +286,8 @@ public:
 
 	int GetDrawLevel() const;
 
+	bool IsAttackRanged(CUnit *goal, const Vec2i &goalPos);
+	
 	PixelPos GetMapPixelPosTopLeft() const;
 	PixelPos GetMapPixelPosCenter() const;
 
diff --git a/src/missile/missile.cpp b/src/missile/missile.cpp
index a95979d8c..2f71cc377 100644
--- a/src/missile/missile.cpp
+++ b/src/missile/missile.cpp
@@ -367,7 +367,10 @@ void FireMissile(CUnit &unit, CUnit *goal, const Vec2i &goalPos)
 	}
 
 	// No missile hits immediately!
-	if (unit.Type->Missile.Missile->Class == MissileClassNone) {
+	if (
+		unit.Type->Missile.Missile->Class == MissileClassNone
+		|| (unit.Type->Animations && unit.Type->Animations->Attack && unit.Type->Animations->RangedAttack && !unit.IsAttackRanged(goal, goalPos)) // treat melee attacks from units that have both attack and ranged attack animations as having missile class none
+	) {
 		// No goal, take target coordinates
 		if (!goal) {
 			if (Map.WallOnMap(goalPos)) {
diff --git a/src/sound/unitsound.cpp b/src/sound/unitsound.cpp
index 7820c32df..637d3dcf3 100644
--- a/src/sound/unitsound.cpp
+++ b/src/sound/unitsound.cpp
@@ -120,6 +120,7 @@ static void MapAnimSounds(CUnitType &type)
 	MapAnimSounds2(type.Animations->Still);
 	MapAnimSounds2(type.Animations->Move);
 	MapAnimSounds2(type.Animations->Attack);
+	MapAnimSounds2(type.Animations->RangedAttack);
 	MapAnimSounds2(type.Animations->SpellCast);
 	for (int i = 0; i <= ANIMATIONS_DEATHTYPES; ++i) {
 		MapAnimSounds2(type.Animations->Death[i]);
diff --git a/src/unit/unit.cpp b/src/unit/unit.cpp
index d4bd4d020..bc6743c9a 100644
--- a/src/unit/unit.cpp
+++ b/src/unit/unit.cpp
@@ -3121,6 +3121,37 @@ bool CUnit::IsUnusable(bool ignore_built_state) const
 	return (!IsAliveOnMap() || (!ignore_built_state && CurrentAction() == UnitActionBuilt));
 }
 
+/**
+**  Check if the unit attacking its goal will result in a ranged attack
+*/
+bool CUnit::IsAttackRanged(CUnit *goal, const Vec2i &goalPos)
+{
+	if (this->Variable[ATTACKRANGE_INDEX].Value <= 1) { //always return false if the units attack range is 1 or lower
+		return false;
+	}
+	
+	if (this->Container) { //if the unit is inside a container, the attack will always be ranged
+		return true;
+	}
+	
+	if (
+		goal
+		&& goal->IsAliveOnMap()
+		&& (
+			this->MapDistanceTo(*goal) > 1
+			|| (this->Type->UnitType != UnitTypeFly && goal->Type->UnitType == UnitTypeFly)
+			|| (this->Type->UnitType == UnitTypeFly && goal->Type->UnitType != UnitTypeFly)
+		)
+	) {
+		return true;
+	}
+	
+	if (!goal && Map.Info.IsPointOnMap(goalPos) && this->MapDistanceTo(goalPos) > 1) {
+		return true;
+	}
+	
+	return false;
+}
 
 /*----------------------------------------------------------------------------
 --  Initialize/Cleanup