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