From 973631c33515a68d6b5dfc6fd446fbe3d65ebd47 Mon Sep 17 00:00:00 2001
From: iddqd-mail <iddqd_mail@mail.ru>
Date: Fri, 15 Jun 2012 11:24:08 +0600
Subject: [PATCH] [+] Ability to get info for unit's bool-flags and currently
 used spell in animations. [+] Ability to change some target's variable when
 missile hits it. Use (in DefineMissile) ChangeVariable for variable name,
 ChangeAmount for changing amount (may be negative) and bool ChangeMax to
 increase the Max field if Value will be greater than Max. [*]
 CclDefineUnitStats now supports more user defined variables (NOTE: saves
 compatibility will be lost) [*] AI units who uses repeat-cast magic won't
 stop on each cast. [-] Units no more attack revealers. [-] Some fixes to
 stored resources. [-] Don't clone attack action for dead units.

---
 src/action/action_attack.cpp             |  2 +-
 src/action/action_upgradeto.cpp          |  2 +-
 src/ai/ai_magic.cpp                      |  5 ++-
 src/animation/animation.cpp              | 19 +++++++++
 src/animation/animation_setplayervar.cpp |  4 +-
 src/include/missile.h                    |  3 ++
 src/include/player.h                     | 10 ++++-
 src/include/unit.h                       | 10 ++++-
 src/missile/missile.cpp                  |  6 +--
 src/missile/script_missile.cpp           | 12 ++++++
 src/stratagus/player.cpp                 | 29 +++++++++-----
 src/stratagus/script_player.cpp          |  2 +-
 src/unit/script_unittype.cpp             | 49 +++++++++++++-----------
 src/unit/unit.cpp                        | 15 +++++++-
 src/unit/unittype.cpp                    |  6 +--
 15 files changed, 126 insertions(+), 48 deletions(-)

diff --git a/src/action/action_attack.cpp b/src/action/action_attack.cpp
index a812eca12..89a2dd426 100644
--- a/src/action/action_attack.cpp
+++ b/src/action/action_attack.cpp
@@ -477,7 +477,7 @@ void COrder_Attack::AttackTarget(CUnit &unit)
 			return;
 		}
 		// Save current command to come back.
-		COrder *savedOrder = this->Clone();
+		COrder *savedOrder = COrder::NewActionAttack(unit, this->goalPos);
 
 		if (unit.StoreOrder(savedOrder) == false) {
 			delete savedOrder;
diff --git a/src/action/action_upgradeto.cpp b/src/action/action_upgradeto.cpp
index b089eda5c..cab047d4f 100644
--- a/src/action/action_upgradeto.cpp
+++ b/src/action/action_upgradeto.cpp
@@ -116,7 +116,7 @@ static int TransformUnitIntoType(CUnit &unit, const CUnitType &newtype)
 	for (int i = 0; i < MaxCosts; ++i) {
 		if (player.MaxResources[i] != -1) {
 			player.MaxResources[i] += newtype.Stats[player.Index].Storing[i] - oldtype.Stats[player.Index].Storing[i];
-			player.SetResource(i, player.StoredResources[i], true);
+			player.SetResource(i, player.StoredResources[i], STORE_BUILDING);
 		}
 	}
 
diff --git a/src/ai/ai_magic.cpp b/src/ai/ai_magic.cpp
index d3e40c3d4..4b906fcde 100644
--- a/src/ai/ai_magic.cpp
+++ b/src/ai/ai_magic.cpp
@@ -37,6 +37,7 @@
 #include "unittype.h"
 #include "unit.h"
 #include "spells.h"
+#include "actions.h"
 #include "ai_local.h"
 
 /*----------------------------------------------------------------------------
@@ -55,8 +56,8 @@ void AiCheckMagic()
 	for (int i = 0; i < n; ++i) {
 		CUnit &unit = player.GetUnit(i);
 
-		// Check only magic units
-		if (unit.Type->CanCastSpell) {
+		// Check only idle magic units
+		if (unit.Type->CanCastSpell && unit.CurrentAction() != UnitActionSpellCast) {
 			for (unsigned int j = 0; j < SpellTypeTable.size(); ++j) {
 				// Check if we can cast this spell. SpellIsAvailable checks for upgrades.
 				if (unit.Type->CanCastSpell[j] && SpellIsAvailable(player, j)
diff --git a/src/animation/animation.cpp b/src/animation/animation.cpp
index cad4a9242..7fb1f3ebe 100644
--- a/src/animation/animation.cpp
+++ b/src/animation/animation.cpp
@@ -39,6 +39,8 @@
 
 #include "stratagus.h"
 
+#include "action/action_spellcast.h"
+
 #include "animation.h"
 
 #include "animation/animation_attack.h"
@@ -66,6 +68,7 @@
 #include "iolib.h"
 #include "player.h"
 #include "script.h"
+#include "spells.h"
 #include "unit.h"
 #include "unittype.h"
 
@@ -178,6 +181,22 @@ int ParseAnimInt(const CUnit *unit, const char *parseint)
 			return goal->Variable[index].Value * 100 / goal->Variable[index].Max;
 		}
 		return 0;
+	} else if ((s[0] == 'b') && unit != NULL) { //unit bool flag detected
+		const int index = UnitTypeVar.BoolFlagNameLookup[cur];// User bool flags
+		if (index == -1) {
+			fprintf(stderr, "Bad bool-flag name '%s'\n", cur);
+			Exit(1);
+			return 0;
+		}
+		return goal->Type->BoolFlag[index].value;
+	} else if ((s[0] == 's') && unit != NULL) { //spell type detected
+		Assert(goal->CurrentAction() == UnitActionSpellCast);
+		const COrder_SpellCast &order = *static_cast<COrder_SpellCast *>(goal->CurrentOrder());
+		const SpellType &spell = order.GetSpell();
+		if (!strcmp(spell.Ident.c_str(), cur)) {
+			return 1;
+		}
+		return 0;
 	} else if (s[0] == 'p' && unit != NULL) { //player variable detected
 		char *next = strchr(cur, '.');
 		if (next == NULL) {
diff --git a/src/animation/animation_setplayervar.cpp b/src/animation/animation_setplayervar.cpp
index 013659236..dd665deb8 100644
--- a/src/animation/animation_setplayervar.cpp
+++ b/src/animation/animation_setplayervar.cpp
@@ -138,14 +138,14 @@ static void SetPlayerData(int player, const char *prop, const char *arg, int val
 			fprintf(stderr, "Invalid resource \"%s\"", arg);
 			Exit(1);
 		}
-		Players[player].SetResource(resId, value);
+		Players[player].SetResource(resId, value, STORE_BOTH);
 	} else if (!strcmp(prop, "StoredResources")) {
 		const int resId = GetResourceIdByName(arg);
 		if (resId == -1) {
 			fprintf(stderr, "Invalid resource \"%s\"", arg);
 			Exit(1);
 		}
-		Players[player].SetResource(resId, value, true);
+		Players[player].SetResource(resId, value, STORE_BUILDING);
 	} else if (!strcmp(prop, "UnitLimit")) {
 		Players[player].UnitLimit = value;
 	} else if (!strcmp(prop, "BuildingLimit")) {
diff --git a/src/include/missile.h b/src/include/missile.h
index 3a5df6216..edbede9de 100644
--- a/src/include/missile.h
+++ b/src/include/missile.h
@@ -360,6 +360,9 @@ public:
 	int DrawLevel;             /// Level to draw missile at
 	int SpriteFrames;          /// number of sprite frames in graphic
 	int NumDirections;         /// number of directions missile can face
+	int ChangeVariable;        /// variable to change
+	int ChangeAmount;          /// how many to change
+	bool ChangeMax;            /// modify the max, if value will exceed it
 
 	/// @todo FiredSound defined but not used!
 	SoundConfig FiredSound;    /// fired sound
diff --git a/src/include/player.h b/src/include/player.h
index a6eb25767..3159ff1ed 100644
--- a/src/include/player.h
+++ b/src/include/player.h
@@ -45,6 +45,14 @@
 #endif
 #include "vec2i.h"
 
+/*----------------------------------------------------------------------------
+--  Definitons
+----------------------------------------------------------------------------*/
+
+#define STORE_OVERALL 0
+#define STORE_BUILDING 1
+#define STORE_BOTH 2
+
 /*----------------------------------------------------------------------------
 --  Declarations
 ----------------------------------------------------------------------------*/
@@ -135,7 +143,7 @@ public:
 	/// Adds/subtracts some resources to/from the player store
 	void ChangeResource(int resource, int value, bool store = false);
 	/// Set a resource of the player
-	void SetResource(int resource, int value, bool store = false);
+	void SetResource(int resource, int value, int type = STORE_OVERALL);
 	/// Check, if there enough resources for action.
 	bool CheckResource(int resource, int value);
 
diff --git a/src/include/unit.h b/src/include/unit.h
index 3b574a4cb..48b4a8e52 100644
--- a/src/include/unit.h
+++ b/src/include/unit.h
@@ -339,6 +339,10 @@
 #include "player.h"
 #endif
 
+#ifndef __MISSILE_H__
+#include "missile.h"
+#endif
+
 #include "vec2i.h"
 
 /*----------------------------------------------------------------------------
@@ -676,6 +680,10 @@ unsigned    ByPlayer : PlayerMax;   /// Track unit seen by player
 		if (IsInvisibile(player)) {
 			return false;
 		}
+		// Don't attack revealers
+		if (this->Type->Revealer) {
+			return false;
+		}
 		if ((player.Type == PlayerComputer && !this->Type->PermanentCloak)
 			|| IsVisible(player) || IsVisibleOnRadar(player)) {
 			return IsAliveOnMap();
@@ -960,7 +968,7 @@ extern void DestroyAllInside(CUnit &source);
 /// Calculate some value to measure the unit's priority for AI
 extern int ThreatCalculate(const CUnit &unit, const CUnit &dest);
 /// Hit unit with damage, if destroyed give attacker the points
-extern void HitUnit(CUnit *attacker, CUnit &target, int damage);
+extern void HitUnit(CUnit *attacker, CUnit &target, int damage, const Missile *missile = NULL);
 
 /// Calculate the distance from current view point to coordinate
 extern int ViewPointDistance(const Vec2i &pos);
diff --git a/src/missile/missile.cpp b/src/missile/missile.cpp
index 63d2d1e26..945866224 100644
--- a/src/missile/missile.cpp
+++ b/src/missile/missile.cpp
@@ -682,7 +682,7 @@ static void MissileHitsGoal(const Missile &missile, CUnit &goal, int splash)
 		} else {
 			Assert(missile.SourceUnit != NULL);
 			HitUnit(missile.SourceUnit, goal,
-					CalculateDamage(*missile.SourceUnit, goal) / splash);
+					CalculateDamage(*missile.SourceUnit, goal) / splash, &missile);
 		}
 	}
 }
@@ -1091,8 +1091,8 @@ void InitMissileTypes()
 **  Constructor.
 */
 MissileType::MissileType(const std::string &ident) :
-	Ident(ident), Transparency(0),
-	DrawLevel(0), SpriteFrames(0), NumDirections(0),
+	Ident(ident), Transparency(0), DrawLevel(0), 
+	SpriteFrames(0), NumDirections(0), ChangeVariable(-1), ChangeAmount(0), ChangeMax(false),
 	CorrectSphashDamage(false), Flip(false), CanHitOwner(false), FriendlyFire(false),
 	AlwaysFire(false), Class(), NumBounces(0), StartDelay(0), Sleep(0), Speed(0),
 	Range(0), SplashFactor(0), ImpactParticle(NULL), G(NULL)
diff --git a/src/missile/script_missile.cpp b/src/missile/script_missile.cpp
index 7f7a7602a..fd0e5b55e 100644
--- a/src/missile/script_missile.cpp
+++ b/src/missile/script_missile.cpp
@@ -116,6 +116,18 @@ void MissileType::Load(lua_State *l)
 			this->FiredSound.Name = LuaToString(l, -1);
 		} else if (!strcmp(value, "ImpactSound")) {
 			this->ImpactSound.Name = LuaToString(l, -1);
+		} else if (!strcmp(value, "ChangeVariable")) {
+			const int index = UnitTypeVar.VariableNameLookup[LuaToString(l, -1)];// User variables
+			if (index == -1) {
+				fprintf(stderr, "Bad variable name '%s'\n", LuaToString(l, -1));
+				Exit(1);
+				return;
+			}
+			this->ChangeVariable = index;
+		} else if (!strcmp(value, "ChangeAmount")) {
+			this->ChangeAmount = LuaToNumber(l, -1);
+		} else if (!strcmp(value, "ChangeMax")) {
+			this->ChangeMax = LuaToBoolean(l, -1);
 		} else if (!strcmp(value, "Class")) {
 			const char *className = LuaToString(l, -1);
 			unsigned int i = 0;
diff --git a/src/stratagus/player.cpp b/src/stratagus/player.cpp
index 7523e6131..abf36e3d9 100644
--- a/src/stratagus/player.cpp
+++ b/src/stratagus/player.cpp
@@ -812,18 +812,18 @@ int CPlayer::GetUnitCount() const
 **  Gets the player resource.
 **
 **  @param resource  Resource to get.
-**  @param store     Resource type to get
+**  @param type      Storing type
 **
-**  @note Resource types: 0 - overall store, 1 - store buildings, 2 - both
+**  @note Storing types: 0 - overall store, 1 - store buildings, 2 - both
 */
 int CPlayer::GetResource(int resource, int type)
 {
 	switch (type) {
-		case 0:
+		case STORE_OVERALL:
 			return this->Resources[resource];
-		case 1:
+		case STORE_BUILDING:
 			return this->StoredResources[resource];
-		case 2:
+		case STORE_BOTH:
 			return this->Resources[resource] + this->StoredResources[resource];
 		default:
 			DebugPrint("Wrong resource type\n");
@@ -844,6 +844,9 @@ void CPlayer::ChangeResource(int resource, int value, bool store)
 		int fromStore = std::min(this->StoredResources[resource], abs(value));
 		this->StoredResources[resource] -= fromStore;
 		this->Resources[resource] -= abs(value) - fromStore;
+		if (this->Resources[resource] < 0) {
+			this->Resources[resource] = 0;
+		}
 	} else {
 		if (store && this->MaxResources[resource] != -1) {
 			this->StoredResources[resource] += std::min(value, this->MaxResources[resource] - this->StoredResources[resource]);
@@ -858,13 +861,21 @@ void CPlayer::ChangeResource(int resource, int value, bool store)
 **
 **  @param resource  Resource to change.
 **  @param value     How many of this resource.
-**  @param store     If true, sets the building store resources, else the overall resources.
+**  @param type      Resource types: 0 - overall store, 1 - store buildings, 2 - both
 */
-void CPlayer::SetResource(int resource, int value, bool store)
+void CPlayer::SetResource(int resource, int value, int type)
 {
-	if (store && this->MaxResources[resource] != -1) {
+	if (type == STORE_BOTH) {
+		if (this->MaxResources[resource] != -1) {
+			const int toStore = std::min(0, value - this->Resources[resource]);
+			this->StoredResources[resource] = std::min(toStore, this->MaxResources[resource]);
+			this->Resources[resource] = std::max(0, value - toStore);
+		} else {
+			this->Resources[resource] = value;
+		}
+	} else if (type == STORE_BUILDING && this->MaxResources[resource] != -1) {
 		this->StoredResources[resource] = std::min(value, this->MaxResources[resource]);
-	} else {
+	} else if (type == STORE_OVERALL) {
 		this->Resources[resource] = value;
 	}
 }
diff --git a/src/stratagus/script_player.cpp b/src/stratagus/script_player.cpp
index 2b8b89a71..839c1e4f2 100644
--- a/src/stratagus/script_player.cpp
+++ b/src/stratagus/script_player.cpp
@@ -840,7 +840,7 @@ static int CclSetPlayerData(lua_State *l)
 
 		const std::string res = LuaToString(l, 3);
 		const int resId = GetResourceIdByName(l, res.c_str());
-		p->SetResource(resId, LuaToNumber(l, 4), true);
+		p->SetResource(resId, LuaToNumber(l, 4), STORE_BUILDING);
 		// } else if (!strcmp(data, "UnitTypesCount")) {
 		// } else if (!strcmp(data, "AiEnabled")) {
 		// } else if (!strcmp(data, "TotalNumUnits")) {
diff --git a/src/unit/script_unittype.cpp b/src/unit/script_unittype.cpp
index c8eea988f..cb3099b62 100644
--- a/src/unit/script_unittype.cpp
+++ b/src/unit/script_unittype.cpp
@@ -1179,16 +1179,11 @@ static int CclDefineUnitType(lua_State *l)
 */
 static int CclDefineUnitStats(lua_State *l)
 {
-	const int args = lua_gettop(l);
-	int j = 0;
-
-	CUnitType *type = UnitTypeByIdent(LuaToString(l, j + 1));
+	CUnitType *type = UnitTypeByIdent(LuaToString(l, 1));
+	const int playerId = LuaToNumber(l, 2);
+	
 	Assert(type);
-	++j;
-
-	int playerId = LuaToNumber(l, j + 1);
 	Assert(playerId < PlayerMax);
-	++j;
 
 	CUnitStats *stats = &type->Stats[playerId];
 	if (!stats->Variables) {
@@ -1196,51 +1191,61 @@ static int CclDefineUnitStats(lua_State *l)
 	}
 
 	// Parse the list: (still everything could be changed!)
-	for (; j < args; ++j) {
-		const char *value = LuaToString(l, j + 1);
+	const int args = lua_rawlen(l, 3);
+	for (int j = 0; j < args; ++j) {
+		lua_rawgeti(l, 3, j + 1);
+		const char *value = LuaToString(l, -1);
+		lua_pop(l, 1);
 		++j;
 
 		if (!strcmp(value, "costs")) {
-			if (!lua_istable(l, j + 1)) {
+			lua_rawgeti(l, 3, j + 1);
+			if (!lua_istable(l, -1)) {
 				LuaError(l, "incorrect argument");
 			}
-			const int subargs = lua_rawlen(l, j + 1);
+			const int subargs = lua_rawlen(l, -1);
 
 			for (int k = 0; k < subargs; ++k) {
-				lua_rawgeti(l, j + 1, k + 1);
+				lua_rawgeti(l, 3, j + 1);
+				lua_rawgeti(l, -1, k + 1);
 				value = LuaToString(l, -1);
 				lua_pop(l, 1);
 				++k;
 				const int resId = GetResourceIdByName(l, value);
-				lua_rawgeti(l, j + 1, k + 1);
+				lua_rawgeti(l, -1, k + 1);
 				stats->Costs[resId] = LuaToNumber(l, -1);
 				lua_pop(l, 1);
+				lua_pop(l, 1);
 			}
 		} else if (!strcmp(value, "storing")) {
-			if (!lua_istable(l, j + 1)) {
+			lua_rawgeti(l, 3, j + 1);
+			if (!lua_istable(l, -1)) {
 				LuaError(l, "incorrect argument");
 			}
-			const int subargs = lua_rawlen(l, j + 1);
+			const int subargs = lua_rawlen(l, -1);
 
 			for (int k = 0; k < subargs; ++k) {
-				lua_rawgeti(l, j + 1, k + 1);
+				lua_rawgeti(l, 3, j + 1);
+				lua_rawgeti(l, -1, k + 1);
 				value = LuaToString(l, -1);
 				lua_pop(l, 1);
 				++k;
 				const int resId = GetResourceIdByName(l, value);
-				lua_rawgeti(l, j + 1, k + 1);
+				lua_rawgeti(l, -1, k + 1);
 				stats->Storing[resId] = LuaToNumber(l, -1);
 				lua_pop(l, 1);
+				lua_pop(l, 1);
 			}
 		} else {
 			int i = UnitTypeVar.VariableNameLookup[value];// User variables
 			if (i != -1) { // valid index
-				if (lua_istable(l, j + 1)) {
-					DefineVariableField(l, stats->Variables + i, j + 1);
+				lua_rawgeti(l, 3, j + 1);
+				if (lua_istable(l, -1)) {
+					DefineVariableField(l, stats->Variables + i, -1);
 				} else if (lua_isnumber(l, -1)) {
 					stats->Variables[i].Enable = 1;
-					stats->Variables[i].Value = LuaToNumber(l, j + 1);
-					stats->Variables[i].Max = LuaToNumber(l, j + 1);
+					stats->Variables[i].Value = LuaToNumber(l, -1);
+					stats->Variables[i].Max = LuaToNumber(l, -1);
 				} else { // Error
 					LuaError(l, "incorrect argument for the variable in unittype");
 				}
diff --git a/src/unit/unit.cpp b/src/unit/unit.cpp
index 02be6fb1e..c916e79cb 100644
--- a/src/unit/unit.cpp
+++ b/src/unit/unit.cpp
@@ -959,7 +959,7 @@ void UnitLost(CUnit &unit)
 				const int newMaxValue = player.MaxResources[i] - type.Stats[player.Index].Storing[i];
 
 				player.MaxResources[i] = std::max(0, newMaxValue);
-				player.SetResource(i, player.StoredResources[i], true);
+				player.SetResource(i, player.StoredResources[i], STORE_BUILDING);
 			}
 		}
 		//  Handle income improvements, look if a player loses a building
@@ -2695,8 +2695,9 @@ int ThreatCalculate(const CUnit &unit, const CUnit &dest)
 **  @param attacker    Unit that attacks.
 **  @param target      Unit that is hit.
 **  @param damage      How many damage to take.
+**  @param missile     Which missile took the damage.
 */
-void HitUnit(CUnit *attacker, CUnit &target, int damage)
+void HitUnit(CUnit *attacker, CUnit &target, int damage, const Missile *missile)
 {
 	// Can now happen by splash damage
 	// Multiple places send x/y as damage, which may be zero
@@ -2836,6 +2837,16 @@ void HitUnit(CUnit *attacker, CUnit &target, int damage)
 		type->OnHit->run();
 	}
 
+	// Increase variables
+	if (missile && missile->Type->ChangeVariable != -1) {
+		const int var = missile->Type->ChangeVariable;
+		target.Variable[var].Enable = 1;
+		target.Variable[var].Value += missile->Type->ChangeAmount;
+		if (target.Variable[var].Value > target.Variable[var].Max && missile->Type->ChangeMax) {
+			target.Variable[var].Max = target.Variable[var].Value;
+		}
+	}
+
 	// Show impact missiles
 	if (target.Variable[SHIELD_INDEX].Value > 0
 		&& !target.Type->Impact[ANIMATIONS_DEATHTYPES + 1].Name.empty()) { // shield impact
diff --git a/src/unit/unittype.cpp b/src/unit/unittype.cpp
index 652d1e8fa..0de9886cb 100644
--- a/src/unit/unittype.cpp
+++ b/src/unit/unittype.cpp
@@ -368,7 +368,7 @@ static bool SaveUnitStats(const CUnitStats &stats, const CUnitType &type, int pl
 	if (stats == type.DefaultStat) {
 		return false;
 	}
-	file.printf("DefineUnitStats(\"%s\", %d,\n  ", type.Ident.c_str(), plynr);
+	file.printf("DefineUnitStats(\"%s\", %d, {\n  ", type.Ident.c_str(), plynr);
 	for (unsigned int i = 0; i < UnitTypeVar.GetNumberVariable(); ++i) {
 		file.printf("\"%s\", {Value = %d, Max = %d, Increase = %d%s},\n  ",
 					UnitTypeVar.VariableNameLookup[i], stats.Variables[i].Value,
@@ -382,14 +382,14 @@ static bool SaveUnitStats(const CUnitStats &stats, const CUnitType &type, int pl
 		}
 		file.printf("\"%s\", %d,", DefaultResourceNames[i].c_str(), stats.Costs[i]);
 	}
-	file.printf("\"storing\", {");
+	file.printf("},\n\"storing\", {");
 	for (unsigned int i = 0; i < MaxCosts; ++i) {
 		if (i) {
 			file.printf(" ");
 		}
 		file.printf("\"%s\", %d,", DefaultResourceNames[i].c_str(), stats.Storing[i]);
 	}
-	file.printf("})\n");
+	file.printf("}})\n");
 	return true;
 }