1 /*
2  * Shotgun Debugger
3  * Copyright 2005 Game Creation Society
4  *
5  * Programmed by Matt Sarnoff
6  * http://www.contrib.andrew.cmu.edu/~msarnoff
7  * http://www.gamecreation.org
8  *
9  * objects.cpp - object (entity, particle, bullet, laser) routines
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24  *
25  */
26 
27 #include "sdb.h"
28 
29 //Level currLevel;
30 ObjectType objType[NUM_OBJ_TYPES];
31 ParticleType partType[NUM_PART_TYPES];
32 BulletType bltType[NUM_BLT_TYPES];
33 LaserType lasType[NUM_LASER_TYPES];
34 WeaponType wpnType[NUM_WPN_TYPES];
35 int oID;
36 
37 // Update an animation
update()38 void Animation::update()
39 {
40   if (numFrames > 1 && !done)
41   {
42     if (frame == stopAtFrame)
43     { setDirection(0); stopAtFrame = -1; }
44 
45     animTime += timer.dT();
46     if (animTime >= delay)
47     {
48       frame += direction;
49       animTime = 0;
50 
51       if (direction > 0)
52       {
53         if (frame >= numFrames)
54         { frame = 0; numLoops++; }
55       }
56       else if (direction < 0)
57       {
58         if (frame < 0)
59         { frame = numFrames-1; numLoops++; }
60       }
61 
62       if (loop > INF)
63         if (numLoops >= loop)
64           done = true;
65     }
66   }
67 }
68 
update()69 void ModelAnimation::update()
70 {
71   if (model < 0 || models[model].pObject.size() <= 1)
72     return;
73 
74   tAnimationInfo *pAnim = &(models[model].pAnimations[
75   (currAnim == ANIM_BASE) ? baseAnim : transientAnim]);
76 
77   nextFrame = (currFrame + 1) % pAnim->endFrame;
78 
79   // Loop to the start if we're on the base animation. If not,
80   // return to the base animation.
81   if (nextFrame == 0)
82   {
83     if (currAnim == ANIM_BASE)
84     {
85       nextFrame = pAnim->startFrame;
86       //nextFrame = (currFrame+1) % pAnim->endFrame;
87     }
88     else
89     {
90       currAnim = ANIM_BASE;
91       transientAnim = -1;
92       tAnimationInfo *pAnim = &(models[model].pAnimations[baseAnim]);
93       currFrame = pAnim->startFrame;
94       nextFrame = (currFrame + 1) % pAnim->endFrame;
95     }
96   }
97 
98   animTime += timer.dT();
99 
100   if (animTime >= 1.0/speed)
101   {
102     currFrame = nextFrame;
103     animTime = 0;
104   }
105 }
106 
setAnimation(int val,bool anim)107 void ModelAnimation::setAnimation(int val, bool anim)
108 {
109   if (model >= 0 && models[model].pObject.size() > 1)
110   {
111     //printf("%d\n", val);
112     if (anim == ANIM_BASE && baseAnim != val)
113     {
114       baseAnim = val;
115       if (currAnim == ANIM_BASE)
116       {
117         tAnimationInfo *pAnim = &(models[model].pAnimations[baseAnim]);
118         currFrame = pAnim->startFrame;
119 
120         nextFrame = (currFrame + 1) % pAnim->endFrame;
121       }
122     }
123     // If we specify a transient ("one-shot") animation, we switch
124     // to it automatically
125     else if (anim == ANIM_TRANSIENT && transientAnim != val)
126     {
127       transientAnim = val;
128       currAnim = ANIM_TRANSIENT;
129       tAnimationInfo *pAnim = &(models[model].pAnimations[transientAnim]);
130       currFrame = pAnim->startFrame;
131       nextFrame = (currFrame + 1) % pAnim->endFrame;
132     }
133   }
134 }
135 
draw()136 void ModelAnimation::draw()
137 {
138   if (model >= 0)
139   {
140     glBindTexture(GL_TEXTURE_2D, textures[models[model].texture]);
141     models[model].draw((currAnim == ANIM_BASE) ? baseAnim : transientAnim, currFrame, nextFrame, animTime, speed);
142   }
143 }
144 
145 // Set properties of a force.
146 // If mode is FORCE_RECT, a and b are the vector's x and y components
147 // If mode is FORCE_POLAR, a and b are the vector's magnitude and angle
set(int obj,float dur,float a,float b,bool mode)148 void Force::set(int obj, float dur, float a, float b, bool mode)
149 {
150   target = obj;
151   duration = dur;
152   (mode == FORCE_RECT) ? force.set(a,b) : force.set(a*cos(b), a*sin(b));
153   reset();
154 }
155 
156 // Applies a force to the target object
apply()157 void Force::apply()
158 {
159   if (active)
160   {
161     forceTime += timer.dT();
162     currLevelObjs[target]->changeVel(force);
163     if (forceTime >= duration) destroyMe = true;
164   }
165 }
166 
reset()167 void Shockwave::reset()
168 {
169   shockTime = 0;
170   active = false;
171   destroyMe = false;
172 }
173 
set(float x,float y,float ir,float irs,float otr,float ors,float dur,float frc,float hlth,bool ag)174 void Shockwave::set(float x, float y, float ir, float irs, float otr, float ors, float dur, float frc, float hlth, bool ag)
175 {
176   pos.set(x, y);
177   innerRad = ir;
178   innerRadSpeed = irs;
179   outerRad = otr;
180   outerRadSpeed = ors;
181   duration = dur;
182   force = frc;
183   health = hlth;
184   affectGladiators = ag;
185   active = false;
186   destroyMe = false;
187   shockTime = duration;
188 }
189 
update()190 void Shockwave::update()
191 {
192   if (active)
193   {
194     innerRad += innerRadSpeed * timer.dT();
195     outerRad += outerRadSpeed * timer.dT();
196 
197     shockTime -= timer.dT();
198 
199     if (shockTime <= 0.0)
200     { active = false; destroyMe = true; }
201   }
202 }
203 
detect(Vector2D p,float * resultForce,float * resultHealth)204 bool Shockwave::detect(Vector2D p, float *resultForce, float *resultHealth)
205 {
206   if (active)
207   {
208     float distSq = distSquared(pos, p);
209     if (distSq >= innerRad*innerRad && distSq <= outerRad*outerRad)
210     {
211       float factor = 1.0-(distSq/(outerRad*outerRad));
212       *resultHealth = (health == SHOCK_ESG) ? SHOCK_ESG : factor*health;
213 
214       *resultForce =  factor*force;
215       return true;
216     }
217   }
218   return false;
219 }
220 
draw()221 void Shockwave::draw()
222 {
223   if (active)
224   {
225     glDisable(GL_TEXTURE_2D);
226     glPushMatrix();
227     glColor3f(1.0, 1.0, 1.0);
228     glTranslatef(pos.c[X], pos.c[Y], 0);
229     glPointSize(4);
230     for (int i = 0; i < 360; i+=15)
231     {
232       glRotatef(15.0, 0.0, 0.0, i);
233       glBegin(GL_POINTS);
234         glVertex3f(innerRad, 0.0,0.5);
235         glVertex3f(outerRad, 0.0,0.5);
236       glEnd();
237     }
238     glPopMatrix();
239   }
240 }
241 
242 // Set the properties of an object type
set(float im,float iw,float ih,float ir,float imv,float ijv,float imtv,float ia,float ita,float hlth,int dth,bool cd,bool grav,bool shad)243 void ObjectType::set(float im, float iw, float ih, float ir, float imv, float ijv,
244                     float imtv, float ia, float ita, float hlth, int dth, bool cd, bool grav, bool shad)
245 {
246   spriteSize.set(iw, ih);
247   boundRadius = ir;
248   mass = im; maxVel = imv; jumpVel = ijv;
249   maxTurnvel = imtv; accel = ia; turnAccel = ita;
250   maxHealth = hlth;
251   deathType = dth;
252   collide = cd; gravAffect = grav; hasShadow = shad;
253   model1 = model2 = 0;
254   numModels = 0;
255   clearSprites();
256 }
257 
258 // Set the properties of a particle type
set(float mrl,float mrs,float mrjs,float rv,float mrrv,float fs,float mrfs,float sf,float ss,float sst,int hb,bool gl,float acc,int anf,float ad,float mrad,int al)259 void ParticleType::set(float mrl, float mrs, float mrjs, float rv, float mrrv, float fs, float mrfs, float sf, float ss, float sst,
260                       int hb, bool gl, float acc, int anf, float ad, float mrad, int al)
261 {
262   maxRandAddedToLife = mrl;
263   maxRandAddedToSpeed = mrs;
264   maxRandAddedToJumpSpeed = mrjs;
265   minRotVel = rv;
266   maxRandAddedToRotVel = mrrv;
267   minFadeSpeed = fs;
268   maxRandAddedToFadeSpeed = mrfs;
269   startFade = sf;
270   scaleSpeed = ss;
271   scaleStop = sst;
272   hitBehavior = hb;
273   glow = gl;
274   accel = acc;
275 
276   animNumFrames = anf;
277   animDelay = ad;
278   maxRandAddedToAnimDelay = mrad;
279   animLoop = al;
280 }
281 
282 // Specify a list of texture IDs that makes up the object's sprite collection
setSprites(int num,...)283 void ObjectType::setSprites(int num, ...)
284 {
285   if (num > 0 && num <= MAX_OBJ_SPRITES)
286   {
287     va_list ap;
288     va_start(ap, num);
289     for (int i = 0; i < num; i++)
290     {
291       int sp = va_arg(ap, int);
292       if (sp >= -1 && sp < NUM_TEXTURES)
293         sprites[i] = sp;
294     }
295   }
296 }
297 
resetIDCounter()298 void Object::resetIDCounter()
299 { oID = 0; }
300 
setID()301 void Object::setID()
302 { id = oID; oID++; }
303 
304 // Initializes an object.
reset()305 void Object::reset()
306 {
307   h = speed = hvel = turnvel = 0;
308 
309   pos = origPos;
310   heading = origHeading;
311 
312   lookAngle = heading;
313   jumpSpeed = jumpHeading = jumpStrafe = jumpPause = 0;
314   spr1Anim.reset();
315   spr2Anim.reset();
316 
317   model[0].set(objType[type].model1);
318   model[1].set((!augmented) ? objType[type].model2 : MDL_PLAYER_TORSO2);
319 
320   ev.reset();
321   health = objType[type].maxHealth;
322   active = false;
323   dying = false;
324   destroyMe = false;
325   hitGround = false;
326   collideWithObjects = false;
327   fall = false;
328   fallTimer = stunTimer = dyingTimer = 0;
329   shockCollisions = 0;
330   collided = -1;
331   currSurfaceFriction = 0;
332   currSurfaceHealth = 0;
333   surfaceHealthSound = false;
334   dirAlert = NO_ALERT;
335   dirAlertType = ALERT_NONE;
336   lastDamageAmount = 0.0;
337 
338   moveVel.set(0, 0);
339   forceVel.set(0, 0);
340 }
341 
changeVel(Vector2D v)342 void Object::changeVel(Vector2D v)
343 { vel += v; }
344 
changeForceVel(Vector2D v)345 void Object::changeForceVel(Vector2D v)
346 { forceVel += (v/objType[type].mass); }
347 
changeForceVelNoMass(Vector2D v)348 void Object::changeForceVelNoMass(Vector2D v)
349 { forceVel += v; }
350 
351 
changeHealth(float amt)352 void Object::changeHealth(float amt)
353 {
354   float prevHealth = health;
355   if (objType[type].maxHealth != INF)
356     health = (health+amt < objType[type].maxHealth) ? health+amt : objType[type].maxHealth;
357 
358   if (type == ENT_PLAYER)
359   {
360     if (health-prevHealth < 0)
361       addBlur(0.1*fabs(health-prevHealth), 1.0, 0.0, 0.0);
362     else if (health-prevHealth > 0)
363       addBlur(0.05*fabs(health-prevHealth), 0.0, 0.0, 1.0);
364   }
365 }
366 
changeHealthAmt(float amt)367 float Object::changeHealthAmt(float amt)
368 {
369   float prevHealth = health;
370   changeHealth(amt);
371   return health - prevHealth;
372 }
373 
374 // Widely-used drawing commands
drawStart()375 void Object::drawStart()
376 {
377   glPushMatrix();
378 
379   glTranslatef(pos.c[X], pos.c[Y], 0);
380 
381   // uncomment to draw bounding circles
382   /*glPushMatrix();
383   glColor3f(1.0, 0.0, 0.0);
384   for (int i = 0; i < 360; i+=15)
385   {
386     glRotatef(15.0, 0.0, 0.0, i);
387     glBegin(GL_POINTS);
388       glVertex2f(objType[type].boundRadius, 0.0);
389     glEnd();
390   }
391   glPopMatrix();*/
392 
393   glRotatef(todeg(lookAngle), 0.0, 0.0, 1.0);
394 
395   glEnable(GL_TEXTURE_2D);
396   glColor4f(1.0, 1.0, 1.0, 1.0);
397 }
398 
drawShadow()399 void Object::drawShadow()
400 {
401   if (active && objType[type].hasShadow && h >= 0)
402   {
403     glPushMatrix();
404     // translate ever so slightly above the ground to avoid being
405     // screwed over by the depth test
406     glTranslatef(pos.c[X], pos.c[Y], 0.01);
407     glRotatef(todeg(lookAngle), 0.0, 0.0, 1.0);
408     glEnable(GL_TEXTURE_2D);
409     glColor4f(1.0, 1.0, 1.0, 1.0);
410     texQuadSize(TEX_SHADOW, objType[type].boundRadius*2, objType[type].boundRadius*2);
411     glDisable(GL_TEXTURE_2D);
412     glPopMatrix();
413   }
414 }
415 
drawEnd()416 void Object::drawEnd()
417 {
418   glDisable(GL_TEXTURE_2D);
419   glPopMatrix();
420 }
421 
422 // Typical way of drawing an object (shadow, spr1, spr2)
draw()423 void Object::draw()
424 {
425   if (active)
426   {
427     drawStart();
428 
429     if (!objType[type].numModels)
430     {
431       glTranslatef(0.0, 0.0, h+0.02);
432       if (spr1 != NO_SPRITE) {texQuad(objType[type].sprites[spr1]);}
433       glTranslatef(0.0, 0.0, 0.01);
434       if (spr2 != NO_SPRITE) {texQuad(objType[type].sprites[spr2]);}
435     }
436     else
437     {
438       glEnable(GL_TEXTURE_2D);
439       glTranslatef(0.0, 0.0, h+0.02);
440 
441       for (int i = 0; i < objType[type].numModels; i++)
442         model[i].draw();
443     }
444     drawEnd();
445   }
446 }
447 
448 // Returns a pointer to the closest wall intersected, or NULL.
wallCollisionDetect(int cell,Vector2D * inter)449 Wall* Object::wallCollisionDetect(int cell, Vector2D *inter)
450 {
451   // Don't do collision detection on static objects
452   if (fabs(vel.mag()) > 0.01 || type == ENT_PLAYER)
453   {
454     Vector2D intersect;
455     int numWalls = currLevel.grid[cell].wall.size();
456     Wall *currWall;
457 
458     Wall *closestHitWall = NULL;
459     Vector2D closestHitIntersect;
460     float closestHitDistance = 1e37; // big numbah!
461 
462     for (int i = 0; i < numWalls + 4*currLevel.grid[cell].door.size(); i++)
463     {
464       // A pointer allows us to combine collision detection with
465       // walls and doors into one loop.
466       if (i < numWalls)
467       currWall = &currLevel.wall[currLevel.grid[cell].wall[i]];
468       else
469       currWall = &currLevel.door[currLevel.grid[cell].door[(i-numWalls)/4]].sides[(i-numWalls)%4];
470 
471       if ((currWall->CollFlags() & COLLIDE_OBJECT && !IS_BULLET(type)) ||
472       (currWall->CollFlags() & COLLIDE_BULLET && IS_BULLET(type)))
473       {
474         // Open doors
475         if (i >= numWalls)
476           currLevel.door[currLevel.grid[cell].door[(i-numWalls)/4]].detect(pos, (type == ENT_PLAYER) ? keys : -1);
477 
478         Vector2D offset = vel;
479         offset.normalize();
480         offset *= objType[type].boundRadius+(vel.mag()*timer.dT());
481 
482         Vector2D back = vel;
483         back.normalize();
484         back *= objType[type].boundRadius;
485 
486         if (currWall->intersection(pos-back, pos+offset, &intersect))
487         {
488           if ((intersect-pos).mag() < closestHitDistance)
489           {
490             closestHitDistance = (intersect-pos).mag();
491             closestHitWall = currWall;
492             closestHitIntersect = Vector2D(intersect.c[X],intersect.c[Y]);
493           }
494         }
495         else
496           currWall->renewTrigger();
497       }
498     }
499     if (closestHitWall)
500     {
501       if (inter)
502         inter->set(closestHitIntersect.c[X], closestHitIntersect.c[Y]);
503 
504       return closestHitWall;
505     }
506   }
507   return NULL;
508 }
509 
510 // Shockwave collision detection
shockwaveCollisionDetect()511 void Object::shockwaveCollisionDetect()
512 {
513   float swf, swh, ang;
514   for (int i = 0; !IS_POWERUP(type) && i < liveShockwaves; i++)
515   {
516     if (shockwaves[i].isActive())
517     {
518       if (shockwaves[i].detect(pos, &swf, &swh) && !(shockCollisions & (1 << (i % 64))))
519       {
520         if (swh == SHOCK_ESG)
521         {
522           if (IS_ENEMY(type) && !Stunned())
523           {
524             stun(15);
525             LAUNCH_MULTI_PARTICLES(25, PART_PARTICLE_SPARK,
526             pos.c[X], pos.c[Y],0.5,torad(rand()%360),1.0,0);
527 
528             LAUNCH_MULTI_PARTICLES(5, PART_SMOKE,
529             pos.c[X], pos.c[Y],0.5,torad(rand()%360),1.0,0);
530 
531             playSound(SND_ROBOT_PARALYZE, 5);
532           }
533         }
534         else
535         {
536           if (type == ENT_GLADIATOR)
537           {
538             if (shockwaves[i].AffectGladiators())
539               damage(swh, DAMAGE_SHOCKWAVE);
540           }
541           else
542             damage(swh, DAMAGE_SHOCKWAVE);
543         }
544 
545         ang = atan2(pos.c[Y]-shockwaves[i].PosY(),pos.c[X]-shockwaves[X].PosX());
546         setDirectionAlert(ang+PI, ALERT_SHOCKWAVE);
547 
548         if (!fixed)
549           changeForceVel(Vector2D(cos(ang)*swf, sin(ang)*swf));
550 
551         // set bit flag
552         shockCollisions |= 1 << (i % 64);
553 
554         if (type == ENT_PLAYER && health <= 0)
555           playSound(SND_EXPLODE, 2);
556       }
557     }
558     else
559     {
560       if (shockCollisions & (1 << (i % 64)))
561         shockCollisions &= ~(1 << (i % 64));
562     }
563   }
564 
565   if (liveShockwaves == 0)
566     shockCollisions = 0;
567 }
568 
569 // Collision with laaaaaaaaaaaaaaasers.
laserCollisionDetect()570 void Object::laserCollisionDetect()
571 {
572   for (int i = 0; !IS_POWERUP(type) && i < liveLasers; i++)
573   {
574     if ((type == ENT_PLAYER && lasers[i].Live() > 0) ||
575       (type != ENT_PLAYER && lasers[i].Live() == 1))
576     {
577       Vector2D intersection;
578       for (int j = 0; j < lasers[i].beams.size(); j++)
579       {
580         if (circleSegmentIntersection(pos, objType[type].boundRadius,
581         lasers[i].beams[j].p1, lasers[i].beams[j].p2, &intersection))
582         {
583           if (type != ENT_PLAYER)
584             damage(-lasType[lasers[i].Type()].damage, DAMAGE_BULLET);
585           else
586             damage(-lasType[lasers[i].Type()].damage*0.3, DAMAGE_BULLET);
587 
588           if (lasers[i].Type() == L_STANDARD)
589           {
590             LAUNCH_MULTI_PARTICLES(15, PART_LASER_SPARK1,
591             intersection.c[X], intersection.c[Y], h, torad(rand()%360),1.0,0);
592           }
593           else if (lasers[i].Type() == L_CHARGED)
594           {
595             LAUNCH_MULTI_PARTICLES(30, PART_LASER_SPARK2,
596             intersection.c[X], intersection.c[Y], h, torad(rand()%360),1.0,0);
597           }
598         }
599       }
600     }
601   }
602 }
603 
604 // Collision with floors; returns true if on a floor.
floorCollisionDetect(int cell,float * fric)605 bool Object::floorCollisionDetect(int cell, float *fric)
606 {
607   if (h <= 0.2)
608   {
609     int s;
610     bool onFloor = false;
611     for (int i = 0; i < currLevel.grid[cell].floor.size(); i++)
612     {
613       s = currLevel.grid[cell].floor[i];
614       if (currLevel.floor[s].CollFlags() & COLLIDE_OBJECT)
615       {
616         if (currLevel.floor[s].pointInBB(pos) && currLevel.floor[s].pointInPolygon(pos))
617         {
618           if (fric)
619             *fric = currLevel.floor[s].Friction();
620 
621           currLevel.floor[s].handleTrigger(false, type == ENT_PLAYER);
622           onFloor = true;
623         }
624         else
625           currLevel.floor[s].renewTrigger();
626       }
627     }
628     return onFloor;
629   }
630   return true;
631 }
632 
633 // Collision with surfaces
surfaceCollisionDetect(int cell,float * fric,float * hlth)634 void Object::surfaceCollisionDetect(int cell, float *fric, float *hlth)
635 {
636   if (h <= 0.2)
637   {
638     int s;
639     for (int i = 0; i < currLevel.grid[cell].surf.size(); i++)
640     {
641       s = currLevel.grid[cell].surf[i];
642       if (currLevel.surf[s].pointInBB(pos) && currLevel.surf[s].pointInPolygon(pos))
643       {
644         if (fric) *fric = currLevel.surf[s].Friction();
645         if (hlth) *hlth = currLevel.surf[s].Health();
646 
647         if (vel.mag() > 1.0 || hvel != 0)
648           currLevel.surf[s].spewParticles(pos);
649 
650         currLevel.surf[s].handleTrigger(false, type == ENT_PLAYER);
651       }
652       else
653           currLevel.surf[s].renewTrigger();
654     }
655   }
656 }
657 
658 
objectCollisionDetect(int cell)659 void Object::objectCollisionDetect(int cell)
660 {
661   if (collideWithObjects && active)
662   {
663     Object *obj;
664 
665     for (int j = 0; j < currLevelObjs.size(); j++)
666     {
667       obj = currLevelObjs[j];
668 
669       if (obj->isActive() && obj->CollideWithObjects())
670       {
671         if (id != obj->ID() &&
672           collided != obj->ID() &&
673           obj->Collided() != id)
674         {
675           // Extend object's reach for powerups
676           if (IS_POWERUP(obj->Type()))
677           {
678             Vector2D offset = vel;
679             offset.normalize();
680             offset *= objType[type].boundRadius+(vel.mag()*timer.dT());
681             if (distSquared(pos+offset, obj->Pos()) <=
682                 (objType[type].boundRadius +
683                 objType[obj->Type()].boundRadius)*
684                 (objType[type].boundRadius +
685                 objType[obj->Type()].boundRadius))
686             {
687               if (type == ENT_PLAYER)
688               {
689                 if (h == 0.0)
690                   givePowerup(j);
691               }
692             }
693           }
694 
695           if (distSquared(pos, obj->Pos()) <=
696               (objType[type].boundRadius +
697               objType[obj->Type()].boundRadius)*
698               (objType[type].boundRadius +
699               objType[obj->Type()].boundRadius))
700           {
701             if ((IS_POWERUP(obj->Type()) || IS_POWERUP(type)) && (h != 0.0 || obj->Height() != 0.0))
702               continue;
703             else
704             {
705               float m1 =  objType[type].mass;
706               float m2 =  objType[obj->Type()].mass;
707 
708               float v1i = vel.mag();
709               float v2i = obj->Vel().mag();
710 
711               float v1f = (m1*v1i - m2*(v1i - 2*v2i))/(m1+m2);
712               float v2f = (m1*(2*v1i - v2i) + m2*v2i)/(m1+m2);
713 
714               float a1 = atan2(pos.c[Y]-obj->PosY(),
715                           pos.c[X]-obj->PosX());
716               float a2 = atan2(obj->PosY()-pos.c[Y],
717                           obj->PosX()-pos.c[X]);
718 
719 
720               setCollided(obj->ID());
721               obj->setCollided(id);
722 
723               if (fabs(v1i) > fabs(v2i))
724                 pos += Vector2D(cos(a1), sin(a1)) * ((vel*timer.dT()).mag());
725               else
726                 obj->addPos(Vector2D(cos(a2), sin(a2)) * ((obj->Vel()*timer.dT()).mag()));
727 
728               if (!fixed)
729                 changeForceVelNoMass(Vector2D(cos(a1), sin(a1))*fabs(v1f)*0.1);
730 
731               if (!obj->Fixed())
732                 obj->changeForceVelNoMass(Vector2D(cos(a2), sin(a2))*fabs(v2f)*0.1);
733 
734               setDirectionAlert(a2, ALERT_TOUCH);
735               obj->setDirectionAlert(a1, ALERT_TOUCH);
736             }
737           }
738         }
739       }
740     }
741   }
742 }
743 
744 // Handles collision detection with walls and surfaces.
levelCollisionDetection()745 void Object::levelCollisionDetection()
746 {
747   if (currLevel.inGrid(pos))
748   {
749     int cell = currLevel.cellNumber(pos);
750 
751     shockwaveCollisionDetect();
752     laserCollisionDetect();
753     if (forceVel.mag() > TERMINAL_VELOCITY)
754     { forceVel.normalize(); forceVel *= TERMINAL_VELOCITY; }
755     vel = moveVel + forceVel;
756 
757     objectCollisionDetect(cell);
758     if (forceVel.mag() > TERMINAL_VELOCITY)
759     { forceVel.normalize(); forceVel *= TERMINAL_VELOCITY; }
760     vel = moveVel + forceVel;
761 
762     Vector2D impactPoint;
763     Wall *closestHitWall = wallCollisionDetect(cell, &impactPoint);
764 
765     // React to a collision, if there was one
766     if (closestHitWall)
767     {
768       // Push the object back by his velocity
769       // in the direction of the wall's normal
770       if (vel.mag() < WALL_COLLISION_THRESHOLD)
771         pos += closestHitWall->normal * ((vel*timer.dT()).mag()+0.1);
772       else
773       {
774         pos = impactPoint;
775         pos += closestHitWall->normal * (objType[type].boundRadius+0.1);
776         moveVel.set(0,0);
777         forceVel.set(0,0);
778       }
779 
780       if (vel.mag() >= WALL_DAMAGE_THRESHOLD)
781         // Lose health based on your impact velocity.
782         damage(-vel.mag()/IMPACT_DAMAGE_FACTOR,false);
783 
784       if (closestHitWall->Health() < 0)
785         damage(closestHitWall->Health(),false);
786       else if (closestHitWall->Health() > 0)
787         changeHealth(closestHitWall->Health());
788 
789       if (closestHitWall->Force())
790         changeForceVel(closestHitWall->normal*closestHitWall->Force());
791 
792       if (closestHitWall->Health() < 0)
793       {
794         if (type == ENT_PLAYER)
795         {
796           LAUNCH_MULTI_PARTICLES(10, PART_PARTICLE_BLOOD,
797           pos.c[X], pos.c[Y],0,torad(rand()%360),1.0,0);
798         }
799       }
800       closestHitWall->handleTrigger(false, type == ENT_PLAYER);
801     }
802 
803     currSurfaceFriction = DEFAULT_SURFACE_FRICTION;
804     currSurfaceHealth = 0;
805 
806     if (!floorCollisionDetect(cell, &currSurfaceFriction) && !fall)
807     {
808       fall = true;
809       currSurfaceFriction = 0;
810       if (type == ENT_PLAYER)
811       {
812         setMessage("Oh crap");
813         playSound(SND_FALL, 2);
814       }
815     }
816 
817     surfaceCollisionDetect(cell, &currSurfaceFriction, &currSurfaceHealth);
818   }
819 }
820 
turn(float amt)821 void Object::turn(float amt)
822 {
823   turnvel = amt;
824   printf("%f\n", turnvel);
825 }
826 
setAnimation(int val,bool anim)827 void Object::setAnimation(int val, bool anim)
828 {
829   for (int i = 0; i < objType[type].numModels; i++)
830     model[i].setAnimation(val, anim);
831 }
832 
833 // Basic framework for updating an object's position, velocity, heading.
834 // States, jumping, sprite representation is per-subtype.
update()835 void Object::update()
836 {
837   if (active && timer.dT() > 0)
838   {
839     if (!currLevel.inGrid(pos))
840     { active = false; return; }
841 
842     for (int i = 0; i < objType[type].numModels; i++)
843       model[i].update();
844 
845     if (objType[type].maxHealth != INF)
846     {
847       if (type == ENT_PLAYER && config.cheat_invul && currSurfaceHealth < 0) {}
848       else
849         changeHealth(currSurfaceHealth * timer.dT());
850     }
851 
852     dyingCheck();
853 
854     if (type == ENT_PLAYER)
855     {
856       if (currSurfaceHealth > 0)
857       {
858         if (surfaceHealthSound) { playSound(SND_HEAL, 2); surfaceHealthSound = false; }
859       }
860       else if (currSurfaceHealth < 0 && !config.cheat_invul)
861       {
862         if (surfaceHealthSound) { playSound(SND_HURTSURFACE, 2); surfaceHealthSound = false; }
863       }
864       else
865         surfaceHealthSound = true;
866     }
867 
868     // Kill the object's ability to do anything if we're stunned
869     if (stunTimer > 0)
870     {
871       ev.reset();
872       stunTimer -= timer.dT();
873     }
874     else
875       stunTimer = 0;
876 
877     turnvel += objType[type].turnAccel * -ev.getLR();
878 
879     //float friction = turnvel * -currSurfaceFriction;
880     turnvel += (turnvel * -currSurfaceFriction);//friction;
881 
882     if (turnvel > objType[type].maxTurnvel * fabs(ev.getLR()))
883   		turnvel = objType[type].maxTurnvel * fabs(ev.getLR());
884     if (turnvel < -objType[type].maxTurnvel * fabs(ev.getLR()))
885   		turnvel = -objType[type].maxTurnvel * fabs(ev.getLR());
886     lookAngle += turnvel * timer.dT();
887 
888     if (h == 0)
889     {
890       heading = lookAngle;
891 
892       if (fall == true)
893          h -= ACCEL_GRAVITY * timer.dT();
894     }
895 
896     float maxVel = objType[type].maxVel;
897     if (augmented) maxVel *= AUG_SPEED_INCREASE;
898 
899     if (moveVel.mag() <= maxVel)
900     {
901       if (h == 0 && jumpPause == 0.0)
902       {
903         moveVel.c[X] += cos(heading) * objType[type].accel * ev.getFB();
904         moveVel.c[Y] += sin(heading) * objType[type].accel * ev.getFB();
905 
906         moveVel.c[X] += cos(heading-PIOVER2) * objType[type].accel * ev.getStrafe();
907         moveVel.c[Y] += sin(heading-PIOVER2) * objType[type].accel * ev.getStrafe();
908 
909       }
910       else
911       {
912         moveVel.c[X] += cos(jumpHeading) * jumpSpeed;
913         moveVel.c[Y] += sin(jumpHeading) * jumpSpeed;
914         moveVel.c[X] += cos(jumpHeading-PIOVER2) * jumpStrafe;
915         moveVel.c[Y] += sin(jumpHeading-PIOVER2) * jumpStrafe;
916       }
917     }
918 
919     if (moveVel.mag() > maxVel)
920     {
921       moveVel.normalize();
922       moveVel *= maxVel;
923     }
924 
925     if (jumpPause > 0)
926       moveVel.set(0,0);
927 
928     if (h == 0)
929       moveVel += moveVel * -currSurfaceFriction;
930     else
931       moveVel += moveVel * -0.1;
932 
933     if (moveVel.mag() > TERMINAL_VELOCITY)
934     {
935       moveVel.normalize();
936       moveVel *= TERMINAL_VELOCITY;
937     }
938 
939     forceVel += (forceVel * -currSurfaceFriction);
940     if (forceVel.mag() < 0.01)
941       forceVel.set(0,0);
942     if (forceVel.mag() > TERMINAL_VELOCITY)
943     {
944       forceVel.normalize();
945       forceVel *= TERMINAL_VELOCITY;
946     }
947 
948     vel = moveVel + forceVel;
949     // Collision detection
950     levelCollisionDetection();
951 
952     vel = moveVel + forceVel;
953     if (vel.mag() > TERMINAL_VELOCITY)
954     {
955       vel.normalize();
956       vel *= TERMINAL_VELOCITY;
957     }
958 
959     pos += (vel) * timer.dT();
960 
961     // Object is affected by gravity if it is not on the ground
962     if (objType[type].gravAffect)
963     {
964       if (h != 0)
965         hvel -= ACCEL_GRAVITY * timer.dT();
966       if (h < 0 && !fall)
967         hitGround = true; // response is per-object-type
968     }
969 
970     h += hvel * timer.dT();
971 
972     if (jumpPause > 0)
973       jumpPause -= timer.dT();
974     else if (jumpPause < 0)
975       jumpPause = 0;
976 
977     if (jumpPause == 0)
978     {
979       spr1Anim.update();
980       spr2Anim.update();
981     }
982 
983     // Respond to triggers
984     if (triggers[respondTo2].hit)
985     { kill(true); }
986 
987     if (type == ENT_PLAYER)
988     {
989       if (health < 25)
990         setBlur(1.0, 1.0, 0.0, 0.0);
991     }
992 
993     if (health != INF)
994     {
995       if (health < 0) health = 0;
996       if (health > objType[type].maxHealth) health = objType[type].maxHealth;
997     }
998 
999     if (dying)
1000     {
1001       dyingTimer -= timer.dT();
1002       if (dyingTimer <= 0)
1003       {
1004         dying = false;
1005         kill(true);
1006       }
1007     }
1008   }
1009 
1010   if (fall)
1011   {
1012     fallTimer += timer.dT();
1013     if (fallTimer >= FALL_KILL_DELAY)
1014     {
1015       kill(false);
1016       fallTimer = 0;
1017       fall = false;
1018       if (type == ENT_PLAYER)
1019         playSound(SND_EXPLODE, 2);
1020     }
1021   }
1022 }
1023 
1024 // by = true for explosions, false for bullets/etc.
damage(float amt,bool by)1025 void Object::damage(float amt, bool by)
1026 {
1027   if (type == ENT_PLAYER)
1028   {
1029     if (amt < 0 && !config.cheat_invul)
1030     {
1031       changeHealth(amt*((augmented)?AUG_DAMAGE_SUPPRESSION:1.0));
1032       P.hits++;
1033     }
1034   }
1035   else
1036     changeHealth(amt);
1037 
1038   if (!(Stunned() || Dying()))
1039   {
1040     if (type != ENT_PLAYER)
1041       setAnimation(MA_FLINCH, ANIM_TRANSIENT);
1042 
1043     if (type == ENT_PLAYER)
1044     { if (!config.cheat_invul) playSound(SND_HURT1 + rand()%2, 2); }
1045     else
1046       playSound(SND_BULLET_HIT_HARD, 1);
1047 
1048     damagedBy = by;
1049     lastDamageAmount = amt;
1050   }
1051 }
1052 
stun(float amt)1053 void Object::stun(float amt)
1054 {
1055   stunTimer += amt;
1056 
1057   if (type != ENT_PLAYER && !Dying())
1058     setAnimation(MA_IDLE, ANIM_BASE);
1059 }
1060 
dyingCheck()1061 void Object::dyingCheck()
1062 {
1063   if (!dying && health <= 0 && health != INF)
1064   {
1065     switch(type)
1066     {
1067       case ENT_PLAYER:
1068         if (damagedBy == DAMAGE_BULLET)
1069         {
1070           model[0].setAnimation(MA_PL_DIESOFT, ANIM_TRANSIENT);
1071           model[0].setAnimation(MA_PL_DEADSOFT, ANIM_BASE);
1072           model[1].setAnimation(MA_PT_DIESOFT, ANIM_TRANSIENT);
1073           model[1].setAnimation(MA_PT_DEADSOFT, ANIM_BASE);
1074         }
1075         else
1076         {
1077           model[0].setAnimation(MA_PL_DIEHARD, ANIM_TRANSIENT);
1078           model[0].setAnimation(MA_PL_DEADHARD, ANIM_BASE);
1079           model[1].setAnimation(MA_PT_DIEHARD, ANIM_TRANSIENT);
1080           model[1].setAnimation(MA_PT_DEADHARD, ANIM_BASE);
1081         }
1082         die(2);
1083         stun(99999);
1084         playSound(SND_EXPLODE, 2);
1085         break;
1086       case ENT_UNARMED_GUARD: case ENT_MIB:
1087       case ENT_ARMED_DRONE: case ENT_HUNTER: case ENT_GLADIATOR:
1088         if (lastDamageAmount > GIB_THRESHOLD)
1089         {
1090           setAnimation(MA_DIE, ANIM_TRANSIENT);
1091           setAnimation(MA_DEAD, ANIM_BASE);
1092           die(1);
1093           stun(10);
1094         }
1095         else
1096           die(0);
1097         break;
1098       default:
1099         die(0);
1100     }
1101   }
1102 }
1103 
kill(bool death)1104 void Object::kill(bool death)
1105 {
1106   health = 0;
1107   active = false;
1108   selectWeapon(0);
1109 
1110   if (type == ENT_PLAYER)
1111     setBlur(1.0, 1.0, 0.0, 0.0);
1112   else if (IS_ENEMY(type))
1113     P.kills++;
1114 
1115   if (death)
1116     doDeath();
1117 }
1118 
doDeath()1119 void Object::doDeath()
1120 {
1121   float distToPlayer, randAng, randRad, px, py, ph, phead;
1122   switch(objType[type].deathType)
1123   {
1124     case DEATH_NONE: break;
1125     case DEATH_PLAYER:
1126       break;
1127     case DEATH_WOOD:
1128       LAUNCH_MULTI_PARTICLES(20, PART_PARTICLE_WOOD,
1129       pos.c[X]+frand()*3.0-1.5, pos.c[Y]+frand()*3.0-1.5,0,torad(rand()%360),1.0,0);
1130       break;
1131     case DEATH_SMALL_EXPLOSION:
1132       LAUNCH_MULTI_PARTICLES(4, PART_EXPLOSION,
1133         pos.c[X]+frand()*7.0-3.5, pos.c[Y]+frand()*7.0-3.5,0.5,torad(rand()%360),1.0,rand()%4);
1134 
1135       LAUNCH_MULTI_PARTICLES(25, PART_EXPLOSIONPART,
1136         pos.c[X], pos.c[Y],0.5,torad(rand()%360),1.0,0);
1137 
1138       addShockwave(pos.c[X], pos.c[Y], 0, 0, 9, 0, 0.5, 48000, -50);
1139 
1140       distToPlayer = dist(pos, PLAYER_OBJECT->Pos());
1141       if (distToPlayer < 20)
1142         setScreenQuake(lerp(5, 0, distToPlayer/20));
1143 
1144       playSound(SND_EXPLOSION, 5);
1145       break;
1146 
1147 
1148     case DEATH_GRENADE_EXPLOSION:
1149       LAUNCH_MULTI_PARTICLES(4, PART_EXPLOSION,
1150         pos.c[X]+frand()*7.0-3.5, pos.c[Y]+frand()*7.0-3.5,0.5,torad(rand()%360),1.0,rand()%4);
1151 
1152       addShockwave(pos.c[X], pos.c[Y], 0, 0, 16, 0, 0.5, 40000, -70);
1153 
1154       distToPlayer = dist(pos, PLAYER_OBJECT->Pos());
1155       if (distToPlayer < 20)
1156         setScreenQuake(lerp(5, 0, distToPlayer/20));
1157 
1158       playSound(SND_GRENADE_EXPLOSION, 5);
1159 
1160       for (int i = 0; i < 25; i++)
1161         launchBullet(-1, BLT_SHRAPNEL, pos.c[X], pos.c[Y], 0.5, torad(rand()%360));
1162 
1163       break;
1164 
1165     case DEATH_EXPLOSION:
1166       LAUNCH_MULTI_PARTICLES(4, PART_EXPLOSION,
1167       pos.c[X]+frand()*7.0-3.5, pos.c[Y]+frand()*7.0-3.5,0.5,torad(rand()%360),1.0,rand()%4);
1168 
1169       LAUNCH_MULTI_PARTICLES(25, PART_EXPLOSIONPART,
1170       pos.c[X], pos.c[Y],0.5,torad(rand()%360),1.0,0);
1171 
1172       LAUNCH_MULTI_PARTICLES(5, PART_SMOKE,
1173       pos.c[X], pos.c[Y],0.5,torad(rand()%360),1.0,0);
1174 
1175       addShockwave(pos.c[X], pos.c[Y], 0, 0, 10, 0, 0.5, 48000, -65);
1176 
1177       distToPlayer = dist(pos, PLAYER_OBJECT->Pos());
1178       if (distToPlayer < 20)
1179         setScreenQuake(lerp(5, 0, distToPlayer/20));
1180 
1181       playSound(SND_EXPLOSION, 5);
1182       break;
1183 
1184 
1185     case DEATH_HEPA_EXPLOSION:
1186       LAUNCH_MULTI_PARTICLES(4, PART_HEPA_EXPLOSION,
1187       pos.c[X]+frand()*14.0-7, pos.c[Y]+frand()*14.0-7,0.5,torad(rand()%360),1.0,0);
1188       LAUNCH_MULTI_PARTICLES(50, PART_HEPA_SPARK,
1189       pos.c[X], pos.c[Y],0.5,torad(rand()%360),1.0,0);
1190 
1191       addShockwave(pos.c[X], pos.c[Y], 0, 0, 20, 0, 0.5, 0, -200);
1192 
1193       distToPlayer = dist(pos, PLAYER_OBJECT->Pos());
1194       if (distToPlayer < 80)
1195         setScreenQuake(lerp(5, 0, distToPlayer/80));
1196 
1197       playSound(SND_HEPA_EXPLOSION, 5);
1198       break;
1199     case DEATH_POWERUP:
1200       for (int i = 0; i < 15; i++)
1201       {
1202         randAng = frand()*PI*2;
1203         randRad = frand()*0.25;
1204         launchParticle(PART_PARTICLE_GLOW,
1205         pos.c[X]+(cos(randAng)*randRad), pos.c[Y]+(sin(randAng)*randRad), 0.5, randAng,1.0,0);
1206       }
1207 
1208       break;
1209     case DEATH_ROBOT:
1210       LAUNCH_MULTI_PARTICLES(25, PART_PARTICLE_METAL,
1211       pos.c[X], pos.c[Y],0,torad(rand()%360),1.0,0);
1212       LAUNCH_MULTI_PARTICLES(25, PART_EXPLOSIONPART,
1213       pos.c[X], pos.c[Y],0.5,torad(rand()%360),1.0,0);
1214 
1215       playSound(SND_ROBOT_DEATH, 5);
1216 
1217       break;
1218     case DEATH_GLADIATOR:
1219       LAUNCH_MULTI_PARTICLES(4, PART_HEPA_EXPLOSION,
1220       pos.c[X]+frand()*14.0-7, pos.c[Y]+frand()*14.0-7,0.5,torad(rand()%360),1.0,0);
1221       LAUNCH_MULTI_PARTICLES(50, PART_HEPA_SPARK,
1222       pos.c[X], pos.c[Y],0.5,torad(rand()%360),1.0,0);
1223 
1224       addShockwave(pos.c[X], pos.c[Y], 0, 0, 20, 0, 0.5, 0, -200, false);
1225 
1226       distToPlayer = dist(pos, PLAYER_OBJECT->Pos());
1227       if (distToPlayer < 80)
1228         setScreenQuake(lerp(5, 0, distToPlayer/80));
1229 
1230       playSound(SND_HEPA_EXPLOSION, 5);
1231       break;
1232     case DEATH_ESG:
1233       px = PLAYER_OBJECT->PosX(); py = PLAYER_OBJECT->PosY();
1234       ph = PLAYER_OBJECT->Height();
1235       phead = PLAYER_OBJECT->LookAngle();
1236 
1237       launchParticle(PART_ESG_SHOCKWAVE,
1238       px+(wpnType[WPN_ESG].spriteSize.c[X]/3.5*cos(phead)),
1239       py+(wpnType[WPN_ESG].spriteSize.c[X]/3.5*sin(phead)),
1240       ph, phead, 1.0, 0);
1241       setBlur(2.0, 1.0, 0.5, 1.0);
1242       setScreenQuake(7.0);
1243       addShockwave(px+(wpnType[WPN_ESG].spriteSize.c[X]/3.5*cos(phead)),
1244       py+(wpnType[WPN_ESG].spriteSize.c[X]/3.5*sin(phead)),
1245       0, 0, 0, 100.0, 0.5, 0, SHOCK_ESG);
1246       break;
1247   }
1248 
1249   handleTrigger(false, type == ENT_PLAYER);
1250 }
1251 
reset()1252 void Entity::reset()
1253 {
1254   Object::reset();
1255   jumping = false;
1256   currWeapon = 0;
1257   keys = 0;
1258   weapons = 1;
1259   walkSound = false;
1260   grenadeThrowTimer = 0;
1261   throwGrenade = false;
1262 
1263   switch(type)
1264   {
1265     case ENT_PLAYER:
1266 
1267       if (!augmented)
1268       {
1269         wpn[0].set(WPN_NONE, MAX_AMMO*(int)config.cheat_weapons, 0);    wpnPos[0].set(0,0);
1270         wpn[1].set(WPN_RIFLE, MAX_AMMO*(int)config.cheat_weapons, 0);   wpnPos[1].set(-0.25, 1.1);
1271         wpn[2].set(WPN_SHOTGUN, MAX_AMMO*(int)config.cheat_weapons, 0); wpnPos[2].set(-0.20, 1.1);
1272         wpn[3].set(WPN_HEPA, MAX_AMMO*(int)config.cheat_weapons, 0);    wpnPos[3].set(-0.35, 0.9);
1273         wpn[4].set(WPN_GRENADE, MAX_AMMO*(int)config.cheat_weapons, 0); wpnPos[4].set(-0.45, 0.9);
1274         wpn[5].set(WPN_LASER, MAX_AMMO*(int)config.cheat_weapons, 0);   wpnPos[5].set(-0.30, 0.9);
1275         wpn[6].set(WPN_ESG, MAX_AMMO*(int)config.cheat_weapons, 0);     wpnPos[6].set(-0.30, 0.9);
1276       }
1277       else
1278         wpn[0].set(WPN_BLASTER, MAX_AMMO, 0); wpnPos[0].set(-0.30, 0.9);
1279 
1280       break;
1281     case ENT_TURRET1: case ENT_SLAVE_TURRET:
1282       wpn[0].set(WPN_TURRET_GUN, MAX_AMMO, -1); wpnPos[0].set(0,2.2);
1283       break;
1284     case ENT_ARMED_DRONE:
1285       wpn[0].set(WPN_RIFLE, MAX_AMMO, -1); wpnPos[0].set(-1.6, 1.5);
1286       break;
1287     case ENT_HUNTER:
1288       wpn[0].set(WPN_RIFLE, MAX_AMMO, -1); wpnPos[0].set(-2.5, 0.4);
1289       wpn[1].set(WPN_RIFLE, MAX_AMMO, -1); wpnPos[1].set(2.5, 0.4);
1290       break;
1291     case ENT_GLADIATOR:
1292       wpn[0].set(WPN_SHOTGUN, MAX_AMMO, -1); wpnPos[0].set(1.6, 1.9);
1293       break;
1294 
1295   }
1296 
1297   if (type == ENT_PLAYER)
1298   {
1299     weapons = (!config.cheat_weapons || augmented) ? 1 : (1 << NUM_PLAYER_WEAPONS)-1;
1300     keys = (!config.cheat_keys) ? 0 : (1 << NUM_KEYS)-1;
1301   }
1302 }
1303 
1304 // Draws an Entity.
draw()1305 void Entity::draw()
1306 {
1307   if (active)
1308   {
1309     if (type == ENT_PLAYER)
1310     {
1311       drawStart();
1312       glEnable(GL_TEXTURE_2D);
1313       glTranslatef(0.0, 0.0, h+0.02);
1314 
1315       for (int i = 0; i < objType[type].numModels; i++)
1316         model[i].draw();
1317 
1318       if (!Dying())
1319       {
1320         glTranslatef(wpnPos[currWeapon].c[Y], -wpnPos[currWeapon].c[X], 4.6);
1321         wpn[currWeapon].draw();
1322       }
1323 
1324       drawEnd();
1325     }
1326     else
1327       Object::draw();
1328   }
1329 }
1330 
setPlayerAnimations()1331 void Entity::setPlayerAnimations()
1332 {
1333   if (type == ENT_PLAYER && !dying)
1334   {
1335     // Torso animation
1336     if (ev.getFB() != 0 || ev.getStrafe() != 0)
1337     {
1338       switch(currWeapon)
1339       {
1340         case WPN_NONE: case WPN_GRENADE:
1341           model[1].setAnimation(MA_PT_RUN, ANIM_BASE);
1342           break;
1343         case WPN_RIFLE: case WPN_SHOTGUN: case WPN_LASER:
1344           model[1].setAnimation(MA_PT_RIFLERUN, ANIM_BASE);
1345           break;
1346         case WPN_HEPA: case WPN_ESG:
1347           model[1].setAnimation(MA_PT_HEPARUN, ANIM_BASE);
1348           break;
1349       }
1350     }
1351     else
1352     {
1353       switch(currWeapon)
1354       {
1355         case WPN_NONE: case WPN_GRENADE:
1356           model[1].setAnimation(MA_PT_STAND, ANIM_BASE);
1357           break;
1358         case WPN_RIFLE: case WPN_SHOTGUN: case WPN_LASER:
1359           model[1].setAnimation(MA_PT_RIFLE, ANIM_BASE);
1360           break;
1361         case WPN_HEPA: case WPN_ESG:
1362           model[1].setAnimation(MA_PT_HEPA, ANIM_BASE);
1363           break;
1364       }
1365     }
1366 
1367     // Leg animation
1368     if (ev.getFB() > 0)
1369       model[0].setAnimation(MA_PL_FWD, ANIM_BASE);
1370     else if (ev.getFB() < 0)
1371       model[0].setAnimation(MA_PL_BACK, ANIM_BASE);
1372     else if (ev.getStrafe() > 0)
1373       model[0].setAnimation(MA_PL_FWDRIGHT, ANIM_BASE);
1374     else if (ev.getStrafe() < 0)
1375       model[0].setAnimation(MA_PL_FWDLEFT, ANIM_BASE);
1376     else
1377       model[0].setAnimation(MA_PL_STAND, ANIM_BASE);
1378   }
1379 }
1380 
1381 
1382 // Updates an Entity: controls sprite appearance based on sprite,
1383 // and handles jump event.
update()1384 void Entity::update()
1385 {
1386   spr1 = 0; spr2 = NO_SPRITE;
1387   Object::update();
1388 
1389   if (type == ENT_PLAYER)
1390   {
1391     wpn[currWeapon].update();
1392 
1393     if (!augmented)
1394     { if (currWeapon != WPN_GRENADE) wpn[WPN_GRENADE].update(); }
1395 
1396     if (currWeapon > 0)
1397     { spr2 = 4; spr2Anim.set(1, 0, 0); }
1398   }
1399 
1400   if (type == ENT_TURRET1 || type == ENT_SLAVE_TURRET || type == ENT_ARMED_DRONE || type == ENT_HUNTER || type == ENT_GLADIATOR)
1401     wpn[0].update();
1402 
1403   if (type == ENT_HUNTER)
1404     wpn[1].update();
1405 
1406   if (active && !fall)
1407   {
1408     if (type == ENT_PLAYER)
1409       setPlayerAnimations();
1410     else
1411     {
1412       if (ev.getFB() > 0 || ev.getStrafe() != 0)
1413       {
1414         if (objType[type].numModels)
1415         { if (!(Stunned() || Dying())) setAnimation((type != ENT_MIB) ? MA_WALK : MA_RUN, ANIM_BASE); }
1416         else
1417         { spr1Anim.setDirection(ANIM_FWD); spr2Anim.setDirection(ANIM_FWD); }
1418       }
1419       else if (ev.getFB() < 0 || ev.getStrafe() != 0)
1420       { spr1Anim.setDirection(ANIM_REV); spr2Anim.setDirection(ANIM_REV); }
1421       else
1422       {
1423         if (objType[type].numModels)
1424         { if (!(Stunned() || Dying())) setAnimation(MA_STAND, ANIM_BASE); }
1425         else
1426         { spr1Anim.freezeAtFrame(0); spr2Anim.freezeAtFrame(0); }
1427       }
1428     }
1429 
1430     if (!ev.getJump() && jumpPause == 0)
1431       jumpReady = true;
1432 
1433     if (h == 0 && !jumping && jumpReady && ev.getJump() && jumpPause == 0)
1434     {
1435       hvel = objType[type].jumpVel;
1436       jumping = true;
1437       jumpReady = false;
1438       jumpSpeed = vel.mag()*0.75 * ev.getFB();
1439       jumpStrafe = vel.mag()*0.75 * ev.getStrafe();
1440       jumpHeading = heading;
1441       playSound(SND_JUMP, 4);
1442 
1443       if (type == ENT_PLAYER)
1444       {
1445         if (currWeapon == WPN_NONE || currWeapon == WPN_GRENADE)
1446           model[1].setAnimation(MA_PT_RUN, ANIM_TRANSIENT);
1447 
1448         model[0].setAnimation(MA_PL_JUMP, ANIM_TRANSIENT);
1449       }
1450     }
1451     if (hitGround)
1452     {
1453       jumpPause = JUMP_PAUSE_TIME;
1454       hvel = 0;
1455       h = 0;
1456       jumping = false;
1457       hitGround = false;
1458       playSound(SND_LAND, 4);
1459     }
1460 
1461     if (ev.getSwitchWpn() && !throwGrenade)
1462     {
1463       if (switchReady)
1464         { selectNextWeapon(); switchReady = false; }
1465     }
1466     else
1467       switchReady = true;
1468 
1469     if (type == ENT_PLAYER)
1470     {
1471       if (ev.getFire() && !Dying())
1472       {
1473         if (currWeapon == WPN_GRENADE)
1474         {
1475           if (wpn[currWeapon].Ready() && !throwGrenade)
1476           {
1477             throwGrenade = true;
1478             grenadeThrowTimer = 0.5;
1479             model[1].setAnimation(MA_PT_GRENADE, ANIM_TRANSIENT);
1480           }
1481         }
1482         else
1483         {
1484           if (wpn[currWeapon].fire(pos.c[X]+(wpnPos[currWeapon].c[X]*cos(lookAngle-PIOVER2))+
1485                 (wpnPos[currWeapon].c[Y]*cos(lookAngle)),
1486                 pos.c[Y]+(wpnPos[currWeapon].c[X]*sin(lookAngle-PIOVER2))+
1487                 (wpnPos[currWeapon].c[Y]*sin(lookAngle)), lookAngle, 4.5))
1488           {
1489             switch(currWeapon)
1490             {
1491               case WPN_RIFLE: case WPN_SHOTGUN:
1492                 model[1].setAnimation(MA_PT_RIFLESHOOT, ANIM_TRANSIENT);
1493                 break;
1494               case WPN_HEPA:
1495                 model[1].setAnimation(MA_PT_HEPASHOOT, ANIM_TRANSIENT);
1496                 break;
1497               case WPN_NONE:
1498                 if (augmented)
1499                   model[1].setAnimation(MA_PT_PALMSHOOT, ANIM_TRANSIENT);
1500                   break;
1501 
1502             }
1503           }
1504         }
1505       }
1506       else
1507         wpn[currWeapon].releaseTrigger(pos.c[X]+(wpnPos[currWeapon].c[X]*cos(lookAngle-PIOVER2))+
1508                 (wpnPos[currWeapon].c[Y]*cos(lookAngle)),
1509                 pos.c[Y]+(wpnPos[currWeapon].c[X]*sin(lookAngle-PIOVER2))+
1510                 (wpnPos[currWeapon].c[Y]*sin(lookAngle)), lookAngle, 4.5);
1511 
1512       if (!augmented)
1513       {
1514         if (ev.getGrenade() && !Dying() && currWeapon != WPN_HEPA && currWeapon != WPN_NONE && currWeapon != WPN_ESG)
1515         {
1516           if (wpn[WPN_GRENADE].Ready() && !throwGrenade)
1517           {
1518             throwGrenade = true;
1519             grenadeThrowTimer = 0.5;
1520 
1521             switch(currWeapon)
1522             {
1523               case WPN_RIFLE: case WPN_SHOTGUN: case WPN_LASER:
1524                 model[1].setAnimation(MA_PT_RIFLEGRENADE, ANIM_TRANSIENT);
1525                 break;
1526               default:
1527                 model[1].setAnimation(MA_PT_GRENADE, ANIM_TRANSIENT);
1528             }
1529           }
1530         }
1531         else
1532           wpn[WPN_GRENADE].releaseTrigger(0, 0, 0, 0);
1533 
1534         if (throwGrenade)
1535         {
1536           grenadeThrowTimer -= timer.dT();
1537           if (grenadeThrowTimer <= 0)
1538           {
1539             int hand = (currWeapon == WPN_GRENADE) ? 1 : -1;
1540             wpn[WPN_GRENADE].fire(pos.c[X]+(wpnPos[WPN_GRENADE].c[X]*hand*cos(lookAngle-PIOVER2))+
1541                 (wpnPos[WPN_GRENADE].c[Y]*cos(lookAngle)),
1542                 pos.c[Y]+(wpnPos[WPN_GRENADE].c[X]*hand*sin(lookAngle-PIOVER2))+
1543                 (wpnPos[WPN_GRENADE].c[Y]*sin(lookAngle)), lookAngle, 4.5);
1544             grenadeThrowTimer = 0;
1545             throwGrenade = false;
1546             setMessage("Grenades: %d", wpn[WPN_GRENADE].ReserveAmmo());
1547           }
1548         }
1549       }
1550 
1551       if (ev.getReload())
1552         wpn[currWeapon].reload();
1553 
1554       if (wpn[currWeapon].isReloading() && !(Dying() || Stunned()))
1555       {
1556           switch(currWeapon)
1557           {
1558             case WPN_RIFLE: case WPN_SHOTGUN: case WPN_LASER:
1559               model[1].setAnimation(MA_PT_RIFLERELOAD, ANIM_TRANSIENT);
1560               break;
1561             case WPN_HEPA: case WPN_ESG:
1562               model[1].setAnimation(MA_PT_HEPARELOAD, ANIM_TRANSIENT);
1563               break;
1564           }
1565       }
1566     }
1567 
1568     if (type == ENT_TURRET1 || type == ENT_SLAVE_TURRET || type == ENT_ARMED_DRONE || type == ENT_HUNTER || type == ENT_GLADIATOR)
1569     {
1570       if (ev.getFire())
1571       {
1572         wpn[0].fire(pos.c[X]+(wpnPos[0].c[X]*cos(lookAngle-PIOVER2))+
1573                 (wpnPos[0].c[Y]*cos(lookAngle)),
1574                 pos.c[Y]+(wpnPos[0].c[X]*sin(lookAngle-PIOVER2))+
1575                 (wpnPos[0].c[Y]*sin(lookAngle)), lookAngle, 0.5);
1576       }
1577       else
1578         wpn[0].releaseTrigger(0,0,0,0);
1579 
1580       if (ev.getReload())
1581         wpn[0].reload();
1582     }
1583 
1584     if (type == ENT_HUNTER)
1585     {
1586       if (ev.getFire())
1587       {
1588         wpn[1].fire(pos.c[X]+(wpnPos[1].c[X]*cos(lookAngle-PIOVER2))+
1589                 (wpnPos[1].c[Y]*cos(lookAngle)),
1590                 pos.c[Y]+(wpnPos[1].c[X]*sin(lookAngle-PIOVER2))+
1591                 (wpnPos[1].c[Y]*sin(lookAngle)), lookAngle, 0.5);
1592       }
1593       else
1594         wpn[1].releaseTrigger(0,0,0,0);
1595 
1596       if (ev.getReload())
1597         wpn[1].reload();
1598     }
1599   }
1600 }
1601 
selectNextWeapon()1602 void Entity::selectNextWeapon()
1603 {
1604   currWeapon++;
1605   if (currWeapon >= NUM_PLAYER_WEAPONS)
1606     currWeapon = 0;
1607   else if (!HAS_WEAPON(weapons, currWeapon))
1608     selectNextWeapon();
1609   else
1610     playSound(SND_CHANGE_WEAPON, 7);
1611 }
1612 
givePowerup(int idx)1613 void Entity::givePowerup(int idx)
1614 {
1615   bool pickedUp = true;
1616   int iAmt;
1617   float fAmt;
1618   int snd = 1;
1619   switch(currLevelObjs[idx]->Type())
1620   {
1621     case POWERUP_HEALTH:
1622       if ((fAmt = changeHealthAmt(POWERUP_HEALTH_AMOUNT)) > 0)
1623       {
1624         setMessage("Health +%0.0f", fAmt);
1625         snd = 2;
1626       }
1627       else
1628       {
1629         setMessage("Health full");
1630         pickedUp = false;
1631       }
1632       break;
1633     case POWERUP_ENERGY_CLIP:
1634       addAmmoPowerup(WPN_RIFLE, "Rifle ammo +%d", "Rifle ammo full");
1635       break;
1636     case POWERUP_SHOTGUN_AMMO:
1637       addAmmoPowerup(WPN_SHOTGUN, "Shotgun ammo +%d", "Shotgun ammo full");
1638       break;
1639     case POWERUP_HEPA_CLIP:
1640       addAmmoPowerup(WPN_HEPA, "Positron cells +%d", "H.E.P.A. ammo full");
1641       break;
1642     case POWERUP_LASER_CELL:
1643       addAmmoPowerup(WPN_LASER, "Laser cells +%d", "Laser ammo full");
1644       break;
1645     case POWERUP_ESG_BATTERY:
1646       addAmmoPowerup(WPN_ESG, "ESG battery +%d", "ESG ammo full");
1647       break;
1648 
1649     case POWERUP_KEY1:
1650     case POWERUP_KEY2:
1651     case POWERUP_KEY3:
1652       giveKey(currLevelObjs[idx]->Type() - POWERUP_KEY1 + 1);
1653       setMessage("Key %d", currLevelObjs[idx]->Type() - POWERUP_KEY1 + 1);
1654       break;
1655 
1656     case WEAPON_RIFLE:
1657       addWeaponSwitch(WPN_RIFLE, "Energy rifle", "Rifle ammo full");
1658       break;
1659     case WEAPON_SHOTGUN:
1660       addWeaponSwitch(WPN_SHOTGUN, "Shotgun", "Shotgun ammo full");
1661       break;
1662     case WEAPON_HEPA:
1663       addWeaponSwitch(WPN_HEPA, "H.E.P.A.", "H.E.P.A. ammo full");
1664       break;
1665     case POWERUP_GRENADE:
1666       addWeapon(WPN_GRENADE, "Frag grenade", "Grenades full");
1667       break;
1668     case WEAPON_RED_LASER:
1669       addWeaponSwitch(WPN_LASER, "Laser", "Laser ammo full");
1670       break;
1671     case WEAPON_ESG:
1672       addWeaponSwitch(WPN_ESG, "EMP Shock Gen", "ESG ammo full");
1673       break;
1674 
1675   }
1676 
1677   if (pickedUp)
1678   {
1679     currLevelObjs[idx]->kill(true);
1680 
1681     if      (snd == 1) playSound(SND_PICKUP_ITEM, 7);
1682     else if (snd == 2) playSound(SND_PICKUP_HEALTH, 7);
1683   }
1684 }
1685 
weaponState(int wp)1686 char Entity::weaponState(int wp)
1687 {
1688   if (!HAS_WEAPON(weapons, wp))
1689     return ' ';
1690   else
1691     return '1'+wp;
1692 }
1693 
keyState(int k)1694 char Entity::keyState(int k)
1695 {
1696   if (!HAS_KEY(keys, k))
1697     return ' ';
1698   else
1699     return '?';
1700 }
1701 
launch(int typ,float x,float y,float height,float head,float alph,int sprite)1702 void Particle::launch(int typ, float x, float y, float height, float head, float alph, int sprite)
1703 {
1704   Object::set(typ, x, y, height, head, 0, 0, 0, 0);
1705   color.white();
1706   pType = typ - PART_PARTICLE;
1707   alpha = alph;
1708   active = true;
1709   rotation = head;
1710   spr1 = sprite;
1711   additionalSpeed = 0;
1712 
1713   health += frand()*partType[pType].maxRandAddedToLife;
1714 
1715   speed = objType[type].maxVel + frand()*partType[pType].maxRandAddedToSpeed;
1716 
1717   hvel = objType[type].jumpVel + frand()*partType[pType].maxRandAddedToJumpSpeed;
1718 
1719   rotVel = partType[pType].minRotVel + frand()*partType[pType].maxRandAddedToRotVel;
1720   fadeSpeed = partType[pType].minFadeSpeed + frand()*partType[pType].maxRandAddedToFadeSpeed;
1721 
1722   anim.set(partType[pType].animNumFrames, partType[pType].animDelay + frand()*partType[pType].maxRandAddedToAnimDelay, partType[pType].animLoop);
1723   anim.setDirection(1);
1724 }
1725 
launch(int typ,float x,float y,float height,float head,float alph,int sprite,float r,float g,float b)1726 void Particle::launch(int typ, float x, float y, float height, float head, float alph, int sprite, float r, float g, float b)
1727 {
1728   launch(typ, x, y, height, head, alph, sprite);
1729   color.set(r, g, b);
1730 }
1731 
1732 
reset()1733 void Particle::reset()
1734 {
1735   Object::reset();
1736   bounceCount = 0;
1737   scale = 1.0;
1738 }
1739 
drawShadow()1740 void Particle::drawShadow()
1741 {
1742   if (active && objType[type].hasShadow && h >= 0)
1743   {
1744     glPushMatrix();
1745     // translate ever so slightly above the ground to avoid being
1746     // screwed over by the depth test
1747     glTranslatef(pos.c[X], pos.c[Y], 0.01);
1748     glRotatef(todeg(heading), 0.0, 0.0, 1.0);
1749     glEnable(GL_TEXTURE_2D);
1750     glColor4f(1.0, 1.0, 1.0, alpha);
1751     texQuadSize(TEX_SHADOW, objType[type].spriteSize.c[X]*scale, objType[type].spriteSize.c[Y]*scale);
1752     glDisable(GL_TEXTURE_2D);
1753     glPopMatrix();
1754   }
1755 }
1756 
draw()1757 void Particle::draw()
1758 {
1759   if (active)
1760   {
1761     glPushMatrix();
1762 
1763     glTranslatef(pos.c[X], pos.c[Y], 0);
1764     glRotatef(todeg(rotation), 0.0, 0.0, 1.0);
1765 
1766     glEnable(GL_TEXTURE_2D);
1767     glColor4f(color.r, color.g, color.b, alpha);
1768 
1769     // translate ever so slightly above the ground to avoid being
1770     // screwed over by the depth test
1771     glTranslatef(0.0, 0.0, 0.01);
1772     if (objType[type].hasShadow) {texQuadSize(TEX_SHADOW, objType[type].spriteSize.c[X]*scale, objType[type].spriteSize.c[Y]*scale); }
1773 
1774     glTranslatef(0.0, 0.0, h);
1775 
1776     if (partType[pType].glow)
1777       glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1778 
1779     if (partType[pType].animNumFrames <= 1)
1780     {
1781       if (spr1 != NO_SPRITE) {texQuadSize(objType[type].sprites[spr1], objType[type].spriteSize.c[X]*scale, objType[type].spriteSize.c[Y]*scale); }
1782       glTranslatef(0.0, 0.0, 0.01);
1783       if (spr2 != NO_SPRITE) {texQuadSize(objType[type].sprites[spr2], objType[type].spriteSize.c[X]*scale, objType[type].spriteSize.c[Y]*scale); }
1784     }
1785     else
1786     {
1787       if (spr1 != NO_SPRITE) {texQuadClip(objType[type].sprites[spr1], anim.Frame(), 1.0/anim.NumFrames());}
1788       glTranslatef(0.0, 0.0, 0.01);
1789       if (spr2 != NO_SPRITE) {texQuadClip(objType[type].sprites[spr2], anim.Frame(), 1.0/anim.NumFrames());}
1790     }
1791     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1792 
1793     glDisable(GL_TEXTURE_2D);
1794     glPopMatrix();
1795   }
1796 }
1797 
levelCollisionDetection()1798 void Particle::levelCollisionDetection()
1799 {
1800   if (currLevel.inGrid(pos))
1801   {
1802     int cell = currLevel.cellNumber(pos);
1803     if (objType[type].collide)
1804     {
1805       Vector2D intersect(0,0);
1806       Wall *closestHitWall = wallCollisionDetect(cell, &intersect);
1807 
1808       if (closestHitWall)
1809       {
1810         // Bullet ricochets if ricochet was specified.
1811         if (closestHitWall->CollFlags() & RICOCHET_BULLET)
1812         {
1813           vel -= closestHitWall->normal * (closestHitWall->normal * vel) * 2;
1814           heading = atan2(vel.c[Y], vel.c[X]);
1815           rotation = heading;
1816         }
1817         else
1818           switch (partType[pType].hitBehavior)
1819           {
1820             case HIT_DIE:
1821               health = 0; break;
1822             case HIT_STOP:
1823               vel.set(0, 0); break;
1824             case HIT_BOUNCE:
1825               // Snell's law, woot.
1826               vel -= closestHitWall->normal * (closestHitWall->normal * vel) * 2;
1827               heading = atan2(vel.c[Y], vel.c[X]);
1828               rotation = heading;
1829           }
1830 
1831         if (IS_BULLET(type))
1832         {
1833           closestHitWall->spewParticles(intersect+closestHitWall->normal*0.25);
1834           closestHitWall->handleTrigger(true, false);
1835         }
1836       }
1837     }
1838     if (!floorCollisionDetect(cell, NULL))
1839       fall = true;
1840   }
1841 }
1842 
1843 // Updates a Particle.
update()1844 void Particle::update()
1845 {
1846   spr2 = NO_SPRITE;
1847 
1848   if (active)
1849   {
1850     if (!currLevel.inGrid(pos))
1851     { active = false; return; }
1852 
1853     ev.setFB(1);
1854 
1855     speed += partType[pType].accel * timer.dT();
1856 
1857     if (speed < 0)
1858       speed = 0;
1859 
1860     vel.set(cos(heading)*speed, sin(heading)*speed);
1861 
1862     levelCollisionDetection();
1863 
1864     pos += vel * timer.dT();
1865 
1866     if (type == ESG_EXPLODER)
1867     {
1868       pos.c[X] = PLAYER_OBJECT->PosX()+(wpnType[WPN_ESG].spriteSize.c[X]/3.5*cos(PLAYER_OBJECT->LookAngle()));
1869       pos.c[Y] = PLAYER_OBJECT->PosY()+(wpnType[WPN_ESG].spriteSize.c[X]/3.5*sin(PLAYER_OBJECT->LookAngle()));
1870     }
1871 
1872     // Object is affected by gravity if it is not on the ground
1873     h += hvel * timer.dT();
1874 
1875     if (objType[type].gravAffect)
1876     {
1877       if (h != 0)
1878         hvel -= ACCEL_GRAVITY * timer.dT();
1879       if (h < 0 && !fall)
1880         hitGround = true;
1881     }
1882 
1883     if (type == BLT_HEPA)
1884     {
1885       launchParticle(PART_HEPA_TRAIL,
1886       pos.c[X], pos.c[Y], 0.5, torad(rand()%360),1.0,0);
1887     }
1888     else if (type == BLT_GRENADE || type == BLT_SHRAPNEL)
1889     {
1890       launchParticle(PART_GRENADE_TRAIL,
1891       pos.c[X], pos.c[Y], h, -rotation,1.0,0);
1892     }
1893 
1894     if (partType[pType].startFade == 0 || health <= partType[pType].startFade)
1895       alpha -= fadeSpeed * timer.dT();
1896 
1897     if (alpha <= 0)
1898       health = 0;
1899 
1900     rotation += rotVel * timer.dT();
1901 
1902     if (partType[pType].scaleStop == 0 ||
1903       (partType[pType].scaleStop > 0 && scale <= partType[pType].scaleStop))
1904         scale += partType[pType].scaleSpeed * timer.dT();
1905 
1906     if (scale <= 0)
1907       health = 0;
1908 
1909     anim.update();
1910 
1911     if (anim.isDone())
1912       health = 0;
1913 
1914     health -= 1 * timer.dT();
1915 
1916     if (health != INF && health <= 0)
1917     { active = false; destroyMe = true; }
1918 
1919     if (hitGround)
1920     {
1921       hitGround = false;
1922       h = 0.00001;
1923       hvel = -hvel*0.5;
1924 
1925       if (type == BLT_GRENADE)
1926         playSound(SND_GRENADE_BOUNCE, 1);
1927     }
1928   }
1929 }
1930 
update()1931 void Bullet::update()
1932 {
1933   if (frameDelay > 0)
1934     frameDelay--;
1935 
1936   Particle::update();
1937 }
1938 
LaserBeam(Vector2D np1,Vector2D np2)1939 LaserBeam::LaserBeam(Vector2D np1, Vector2D np2)
1940 {
1941   p1 = np1; p2 = np2;
1942   angle = atan2(p2.c[Y]-p1.c[Y], p2.c[X]-p1.c[X]);
1943   length = (p2-p1).mag();
1944 }
1945 
launch(int typ,float x,float y,float height,float head)1946 void Laser::launch(int typ, float x, float y, float height, float head)
1947 {
1948   h = height;
1949   alpha = 1.0;
1950   type = typ;
1951   beams.clear();
1952 
1953   float distLeft = lasType[type].maxRange;
1954   int bounces = 0;
1955   Vector2D startPos(x-cos(head)*wpnType[WPN_LASER].spriteSize.c[X], y-sin(head)*wpnType[WPN_LASER].spriteSize.c[X]);
1956   while (distLeft > 0.001 && bounces < lasType[type].maxBounces)
1957   {
1958     Vector2D endPos;
1959     Wall *closestHitWall = wallCollisionDetect(startPos, head, distLeft, &endPos);
1960 
1961     // If we didn't hit a wall, the beam keeps going for the remainder of its distance
1962     if (closestHitWall == NULL)
1963       endPos = startPos + Vector2D(cos(head)*distLeft, sin(head)*distLeft);
1964     else
1965     {
1966       closestHitWall->handleTrigger(true, false);
1967       Vector2D dir = endPos-startPos; dir.normalize();
1968 
1969       dir -= closestHitWall->normal *
1970       (closestHitWall->normal * dir) * 2;
1971       head = atan2(dir.c[Y], dir.c[X]);
1972 
1973       endPos += closestHitWall->normal * 0.1;
1974 
1975       bounces++;
1976     }
1977 
1978     distLeft -= (endPos-startPos).mag();
1979     beams.push_back(LaserBeam(startPos, endPos));
1980     startPos.set(endPos.c[X], endPos.c[Y]);
1981   }
1982   beams[0].p1.set(x, y);
1983 
1984   live = 2;
1985   active = true;
1986   destroyMe = false;
1987 }
1988 
wallCollisionDetect(Vector2D pos,float head,float distLeft,Vector2D * inter)1989 Wall* Laser::wallCollisionDetect(Vector2D pos, float head, float distLeft, Vector2D *inter)
1990 {
1991   Vector2D intersect;
1992   int numWalls = currLevel.wall.size();
1993   Wall *currWall;
1994 
1995   Wall *closestHitWall = NULL;
1996   Vector2D closestHitIntersect;
1997   float closestHitDistance = 1e37;
1998 
1999   for (int i = 0; i < numWalls + 4*currLevel.door.size(); i++)
2000   {
2001     // A pointer allows us to combine collision detection with
2002     // walls and doors into one loop.
2003     if (i < numWalls)
2004       currWall = &currLevel.wall[i];
2005     else
2006       currWall = &currLevel.door[(i-numWalls)/4].sides[(i-numWalls)%4];
2007 
2008     if (currWall->CollFlags() & COLLIDE_BULLET)
2009     {
2010       Vector2D offset(cos(head)*distLeft, sin(head)*distLeft);
2011 
2012       if (currWall->intersection(pos, pos+offset, &intersect))
2013       {
2014         if ((intersect-pos).mag() < closestHitDistance)
2015         {
2016           closestHitDistance = (intersect-pos).mag();
2017           closestHitWall = currWall;
2018           closestHitIntersect.set(intersect.c[X],intersect.c[Y]);
2019         }
2020       }
2021       else
2022         currWall->renewTrigger();
2023     }
2024   }
2025   if (closestHitWall)
2026   {
2027     if (inter)
2028       inter->set(closestHitIntersect.c[X], closestHitIntersect.c[Y]);
2029 
2030     return closestHitWall;
2031   }
2032 
2033   return NULL;
2034 }
2035 
draw()2036 void Laser::draw()
2037 {
2038   if (active)
2039   {
2040     glPushMatrix();
2041 
2042     glEnable(GL_TEXTURE_2D);
2043     glColor4f(1.0, 1.0, 1.0, alpha);
2044     glTranslatef(0.0, 0.0, h);
2045 
2046     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
2047 
2048     glBindTexture(GL_TEXTURE_2D, textures[lasType[type].sprite]);
2049     for (int i = 0; i < beams.size(); i++)
2050     {
2051       glPushMatrix();
2052       glTranslatef(beams[i].p1.c[X], beams[i].p1.c[Y], 0.0);
2053       glRotatef(todeg(beams[i].angle), 0.0, 0.0, 1.0);
2054       glBegin(GL_QUADS);
2055         glTexCoord2f(0.05, 1);
2056         glVertex2f(0, -lasType[type].width*0.5*alpha);
2057         glTexCoord2f(1, 1);
2058         glVertex2f(beams[i].length, -lasType[type].width*0.5*alpha);
2059         glTexCoord2f(1, 0);
2060         glVertex2f(beams[i].length, lasType[type].width*0.5*alpha);
2061         glTexCoord2f(0.05, 0);
2062         glVertex2f(0, lasType[type].width*0.5*alpha);
2063       glEnd();
2064       glPopMatrix();
2065     }
2066 
2067 
2068     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2069     glPopMatrix();
2070     glDisable(GL_TEXTURE_2D);
2071   }
2072 
2073 }
2074 
update()2075 void Laser::update()
2076 {
2077   if (active)
2078   {
2079     if (live > 0)
2080       live--;
2081 
2082     alpha -= lasType[type].fadeSpeed * timer.dT();
2083 
2084     if (alpha <= 0)
2085     {
2086       alpha = 0;
2087       active = false;
2088       destroyMe = true;
2089     }
2090   }
2091 }
2092 
2093 // Defines the type of all objects
initObjectTypes()2094 void initObjectTypes()
2095 {
2096   for (int i = 0; i < NUM_OBJ_TYPES; i++)
2097     switch(i)
2098     {
2099       // float im, float iw, float ih, float ir, float imv, float ijv, float imtv, float ia, float ita, float hlth, bool grav, bool shad)
2100       case ENT_PLAYER:
2101         objType[i].set(160, 1.5, 6.0, 1.0, 17, 8, torad(150), 25, torad(5000), 100, DEATH_PLAYER, true, true, true);
2102         objType[i].setModels(MDL_PLAYER_LEGS, MDL_PLAYER_TORSO);
2103         break;
2104       case ENT_BOX:
2105         objType[i].set(140, 3.0, 3.0, 2.0,  0, 0, 0, 0, 0, 25, DEATH_WOOD, true, true, true);
2106         objType[i].setModels(MDL_BOX, NO_MODEL);
2107         break;
2108       case ENT_BARREL:
2109         objType[i].set(180, 2.5, 2.5, 1.25,  0, 0, 0, 0, 0, 35, DEATH_EXPLOSION, true, true, true);
2110         objType[i].setModels(MDL_BARREL, NO_MODEL);
2111         break;
2112       case ENT_STEEL_CRATE:
2113         objType[i].set(250, 3.0, 3.0, 2.0,  0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2114         objType[i].setModels(MDL_STEELBOX, NO_MODEL);
2115         break;
2116 
2117       case ENT_UNARMED_GUARD:
2118         objType[i].set(100, 3.0, 3.0, 1.5,  15, 0, torad(150), 25, torad(5000), 50, DEATH_SMALL_EXPLOSION, true, true, true);
2119         objType[i].setModels(MDL_UNARMED_GUARD, NO_MODEL);
2120         break;
2121       case ENT_MIB:
2122         objType[i].set(100, 3.0, 3.0, 1.5,  14, 0, torad(150), 25, torad(5000), 50, DEATH_NONE, true, true, true);
2123         objType[i].setModels(MDL_MIB, NO_MODEL);
2124         break;
2125       case ENT_TURRET1: case ENT_SLAVE_TURRET:
2126         objType[i].set(10000, 5.0, 5.0, 1.5, 0, 0, torad(75), 0, torad(5000), 50, DEATH_ROBOT, true, true, true);
2127         objType[i].setModels(MDL_TURRET, NO_MODEL);
2128         break;
2129       case ENT_ARMED_DRONE:
2130         objType[i].set(200, 4.0, 4.0, 1.25, 13, 0, torad(200), 25, torad(4000), 75, DEATH_ROBOT, true, true, true);
2131         objType[i].setModels(MDL_ARMED_DRONE, NO_MODEL);
2132         break;
2133       case ENT_HUNTER:
2134         objType[i].set(450, 4.0, 4.0, 1.25, 14, 0, torad(200), 25, torad(4000), 100, DEATH_ROBOT, true, true, true);
2135         objType[i].setModels(MDL_HUNTER, NO_MODEL);
2136         break;
2137       case ENT_GLADIATOR:
2138         objType[i].set(700, 4.0, 4.0, 1.5, 8, 0, torad(200), 25, torad(4000), 250, DEATH_GLADIATOR, true, true, true);
2139         objType[i].setModels(MDL_GLADIATOR, NO_MODEL);
2140         break;
2141       case POWERUP_HEALTH:
2142         objType[i].set(5, 1.5, 0.75, 0.5, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2143         objType[i].setSprites(1, TEX_POWERUP_HEALTH);
2144         break;
2145       case POWERUP_ENERGY_CLIP:
2146         objType[i].set(1, 0.5, 1.0, 0.25, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2147         objType[i].setSprites(1, TEX_POWERUP_ENERGY_CLIP);
2148         break;
2149       case POWERUP_SHOTGUN_AMMO:
2150         objType[i].set(1, 1.0, 1.0, 0.5, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2151         objType[i].setSprites(1, TEX_POWERUP_SHOTGUN_AMMO);
2152         break;
2153       case POWERUP_HEPA_CLIP:
2154         objType[i].set(1, 0.5, 1.0, 0.25, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2155         objType[i].setSprites(1, TEX_POWERUP_HEPA_CLIP);
2156         break;
2157       case POWERUP_LASER_CELL:
2158         objType[i].set(1, 1.5, 0.45, 0.3, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2159         objType[i].setSprites(1, TEX_POWERUP_LASER_CELL);
2160         break;
2161       case POWERUP_ESG_BATTERY:
2162         objType[i].set(1, 1.0, 1.0, 0.5, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2163         objType[i].setSprites(1, TEX_POWERUP_ESG_BATTERY);
2164         break;
2165       case POWERUP_KEY1:
2166         objType[i].set(1, 0.5, 1.0, 0.25, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2167         objType[i].setSprites(1, TEX_POWERUP_KEY1);
2168         break;
2169       case POWERUP_KEY2:
2170         objType[i].set(1, 0.5, 1.0, 0.25, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2171         objType[i].setSprites(1, TEX_POWERUP_KEY2);
2172         break;
2173       case POWERUP_KEY3:
2174         objType[i].set(1, 0.5, 1.0, 0.25, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2175         objType[i].setSprites(1, TEX_POWERUP_KEY3);
2176         break;
2177       case WEAPON_RIFLE:
2178         objType[i].set(10, 4.0, 1.0, 1.0, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2179         objType[i].setSprites(1, TEX_WEAPON_RIFLE);
2180         break;
2181       case WEAPON_SHOTGUN:
2182         objType[i].set(10, 4.0, 1.0, 1.0, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2183         objType[i].setSprites(1, TEX_WEAPON_SHOTGUN);
2184         break;
2185       case WEAPON_HEPA:
2186         objType[i].set(10, 4.0, 2.0, 1.0, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2187         objType[i].setSprites(1, TEX_WEAPON_HEPA);
2188         break;
2189       case POWERUP_GRENADE:
2190         objType[i].set(1, 0.75, 0.75, 0.25, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2191         objType[i].setSprites(1, TEX_WPN_GRENADE);
2192         break;
2193       case WEAPON_RED_LASER:
2194         objType[i].set(10, 4.0, 2.0, 1.0, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2195         objType[i].setSprites(1, TEX_WEAPON_LASER);
2196         break;
2197       case WEAPON_ESG:
2198         objType[i].set(10, 4.0, 4.0, 1.0, 0, 0, 0, 0, 0, INF, DEATH_NONE, true, true, true);
2199         objType[i].setSprites(1, TEX_WEAPON_ESG);
2200         break;
2201 
2202       case EXPLODER:
2203         objType[i].set(1000000, 0, 0, 0, 0, 0, 0, 0, 0, INF, DEATH_EXPLOSION, false, false, false);
2204         break;
2205 
2206       case PART_PARTICLE:
2207         objType[i].set(0.01, 0.25, 0.25, 0.25, 1, 0, 0, 100, 0, 2.0, DEATH_NONE, false, true, false);
2208         objType[i].setSprites(1, TEX_PARTICLE);
2209         break;
2210       case PART_PARTICLE_BLOOD:
2211         objType[i].set(0.01, 0.25, 0.25, 0.25, 1, 0, 0, 100, 0, 4, DEATH_NONE, false, true, false);
2212         objType[i].setSprites(1, TEX_PARTICLE_BLOOD);
2213         break;
2214       case PART_PARTICLE_SLIME:
2215         objType[i].set(0.01, 0.25, 0.25, 0.25, 1, 0, 0, 100, 0, 1, DEATH_NONE, false, true, false);
2216         objType[i].setSprites(1, TEX_PARTICLE_SLIME);
2217         break;
2218       case PART_PARTICLE_SPARK:
2219         objType[i].set(0.01, 1.5, 0.125, 0.25, 15, 0, 0, 100, 0, 4, DEATH_NONE, false, true, false);
2220         objType[i].setSprites(1, TEX_PARTICLE_SPARK);
2221         break;
2222       case PART_PARTICLE_ENERGY:
2223         objType[i].set(0.01, 0.75, 0.75, 0.75, 0.5, 0, 0, 100, 0, 1, DEATH_NONE, false, true, false);
2224         objType[i].setSprites(1, TEX_PARTICLE_ENERGY);
2225         break;
2226       case PART_PARTICLE_WOOD:
2227         objType[i].set(0.01, 1.0, 0.5, 0.5, 1, 10, 0, 100, 0, 2, DEATH_NONE, false, true, true);
2228         objType[i].setSprites(1, TEX_PARTICLE_WOOD);
2229         break;
2230      case PART_PARTICLE_METAL:
2231         objType[i].set(0.01, 1.0, 0.25, 0.5,3, 20, 0, 100, 0, 2, DEATH_NONE, true, true, true);
2232         objType[i].setSprites(1, TEX_PARTICLE_METAL);
2233         break;
2234       case PART_MF_RIFLE:
2235         objType[i].set(0.01, 1.0, 0.5, 1.0, 0, 0, 0, 0, 0, 0.0625, DEATH_NONE, false, false, false);
2236         objType[i].setSprites(1, TEX_MF_RIFLE);
2237         break;
2238       case PART_MF_SHOTGUN:
2239         objType[i].set(0.01, 1.0, 1.0, 1.0, 0, 0, 0, 0, 0, 0.0625, DEATH_NONE, false, false, false);
2240         objType[i].setSprites(1, TEX_MF_SHOTGUN);
2241         break;
2242       case BLT_RIFLE:
2243         objType[i].set(0.0044, 2.0, 0.15, 0.5, 150, 0, 0, 0, 0, 1.0, DEATH_NONE, true, false, false);
2244         objType[i].setSprites(1, TEX_BLT_RIFLE);
2245         break;
2246       case BLT_SHOTGUN:
2247         objType[i].set(0.0044, 0.5, 0.15, 0.5, 150, 0, 0, 0, 0, 1.0, DEATH_NONE, true, false, false);
2248         objType[i].setSprites(1, TEX_BLT_RIFLE);
2249         break;
2250      case BLT_HEPA:
2251         objType[i].set(0.0001, 1.0, 1.0, 0.5, 20, 0, 0, 0, 0, 2.0, DEATH_HEPA_EXPLOSION, true, false, false);
2252         objType[i].setSprites(1, TEX_PARTICLE_ENERGY);
2253         break;
2254      case BLT_GRENADE:
2255         objType[i].set(1, 0.65, 0.65, 0.3, 20, 0, 0, 0, 0, 3.0, DEATH_GRENADE_EXPLOSION, true, true, true);
2256         objType[i].setSprites(1, TEX_BLT_GRENADE);
2257         break;
2258      case BLT_SHRAPNEL:
2259         objType[i].set(1, 0.5, 0.5, 0.25, 100, 0, 0, 0, 0, .35, DEATH_NONE, true, false, false);
2260         objType[i].setSprites(1, TEX_BLT_SHRAPNEL);
2261         break;
2262      case BLT_BLASTER:
2263         objType[i].set(0.0044, 6.0, 2.0, 0.5, 110, 0, 0, 0, 0, 1.0, DEATH_NONE, true, false, false);
2264         objType[i].setSprites(1, TEX_BLT_BLASTER);
2265         break;
2266      case PART_RIFLE_SHELL:
2267         objType[i].set(0.0044, 0.03125, 0.125, 0.5, 0.5, 10, 0, 0.1, 0, 2.0, DEATH_NONE, false, true, true);
2268         objType[i].setSprites(1, TEX_RIFLE_SHELL);
2269         break;
2270      case PART_SHOTGUN_SHELL:
2271         objType[i].set(0.0044, 0.125, 0.25, 0.5, 0.5, 5, 0, 0.1, 0, 2.0, DEATH_NONE, false, true, true);
2272         objType[i].setSprites(1, TEX_SHOTGUN_SHELL);
2273         break;
2274      case PART_EXPLOSION:
2275         objType[i].set(0, 8.0, 8.0, 8.0, 0, 0, 0, 0, 0, 0.4, DEATH_NONE, false, false, false);
2276         objType[i].setSprites(4, TEX_EXPLOSION1, TEX_EXPLOSION2, TEX_EXPLOSION3, TEX_EXPLOSION4);
2277         break;
2278      case PART_EXPLOSIONPART:
2279         objType[i].set(0, 1.0, 0.5, 0.5, 10, 0, 0, 0, 0, 0.5, DEATH_NONE, false, true, false);
2280         objType[i].setSprites(1, TEX_EXPLOSIONPART);
2281         break;
2282      case PART_SMOKE:
2283         objType[i].set(0, 2.0, 2.0, 2.0, 2, 1, 0, 0, 0, 30, DEATH_NONE, false, false, false);
2284         objType[i].setSprites(1, TEX_SMOKE);
2285         break;
2286      case PART_PARTICLE_GLOW:
2287         objType[i].set(0, 0.4, 0.4, 0.4, 0, 0, 0, 0, 0, 5, DEATH_NONE, false, false, false);
2288         objType[i].setSprites(1, TEX_PARTICLE_GLOW);
2289         break;
2290      case PART_HEPA_TRAIL:
2291         objType[i].set(0, 2.0, 2.0, 1.0, 0, 0, 0, 0, 0, 5, DEATH_NONE, false, false, false);
2292         objType[i].setSprites(1, TEX_PARTICLE_ENERGY);
2293         break;
2294      case PART_HEPA_EXPLOSION:
2295         objType[i].set(0, 4.0, 4.0, 2.0, 0, 0, 0, 0, 0, 5, DEATH_NONE, false, false, false);
2296         objType[i].setSprites(1, TEX_LIGHT);
2297         break;
2298      case PART_HEPA_SPARK:
2299         objType[i].set(0.01, 3.0, 3.0, 0.25, 1, 0, 0, 100, 0, 6, DEATH_NONE, false, true, false);
2300         objType[i].setSprites(1, TEX_PARTICLE_ENERGY);
2301         break;
2302      case PART_GRENADE_TRAIL:
2303         objType[i].set(0.01, 0.125, 0.125, 0.125, 2, 0, 0, 100, 0, 1, DEATH_NONE, false, true, false);
2304         objType[i].setSprites(1, TEX_PARTICLE_SLIME);
2305         break;
2306      case PART_LASER_SPARK1:
2307         objType[i].set(0.01, 1.0, 1.0, 0.25, 0.5, 5, 0, 100, 0, 6, DEATH_NONE, false, false, false);
2308         objType[i].setSprites(1, TEX_LASER_SPARK1);
2309         break;
2310      case PART_LASER_SPARK2:
2311         objType[i].set(0.01, 2.0, 2.0, 1.0, 0.5, 5, 0, 100, 0, 6, DEATH_NONE, false, false, false);
2312         objType[i].setSprites(1, TEX_LASER_SPARK2);
2313         break;
2314      case PART_ESG_SHOCKWAVE:
2315         objType[i].set(0.01, 1.0, 1.0, 2.5, 0, 0, 0, 100, 0, 0.5, DEATH_NONE, false, false, false);
2316         objType[i].setSprites(1, TEX_ESG_SHOCKWAVE);
2317         break;
2318      case PART_BIT:
2319         objType[i].set(0.01, 5.0, 5.0, 1.0, 0, 0, 0, 0, 0, 10, DEATH_NONE, false, false, false);
2320         objType[i].setSprites(1, TEX_BIT);
2321         break;
2322      case ESG_EXPLODER:
2323         objType[i].set(1, 0, 0, 0, 0, 0, 0, 0, 0, 0.75, DEATH_ESG, false, false, false);
2324         break;
2325     }
2326 
2327   for (int i = 0; i < NUM_PART_TYPES; i++)
2328     switch(i)
2329     {
2330       // float mrl, float mrs, float mrjs, float rv, float mrrv, float fs, float mrfs, float sf, float ss, float sst, int hb, bool gl, float acc, int anf, float ad, float mrad, int al
2331       case P_PART_PARTICLE: case P_PART_PARTICLE_SLIME: case P_PART_PARTICLE_SPARK:
2332         partType[i].set(0, 5, 25,   0, 0,  0.5, 0.1, 0, 0.0, 0.0, HIT_BOUNCE, true, 0,    1, 0, 0, 0);
2333         break;
2334       case P_PART_PARTICLE_WOOD: case P_PART_PARTICLE_METAL:
2335         partType[i].set(0, 5, 5,   -torad(90), torad(180), 2.0, 0.1, 0.5, 0.0,  0.0, HIT_BOUNCE, false, 0,    1, 0, 0, 0);
2336         break;
2337       case P_PART_PARTICLE_BLOOD:
2338         partType[i].set(0, 5, 5,   0, 0,   0.25, 0.1, 0, 0.0,  0.0, HIT_BOUNCE, false, 0,   1, 0, 0, 0);
2339         break;
2340       case P_PART_PARTICLE_ENERGY:
2341         partType[i].set(0, 2, 20,   0, 0,   1.0, 0.1, 0, 0.0,  0.0, HIT_BOUNCE, true, 0,   1, 0, 0, 0);
2342         break;
2343         break;
2344       case P_PART_MF_RIFLE: case P_PART_MF_SHOTGUN:
2345         partType[i].set(0, 0, 0, 0, 0, 4.0, 0, 0, 0.0,  0.0, HIT_NOTHING, false, 0,    1, 0, 0, 0);
2346         break;
2347       case P_BLT_RIFLE: case P_BLT_SHOTGUN:
2348         partType[i].set(0, 0, 0, 0, 0, 0, 0, 0, 0.0,  0.0, HIT_DIE, true, 0,   1, 0, 0, 0);
2349         break;
2350       case P_BLT_BLASTER:
2351         partType[i].set(0, 0, 0, 0, 0, 0, 0, 0, 0.0,  0.0, HIT_DIE, true, 0,   1, 0, 0, 0);
2352         break;
2353       case P_BLT_HEPA:
2354         partType[i].set(0, 0, 0, 0, 0, 0, 0, 0, 0.0,  0.0, HIT_DIE, true, 100,   1, 0, 0, 0);
2355         break;
2356       case P_BLT_SHRAPNEL:
2357         partType[i].set(.5, 50, 0, -torad(360), torad(360), 0, 0, 0, 0.0,  0.0, HIT_BOUNCE, true, 0,   4, 0.25, 0, INF);
2358         break;
2359      case P_BLT_GRENADE:
2360         partType[i].set(0, 0, 20, -torad(360), torad(360), 0, 0, 0, 0.0,  0.0, HIT_BOUNCE, false, -7.5,   2, 0.5, 0, INF);
2361         break;
2362      case P_PART_RIFLE_SHELL: case P_PART_SHOTGUN_SHELL:
2363         partType[i].set(0, 1, 5, -torad(180), 0, 0.5, 0, 0, 0.0,  0.0, HIT_DIE, false, 0,   1, 0, 0, 0);
2364         break;
2365      case P_PART_EXPLOSION:
2366         partType[i].set(0, 0, 0, 0, 0, 4.0, 0, 0.25, 2,  0.0, HIT_NOTHING, true, 0,    1, 0, 0, 0);
2367         break;
2368      case P_PART_EXPLOSIONPART:
2369         partType[i].set(0, 25, 5,   0, 0,  4.0, 0.0, 0.25, 0.0,  0.0, HIT_DIE, true, 0,   1, 0, 0, 0);
2370         break;
2371      case P_PART_SMOKE:
2372         partType[i].set(1, 2, 5,   0, 0,  0.5, 0.0, 0, 2,  0.0, HIT_NOTHING, false, 0,   1, 0, 0, 0);
2373         break;
2374      case P_PART_PARTICLE_GLOW:
2375         partType[i].set(0, 1, 0,   0, 0,  0.9, 0.3, 0, 3.5,  0.0, HIT_NOTHING, true, 0,   1, 0, 0, 0);
2376         break;
2377       case P_PART_HEPA_TRAIL:
2378         partType[i].set(0, 5, 0,   0, 0,  0.5, 0.5, 0, -1,  0.0, HIT_NOTHING, true, 0,   1, 0, 0, 0);
2379         break;
2380       case P_PART_HEPA_EXPLOSION:
2381         partType[i].set(0, 0, 0,   0, 0,  0.5, 0.5, 0, 50,  3.0, HIT_NOTHING, true, 0,   1, 0, 0, 0);
2382         break;
2383       case P_PART_HEPA_SPARK:
2384         partType[i].set(1, 10, 15,   0, 0,  0, 0, 0, -0.5, 0.0, HIT_NOTHING, true, 0,    1, 0, 0, 0);
2385         break;
2386       case P_PART_GRENADE_TRAIL:
2387         partType[i].set(0, 5, 25,   0, 0,  1.0, 0.1, 0, 0.0, 0.0, HIT_BOUNCE, true, 0,    1, 0, 0, 0);
2388         break;
2389       case P_PART_LASER_SPARK1:
2390         partType[i].set(1, 2, 25,   0, 0,  0, 0, 0, -0.5, 0.0, HIT_NOTHING, true, 0,    1, 0, 0, 0);
2391         break;
2392       case P_PART_LASER_SPARK2:
2393         partType[i].set(1, 3, 15,   0, 0,  0, 0, 0, 0, 0.0, HIT_NOTHING, true, 0,    1, 0, 0, 0);
2394         break;
2395       case P_PART_ESG_SHOCKWAVE:
2396         partType[i].set(0, 0, 0,   0, 0,  0, 0, 0, 200.0, 0.0, HIT_NOTHING, true, 0,    1, 0, 0, 0);
2397         break;
2398       case P_PART_BIT:
2399         partType[i].set(0, 0, 0,   0, 0,  0, 0, 0, 0, 0.0, HIT_NOTHING, false, 0,    2, 0.25, 1.0, INF);
2400         break;
2401       case P_ESG_EXPLODER:
2402         partType[i].set(0, 0, 0,  0, 0,  0, 0, 0, 0, 0, HIT_NOTHING, true, 0,    1, 0, 0, 0);
2403         break;
2404     }
2405 
2406   for (int i = 0; i < NUM_BLT_TYPES; i++)
2407     switch(i)
2408     {
2409       case B_BLT_RIFLE:
2410         bltType[i].set(5, 260);
2411         break;
2412       case B_BLT_SHOTGUN:
2413         bltType[i].set(6, 400);
2414         break;
2415       case B_BLT_HEPA:
2416         bltType[i].set(10, 1200);
2417         break;
2418       case B_BLT_GRENADE:
2419         bltType[i].set(10, 1200);
2420         break;
2421       case B_BLT_SHRAPNEL:
2422         bltType[i].set(13, 400);
2423         break;
2424       case B_BLT_BLASTER:
2425         bltType[i].set(45, 480);
2426         break;
2427     }
2428 
2429   // void set(int spr, float w, float mr, int mb, float dmg, float fs)
2430   for (int i = 0; i < NUM_LASER_TYPES; i++)
2431     switch(i)
2432     {
2433       case L_STANDARD:
2434         lasType[i].set(TEX_BEAM1, 0.5, 200, 7, 40, 0.5);
2435         break;
2436       case L_CHARGED:
2437         lasType[i].set(TEX_BEAM2, 3.0, 100, 1, 240, 0.25);
2438     }
2439 }
2440