1 /*
2 Copyright (C) 2007, 2010 - Bit-Blot
3
4 This file is part of Aquaria.
5
6 Aquaria is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program 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.
14
15 See the GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21 #include "Shot.h"
22 #include "DSQ.h"
23 #include "Game.h"
24 #include "Avatar.h"
25
26 #include "../BBGE/MathFunctions.h"
27
28 Shot::Shots Shot::shots;
29 Shot::Shots Shot::deleteShots;
30 Shot::ShotBank Shot::shotBank;
31 unsigned int Shot::shotsIter = 0;
32
33 std::string Shot::shotBankPath = "";
34
ShotData()35 ShotData::ShotData()
36 {
37 avatarKickBack= 0;
38 avatarKickBackTime = 0;
39 effectTime = 0;
40 rotIncr = 0;
41 alwaysDoHitEffects = 1;
42 bounceType = BOUNCE_NONE;
43 segments = false;
44 damage = 0;
45 maxSpeed = 800;
46 homing = 300;
47 scale = Vector(1,1);
48 segScale = Vector(1,1);
49 numSegs = 0;
50 segDist = 16;
51 blendType = RenderObject::BLEND_DEFAULT;
52 collideRadius = 8;
53 damageType = DT_ENEMY_ENERGYBLAST;
54 lifeTime = 8;
55 spinSpeed = 0;
56 segTaper = 0;
57 hitWalls = true;
58 invisible = checkDamageTarget = false;
59 homingMax = 0;
60 homingIncr = 0;
61 dieOnHit = 1;
62 dieOnKill = false;
63 hitEnts = 1;
64 wallHitRadius = 0;
65 rotateToVel = 1;
66 waveSpeed = waveMag = 0;
67 ignoreShield = false;
68 }
69
readEquals2(T & in)70 template <typename T> void readEquals2(T &in)
71 {
72 std::string temp;
73 in >> temp;
74 }
75
updatePosition()76 void Shot::updatePosition()
77 {
78 if (emitter)
79 {
80 emitter->position = this->position + offset;
81 }
82 }
83
bankLoad(const std::string & file,const std::string & path)84 void ShotData::bankLoad(const std::string &file, const std::string &path)
85 {
86 std::string usef = path + file + ".txt";
87
88 // FIXME: Li's attack and the pet blaster's energy balls are missing
89 // the CheckDamageTarget flag, preventing entities (such as seahorses)
90 // from properly ignoring the shots. In lieu of modifying the
91 // separately-distributed data files, we add a hack here to set the
92 // flag on those two shot types.
93 if (nocasecmp(file,"li") == 0 || nocasecmp(file,"petblasterfire") == 0)
94 {
95 checkDamageTarget = true;
96 }
97
98 this->name = file;
99 stringToLower(this->name);
100
101 debugLog(usef);
102 char *data = readFile(core->adjustFilenameCase(usef).c_str());
103 if (!data)
104 return;
105 SimpleIStringStream inf(data, SimpleIStringStream::TAKE_OVER);
106 std::string token;
107 while (inf >> token)
108 {
109 readEquals2(inf);
110 if (token == "TrailPrt")
111 inf >> trailPrt;
112 else if (token == "Texture")
113 inf >> texture;
114 else if (token == "Scale")
115 inf >> scale.x >> scale.y;
116 else if (token == "BounceSfx")
117 inf >> bounceSfx;
118 else if (token == "HitSfx")
119 inf >> hitSfx;
120 else if (token == "FireSfx")
121 inf >> fireSfx;
122 else if (token == "HitPrt")
123 inf >> hitPrt;
124 else if (token == "BouncePrt")
125 inf >> bouncePrt;
126 else if (token == "FirePrt")
127 inf >> firePrt;
128 else if (token == "NumSegs")
129 inf >> numSegs;
130 else if (token == "SegDist")
131 inf >> segDist;
132 else if (token == "SegTexture")
133 inf >> segTexture;
134 else if (token == "SegTaper")
135 inf >> segTaper;
136 else if (token == "SegScale")
137 inf >> segScale.x >> segScale.y;
138 else if (token == "Homing")
139 inf >> homing;
140 else if (token == "HomingIncr")
141 inf >> homingIncr;
142 else if (token == "HomingMax")
143 inf >> homingMax;
144 else if (token == "MaxSpeed")
145 inf >> maxSpeed;
146 else if (token == "Gravity")
147 inf >> gravity.x >> gravity.y;
148 else if (token == "Spin")
149 inf >> spinSpeed;
150 else if (token == "AvatarKickBack")
151 inf >> avatarKickBack;
152 else if (token == "AvatarKickBackTime")
153 inf >> avatarKickBackTime;
154 else if (token == "CollideRadius")
155 inf >> collideRadius;
156 else if (token == "RotIncr")
157 inf >> rotIncr;
158 else if (token == "RotateToVel")
159 inf >> rotateToVel;
160 else if (token == "WallHitRadius")
161 inf >> wallHitRadius;
162 else if (token == "Wave")
163 inf >> waveMag >> waveSpeed;
164 else if (token == "BounceType" || token == "Blend")
165 {
166 std::string bt;
167 inf >> bt;
168 if (bt == "BOUNCE_NONE")
169 bounceType = BOUNCE_NONE;
170 else if (bt == "BOUNCE_REAL")
171 bounceType = BOUNCE_REAL;
172 }
173 else if (token == "BlendType")
174 {
175 std::string bt;
176 inf >> bt;
177 if (bt == "BLEND_ADD")
178 blendType = RenderObject::BLEND_ADD;
179 }
180 else if (token == "Damage")
181 {
182 inf >> damage;
183 }
184 else if (token == "EffectTime")
185 inf >> effectTime;
186 else if (token == "LifeTime" || token == "Life")
187 inf >> lifeTime;
188 else if (token == "HitObs")
189 inf >> hitWalls;
190 else if (token == "HitEnts")
191 inf >> hitEnts;
192 else if (token == "SpawnEntity")
193 inf >> spawnEntity;
194 else if (token == "DamageType")
195 {
196 std::string bt;
197 inf >> bt;
198 if (bt == "DT_AVATAR_ENERGYBLAST")
199 damageType = DT_AVATAR_ENERGYBLAST;
200 else if (bt == "DT_AVATAR_SHOCK")
201 damageType = DT_AVATAR_SHOCK;
202 else if (bt == "DT_AVATAR_BITE")
203 damageType = DT_AVATAR_BITE;
204 else if (bt == "DT_AVATAR_PETBITE")
205 damageType = DT_AVATAR_PETBITE;
206 else if (bt == "DT_AVATAR_LANCE")
207 damageType = DT_AVATAR_LANCE;
208 else if (bt == "DT_AVATAR_CREATORSHOT")
209 damageType = DT_AVATAR_CREATORSHOT;
210 else if (bt == "DT_AVATAR_LIZAP")
211 damageType = DT_AVATAR_LIZAP;
212 else if (bt == "DT_AVATAR_DUALFORMLI")
213 damageType = DT_AVATAR_DUALFORMLI;
214 else if (bt == "DT_AVATAR_DUALFORMNAIJA")
215 damageType = DT_AVATAR_DUALFORMNAIJA;
216 else if (bt == "DT_AVATAR_BUBBLE")
217 damageType = DT_AVATAR_BUBBLE;
218 else if (bt == "DT_AVATAR_VINE")
219 damageType = DT_AVATAR_VINE;
220 else if (bt == "DT_NONE")
221 damageType = DT_NONE;
222 else if (bt == "DT_AVATAR_SEED")
223 damageType = DT_AVATAR_SEED;
224 else if (bt == "DT_ENEMY_INK")
225 damageType = DT_ENEMY_INK;
226 else if (bt == "DT_ENEMY_POISON")
227 damageType = DT_ENEMY_POISON;
228 else if (bt == "DT_ENEMY_CREATOR")
229 damageType = DT_ENEMY_CREATOR;
230 else if (bt == "DT_ENEMY_MANTISBOMB")
231 damageType = DT_ENEMY_MANTISBOMB;
232 else
233 damageType = (DamageType)atoi(bt.c_str());
234 }
235 else if (token == "Invisible")
236 inf >> invisible;
237 else if (token == "CheckDamageTarget")
238 inf >> checkDamageTarget;
239 else if (token == "AlwaysDoHitEffects")
240 inf >> alwaysDoHitEffects;
241 else if (token == "DieOnHit")
242 inf >> dieOnHit;
243 else if (token == "IgnoreShield")
244 inf >> ignoreShield;
245 else if (token == "DieOnKill")
246 inf >> dieOnKill;
247 else
248 {
249 // if having weirdness, check for these
250 errorLog(file + " : unidentified token: " + token);
251 }
252
253
254 }
255 }
256
fire(bool playSfx)257 void Shot::fire(bool playSfx)
258 {
259 if (shotData)
260 {
261 if (!shotData->firePrt.empty())
262 {
263 dsq->spawnParticleEffect(shotData->firePrt, position);
264 }
265
266 if (!fired)
267 {
268 if (!shotData->fireSfx.empty() && playSfx)
269 {
270 dsq->playPositionalSfx(shotData->fireSfx, position);
271 }
272 fired = true;
273 }
274 }
275 }
276
277
Shot()278 Shot::Shot() : Quad(), Segmented(0,0)
279 {
280 addType(SCO_SHOT);
281 extraDamage= 0;
282 waveTimer = rand()%314;
283 emitter = 0;
284 lifeTime = 0;
285 shotData = 0;
286 targetPt = -1;
287 fired = false;
288 target = 0;
289 dead = false;
290 damageType = DT_NONE;
291 checkDamageTarget = false;
292 enqueuedForDelete = false;
293 shotIdx = shots.size();
294 shots.push_back(this);
295 }
296
loadShotCallback(const std::string & filename,intptr_t param)297 void loadShotCallback(const std::string &filename, intptr_t param)
298 {
299 ShotData shotData;
300
301 std::string ident;
302 int first = filename.find_last_of('/')+1;
303 ident = filename.substr(first, filename.find_last_of('.')-first);
304 stringToLower(ident);
305 debugLog(ident);
306 shotData.bankLoad(ident, Shot::shotBankPath);
307 Shot::shotBank[ident] = shotData;
308 }
309
loadShotBank(const std::string & bank1,const std::string & bank2)310 void Shot::loadShotBank(const std::string &bank1, const std::string &bank2)
311 {
312 clearShotBank();
313
314 shotBankPath = bank1;
315 forEachFile(bank1, ".txt", loadShotCallback, 0);
316
317 if (!bank2.empty())
318 {
319 shotBankPath = bank2;
320 forEachFile(bank2, ".txt", loadShotCallback, 0);
321 }
322
323 shotBankPath = "";
324 }
325
clearShotBank()326 void Shot::clearShotBank()
327 {
328 shotBank.clear();
329 for (Shot::Shots::iterator i = Shot::shots.begin(); i != Shot::shots.end(); i++)
330 {
331 (*i)->shotData = 0;
332 }
333 }
334
getShotData(const std::string & ident)335 ShotData* Shot::getShotData(const std::string &ident)
336 {
337 std::string id = ident;
338 stringToLower(id);
339 return &shotBank[id];
340 }
341
loadBankShot(const std::string & ident,Shot * setter)342 void Shot::loadBankShot(const std::string &ident, Shot *setter)
343 {
344 if (setter)
345 {
346 std::string id = ident;
347 stringToLower(id);
348 //setter->shotData = &shotBank[id];
349 setter->applyShotData(&shotBank[id]);
350 }
351 }
352
applyShotData(ShotData * shotData)353 void Shot::applyShotData(ShotData *shotData)
354 {
355 if (shotData)
356 {
357 this->shotData = shotData;
358 this->setBlendType(shotData->blendType);
359 this->homingness = shotData->homing;
360 this->maxSpeed = shotData->maxSpeed;
361 this->setTexture(shotData->texture);
362 this->scale = shotData->scale;
363 this->lifeTime = shotData->lifeTime;
364 this->collideRadius = shotData->collideRadius;
365 this->renderQuad = !shotData->invisible;
366 this->gravity = shotData->gravity;
367 this->damageType = shotData->damageType;
368 this->checkDamageTarget = shotData->checkDamageTarget;
369 if (!shotData->trailPrt.empty())
370 {
371 setParticleEffect(shotData->trailPrt);
372 }
373
374 if (shotData->numSegs > 0)
375 {
376 segments.resize(shotData->numSegs);
377 for (int i = segments.size()-1; i >=0 ; i--)
378 {
379 Quad *flame = new Quad;
380 flame->setTexture(shotData->segTexture);
381 flame->scale = shotData->segScale - Vector(shotData->segTaper, shotData->segTaper)*(i);
382 flame->setBlendType(this->blendType);
383 flame->alpha = 0.5;
384 dsq->game->addRenderObject(flame, LR_PARTICLES);
385 segments[i] = flame;
386 segments[i]->position = position;
387 }
388
389 maxDist = shotData->segDist;
390 minDist = 0;
391
392 initSegments(position);
393 }
394 }
395 }
396
suicide()397 void Shot::suicide()
398 {
399 setLife(1);
400 setDecayRate(20);
401 velocity = 0;
402 fadeAlphaWithLife = true;
403 dead = true;
404 onHitWall();
405 }
406
setParticleEffect(const std::string & particleEffect)407 void Shot::setParticleEffect(const std::string &particleEffect)
408 {
409 if(dead)
410 return;
411 if(particleEffect.empty())
412 {
413 if(emitter)
414 emitter->stop();
415 return;
416 }
417 if(!emitter)
418 {
419 emitter = new ParticleEffect;
420 dsq->game->addRenderObject(emitter, LR_PARTICLES);
421 }
422 emitter->load(particleEffect);
423 emitter->start();
424 }
425
onEndOfLife()426 void Shot::onEndOfLife()
427 {
428 destroySegments(0.2);
429 dead = true;
430
431 if (emitter)
432 {
433 emitter->killParticleEffect();
434 emitter = 0;
435 }
436
437 if (!enqueuedForDelete)
438 {
439 enqueuedForDelete = true;
440 deleteShots.push_back(this);
441 }
442 }
443
doHitEffects()444 void Shot::doHitEffects()
445 {
446 BBGE_PROF(Shot_doHitEffects);
447 if (shotData)
448 {
449 if (!shotData->hitPrt.empty())
450 dsq->spawnParticleEffect(shotData->hitPrt, position);
451 if (!shotData->hitSfx.empty())
452 dsq->playPositionalSfx(shotData->hitSfx, position);
453 }
454 }
455
onHitWall()456 void Shot::onHitWall()
457 {
458 BBGE_PROF(Shot_onHitWall);
459 doHitEffects();
460 updateSegments(position);
461 destroySegments(0.2);
462 if (emitter)
463 {
464 emitter->killParticleEffect();
465 emitter = 0;
466 }
467
468 if (shotData)
469 {
470 if (!shotData->spawnEntity.empty())
471 {
472 dsq->game->createEntity(shotData->spawnEntity, 0, position, 0, false, "", ET_ENEMY, true);
473 //(shotData->spawnEntity, 0, position, 0, false, "");
474 if (shotData->spawnEntity == "NatureFormFlowers")
475 {
476 dsq->game->registerSporeDrop(position, 0);
477 }
478 else
479 {
480 dsq->game->registerSporeDrop(position, 2);
481 }
482 }
483 }
484 }
485
killAllShots()486 void Shot::killAllShots()
487 {
488 for (Shots::iterator i = shots.begin(); i != shots.end(); ++i)
489 (*i)->safeKill();
490 }
491
clearShotGarbage()492 void Shot::clearShotGarbage()
493 {
494 for(size_t i = 0; i < deleteShots.size(); ++i)
495 {
496 Shot *s = deleteShots[i];
497 const unsigned int idx = s->shotIdx;
498 // move last shot to deleted one and shorten vector
499 if(idx < shots.size() && shots[idx] == s)
500 {
501 Shot *lastshot = shots.back();
502 shots[idx] = lastshot;
503 lastshot->shotIdx = idx;
504 shots.pop_back();
505 }
506 else
507 errorLog("Shot::clearShotGarbage(): wrong index in shot vector");
508 }
509 deleteShots.clear();
510 }
511
512
reflectFromEntity(Entity * e)513 void Shot::reflectFromEntity(Entity *e)
514 {
515 Entity *oldFirer = firer;
516 DamageType dt = getDamageType();
517 if (dt >= DT_ENEMY && dt < DT_ENEMY_MAX)
518 {
519 firer = e;
520 target = oldFirer;
521 //int d = (int)dt;
522 //d += DT_AVATAR;oll
523 //damageType = DamageType(d);
524 }
525 }
526
targetDied(Entity * target)527 void Shot::targetDied(Entity *target)
528 {
529 int c = 0;
530 for (Shots::iterator i = shots.begin(); i != shots.end(); i++)
531 {
532 if ((*i)->target == target)
533 {
534 debugLog("removing target from shot");
535 (*i)->target = 0;
536 }
537 if ((*i)->firer == target)
538 {
539 (*i)->firer = 0;
540 }
541 c++;
542 }
543
544
545 /*
546 std::ostringstream os;
547 os << "# of shots in list: " << c;
548 debugLog(os.str());
549 */
550 }
551
isHitEnts() const552 bool Shot::isHitEnts() const
553 {
554 if (!shotData || shotData->hitEnts)
555 {
556 return true;
557 }
558 return false;
559 }
560
hitEntity(Entity * e,Bone * b)561 void Shot::hitEntity(Entity *e, Bone *b)
562 {
563 if (!dead)
564 {
565 bool die = true;
566 bool doEffects=true;
567
568 if (e)
569 {
570 DamageData d;
571 d.attacker = firer;
572 d.bone = b;
573 d.damage = getDamage();
574 d.damageType = getDamageType();
575 d.hitPos = position;
576 d.shot = this;
577 if (shotData)
578 d.effectTime = shotData->effectTime;
579 if ((firer && firer->getEntityType() == ET_AVATAR))
580 d.form = dsq->continuity.form;
581
582 if (!e->canShotHit(d))
583 return;
584
585
586 if (damageType == DT_AVATAR_BITE)
587 {
588 //debugLog("Shot::hitEntity bittenEntities.push_back");
589 dsq->game->avatar->bittenEntities.push_back(e);
590 }
591
592 bool damaged = e->damage(d);
593
594 // doesn't have anything to do with effectTime
595 if (shotData)
596 {
597 if (!damaged && checkDamageTarget && !shotData->alwaysDoHitEffects)
598 {
599 doEffects = false;
600 }
601 }
602
603 if (e->isEntityDead())
604 {
605 die = shotData ? shotData->dieOnKill : false;
606 }
607
608 if (firer)
609 {
610 firer->shotHitEntity(e, this, b);
611 }
612
613
614 //debugLog("Shot hit enemy: " + e->name);
615 }
616 else
617 {
618 //debugLog("Shot hit 0 enemy");
619 }
620
621 if (doEffects)
622 doHitEffects();
623
624 target = 0;
625
626 if ((!shotData || shotData->dieOnHit) && die)
627 {
628 lifeTime = 0;
629 fadeAlphaWithLife = true;
630 velocity = 0;
631 setLife(1);
632 setDecayRate(10);
633 destroySegments(0.1);
634 dead = true;
635 if (emitter)
636 {
637 emitter->killParticleEffect();
638 emitter = 0;
639 }
640 }
641 }
642
643 //d.bone = c.bone;
644 }
645
noSegs()646 void Shot::noSegs()
647 {
648 if (numSegments > 0)
649 {
650 destroySegments();
651 }
652 }
653
getCollideRadius() const654 int Shot::getCollideRadius() const
655 {
656 if (shotData)
657 return shotData->collideRadius;
658 return 0;
659 }
660
getDamage() const661 float Shot::getDamage() const
662 {
663 if (shotData)
664 {
665 return shotData->damage + extraDamage;
666 }
667 return 0;
668 }
669
getDamageType() const670 DamageType Shot::getDamageType() const
671 {
672 return damageType;
673 }
674
setAimVector(const Vector & aim)675 void Shot::setAimVector(const Vector &aim)
676 {
677 velocity = aim;
678 if (shotData)
679 {
680 velocity.setLength2D(shotData->maxSpeed);
681 }
682 /*
683 std::ostringstream os;
684 os << "setting aim vector(" << aim.x << ", " << aim.y << ") to vel(" << velocity.x << ", " << velocity.y << ")";
685 debugLog(os.str());
686 */
687 }
688
setTarget(Entity * target)689 void Shot::setTarget(Entity *target)
690 {
691 this->target = target;
692 }
693
setTargetPoint(int pt)694 void Shot::setTargetPoint(int pt)
695 {
696 targetPt = pt;
697 }
698
isObstructed(float dt) const699 bool Shot::isObstructed(float dt) const
700 {
701 if (shotData->wallHitRadius == 0)
702 {
703 TileVector t(position + velocity * dt);
704 if (dsq->game->isObstructed(t)
705 || dsq->game->isObstructed(TileVector(t.x+1, t.y))
706 || dsq->game->isObstructed(TileVector(t.x-1, t.y))
707 || dsq->game->isObstructed(TileVector(t.x, t.y+1))
708 || dsq->game->isObstructed(TileVector(t.x, t.y-1)))
709 return true;
710 }
711 else
712 {
713 if (dsq->game->collideCircleWithGrid(position, shotData->wallHitRadius))
714 {
715 return true;
716 }
717 }
718
719 return false;
720 }
721
onUpdate(float dt)722 void Shot::onUpdate(float dt)
723 {
724 if (dsq->game->isPaused()) return;
725 if (dsq->game->isWorldPaused()) return;
726 if (!shotData) return;
727
728
729 if (target)
730 {
731 if (target->getState() == Entity::STATE_DEATHSCENE)
732 target = 0;
733 else if (target->alpha == 0)
734 target = 0;
735 }
736 if (life >= 1.0f)
737 {
738 if (velocity.isZero())
739 {
740 //velocity = Vector(rand()%100-50, rand()%100-50);
741 }
742 else if (velocity.isLength2DIn(maxSpeed*0.75f))
743 {
744 velocity.setLength2D(maxSpeed);
745 }
746 }
747
748 /*
749 if (!gravity.isZero())
750 {
751 velocity += shotData->gravity * dt;
752 }
753 */
754
755 /*
756 std::ostringstream os;
757 os << "shotVel(" << velocity.x << ", " << velocity.y << ")";
758 debugLog(os.str());
759 */
760
761 homingness += shotData->homingIncr*dt;
762 if (shotData->homingMax != 0 && homingness > shotData->homingMax)
763 {
764 homingness = shotData->homingMax;
765 }
766
767 if (shotData->waveMag)
768 {
769 waveTimer += shotData->waveSpeed * dt;
770 float off = sinf(waveTimer)*shotData->waveMag;
771 Vector side = velocity.getPerpendicularLeft();
772 side.setLength2D(off);
773 offset = side;
774 }
775
776 if (shotData->rotIncr)
777 {
778 Vector add = velocity.getPerpendicularRight();
779 add.setLength2D(shotData->rotIncr);
780 velocity += add * dt;
781 }
782 //emitter.update(dt);
783 if (emitter)
784 {
785 emitter->position = position + offset;
786 if (emitter->isDead())
787 emitter = 0;
788 }
789
790 if (target && lifeTime > 0 && damageType != DT_NONE)
791 {
792 if (!target->isDamageTarget(damageType))
793 {
794 target = 0;
795 }
796 }
797
798 Quad::onUpdate(dt);
799 updateSegments(position);
800 if (!dead)
801 {
802 if (lifeTime > 0)
803 {
804 lifeTime -= dt;
805 if (lifeTime <= 0)
806 {
807 velocity = Vector(0,0);
808
809 setDecayRate(10);
810 destroySegments(0.1);
811 lifeTime = 0;
812 fadeAlphaWithLife = true;
813 setLife(1);
814 return;
815 }
816 }
817 //TileVector t(position);
818 Vector diff;
819 if (target)
820 diff = target->getTargetPoint(targetPt) - this->position;
821 diff.z = 0;
822 if (shotData->hitWalls)
823 {
824 if (isObstructed(dt))
825 {
826 switch(shotData->bounceType)
827 {
828 case BOUNCE_REAL:
829 {
830 // Should have been checked in last onUpdate()
831 // If it is stuck now, it must have been fired from a bad position,
832 // the obstruction map changed, or it was a bouncing beast form shot,
833 // fired from avatar head - which may be inside a wall.
834 // In any of these cases, there is nowhere to bounce, so we let the shot die. -- FG
835 if (!isObstructed(0))
836 {
837 if (!shotData->bounceSfx.empty())
838 {
839 dsq->playPositionalSfx(shotData->bounceSfx, position);
840 if(!shotData->bouncePrt.empty())
841 dsq->spawnParticleEffect(shotData->bouncePrt, position);
842 }
843 float len = velocity.getLength2D();
844 Vector I = velocity/len;
845 Vector N = dsq->game->getWallNormal(position);
846
847 if (!N.isZero())
848 {
849 //2*(-I dot N)*N + I
850 velocity = 2*(-I.dot(N))*N + I;
851 velocity *= len;
852 }
853 break;
854 }
855 // fall through
856 }
857 default:
858 {
859 suicide();
860 }
861 break;
862 }
863 }
864 }
865
866 if (!velocity.isZero() && target)
867 {
868 Vector add = diff;
869 add.setLength2D(homingness*dt);
870 velocity += add;
871 velocity.capLength2D(maxSpeed);
872 }
873
874 if (!dead)
875 {
876 if (shotData->spinSpeed != 0)
877 {
878 if (velocity.x > 0)
879 {
880 rotation.z += shotData->spinSpeed*dt;
881 }
882 else if (velocity.x < 0)
883 {
884 rotation.z -= shotData->spinSpeed*dt;
885 }
886 }
887 else
888 {
889 if (shotData->rotateToVel)
890 rotateToVec(velocity, 0, 0);
891 }
892 }
893
894 }
895 }
896
897 // HACK : move this to a common base shared with Entity
rotateToVec(Vector addVec,float time,int offsetAngle)898 void Shot::rotateToVec(Vector addVec, float time, int offsetAngle)
899 {
900 // HACK: this mucks up wall normals for some reason
901 // if (vel.getSquaredLength2D() <= 0) return;
902 if (addVec.x == 0 && addVec.y == 0)
903 {
904 rotation.interpolateTo(Vector(0,0,0), time, 0);
905 }
906 else
907 {
908 float angle=0;
909 MathFunctions::calculateAngleBetweenVectorsInDegrees(Vector(0,0,0), addVec, angle);
910 angle = 180-(360-angle);
911 angle += offsetAngle;
912 if (rotation.z <= -90 && angle >= 90)
913 {
914 rotation.z = 360 + rotation.z;
915 }
916 if (rotation.z >= 90 && angle <= -90)
917 rotation.z = rotation.z - 360;
918
919
920 rotation.interpolateTo(Vector(0,0,angle), time, 0);
921 }
922 }
923
924