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