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