1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 #include "Cannon.h"
4 #include "Game/TraceRay.h"
5 #include "Map/Ground.h"
6 #include "Map/MapInfo.h"
7 #include "Sim/Projectiles/Unsynced/HeatCloudProjectile.h"
8 #include "Sim/Projectiles/Unsynced/SmokeProjectile.h"
9 #include "Sim/Projectiles/WeaponProjectiles/WeaponProjectileFactory.h"
10 #include "Sim/Units/Unit.h"
11 #include "System/Sync/SyncTracer.h"
12 #include "WeaponDefHandler.h"
13 #include "System/myMath.h"
14 #include "System/FastMath.h"
15 
16 CR_BIND_DERIVED(CCannon, CWeapon, (NULL, NULL))
17 
18 CR_REG_METADATA(CCannon,(
19 	CR_MEMBER(highTrajectory),
20 	CR_MEMBER(rangeFactor),
21 	CR_MEMBER(lastDiff),
22 	CR_MEMBER(lastDir),
23 	CR_MEMBER(gravity),
24 	CR_RESERVED(32)
25 ))
26 
27 //////////////////////////////////////////////////////////////////////
28 // Construction/Destruction
29 //////////////////////////////////////////////////////////////////////
30 
CCannon(CUnit * owner,const WeaponDef * def)31 CCannon::CCannon(CUnit* owner, const WeaponDef* def)
32 	: CWeapon(owner, def)
33 	, rangeFactor(1.0f)
34 	, lastDir(-UpVector)
35 	, highTrajectory(false)
36 	, gravity(0.0f)
37 {
38 }
39 
Init()40 void CCannon::Init()
41 {
42 	gravity = (weaponDef->myGravity == 0)? mapInfo->map.gravity : -(weaponDef->myGravity);
43 	highTrajectory = (weaponDef->highTrajectory == 1);
44 
45 	CWeapon::Init();
46 }
47 
UpdateRange(float val)48 void CCannon::UpdateRange(float val)
49 {
50 	// clamp so as to not extend range if projectile
51 	// speed is too low to reach the *updated* range
52 	// note: new range can be zero (!) making range
53 	// and height factors irrelevant
54 	range = val;
55 	rangeFactor = Clamp(range / GetRange2D(0.0f, 1.0f), 0.0f, 1.0f);
56 
57 	// some magical (but working) equations
58 	// useful properties: if rangeFactor == 1, heightBoostFactor == 1
59 	// TODO find something better?
60 	if (heightBoostFactor < 0.0f && rangeFactor > 0.0f) {
61 		heightBoostFactor = (2.0f - rangeFactor) / math::sqrt(rangeFactor);
62 	}
63 }
64 
65 
Update()66 void CCannon::Update()
67 {
68 	if (targetType != Target_None) {
69 		weaponPos = owner->GetObjectSpacePos(relWeaponPos);
70 		weaponMuzzlePos = owner->GetObjectSpacePos(relWeaponMuzzlePos);
71 
72 		const float3 targetVec = targetPos - weaponPos;
73 		const float speed2D = (wantedDir = GetWantedDir(targetVec)).Length2D() * projectileSpeed;
74 
75 		predict = ((speed2D == 0.0f) ? 1.0f : (targetVec.Length2D() / speed2D));
76 	} else {
77 		predict = 0.0f;
78 	}
79 
80 	CWeapon::Update();
81 }
82 
83 
HaveFreeLineOfFire(const float3 & pos,bool userTarget,const CUnit * unit) const84 bool CCannon::HaveFreeLineOfFire(const float3& pos, bool userTarget, const CUnit* unit) const
85 {
86 	// assume we can still fire at partially submerged targets
87 	if (!weaponDef->waterweapon && TargetUnitOrPositionUnderWater(pos, unit))
88 		return false;
89 
90 	if (projectileSpeed == 0) {
91 		return true;
92 	}
93 
94 	float3 dif(pos - weaponMuzzlePos);
95 	float3 dir(GetWantedDir2(dif));
96 
97 	if (dir.SqLength() == 0) {
98 		return false;
99 	}
100 
101 	float3 flatDir(dif.x, 0, dif.z);
102 	float flatLength = flatDir.Length();
103 	if (flatLength == 0) {
104 		return true;
105 	}
106 	flatDir /= flatLength;
107 
108 	const float linear = dir.y;
109 	const float quadratic = gravity / (projectileSpeed * projectileSpeed) * 0.5f;
110 	const float groundDist = ((avoidFlags & Collision::NOGROUND) == 0)?
111 		CGround::TrajectoryGroundCol(weaponMuzzlePos, flatDir, flatLength - 10, linear, quadratic):
112 		-1.0f;
113 	const float spread = (AccuracyExperience() + SprayAngleExperience()) * 0.6f * 0.9f;
114 
115 	if (groundDist > 0.0f) {
116 		return false;
117 	}
118 
119 	//FIXME add a forcedUserTarget (a forced fire mode enabled with meta key or something) and skip the test below then
120 	if (TraceRay::TestTrajectoryCone(weaponMuzzlePos, flatDir, flatLength,
121 		dir.y, quadratic, spread, owner->allyteam, avoidFlags, owner)) {
122 		return false;
123 	}
124 
125 	return true;
126 }
127 
FireImpl(bool scriptCall)128 void CCannon::FireImpl(bool scriptCall)
129 {
130 	float3 diff = targetPos - weaponMuzzlePos;
131 	float3 dir = (diff.SqLength() > 4.0f) ? GetWantedDir(diff) : diff; // prevent vertical aim when emit-sfx firing the weapon
132 
133 	dir += (gs->randVector() * SprayAngleExperience() + SalvoErrorExperience());
134 	dir.SafeNormalize();
135 
136 	int ttl = 0;
137 	const float sqSpeed2D = dir.SqLength2D() * projectileSpeed * projectileSpeed;
138 	const int predict = math::ceil((sqSpeed2D == 0.0f) ?
139 		(-2.0f * projectileSpeed * dir.y / gravity):
140 		math::sqrt(diff.SqLength2D() / sqSpeed2D));
141 
142 	if (weaponDef->flighttime > 0) {
143 		ttl = weaponDef->flighttime;
144 	} else if (weaponDef->selfExplode) {
145 		ttl = (predict + gs->randFloat() * 2.5f - 0.5f);
146 	} else if ((weaponDef->groundBounce || weaponDef->waterBounce) && weaponDef->numBounce > 0) {
147 		ttl = (predict * (1 + weaponDef->numBounce * weaponDef->bounceRebound));
148 	} else {
149 		ttl = predict * 2;
150 	}
151 
152 	ProjectileParams params = GetProjectileParams();
153 	params.pos = weaponMuzzlePos;
154 	params.speed = dir * projectileSpeed;
155 	params.ttl = ttl;
156 	params.gravity = gravity;
157 
158 	WeaponProjectileFactory::LoadProjectile(params);
159 }
160 
SlowUpdate()161 void CCannon::SlowUpdate()
162 {
163 	if (weaponDef->highTrajectory == 2 && owner->useHighTrajectory != highTrajectory) {
164 		highTrajectory = owner->useHighTrajectory;
165 	}
166 
167 	CWeapon::SlowUpdate();
168 }
169 
AttackGround(float3 pos,bool userTarget)170 bool CCannon::AttackGround(float3 pos, bool userTarget)
171 {
172 	if (owner->UnderFirstPersonControl()) {
173 		// mostly prevents firing longer than max range using fps mode
174 		pos.y = CGround::GetHeightAboveWater(pos.x, pos.z);
175 	}
176 
177 	// NOTE: this calls back into our derived TryTarget
178 	return (CWeapon::AttackGround(pos, userTarget));
179 }
180 
GetWantedDir(const float3 & diff)181 float3 CCannon::GetWantedDir(const float3& diff)
182 {
183 	// try to cache results, sacrifice some (not much too much even for a pewee) accuracy
184 	// it saves a dozen or two expensive calculations per second when 5 guardians
185 	// are shooting at several slow- and fast-moving targets
186 	if (math::fabs(diff.x - lastDiff.x) < (SQUARE_SIZE / 4.0f) &&
187 		math::fabs(diff.y - lastDiff.y) < (SQUARE_SIZE / 4.0f) &&
188 		math::fabs(diff.z - lastDiff.z) < (SQUARE_SIZE / 4.0f)) {
189 		return lastDir;
190 	}
191 
192 	const float3 dir = GetWantedDir2(diff);
193 	lastDiff = diff;
194 	lastDir  = dir;
195 	return dir;
196 }
197 
GetWantedDir2(const float3 & diff) const198 float3 CCannon::GetWantedDir2(const float3& diff) const
199 {
200 	const float Dsq = diff.SqLength();
201 	const float DFsq = diff.SqLength2D();
202 	const float g = gravity;
203 	const float v = projectileSpeed;
204 	const float dy  = diff.y;
205 	const float dxz = math::sqrt(DFsq);
206 	float Vxz = 0.0f;
207 	float Vy  = 0.0f;
208 
209 	if (Dsq == 0.0f) {
210 		Vy = highTrajectory ? v : -v;
211 	} else {
212 		// FIXME: temporary safeguards against FP overflow
213 		// (introduced by extreme off-map unit positions; the term
214 		// DFsq * Dsq * ... * dy should never even approach 1e38)
215 		if (Dsq < 1e12f && math::fabs(dy) < 1e6f) {
216 			const float root1 = v*v*v*v + 2.0f*v*v*g*dy - g*g*DFsq;
217 
218 			if (root1 >= 0.0f) {
219 				const float root2 = 2.0f * DFsq * Dsq * (v * v + g * dy + (highTrajectory ? -1.0f : 1.0f) * math::sqrt(root1));
220 
221 				if (root2 >= 0.0f) {
222 					Vxz = math::sqrt(root2) / (2.0f * Dsq);
223 					Vy = (dxz == 0.0f || Vxz == 0.0f) ? v : (Vxz * dy / dxz  -  dxz * g / (2.0f * Vxz));
224 				}
225 			}
226 		}
227 	}
228 
229 	float3 dir = ZeroVector;
230 
231 	if (Vxz != 0.0f || Vy != 0.0f) {
232 		dir.x = diff.x;
233 		dir.z = diff.z;
234 		dir.SafeNormalize();
235 		dir *= Vxz;
236 		dir.y = Vy;
237 		dir.SafeNormalize();
238 	}
239 
240 	return dir;
241 }
242 
GetRange2D(float yDiff,float rFact) const243 float CCannon::GetRange2D(float yDiff, float rFact) const
244 {
245 	const float speedFactor = 0.7071067f; // sin pi/4 == cos pi/4
246 	const float smoothHeight = 100.0f;  // completely arbitrary
247 	const float speed2d = projectileSpeed * speedFactor; // speed in one direction in max-range case
248 	const float speed2dSq = speed2d * speed2d;
249 
250 	if (yDiff < -smoothHeight) {
251 		yDiff *= heightBoostFactor;
252 	} else if (yDiff < 0.0f) {
253 		// smooth a bit
254 		// f(0) == 1, f(smoothHeight) == heightBoostFactor
255 		yDiff *= 1.0f + (heightBoostFactor - 1.0f) * (-yDiff)/smoothHeight;
256 	}
257 
258 	const float root1 = speed2dSq + 2.0f * gravity * yDiff;
259 
260 	if (root1 < 0.0f) {
261 		return 0.0f;
262 	}
263 
264 	return (rFact * (speed2dSq + speed2d * math::sqrt(root1)) / (-gravity));
265 }
266 
GetRange2D(float yDiff) const267 float CCannon::GetRange2D(float yDiff) const
268 {
269 	return (GetRange2D(yDiff, rangeFactor));
270 }
271 
272