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