1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3
4 #include "StarburstProjectile.h"
5 #include "Game/Camera.h"
6 #include "Game/GameHelper.h"
7 #include "Map/Ground.h"
8 #include "Rendering/GlobalRendering.h"
9 #include "Rendering/ProjectileDrawer.h"
10 #include "Rendering/GL/VertexArray.h"
11 #include "Rendering/Textures/TextureAtlas.h"
12 #include "Sim/Projectiles/ProjectileHandler.h"
13 #include "Sim/Projectiles/Unsynced/SmokeTrailProjectile.h"
14 #include "Sim/Units/Unit.h"
15 #include "Sim/Weapons/WeaponDef.h"
16 #include "System/Sync/SyncTracer.h"
17 #include "System/Color.h"
18 #include "System/Matrix44f.h"
19 #include "System/myMath.h"
20 #include "System/TimeProfiler.h"
21
22 // the smokes life-time in frames
23 static const float SMOKE_TIME = 70.0f;
24
25 static const float TRACER_PARTS_STEP = 2.0f;
26 static const unsigned int MAX_NUM_AGEMODS = 32;
27
28 CR_BIND(CStarburstProjectile::TracerPart, )
29 CR_REG_METADATA_SUB(CStarburstProjectile, TracerPart, (
30 CR_MEMBER(pos),
31 CR_MEMBER(dir),
32 CR_MEMBER(speedf),
33 CR_MEMBER(ageMods),
34 CR_MEMBER(numAgeMods)
35 ))
36
37
38 CR_BIND_DERIVED(CStarburstProjectile, CWeaponProjectile, (ProjectileParams()))
39 CR_REG_METADATA(CStarburstProjectile, (
40 CR_SETFLAG(CF_Synced),
41 CR_MEMBER(tracking),
42 CR_MEMBER(maxGoodDif),
43 CR_MEMBER(maxSpeed),
44 CR_MEMBER(acceleration),
45 CR_MEMBER(uptime),
46 CR_MEMBER(areaOfEffect),
47 CR_MEMBER(age),
48 CR_MEMBER(oldSmoke),
49 CR_MEMBER(oldSmokeDir),
50 CR_MEMBER(drawTrail),
51 CR_MEMBER(numParts),
52 CR_MEMBER(doturn),
53 CR_MEMBER(curCallback),
54 CR_MEMBER(missileAge),
55 CR_MEMBER(distanceToTravel),
56 CR_MEMBER(aimError),
57 CR_MEMBER(curTracerPart),
58 CR_MEMBER(tracerParts)
59 ))
60
61
CStarburstProjectile(const ProjectileParams & params)62 CStarburstProjectile::CStarburstProjectile(const ProjectileParams& params): CWeaponProjectile(params)
63 , tracking(params.tracking)
64 , maxGoodDif(0.0f)
65 , maxSpeed(0.0f)
66 , acceleration(0.f)
67 , areaOfEffect(0.0f)
68 , distanceToTravel(params.maxRange)
69
70 , uptime(0)
71 , age(0)
72
73 , oldSmoke(pos)
74 , aimError(params.error)
75
76 , drawTrail(true)
77 , doturn(true)
78 , curCallback(NULL)
79
80 , numParts(0)
81 , missileAge(0)
82 , curTracerPart(0)
83 {
84 projectileType = WEAPON_STARBURST_PROJECTILE;
85
86
87 if (weaponDef != NULL) {
88 maxSpeed = weaponDef->projectilespeed;
89 areaOfEffect = weaponDef->damageAreaOfEffect;
90 uptime = weaponDef->uptime * GAME_SPEED;
91
92 if (weaponDef->flighttime == 0) {
93 ttl = std::min(3000.0f, uptime + weaponDef->range / maxSpeed + 100);
94 } else {
95 ttl = weaponDef->flighttime;
96 }
97 }
98
99 maxGoodDif = math::cos(tracking * 0.6f);
100 oldSmokeDir = dir;
101
102 const float3 camDir = (pos - camera->GetPos()).ANormalize();
103 const float camDist = (camera->GetPos().distance(pos) * 0.2f) + ((1.0f - math::fabs(camDir.dot(dir))) * 3000);
104
105 drawTrail = (camDist >= 200.0f);
106 drawRadius = maxSpeed * 8.0f;
107
108 for (unsigned int a = 0; a < NUM_TRACER_PARTS; ++a) {
109 tracerParts[a].dir = dir;
110 tracerParts[a].pos = pos;
111 tracerParts[a].speedf = speed.w;
112
113 tracerParts[a].ageMods.resize(MAX_NUM_AGEMODS, 1.0f);
114 tracerParts[a].numAgeMods = std::min(MAX_NUM_AGEMODS, static_cast<unsigned int>((speed.w + 0.6f) / TRACER_PARTS_STEP));
115 }
116 castShadow = true;
117
118 #ifdef TRACE_SYNC
119 tracefile << "[" << __FUNCTION__ << "] ";
120 tracefile << pos.x << " " << pos.y << " " << pos.z << " " << speed.x << " " << speed.y << " " << speed.z << "\n";
121 #endif
122 }
123
Detach()124 void CStarburstProjectile::Detach()
125 {
126 // SYNCED
127 if (curCallback) {
128 // this is unsynced, but it prevents some callback crash on exit
129 curCallback->drawCallbacker = NULL;
130 }
131
132 CProjectile::Detach();
133 }
134
~CStarburstProjectile()135 CStarburstProjectile::~CStarburstProjectile()
136 {
137 // UNSYNCED
138 for (unsigned int a = 0; a < NUM_TRACER_PARTS; ++a) {
139 tracerParts[a].ageMods.clear();
140 }
141 }
142
143
Collision()144 void CStarburstProjectile::Collision()
145 {
146 if (weaponDef->visuals.smokeTrail) {
147 new CSmokeTrailProjectile(owner(), pos, oldSmoke, dir, oldSmokeDir, false, true, 7, SMOKE_TIME, 0.7f, drawTrail, 0, weaponDef->visuals.texture2);
148 }
149
150 oldSmokeDir = dir;
151 CWeaponProjectile::Collision();
152 oldSmoke = pos;
153 }
154
Collision(CUnit * unit)155 void CStarburstProjectile::Collision(CUnit* unit)
156 {
157 if (weaponDef->visuals.smokeTrail) {
158 new CSmokeTrailProjectile(owner(), pos, oldSmoke, dir, oldSmokeDir, false, true, 7, SMOKE_TIME, 0.7f, drawTrail, 0, weaponDef->visuals.texture2);
159 }
160
161 oldSmokeDir = dir;
162 CWeaponProjectile::Collision(unit);
163 oldSmoke = pos;
164 }
165
Collision(CFeature * feature)166 void CStarburstProjectile::Collision(CFeature* feature)
167 {
168 if (weaponDef->visuals.smokeTrail) {
169 new CSmokeTrailProjectile(owner(), pos, oldSmoke, dir, oldSmokeDir, false, true, 7, SMOKE_TIME, 0.7f, drawTrail, 0, weaponDef->visuals.texture2);
170 }
171
172 oldSmokeDir = dir;
173 CWeaponProjectile::Collision(feature);
174 oldSmoke = pos;
175 }
176
177
Update()178 void CStarburstProjectile::Update()
179 {
180 ttl--;
181 uptime--;
182 missileAge++;
183
184 if (target != NULL && owner() != NULL && weaponDef->tracks) {
185 targetPos = target->pos;
186 CUnit* u = dynamic_cast<CUnit*>(target);
187
188 if (u != NULL) {
189 targetPos = u->GetErrorPos(owner()->allyteam, true);
190 }
191 }
192
193 if (uptime > 0) {
194 if (!luaMoveCtrl) {
195 if (speed.w < maxSpeed)
196 speed.w += weaponDef->weaponacceleration;
197
198 // do not need to update dir or speed.w here
199 CWorldObject::SetVelocity(dir * speed.w);
200 }
201 } else if (doturn && ttl > 0 && distanceToTravel > 0.0f) {
202 if (!luaMoveCtrl) {
203 float3 targetErrorVec = ((targetPos - pos).Normalize() + aimError).Normalize();
204
205 if (targetErrorVec.dot(dir) > 0.99f) {
206 dir = targetErrorVec;
207 doturn = false;
208 } else {
209 targetErrorVec = targetErrorVec - dir;
210 targetErrorVec -= dir * (targetErrorVec.dot(dir));
211 targetErrorVec.Normalize();
212
213 if (weaponDef->turnrate != 0) {
214 dir = (dir + (targetErrorVec * weaponDef->turnrate)).Normalize();
215 } else {
216 dir = (dir + (targetErrorVec * 0.06f)).Normalize();
217 }
218 }
219
220 // do not need to update dir or speed.w here
221 CWorldObject::SetVelocity(dir * speed.w);
222
223 if (distanceToTravel != MAX_PROJECTILE_RANGE) {
224 distanceToTravel -= speed.Length2D();
225 }
226 }
227 } else if (ttl > 0 && distanceToTravel > 0.0f) {
228 if (!luaMoveCtrl) {
229 if (speed.w < maxSpeed)
230 speed.w += weaponDef->weaponacceleration;
231
232 float3 targetErrorVec = (targetPos - pos).Normalize();
233
234 if (targetErrorVec.dot(dir) > maxGoodDif) {
235 dir = targetErrorVec;
236 } else {
237 targetErrorVec = targetErrorVec - dir;
238 targetErrorVec = (targetErrorVec - (dir * (targetErrorVec.dot(dir)))).SafeNormalize();
239
240 dir = (dir + (targetErrorVec * tracking)).SafeNormalize();
241 }
242
243 // do not need to update dir or speed.w here
244 CWorldObject::SetVelocity(dir * speed.w);
245
246 if (distanceToTravel != MAX_PROJECTILE_RANGE) {
247 distanceToTravel -= speed.Length2D();
248 }
249 }
250 } else {
251 if (!luaMoveCtrl) {
252 // changes dir and speed.w, must keep speed-vector in sync
253 SetDirectionAndSpeed((dir + (UpVector * mygravity)).Normalize(), speed.w - mygravity);
254 }
255 }
256
257 if (!luaMoveCtrl) {
258 SetPosition(pos + speed);
259 }
260
261 if (ttl > 0) {
262 explGenHandler->GenExplosion(cegID, pos, dir, ttl, areaOfEffect, 0.0f, NULL, NULL);
263 }
264
265
266 {
267 const unsigned int newTracerPart = (curTracerPart + 1) % NUM_TRACER_PARTS;
268
269 curTracerPart = newTracerPart;
270 TracerPart* tracerPart = &tracerParts[curTracerPart];
271 tracerPart->pos = pos;
272 tracerPart->dir = dir;
273 tracerPart->speedf = speed.w;
274
275 unsigned int newsize = 0;
276
277 for (float aa = 0; aa < speed.w + 0.6f && newsize < MAX_NUM_AGEMODS; aa += TRACER_PARTS_STEP, ++newsize) {
278 const float ageMod = (missileAge < 20) ? 1.0f : (0.6f + (rand() * 0.8f) / RAND_MAX);
279 tracerPart->ageMods[newsize] = ageMod;
280 }
281
282 if (tracerPart->numAgeMods != newsize) {
283 tracerPart->numAgeMods = newsize;
284 }
285 }
286
287 age++;
288 numParts++;
289
290 if (weaponDef->visuals.smokeTrail && !(age & 7)) {
291 if (curCallback != NULL) {
292 curCallback->drawCallbacker = NULL;
293 }
294
295 curCallback = new CSmokeTrailProjectile(
296 owner(),
297 pos,
298 oldSmoke,
299 dir,
300 oldSmokeDir,
301 age == 8,
302 false,
303 7,
304 SMOKE_TIME,
305 0.7f,
306 drawTrail,
307 this,
308 weaponDef->visuals.texture2
309 );
310
311 oldSmoke = pos;
312 oldSmokeDir = dir;
313 numParts = 0;
314 useAirLos = curCallback->useAirLos;
315
316 if (!drawTrail) {
317 const float3 camDir = (pos - camera->GetPos()).ANormalize();
318 const float camDist = (camera->GetPos().distance(pos) * 0.2f + (1 - math::fabs(camDir.dot(dir))) * 3000);
319
320 drawTrail = (camDist > 300.0f);
321 }
322 }
323
324 UpdateInterception();
325 }
326
Draw()327 void CStarburstProjectile::Draw()
328 {
329 inArray = true;
330
331 if (weaponDef->visuals.smokeTrail) {
332 const int curNumParts = numParts;
333
334 va->EnlargeArrays(4 + (4 * curNumParts), 0, VA_SIZE_TC);
335
336 const float age2 = (age & 7) + globalRendering->timeOffset;
337 const float color = 0.7f;
338
339 if (drawTrail) {
340 // draw the trail as a single quad
341 const float3 dif1 = (drawPos - camera->GetPos()).ANormalize();
342 const float3 dir1 = (dif1.cross(dir)).ANormalize();
343 const float3 dif2 = (oldSmoke - camera->GetPos()).ANormalize();
344 const float3 dir2 = (dif2.cross(oldSmokeDir)).ANormalize();
345
346 const float a1 =
347 ((1.0f - (0.0f / SMOKE_TIME))) *
348 (0.7f + math::fabs(dif1.dot(dir)));
349 const float a2 =
350 (age < 8)? 0.0f:
351 ((1.0f - (age2 / SMOKE_TIME))) *
352 (0.7f + math::fabs(dif2.dot(oldSmokeDir)));
353 const float alpha1 = Clamp(a1, 0.0f, 1.0f);
354 const float alpha2 = Clamp(a2, 0.0f, 1.0f);
355
356 const float size1 = 1.0f;
357 const float size2 = (1.0f + age2 * (1.0f / SMOKE_TIME) * 7.0f);
358
359 const float txs =
360 weaponDef->visuals.texture2->xend -
361 (weaponDef->visuals.texture2->xend - weaponDef->visuals.texture2->xstart) *
362 (age2 / 8.0f);
363
364 SColor col (color * alpha1, color * alpha1, color * alpha1, alpha1);
365 SColor col2(color * alpha2, color * alpha2, color * alpha2, alpha2);
366
367 va->AddVertexQTC(drawPos - dir1 * size1, txs, weaponDef->visuals.texture2->ystart, col);
368 va->AddVertexQTC(drawPos + dir1 * size1, txs, weaponDef->visuals.texture2->yend, col);
369 va->AddVertexQTC(oldSmoke + dir2 * size2, weaponDef->visuals.texture2->xend, weaponDef->visuals.texture2->yend, col2);
370 va->AddVertexQTC(oldSmoke - dir2 * size2, weaponDef->visuals.texture2->xend, weaponDef->visuals.texture2->ystart, col2);
371 } else {
372 // draw the trail as particles
373 const float dist = pos.distance(oldSmoke);
374 const float3 dirpos1 = pos - dir * dist * 0.33f;
375 const float3 dirpos2 = oldSmoke + oldSmokeDir * dist * 0.33f;
376 const SColor col(color, color, color);
377
378 for (int a = 0; a < curNumParts; ++a) {
379 // CAUTION: loop count must match EnlargeArrays above
380 const float size = 1 + (a * (1.0f / SMOKE_TIME) * 7.0f);
381 const float3 pos1 = CalcBeizer((float)a / curNumParts, pos, dirpos1, dirpos2, oldSmoke);
382
383 #define st projectileDrawer->smoketex[0]
384 va->AddVertexQTC(pos1 + ( camera->up + camera->right) * size, st->xstart, st->ystart, col);
385 va->AddVertexQTC(pos1 + ( camera->up - camera->right) * size, st->xend, st->ystart, col);
386 va->AddVertexQTC(pos1 + (-camera->up - camera->right) * size, st->xend, st->ystart, col);
387 va->AddVertexQTC(pos1 + (-camera->up + camera->right) * size, st->xstart, st->ystart, col);
388 #undef st
389 }
390 }
391 }
392
393 DrawCallback();
394 }
395
DrawCallback()396 void CStarburstProjectile::DrawCallback()
397 {
398 inArray = true;
399
400 unsigned char col[4];
401
402 unsigned int part = curTracerPart;
403
404 for (unsigned int a = 0; a < NUM_TRACER_PARTS; ++a) {
405 const TracerPart* tracerPart = &tracerParts[part];
406 const float3& opos = tracerPart->pos;
407 const float3& odir = tracerPart->dir;
408 const float ospeed = tracerPart->speedf;
409 float aa = 0;
410
411 unsigned int numAgeMods = tracerPart->numAgeMods;
412
413 for (int num = 0; num < numAgeMods; aa += TRACER_PARTS_STEP, ++num) {
414 const float ageMod = tracerPart->ageMods[num];
415 const float age2 = (a + (aa / (ospeed + 0.01f))) * 0.2f;
416 const float3 interPos = opos - (odir * ((a * 0.5f) + aa));
417
418 col[3] = 1;
419
420 if (missileAge < 20) {
421 const float alpha = std::max(0.0f, (1.0f - age2) * (1.0f - age2));
422 col[0] = (255 * alpha);
423 col[1] = (200 * alpha);
424 col[2] = (150 * alpha);
425 } else {
426 const float alpha = std::max(0.0f, ((1.0f - age2) * std::max(0.0f, 1.0f - age2)));
427 col[0] = (255 * alpha);
428 col[1] = (200 * alpha);
429 col[2] = (150 * alpha);
430 }
431
432 const float drawsize = 1.0f + age2 * 0.8f * ageMod * 7;
433
434 #define wt3 weaponDef->visuals.texture3
435 va->AddVertexTC(interPos - camera->right * drawsize - camera->up * drawsize, wt3->xstart, wt3->ystart, col);
436 va->AddVertexTC(interPos + camera->right * drawsize - camera->up * drawsize, wt3->xend, wt3->ystart, col);
437 va->AddVertexTC(interPos + camera->right * drawsize + camera->up * drawsize, wt3->xend, wt3->yend, col);
438 va->AddVertexTC(interPos - camera->right * drawsize + camera->up * drawsize, wt3->xstart, wt3->yend, col);
439 #undef wt3
440 }
441
442 // unsigned, so LHS will wrap around to UINT_MAX
443 part = std::min(part - 1, NUM_TRACER_PARTS - 1);
444 }
445
446 // draw the engine flare
447 col[0] = 255;
448 col[1] = 180;
449 col[2] = 180;
450 col[3] = 1;
451
452 const float fsize = 25.0f;
453
454 #define wt1 weaponDef->visuals.texture1
455 va->AddVertexTC(drawPos - camera->right * fsize - camera->up * fsize, wt1->xstart, wt1->ystart, col);
456 va->AddVertexTC(drawPos + camera->right * fsize - camera->up * fsize, wt1->xend, wt1->ystart, col);
457 va->AddVertexTC(drawPos + camera->right * fsize + camera->up * fsize, wt1->xend, wt1->yend, col);
458 va->AddVertexTC(drawPos - camera->right * fsize + camera->up * fsize, wt1->xstart, wt1->yend, col);
459 #undef wt1
460 }
461
ShieldRepulse(CPlasmaRepulser * shield,float3 shieldPos,float shieldForce,float shieldMaxSpeed)462 int CStarburstProjectile::ShieldRepulse(CPlasmaRepulser* shield, float3 shieldPos, float shieldForce, float shieldMaxSpeed)
463 {
464 const float3 rdir = (pos - shieldPos).Normalize();
465
466 if (ttl > 0) {
467 float3 dif2 = rdir - dir;
468
469 // steer away twice as fast as we can steer toward target
470 const float tracking = std::max(shieldForce * 0.05f, weaponDef->turnrate * 2.0f);
471
472 if (dif2.Length() < tracking) {
473 dir = rdir;
474 } else {
475 dif2 = (dif2 - (dir * (dif2.dot(dir)))).Normalize();
476 dir = (dir + (dif2 * tracking)).Normalize();
477 }
478
479 return 2;
480 }
481
482 return 0;
483 }
484