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