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