1 ////////////////////////////////////////////////////////////////////////////////
2 //    Scorched3D (c) 2000-2011
3 //
4 //    This file is part of Scorched3D.
5 //
6 //    Scorched3D is free software; you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation; either version 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    Scorched3D is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License along
17 //    with this program; if not, write to the Free Software Foundation, Inc.,
18 //    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 ////////////////////////////////////////////////////////////////////////////////
20 
21 #include <engine/ScorchedContext.h>
22 #include <engine/ActionController.h>
23 #include <engine/Simulator.h>
24 #include <target/TargetContainer.h>
25 #include <target/TargetDamageCalc.h>
26 #include <target/TargetRenderer.h>
27 #include <target/TargetState.h>
28 #include <target/TargetSpace.h>
29 #include <target/TargetLife.h>
30 #include <tank/TankViewPoints.h>
31 #include <actions/Napalm.h>
32 #include <actions/CameraPositionAction.h>
33 #ifndef S3D_SERVER
34 	#include <sprites/ExplosionTextures.h>
35 	#include <GLEXT/GLStateExtension.h>
36 	#include <landscape/Landscape.h>
37 	#include <landscape/DeformTextures.h>
38 	#include <landscape/Smoke.h>
39 	#include <client/ScorchedClient.h>
40 #endif
41 #include <landscapemap/LandscapeMaps.h>
42 #include <landscapedef/LandscapeDefinition.h>
43 #include <landscapedef/LandscapeTex.h>
44 #include <weapons/AccessoryStore.h>
45 #include <common/Defines.h>
46 #include <common/StatsLogger.h>
47 #include <common/OptionsScorched.h>
48 
49 static const int deformSize = 3;
50 static DeformLandscape::DeformPoints deformMap;
51 static bool deformCreated = false;
52 
53 #define XY_TO_UINT(x, y) ((((unsigned int) x) << 16) | (((unsigned int) y) & 0xffff))
54 #define XY2_TO_UINT(x, y) ((((unsigned int) x - x % 2) << 16) | (((unsigned int) y - y % 2) & 0xffff))
55 #define UINT_TO_X(pt) ((int)(pt >> 16))
56 #define UINT_TO_Y(pt) ((int)(pt & 0xffff))
57 
Napalm(int x,int y,Weapon * weapon,NapalmParams * params,WeaponFireContext & weaponContext)58 Napalm::Napalm(int x, int y, Weapon *weapon,
59 	NapalmParams *params,
60 	WeaponFireContext &weaponContext) :
61 	Action(weaponContext.getInternalContext().getReferenced()),
62 	startX_(x), startY_(y), napalmTime_(0),
63 	weapon_(weapon), params_(params),
64 	weaponContext_(weaponContext),
65 	totalTime_(0), hurtTime_(0),
66 	counter_(0.1f, 0.1f), set_(0),
67 	particleSet_(0), vPoint_(0)
68 {
69 }
70 
~Napalm()71 Napalm::~Napalm()
72 {
73 	delete params_;
74 	if (vPoint_) vPoint_->decrementReference();
75 	while (!napalmPoints_.empty())
76 	{
77 		NapalmEntry *entry = napalmPoints_.front();
78 		delete entry;
79 		napalmPoints_.pop_front();
80 	}
81 }
82 
init()83 void Napalm::init()
84 {
85 	if (!deformCreated)
86 	{
87 		deformCreated = true;
88 
89 		Vector center(deformSize + 1, deformSize + 1);
90 		for (int a=0; a<(deformSize + 1) * 2; a++)
91 		{
92 			for (int b=0; b<(deformSize + 1) * 2; b++)
93 			{
94 				Vector pos(a, b);
95 				float dist = (center - pos).Magnitude();
96 				dist /= deformSize;
97 				dist = 1.0f - MIN(1.0f, dist);
98 
99 				DIALOG_ASSERT(a < 100 && b < 100);
100 				deformMap.map[a][b] = fixed::fromFloat(dist);
101 			}
102 		}
103 	}
104 
105 	edgePoints_.insert(XY_TO_UINT(startX_, startY_));
106 
107 #ifndef S3D_SERVER
108 	if (!context_->getServerMode())
109 	{
110 		if (!params_->getNoCameraTrack())
111 		{
112 			FixedVector position(fixed(startX_), fixed(startY_), context_->getLandscapeMaps().
113 				getGroundMaps().getHeight(startX_, startY_));
114 			vPoint_ = new TankViewPointProvider();
115 			vPoint_->setValues(position);
116 			vPoint_->incrementReference();
117 
118 			CameraPositionAction *pos = new CameraPositionAction(
119 				weaponContext_.getPlayerId(),
120 				vPoint_,
121 				5, 5, true);
122 			context_->getActionController().addAction(pos);
123 		}
124 
125 		set_ = ExplosionTextures::instance()->getTextureSetByName(
126 			params_->getNapalmTexture());
127 	}
128 #endif // #ifndef S3D_SERVER
129 }
130 
getActionDetails()131 std::string Napalm::getActionDetails()
132 {
133 	return S3D::formatStringBuffer("%i,%i %s",
134 		startX_, startY_, weapon_->getParent()->getName());
135 }
136 
simulate(fixed frameTime,bool & remove)137 void Napalm::simulate(fixed frameTime, bool &remove)
138 {
139 #ifndef S3D_SERVER
140 	if (!context_->getServerMode())
141 	{
142 		if (!napalmPoints_.empty() &&
143 			!params_->getNoSmoke() &&
144 			counter_.nextDraw(frameTime.asFloat()))
145 		{
146 			NapalmEntry *entry = 0;
147 			int count = rand() % napalmPoints_.size();
148 			std::list<NapalmEntry *>::iterator itor;
149 			for (itor = napalmPoints_.begin();
150 				itor != napalmPoints_.end();
151 				itor++, count --)
152 			{
153 				entry = *itor;
154 				if (count <=0) break;
155 			}
156 
157 			fixed posZ =
158 				ScorchedClient::instance()->getLandscapeMaps().getGroundMaps().getHeight(
159 				entry->posX, entry->posY);
160 			Landscape::instance()->getSmoke().
161 				addSmoke(float(entry->posX), float(entry->posY), posZ.asFloat());
162 		}
163 	}
164 #endif // #ifndef S3D_SERVER
165 
166 	// Add napalm for the period of the time interval
167 	// once the time interval has expired then start taking it away
168 	// Once all napalm has disapeared the simulation is over
169 	totalTime_ += frameTime;
170 	while (totalTime_ > params_->getStepTime())
171 	{
172 		totalTime_ -= params_->getStepTime();
173 		napalmTime_ += params_->getStepTime();
174 		if (napalmTime_ < params_->getNapalmTime())
175 		{
176 			// Still within the time period, add more napalm
177 			if (int(napalmPoints_.size()) < params_->getNumberParticles())
178 			{
179 				simulateAddStep();
180 			}
181 
182 			// Check for the case where we land in water
183 			if (napalmPoints_.empty())
184 			{
185 				remove = true;
186 				break;
187 			}
188 		}
189 		else
190 		{
191 			// Not within the time period remove napalm
192 			if (!napalmPoints_.empty())
193 			{
194 				simulateRmStep();
195 			}
196 			else
197 			{
198 				remove = true;
199 				break;
200 			}
201 		}
202 	}
203 
204 	// Calculate how much damage to make to the tanks
205 	hurtTime_ += frameTime;
206 	while (hurtTime_ > params_->getHurtStepTime())
207 	{
208 		hurtTime_ -= params_->getHurtStepTime();
209 
210 		simulateDamage();
211 	}
212 
213 	Action::simulate(frameTime, remove);
214 }
215 
getHeight(int x,int y)216 fixed Napalm::getHeight(int x, int y)
217 {
218 	LandscapeMaps *hmap = &context_->getLandscapeMaps();
219 	if (x < 0 || y < 0 ||
220 		x > hmap->getGroundMaps().getLandscapeWidth() ||
221 		y > hmap->getGroundMaps().getLandscapeHeight())
222 	{
223 		// The height at the sides of the landscape is huge
224 		// so we will never go there with the napalm
225 		return fixed::MAX_FIXED;
226 	}
227 
228 	// Return the correct height the square + the
229 	// height of all the napalm on this square
230 	// the napalm builds up and get higher so
231 	// we can go over small bumps
232 	return hmap->getGroundMaps().getHeight(x, y) +
233 		hmap->getGroundMaps().getNapalmHeight(x, y);
234 }
235 
simulateRmStep()236 void Napalm::simulateRmStep()
237 {
238 	int pset = napalmPoints_.front()->pset;
239 	while (!napalmPoints_.empty())
240 	{
241 		// Check if the entry should be removed
242 		NapalmEntry *entry = napalmPoints_.front();
243 		if (pset != entry->pset) break;
244 
245 		// Remove the first napalm point from the list
246 		// and remove the height from the landscape
247 		napalmPoints_.pop_front();
248 		int x = entry->posX;
249 		int y = entry->posY;
250 		delete entry;
251 
252 		unsigned int pointsCount = XY2_TO_UINT(x, y);
253 		std::map<unsigned int, int>::iterator countItor =
254 			napalmPointsCount_.find(pointsCount);
255 		if (countItor != napalmPointsCount_.end())
256 		{
257 			countItor->second--;
258 			if (countItor->second == 0) napalmPointsCount_.erase(countItor);
259 		}
260 
261 		context_->getLandscapeMaps().getGroundMaps().getNapalmHeight(x, y) -= params_->getNapalmHeight();
262 	}
263 }
264 
simulateAddStep()265 void Napalm::simulateAddStep()
266 {
267 	particleSet_++;
268 
269 	std::set<unsigned int> currentEdges = edgePoints_;
270 	edgePoints_.clear();
271 
272 	std::set<unsigned int>::iterator itor;
273 	for (itor = currentEdges.begin();
274 		itor != currentEdges.end();
275 		++itor)
276 	{
277 		unsigned int currentEdge = *itor;
278 		int x = UINT_TO_X(currentEdge);
279 		int y = UINT_TO_Y(currentEdge);
280 
281 		simulateAddEdge(x, y);
282 	}
283 }
284 
simulateAddEdge(int x,int y)285 void Napalm::simulateAddEdge(int x, int y)
286 {
287 	// Get the height of this point
288 	fixed height = getHeight(x, y);
289 
290 	if (!params_->getAllowUnderWater())
291 	{
292 		// Check napalm is under water
293 		fixed waterHeight = -10;
294 		LandscapeTex &tex = *context_->getLandscapeMaps().getDefinitions().getTex();
295 		if (tex.border->getType() == LandscapeTexType::eWater)
296 		{
297 			LandscapeTexBorderWater *water =
298 				(LandscapeTexBorderWater *) tex.border;
299        	 	waterHeight = water->height;
300 		}
301 
302 		if (height < waterHeight) // Water height
303 		{
304 			// Perhaps we could add a boiling water sound at some point
305 			return;
306 		}
307 	}
308 
309 	// Add this current point to the napalm map
310 	RandomGenerator &random = context_->getSimulator().getRandomGenerator();
311 	int offset = (random.getRandFixed("Napalm") * 31).asInt();
312 	NapalmEntry *newEntry = new NapalmEntry(x, y, offset, particleSet_);
313 	napalmPoints_.push_back(newEntry);
314 
315 	unsigned int pointsCount = XY2_TO_UINT(x, y);
316 	std::map<unsigned int, int>::iterator countItor =
317 		napalmPointsCount_.find(pointsCount);
318 	if (countItor == napalmPointsCount_.end())
319 	{
320 		napalmPointsCount_.insert(std::pair<unsigned int, int>(pointsCount, 1));
321 	}
322 	else
323 	{
324 		countItor->second++;
325 	}
326 
327 #ifndef S3D_SERVER
328 	if (!context_->getServerMode())
329 	{
330 		ParticleEmitter emitter;
331 		emitter.setAttributes(
332 			params_->getNapalmTime().asFloat(), params_->getNapalmTime().asFloat(),
333 			0.5f, 1.0f, // Mass
334 			0.01f, 0.02f, // Friction
335 			Vector(0.0f, 0.0f, 0.0f), Vector(0.0f, 0.0f, 0.0f), // Velocity
336 			Vector(1.0f, 1.0f, 1.0f), 0.9f, // StartColor1
337 			Vector(1.0f, 1.0f, 1.0f), 0.6f, // StartColor2
338 			Vector(1.0f, 1.0f, 1.0f), 0.0f, // EndColor1
339 			Vector(1.0f, 1.0f, 1.0f), 0.1f, // EndColor2
340 			1.5f, 1.5f, 1.5f, 1.5f, // Start Size
341 			1.5f, 1.5f, 1.5f, 1.5f, // EndSize
342 			Vector(0.0f, 0.0f, 0.0f), // Gravity
343 			params_->getLuminance(),
344 			false);
345 		Vector position1(float(x) + 0.5f, float(y) - 0.2f, 0.0f);
346 		Vector position2(float(x) - 0.5f, float(y) - 0.2f, 0.0f);
347 		Vector position3(float(x) + 0.0f, float(y) + 0.5f, 0.0f);
348 		emitter.emitNapalm(
349 			position1,
350 			ScorchedClient::instance()->getParticleEngine(),
351 			set_);
352 		emitter.emitNapalm(
353 			position2,
354 			ScorchedClient::instance()->getParticleEngine(),
355 			set_);
356 		emitter.emitNapalm(
357 			position3,
358 			ScorchedClient::instance()->getParticleEngine(),
359 			set_);
360 
361 		if (vPoint_) vPoint_->setValues(FixedVector::fromVector(position1));
362 
363 		// Add the ground scorch
364 		if (!GLStateExtension::getNoTexSubImage())
365 		{
366 			if (height == context_->getLandscapeMaps().getGroundMaps().getHeight(x, y))
367 			{
368 				if (RAND < params_->getGroundScorchPer().asFloat())
369 				{
370 					Vector pos(x, y);
371 					DeformTextures::deformLandscape(pos,
372 						(int) (deformSize + 1),
373 						ExplosionTextures::instance()->getScorchBitmap(
374 							params_->getDeformTexture()),
375 						deformMap);
376 				}
377 			}
378 		}
379 	}
380 #endif // #ifndef S3D_SERVER
381 
382 	context_->getLandscapeMaps().getGroundMaps().getNapalmHeight(x, y) += params_->getNapalmHeight();
383 
384 	// Calculate every time as the landscape may change
385 	// due to other actions
386 	fixed heightL = getHeight(x-1, y);
387 	fixed heightR = getHeight(x+1, y);
388 	fixed heightU = getHeight(x, y+1);
389 	fixed heightD = getHeight(x, y-1);
390 
391 	if (params_->getSingleFlow())
392 	{
393 		fixed *heightLR = 0;
394 		int LR = 0;
395 		if (heightL < heightR)
396 		{
397 			heightLR = &heightL;
398 			LR = -1;
399 		}
400 		else if (heightL == heightR)
401 		{
402 			if (random.getRandUInt("Napalm") % 2 == 0)
403 			{
404 				heightLR = &heightL;
405 				LR = -1;
406 			}
407 			else
408 			{
409 				heightLR = &heightR;
410 				LR = +1;
411 			}
412 		}
413 		else
414 		{
415 			heightLR = &heightR;
416 			LR = +1;
417 		}
418 
419 		fixed *heightUD = 0;
420 		int UD = 0;
421 		if (heightU < heightD)
422 		{
423 			heightUD = &heightU;
424 			UD = +1;
425 		}
426 		else if (heightU == heightD)
427 		{
428 			if (random.getRandUInt("Napalm") % 2 == 0)
429 			{
430 				heightUD = &heightU;
431 				UD = +1;
432 			}
433 			else
434 			{
435 				heightUD = &heightD;
436 				UD = -1;
437 			}
438 		}
439 		else
440 		{
441 			heightUD = &heightD;
442 			UD = -1;
443 		}
444 
445 		enum Direction
446 		{
447 			eUD,
448 			eLR,
449 			eNone
450 		} dir = eNone;
451 		if (*heightLR < *heightUD)
452 		{
453 			if (*heightLR < height) dir = eLR;
454 		}
455 		else if (*heightLR == *heightUD)
456 		{
457 			if (*heightLR < height)
458 			{
459 				if (random.getRandUInt("Napalm") % 2 == 0)
460 				{
461 					dir = eUD;
462 				}
463 				else
464 				{
465 					dir = eLR;
466 				}
467 			}
468 		}
469 		else
470 		{
471 			if (*heightUD < height)
472 			{
473 				if (*heightLR < height) dir = eUD;
474 			}
475 		}
476 
477 		switch (dir)
478 		{
479 		case eUD:
480 			edgePoints_.insert(XY_TO_UINT(x, y + UD));
481 			break;
482 		case eLR:
483 			edgePoints_.insert(XY_TO_UINT(x + LR, y));
484 			break;
485 		default:
486 			// None of the landscape is currently lower than the current point
487 			// Just wait, as this point will be now be covered in napalm
488 			// and may get higher and higher until it is
489 			edgePoints_.insert(XY_TO_UINT(x, y));
490 			break;
491 		}
492 	}
493 	else
494 	{
495 		int addedCount = 0;
496 		if (heightL < height)
497 		{
498 			// Move left
499 			addedCount++;
500 			edgePoints_.insert(XY_TO_UINT(x - 1, y));
501 		}
502 		if (heightR < height)
503 		{
504 			// Move right
505 			addedCount++;
506 			edgePoints_.insert(XY_TO_UINT(x + 1, y));
507 		}
508 		if (heightU < height)
509 		{
510 			// Move up
511 			addedCount++;
512 			edgePoints_.insert(XY_TO_UINT(x, y + 1));
513 		}
514 		if (heightD < height)
515 		{
516 			// Move down
517 			addedCount++;
518 			edgePoints_.insert(XY_TO_UINT(x, y - 1));
519 		}
520 		if (addedCount == 0)
521 		{
522 			// None of the landscape is currently lower than the current point
523 			// Just wait, as this point will be now be covered in napalm
524 			// and may get higher and higher until it is
525 			edgePoints_.insert(XY_TO_UINT(x, y));
526 		}
527 	}
528 }
529 
simulateDamage()530 void Napalm::simulateDamage()
531 {
532 	const int EffectRadius = params_->getEffectRadius();
533 
534 	// Store how much each tank is damaged
535 	// Keep in a map so we don't need to create multiple
536 	// damage actions.  Now we only create one per tank
537 	static std::map<unsigned int, fixed> TargetDamageCalc;
538 	TargetDamageCalc.clear();
539 
540 	// Add damage into the damage map for each napalm point that is near to
541 	// the tanks
542 	std::map<unsigned int, int>::iterator itor =
543 		napalmPointsCount_.begin();
544 	std::map<unsigned int, int>::iterator endItor =
545 		napalmPointsCount_.end();
546 	for (;itor != endItor; ++itor)
547 	{
548 		unsigned int pointsCount = itor->first;
549 		fixed count = fixed(itor->second);
550 		int x = UINT_TO_X(pointsCount);
551 		int y = UINT_TO_Y(pointsCount);
552 
553 		fixed height = context_->getLandscapeMaps().getGroundMaps().
554 			getHeight(x, y);
555 		FixedVector position(
556 			fixed(x),
557 			fixed(y),
558 			height);
559 
560 		if (params_->getLandscapeErosion() > 0)
561 		{
562 			DeformLandscape::deformLandscape(*context_, position,
563 				1, true, params_->getLandscapeErosion(), params_->getDeformTexture());
564 			TargetDamageCalc::explosion(
565 				*context_, weapon_, weaponContext_,
566 				position, 1, 0, true, false);
567 		}
568 
569 		std::map<unsigned int, Target *> collisionTargets;
570 		context_->getTargetSpace().getCollisionSet(position,
571 			fixed(EffectRadius), collisionTargets);
572 		std::map<unsigned int, Target *>::iterator itor;
573 		for (itor = collisionTargets.begin();
574 			itor != collisionTargets.end();
575 			++itor)
576 		{
577 			Target *target = (*itor).second;
578 			if (target->getAlive())
579 			{
580 				std::map<unsigned int, fixed>::iterator damageItor =
581 					TargetDamageCalc.find(target->getPlayerId());
582 				if (damageItor == TargetDamageCalc.end())
583 				{
584 					TargetDamageCalc[target->getPlayerId()] = count * params_->getHurtPerSecond();
585 				}
586 				else
587 				{
588 					TargetDamageCalc[target->getPlayerId()] += count * params_->getHurtPerSecond();
589 				}
590 			}
591 		}
592 	}
593 
594 	// Add all the damage to the tanks (if any)
595 	if (!TargetDamageCalc.empty())
596 	{
597 		std::map<unsigned int, fixed>::iterator damageItor;
598 		for (damageItor = TargetDamageCalc.begin();
599 			damageItor != TargetDamageCalc.end();
600 			++damageItor)
601 		{
602 			Target *target =
603 				context_->getTargetContainer().getTargetById(damageItor->first);
604 			fixed damage = (*damageItor).second;
605 
606 			// Set this target to burnt
607 			if (target->getRenderer() &&
608 				!params_->getNoObjectDamage())
609 			{
610 				target->getRenderer()->targetBurnt();
611 			}
612 
613 			// Add damage to the tank
614 			// If allowed for this target type (mainly for trees)
615 			if (!target->getTargetState().getNoDamageBurn())
616 			{
617 				if (burnedTargets_.find(target->getPlayerId()) == burnedTargets_.end())
618 				{
619 					burnedTargets_.insert(target->getPlayerId());
620 					addBurnAction(target);
621 				}
622 
623 				// Do last as it may remove the target
624 				TargetDamageCalc::damageTarget(*context_, target->getPlayerId(), weapon_,
625 					weaponContext_, damage, true, false, false);
626 			}
627 		}
628 		TargetDamageCalc.clear();
629 	}
630 }
631 
addBurnAction(Target * target)632 void Napalm::addBurnAction(Target *target)
633 {
634 	Weapon *weapon = target->getBurnAction();
635 	if (weapon)
636 	{
637 		if (context_->getOptionsGame().getActionSyncCheck())
638 		{
639 			context_->getSimulator().addSyncCheck(
640 				S3D::formatStringBuffer("BurnAction: %u %s",
641 					target->getPlayerId(),
642 					weapon->getParent()->getName()));
643 		}
644 
645 		FixedVector position = target->getLife().getTargetPosition();
646 		FixedVector velocity;
647 		WeaponFireContext weaponContext(weaponContext_.getPlayerId(),
648 			weaponContext_.getInternalContext().getSelectPositionX(),
649 			weaponContext_.getInternalContext().getSelectPositionY(),
650 			weaponContext_.getInternalContext().getVelocityVector(),
651 			weaponContext_.getInternalContext().getReferenced(),
652 			false);
653 		weapon->fire(*context_, weaponContext,
654 			position, velocity);
655 		StatsLogger::instance()->weaponFired(weapon, true);
656 	}
657 }
658