1 /* bzflag
2  * Copyright (c) 1993-2021 Tim Riker
3  *
4  * This package is free software;  you can redistribute it and/or
5  * modify it under the terms of the license found in the file
6  * named COPYING that should have accompanied this file.
7  *
8  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11  */
12 
13 /* interface header */
14 #include "LocalPlayer.h"
15 
16 /* common implementation headers */
17 #include "CommandManager.h"
18 #include "BZDBCache.h"
19 #include "FlagSceneNode.h"
20 #include "CollisionManager.h"
21 #include "PhysicsDriver.h"
22 #include "BzfEvent.h"
23 #include "WallObstacle.h"
24 #include "MeshObstacle.h"
25 
26 /* local implementation headers */
27 #include "World.h"
28 #include "sound.h"
29 #include "ForceFeedback.h"
30 #include "effectsRenderer.h"
31 
32 /* system implementation headers */
33 #include <algorithm>
34 
35 LocalPlayer*        LocalPlayer::mainPlayer = NULL;
36 
LocalPlayer(const PlayerId & _id,const char * name,const char * _motto)37 LocalPlayer::LocalPlayer(const PlayerId& _id,
38                          const char* name, const char* _motto) :
39     BaseLocalPlayer(_id, name, _motto),
40     gettingSound(true),
41     server(ServerLink::getServer()),
42     location(Dead),
43     firingStatus(Deceased),
44     flagShakingTime(0.0f),
45     flagShakingWins(0),
46     antidoteFlag(NULL),
47     desiredSpeed(0.0f),
48     desiredAngVel(0.0f),
49     lastSpeed(0.0f),
50     anyShotActive(false),
51     target(NULL),
52     nemesis(NULL),
53     recipient(NULL),
54     inputChanged(false),
55     stuckFrameCount(0),
56     spawning(false),
57     wingsFlapCount(0),
58     left(false),
59     right(false),
60     up(false),
61     down(false),
62     entryDrop(true),
63     wantJump(false),
64     jumpPressed(false),
65     deathPhyDrv(-1)
66 {
67     // initialize shots array to no shots fired
68     const int numShots = World::getWorld()->getMaxShots();
69     shots = new LocalShotPath*[numShots];
70     for (int i = 0; i < numShots; i++)
71         shots[i] = NULL;
72     // set input method
73     if (BZDB.isTrue("allowInputChange"))
74         inputMethod = Mouse;
75     else
76         setInputMethod(BZDB.get("activeInputDevice"));
77 }
78 
~LocalPlayer()79 LocalPlayer::~LocalPlayer()
80 {
81     // free shots
82     const int numShots = World::getWorld()->getMaxShots();
83     for (int i = 0; i < numShots; i++)
84         if (shots[i])
85             delete shots[i];
86     delete[] shots;
87 
88     // free antidote flag
89     delete antidoteFlag;
90 }
91 
setMyTank(LocalPlayer * player)92 void            LocalPlayer::setMyTank(LocalPlayer* player)
93 {
94     mainPlayer = player;
95 }
96 
doUpdate(float dt)97 void            LocalPlayer::doUpdate(float dt)
98 {
99     const bool hadShotActive = anyShotActive;
100     const int numShots = World::getWorld()->getMaxShots();
101     static TimeKeeper pauseTime = TimeKeeper::getNullTime();
102     static bool wasPaused = false;
103 
104     // if paused then boost the reload times by dt (so that, effectively,
105     // reloading isn't performed)
106     int i;
107     if (isPaused())
108     {
109         for (i = 0; i < numShots; i++)
110         {
111             if (shots[i])
112                 shots[i]->boostReloadTime(dt);
113         }
114 
115         // if we've been paused for a long time, drop our flag
116         if (!wasPaused)
117         {
118             pauseTime = TimeKeeper::getTick();
119             wasPaused = true;
120         }
121         if (TimeKeeper::getTick() -  pauseTime > BZDB.eval(StateDatabase::BZDB_PAUSEDROPTIME))
122         {
123             server->sendDropFlag(getPosition());
124             setStatus(getStatus() & ~PlayerState::FlagActive);
125             pauseTime = TimeKeeper::getSunExplodeTime();
126         }
127 
128     }
129     else
130     {
131         pauseTime = TimeKeeper::getNullTime();
132         wasPaused = false;
133     }
134 
135     // reap dead (reloaded) shots
136     for (i = 0; i < numShots; i++)
137         if (shots[i] && shots[i]->isReloaded())
138         {
139             if (!shots[i]->isExpired())
140                 shots[i]->setExpired();
141             delete shots[i];
142             shots[i] = NULL;
143         }
144 
145     // update shots
146     anyShotActive = false;
147     for (i = 0; i < numShots; i++)
148         if (shots[i])
149         {
150             shots[i]->update(dt);
151             if (!shots[i]->isExpired()) anyShotActive = true;
152         }
153 
154     // if no shots now out (but there had been) then reset target
155     if (!anyShotActive && hadShotActive)
156         target = NULL;
157 
158     // drop bad flag if timeout has expired
159     if (!isPaused() && dt > 0.0f && World::getWorld()->allowShakeTimeout() &&
160             getFlag() != Flags::Null && getFlag()->endurance == FlagSticky &&
161             flagShakingTime > 0.0f)
162     {
163         flagShakingTime -= dt;
164         if (flagShakingTime <= 0.0f)
165         {
166             flagShakingTime = 0.0f;
167             server->sendDropFlag(getPosition());
168         }
169     }
170 }
171 
172 
doSlideMotion(float dt,float slideTime,float newAngVel,float * newVelocity)173 void LocalPlayer::doSlideMotion(float dt, float slideTime,
174                                 float newAngVel, float* newVelocity)
175 {
176     const float oldAzimuth = getAngle();
177     const float* oldVelocity = getVelocity();
178 
179     const float angle = oldAzimuth + (0.5f * dt * newAngVel);
180     const float cos_val = cosf(angle);
181     const float sin_val = sinf(angle);
182     const float scale = (dt / slideTime);
183     const float speedAdj = desiredSpeed * scale;
184     const float* ov = oldVelocity;
185     const float oldSpeed = sqrtf((ov[0] * ov[0]) + (ov[1] * ov[1]));
186     float* nv = newVelocity;
187     nv[0] = ov[0] + (cos_val * speedAdj);
188     nv[1] = ov[1] + (sin_val * speedAdj);
189     const float newSpeed = sqrtf((nv[0] * nv[0]) + (nv[1] * nv[1]));
190 
191     float maxSpeed = getMaxSpeed();
192 
193     if (newSpeed > maxSpeed)
194     {
195         float adjSpeed;
196         if (oldSpeed > maxSpeed)
197         {
198             adjSpeed = oldSpeed - (dt * (maxSpeed / slideTime));
199             if (adjSpeed < 0.0f)
200                 adjSpeed = 0.0f;
201         }
202         else
203             adjSpeed = maxSpeed;
204         const float speedScale = adjSpeed / newSpeed;
205         nv[0] *= speedScale;
206         nv[1] *= speedScale;
207     }
208     return;
209 }
210 
211 
getNewAngVel(float old,float desired)212 float LocalPlayer::getNewAngVel(float old, float desired)
213 {
214     float newAngVel;
215 
216     if ((inputMethod != Keyboard) || (getPhysicsDriver() >= 0))
217     {
218         // mouse and joystick users
219         newAngVel = desired;
220 
221     }
222     else
223     {
224 
225         /* keybaord users
226          * the larger the oldAngVel contribution, the more slowly an
227          * angular velocity converges to the desired "max" velocity; the
228          * contribution of the desired and old velocity should add up to
229          * one for a linear convergence rate.
230          */
231         newAngVel = (old * 0.8f) + (desired * 0.2f);
232 
233         // instant stop
234         if ((old * desired < 0.0f) ||
235                 (NEAR_ZERO(desired, ZERO_TOLERANCE)))
236             newAngVel = desired;
237     }
238     return newAngVel;
239 }
240 
241 
doUpdateMotion(float dt)242 void            LocalPlayer::doUpdateMotion(float dt)
243 {
244     static const float MinSearchStep = 0.0001f;
245     static const int MaxSearchSteps = 7;
246     static const int MaxSteps = 4;
247     static const float TinyDistance = 0.001f;
248 
249     // save old state
250     const Location oldLocation = location;
251     const float* oldPosition = getPosition();
252     const float oldAzimuth = getAngle();
253     const float oldAngVel = getAngularVelocity();
254     const float* oldVelocity = getVelocity();
255 
256     // prepare new state
257     float newVelocity[3];
258     newVelocity[0] = oldVelocity[0];
259     newVelocity[1] = oldVelocity[1];
260     newVelocity[2] = oldVelocity[2];
261     float newAngVel = 0.0f;
262 
263     clearRemoteSounds();
264 
265     // if was teleporting and exceeded teleport time then not teleporting anymore
266     if (isTeleporting() &&
267             ((lastTime - getTeleportTime()) >= BZDB.eval(StateDatabase::BZDB_TELEPORTTIME)))
268         setStatus(getStatus() & ~short(PlayerState::Teleporting));
269 
270     // phased means we can pass through buildings
271     const bool phased = ((location == Dead) || (location == Exploding) ||
272                          (getFlag() == Flags::OscillationOverthruster) ||
273                          isPhantomZoned());
274 
275     float groundLimit = 0.0f;
276     if (getFlag() == Flags::Burrow)
277         groundLimit = BZDB.eval(StateDatabase::BZDB_BURROWDEPTH);
278 
279     // get linear and angular speed at start of time step
280     if (!NEAR_ZERO(dt,ZERO_TOLERANCE))
281     {
282         if (location == Dead || isPaused())
283         {
284             // can't move if paused or dead -- set dt to zero instead of
285             // clearing velocity and newAngVel for when we resume (if paused)
286             dt = 0.0f;
287             newAngVel = oldAngVel;
288         }
289         else if (location == Exploding)
290         {
291             // see if explosing time has expired
292             if (lastTime - getExplodeTime() >= BZDB.eval(StateDatabase::BZDB_EXPLODETIME))
293             {
294                 dt -= float((lastTime - getExplodeTime()) - BZDB.eval(StateDatabase::BZDB_EXPLODETIME));
295                 if (dt < 0.0f)
296                     dt = 0.0f;
297                 setStatus(PlayerState::DeadStatus);
298                 location = Dead;
299                 if (isAutoPilot())
300                     CMDMGR.run("restart");
301             }
302 
303             // can't control explosion motion
304             newVelocity[2] += BZDBCache::gravity * dt;
305             newAngVel = 0.0f; // or oldAngVel to spin while exploding
306         }
307         else if ((location == OnGround) || (location == OnBuilding) ||
308                  (location == InBuilding && oldPosition[2] == groundLimit))
309         {
310             // full control
311             float speed = desiredSpeed;
312 
313             // angular velocity
314             newAngVel = getNewAngVel(oldAngVel, desiredAngVel);
315 
316             // limit acceleration
317             doMomentum(dt, speed, newAngVel);
318 
319             // compute velocity so far
320             const float angle = oldAzimuth + 0.5f * dt * newAngVel;
321             newVelocity[0] = speed * cosf(angle);
322             newVelocity[1] = speed * sinf(angle);
323             newVelocity[2] = 0.0f;
324 
325             // now friction, if any
326             doFriction(dt, oldVelocity, newVelocity);
327 
328             // reset our flap count if we have wings
329             if (getFlag() == Flags::Wings)
330                 wingsFlapCount = (int) BZDB.eval(StateDatabase::BZDB_WINGSJUMPCOUNT);
331 
332             if ((oldPosition[2] < 0.0f) && (getFlag() == Flags::Burrow))
333                 newVelocity[2] += 4 * BZDBCache::gravity * dt;
334             else if (oldPosition[2] > groundLimit)
335                 newVelocity[2] += BZDBCache::gravity * dt;
336 
337             // save speed for next update
338             lastSpeed = speed;
339         }
340         else
341         {
342             // can't control motion in air unless have wings
343             if (getFlag() == Flags::Wings)
344             {
345                 float speed = desiredSpeed;
346 
347                 // angular velocity
348                 newAngVel = getNewAngVel(oldAngVel, desiredAngVel);
349 
350                 // compute horizontal velocity so far
351                 const float slideTime = BZDB.eval(StateDatabase::BZDB_WINGSSLIDETIME);
352                 if (slideTime > 0.0)
353                     doSlideMotion(dt, slideTime, newAngVel, newVelocity);
354                 else
355                 {
356                     const float angle = oldAzimuth + 0.5f * dt * newAngVel;
357                     newVelocity[0] = speed * cosf(angle);
358                     newVelocity[1] = speed * sinf(angle);
359                 }
360 
361                 newVelocity[2] += BZDB.eval(StateDatabase::BZDB_WINGSGRAVITY) * dt;
362                 lastSpeed = speed;
363             }
364             else
365             {
366                 newVelocity[2] += BZDBCache::gravity * dt;
367                 newAngVel = oldAngVel;
368             }
369         }
370 
371 
372         // now apply outside forces
373         doForces(dt, newVelocity, newAngVel);
374 
375         // below the ground: however I got there, creep up
376         if (oldPosition[2] < groundLimit)
377             newVelocity[2] = std::max(newVelocity[2], -oldPosition[2] / 2.0f + 0.5f);
378     }
379 
380     // jump here, we allow a little change in horizontal motion
381     if (wantJump)
382     {
383         doJump();
384         if (!wantJump)
385         {
386             newVelocity[2] = oldVelocity[2];
387             if ((lastObstacle != NULL) && !lastObstacle->isFlatTop()
388                     && BZDB.isTrue(StateDatabase::BZDB_NOCLIMB))
389             {
390                 newVelocity[0] = 0.0f;
391                 newVelocity[1] = 0.0f;
392             }
393         }
394     }
395 
396     // do the physics driver stuff
397     const int driverId = getPhysicsDriver();
398     const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driverId);
399     if (phydrv != NULL)
400     {
401         const float* v = phydrv->getLinearVel();
402 
403         newVelocity[2] += v[2];
404 
405         if (phydrv->getIsSlide())
406         {
407             const float slideTime = phydrv->getSlideTime();
408             doSlideMotion(dt, slideTime, newAngVel, newVelocity);
409         }
410         else
411         {
412             // adjust the horizontal velocity
413             newVelocity[0] += v[0];
414             newVelocity[1] += v[1];
415 
416             const float av = phydrv->getAngularVel();
417             const float* ap = phydrv->getAngularPos();
418 
419             if (av != 0.0f)
420             {
421                 // the angular velocity is in radians/sec
422                 newAngVel += av;
423                 const float dx = oldPosition[0] - ap[0];
424                 const float dy = oldPosition[1] - ap[1];
425                 newVelocity[0] -= av * dy;
426                 newVelocity[1] += av * dx;
427             }
428         }
429     }
430     lastObstacle = NULL;
431 
432     // get new position so far (which is just the current position)
433     float newPos[3];
434     newPos[0] = oldPosition[0];
435     newPos[1] = oldPosition[1];
436     newPos[2] = oldPosition[2];
437     float newAzimuth = oldAzimuth;
438 
439     // move tank through the time step.  if there's a collision then
440     // move the tank up to the collision, adjust the velocity to
441     // prevent interpenetration, and repeat.  avoid infinite loops
442     // by only allowing a maximum number of repeats.
443     bool expelled;
444     const Obstacle* obstacle;
445     float timeStep = dt;
446     int stuck = false;
447     if (location != Dead && location != Exploding)
448     {
449         location = OnGround;
450 
451         // anti-stuck code is useful only when alive
452         // then only any 100 frames while stuck, take an action
453 
454         // try to see if we are stuck on a building
455         obstacle = getHitBuilding(newPos, newAzimuth, newPos, newAzimuth,
456                                   phased, expelled);
457 
458         if (obstacle && expelled)
459         {
460             stuckFrameCount++;
461             stuck = true;
462         }
463         else
464             stuckFrameCount = 0;
465 
466         if (stuckFrameCount > 100)
467         {
468             stuckFrameCount = 0;
469             // we are using a maximum value on time for frame to avoid lagging problem
470             setDesiredSpeed(0.25f);
471             float delta = dt > 0.1f ? 0.1f : dt;
472             float normalStuck[3];
473             obstacle->getNormal(newPos, normalStuck);
474             // use all the given speed to exit
475             float movementMax = desiredSpeed * delta;
476 
477             newVelocity[0] = movementMax * normalStuck[0];
478             newVelocity[1] = movementMax * normalStuck[1];
479             if ((World::getWorld()->allowJumping() || (getFlag() == Flags::Jumping) || (getFlag() == Flags::Wings)) &&
480                     (getFlag() != Flags::NoJumping))
481                 newVelocity[2] = movementMax * normalStuck[2];
482             else
483                 newVelocity[2] = 0.0f;
484 
485             // exit will be in the normal direction
486             newPos[0] += newVelocity[0];
487             newPos[1] += newVelocity[1];
488             newPos[2] += newVelocity[2];
489             // compute time for all other kind of movements
490             timeStep -= delta;
491         }
492     }
493 
494     float nominalPlanarSpeed2
495         = newVelocity[0] * newVelocity[0]
496           + newVelocity[1] * newVelocity[1];
497 
498     for (int numSteps = 0; numSteps < MaxSteps; numSteps++)
499     {
500         // record position at beginning of time step
501         float tmpPos[3], tmpAzimuth;
502         tmpAzimuth = newAzimuth;
503         tmpPos[0] = newPos[0];
504         tmpPos[1] = newPos[1];
505         tmpPos[2] = newPos[2];
506 
507         // get position at end of time step
508         newAzimuth = tmpAzimuth + timeStep * newAngVel;
509         newPos[0] = tmpPos[0] + timeStep * newVelocity[0];
510         newPos[1] = tmpPos[1] + timeStep * newVelocity[1];
511         newPos[2] = tmpPos[2] + timeStep * newVelocity[2];
512         if ((newPos[2] < groundLimit) && (newVelocity[2] < 0))
513         {
514             // Hit lower limit, stop falling
515             newPos[2] = groundLimit;
516             if (location == Exploding)
517             {
518                 // tank pieces reach the ground, friction
519                 // stop them, & mainly player view
520                 newPos[0] = tmpPos[0];
521                 newPos[1] = tmpPos[1];
522             }
523         }
524 
525         // see if we hit anything.  if not then we're done.
526         obstacle = getHitBuilding(tmpPos, tmpAzimuth, newPos, newAzimuth,
527                                   phased, expelled);
528 
529         if (!obstacle || !expelled) break;
530 
531         float obstacleTop = obstacle->getPosition()[2] + obstacle->getHeight();
532         if ((oldLocation != InAir) && obstacle->isFlatTop() &&
533                 (obstacleTop != tmpPos[2]) &&
534                 (obstacleTop < (tmpPos[2] + BZDB.eval(StateDatabase::BZDB_MAXBUMPHEIGHT))))
535         {
536             newPos[0] = oldPosition[0];
537             newPos[1] = oldPosition[1];
538             newPos[2] = obstacleTop;
539 
540             // drive over bumps
541             const Obstacle* bumpObstacle = getHitBuilding(newPos, tmpAzimuth,
542                                            newPos, newAzimuth,
543                                            phased, expelled);
544             if (bumpObstacle == NULL)
545             {
546                 move(newPos, getAngle());
547                 newPos[0] += newVelocity[0] * (dt * 0.5f);
548                 newPos[1] += newVelocity[1] * (dt * 0.5f);
549                 break;
550             }
551         }
552 
553         // record position when hitting
554         float hitPos[3], hitAzimuth;
555         hitAzimuth = newAzimuth;
556         hitPos[0] = newPos[0];
557         hitPos[1] = newPos[1];
558         hitPos[2] = newPos[2];
559 
560         // find the latest time before the collision
561         float searchTime = 0.0f, searchStep = 0.5f * timeStep;
562         for (int i = 0; searchStep > MinSearchStep && i < MaxSearchSteps;
563                 searchStep *= 0.5f, i++)
564         {
565             // get intermediate position
566             const float t = searchTime + searchStep;
567             newAzimuth = tmpAzimuth + (t * newAngVel);
568             newPos[0] = tmpPos[0] + (t * newVelocity[0]);
569             newPos[1] = tmpPos[1] + (t * newVelocity[1]);
570             newPos[2] = tmpPos[2] + (t * newVelocity[2]);
571             if ((newPos[2] < groundLimit) && (newVelocity[2] < 0))
572                 newPos[2] = groundLimit;
573 
574             // see if we hit anything
575             bool searchExpelled;
576             const Obstacle* searchObstacle =
577                 getHitBuilding(tmpPos, tmpAzimuth, newPos, newAzimuth,
578                                phased, searchExpelled);
579 
580             if (!searchObstacle || !searchExpelled)
581             {
582                 // if no hit then search latter half of time step
583                 searchTime = t;
584             }
585             else if (searchObstacle)
586             {
587                 // if we hit a building then record which one and where
588                 obstacle = searchObstacle;
589 
590                 expelled = searchExpelled;
591                 hitAzimuth = newAzimuth;
592                 hitPos[0] = newPos[0];
593                 hitPos[1] = newPos[1];
594                 hitPos[2] = newPos[2];
595             }
596         }
597 
598         // get position just before impact
599         newAzimuth = tmpAzimuth + (searchTime * newAngVel);
600         newPos[0] = tmpPos[0] + (searchTime * newVelocity[0]);
601         newPos[1] = tmpPos[1] + (searchTime * newVelocity[1]);
602         newPos[2] = tmpPos[2] + (searchTime * newVelocity[2]);
603         if (oldPosition[2] < groundLimit)
604             newVelocity[2] = std::max(newVelocity[2], -oldPosition[2] / 2.0f + 0.5f);
605 
606 
607         // record how much time is left in time step
608         timeStep -= searchTime;
609 
610         // get normal at intersection.  sometimes fancy test says there's
611         // no intersection but we're expecting one so, in that case, fall
612         // back to simple normal calculation.
613         float normal[3];
614         if (!getHitNormal(obstacle, newPos, newAzimuth, hitPos, hitAzimuth, normal))
615             obstacle->getNormal(newPos, normal);
616 
617         // check for being on a building
618         if ((newPos[2] > 0.0f) && (normal[2] > 0.001f))
619         {
620             if (location != Dead && location != Exploding && expelled)
621             {
622                 location = OnBuilding;
623                 lastObstacle = obstacle;
624             }
625             newVelocity[2] = 0.0f;
626         }
627         else
628         {
629             // get component of velocity in normal direction (in horizontal plane)
630             float mag = (normal[0] * newVelocity[0]) +
631                         (normal[1] * newVelocity[1]);
632 
633             // handle upward normal component to prevent an upward force
634             if (!NEAR_ZERO(normal[2], ZERO_TOLERANCE))
635             {
636                 // if going down then stop falling
637                 if (newVelocity[2] < 0.0f && newVelocity[2] -
638                         (mag + normal[2] * newVelocity[2]) * normal[2] > 0.0f)
639                     newVelocity[2] = 0.0f;
640 
641                 // normalize force magnitude in horizontal plane
642                 float horNormal = normal[0] * normal[0] + normal[1] * normal[1];
643                 if (!NEAR_ZERO(horNormal, ZERO_TOLERANCE))
644                     mag /= horNormal;
645             }
646 
647             // cancel out component in normal direction (if velocity and
648             // normal point in opposite directions).  also back off a tiny
649             // amount to prevent a spurious collision against the same
650             // obstacle.
651             if (mag < 0.0f)
652             {
653                 newVelocity[0] -= mag * normal[0];
654                 newVelocity[1] -= mag * normal[1];
655 
656                 newPos[0] -= TinyDistance * mag * normal[0];
657                 newPos[1] -= TinyDistance * mag * normal[1];
658             }
659             if (mag > -0.01f)
660             {
661                 // assume we're not allowed to turn anymore if there's no
662                 // significant velocity component to cancel out.
663                 newAngVel = 0.0f;
664             }
665         }
666     }
667 
668     // pick new location if we haven't already done so
669     if (location == OnGround)
670     {
671         if (obstacle && (!expelled || stuck))
672             location = InBuilding;
673         else if (newPos[2] > 0.0f)
674             location = InAir;
675     }
676 
677     // see if we're crossing a wall
678     if (location == InBuilding && getFlag() == Flags::OscillationOverthruster)
679     {
680         if (obstacle->isCrossing(newPos, newAzimuth,
681                                  0.5f * BZDBCache::tankLength,
682                                  0.5f * BZDBCache::tankWidth,
683                                  BZDBCache::tankHeight, NULL))
684             setStatus(getStatus() | int(PlayerState::CrossingWall));
685         else
686             setStatus(getStatus() & ~int(PlayerState::CrossingWall));
687     }
688     else if (World::getWorld()->crossingTeleporter( newPos, newAzimuth,
689              0.5f * BZDBCache::tankLength,
690              0.5f * BZDBCache::tankWidth,
691              BZDBCache::tankHeight, crossingPlane))
692         setStatus(getStatus() | int(PlayerState::CrossingWall));
693     else
694         setStatus(getStatus() & ~int(PlayerState::CrossingWall));
695 
696     // compute actual velocities.  do this before teleportation.
697     if (!NEAR_ZERO(dt, ZERO_TOLERANCE))
698     {
699         const float oodt = 1.0f / dt;
700         newAngVel = (newAzimuth - oldAzimuth) * oodt;
701         newVelocity[0] = (newPos[0] - oldPosition[0]) * oodt;
702         newVelocity[1] = (newPos[1] - oldPosition[1]) * oodt;
703         newVelocity[2] = (newPos[2] - oldPosition[2]) * oodt;
704 
705         float newPlanarSpeed2 = newVelocity[0] * newVelocity[0]
706                                 + newVelocity[1] * newVelocity[1];
707         float scaling = newPlanarSpeed2 / nominalPlanarSpeed2;
708         if (scaling > 1.0f)
709         {
710             scaling = sqrtf(scaling);
711             newVelocity[0] /= scaling;
712             newVelocity[1] /= scaling;
713         }
714     }
715 
716     // see if we teleported
717     int face;
718     const Teleporter* teleporter;
719     if (!isAlive())
720         teleporter = NULL;
721     else
722         teleporter = World::getWorld()->crossesTeleporter(oldPosition, newPos, face);
723 
724     if (teleporter)
725     {
726         if (getFlag() == Flags::PhantomZone)
727         {
728             // change zoned state
729             setStatus(getStatus() ^ PlayerState::FlagActive);
730             if (gettingSound)
731                 playLocalSound(SFX_PHANTOM);
732         }
733         else
734         {
735             // teleport
736             const int source = World::getWorld()->getTeleporter(teleporter, face);
737             int targetTele = World::getWorld()->getTeleportTarget(source);
738 
739             int outFace;
740             const Teleporter* outPort =
741                 World::getWorld()->getTeleporter(targetTele, outFace);
742             teleporter->getPointWRT(*outPort, face, outFace,
743                                     newPos, newVelocity, newAzimuth,
744                                     newPos, newVelocity, &newAzimuth);
745 
746             // check for a hit on the other side
747             const Obstacle* teleObs =
748                 getHitBuilding(newPos, newAzimuth, newPos, newAzimuth, phased, expelled);
749             if (teleObs != NULL)
750             {
751                 // revert
752                 memcpy (newPos, oldPosition, sizeof(float[3]));
753                 newVelocity[0] = newVelocity[1] = 0.0f;
754                 newVelocity[2] = oldVelocity[2];
755                 newAzimuth = oldAzimuth;
756             }
757             else
758             {
759                 // save teleport info
760                 setTeleport(lastTime, source, targetTele);
761                 server->sendTeleport(source, targetTele);
762                 if (gettingSound)
763                     playLocalSound(SFX_TELEPORT);
764             }
765         }
766     }
767 
768     // setup the physics driver
769     setPhysicsDriver(-1);
770     if ((lastObstacle != NULL) &&
771             (lastObstacle->getType() == MeshFace::getClassName()))
772     {
773         const MeshFace* meshFace = (const MeshFace*) lastObstacle;
774         int driverIdent = meshFace->getPhysicsDriver();
775         const PhysicsDriver* phydriver = PHYDRVMGR.getDriver(driverIdent);
776         if (phydriver != NULL)
777             setPhysicsDriver(driverIdent);
778     }
779 
780     // deal with drop sounds and effects
781     if (entryDrop)
782     {
783         // because the starting position that the server sends can result
784         // in an initial InAir condition, we use this bool to avoid having
785         // a false jump mess with the spawnEffect()
786         // FIXME: this isn't a clean way to do it
787         if ((oldLocation == InAir) == (location == InAir))
788             entryDrop = false;
789     }
790 
791     const bool justLanded =
792         (oldLocation == InAir) &&
793         ((location == OnGround) || (location == OnBuilding));
794 
795     if (justLanded)
796     {
797         setLandingSpeed(oldVelocity[2]);
798         EFFECTS.addLandEffect(getColor(),newPos,getAngle());
799     }
800     if (gettingSound)
801     {
802         const PhysicsDriver* phydriver = PHYDRVMGR.getDriver(getPhysicsDriver());
803         if ((phydriver != NULL) && (phydriver->getLinearVel()[2] > 0.0f))
804         {
805             playLocalSound(SFX_BOUNCE);
806             addRemoteSound(PlayerState::BounceSound);
807         }
808         else if (justLanded && !entryDrop)
809             playLocalSound(SFX_LAND);
810         else if ((location == OnGround) &&
811                  (oldPosition[2] == 0.0f) && (newPos[2] < 0.f))
812             playLocalSound(SFX_BURROW);
813     }
814 
815     // set falling status
816     if (location == OnGround || location == OnBuilding ||
817             (location == InBuilding && newPos[2] == 0.0f))
818         setStatus(getStatus() & ~short(PlayerState::Falling));
819     else if (location == InAir || location == InBuilding)
820         setStatus(getStatus() | short(PlayerState::Falling));
821 
822     // set UserInput status (determines how animated treads are drawn)
823     const PhysicsDriver* phydrv2 = PHYDRVMGR.getDriver(getPhysicsDriver());
824     if (((phydrv2 != NULL) && phydrv2->getIsSlide()) ||
825             ((getFlag() == Flags::Wings) && (location == InAir) &&
826              (BZDB.eval(StateDatabase::BZDB_WINGSSLIDETIME) > 0.0f)))
827         setStatus(getStatus() | short(PlayerState::UserInputs));
828     else
829         setStatus(getStatus() & ~short(PlayerState::UserInputs));
830 
831     // compute firing status
832     switch (location)
833     {
834     case Dead:
835     case Exploding:
836         firingStatus = Deceased;
837         break;
838     case InBuilding:
839         firingStatus = (getFlag() == Flags::PhantomZone) ? Zoned : Sealed;
840         break;
841     default:
842         if (isPhantomZoned())
843             firingStatus = Zoned;
844         else if ((getReloadTime() > 0.0f) && (getFlag() != Flags::TriggerHappy))
845             firingStatus = Loading;
846         else
847             firingStatus = Ready;
848         break;
849     }
850 
851     // burrowed and oscillating tanks get some resistance in their joystick
852     // if they have ff on
853     if ((location == InBuilding) || (newPos[2] < -0.5f))
854         ForceFeedback::solidMatterFriction();
855 
856     // calculate the list of inside buildings
857     insideBuildings.clear();
858     if (location == InBuilding)
859         collectInsideBuildings();
860 
861     // move tank
862     move(newPos, newAzimuth);
863     setVelocity(newVelocity);
864     setAngularVelocity(newAngVel);
865     setRelativeMotion();
866     newAzimuth = getAngle(); // pickup the limited angle range from move()
867 
868     // see if I'm over my antidote
869     if (antidoteFlag && location == OnGround)
870     {
871         float dist =
872             ((flagAntidotePos[0] - newPos[0]) * (flagAntidotePos[0] - newPos[0])) +
873             ((flagAntidotePos[1] - newPos[1]) * (flagAntidotePos[1] - newPos[1]));
874         const float twoRads = getRadius() + BZDBCache::flagRadius;
875         if (dist < (twoRads * twoRads))
876             server->sendDropFlag(getPosition());
877     }
878 
879     if ((getFlag() == Flags::Bouncy) && ((location == OnGround) || (location == OnBuilding)))
880     {
881         if (oldLocation != InAir)
882         {
883             if ((TimeKeeper::getTick() - bounceTime) > 0)
884                 doJump();
885         }
886         else
887         {
888             bounceTime = TimeKeeper::getTick();
889             bounceTime += 0.2f;
890         }
891     }
892 
893     if (gettingSound)
894     {
895         if (oldPosition[0] != newPos[0] || oldPosition[1] != newPos[1] ||
896                 oldPosition[2] != newPos[2] || oldAzimuth != newAzimuth)
897         {
898             moveSoundReceiver(newPos[0], newPos[1], newPos[2], newAzimuth,
899                               NEAR_ZERO(dt, ZERO_TOLERANCE) ||
900                               ((teleporter != NULL) && (getFlag() != Flags::PhantomZone)));
901         }
902         if (NEAR_ZERO(dt, ZERO_TOLERANCE))
903             speedSoundReceiver(newVelocity[0], newVelocity[1], newVelocity[2]);
904         else
905         {
906             speedSoundReceiver((newPos[0] - oldPosition[0]) / dt,
907                                (newPos[1] - oldPosition[1]) / dt,
908                                (newPos[2] - oldPosition[2]) / dt);
909         }
910     }
911 }
912 
913 
getHitBuilding(const float * p,float a,bool phased,bool & expelled) const914 const Obstacle* LocalPlayer::getHitBuilding(const float* p, float a,
915         bool phased, bool& expelled) const
916 {
917     const float* dims = getDimensions();
918     const Obstacle* obstacle =
919         World::getWorld()->hitBuilding(p, a, dims[0], dims[1], dims[2]);
920 
921     expelled = (obstacle != NULL);
922     if (expelled && phased)
923         expelled = (obstacle->getType() == WallObstacle::getClassName() ||
924                     obstacle->getType() == Teleporter::getClassName() ||
925                     (getFlag() == Flags::OscillationOverthruster && desiredSpeed < 0.0f &&
926                      p[2] == 0.0f));
927     return obstacle;
928 }
929 
930 
getHitBuilding(const float * oldP,float oldA,const float * p,float a,bool phased,bool & expelled)931 const Obstacle* LocalPlayer::getHitBuilding(const float* oldP, float oldA,
932         const float* p, float a,
933         bool phased, bool& expelled)
934 {
935     const bool hasOOflag = getFlag() == Flags::OscillationOverthruster;
936     const float* dims = getDimensions();
937     const Obstacle* obstacle = World::getWorld()->
938                                hitBuilding(oldP, oldA, p, a, dims[0], dims[1], dims[2], !hasOOflag);
939 
940     expelled = (obstacle != NULL);
941     if (expelled && phased)
942         expelled = (obstacle->getType() == WallObstacle::getClassName() ||
943                     obstacle->getType() == Teleporter::getClassName() ||
944                     (hasOOflag && desiredSpeed < 0.0f && p[2] == 0.0f));
945 
946     if (obstacle != NULL)
947     {
948         if (obstacle->getType() == MeshFace::getClassName())
949         {
950             const MeshFace* face = (const MeshFace*) obstacle;
951             const int driver = face->getPhysicsDriver();
952             const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driver);
953             if ((phydrv != NULL) && phydrv->getIsDeath())
954                 deathPhyDrv = driver;
955         }
956     }
957 
958     return obstacle;
959 }
960 
961 
getHitNormal(const Obstacle * o,const float * pos1,float azimuth1,const float * pos2,float azimuth2,float * normal) const962 bool LocalPlayer::getHitNormal(const Obstacle* o,
963                                const float* pos1, float azimuth1,
964                                const float* pos2, float azimuth2,
965                                float* normal) const
966 {
967     const float* dims = getDimensions();
968     return o->getHitNormal(pos1, azimuth1, pos2, azimuth2,
969                            dims[0], dims[1], dims[2], normal);
970 }
971 
972 
notInObstacleList(const Obstacle * obs,const std::vector<const Obstacle * > & list)973 static bool notInObstacleList(const Obstacle* obs,
974                               const std::vector<const Obstacle*>& list)
975 {
976     for (unsigned int i = 0; i < list.size(); i++)
977     {
978         if (obs == list[i])
979             return false;
980     }
981     return true;
982 }
983 
984 
collectInsideBuildings()985 void LocalPlayer::collectInsideBuildings()
986 {
987     const float* pos = getPosition();
988     const float angle = getAngle();
989     const float* dims = getDimensions();
990 
991     // get the list of possible inside buildings
992     const ObsList* olist =
993         COLLISIONMGR.boxTest (pos, angle, dims[0], dims[1], dims[2]);
994 
995     for (int i = 0; i < olist->count; i++)
996     {
997         const Obstacle* obs = olist->list[i];
998         if (obs->inBox(pos, angle, dims[0], dims[1], dims[2]))
999         {
1000             if (obs->getType() == MeshFace::getClassName())
1001             {
1002                 const MeshFace* face = (const MeshFace*) obs;
1003                 const MeshObstacle* mesh = (const MeshObstacle*) face->getMesh();
1004                 // check it for the death touch
1005                 if (deathPhyDrv < 0)
1006                 {
1007                     const int driver = face->getPhysicsDriver();
1008                     const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driver);
1009                     if ((phydrv != NULL) && (phydrv->getIsDeath()))
1010                         deathPhyDrv = driver;
1011                 }
1012                 // add the mesh if not already present
1013                 if (!obs->isDriveThrough() &&
1014                         notInObstacleList(mesh, insideBuildings))
1015                     insideBuildings.push_back(mesh);
1016             }
1017             else if (!obs->isDriveThrough())
1018             {
1019                 if (obs->getType() == MeshObstacle::getClassName())
1020                 {
1021                     // add the mesh if not already present
1022                     if (notInObstacleList(obs, insideBuildings))
1023                         insideBuildings.push_back(obs);
1024                 }
1025                 else
1026                     insideBuildings.push_back(obs);
1027             }
1028         }
1029     }
1030 
1031     return;
1032 }
1033 
1034 
getFlagShakingTime() const1035 float           LocalPlayer::getFlagShakingTime() const
1036 {
1037     return flagShakingTime;
1038 }
1039 
getFlagShakingWins() const1040 int         LocalPlayer::getFlagShakingWins() const
1041 {
1042     return flagShakingWins;
1043 }
1044 
getAntidoteLocation() const1045 const GLfloat*      LocalPlayer::getAntidoteLocation() const
1046 {
1047     return (const GLfloat*)(antidoteFlag ? antidoteFlag->getSphere() : NULL);
1048 }
1049 
getShot(int index) const1050 ShotPath*       LocalPlayer::getShot(int index) const
1051 {
1052     index &= 0x00FF;
1053     if ((index < 0) || (index >= World::getWorld()->getMaxShots()))
1054         return NULL;
1055     return shots[index];
1056 }
1057 
restart(const float * pos,float _azimuth)1058 void            LocalPlayer::restart(const float* pos, float _azimuth)
1059 {
1060     // put me in limbo
1061     setStatus(short(PlayerState::DeadStatus));
1062 
1063     // can't have a flag
1064     setFlag(Flags::Null);
1065 
1066     // get rid of existing shots
1067     const int numShots = World::getWorld()->getMaxShots();
1068     // get rid of existing shots
1069     for (int i = 0; i < numShots; i++)
1070         if (shots[i])
1071         {
1072             delete shots[i];
1073             shots[i] = NULL;
1074         }
1075     anyShotActive = false;
1076 
1077     // no target
1078     target = NULL;
1079 
1080     // no death
1081     deathPhyDrv = -1;
1082 
1083     // initialize position/speed state
1084     static const float zero[3] = { 0.0f, 0.0f, 0.0f };
1085     location = (pos[2] > 0.0f) ? OnBuilding : OnGround;
1086     lastObstacle = NULL;
1087     lastSpeed = 0.0f;
1088     desiredSpeed = 0.0f;
1089     desiredAngVel = 0.0f;
1090     move(pos, _azimuth);
1091     setVelocity(zero);
1092     setAngularVelocity(0.0f);
1093     left  = false;
1094     right = false;
1095     up    = false;
1096     down  = false;
1097     wantJump = false;
1098     doUpdateMotion(0.0f);
1099     entryDrop = true;
1100 
1101     // make me alive now
1102     setStatus(getStatus() | short(PlayerState::Alive));
1103 }
1104 
setTeam(TeamColor _team)1105 void            LocalPlayer::setTeam(TeamColor _team)
1106 {
1107     changeTeam(_team);
1108 }
1109 
setDesiredSpeed(float fracOfMaxSpeed)1110 void            LocalPlayer::setDesiredSpeed(float fracOfMaxSpeed)
1111 {
1112     FlagType* flag = getFlag();
1113     // can't go faster forward than at top speed, and backward at half speed
1114     if (fracOfMaxSpeed > 1.0f) fracOfMaxSpeed = 1.0f;
1115     else if (fracOfMaxSpeed < -0.5f) fracOfMaxSpeed = -0.5f;
1116 
1117     // oscillation overthruster tank in building can't back up
1118     if (fracOfMaxSpeed < 0.0f && getLocation() == InBuilding &&
1119             flag == Flags::OscillationOverthruster)
1120         fracOfMaxSpeed = 0.0f;
1121 
1122     // boost speed for certain flags
1123     if (flag == Flags::Velocity)
1124         fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_VELOCITYAD);
1125     else if (flag == Flags::Thief)
1126         fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_THIEFVELAD);
1127     else if ((flag == Flags::Burrow) && (getPosition()[2] < 0.0f))
1128         fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_BURROWSPEEDAD);
1129     else if ((flag == Flags::ForwardOnly) && (fracOfMaxSpeed < 0.0))
1130         fracOfMaxSpeed = 0.0f;
1131     else if ((flag == Flags::ReverseOnly) && (fracOfMaxSpeed > 0.0))
1132         fracOfMaxSpeed = 0.0f;
1133     else if (flag == Flags::Agility)
1134     {
1135         if ((TimeKeeper::getTick() - agilityTime) < BZDB.eval(StateDatabase::BZDB_AGILITYTIMEWINDOW))
1136             fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_AGILITYADVEL);
1137         else
1138         {
1139             float oldFrac = desiredSpeed / BZDBCache::tankSpeed;
1140             if (oldFrac > 1.0f)
1141                 oldFrac = 1.0f;
1142             else if (oldFrac < -0.5f)
1143                 oldFrac = -0.5f;
1144             float limit = BZDB.eval(StateDatabase::BZDB_AGILITYVELDELTA);
1145             if (fracOfMaxSpeed < 0.0f)
1146                 limit /= 2.0f;
1147             if (fabs(fracOfMaxSpeed - oldFrac) > limit)
1148             {
1149                 fracOfMaxSpeed *= BZDB.eval(StateDatabase::BZDB_AGILITYADVEL);
1150                 agilityTime = TimeKeeper::getTick();
1151             }
1152         }
1153     }
1154 
1155     // apply handicap advantage to tank speed
1156     fracOfMaxSpeed *= (1.0f + (handicap * (BZDB.eval(StateDatabase::BZDB_HANDICAPVELAD) - 1.0f)));
1157 
1158     // set desired speed
1159     desiredSpeed = BZDBCache::tankSpeed * fracOfMaxSpeed;
1160     Player::setUserSpeed(desiredSpeed);
1161 
1162     return;
1163 }
1164 
1165 
setDesiredAngVel(float fracOfMaxAngVel)1166 void            LocalPlayer::setDesiredAngVel(float fracOfMaxAngVel)
1167 {
1168     FlagType* flag = getFlag();
1169 
1170     // limit turn speed to maximum
1171     if (fracOfMaxAngVel > 1.0f) fracOfMaxAngVel = 1.0f;
1172     else if (fracOfMaxAngVel < -1.0f) fracOfMaxAngVel = -1.0f;
1173 
1174     // further limit turn speed for certain flags
1175     if (fracOfMaxAngVel < 0.0f && getFlag() == Flags::LeftTurnOnly)
1176         fracOfMaxAngVel = 0.0f;
1177     else if (fracOfMaxAngVel > 0.0f && getFlag() == Flags::RightTurnOnly)
1178         fracOfMaxAngVel = 0.0f;
1179 
1180     // boost turn speed for other flags
1181     if (flag == Flags::QuickTurn)
1182         fracOfMaxAngVel *= BZDB.eval(StateDatabase::BZDB_ANGULARAD);
1183     else if ((flag == Flags::Burrow) && (getPosition()[2] < 0.0f))
1184         fracOfMaxAngVel *= BZDB.eval(StateDatabase::BZDB_BURROWANGULARAD);
1185 
1186     // apply handicap advantage to tank speed
1187     fracOfMaxAngVel *= (1.0f + (handicap * (BZDB.eval(StateDatabase::BZDB_HANDICAPANGAD) - 1.0f)));
1188 
1189     // set desired turn speed
1190     desiredAngVel = fracOfMaxAngVel * BZDB.eval(StateDatabase::BZDB_TANKANGVEL);
1191     Player::setUserAngVel(desiredAngVel);
1192 
1193     return;
1194 }
1195 
1196 
setPause(bool pause)1197 void            LocalPlayer::setPause(bool pause)
1198 {
1199     if (isAlive())
1200     {
1201         if (pause && !isPaused())
1202         {
1203             setStatus(getStatus() | short(PlayerState::Paused));
1204             server->sendPaused(true);
1205         }
1206         else if (!pause && isPaused())
1207         {
1208             setStatus(getStatus() & ~short(PlayerState::Paused));
1209             server->sendPaused(false);
1210         }
1211     }
1212 }
1213 
activateAutoPilot(bool autopilot)1214 void            LocalPlayer::activateAutoPilot(bool autopilot)
1215 {
1216     // If desired and actual state is the same, ignore the request
1217     if (autopilot == isAutoPilot())
1218         return;
1219 
1220     setAutoPilot(autopilot);
1221     server->sendAutoPilot(autopilot);
1222     if (!autopilot)
1223         setTarget(NULL);
1224 }
1225 
fireShot()1226 bool            LocalPlayer::fireShot()
1227 {
1228     if (! (firingStatus == Ready || firingStatus == Zoned))
1229         return false;
1230 
1231     // find an empty slot
1232     const int numShots = World::getWorld()->getMaxShots();
1233     int i;
1234     for (i = 0; i < numShots; i++)
1235         if (!shots[i])
1236             break;
1237     if (i == numShots) return false;
1238 
1239     // make sure we're allowed to shoot
1240     if (!isAlive() || isPaused() ||
1241             ((location == InBuilding) && !isPhantomZoned()))
1242         return false;
1243 
1244     // prepare shot
1245     FiringInfo firingInfo(*this, i + getSalt());
1246     // FIXME team coloring of shot is never used; it was meant to be used
1247     // for rabbit mode to correctly calculate team kills when rabbit changes
1248     firingInfo.shot.team = getTeam();
1249     if (firingInfo.flagType == Flags::ShockWave)
1250     {
1251         // move shot origin under tank and make it stationary
1252         const float* pos = getPosition();
1253         firingInfo.shot.pos[0] = pos[0];
1254         firingInfo.shot.pos[1] = pos[1];
1255         firingInfo.shot.pos[2] = pos[2];
1256         firingInfo.shot.vel[0] = 0.0f;
1257         firingInfo.shot.vel[1] = 0.0f;
1258         firingInfo.shot.vel[2] = 0.0f;
1259     }
1260     else
1261     {
1262         // apply any handicap advantage to shot speed
1263         if (handicap > 0.0f)
1264         {
1265             const float speedAd = 1.0f + (handicap * (BZDB.eval(StateDatabase::BZDB_HANDICAPSHOTAD) - 1.0f));
1266             const float* dir = getForward();
1267             const float* tankVel = getVelocity();
1268             const float shotSpeed = speedAd * BZDB.eval(StateDatabase::BZDB_SHOTSPEED);
1269             firingInfo.shot.vel[0] = tankVel[0] + shotSpeed * dir[0];
1270             firingInfo.shot.vel[1] = tankVel[1] + shotSpeed * dir[1];
1271             firingInfo.shot.vel[2] = tankVel[2] + shotSpeed * dir[2];
1272         }
1273         // Set _shotsKeepVerticalVelocity on the server if you want shots
1274         // to have the same vertical velocity as the tank when fired.
1275         // keeping shots moving horizontally makes the game more playable.
1276         if (!BZDB.isTrue(StateDatabase::BZDB_SHOTSKEEPVERTICALV)) firingInfo.shot.vel[2] = 0.0f;
1277     }
1278 
1279     // make shot and put it in the table
1280     shots[i] = new LocalShotPath(firingInfo);
1281 
1282     // Insert timestamp, useful for dead reckoning jitter fixing
1283     // TODO should maybe use getTick() instead? must double check
1284     const float timeStamp = float(TimeKeeper::getCurrent() - TimeKeeper::getNullTime());
1285     firingInfo.timeSent = timeStamp;
1286 
1287     // always send a player-update message. To synchronize movement and
1288     // shot start. They should generally travel on the same frame, when
1289     // flushing the output queues.
1290     server->sendPlayerUpdate(this);
1291     server->sendBeginShot(firingInfo);
1292 
1293     if (BZDB.isTrue("enableLocalShotEffect") && SceneRenderer::instance().useQuality() >= 2)
1294         EFFECTS.addShotEffect(getColor(), firingInfo.shot.pos, getAngle(), getVelocity());
1295 
1296     if (gettingSound)
1297     {
1298         if (firingInfo.flagType == Flags::ShockWave)
1299         {
1300             playLocalSound(SFX_SHOCK);
1301             ForceFeedback::shockwaveFired();
1302         }
1303         else if (firingInfo.flagType == Flags::Laser)
1304         {
1305             playLocalSound(SFX_LASER);
1306             ForceFeedback::laserFired();
1307         }
1308         else if (firingInfo.flagType == Flags::GuidedMissile)
1309         {
1310             playLocalSound(SFX_MISSILE);
1311             ForceFeedback::shotFired();
1312         }
1313         else if (firingInfo.flagType == Flags::Thief)
1314         {
1315             playLocalSound(SFX_THIEF);
1316             ForceFeedback::shotFired();
1317         }
1318         else
1319         {
1320             playLocalSound(SFX_FIRE);
1321             ForceFeedback::shotFired();
1322         }
1323     }
1324 
1325     shotStatistics.recordFire(firingInfo.flagType,getForward(),firingInfo.shot.vel);
1326 
1327     if (getFlag() == Flags::TriggerHappy)
1328     {
1329         // make sure all the shots don't go off at once
1330         forceReload(BZDB.eval(StateDatabase::BZDB_RELOADTIME) / numShots);
1331     }
1332     return true;
1333 }
1334 
getReloadTime() const1335 float           LocalPlayer::getReloadTime() const
1336 {
1337     const int numShots = World::getWorld()->getMaxShots();
1338     if (numShots <= 0)
1339         return 0.0f;
1340 
1341     float time = float(jamTime - TimeKeeper::getTick());
1342     if (time > 0.0f)
1343         return time;
1344 
1345     // look for an empty slot
1346     int i;
1347     for (i = 0; i < numShots; i++)
1348     {
1349         if (!shots[i])
1350             return 0.0f;
1351     }
1352 
1353     // look for the shot fired least recently
1354     float minTime = float(shots[0]->getReloadTime() -
1355                           (shots[0]->getCurrentTime() - shots[0]->getStartTime()));
1356     for (i = 1; i < numShots; i++)
1357     {
1358         const float t = float(shots[i]->getReloadTime() -
1359                               (shots[i]->getCurrentTime() - shots[i]->getStartTime()));
1360         if (t < minTime)
1361             minTime = t;
1362     }
1363 
1364     if (minTime < 0.0f)
1365         minTime = 0.0f;
1366 
1367     return minTime;
1368 }
1369 
doEndShot(int ident,bool isHit,float * pos)1370 bool LocalPlayer::doEndShot(int ident, bool isHit, float* pos)
1371 {
1372     const int index = ident & 255;
1373     const int slt   = (ident >> 8) & 127;
1374 
1375     // special id used in some messages (and really shouldn't be sent here)
1376     if (ident == -1)
1377         return false;
1378 
1379     // ignore bogus shots (those with a bad index or for shots that don't exist)
1380     if (index < 0 || index >= World::getWorld()->getMaxShots() || !shots[index])
1381         return false;
1382 
1383     // ignore shots that already ending
1384     if (shots[index]->isExpired() || shots[index]->isExpiring())
1385         return false;
1386 
1387     // ignore shots that have the wrong salt.  since we reuse shot indices
1388     // it's possible for an old MsgShotEnd to arrive after we've started a
1389     // new shot.  that's where the salt comes in.  it changes for each shot
1390     // so we can identify an old shot from a new one.
1391     if (slt != ((shots[index]->getShotId() >> 8) & 127))
1392         return false;
1393 
1394     // keep shot statistics
1395     shotStatistics.recordHit(shots[index]->getFlag());
1396 
1397     // don't stop if it's because were hitting something and we don't stop
1398     // when we hit something.
1399     if (isHit && !shots[index]->isStoppedByHit())
1400         return false;
1401 
1402     // end it
1403     const float* shotPos = shots[index]->getPosition();
1404     pos[0] = shotPos[0];
1405     pos[1] = shotPos[1];
1406     pos[2] = shotPos[2];
1407     shots[index]->setExpired();
1408     return true;
1409 }
1410 
setJump()1411 void            LocalPlayer::setJump()
1412 {
1413     wantJump = jumpPressed;
1414 }
1415 
setJumpPressed(bool value)1416 void            LocalPlayer::setJumpPressed(bool value)
1417 {
1418     jumpPressed = value;
1419 }
1420 
doJump()1421 void            LocalPlayer::doJump()
1422 {
1423     FlagType* flag = getFlag();
1424 
1425     // can't jump while burrowed
1426     if (getPosition()[2] < 0.0f)
1427         return;
1428 
1429     if (flag == Flags::Wings)
1430     {
1431         if (wingsFlapCount <= 0)
1432             return;
1433         wingsFlapCount--;
1434     }
1435     else if ((location != OnGround) && (location != OnBuilding))
1436     {
1437         // can't jump unless on the ground or a building
1438         if (flag != Flags::Wings)
1439             return;
1440         if (wingsFlapCount <= 0)
1441             return;
1442         wingsFlapCount--;
1443     }
1444     else if ((flag != Flags::Bouncy) &&
1445              ((flag != Flags::Jumping && !World::getWorld()->allowJumping()) ||
1446               (flag == Flags::NoJumping)))
1447         return;
1448 
1449     // jump velocity
1450     const float* oldVelocity = getVelocity();
1451     float newVelocity[3];
1452     newVelocity[0] = oldVelocity[0];
1453     newVelocity[1] = oldVelocity[1];
1454     if (flag == Flags::Wings)
1455     {
1456         newVelocity[2] = BZDB.eval(StateDatabase::BZDB_WINGSJUMPVELOCITY);
1457         // if you're falling, wings will just slow you down
1458         if (oldVelocity[2] < 0)
1459         {
1460             newVelocity[2] += oldVelocity[2];
1461             // if you're already going up faster, just keep doing that
1462         }
1463         else if (oldVelocity[2] > newVelocity[2])
1464             newVelocity[2] = oldVelocity[2];
1465     }
1466     else if (flag == Flags::Bouncy)
1467     {
1468         const float factor = 0.25f + ((float)bzfrand() * 0.75f);
1469         newVelocity[2] = factor * BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY);
1470     }
1471     else
1472         newVelocity[2] = BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY);
1473     setVelocity(newVelocity);
1474     location = InAir;
1475 
1476     // setup the graphics
1477     fireJumpJets();
1478 
1479     // setup the sound
1480     if (gettingSound)
1481     {
1482         if (flag == Flags::Wings)
1483         {
1484             playLocalSound(SFX_FLAP);
1485             addRemoteSound(PlayerState::WingsSound);
1486         }
1487         else
1488         {
1489             playLocalSound(SFX_JUMP);
1490             addRemoteSound(PlayerState::JumpSound);
1491         }
1492     }
1493 
1494     wantJump = false;
1495 }
1496 
setTarget(const Player * _target)1497 void            LocalPlayer::setTarget(const Player* _target)
1498 {
1499     target = _target;
1500 }
1501 
setNemesis(const Player * _nemesis)1502 void            LocalPlayer::setNemesis(const Player* _nemesis)
1503 {
1504     if ((_nemesis == NULL) || _nemesis->getPlayerType() == TankPlayer)
1505         nemesis = _nemesis;
1506 }
1507 
setRecipient(const Player * _recipient)1508 void            LocalPlayer::setRecipient(const Player* _recipient)
1509 {
1510     if ((_recipient == NULL) || (_recipient->getId() <= LastRealPlayer))
1511         recipient = _recipient;
1512 }
1513 
explodeTank()1514 void            LocalPlayer::explodeTank()
1515 {
1516     if (location == Dead || location == Exploding) return;
1517     float gravity      = BZDBCache::gravity;
1518     float explodeTim   = BZDB.eval(StateDatabase::BZDB_EXPLODETIME);
1519     setExplodePos(getPosition());
1520     // Limiting max height increment to this value (the old default value)
1521     const float zMax  = 49.0f;
1522     setExplode(TimeKeeper::getTick());
1523     const float* oldVelocity = getVelocity();
1524     float newVelocity[3];
1525     newVelocity[0] = oldVelocity[0];
1526     newVelocity[1] = oldVelocity[1];
1527     if (gravity < 0)
1528     {
1529         // comparing 2 speed:
1530         //   to have a symmetric path (ending at same height as starting)
1531         //   to reach the apex of parabola, under the max height established
1532         // take the less
1533         newVelocity[2] = - 0.5f * gravity * explodeTim;
1534         float maxSpeed = sqrtf(- 2.0f * zMax * gravity);
1535         if (newVelocity[2] > maxSpeed)
1536             newVelocity[2] = maxSpeed;
1537     }
1538     else
1539         newVelocity[2] = oldVelocity[2];
1540     TankDeathOverride* death = getDeathEffect();
1541     if (death)
1542     {
1543         fvec3 v(newVelocity[0],newVelocity[1],newVelocity[2]);
1544         if (death->GetDeathVector(v))
1545         {
1546             newVelocity[0] = v.x;
1547             newVelocity[1] = v.y;
1548             newVelocity[2] = v.z;
1549         }
1550     }
1551     setVelocity(newVelocity);
1552     location = Exploding;
1553     target = NULL;        // lose lock when dead
1554 }
1555 
doMomentum(float dt,float & speed,float & angVel)1556 void            LocalPlayer::doMomentum(float dt,
1557                                         float& speed, float& angVel)
1558 {
1559     // get maximum linear and angular accelerations
1560     float linearAcc = (getFlag() == Flags::Momentum) ? BZDB.eval(StateDatabase::BZDB_MOMENTUMLINACC) :
1561                       World::getWorld()->getLinearAcceleration();
1562     float angularAcc = (getFlag() == Flags::Momentum) ? BZDB.eval(StateDatabase::BZDB_MOMENTUMANGACC) :
1563                        World::getWorld()->getAngularAcceleration();
1564 
1565     // limit linear acceleration
1566     if (linearAcc > 0.0f)
1567     {
1568         const float acc = (speed - lastSpeed) / dt;
1569         if (acc > 20.0f * linearAcc) speed = lastSpeed + dt * 20.0f*linearAcc;
1570         else if (acc < -20.0f * linearAcc) speed = lastSpeed - dt * 20.0f*linearAcc;
1571     }
1572 
1573     // limit angular acceleration
1574     if (angularAcc > 0.0f)
1575     {
1576         const float oldAngVel = getAngularVelocity();
1577         const float angAcc = (angVel - oldAngVel) / dt;
1578         if (angAcc > angularAcc) angVel = oldAngVel + dt * angularAcc;
1579         else if (angAcc < -angularAcc) angVel = oldAngVel - dt * angularAcc;
1580     }
1581 }
1582 
doFriction(float dt,const float * oldVelocity,float * newVelocity)1583 void            LocalPlayer::doFriction(float dt,
1584                                         const float *oldVelocity, float *newVelocity)
1585 {
1586     const float friction = (getFlag() == Flags::Momentum) ? BZDB.eval(StateDatabase::BZDB_MOMENTUMFRICTION) :
1587                            BZDB.eval(StateDatabase::BZDB_FRICTION);
1588 
1589     if (friction > 0.0f)
1590     {
1591         // limit vector acceleration
1592 
1593         float delta[2] = {newVelocity[0] - oldVelocity[0], newVelocity[1] - oldVelocity[1]};
1594         float acc2 = (delta[0] * delta[0] + delta[1] * delta[1]) / (dt*dt);
1595         float accLimit = 20.0f * friction;
1596 
1597         if (acc2 > accLimit*accLimit)
1598         {
1599             float ratio = accLimit / sqrtf(acc2);
1600             newVelocity[0] = oldVelocity[0] + delta[0]*ratio;
1601             newVelocity[1] = oldVelocity[1] + delta[1]*ratio;
1602         }
1603     }
1604 }
1605 
doForces(float UNUSED (dt),float * UNUSED (velocity),float & UNUSED (angVel))1606 void            LocalPlayer::doForces(float UNUSED(dt),
1607                                       float* UNUSED(velocity),
1608                                       float& UNUSED(angVel))
1609 {
1610     // apply external forces
1611     // do nothing -- no external forces right now
1612 }
1613 
1614 // NOTE -- minTime should be initialized to Infinity by the caller
checkHit(const Player * source,const ShotPath * & hit,float & minTime) const1615 bool            LocalPlayer::checkHit(const Player* source,
1616                                       const ShotPath*& hit, float& minTime) const
1617 {
1618     bool goodHit = false;
1619 
1620     // if firing tank is paused then it doesn't count
1621     if (source->isPaused()) return goodHit;
1622 
1623     const int maxShots = source->getMaxShots();
1624     for (int i = 0; i < maxShots; i++)
1625     {
1626         // get shot
1627         const ShotPath* shot = source->getShot(i);
1628         if (!shot || shot->isExpired()) continue;
1629 
1630         // my own shock wave cannot kill me
1631         if (source == this && ((shot->getFlag() == Flags::ShockWave) || (shot->getFlag() == Flags::Thief))) continue;
1632 
1633         // if no team kills, shots of my team cannot kill me. Thief can still take
1634         // a teammate's flag.
1635         if (getTeam() != RogueTeam && !World::getWorld()->allowTeamKills() &&
1636                 shot->getFlag() != Flags::Thief && shot->getTeam() == getTeam() &&
1637                 source != this) continue;
1638 
1639         // short circuit test if shot can't possibly hit.
1640         // only superbullet or shockwave can kill zoned dude
1641         const FlagType* shotFlag = shot->getFlag();
1642         if (isPhantomZoned() &&
1643                 (shotFlag != Flags::ShockWave) &&
1644                 (shotFlag != Flags::SuperBullet) &&
1645                 (shotFlag != Flags::PhantomZone))
1646             continue;
1647 
1648         // laser can't hit a cloaked tank
1649         if ((getFlag() == Flags::Cloaking) && (shotFlag == Flags::Laser))
1650             continue;
1651 
1652         // zoned shots only kill zoned tanks
1653         if ((shotFlag == Flags::PhantomZone) && !isPhantomZoned())
1654             continue;
1655 
1656         // test myself against shot
1657         float position[3];
1658         const float t = shot->checkHit(this, position);
1659         if (t >= minTime) continue;
1660 
1661         // test if shot hit a part of my tank that's through a teleporter.
1662         // hit is no good if hit point is behind crossing plane.
1663         if (isCrossingWall() && position[0] * crossingPlane[0] +
1664                 position[1] * crossingPlane[1] +
1665                 position[2] * crossingPlane[2] + crossingPlane[3] < 0.0)
1666             continue;
1667 
1668         // okay, shot hit
1669         goodHit = true;
1670         hit = shot;
1671         minTime = t;
1672     }
1673     return goodHit;
1674 }
1675 
setFlag(FlagType * flag)1676 void            LocalPlayer::setFlag(FlagType* flag)
1677 {
1678     Player::setFlag(flag);
1679 
1680     float worldSize = BZDBCache::worldSize;
1681     // if it's bad then reset countdowns and set antidote flag
1682     if (getFlag() != Flags::Null && getFlag()->endurance == FlagSticky)
1683     {
1684         if (World::getWorld()->allowShakeTimeout())
1685             flagShakingTime = World::getWorld()->getFlagShakeTimeout();
1686         if (World::getWorld()->allowShakeWins())
1687             flagShakingWins = World::getWorld()->getFlagShakeWins();
1688         if (World::getWorld()->allowAntidote())
1689         {
1690             float tankRadius = BZDBCache::tankRadius;
1691             float baseSize = BZDB.eval(StateDatabase::BZDB_BASESIZE);
1692             int tryCount = 0;
1693             do
1694             {
1695                 tryCount++;
1696                 if (tryCount > 100) // if it takes this long, just screw it.
1697                     break;
1698 
1699                 if (World::getWorld()->allowTeamFlags())
1700                 {
1701                     flagAntidotePos[0] = 0.5f * worldSize * ((float)bzfrand() - 0.5f);
1702                     flagAntidotePos[1] = 0.5f * worldSize * ((float)bzfrand() - 0.5f);
1703                     flagAntidotePos[2] = 0.0f;
1704                 }
1705                 else
1706                 {
1707                     flagAntidotePos[0] = (worldSize - baseSize) * ((float)bzfrand() - 0.5f);
1708                     flagAntidotePos[1] = (worldSize - baseSize) * ((float)bzfrand() - 0.5f);
1709                     flagAntidotePos[2] = 0.0f;
1710                 }
1711             }
1712             while (World::getWorld()->inBuilding(flagAntidotePos, tankRadius,
1713                                                  BZDBCache::tankHeight));
1714             antidoteFlag = new FlagSceneNode(flagAntidotePos);
1715             antidoteFlag->setColor(1.0f, 1.0f, 0.0f);
1716             World::setFlagTexture(antidoteFlag);
1717         }
1718     }
1719     else
1720     {
1721         delete antidoteFlag;
1722         antidoteFlag = NULL;
1723         flagShakingTime = 0.0f;
1724         flagShakingWins = 0;
1725     }
1726 }
1727 
changeScore(short deltaWins,short deltaLosses,short deltaTks)1728 void            LocalPlayer::changeScore(short deltaWins,
1729         short deltaLosses,
1730         short deltaTks)
1731 {
1732     Player::changeScore(deltaWins, deltaLosses, deltaTks);
1733     if (deltaWins > 0 && World::getWorld()->allowShakeWins() &&
1734             flagShakingWins > 0)
1735     {
1736         flagShakingWins -= deltaWins;
1737         if (flagShakingWins <= 0)
1738         {
1739             flagShakingWins = 0;
1740             server->sendDropFlag(getPosition());
1741         }
1742     }
1743 }
1744 
addAntidote(SceneDatabase * scene)1745 void            LocalPlayer::addAntidote(SceneDatabase* scene)
1746 {
1747     if (antidoteFlag)
1748         scene->addDynamicNode(antidoteFlag);
1749 }
1750 
getInputMethodName(InputMethod whatInput)1751 std::string     LocalPlayer::getInputMethodName(InputMethod whatInput)
1752 {
1753     switch (whatInput)
1754     {
1755     case Keyboard:
1756         return std::string("Keyboard");
1757         break;
1758     case Mouse:
1759         return std::string("Mouse");
1760         break;
1761     case Joystick:
1762         return std::string("Joystick");
1763         break;
1764     default:
1765         return std::string("Unnamed Device");
1766     }
1767 }
1768 
setKey(int button,bool pressed)1769 void LocalPlayer::setKey(int button, bool pressed)
1770 {
1771     switch (button)
1772     {
1773     case BzfKeyEvent::Left:
1774         left = pressed;
1775         break;
1776     case BzfKeyEvent::Right:
1777         right = pressed;
1778         break;
1779     case BzfKeyEvent::Up:
1780         up = pressed;
1781         break;
1782     case BzfKeyEvent::Down:
1783         down = pressed;
1784         break;
1785     }
1786 }
1787 
1788 // Local Variables: ***
1789 // mode: C++ ***
1790 // tab-width: 4 ***
1791 // c-basic-offset: 4 ***
1792 // indent-tabs-mode: nil ***
1793 // End: ***
1794 // ex: shiftwidth=4 tabstop=4
1795