1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include <iostream>
4 #include <stdexcept>
5 #include <cassert>
6 #include <boost/cstdint.hpp>
7
8 #include "ExplosionGenerator.h"
9 #include "ExpGenSpawner.h" //!!
10 #include "Game/Camera.h"
11 #include "Game/GlobalUnsynced.h"
12 #include "Lua/LuaParser.h"
13 #include "Map/Ground.h"
14 #include "Rendering/GroundFlash.h"
15 #include "Rendering/ProjectileDrawer.h"
16 #include "Rendering/Textures/ColorMap.h"
17 #include "Rendering/Textures/TextureAtlas.h"
18 #include "Sim/Projectiles/ProjectileHandler.h"
19 #include "Sim/Projectiles/Unsynced/BubbleProjectile.h"
20 #include "Sim/Projectiles/Unsynced/DirtProjectile.h"
21 #include "Sim/Projectiles/Unsynced/ExploSpikeProjectile.h"
22 #include "Sim/Projectiles/Unsynced/HeatCloudProjectile.h"
23 #include "Sim/Projectiles/Unsynced/SmokeProjectile2.h"
24 #include "Sim/Projectiles/Unsynced/SpherePartProjectile.h"
25 #include "Sim/Projectiles/Unsynced/WakeProjectile.h"
26 #include "Sim/Projectiles/Unsynced/WreckProjectile.h"
27
28 #include "System/creg/STL_Map.h"
29 #include "System/Config/ConfigHandler.h"
30 #include "System/FileSystem/FileSystemInitializer.h"
31 #include "System/Log/DefaultFilter.h"
32 #include "System/Log/ILog.h"
33 #include "System/Exceptions.h"
34 #include "System/creg/VarTypes.h"
35 #include "System/FileSystem/ArchiveScanner.h"
36 #include "System/FileSystem/FileHandler.h"
37 #include "System/FileSystem/VFSHandler.h"
38 #include "System/Util.h"
39
40
41 CR_BIND_DERIVED_INTERFACE(CExpGenSpawnable, CWorldObject)
42 CR_REG_METADATA(CExpGenSpawnable, )
43
44 CR_BIND_INTERFACE(IExplosionGenerator)
45 CR_REG_METADATA(IExplosionGenerator, (
46 CR_MEMBER(generatorID)
47 ))
48
49 CR_BIND_DERIVED(CStdExplosionGenerator, IExplosionGenerator, )
50
51 CR_BIND(CCustomExplosionGenerator::ProjectileSpawnInfo, )
52 CR_REG_METADATA_SUB(CCustomExplosionGenerator, ProjectileSpawnInfo, (
53 //CR_MEMBER(projectileClass), FIXME is pointer
54 CR_MEMBER(code),
55 CR_MEMBER(count),
56 CR_MEMBER(flags)
57 ))
58
59 CR_BIND(CCustomExplosionGenerator::GroundFlashInfo, )
60 CR_REG_METADATA_SUB(CCustomExplosionGenerator, GroundFlashInfo, (
61 CR_MEMBER(flashSize),
62 CR_MEMBER(flashAlpha),
63 CR_MEMBER(circleGrowth),
64 CR_MEMBER(circleAlpha),
65 CR_MEMBER(ttl),
66 CR_MEMBER(flags),
67 CR_MEMBER(color)
68 ))
69
70 CR_BIND(CCustomExplosionGenerator::ExpGenParams, )
71 CR_REG_METADATA_SUB(CCustomExplosionGenerator, ExpGenParams, (
72 CR_MEMBER(projectiles),
73 CR_MEMBER(groundFlash),
74 CR_MEMBER(useDefaultExplosions)
75 ))
76
77 CR_BIND_DERIVED(CCustomExplosionGenerator, CStdExplosionGenerator, )
78 CR_REG_METADATA(CCustomExplosionGenerator, (
79 CR_MEMBER(expGenParams)
80 ))
81
82
83 CExplosionGeneratorHandler* explGenHandler = NULL;
84
CExpGenSpawnable()85 CExpGenSpawnable::CExpGenSpawnable(): CWorldObject() {}
CExpGenSpawnable(const float3 & pos,const float3 & spd)86 CExpGenSpawnable::CExpGenSpawnable(const float3& pos, const float3& spd): CWorldObject(pos, spd) {}
87
88
89
GetFlagsFromTable(const LuaTable & table)90 unsigned int CCustomExplosionGenerator::GetFlagsFromTable(const LuaTable& table)
91 {
92 unsigned int flags = 0;
93
94 if (table.GetBool("ground", false)) { flags |= CCustomExplosionGenerator::SPW_GROUND; }
95 if (table.GetBool("water", false)) { flags |= CCustomExplosionGenerator::SPW_WATER; }
96 if (table.GetBool("air", false)) { flags |= CCustomExplosionGenerator::SPW_AIR; }
97 if (table.GetBool("underwater", false)) { flags |= CCustomExplosionGenerator::SPW_UNDERWATER; }
98 if (table.GetBool("unit", false)) { flags |= CCustomExplosionGenerator::SPW_UNIT; }
99 if (table.GetBool("nounit", false)) { flags |= CCustomExplosionGenerator::SPW_NO_UNIT; }
100
101 return flags;
102 }
103
GetFlagsFromHeight(float height,float groundHeight)104 unsigned int CCustomExplosionGenerator::GetFlagsFromHeight(float height, float groundHeight)
105 {
106 unsigned int flags = 0;
107
108 const float waterDist = std::abs(height);
109 const float altitude = height - groundHeight;
110
111 // note: ranges do not overlap, although code in
112 // *ExplosionGenerator::Explosion assumes they can
113 if (altitude < -1.0f) {
114 /* underground! don't spawn CEG! */
115 } else
116 if (height >= 5.0f) { // above water
117 if (altitude >= 20.0f) {
118 flags |= CCustomExplosionGenerator::SPW_AIR; // air
119 } else {
120 flags |= CCustomExplosionGenerator::SPW_GROUND; // ground
121 }
122 } else
123 if (waterDist < 5.0f) { // water surface
124 if (groundHeight > -2.0f) {
125 flags |= CCustomExplosionGenerator::SPW_GROUND; // shallow water (use ground fx)
126 } else {
127 flags |= CCustomExplosionGenerator::SPW_WATER; // water (surface)
128 }
129 } else
130 /*if (height <= -5.0f) */ { // under water
131 flags |= CCustomExplosionGenerator::SPW_UNDERWATER; // underwater
132 }
133
134 return flags;
135 }
136
137
138
Load(const LuaTable & aliasTable)139 void ClassAliasList::Load(const LuaTable& aliasTable)
140 {
141 map<string, string> aliasList;
142 aliasTable.GetMap(aliasList);
143 aliases.insert(aliasList.begin(), aliasList.end());
144 }
145
GetClass(const string & name) const146 creg::Class* ClassAliasList::GetClass(const string& name) const
147 {
148 string n = name;
149
150 for (;;) {
151 map<string, string>::const_iterator i = aliases.find(n);
152 if (i == aliases.end()) {
153 break;
154 }
155 n = i->second;
156 }
157
158 creg::Class* cls = creg::System::GetClass(n);
159
160 if (cls == NULL) {
161 LOG_L(L_WARNING, "[%s] name \"%s\" does not match any ExplosionGenerator class (forgot the \"%s\" prefix?)", __FUNCTION__, name.c_str(), CEG_PREFIX_STRING);
162 }
163
164 return cls;
165 }
166
FindAlias(const string & className) const167 string ClassAliasList::FindAlias(const string& className) const
168 {
169 for (map<string, string>::const_iterator i = aliases.begin(); i != aliases.end(); ++i) {
170 if (i->second == className) {
171 return i->first;
172 }
173 }
174
175 return className;
176 }
177
178
179
180
181
182
CExplosionGeneratorHandler()183 CExplosionGeneratorHandler::CExplosionGeneratorHandler()
184 {
185 explosionGenerators.reserve(32);
186 explosionGenerators.push_back(new CStdExplosionGenerator()); // id=0
187 explosionGenerators[0]->SetGeneratorID(EXPGEN_ID_STANDARD);
188
189 exploParser = NULL;
190 aliasParser = NULL;
191 explTblRoot = NULL;
192
193 ParseExplosionTables();
194 }
195
~CExplosionGeneratorHandler()196 CExplosionGeneratorHandler::~CExplosionGeneratorHandler()
197 {
198 delete exploParser; exploParser = NULL;
199 delete aliasParser; aliasParser = NULL;
200 delete explTblRoot; explTblRoot = NULL;
201
202 delete explosionGenerators[0];
203
204 for (unsigned int n = 1; n < explosionGenerators.size(); n++) {
205 creg::Class* cls = explosionGenerators[n]->GetClass();
206 cls->DeleteInstance(explosionGenerators[n]);
207 }
208
209 explosionGenerators.clear();
210
211 expGenTagIdentMap.clear();
212 expGenIdentTagMap.clear();
213 }
214
ParseExplosionTables()215 void CExplosionGeneratorHandler::ParseExplosionTables() {
216 delete exploParser;
217 delete aliasParser;
218 delete explTblRoot;
219
220 exploParser = new LuaParser("gamedata/explosions.lua", SPRING_VFS_MOD_BASE, SPRING_VFS_ZIP);
221 aliasParser = new LuaParser("gamedata/explosion_alias.lua", SPRING_VFS_MOD_BASE, SPRING_VFS_ZIP);
222 explTblRoot = NULL;
223
224 if (!aliasParser->Execute()) {
225 LOG_L(L_ERROR, "Failed to parse explosion aliases: %s",
226 aliasParser->GetErrorLog().c_str());
227 } else {
228 const LuaTable& aliasRoot = aliasParser->GetRoot();
229
230 projectileClasses.Clear();
231 projectileClasses.Load(aliasRoot.SubTable("projectiles"));
232 generatorClasses.Clear();
233 generatorClasses.Load(aliasRoot.SubTable("generators"));
234 }
235
236 if (!exploParser->Execute()) {
237 LOG_L(L_ERROR, "Failed to parse explosions: %s", exploParser->GetErrorLog().c_str());
238 } else {
239 explTblRoot = new LuaTable(exploParser->GetRoot());
240 }
241 }
242
ReloadGenerators(const std::string & tag)243 void CExplosionGeneratorHandler::ReloadGenerators(const std::string& tag) {
244 // re-parse the projectile and generator tables
245 ParseExplosionTables();
246
247 const char* preFmt = "[%s][generatorID=%u] reloading CEG \"%s\"";
248 const char* pstFmt = "[%s][generatorID=%u] failed to reload CEG \"%s\"";
249
250 // NOTE:
251 // maps store tags inclusive of CEG_PREFIX_STRING
252 // but the Lua subtables that define each CEG are
253 // only indexed by tag postfix
254 if (tag.empty()) {
255 for (unsigned int n = 1; n < explosionGenerators.size(); n++) {
256 IExplosionGenerator* eg = explosionGenerators[n];
257
258 // standard EG's (empty postfix) do not need to be reloaded
259 if (expGenIdentTagMap.find(n) == expGenIdentTagMap.end())
260 continue;
261
262 assert(eg->GetGeneratorID() == n);
263 LOG(preFmt, __FUNCTION__, n, expGenIdentTagMap[n].c_str());
264
265 if (!eg->Reload(this, expGenIdentTagMap[n].substr(7))) {
266 LOG_L(L_WARNING, pstFmt, __FUNCTION__, n, expGenIdentTagMap[n].c_str());
267 }
268 }
269 } else {
270 const TagIdentMapConstIt it = expGenTagIdentMap.find(tag);
271
272 if (it == expGenTagIdentMap.end()) {
273 LOG_L(L_WARNING, "[%s] no CEG named \"%s\" (forgot the \"%s\" prefix?)", __FUNCTION__, tag.c_str(), CEG_PREFIX_STRING);
274 return;
275 }
276
277 assert(explosionGenerators[it->second]->GetGeneratorID() == it->second);
278 LOG(preFmt, __FUNCTION__, it->second, tag.c_str());
279
280 if (!explosionGenerators[it->second]->Reload(this, tag.substr(7))) {
281 LOG_L(L_WARNING, pstFmt, __FUNCTION__, it->second, tag.c_str());
282 }
283 }
284 }
285
286
287
LoadGeneratorID(const std::string & tag)288 unsigned int CExplosionGeneratorHandler::LoadGeneratorID(const std::string& tag)
289 {
290 IExplosionGenerator* eg = LoadGenerator(tag);
291
292 if (eg == NULL)
293 return EXPGEN_ID_INVALID;
294
295 return (eg->GetGeneratorID());
296 }
297
298 // creates either a standard or a custom explosion generator instance
299 // NOTE:
300 // can be called recursively for custom instances (LoadGenerator ->
301 // Load -> ParseExplosionCode -> LoadGenerator -> ...), generators
302 // must NOT be overwritten
LoadGenerator(const string & tag)303 IExplosionGenerator* CExplosionGeneratorHandler::LoadGenerator(const string& tag)
304 {
305 const TagIdentMapConstIt it = expGenTagIdentMap.find(tag);
306
307 if (it != expGenTagIdentMap.end())
308 return explosionGenerators[it->second];
309
310 // tag is either "CStdExplosionGenerator" (or some sub-string, eg.
311 // "std") which maps to CStdExplosionGenerator or "custom:postfix"
312 // which maps to CCustomExplosionGenerator, all others cause NULL
313 // to be returned
314 string prefix;
315 string postfix;
316
317 const string::size_type seppos = tag.find(':');
318
319 if (seppos != string::npos) {
320 // grab the "custom" prefix (the only supported value)
321 prefix = tag.substr(0, seppos);
322 postfix = tag.substr(seppos + 1);
323 assert((prefix + ":") == CEG_PREFIX_STRING);
324 } else {
325 prefix = tag;
326 }
327
328 creg::Class* cls = generatorClasses.GetClass(prefix);
329
330 if (cls == NULL)
331 return NULL;
332
333 if (!cls->IsSubclassOf(IExplosionGenerator::StaticClass()))
334 throw content_error(prefix + " is not a subclass of IExplosionGenerator");
335
336 IExplosionGenerator* explGen = static_cast<IExplosionGenerator*>(cls->CreateInstance());
337 explGen->SetGeneratorID(explosionGenerators.size());
338
339 // can still be a standard generator, but with non-zero ID
340 assert(explGen->GetGeneratorID() != EXPGEN_ID_STANDARD);
341
342 // save generator so ID is valid *before* possible recursion
343 explosionGenerators.push_back(explGen);
344
345 if (!postfix.empty()) {
346 // standard EG's have no postfix (nor always a prefix)
347 // custom EG's always have CEG_PREFIX_STRING in front
348 expGenTagIdentMap[tag] = explGen->GetGeneratorID();
349 expGenIdentTagMap[explGen->GetGeneratorID()] = tag;
350
351 explGen->Load(this, postfix);
352 }
353
354 return explGen;
355 }
356
GetGenerator(unsigned int expGenID)357 IExplosionGenerator* CExplosionGeneratorHandler::GetGenerator(unsigned int expGenID)
358 {
359 if (expGenID == EXPGEN_ID_INVALID)
360 return NULL;
361 if (expGenID >= explosionGenerators.size())
362 return NULL;
363
364 return explosionGenerators[expGenID];
365 }
366
GenExplosion(unsigned int expGenID,const float3 & pos,const float3 & dir,float damage,float radius,float gfxMod,CUnit * owner,CUnit * hit)367 bool CExplosionGeneratorHandler::GenExplosion(
368 unsigned int expGenID,
369 const float3& pos,
370 const float3& dir,
371 float damage,
372 float radius,
373 float gfxMod,
374 CUnit* owner,
375 CUnit* hit
376 ) {
377 if (expGenID == EXPGEN_ID_INVALID)
378 return false;
379
380 assert(expGenID < explosionGenerators.size());
381
382 return explosionGenerators[expGenID]->Explosion(pos, dir, damage, radius, gfxMod, owner, hit);
383 }
384
385
386
Explosion(const float3 & pos,const float3 & dir,float damage,float radius,float gfxMod,CUnit * owner,CUnit * hit)387 bool CStdExplosionGenerator::Explosion(
388 const float3& pos,
389 const float3& dir,
390 float damage,
391 float radius,
392 float gfxMod,
393 CUnit* owner,
394 CUnit* hit
395 ) {
396 const float groundHeight = CGround::GetHeightReal(pos.x, pos.z);
397 const float altitude = pos.y - groundHeight;
398
399 float3 camVect = camera->GetPos() - pos;
400
401 const unsigned int flags = CCustomExplosionGenerator::GetFlagsFromHeight(pos.y, groundHeight);
402 const bool airExplosion = ((flags & CCustomExplosionGenerator::SPW_AIR ) != 0);
403 const bool groundExplosion = ((flags & CCustomExplosionGenerator::SPW_GROUND ) != 0);
404 const bool waterExplosion = ((flags & CCustomExplosionGenerator::SPW_WATER ) != 0);
405 const bool uwExplosion = ((flags & CCustomExplosionGenerator::SPW_UNDERWATER) != 0);
406
407 // limit the visual effects based on the radius
408 damage /= 20.0f;
409 damage = std::min(damage, radius * 1.5f);
410 damage *= gfxMod;
411 damage = std::max(damage, 0.0f);
412
413 const float sqrtDmg = math::sqrt(damage);
414 const float camLength = camVect.Length();
415 float moveLength = radius * 0.03f;
416
417 if (camLength > 0.0f) { camVect /= camLength; }
418 if (camLength < moveLength + 2) { moveLength = camLength - 2; }
419
420 const float3 npos = pos + camVect * moveLength;
421
422 new CHeatCloudProjectile(owner, npos, float3(0.0f, 0.3f, 0.0f), 8.0f + sqrtDmg * 0.5f, 7 + damage * 2.8f);
423
424 if (projectileHandler->particleSaturation < 1.0f) {
425 // turn off lots of graphic only particles when we have more particles than we want
426 float smokeDamage = damage;
427 float smokeDamageSQRT = 0.0f;
428 float smokeDamageISQRT = 0.0f;
429
430 if (uwExplosion) { smokeDamage *= 0.3f; }
431 if (airExplosion || waterExplosion) { smokeDamage *= 0.6f; }
432
433 if (smokeDamage > 0.01f) {
434 smokeDamageSQRT = math::sqrt(smokeDamage);
435 smokeDamageISQRT = 1.0f / (smokeDamageSQRT * 0.35f);
436 }
437
438 for (int a = 0; a < smokeDamage * 0.6f; ++a) {
439 const float3 speed(
440 (-0.1f + gu->RandFloat() * 0.2f),
441 ( 0.1f + gu->RandFloat() * 0.3f) * smokeDamageISQRT,
442 (-0.1f + gu->RandFloat() * 0.2f)
443 );
444
445 const float h = CGround::GetApproximateHeight(npos.x, npos.z);
446 const float time = (40.0f + smokeDamageSQRT * 15.0f) * (0.8f + gu->RandFloat() * 0.7f);
447
448 float3 npos = pos + gu->RandVector() * smokeDamage;
449 npos.y = std::max(npos.y, h);
450
451 new CSmokeProjectile2(owner, pos, npos, speed, time, smokeDamageSQRT * 4.0f, 0.4f, 0.6f);
452 }
453
454 if (groundExplosion) {
455 const int numDirt = std::min(20.0f, damage * 0.8f);
456 const float explSpeedMod = 0.7f + std::min(30.0f, damage) / GAME_SPEED;
457 const float3 color(0.15f, 0.1f, 0.05f);
458
459 for (int a = 0; a < numDirt; ++a) {
460 const float3 explSpeed = float3(
461 (0.5f - gu->RandFloat()) * 1.5f,
462 1.7f + gu->RandFloat() * 1.6f,
463 (0.5f - gu->RandFloat()) * 1.5f
464 );
465
466 const float3 npos(
467 pos.x - (0.5f - gu->RandFloat()) * (radius * 0.6f),
468 pos.y - 2.0f - damage * 0.2f,
469 pos.z - (0.5f - gu->RandFloat()) * (radius * 0.6f)
470 );
471
472 new CDirtProjectile(owner, npos, explSpeed * explSpeedMod, 90.0f + damage * 2.0f, 2.0f + sqrtDmg * 1.5f, 0.4f, 0.999f, color);
473 }
474 }
475
476 if (!airExplosion && !uwExplosion && waterExplosion) {
477 const int numDirt = std::min(40.f, damage * 0.8f);
478 const float3 color(1.0f, 1.0f, 1.0f);
479
480 for (int a = 0; a < numDirt; ++a) {
481 const float3 speed(
482 ( 0.5f - gu->RandFloat()) * 0.2f,
483 (a * 0.1f + gu->RandFloat() * 0.8f),
484 ( 0.5f - gu->RandFloat()) * 0.2f
485 );
486 const float3 npos(
487 pos.x - (0.5f - gu->RandFloat()) * (radius * 0.2f),
488 pos.y - 2.0f - sqrtDmg * 2.0f,
489 pos.z - (0.5f - gu->RandFloat()) * (radius * 0.2f)
490 );
491
492 new CDirtProjectile(
493 owner,
494 npos,
495 speed * (0.7f + std::min(30.0f, damage) / GAME_SPEED),
496 90.0f + damage * 2.0f,
497 2.0f + sqrtDmg * 2.0f,
498 0.3f,
499 0.99f,
500 color
501 );
502 }
503 }
504 if (damage >= 20.0f && !uwExplosion && !airExplosion) {
505 const int numDebris = (gu->RandInt() % 6) + 3 + int(damage * 0.04f);
506 const float explSpeedMod = (0.7f + std::min(30.0f, damage) / 23);
507
508 for (int a = 0; a < numDebris; ++a) {
509 const float3 explSpeed = (altitude < 4.0f)?
510 float3((0.5f - gu->RandFloat()) * 2.0f, 1.8f + gu->RandFloat() * 1.8f, (0.5f - gu->RandFloat()) * 2.0f):
511 float3(gu->RandVector() * 2);
512
513 const float3 npos(
514 pos.x - (0.5f - gu->RandFloat()) * (radius * 1),
515 pos.y,
516 pos.z - (0.5f - gu->RandFloat()) * (radius * 1)
517 );
518
519 new CWreckProjectile(owner, npos, explSpeed * explSpeedMod, 90.0f + damage * 2.0f);
520 }
521 }
522 if (uwExplosion) {
523 const int numBubbles = (damage * 0.7f);
524
525 for (int a = 0; a < numBubbles; ++a) {
526 new CBubbleProjectile(
527 owner,
528 pos + gu->RandVector() * radius * 0.5f,
529 gu->RandVector() * 0.2f + float3(0.0f, 0.2f, 0.0f),
530 damage * 2.0f + gu->RandFloat() * damage,
531 1.0f + gu->RandFloat() * 2.0f,
532 0.02f,
533 0.5f + gu->RandFloat() * 0.3f
534 );
535 }
536 }
537 if (waterExplosion && !uwExplosion && !airExplosion) {
538 const int numWake = (damage * 0.5f);
539
540 for (int a = 0; a < numWake; ++a) {
541 new CWakeProjectile(
542 owner,
543 pos + gu->RandVector() * radius * 0.2f,
544 gu->RandVector() * radius * 0.003f,
545 sqrtDmg * 4.0f,
546 damage * 0.03f,
547 0.3f + gu->RandFloat() * 0.2f,
548 0.8f / (sqrtDmg * 3 + 50 + gu->RandFloat() * 90.0f),
549 1
550 );
551 }
552 }
553 if (radius > 10.0f && damage > 4.0f) {
554 const int numSpike = int(sqrtDmg) + 8;
555 const float explSpeedMod = (8 + damage * 3.0f) / (9 + sqrtDmg * 0.7f) * 0.35f;
556
557 for (int a = 0; a < numSpike; ++a) {
558 float3 explSpeed = (gu->RandVector()).SafeNormalize() * explSpeedMod;
559
560 if (!airExplosion && !waterExplosion && (explSpeed.y < 0.0f)) {
561 explSpeed.y = -explSpeed.y;
562 }
563
564 new CExploSpikeProjectile(
565 owner,
566 pos + explSpeed,
567 explSpeed * (0.9f + gu->RandFloat() * 0.4f),
568 radius * 0.1f,
569 radius * 0.1f,
570 0.6f,
571 0.8f / (8.0f + sqrtDmg)
572 );
573 }
574 }
575 }
576
577 if (radius > 20.0f && damage > 6.0f && altitude < (radius * 0.7f)) {
578 const float flashSize = std::max(radius, damage * 2);
579 const float ttl = 8 + sqrtDmg * 0.8f;
580
581 if (flashSize > 5.f && ttl > 15.f) {
582 const float flashAlpha = std::min(0.8f, damage * 0.01f);
583
584 float circleAlpha = 0;
585 float circleGrowth = 0;
586
587 if (radius > 40.0f && damage > 12.0f) {
588 circleAlpha = std::min(0.5f, damage * 0.01f);
589 circleGrowth = (8.0f + damage * 2.5f) / (9.0f + sqrtDmg * 0.7f) * 0.55f;
590 }
591
592 new CStandardGroundFlash(pos, circleAlpha, flashAlpha, flashSize, circleGrowth, ttl);
593 }
594 }
595
596 if (radius > 40.0f && damage > 12.0f) {
597 CSpherePartProjectile::CreateSphere(
598 owner,
599 5.0f + int(sqrtDmg * 0.7f),
600 std::min(0.7f, damage * 0.02f),
601 (8.0f + damage * 2.5f) / (9.0f + sqrtDmg * 0.7f) * 0.5f,
602 pos
603 );
604 }
605
606 return true;
607 }
608
609
610
ExecuteExplosionCode(const char * code,float damage,char * instance,int spawnIndex,const float3 & dir)611 void CCustomExplosionGenerator::ExecuteExplosionCode(const char* code, float damage, char* instance, int spawnIndex, const float3& dir)
612 {
613 float val = 0.0f;
614 void* ptr = NULL;
615 float buffer[16];
616
617 for (;;) {
618 switch (*(code++)) {
619 case OP_END: {
620 return;
621 }
622 case OP_STOREI: {
623 boost::uint16_t offset = *(boost::uint16_t*) code;
624 code += 2;
625 *(int*) (instance + offset) = (int) val;
626 val = 0.0f;
627 break;
628 }
629 case OP_STOREF: {
630 boost::uint16_t offset = *(boost::uint16_t*) code;
631 code += 2;
632 *(float*) (instance + offset) = val;
633 val = 0.0f;
634 break;
635 }
636 case OP_STOREC: {
637 boost::uint16_t offset = *(boost::uint16_t*) code;
638 code += 2;
639 *(unsigned char*) (instance + offset) = (int) val;
640 val = 0.0f;
641 break;
642 }
643 case OP_ADD: {
644 val += *(float*) code;
645 code += 4;
646 break;
647 }
648 case OP_RAND: {
649 val += gu->RandFloat() * (*(float*) code);
650 code += 4;
651 break;
652 }
653 case OP_DAMAGE: {
654 val += damage * (*(float*) code);
655 code += 4;
656 break;
657 }
658 case OP_INDEX: {
659 val += spawnIndex * (*(float*) code);
660 code += 4;
661 break;
662 }
663 case OP_LOADP: {
664 ptr = *(void**) code;
665 code += sizeof(void*);
666 break;
667 }
668 case OP_STOREP: {
669 boost::uint16_t offset = *(boost::uint16_t*) code;
670 code += 2;
671 *(void**) (instance + offset) = ptr;
672 ptr = NULL;
673 break;
674 }
675 case OP_DIR: {
676 boost::uint16_t offset = *(boost::uint16_t*) code;
677 code += 2;
678 *reinterpret_cast<float3*>(instance + offset) = dir;
679 break;
680 }
681 case OP_SAWTOOTH: {
682 // this translates to modulo except it works with floats
683 val -= (*(float*) code) * math::floor(val / (*(float*) code));
684 code += 4;
685 break;
686 }
687 case OP_DISCRETE: {
688 val = (*(float*) code) * math::floor(SafeDivide(val, (*(float*) code)));
689 code += 4;
690 break;
691 }
692 case OP_SINE: {
693 val = (*(float*) code) * math::sin(val);
694 code += 4;
695 break;
696 }
697 case OP_YANK: {
698 buffer[(*(int*) code)] = val;
699 val = 0;
700 code += 4;
701 break;
702 }
703 case OP_MULTIPLY: {
704 val *= buffer[(*(int*) code)];
705 code += 4;
706 break;
707 }
708 case OP_ADDBUFF: {
709 val += buffer[(*(int*) code)];
710 code += 4;
711 break;
712 }
713 case OP_POW: {
714 val = math::pow(val, (*(float*) code));
715 code += 4;
716 break;
717 }
718 case OP_POWBUFF: {
719 val = math::pow(val, buffer[(*(int*) code)]);
720 code += 4;
721 break;
722 }
723 default: {
724 assert(false);
725 break;
726 }
727 }
728 }
729 }
730
731
732
ParseExplosionCode(CCustomExplosionGenerator::ProjectileSpawnInfo * psi,const int offset,const boost::shared_ptr<creg::IType> type,const string & script,string & code)733 void CCustomExplosionGenerator::ParseExplosionCode(
734 CCustomExplosionGenerator::ProjectileSpawnInfo* psi,
735 const int offset,
736 const boost::shared_ptr<creg::IType> type,
737 const string& script,
738 string& code)
739 {
740 string::size_type end = script.find(';', 0);
741 string vastr = script.substr(0, end);
742
743 if (vastr == "dir") { // first see if we can match any keywords
744 // if the user uses a keyword assume he knows that it is put on the right datatype for now
745 code += OP_DIR;
746 boost::uint16_t ofs = offset;
747 code.append((char*) &ofs, (char*) &ofs + 2);
748 }
749 else if (dynamic_cast<creg::BasicType*>(type.get())) {
750 const creg::BasicType* basicType = (creg::BasicType*) type.get();
751 const bool legalType =
752 (basicType->id == creg::crInt ) ||
753 (basicType->id == creg::crFloat) ||
754 (basicType->id == creg::crUChar) ||
755 (basicType->id == creg::crBool );
756
757 if (!legalType) {
758 throw content_error("[CCEG::ParseExplosionCode] projectile type-properties other than int, float, uchar, or bool are not supported (" + script + ")");
759 }
760
761 int p = 0;
762
763 while (p < script.length()) {
764 char opcode = OP_END;
765 char c = script[p++];
766
767 // consume whitespace
768 if (c == ' ')
769 continue;
770
771 bool useInt = false;
772
773 if (c == 'i') opcode = OP_INDEX;
774 else if (c == 'r') opcode = OP_RAND;
775 else if (c == 'd') opcode = OP_DAMAGE;
776 else if (c == 'm') opcode = OP_SAWTOOTH;
777 else if (c == 'k') opcode = OP_DISCRETE;
778 else if (c == 's') opcode = OP_SINE;
779 else if (c == 'p') opcode = OP_POW;
780 else if (c == 'y') { opcode = OP_YANK; useInt = true; }
781 else if (c == 'x') { opcode = OP_MULTIPLY; useInt = true; }
782 else if (c == 'a') { opcode = OP_ADDBUFF; useInt = true; }
783 else if (c == 'q') { opcode = OP_POWBUFF; useInt = true; }
784 else if (isdigit(c) || c == '.' || c == '-') { opcode = OP_ADD; p--; }
785 else {
786 const char* fmt = "[CCEG::ParseExplosionCode] unknown op-code \"%c\" in \"%s\" at index %d";
787 LOG_L(L_WARNING, fmt, c, script.c_str(), p);
788 continue;
789 }
790
791 // be sure to exit cleanly if there are no more operators or operands
792 if (p >= script.size())
793 continue;
794
795 char* endp = NULL;
796
797 if (!useInt) {
798 // strtod&co expect C-style strings with NULLs,
799 // c_str() is guaranteed to be NULL-terminated
800 // (whether .data() == .c_str() depends on the
801 // implementation of std::string)
802 const float v = (float)strtod(&script.c_str()[p], &endp);
803
804 p += (endp - &script.c_str()[p]);
805 code += opcode;
806 code.append((char*) &v, ((char*) &v) + 4);
807 } else {
808 const int v = std::max(0, std::min(16, (int)strtol(&script.c_str()[p], &endp, 10)));
809
810 p += (endp - &script.c_str()[p]);
811 code += opcode;
812 code.append((char*) &v, ((char*) &v) + 4);
813 }
814 }
815
816 switch (basicType->id) {
817 case creg::crInt: code.push_back(OP_STOREI); break;
818 case creg::crBool: code.push_back(OP_STOREC); break;
819 case creg::crFloat: code.push_back(OP_STOREF); break;
820 case creg::crUChar: code.push_back(OP_STOREC); break;
821 default: break;
822 }
823
824 boost::uint16_t ofs = offset;
825 code.append((char*)&ofs, (char*)&ofs + 2);
826 }
827 else if (dynamic_cast<creg::ObjectInstanceType*>(type.get())) {
828 creg::ObjectInstanceType *oit = (creg::ObjectInstanceType *)type.get();
829
830 string::size_type start = 0;
831 for (creg::Class* c = oit->objectClass; c; c=c->base) {
832 for (int a = 0; a < c->members.size(); a++) {
833 string::size_type end = script.find(',', start+1);
834 ParseExplosionCode(psi, offset + c->members [a]->offset, c->members[a]->type, script.substr(start,end-start), code);
835 start = end+1;
836 if (start >= script.length()) { break; }
837 }
838 if (start >= script.length()) { break; }
839 }
840 }
841 else if (dynamic_cast<creg::StaticArrayBaseType*>(type.get())) {
842 creg::StaticArrayBaseType *sat = (creg::StaticArrayBaseType*)type.get();
843
844 string::size_type start = 0;
845 for (unsigned int i=0; i < sat->size; i++) {
846 string::size_type end = script.find(',', start+1);
847 ParseExplosionCode(psi, offset + sat->elemSize * i, sat->elemType, script.substr(start, end-start), code);
848 start = end+1;
849 if (start >= script.length()) { break; }
850 }
851 }
852 else {
853 if (type->GetName() == "AtlasedTexture*") {
854 string::size_type end = script.find(';', 0);
855 string texname = script.substr(0, end);
856 // this memory is managed by textureAtlas (CTextureAtlas)
857 void* tex = &projectileDrawer->textureAtlas->GetTexture(texname);
858 code += OP_LOADP;
859 code.append((char*)(&tex), ((char*)(&tex)) + sizeof(void*));
860 code += OP_STOREP;
861 boost::uint16_t ofs = offset;
862 code.append((char*)&ofs, (char*)&ofs + 2);
863 } else if (type->GetName() == "GroundFXTexture*") {
864 string::size_type end = script.find(';', 0);
865 string texname = script.substr(0, end);
866 // this memory is managed by groundFXAtlas (CTextureAtlas)
867 void* tex = &projectileDrawer->groundFXAtlas->GetTexture(texname);
868 code += OP_LOADP;
869 code.append((char*)(&tex), ((char*)(&tex)) + sizeof(void*));
870 code += OP_STOREP;
871 boost::uint16_t ofs = offset;
872 code.append((char*)&ofs, (char*)&ofs + 2);
873 } else if (type->GetName() == "CColorMap*") {
874 string::size_type end = script.find(';', 0);
875 string colorstring = script.substr(0, end);
876 // gets stored and deleted at game end from inside CColorMap
877 void* colormap = CColorMap::LoadFromDefString(colorstring);
878 code += OP_LOADP;
879 code.append((char*)(&colormap), ((char*)(&colormap)) + sizeof(void*));
880 code += OP_STOREP;
881 boost::uint16_t ofs = offset;
882 code.append((char*)&ofs, (char*)&ofs + 2);
883 } else if (type->GetName() == "IExplosionGenerator*") {
884 string::size_type end = script.find(';', 0);
885 string name = script.substr(0, end);
886
887 // managed by CExplosionGeneratorHandler
888 IExplosionGenerator* explGen = explGenHandler->LoadGenerator(name);
889
890 void* explGenRaw = (void*) explGen;
891 code += OP_LOADP;
892 code.append((char*)(&explGenRaw), ((char*)(&explGenRaw)) + sizeof(void*));
893 code += OP_STOREP;
894 boost::uint16_t ofs = offset;
895 code.append((char*)&ofs, (char*)&ofs + 2);
896 }
897 }
898 }
899
900
901
Load(CExplosionGeneratorHandler * handler,const string & tag)902 bool CCustomExplosionGenerator::Load(CExplosionGeneratorHandler* handler, const string& tag)
903 {
904 const LuaTable* root = handler->GetExplosionTableRoot();
905 const LuaTable& expTable = (root != NULL)? root->SubTable(tag): LuaTable();
906
907 if (!expTable.IsValid()) {
908 // not a fatal error: any calls to Explosion will just return early
909 LOG_L(L_WARNING, "[CCEG::%s] table for CEG \"%s\" invalid (parse errors?)", __FUNCTION__, tag.c_str());
910 return false;
911 }
912
913 expGenParams.projectiles.clear();
914 expGenParams.groundFlash = GroundFlashInfo();
915
916 vector<string> spawns;
917 expTable.GetKeys(spawns);
918
919 for (unsigned int n = 0; n < spawns.size(); n++) {
920 ProjectileSpawnInfo psi;
921
922 const string& spawnName = spawns[n];
923 const LuaTable& spawnTable = expTable.SubTable(spawnName);
924
925 // NOTE:
926 // *every* CEG table contains a spawn called "filename"
927 // see springcontent/gamedata/explosions.lua::LoadTDFs
928 if (!spawnTable.IsValid())
929 continue;
930 if (spawnName == "groundflash" || spawnName == "filename")
931 continue;
932
933 const string& className = spawnTable.GetString("class", spawnName);
934
935 psi.projectileClass = (handler->GetProjectileClasses()).GetClass(className);
936 psi.flags = GetFlagsFromTable(spawnTable);
937 psi.count = std::max(0, spawnTable.GetInt("count", 1));
938
939 if (psi.projectileClass == NULL) {
940 LOG_L(L_WARNING, "[CCEG::%s] %s: Unknown class \"%s\"", __FUNCTION__, tag.c_str(), className.c_str());
941 continue;
942 }
943
944 if (psi.projectileClass->binder->flags & creg::CF_Synced) {
945 LOG_L(L_WARNING, "[CCEG::%s] %s: Tried to access synced class \"%s\"", __FUNCTION__, tag.c_str(), className.c_str());
946 continue;
947 }
948
949 string code;
950 map<string, string> props;
951 map<string, string>::const_iterator propIt;
952
953 spawnTable.SubTable("properties").GetMap(props);
954
955 for (propIt = props.begin(); propIt != props.end(); ++propIt) {
956 const creg::Class::Member* m = psi.projectileClass->FindMember(propIt->first.c_str());
957
958 if (m && (m->flags & creg::CM_Config)) {
959 ParseExplosionCode(&psi, m->offset, m->type, propIt->second, code);
960 } else {
961 LOG_L(L_WARNING, "[CCEG::%s] %s: Unknown tag %s::%s", __FUNCTION__, tag.c_str(), className.c_str(), propIt->first.c_str());
962 }
963 }
964
965 code += (char)OP_END;
966 psi.code.resize(code.size());
967 copy(code.begin(), code.end(), psi.code.begin());
968
969 expGenParams.projectiles.push_back(psi);
970 }
971
972 const LuaTable gndTable = expTable.SubTable("groundflash");
973 const int ttl = gndTable.GetInt("ttl", 0);
974
975 if (ttl > 0) {
976 expGenParams.groundFlash.circleAlpha = gndTable.GetFloat("circleAlpha", 0.0f);
977 expGenParams.groundFlash.flashSize = gndTable.GetFloat("flashSize", 0.0f);
978 expGenParams.groundFlash.flashAlpha = gndTable.GetFloat("flashAlpha", 0.0f);
979 expGenParams.groundFlash.circleGrowth = gndTable.GetFloat("circleGrowth", 0.0f);
980 expGenParams.groundFlash.color = gndTable.GetFloat3("color", float3(1.0f, 1.0f, 0.8f));
981
982 expGenParams.groundFlash.flags = SPW_GROUND | GetFlagsFromTable(gndTable);
983 expGenParams.groundFlash.ttl = ttl;
984 }
985
986 expGenParams.useDefaultExplosions = expTable.GetBool("useDefaultExplosions", false);
987 return true;
988 }
989
Reload(CExplosionGeneratorHandler * handler,const std::string & tag)990 bool CCustomExplosionGenerator::Reload(CExplosionGeneratorHandler* handler, const std::string& tag) {
991 const ExpGenParams oldParams = expGenParams;
992
993 if (!Load(explGenHandler, tag)) {
994 expGenParams = oldParams;
995 return false;
996 }
997
998 return true;
999 }
1000
Explosion(const float3 & pos,const float3 & dir,float damage,float radius,float gfxMod,CUnit * owner,CUnit * hit)1001 bool CCustomExplosionGenerator::Explosion(
1002 const float3& pos,
1003 const float3& dir,
1004 float damage,
1005 float radius,
1006 float gfxMod,
1007 CUnit* owner,
1008 CUnit* hit
1009 ) {
1010 const float groundHeight = CGround::GetHeightReal(pos.x, pos.z);
1011
1012 unsigned int flags = GetFlagsFromHeight(pos.y, groundHeight);
1013 const bool groundExplosion = ((flags & CCustomExplosionGenerator::SPW_GROUND) != 0);
1014
1015 if (hit) flags |= SPW_UNIT;
1016 else flags |= SPW_NO_UNIT;
1017
1018 const std::vector<ProjectileSpawnInfo>& spawnInfo = expGenParams.projectiles;
1019 const GroundFlashInfo& groundFlash = expGenParams.groundFlash;
1020
1021 for (int a = 0; a < spawnInfo.size(); a++) {
1022 const ProjectileSpawnInfo& psi = spawnInfo[a];
1023
1024 if ((psi.flags & flags) == 0)
1025 continue;
1026
1027 // no new projectiles if we're saturated
1028 if (projectileHandler->particleSaturation > 1.0f)
1029 continue;
1030
1031 for (unsigned int c = 0; c < psi.count; c++) {
1032 CExpGenSpawnable* projectile = static_cast<CExpGenSpawnable*>((psi.projectileClass)->CreateInstance());
1033 ExecuteExplosionCode(&psi.code[0], damage, (char*) projectile, c, dir);
1034 projectile->Init(owner, pos);
1035 }
1036 }
1037
1038 if (groundExplosion && (groundFlash.ttl > 0) && (groundFlash.flashSize > 1)) {
1039 new CStandardGroundFlash(pos, groundFlash.circleAlpha, groundFlash.flashAlpha,
1040 groundFlash.flashSize, groundFlash.circleGrowth, groundFlash.ttl, groundFlash.color);
1041 }
1042
1043 if (expGenParams.useDefaultExplosions) {
1044 return (explGenHandler->GenExplosion(CExplosionGeneratorHandler::EXPGEN_ID_STANDARD, pos, dir, damage, radius, gfxMod, owner, hit));
1045 }
1046
1047 return true;
1048 }
1049
1050
OutputProjectileClassInfo()1051 bool CCustomExplosionGenerator::OutputProjectileClassInfo()
1052 {
1053 LOG_DISABLE();
1054 // we need to load basecontent for class aliases
1055 FileSystemInitializer::Initialize();
1056 vfsHandler->AddArchiveWithDeps(archiveScanner->ArchiveFromName("Spring content v1"), false);
1057 LOG_ENABLE();
1058
1059 creg::System::InitializeClasses();
1060 const vector<creg::Class*>& classes = creg::System::GetClasses();
1061 CExplosionGeneratorHandler egh;
1062
1063 std::cout << "{" << std::endl;
1064
1065 for (vector<creg::Class*>::const_iterator ci = classes.begin(); ci != classes.end(); ++ci) {
1066 creg::Class* c = *ci;
1067
1068 if (!c->IsSubclassOf(CExpGenSpawnable::StaticClass()) || c == CExpGenSpawnable::StaticClass())
1069 continue;
1070
1071 if (c->binder->flags & creg::CF_Synced)
1072 continue;
1073
1074 if (ci != classes.begin())
1075 std::cout << "," << std::endl;
1076
1077 std::cout << " \"" << c->name << "\": {" << std::endl;
1078 std::cout << " \"alias\": \"" << (egh.GetProjectileClasses()).FindAlias(c->name) << "\"";
1079 for (; c; c = c->base) {
1080 for (unsigned int a = 0; a < c->members.size(); a++) {
1081 if (c->members[a]->flags & creg::CM_Config) {
1082 std::cout << "," << std::endl;
1083 std::cout << " \"" << c->members[a]->name << "\": \"" << c->members[a]->type->GetName() << "\"";
1084 }
1085 }
1086 }
1087
1088 std::cout << std::endl << " }";
1089 }
1090 std::cout << std::endl << "}" << std::endl;
1091
1092 FileSystemInitializer::Cleanup();
1093 return true;
1094 }
1095
1096