1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include "Game/GameHelper.h"
4 #include "Rendering/Colors.h"
5 #include "Rendering/GL/VertexArray.h"
6 #include "Sim/Projectiles/ProjectileHandler.h"
7 #include "Sim/Projectiles/ProjectileParams.h"
8 #include "Sim/Projectiles/WeaponProjectiles/WeaponProjectile.h"
9 #include "Sim/Weapons/WeaponDefHandler.h"
10 #include "Sim/Features/Feature.h"
11 #include "Sim/Units/Unit.h"
12 #include "Sim/Misc/InterceptHandler.h"
13 #include "Sim/Misc/QuadField.h"
14 #include "Map/Ground.h"
15 #include "System/Matrix44f.h"
16
17 CR_BIND_DERIVED(CWeaponProjectile, CProjectile, )
18
19 CR_REG_METADATA(CWeaponProjectile,(
20 CR_SETFLAG(CF_Synced),
21 CR_MEMBER(targeted),
22 CR_IGNORED(weaponDef), //PostLoad
23 CR_MEMBER(target),
24 CR_MEMBER(targetPos),
25 CR_MEMBER(startPos),
26 CR_MEMBER(ttl),
27 CR_MEMBER(bounces),
28 CR_MEMBER(weaponDefID),
29 CR_POSTLOAD(PostLoad)
30 ))
31
32
33
CWeaponProjectile()34 CWeaponProjectile::CWeaponProjectile(): CProjectile()
35 , weaponDef(NULL)
36 , target(NULL)
37
38 , weaponDefID(0)
39
40 , ttl(0)
41 , bounces(0)
42
43 , targeted(false)
44 {
45 }
46
CWeaponProjectile(const ProjectileParams & params)47 CWeaponProjectile::CWeaponProjectile(const ProjectileParams& params)
48 : CProjectile(params.pos, params.speed, params.owner, true, true, false, (params.weaponDef)->IsHitScanWeapon())
49
50 , weaponDef(params.weaponDef)
51 , target(params.target)
52
53 , weaponDefID(-1u)
54
55 , ttl(params.ttl)
56 , bounces(0)
57
58 , targeted(false)
59
60 , startPos(params.pos)
61 , targetPos(params.end)
62 {
63 projectileType = WEAPON_BASE_PROJECTILE;
64
65 if (weaponDef->IsHitScanWeapon()) {
66 // the else-case (default) is handled in CProjectile::Init
67 //
68 // ray projectiles must all set this to false because their collision
69 // detection is handled by the weapons firing them, ProjectileHandler
70 // will skip any tests for these
71 checkCol = false;
72 // type has not yet been set by derived ctor's at this point
73 // useAirLos = (projectileType != WEAPON_LIGHTNING_PROJECTILE);
74 useAirLos = true;
75
76 // NOTE:
77 // {BeamLaser, Lightning}Projectile's do NOT actually move (their
78 // speed is never added to pos) and never alter their speed either
79 // they additionally override our ::Update (so CProjectile::Update
80 // is also never called) which means assigning speed a non-zerovec
81 // value should have no side-effects
82 SetPosition(startPos);
83 SetVelocityAndSpeed(targetPos - startPos);
84
85 // ProjectileDrawer vis-culls by pos == startPos, but we
86 // want to see the beam even if camera is near targetPos
87 // --> use full distance for drawRadius
88 SetRadiusAndHeight((targetPos - startPos).Length(), 0.0f);
89 }
90
91 collisionFlags = weaponDef->collisionFlags;
92 weaponDefID = params.weaponDef->id;
93 alwaysVisible = weaponDef->visuals.alwaysVisible;
94 ignoreWater = weaponDef->waterweapon;
95
96 CSolidObject* so = NULL;
97 CWeaponProjectile* po = NULL;
98
99 if ((so = dynamic_cast<CSolidObject*>(target)) != NULL) {
100 AddDeathDependence(so, DEPENDENCE_WEAPONTARGET);
101 }
102 if ((po = dynamic_cast<CWeaponProjectile*>(target)) != NULL) {
103 po->SetBeingIntercepted(po->IsBeingIntercepted() || weaponDef->interceptSolo);
104 AddDeathDependence(po, DEPENDENCE_INTERCEPTTARGET);
105 }
106
107 if (params.model != NULL) {
108 model = params.model;
109 } else {
110 model = weaponDef->LoadModel();
111 }
112
113 if (params.owner == NULL) {
114 // the else-case (default) is handled in CProjectile::Init
115 ownerID = params.ownerID;
116 teamID = params.teamID;
117 }
118
119 if (params.cegID != -1u) {
120 cegID = params.cegID;
121 } else {
122 cegID = weaponDef->ptrailExplosionGeneratorID;
123 }
124
125 // must happen after setting position and velocity
126 projectileHandler->AddProjectile(this);
127 quadField->AddProjectile(this);
128
129 ASSERT_SYNCED(id);
130
131 if (weaponDef->interceptedByShieldType) {
132 // this needs a valid projectile id set
133 assert(id >= 0);
134 interceptHandler.AddShieldInterceptableProjectile(this);
135 }
136
137 if (weaponDef->targetable) {
138 interceptHandler.AddInterceptTarget(this, targetPos);
139 }
140 }
141
142
143
Explode(CUnit * hitUnit,CFeature * hitFeature,float3 impactPos,float3 impactDir)144 void CWeaponProjectile::Explode(
145 CUnit* hitUnit,
146 CFeature* hitFeature,
147 float3 impactPos,
148 float3 impactDir
149 ) {
150 const DamageArray& damageArray = CWeaponDefHandler::DynamicDamages(weaponDef, startPos, impactPos);
151 const CGameHelper::ExplosionParams params = {
152 impactPos,
153 impactDir.SafeNormalize(),
154 damageArray,
155 weaponDef,
156 owner(),
157 hitUnit,
158 hitFeature,
159 weaponDef->craterAreaOfEffect,
160 weaponDef->damageAreaOfEffect,
161 weaponDef->edgeEffectiveness,
162 weaponDef->explosionSpeed,
163 weaponDef->noExplode? 0.3f: 1.0f, // gfxMod
164 weaponDef->impactOnly,
165 weaponDef->noExplode || weaponDef->noSelfDamage, // ignoreOwner
166 true, // damgeGround
167 static_cast<unsigned int>(id)
168 };
169
170 helper->Explosion(params);
171
172 if (!weaponDef->noExplode || TraveledRange()) {
173 // remove ourselves from the simulation (otherwise
174 // keep traveling and generating more explosions)
175 CProjectile::Collision();
176 }
177 }
178
Collision()179 void CWeaponProjectile::Collision()
180 {
181 Collision((CFeature*) NULL);
182 }
183
Collision(CFeature * feature)184 void CWeaponProjectile::Collision(CFeature* feature)
185 {
186 float3 impactPos = pos;
187 float3 impactDir = speed;
188
189 if (feature != NULL) {
190 if (hitscan) {
191 impactPos = feature->pos;
192 impactDir = targetPos - startPos;
193 }
194
195 if (gs->randFloat() < weaponDef->fireStarter) {
196 feature->StartFire();
197 }
198 } else {
199 if (hitscan) {
200 impactPos = targetPos;
201 impactDir = targetPos - startPos;
202 }
203 }
204
205 Explode(NULL, feature, impactPos, impactDir);
206 }
207
Collision(CUnit * unit)208 void CWeaponProjectile::Collision(CUnit* unit)
209 {
210 float3 impactPos = pos;
211 float3 impactDir = speed;
212
213 if (unit != NULL) {
214 if (hitscan) {
215 impactPos = unit->pos;
216 impactDir = targetPos - startPos;
217 }
218 } else {
219 assert(false);
220 }
221
222 Explode(unit, NULL, impactPos, impactDir);
223 }
224
Update()225 void CWeaponProjectile::Update()
226 {
227 CProjectile::Update();
228 UpdateGroundBounce();
229 UpdateInterception();
230 }
231
232
UpdateInterception()233 void CWeaponProjectile::UpdateInterception()
234 {
235 if (target == NULL)
236 return;
237
238 CWeaponProjectile* po = dynamic_cast<CWeaponProjectile*>(target);
239
240 if (po == NULL)
241 return;
242
243 // we are the interceptor, point us toward the interceptee pos each frame
244 // (normally not needed, subclasses handle it directly in their Update()'s
245 // *until* our owner dies)
246 if (owner() == NULL) {
247 targetPos = po->pos + po->speed;
248 }
249
250 if (hitscan) {
251 if (ClosestPointOnLine(startPos, targetPos, po->pos).SqDistance(po->pos) < Square(weaponDef->collisionSize)) {
252 po->Collision();
253 Collision();
254 }
255 } else {
256 // FIXME: if (pos.SqDistance(po->pos) < Square(weaponDef->collisionSize)) {
257 if (pos.SqDistance(po->pos) < Square(weaponDef->damageAreaOfEffect)) {
258 po->Collision();
259 Collision();
260 }
261 }
262 }
263
264
UpdateGroundBounce()265 void CWeaponProjectile::UpdateGroundBounce()
266 {
267 // projectile is not allowed to bounce on either surface
268 if (!weaponDef->groundBounce && !weaponDef->waterBounce)
269 return;
270 // max bounce already reached?
271 if ((bounces + 1) > weaponDef->numBounce)
272 return;
273 if (luaMoveCtrl)
274 return;
275 if (ttl <= 0)
276 return;
277
278 // water or ground bounce?
279 float3 normal;
280 bool bounced = false;
281 const float distWaterHit = (pos.y > 0.0f && speed.y < 0.0f) ? (pos.y / -speed.y) : -1.0f;
282 const bool intersectWater = (distWaterHit >= 0.0f) && (distWaterHit <= 1.0f);
283 if (intersectWater && weaponDef->waterBounce) {
284 pos += speed * distWaterHit;
285 pos.y = 0.5f;
286 normal = CGround::GetNormalAboveWater(pos.x, pos.z);
287 bounced = true;
288 } else {
289 const float distGroundHit = CGround::LineGroundCol(pos, pos + speed); //TODO use traj one for traj weapons?
290 const bool intersectGround = (distGroundHit >= 0.0f);
291 if (intersectGround && weaponDef->groundBounce) {
292 static const float dontTouchSurface = 0.99f;
293 pos += dir * distGroundHit * dontTouchSurface;
294 normal = CGround::GetNormal(pos.x, pos.z);
295 bounced = true;
296 }
297 }
298
299 if (!bounced)
300 return;
301
302 // spawn CEG before bouncing, otherwise we might be too
303 // far up in the air if it has the (under)water flag set
304 explGenHandler->GenExplosion(weaponDef->bounceExplosionGeneratorID, pos, normal, speed.w, 1.0f, 1.0f, owner(), NULL);
305
306 ++bounces;
307 const float dot = math::fabs(speed.dot(normal));
308 CWorldObject::SetVelocity(speed - (speed + normal * dot) * (1 - weaponDef->bounceSlip ));
309 CWorldObject::SetVelocity( speed + normal * dot * (1 + weaponDef->bounceRebound));
310 SetVelocityAndSpeed(speed);
311 }
312
313
314
TraveledRange() const315 bool CWeaponProjectile::TraveledRange() const
316 {
317 return ((pos - startPos).SqLength() > (weaponDef->range * weaponDef->range));
318 }
319
320
321
DrawOnMinimap(CVertexArray & lines,CVertexArray & points)322 void CWeaponProjectile::DrawOnMinimap(CVertexArray& lines, CVertexArray& points)
323 {
324 points.AddVertexQC(pos, color4::yellow);
325 }
326
DependentDied(CObject * o)327 void CWeaponProjectile::DependentDied(CObject* o)
328 {
329 if (o == target) {
330 target = NULL;
331 }
332 }
333
PostLoad()334 void CWeaponProjectile::PostLoad()
335 {
336 weaponDef = weaponDefHandler->GetWeaponDefByID(weaponDefID);
337 model = weaponDef->LoadModel();
338 }
339