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 "playing.h"
15 
16 // system includes
17 #ifdef _WIN32
18 #include <shlobj.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <direct.h>
22 #else
23 #include <pwd.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <dirent.h>
27 #include <utime.h>
28 #endif
29 #include <cmath>
30 
31 // common headers
32 #include "AccessList.h"
33 #include "AnsiCodes.h"
34 #include "AresHandler.h"
35 #include "BackgroundRenderer.h"
36 #include "BaseBuilding.h"
37 #include "BillboardSceneNode.h"
38 #include "BZDBCache.h"
39 #include "BzfMedia.h"
40 #include "bzsignal.h"
41 #include "CommandsStandard.h"
42 #include "DirectoryNames.h"
43 #include "ErrorHandler.h"
44 #include "FileManager.h"
45 #include "FlagSceneNode.h"
46 #include "GameTime.h"
47 #include "KeyManager.h"
48 #include "md5.h"
49 #include "ObstacleList.h"
50 #include "ObstacleMgr.h"
51 #include "OpenGLFramebuffer.h"
52 #include "PhysicsDriver.h"
53 #include "PlatformFactory.h"
54 #include "QuadWallSceneNode.h"
55 #include "ServerList.h"
56 #include "SphereSceneNode.h"
57 #include "TankGeometryMgr.h"
58 #include "Team.h"
59 #include "TextureManager.h"
60 #include "TextUtils.h"
61 #include "version.h"
62 #include "WordFilter.h"
63 #include "ZSceneDatabase.h"
64 
65 // local implementation headers
66 #include "AutoPilot.h"
67 #include "bzflag.h"
68 #include "commands.h"
69 #include "daylight.h"
70 #include "Downloads.h"
71 #include "effectsRenderer.h"
72 #include "FlashClock.h"
73 #include "ForceFeedback.h"
74 #include "LocalPlayer.h"
75 #include "HUDDialogStack.h"
76 #include "HUDRenderer.h"
77 #include "MainMenu.h"
78 #include "motd.h"
79 #include "RadarRenderer.h"
80 #include "Roaming.h"
81 #include "RobotPlayer.h"
82 #include "Roster.h"
83 #include "SceneBuilder.h"
84 #include "ScoreboardRenderer.h"
85 #include "sound.h"
86 #include "ShotStats.h"
87 #include "TrackMarks.h"
88 #include "World.h"
89 #include "WorldBuilder.h"
90 #include "HUDui.h"
91 
92 #include "CollisionManager.h"
93 
94 #include <sstream>
95 
96 //#include "messages.h"
97 
98 static const float  FlagHelpDuration = 60.0f;
99 StartupInfo     startupInfo;
100 static MainMenu     *mainMenu;
101 ServerLink      *serverLink = NULL;
102 static World        *world = NULL;
103 static LocalPlayer  *myTank = NULL;
104 static BzfDisplay   *display = NULL;
105 MainWindow      *mainWindow = NULL;
106 static SceneRenderer    *sceneRenderer = NULL;
107 ControlPanel        *controlPanel = NULL;
108 static RadarRenderer    *radar = NULL;
109 HUDRenderer     *hud = NULL;
110 static ScoreboardRenderer *scoreboard = NULL;
111 static ShotStats    *shotStats = NULL;
112 static SceneDatabaseBuilder* sceneBuilder = NULL;
113 static Team     *teams = NULL;
114 int         numFlags = 0;
115 static bool     joinRequested = false;
116 static bool     waitingDNS    = false;
117 static bool     serverError = false;
118 static bool     serverDied = false;
119 bool            fireButton = false;
120 bool            roamButton = false;
121 static bool     firstLife = false;
122 static bool     showFPS = false;
123 static bool     showDrawTime = false;
124 bool            pausedByUnmap = false;
125 static bool     unmapped = false;
126 static int      preUnmapFormat = -1;
127 static double       epochOffset;
128 static double       lastEpochOffset;
129 float           clockAdjust = 0.0f;
130 float           pauseCountdown = 0.0f;
131 float           destructCountdown = 0.0f;
132 static float        testVideoFormatTimer = 0.0f;
133 static int      testVideoPrevFormat = -1;
134 static std::vector<PlayingCallbackItem> playingCallbacks;
135 bool            gameOver = false;
136 static std::vector<BillboardSceneNode*> explosions;
137 static std::vector<BillboardSceneNode*> prototypeExplosions;
138 int         savedVolume = -1;
139 static bool     grabMouseAlways = false;
140 static FlashClock   pulse;
141 static bool     wasRabbit = false;
142 static bool     justJoined = false;
143 
144 int         sentForbidIdentify = 0;
145 
146 float           roamDZoom = 0.0f;
147 
148 static MessageOfTheDay  *motd = NULL;
149 DefaultCompleter    completer;
150 
151 char            messageMessage[PlayerIdPLen + MessageLen];
152 
153 double          lastObserverUpdateTime = -1;
154 
155 static void     setHuntTarget();
156 static void     setTankFlags();
157 static const void   *handleMsgSetVars(const void *msg);
158 static void     handlePlayerMessage(uint16_t, uint16_t, const void*);
159 static void     handleFlagTransferred(Player* fromTank, Player* toTank, int flagIndex);
160 static void     enteringServer(const void *buf);
161 static void     joinInternetGame2();
162 static void     cleanWorldCache();
163 static void     markOld(std::string &fileName);
164 #ifdef ROBOT
165 static void     setRobotTarget(RobotPlayer* robot);
166 #endif
167 
168 static ResourceGetter   *resourceDownloader = NULL;
169 
170 // Far and Near Frustum clipping planes
171 static const float FarPlaneScale = 1.5f; // gets multiplied by BZDB_WORLDSIZE
172 static const float FarPlaneDefault = FarPlaneScale * 800.0f;
173 static const float FarDeepPlaneScale = 10.0f;
174 static const float FarDeepPlaneDefault = FarPlaneDefault * FarDeepPlaneScale;
175 static const float NearPlaneNormal = 1.0f;
176 static const float NearPlaneClose = 0.25f; // for drawing in the cockpit
177 static bool FarPlaneCull = false;
178 static float FarPlane = FarPlaneDefault;
179 static float FarDeepPlane = FarDeepPlaneDefault;
180 static float NearPlane = NearPlaneNormal;
181 
182 static bool leftMouseButton   = false;
183 static bool rightMouseButton  = false;
184 static bool middleMouseButton = false;
185 
186 static const char*  blowedUpMessage[] =
187 {
188     NULL,
189     "Got shot by ",
190     "Got flattened by ",
191     "Team flag was captured by ",
192     "Teammate hit with Genocide by ",
193     "Tank Self Destructed",
194     "Tank Rusted"
195 };
196 static bool     gotBlowedUp(BaseLocalPlayer* tank,
197                             BlowedUpReason reason,
198                             PlayerId killer,
199                             const ShotPath *hit = NULL,
200                             int physicsDriver = -1);
201 
202 #ifdef ROBOT
203 static void     handleMyTankKilled(int reason);
204 static ServerLink   *robotServer[MAX_ROBOTS];
205 #endif
206 
207 static double       userTimeEpochOffset;
208 
209 static bool     entered = false;
210 static bool     joiningGame = false;
211 static WorldBuilder *worldBuilder = NULL;
212 static std::string  worldUrl;
213 static std::string  worldCachePath;
214 static std::string  md5Digest;
215 static uint32_t     worldPtr = 0;
216 static char     *worldDatabase = NULL;
217 static bool     isCacheTemp;
218 static std::ostream *cacheOut = NULL;
219 static bool     downloadingInitialTexture = false;
220 
221 static AresHandler* ares = NULL;
initGlobalAres()222 void initGlobalAres()
223 {
224     ares = new AresHandler(0);
225 }
killGlobalAres()226 void killGlobalAres()
227 {
228     delete ares;
229     ares = NULL;
230 }
231 static Address serverNetworkAddress = Address();
232 
233 OpenGLFramebuffer glFramebuffer;
234 
235 static AccessList   ServerAccessList("ServerAccess.txt", NULL);
236 
237 // access silencePlayers from bzflag.cxx
getSilenceList()238 std::vector<std::string>& getSilenceList()
239 {
240     return silencePlayers;
241 }
242 
243 // try to select the next recipient in the specified direction
244 // eventually avoiding robots
selectNextRecipient(bool forward,bool robotIn)245 void selectNextRecipient (bool forward, bool robotIn)
246 {
247     LocalPlayer *my = LocalPlayer::getMyTank();
248     const Player *recipient = my->getRecipient();
249     int rindex;
250     if (!recipient)
251     {
252         rindex = - 1;
253         forward = true;
254     }
255     else
256     {
257         const PlayerId id = recipient->getId();
258         rindex = lookupPlayerIndex(id);
259     }
260     int i = rindex;
261     while (true)
262     {
263         if (forward)
264         {
265             i++;
266             if (i == curMaxPlayers)
267             {
268                 // if no old rec id we have just ended our search
269                 if (recipient == NULL)
270                     break;
271                 else
272                     // wrap around
273                     i = 0;
274             }
275         }
276         else
277         {
278             if (i == 0)
279                 // wrap around
280                 i = curMaxPlayers;
281             i--;
282         }
283         if (i == rindex)
284             break;
285         if (remotePlayers[i] && (robotIn || remotePlayers[i]->getPlayerType() == TankPlayer))
286         {
287             my->setRecipient(remotePlayers[i]);
288             break;
289         }
290     }
291 }
292 
293 //
294 // should we grab the mouse?
295 //
296 
setGrabMouse(bool grab)297 static void     setGrabMouse(bool grab)
298 {
299     grabMouseAlways = grab;
300 }
301 
shouldGrabMouse()302 bool        shouldGrabMouse()
303 {
304 #if defined(_WIN32) && defined(DEBUG)
305     return false;
306 #endif
307     return grabMouseAlways && !unmapped &&
308            (myTank == NULL || !myTank->isPaused() || myTank->isAutoPilot());
309 }
310 
311 //
312 // some simple global functions
313 //
314 
warnAboutMainFlags()315 void warnAboutMainFlags()
316 {
317     // warning message for hidden flags
318     if (!BZDBCache::displayMainFlags)
319     {
320         std::string showFlagsMsg = ColorStrings[YellowColor];
321         showFlagsMsg += "Flags on field hidden, to show them ";
322         std::vector<std::string> keys = KEYMGR.getKeysFromCommand("toggleFlags main", true);
323 
324         if (!keys.empty())
325         {
326             showFlagsMsg += "hit \"" + ColorStrings[WhiteColor];
327             showFlagsMsg += tolower(keys[0][0]);
328             showFlagsMsg += ColorStrings[YellowColor] + "\"";
329         }
330         else
331             showFlagsMsg += " bind a key to Toggle Flags on Field";
332         addMessage(NULL, showFlagsMsg);
333     }
334 }
335 
warnAboutRadarFlags()336 void warnAboutRadarFlags()
337 {
338     if (!BZDB.isTrue("displayRadarFlags"))
339     {
340         std::string showFlagsMsg = ColorStrings[YellowColor];
341         showFlagsMsg += "Flags on radar hidden, to show them ";
342         std::vector<std::string> keys = KEYMGR.getKeysFromCommand("toggleFlags radar", true);
343 
344         if (keys.empty())
345             showFlagsMsg += " bind a key to Toggle Flags on Radar";
346         else
347         {
348             showFlagsMsg += "hit \"" + ColorStrings[WhiteColor];
349             showFlagsMsg += tolower(keys[0][0]);
350             showFlagsMsg += ColorStrings[YellowColor] + "\"";
351         }
352         addMessage(NULL, showFlagsMsg);
353     }
354 }
355 
warnAboutRadar()356 void warnAboutRadar()
357 {
358     if (!BZDB.isTrue("displayRadar"))
359     {
360         std::string message = ColorStrings[YellowColor];
361         message += "To toggle the radar ";
362         std::vector<std::string> keys = KEYMGR.getKeysFromCommand("toggleRadar", true);
363 
364         if (keys.empty())
365             message += " bind a key to Toggle Radar";
366         else
367         {
368             message += "hit \"" + ColorStrings[WhiteColor];
369             message += keys[0];
370             message += ColorStrings[YellowColor] + "\"";
371         }
372 
373         addMessage(NULL, message);
374     }
375 }
376 
warnAboutConsole()377 void warnAboutConsole()
378 {
379     if (!BZDB.isTrue("displayConsole"))
380     {
381         std::string message = ColorStrings[YellowColor];
382         message += "To toggle the console ";
383         std::vector<std::string> keys = KEYMGR.getKeysFromCommand("toggleConsole", true);
384 
385         if (keys.empty())
386             message += " bind a key to Toggle Console";
387         else
388         {
389             message += "hit \"" + ColorStrings[WhiteColor];
390             message += keys[0];
391             message += ColorStrings[YellowColor] + "\"";
392         }
393 
394         // can't use a console message for this one
395         hud->setAlert(3, message.c_str(), 2.0f, true);
396     }
397 }
398 
399 
isViewTank(Player * tank)400 inline bool isViewTank(Player* tank)
401 {
402     return (((tank != NULL) &&
403              (tank == LocalPlayer::getMyTank())) ||
404             (ROAM.isRoaming()
405              && (ROAM.getMode() == Roaming::roamViewFP)
406              && (ROAM.getTargetTank() == tank)));
407 }
408 
409 
getDisplay()410 BzfDisplay*     getDisplay()
411 {
412     return display;
413 }
414 
getMainWindow()415 MainWindow*     getMainWindow()
416 {
417     return mainWindow;
418 }
419 
getShotStats()420 ShotStats*      getShotStats()
421 {
422     return shotStats;
423 }
424 
getSceneRenderer()425 SceneRenderer*      getSceneRenderer()
426 {
427     return sceneRenderer;
428 }
429 
setSceneDatabase()430 void            setSceneDatabase()
431 {
432     SceneDatabase *scene; // FIXME - test the zbuffer here
433 
434     // delete the old database
435     sceneRenderer->setSceneDatabase(NULL);
436 
437     // make the scene, and record the processing time
438     TimeKeeper startTime = TimeKeeper::getCurrent();
439     scene = sceneBuilder->make(world);
440     float elapsed = float(TimeKeeper::getCurrent() - startTime);
441 
442     // print debugging info
443     if (BZDBCache::zbuffer)
444         logDebugMessage(2,"ZSceneDatabase processed in %.3f seconds.\n", elapsed);
445     else
446         logDebugMessage(2,"BSPSceneDatabase processed in %.3f seconds.\n", elapsed);
447 
448     // set the scene
449     sceneRenderer->setSceneDatabase(scene);
450 }
451 
getStartupInfo()452 StartupInfo*        getStartupInfo()
453 {
454     return &startupInfo;
455 }
456 
setVideoFormat(int index,bool test)457 bool            setVideoFormat(int index, bool test)
458 {
459 #if defined(_WIN32)
460     // give windows extra time to test format (context reloading takes a while)
461     static const float testDuration = 10.0f;
462 #else
463     static const float testDuration = 5.0f;
464 #endif
465 
466     // ignore bad formats or when the format test timer is running
467     if (testVideoFormatTimer != 0.0f || !display->isValidResolution(index))
468         return false;
469 
470     // ignore if no change
471     if (display->getResolution() == index) return true;
472 
473     // change it
474     testVideoPrevFormat = display->getResolution();
475     if (!display->setResolution(index)) return false;
476 
477     // handle resize
478     mainWindow->setFullscreen();
479     mainWindow->getWindow()->callResizeCallbacks();
480     mainWindow->warpMouse();
481     if (test) testVideoFormatTimer = testDuration;
482     else if (shouldGrabMouse()) mainWindow->grabMouse();
483     return true;
484 }
485 
addPlayingCallback(PlayingCallback cb,void * data)486 void            addPlayingCallback(PlayingCallback cb, void* data)
487 {
488     PlayingCallbackItem item;
489     item.cb = cb;
490     item.data = data;
491     playingCallbacks.push_back(item);
492 }
493 
removePlayingCallback(PlayingCallback _cb,void * data)494 void            removePlayingCallback(PlayingCallback _cb, void* data)
495 {
496     std::vector<PlayingCallbackItem>::iterator it = playingCallbacks.begin();
497     while (it != playingCallbacks.end())
498     {
499         if (it->cb == _cb && it->data == data)
500         {
501             playingCallbacks.erase(it);
502             break;
503         }
504         ++it;
505     }
506 }
507 
callPlayingCallbacks()508 static void     callPlayingCallbacks()
509 {
510     const int count = playingCallbacks.size();
511     for (int i = 0; i < count; i++)
512     {
513         const PlayingCallbackItem& cb = playingCallbacks[i];
514         (*cb.cb)(cb.data);
515     }
516 }
517 
joinGame()518 void            joinGame()
519 {
520     if (joiningGame)
521     {
522         if (worldBuilder)
523         {
524             delete worldBuilder;
525             worldBuilder = NULL;
526         }
527         if (worldDatabase)
528         {
529             delete[] worldDatabase;
530             worldDatabase = NULL;
531         }
532         HUDDialogStack::get()->setFailedMessage("Download stopped by user action");
533         joiningGame = false;
534     }
535     joinRequested = true;
536 }
537 
538 //
539 // handle signals that should kill me quickly
540 //
541 
dying(int sig)542 static void     dying(int sig)
543 {
544     bzSignal(sig, SIG_DFL);
545     display->setDefaultResolution();
546     raise(sig);
547 }
548 
549 //
550 // handle signals that should kill me nicely
551 //
552 
suicide(int sig)553 static void     suicide(int sig)
554 {
555     bzSignal(sig, SIG_PF(suicide));
556     CommandsStandard::quit();
557 }
558 
559 //
560 // handle signals that should disconnect me from the server
561 //
562 
hangup(int sig)563 static void     hangup(int sig)
564 {
565     bzSignal(sig, SIG_PF(hangup));
566     serverDied = true;
567     serverError = true;
568 }
569 
lookupServer(const Player * _player)570 static ServerLink*  lookupServer(const Player *_player)
571 {
572     PlayerId id = _player->getId();
573     if (myTank->getId() == id) return serverLink;
574 #ifdef ROBOT
575     for (int i = 0; i < numRobots; i++)
576         if (robots[i] && robots[i]->getId() == id)
577             return robotServer[i];
578 #endif
579     return NULL;
580 }
581 
582 //
583 // user input handling
584 //
585 
586 #if defined(DEBUG)
587 #define FREEZING
588 #endif
589 #if defined(FREEZING)
590 static bool     motionFreeze = false;
591 #endif
592 
593 static enum { None, Left, Right, Up, Down } keyboardMovement;
594 static int shiftKeyStatus;
595 
596 
doKeyCommon(const BzfKeyEvent & key,bool pressed)597 static bool doKeyCommon(const BzfKeyEvent& key, bool pressed)
598 {
599     keyboardMovement = None;
600     shiftKeyStatus   = key.shift;
601     const std::string cmd = KEYMGR.get(key, pressed);
602     if (key.ascii == 27)
603     {
604         if (pressed)
605         {
606             mainMenu->createControls();
607             HUDDialogStack::get()->push(mainMenu);
608         }
609         return true;
610     }
611     else if (scoreboard->getHuntState() == ScoreboardRenderer::HUNT_SELECTING)
612     {
613         if (key.button == BzfKeyEvent::Down || KEYMGR.get(key, true) == "identify")
614         {
615             if (pressed)
616                 scoreboard->setHuntNextEvent();
617             return true;
618         }
619         else if (key.button == BzfKeyEvent::Up || KEYMGR.get(key, true) == "drop")
620         {
621             if (pressed)
622                 scoreboard->setHuntPrevEvent();
623             return true;
624         }
625         else if (KEYMGR.get(key, true) == "fire")
626         {
627             if (pressed)
628                 scoreboard->setHuntSelectEvent();
629             return true;
630         }
631     }
632 
633     std::string cmdDrive = cmd;
634     if (cmdDrive.empty())
635     {
636         // Check for driving keys
637         BzfKeyEvent cleanKey = key;
638         cleanKey.shift = 0;
639         cmdDrive = KEYMGR.get(cleanKey, pressed);
640     }
641     if (cmdDrive == "turn left")
642         keyboardMovement = Left;
643     else if (cmdDrive == "turn right")
644         keyboardMovement = Right;
645     else if (cmdDrive == "drive forward")
646         keyboardMovement = Up;
647     else if (cmdDrive == "drive reverse")
648         keyboardMovement = Down;
649 
650     if (myTank)
651     {
652         switch (keyboardMovement)
653         {
654         case Left:
655             myTank->setKey(BzfKeyEvent::Left, pressed);
656             break;
657         case Right:
658             myTank->setKey(BzfKeyEvent::Right, pressed);
659             break;
660         case Up:
661             myTank->setKey(BzfKeyEvent::Up, pressed);
662             break;
663         case Down:
664             myTank->setKey(BzfKeyEvent::Down, pressed);
665             break;
666         case None:
667             break;
668         }
669     }
670 
671     if (!cmd.empty())
672     {
673         if (cmd == "fire")
674             fireButton = pressed;
675         roamButton = pressed;
676         if (keyboardMovement == None)
677         {
678             std::string result = CMDMGR.run(cmd);
679             if (!result.empty())
680                 controlPanel->addMessage(result);
681         }
682         return true;
683     }
684 
685     // if we don't have a tank, the following key commands don't apply
686     if (!myTank)
687         return false;
688 
689     {
690         // built-in unchangeable keys.  only perform if not masked.
691         switch (key.ascii)
692         {
693         case 'T':
694         case 't':
695             // toggle frames-per-second display
696             if (pressed)
697             {
698                 showFPS = !showFPS;
699                 if (!showFPS)
700                     hud->setFPS(-1.0);
701             }
702             return true;
703 
704         case 'Y':
705         case 'y':
706             // toggle milliseconds for drawing
707             if (pressed)
708             {
709                 showDrawTime = !showDrawTime;
710                 if (!showDrawTime)
711                     hud->setDrawTime(-1.0);
712             }
713             return true;
714 
715             // for testing forced recreation of OpenGL context
716 #if defined(DEBUG_RENDERING)
717         case 'X':
718             if (pressed && ((shiftKeyStatus & BzfKeyEvent::AltKey) != 0))
719             {
720                 // destroy OpenGL context
721                 getMainWindow()->getWindow()->freeContext();
722 
723                 // recreate OpenGL context
724                 getMainWindow()->getWindow()->makeContext();
725 
726                 // force a redraw (mainly for control panel)
727                 getMainWindow()->getWindow()->callExposeCallbacks();
728 
729                 // cause sun/moon to be repositioned immediately
730                 lastEpochOffset = epochOffset - 5.0;
731 
732                 // reload display lists and textures and initialize other state
733                 OpenGLGState::initContext();
734             }
735             return true;
736 #endif // DEBUG_RENDERING
737 
738         case ']':
739         case '}':
740             // plus 30 seconds
741             if (pressed) clockAdjust += 30.0f;
742             return true;
743 
744         case '[':
745         case '{':
746             // minus 30 seconds
747             if (pressed) clockAdjust -= 30.0f;
748             return true;
749 
750         default:
751             break;
752         } // end switch on key
753         // Shot/Accuracy Statistics display
754         if (key.button == BzfKeyEvent::Home && pressed)
755         {
756             if (!shotStats)
757                 shotStats = new ShotStats;
758 
759             HUDDialogStack::get()->push(shotStats);
760             return true;
761         }
762     } // end key handle
763     return false;
764 }
765 
doKeyNotPlaying(const BzfKeyEvent &,bool,bool)766 static void doKeyNotPlaying(const BzfKeyEvent&, bool, bool)
767 {
768 }
769 
doKeyPlaying(const BzfKeyEvent & key,bool pressed,bool haveBinding)770 static void doKeyPlaying(const BzfKeyEvent& key, bool pressed, bool haveBinding)
771 {
772 #if defined(FREEZING)
773     if (key.ascii == '`' && pressed && !haveBinding && key.shift)
774     {
775         // toggle motion freeze
776         motionFreeze = !motionFreeze;
777         if (motionFreeze)
778             addMessage(NULL, "The tank's motion is now frozen! ... Press Shift+` to unfreeze");
779         return;
780     }
781 #endif
782 
783     if (key.ascii == 0 &&
784             key.button >= BzfKeyEvent::F1 &&
785             key.button <= BzfKeyEvent::F10 &&
786             (key.shift & (BzfKeyEvent::ControlKey +
787                           BzfKeyEvent::AltKey)) != 0 && !haveBinding)
788     {
789         // [Ctrl]-[Fx] is message to team
790         // [Alt]-[Fx] is message to all
791         if (pressed)
792         {
793             char name[32];
794             int msgno = (key.button - BzfKeyEvent::F1) + 1;
795             void* buf = messageMessage;
796             if (key.shift == BzfKeyEvent::ControlKey && world->allowTeams())
797             {
798                 sprintf(name, "quickTeamMessage%d", msgno);
799                 buf = nboPackUByte(buf, TeamToPlayerId(myTank->getTeam()));
800             }
801             else
802             {
803                 sprintf(name, "quickMessage%d", msgno);
804                 buf = nboPackUByte(buf, AllPlayers);
805             }
806             if (BZDB.isSet(name))
807             {
808                 char messageBuffer[MessageLen];
809                 strncpy(messageBuffer,
810                         BZDB.get(name).c_str(),
811                         MessageLen - 1);
812                 messageBuffer[MessageLen - 1] = '\0';
813                 nboPackString(buf, messageBuffer, MessageLen);
814                 serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage);
815             }
816         }
817     }
818     else if (myTank->isAlive())
819     {
820         // Might be a direction key. Save it for later.
821         if ((myTank->getInputMethod() != LocalPlayer::Keyboard) && pressed)
822         {
823             if (keyboardMovement != None)
824                 if (BZDB.isTrue("allowInputChange"))
825                     myTank->setInputMethod(LocalPlayer::Keyboard);
826         }
827     }
828 }
829 
830 
roamMouseWheel(const BzfKeyEvent & key,bool pressed)831 static bool roamMouseWheel(const BzfKeyEvent& key, bool pressed)
832 {
833     if ((key.button != BzfKeyEvent::WheelUp) &&
834             (key.button != BzfKeyEvent::WheelDown))
835         return false;
836     if (middleMouseButton || (leftMouseButton == rightMouseButton))
837         return false;
838     if (!ROAM.isRoaming() || !myTank || (myTank->getTeam() != ObserverTeam))
839         return false;
840 
841     if (pressed)
842     {
843         const bool roamMouseWheelSwap = BZDB.isTrue("roamMouseWheelSwap");
844         const bool up = (key.button == BzfKeyEvent::WheelUp);
845         if (leftMouseButton != roamMouseWheelSwap)
846             ROAM.changeTarget(up ? Roaming::next : Roaming::previous);
847         else if (rightMouseButton != roamMouseWheelSwap)
848         {
849             const int newMode = ROAM.getMode() + (up ? +1 : -1);
850             if ((newMode < int(Roaming::roamViewCount)) &&
851                     (newMode > int(Roaming::roamViewDisabled)))
852                 ROAM.setMode(Roaming::RoamingView(newMode));
853         }
854     }
855 
856     return true;
857 }
858 
859 
doKey(const BzfKeyEvent & key,bool pressed)860 static void doKey(const BzfKeyEvent& key, bool pressed)
861 {
862     switch (key.button)
863     {
864     case BzfKeyEvent::LeftMouse:
865     {
866         leftMouseButton   = pressed;
867         break;
868     }
869     case BzfKeyEvent::RightMouse:
870     {
871         rightMouseButton  = pressed;
872         break;
873     }
874     case BzfKeyEvent::MiddleMouse:
875     {
876         middleMouseButton = pressed;
877         break;
878     }
879     }
880 
881     if (roamMouseWheel(key, pressed))
882         return;
883 
884     if (myTank)
885     {
886         const std::string cmd = KEYMGR.get(key, pressed);
887         if (cmd == "jump")
888             myTank->setJumpPressed(pressed);
889     }
890 
891     if (HUDui::getFocus())
892     {
893         if ((pressed && HUDui::keyPress(key)) ||
894                 (!pressed && HUDui::keyRelease(key)))
895             return;
896     }
897 
898     bool haveBinding = doKeyCommon(key, pressed);
899 
900     if (!myTank)
901         doKeyNotPlaying(key, pressed, haveBinding);
902     else
903         doKeyPlaying(key, pressed, haveBinding);
904 }
905 
doMotion()906 static void     doMotion()
907 {
908     float rotation = 0.0f, speed = 1.0f;
909     const int noMotionSize = hud->getNoMotionSize();
910     const int maxMotionSize = hud->getMaxMotionSize();
911 
912     int keyboardRotation = myTank->getRotation();
913     int keyboardSpeed    = myTank->getSpeed();
914     /* see if controls are reversed */
915     if (myTank->getFlag() == Flags::ReverseControls)
916     {
917         keyboardRotation = -keyboardRotation;
918         keyboardSpeed    = -keyboardSpeed;
919     }
920 
921     // mouse is default steering method; query mouse pos always, not doing so
922     // can lead to stuttering movement with X and software rendering (uncertain why)
923     int mx = 0, my = 0;
924     mainWindow->getMousePosition(mx, my);
925 
926     // determine if joystick motion should be used instead of mouse motion
927     // when the player bumps the mouse, LocalPlayer::getInputMethod return Mouse;
928     // make it return Joystick when the user bumps the joystick
929     if (mainWindow->haveJoystick())
930     {
931         if (myTank->getInputMethod() == LocalPlayer::Joystick)
932         {
933             // if we're using the joystick right now, replace mouse coords with joystick coords
934             mainWindow->getJoyPosition(mx, my);
935         }
936         else
937         {
938             // if the joystick is not active, and we're not forced to some other input method,
939             // see if it's moved and autoswitch
940             if (BZDB.isTrue("allowInputChange"))
941             {
942                 int jx = 0, jy = 0;
943                 mainWindow->getJoyPosition(jx, jy);
944                 // if we aren't using the joystick, but it's moving, start using it
945                 if ((jx < -noMotionSize * 2) || (jx > noMotionSize * 2)
946                         || (jy < -noMotionSize * 2) || (jy > noMotionSize * 2))
947                     myTank->setInputMethod(LocalPlayer::Joystick); // joystick motion
948             } // allowInputChange
949         } // getInputMethod == Joystick
950     } // mainWindow->Joystick
951 
952     /* see if controls are reversed */
953     if (myTank->getFlag() == Flags::ReverseControls)
954     {
955         mx = -mx;
956         my = -my;
957     }
958 
959 #if defined(FREEZING)
960     if (motionFreeze) return;
961 #endif
962 
963     if (myTank->isAutoPilot())
964         doAutoPilot(rotation, speed);
965     else if (myTank->getInputMethod() == LocalPlayer::Keyboard)
966     {
967 
968         rotation = (float)keyboardRotation;
969         speed    = (float)keyboardSpeed;
970         if (speed < 0.0f)
971             speed /= 2.0;
972 
973         rotation *= BZDB.eval("displayFOV") / 60.0f;
974         if (BZDB.isTrue("slowKeyboard"))
975         {
976             rotation *= 0.5f;
977             speed *= 0.5f;
978         }
979     }
980     else     // both mouse and joystick
981     {
982 
983         // calculate desired rotation
984         if (keyboardRotation && !devDriving)
985         {
986             rotation = float(keyboardRotation);
987             rotation *= BZDB.eval("displayFOV") / 60.0f;
988             if (BZDB.isTrue("slowKeyboard"))
989                 rotation *= 0.5f;
990         }
991         else if (mx < -noMotionSize)
992         {
993             rotation = float(-mx - noMotionSize) / float(maxMotionSize - noMotionSize);
994             if (rotation > 1.0f)
995                 rotation = 1.0f;
996         }
997         else if (mx > noMotionSize)
998         {
999             rotation = -float(mx - noMotionSize) / float(maxMotionSize - noMotionSize);
1000             if (rotation < -1.0f)
1001                 rotation = -1.0f;
1002         }
1003 
1004         // calculate desired speed
1005         if (keyboardSpeed && !devDriving)
1006         {
1007             speed = float(keyboardSpeed);
1008             if (speed < 0.0f)
1009                 speed *= 0.5f;
1010             if (BZDB.isTrue("slowKeyboard"))
1011                 speed *= 0.5f;
1012         }
1013         else if (my < -noMotionSize)
1014         {
1015             speed = float(-my - noMotionSize) / float(maxMotionSize - noMotionSize);
1016             if (speed > 1.0f)
1017                 speed = 1.0f;
1018         }
1019         else if (my > noMotionSize)
1020         {
1021             speed = -float(my - noMotionSize) / float(maxMotionSize - noMotionSize);
1022             if (speed < -0.5f)
1023                 speed = -0.5f;
1024         }
1025         else
1026             speed = 0.0f;
1027     }
1028 
1029     myTank->setDesiredAngVel(rotation);
1030     myTank->setDesiredSpeed(speed);
1031 }
1032 
1033 
mouseClamp()1034 static void mouseClamp()
1035 {
1036     // only clamp when it might be useful
1037     if (HUDDialogStack::get()->isActive() || (myTank == NULL) || !myTank->isAlive() ||
1038             myTank->isPaused() || (myTank->getTeam() == ObserverTeam))
1039     {
1040         mainWindow->disableConfineToMotionbox();
1041         return;
1042     }
1043 
1044     // do not clamp if CTRL is being held down
1045     bool alt, ctrl, shift;
1046     display->getModState(alt, ctrl, shift);
1047     if (ctrl)
1048     {
1049         mainWindow->disableConfineToMotionbox();
1050         return;
1051     }
1052 
1053     // calculate the max motion size in pixels
1054     const int clampFudge = 2;
1055     const int w  = mainWindow->getWidth();
1056     const int vh = mainWindow->getViewHeight();
1057     const float xScale = (float)w  / (float) MinX;
1058     const float yScale = (float)vh / (float) MinY;
1059     const float scale = (xScale < yScale) ? xScale : yScale;
1060     const float effScale =  scale * (0.7f + RENDERER.getMaxMotionFactor() / 16.667f);
1061     const int maxMotionSize = (int)((float)MaxMotionSize * effScale);
1062     const int pixels = maxMotionSize + clampFudge;
1063 
1064     // calculate the clamp extents
1065     const int xc = (w / 2);
1066     const int xn = xc - pixels;
1067     const int xp = xc + pixels;
1068     const int yc = (vh / 2);
1069     const int yn = yc - pixels;
1070     const int yp = yc + pixels;
1071 
1072     // clamp, as required
1073     mainWindow->confineToMotionbox(xn, yn, xp, yp);
1074 }
1075 
1076 
doEvent(BzfDisplay * disply)1077 static void     doEvent(BzfDisplay *disply)
1078 {
1079     BzfEvent event;
1080     if (!disply->getEvent(event)) return;
1081 
1082     switch (event.type)
1083     {
1084     case BzfEvent::Quit:
1085         CommandsStandard::quit();
1086         break;
1087 
1088     case BzfEvent::Redraw:
1089         mainWindow->getWindow()->callExposeCallbacks();
1090         sceneRenderer->setExposed();
1091         break;
1092 
1093     case BzfEvent::Resize:
1094         if (mainWindow->getWidth() != event.resize.width || mainWindow->getHeight() != event.resize.height)
1095         {
1096             mainWindow->getWindow()->setSize(event.resize.width, event.resize.height);
1097             mainWindow->getWindow()->callResizeCallbacks();
1098         }
1099         break;
1100 
1101     case BzfEvent::Map:
1102         // window has been mapped.  this normally occurs when the game
1103         // is uniconified.  if the player was paused because of an unmap
1104         // then resume.
1105         if (pausedByUnmap)
1106         {
1107             pausedByUnmap = false;
1108             pauseCountdown = 0.0f;
1109             if (myTank && myTank->isAlive() && myTank->isPaused())
1110             {
1111                 myTank->setPause(false);
1112                 controlPanel->addMessage("Resumed");
1113             }
1114         }
1115 
1116         // restore the resolution we want if full screen
1117         if (mainWindow->getFullscreen())
1118         {
1119             if (preUnmapFormat != -1)
1120             {
1121                 disply->setResolution(preUnmapFormat);
1122                 mainWindow->warpMouse();
1123             }
1124         }
1125 
1126         // restore the sound
1127         if (savedVolume != -1)
1128         {
1129             setSoundVolume(savedVolume);
1130             savedVolume = -1;
1131         }
1132 
1133         unmapped = false;
1134         if (shouldGrabMouse())
1135             mainWindow->grabMouse();
1136         break;
1137 
1138     case BzfEvent::Unmap:
1139         // begin pause countdown when unmapped if:  we're not already
1140         // paused because of an unmap (shouldn't happen), we're not
1141         // already counting down to pausing, we're alive, and we're not
1142         // already paused.
1143         if (!pausedByUnmap && (pauseCountdown == 0.0f) &&
1144                 myTank && myTank->isAlive() &&
1145                 !myTank->isPaused() && !myTank->isAutoPilot() &&
1146                 !BZDB.isTrue("noUnmapPause"))   // handy for testing
1147         {
1148             // get ready to pause (no cheating through instantaneous pausing)
1149             pauseCountdown = 5.0f;
1150 
1151             // set this even though we haven't really paused yet
1152             pausedByUnmap = true;
1153         }
1154 
1155         // ungrab the mouse if we're running full screen
1156         if (mainWindow->getFullscreen() && !unmapped) // skip if already unmapped to avoid losing previous resolution
1157         {
1158             preUnmapFormat = -1;
1159             if (disply->getNumResolutions() > 1)
1160             {
1161                 preUnmapFormat = disply->getResolution();
1162                 disply->setDefaultResolution();
1163             }
1164         }
1165 
1166         // clear the mouse button states
1167         leftMouseButton = rightMouseButton = middleMouseButton = false;
1168 
1169         // turn off the sound
1170         if (savedVolume == -1)
1171         {
1172             savedVolume = getSoundVolume();
1173             setSoundVolume(0);
1174         }
1175 
1176         unmapped = true;
1177         mainWindow->ungrabMouse();
1178         break;
1179 
1180     case BzfEvent::KeyUp:
1181         doKey(event.keyUp, false);
1182         break;
1183 
1184     case BzfEvent::KeyDown:
1185         doKey(event.keyDown, true);
1186         break;
1187 
1188     case BzfEvent::MouseMove:
1189         if (myTank && myTank->isAlive() && (myTank->getInputMethod() != LocalPlayer::Mouse)
1190                 && (BZDB.isTrue("allowInputChange")))
1191             myTank->setInputMethod(LocalPlayer::Mouse);
1192         if (BZDB.isTrue("mouseClamp"))
1193             mouseClamp();
1194         break;
1195 
1196     default:
1197         /* unset */
1198         break;
1199     }
1200 }
1201 
addMessage(const Player * _player,const std::string & msg,int mode,bool highlight,const char * oldColor)1202 void        addMessage(const Player *_player, const std::string& msg,
1203                        int mode, bool highlight, const char* oldColor)
1204 {
1205     std::string fullMessage;
1206 
1207     if (BZDB.isTrue("colorful"))
1208     {
1209         if (_player)
1210         {
1211             if (highlight)
1212             {
1213                 if (BZDB.get("killerhighlight") == "1")
1214                     fullMessage += ColorStrings[PulsatingColor];
1215                 else if (BZDB.get("killerhighlight") == "2")
1216                     fullMessage += ColorStrings[UnderlineColor];
1217             }
1218             const PlayerId pid = _player->getId();
1219             if (pid < 200)
1220             {
1221                 TeamColor color = _player->getTeam();
1222                 fullMessage += Team::getAnsiCode(color);
1223             }
1224             else if (pid == ServerPlayer)
1225                 fullMessage += ColorStrings[YellowColor];
1226             else
1227             {
1228                 fullMessage += ColorStrings[CyanColor]; //replay observers
1229             }
1230             fullMessage += _player->getCallSign();
1231 
1232             if (highlight)
1233                 fullMessage += ColorStrings[ResetColor];
1234 #ifdef BWSUPPORT
1235             fullMessage += " (";
1236             fullMessage += Team::getName(_player->getTeam());
1237             fullMessage += ")";
1238 #endif
1239             fullMessage += ColorStrings[DefaultColor] + ": ";
1240         }
1241         fullMessage += msg;
1242     }
1243     else
1244     {
1245         if (oldColor != NULL)
1246             fullMessage = oldColor;
1247 
1248         if (_player)
1249         {
1250             fullMessage += _player->getCallSign();
1251 
1252 #ifdef BWSUPPORT
1253             fullMessage += " (";
1254             fullMessage += Team::getName(_player->getTeam());
1255             fullMessage += ")";
1256 #endif
1257             fullMessage += ": ";
1258         }
1259         fullMessage += stripAnsiCodes(msg);
1260     }
1261     controlPanel->addMessage(fullMessage, mode);
1262 }
1263 
updateHighScores()1264 static void     updateHighScores()
1265 {
1266     /* check scores to see if my team and/or have the high score.  change
1267      * `>= bestScore' to `> bestScore' if you want to share the number
1268      * one spot. */
1269     bool anyPlayers = false;
1270     int i;
1271     for (i = 0; i < curMaxPlayers; i++)
1272         if (remotePlayers[i])
1273         {
1274             anyPlayers = true;
1275             break;
1276         }
1277 #ifdef ROBOT
1278     if (!anyPlayers)
1279     {
1280         for (i = 0; i < numRobots; i++)
1281             if (robots[i])
1282             {
1283                 anyPlayers = true;
1284                 break;
1285             }
1286     }
1287 #endif
1288     if (!anyPlayers)
1289     {
1290         hud->setPlayerHasHighScore(false);
1291         hud->setTeamHasHighScore(false);
1292         return;
1293     }
1294 
1295     bool haveBest = true;
1296     int bestScore = myTank ? myTank->getScore() : 0;
1297     for (i = 0; i < curMaxPlayers; i++)
1298         if (remotePlayers[i] && remotePlayers[i]->getScore() >= bestScore)
1299         {
1300             haveBest = false;
1301             break;
1302         }
1303 #ifdef ROBOT
1304     if (haveBest)
1305     {
1306         for (i = 0; i < numRobots; i++)
1307             if (robots[i] && robots[i]->getScore() >= bestScore)
1308             {
1309                 haveBest = false;
1310                 break;
1311             }
1312     }
1313 #endif
1314     hud->setPlayerHasHighScore(haveBest);
1315 
1316     if (myTank && Team::isColorTeam(myTank->getTeam()))
1317     {
1318         const Team& myTeam = World::getWorld()->getTeam(int(myTank->getTeam()));
1319         bestScore = myTeam.getWins() - myTeam.getLosses();
1320         haveBest = true;
1321         for (i = 0; i < NumTeams; i++)
1322         {
1323             if (i == int(myTank->getTeam())) continue;
1324             const Team& team = World::getWorld()->getTeam(i);
1325             if (team.size > 0 && team.getWins() - team.getLosses() >= bestScore)
1326             {
1327                 haveBest = false;
1328                 break;
1329             }
1330         }
1331         hud->setTeamHasHighScore(haveBest);
1332     }
1333     else
1334         hud->setTeamHasHighScore(false);
1335 }
1336 
updateFlag(FlagType * flag)1337 static void     updateFlag(FlagType* flag)
1338 {
1339     if (flag == Flags::Null)
1340     {
1341         hud->setColor(1.0f, 0.625f, 0.125f);
1342         hud->setAlert(2, NULL, 0.0f);
1343     }
1344     else
1345     {
1346         const float* color = flag->getColor();
1347         hud->setColor(color[0], color[1], color[2]);
1348         hud->setAlert(2, flag->flagName.c_str(), 3.0f, flag->endurance == FlagSticky);
1349     }
1350 
1351     if (BZDB.isTrue("displayFlagHelp"))
1352         hud->setFlagHelp(flag, FlagHelpDuration);
1353 
1354     if ((!radar && !myTank) || !World::getWorld())
1355         return;
1356 
1357     radar->setJammed(flag == Flags::Jamming);
1358     hud->setAltitudeTape(flag == Flags::Jumping || World::getWorld()->allowJumping());
1359 }
1360 
1361 
notifyBzfKeyMapChanged()1362 void            notifyBzfKeyMapChanged()
1363 {
1364     std::string restartLabel = "Right Mouse";
1365     std::vector<std::string> keys = KEYMGR.getKeysFromCommand("restart", false);
1366 
1367     if (keys.empty())
1368     {
1369         // found nothing on down binding, so try up
1370         keys = KEYMGR.getKeysFromCommand("identify", true);
1371         if (keys.empty())
1372             std::cerr << "There does not appear to be any key bound to enter the game" << std::endl;
1373     }
1374 
1375     if (keys.size() >= 1)
1376     {
1377         // display single letter keys as a quoted lowercase letter
1378         if (keys[0].size() == 1)
1379         {
1380             restartLabel = '\"';
1381             restartLabel += tolower(keys[0][0]);
1382             restartLabel += '\"';
1383         }
1384         else
1385             restartLabel = keys[0];
1386     }
1387 
1388     // only show the first 2 keys found to keep things simple
1389     if (keys.size() > 1)
1390     {
1391         restartLabel.append(" or ");
1392         // display single letter keys as quoted lowercase letter
1393         if (keys[1].size() == 1)
1394         {
1395             restartLabel += '\"';
1396             restartLabel += tolower(keys[1][0]);
1397             restartLabel += '\"';
1398         }
1399         else
1400             restartLabel.append(keys[1]);
1401     }
1402 
1403     hud->setRestartKeyLabel(restartLabel);
1404 }
1405 
1406 
1407 //
1408 // server message handling
1409 //
addPlayer(PlayerId id,const void * msg,int showMessage)1410 static Player*      addPlayer(PlayerId id, const void* msg, int showMessage)
1411 {
1412     uint16_t team, type, wins, losses, tks;
1413     char callsign[CallSignLen];
1414     char motto[MottoLen];
1415     msg = nboUnpackUShort (msg, type);
1416     msg = nboUnpackUShort (msg, team);
1417     msg = nboUnpackUShort (msg, wins);
1418     msg = nboUnpackUShort (msg, losses);
1419     msg = nboUnpackUShort (msg, tks);
1420     msg = nboUnpackString (msg, callsign, CallSignLen);
1421     msg = nboUnpackString (msg, motto, MottoLen);
1422 
1423     // Strip any ANSI color codes
1424     strncpy (callsign, stripAnsiCodes (std::string (callsign)).c_str (), 31);
1425 
1426     // id is slot, check if it's empty
1427     const int i = id;
1428 
1429     // sanity check
1430     if (i < 0)
1431     {
1432         printError (TextUtils::format ("Invalid player identification (%d)", i));
1433         std::
1434         cerr <<
1435              "WARNING: invalid player identification when adding player with id "
1436              << i << std::endl;
1437         return NULL;
1438     }
1439 
1440     if (remotePlayers[i])
1441     {
1442         // we're not in synch with server -> help! not a good sign, but not fatal.
1443         printError ("Server error when adding player, player already added");
1444         std::cerr << "WARNING: player already exists at location with id "
1445                   << i << std::endl;
1446         return NULL;
1447     }
1448 
1449     if (i >= curMaxPlayers)
1450     {
1451         curMaxPlayers = i + 1;
1452         World::getWorld ()->setCurMaxPlayers (curMaxPlayers);
1453     }
1454 
1455     // add player
1456     if (PlayerType (type) == TankPlayer || PlayerType (type) == ComputerPlayer)
1457     {
1458         remotePlayers[i] = new RemotePlayer (id, TeamColor (team), callsign, motto,
1459                                              PlayerType (type));
1460         remotePlayers[i]->changeScore (short (wins), short (losses), short (tks));
1461     }
1462 
1463 #ifdef ROBOT
1464     if (PlayerType (type) == ComputerPlayer)
1465         for (int j = 0; j < numRobots; j++)
1466             if (robots[j] && !strncmp (robots[j]->getCallSign (), callsign,
1467                                        CallSignLen))
1468             {
1469                 robots[j]->setTeam (TeamColor (team));
1470                 break;
1471             }
1472 #endif
1473 
1474     // show the message if we don't have the playerlist
1475     // permission.  if * we do, MsgAdminInfo should arrive
1476     // with more info.
1477     if (showMessage && !myTank->hasPlayerList ())
1478     {
1479         std::string message ("joining as ");
1480         if (team == ObserverTeam)
1481             message += "an observer";
1482         else
1483         {
1484             switch (PlayerType (type))
1485             {
1486             case TankPlayer:
1487                 message += "a tank";
1488                 break;
1489             case ComputerPlayer:
1490                 message += "a robot tank";
1491                 break;
1492             default:
1493                 message += "an unknown type";
1494                 break;
1495             }
1496         }
1497         if (!remotePlayers[i])
1498         {
1499             std::string name (callsign);
1500             name += ": " + message;
1501             message = name;
1502         }
1503         addMessage (remotePlayers[i], message);
1504     }
1505     completer.registerWord(callsign, true /* quote spaces */);
1506 
1507     if (shotStats)
1508         shotStats->refresh();
1509 
1510     return remotePlayers[i];
1511 }
1512 
1513 
printIpInfo(const Player * _player,const Address & addr,const std::string & note)1514 static void printIpInfo (const Player *_player, const Address& addr,
1515                          const std::string &note)
1516 {
1517     if (_player == NULL)
1518         return;
1519     std::string colorStr;
1520     if (_player->getId() < 200)
1521     {
1522         TeamColor color = _player->getTeam();
1523         colorStr = Team::getAnsiCode(color);
1524     }
1525     else
1526     {
1527         colorStr = ColorStrings[CyanColor]; // replay observers
1528     }
1529     const std::string addrStr = addr.getDotNotation();
1530     std::string message = ColorStrings[CyanColor]; // default color
1531     message += "IPINFO: ";
1532     if (BZDBCache::colorful) message += colorStr;
1533     message += _player->getCallSign();
1534     if (BZDBCache::colorful) message += ColorStrings[CyanColor];
1535     message += "\t from: ";
1536     if (BZDBCache::colorful) message += colorStr;
1537     message += addrStr;
1538 
1539     message += ColorStrings[WhiteColor];
1540     for (int i = 0; i < (17 - (int)addrStr.size()); i++)
1541         message += " ";
1542     message += note;
1543 
1544     // print into the Server Menu
1545     controlPanel->addMessage(message, 2);
1546 
1547     return;
1548 }
1549 
1550 
removePlayer(PlayerId id)1551 static bool removePlayer (PlayerId id)
1552 {
1553     int playerIndex = lookupPlayerIndex(id);
1554 
1555     if (playerIndex < 0)
1556         return false;
1557 
1558     Player* p = remotePlayers[playerIndex];
1559 
1560     Address addr;
1561     std::string msg = "signing off";
1562     if (!p->getIpAddress(addr))
1563         addMessage(p, "signing off");
1564     else
1565     {
1566         msg += " from ";
1567         msg += addr.getDotNotation();
1568         p->setIpAddress(addr);
1569         addMessage(p, msg);
1570         if (BZDB.evalInt("showips") > 1)
1571             printIpInfo (p, addr, "(leave)");
1572     }
1573 
1574     if (myTank->getRecipient() == p)
1575         myTank->setRecipient(0);
1576     if (myTank->getNemesis() == p)
1577         myTank->setNemesis(0);
1578 
1579     completer.unregisterWord(p->getCallSign());
1580 
1581     delete remotePlayers[playerIndex];
1582     remotePlayers[playerIndex] = NULL;
1583 
1584     while ((playerIndex >= 0)
1585             &&     (playerIndex+1 == curMaxPlayers)
1586             &&     (remotePlayers[playerIndex] == NULL))
1587     {
1588         playerIndex--;
1589         curMaxPlayers--;
1590     }
1591     World::getWorld()->setCurMaxPlayers(curMaxPlayers);
1592 
1593     if (shotStats)
1594         shotStats->refresh();
1595 
1596     return true;
1597 }
1598 
1599 
isCached(char * hexDigest)1600 static bool isCached(char *hexDigest)
1601 {
1602     std::istream *cachedWorld;
1603     bool cached    = false;
1604     worldCachePath = getCacheDirName();
1605     worldCachePath += hexDigest;
1606     worldCachePath += ".bwc";
1607     if ((cachedWorld = FILEMGR.createDataInStream(worldCachePath, true)))
1608     {
1609         cached = true;
1610         delete cachedWorld;
1611     }
1612     return cached;
1613 }
1614 
1615 
curlProgressFunc(void * UNUSED (clientp),double dltotal,double dlnow,double UNUSED (ultotal),double UNUSED (ulnow))1616 int curlProgressFunc(void* UNUSED(clientp),
1617                      double dltotal, double dlnow,
1618                      double UNUSED(ultotal), double UNUSED(ulnow))
1619 {
1620     // FIXME: beaucoup cheeze here in the aborting style
1621     //    we should be using async dns and multi-curl
1622 
1623     // abort the download?
1624     BzfEvent event;
1625     if (display->isEventPending())
1626     {
1627         if (display->peekEvent(event))
1628         {
1629             switch (event.type)
1630             {
1631             case BzfEvent::Quit:
1632                 return 1;         // terminate the curl call
1633             case BzfEvent::KeyDown:
1634                 display->getEvent(event); // flush the event
1635                 if (event.keyDown.ascii == 27)
1636                 {
1637                     return 1;           // terminate the curl call
1638                 }
1639                 break;
1640             case BzfEvent::KeyUp:
1641                 display->getEvent(event); // flush the event
1642                 break;
1643             case BzfEvent::MouseMove:
1644                 display->getEvent(event); // flush the event
1645                 break;
1646             case BzfEvent::Unset:
1647             case BzfEvent::Map:
1648             case BzfEvent::Unmap:
1649             case BzfEvent::Redraw:
1650             case BzfEvent::Resize:
1651                 // leave the event, it might be important
1652                 break;
1653             }
1654         }
1655     }
1656 
1657     // update the status
1658     double percentage = 0.0;
1659     if ((int)dltotal > 0)
1660         percentage = 100.0 * dlnow / dltotal;
1661     char buffer[128];
1662     sprintf (buffer, "%2.1f%% (%i/%i)", percentage, (int)dlnow, (int)dltotal);
1663     HUDDialogStack::get()->setFailedMessage(buffer);
1664 
1665     return 0;
1666 }
1667 
loadCachedWorld()1668 static void loadCachedWorld()
1669 {
1670     // can't get a cache from nothing
1671     if (worldCachePath == std::string(""))
1672     {
1673         joiningGame = false;
1674         return;
1675     }
1676 
1677     // lookup the cached world
1678     std::istream *cachedWorld = FILEMGR.createDataInStream(worldCachePath, true);
1679     if (!cachedWorld)
1680     {
1681         HUDDialogStack::get()->setFailedMessage("World cache files disappeared.  Join canceled");
1682         drawFrame(0.0f);
1683         remove(worldCachePath.c_str());
1684         joiningGame = false;
1685         return;
1686     }
1687 
1688     // status update
1689     HUDDialogStack::get()->setFailedMessage("Loading world into memory...");
1690     drawFrame(0.0f);
1691 
1692     // get the world size
1693     cachedWorld->seekg(0, std::ios::end);
1694     std::streampos size = cachedWorld->tellg();
1695     unsigned long charSize = (unsigned long)std::streamoff(size);
1696 
1697     // load the cached world
1698     cachedWorld->seekg(0);
1699     char *localWorldDatabase = new char[charSize];
1700     if (!localWorldDatabase)
1701     {
1702         HUDDialogStack::get()->setFailedMessage("Error loading cached world.  Join canceled");
1703         drawFrame(0.0f);
1704         remove(worldCachePath.c_str());
1705         joiningGame = false;
1706         return;
1707     }
1708     cachedWorld->read(localWorldDatabase, charSize);
1709     delete cachedWorld;
1710 
1711     // verify
1712     HUDDialogStack::get()->setFailedMessage("Verifying world integrity...");
1713     drawFrame(0.0f);
1714     MD5 md5;
1715     md5.update((unsigned char *)localWorldDatabase, charSize);
1716     md5.finalize();
1717     std::string digest = md5.hexdigest();
1718     if (digest != md5Digest)
1719     {
1720         if (worldBuilder)
1721             delete worldBuilder;
1722         worldBuilder = NULL;
1723         delete[] localWorldDatabase;
1724         HUDDialogStack::get()->setFailedMessage("Error on md5. Removing offending file.");
1725         remove(worldCachePath.c_str());
1726         joiningGame = false;
1727         return;
1728     }
1729 
1730     // make world
1731     HUDDialogStack::get()->setFailedMessage("Preparing world...");
1732     drawFrame(0.0f);
1733     if (world)
1734     {
1735         delete world;
1736         world = NULL;
1737     }
1738     if (!worldBuilder->unpack(localWorldDatabase))
1739     {
1740         // world didn't make for some reason
1741         if (worldBuilder)
1742             delete worldBuilder;
1743         worldBuilder = NULL;
1744         delete[] localWorldDatabase;
1745         HUDDialogStack::get()->setFailedMessage("Error unpacking world database. Join canceled.");
1746         remove(worldCachePath.c_str());
1747         joiningGame = false;
1748         return;
1749     }
1750     delete[] localWorldDatabase;
1751 
1752     // return world
1753     world = worldBuilder->getWorld();
1754     if (worldBuilder)
1755         delete worldBuilder;
1756     worldBuilder = NULL;
1757 
1758     HUDDialogStack::get()->setFailedMessage("Downloading files...");
1759 
1760     const bool doDownloads =  BZDB.isTrue("doDownloads");
1761     const bool updateDownloads =  BZDB.isTrue("updateDownloads");
1762     Downloads::startDownloads(doDownloads, updateDownloads, false);
1763     downloadingInitialTexture  = true;
1764 }
1765 
1766 class WorldDownLoader : cURLManager
1767 {
1768 public:
1769     void start(char * hexDigest);
1770 private:
1771     void askToBZFS();
1772     virtual void finalization(char *data, unsigned int length, bool good);
1773 };
1774 
start(char * hexDigest)1775 void WorldDownLoader::start(char * hexDigest)
1776 {
1777     if (isCached(hexDigest))
1778         loadCachedWorld();
1779     else if (worldUrl.size())
1780     {
1781         HUDDialogStack::get()->setFailedMessage
1782         (("Loading world from " + worldUrl).c_str());
1783         setProgressFunction(curlProgressFunc, worldUrl.c_str());
1784         setURL(worldUrl);
1785         addHandle();
1786         worldUrl = ""; // clear the state
1787     }
1788     else
1789         askToBZFS();
1790 }
1791 
finalization(char * data,unsigned int length,bool good)1792 void WorldDownLoader::finalization(char *data, unsigned int length, bool good)
1793 {
1794     if (good)
1795     {
1796         worldDatabase = data;
1797         theData       = NULL;
1798         MD5 md5;
1799         md5.update((unsigned char *)worldDatabase, length);
1800         md5.finalize();
1801         std::string digest = md5.hexdigest();
1802         if (digest != md5Digest)
1803         {
1804             HUDDialogStack::get()->setFailedMessage("Download from URL failed");
1805             askToBZFS();
1806         }
1807         else
1808         {
1809             std::ostream* cache =
1810                 FILEMGR.createDataOutStream(worldCachePath, true, true);
1811             if (cache != NULL)
1812             {
1813                 cache->write(worldDatabase, length);
1814                 delete cache;
1815                 loadCachedWorld();
1816             }
1817             else
1818             {
1819                 HUDDialogStack::get()->setFailedMessage("Problem writing cache");
1820                 askToBZFS();
1821             }
1822         }
1823     }
1824     else
1825         askToBZFS();
1826 }
1827 
askToBZFS()1828 void WorldDownLoader::askToBZFS()
1829 {
1830     HUDDialogStack::get()->setFailedMessage("Downloading World...");
1831     char message[MaxPacketLen];
1832     // ask for world
1833     nboPackUInt(message, 0);
1834     serverLink->send(MsgGetWorld, sizeof(uint32_t), message);
1835     worldPtr = 0;
1836     if (cacheOut)
1837         delete cacheOut;
1838     cacheOut = FILEMGR.createDataOutStream(worldCachePath, true, true);
1839 }
1840 
1841 static WorldDownLoader *worldDownLoader;
1842 
dumpMissingFlag(const char * buf,uint16_t len)1843 static void dumpMissingFlag(const char *buf, uint16_t len)
1844 {
1845     int i;
1846     int nFlags = len/2;
1847     std::string flags;
1848 
1849     for (i = 0; i < nFlags; i++)
1850     {
1851         /* We can't use FlagType::unpack() here, since it counts on the
1852          * flags existing in our flag database.
1853          */
1854         if (i)
1855             flags += ", ";
1856         flags += buf[0];
1857         if (buf[1])
1858             flags += buf[1];
1859         buf += 2;
1860     }
1861 
1862     std::vector<std::string> args;
1863     args.push_back(flags);
1864     HUDDialogStack::get()->setFailedMessage
1865     (TextUtils::format("Flags not supported by this client: {1}",
1866                        &args).c_str());
1867 }
1868 
processWorldChunk(const void * buf,uint16_t len,int bytesLeft)1869 static bool processWorldChunk(const void *buf, uint16_t len, int bytesLeft)
1870 {
1871     int totalSize = worldPtr + len + bytesLeft;
1872     int doneSize  = worldPtr + len;
1873     if (cacheOut)
1874         cacheOut->write((const char *)buf, len);
1875     HUDDialogStack::get()->setFailedMessage
1876     (TextUtils::format
1877      ("Downloading World (%2d%% complete/%d kb remaining)...",
1878       (100 * doneSize / totalSize), bytesLeft / 1024).c_str());
1879     return bytesLeft == 0;
1880 }
1881 
sendMeaCulpa(PlayerId victim)1882 static void sendMeaCulpa(PlayerId victim)
1883 {
1884     char meaculpa[MessageLen];
1885     char *buf;
1886 
1887     if (!myTank->isAutoPilot())
1888         return;
1889 
1890     strncpy(meaculpa, "sorry, i'm just a silly machine", MessageLen);
1891     buf = messageMessage;
1892     buf = (char*)nboPackUByte(buf, victim);
1893     nboPackString(buf, meaculpa, MessageLen-1);
1894     serverLink->send(MsgMessage, sizeof(messageMessage), messageMessage);
1895 }
1896 
handleNearFlag(const void * msg,uint16_t)1897 static void handleNearFlag (const void *msg, uint16_t)
1898 {
1899     float pos[3];
1900     std::string flagName;
1901     msg = nboUnpackVector(msg, pos);
1902     msg = nboUnpackStdString(msg, flagName);
1903 
1904     std::string fullMessage = "Closest Flag: " + flagName;
1905     addMessage(NULL, ColorStrings[YellowColor]+fullMessage+ColorStrings[DefaultColor], 2, false, NULL);
1906 
1907     if (myTank)
1908         hud->setAlert(0, fullMessage.c_str(), 5.0f, false);
1909 }
1910 
handleServerMessage(bool human,uint16_t code,uint16_t len,const void * msg)1911 static void     handleServerMessage(bool human, uint16_t code,
1912                                     uint16_t len, const void* msg)
1913 {
1914     bool checkScores = false;
1915     static WordFilter *wordfilter = (WordFilter *)BZDB.getPointer("filter");
1916 
1917     switch (code)
1918     {
1919 
1920     case MsgNearFlag:
1921         // MsgNearFlag may arrive up to 1 lag period after dropping ID,
1922         // so process this only when carrying the ID flag
1923         if (myTank && myTank->getFlag() == Flags::Identify)
1924             handleNearFlag(msg,len);
1925         break;
1926 
1927     case MsgFetchResources:
1928         if (BZDB.isSet ("_noRemoteFiles") && BZDB.isTrue ("_noRemoteFiles"))
1929             break;
1930         else
1931         {
1932             uint16_t numItems;
1933             const void *buf;
1934 
1935             buf = nboUnpackUShort (msg, numItems);    // the type
1936 
1937             for (int i = 0; i < numItems; i++)
1938             {
1939                 uint16_t itemType;
1940                 char buffer[MessageLen];
1941                 uint16_t stringLen;
1942                 trResourceItem item;
1943 
1944                 buf = nboUnpackUShort (buf, itemType);
1945                 item.resType = (teResourceType) itemType;
1946 
1947                 // URL
1948                 buf = nboUnpackUShort (buf, stringLen);
1949                 buf = nboUnpackString (buf, buffer, stringLen);
1950 
1951                 buffer[stringLen] = '\0';
1952                 item.URL = buffer;
1953 
1954                 item.filePath = PlatformFactory::getMedia ()->getMediaDirectory() + DirectorySeparator;
1955                 std::vector < std::string > temp =
1956                     TextUtils::tokenize (item.URL, std::string ("/"));
1957 
1958                 item.fileName = temp[temp.size () - 1];
1959                 item.filePath += item.fileName;
1960 
1961                 std::string hostname;
1962                 parseHostname (item.URL, hostname);
1963                 if (authorizedServer (hostname))
1964                 {
1965                     if (!resourceDownloader)
1966                         resourceDownloader = new ResourceGetter;
1967                     resourceDownloader->addResource (item);
1968                 }
1969             }
1970         }
1971         break;
1972 
1973     case MsgCustomSound:
1974         // bail out if we don't want to do remote sounds
1975         if (BZDB.isSet ("_noRemoteSounds") && BZDB.isTrue ("_noRemoteSounds"))
1976             break;
1977         else
1978         {
1979             const void *buf;
1980             char buffer[MessageLen];
1981             uint16_t soundType;
1982             uint16_t stringLen;
1983             std::string soundName;
1984 
1985             buf = nboUnpackUShort (msg, soundType);   // the type
1986             buf = nboUnpackUShort (buf, stringLen);   // how long our str is
1987             buf = nboUnpackString (buf, buffer, stringLen);
1988 
1989             buffer[stringLen] = '\0';
1990             soundName = buffer;
1991 
1992             if (soundType == LocalCustomSound)
1993                 playLocalSound (soundName);
1994         }
1995         break;
1996 
1997     case MsgUDPLinkEstablished:
1998         // server got our initial UDP packet
1999         serverLink->enableOutboundUDP();
2000         break;
2001 
2002     case MsgUDPLinkRequest:
2003         // we got server's initial UDP packet
2004         serverLink->confirmIncomingUDP();
2005         break;
2006 
2007     case MsgSuperKill:
2008         printError("Server forced a disconnect");
2009         serverError = true;
2010         break;
2011 
2012     case MsgAccept:
2013         break;
2014 
2015     case MsgReject:
2016     {
2017         const void *buf;
2018         char buffer[MessageLen];
2019         uint16_t rejcode;
2020         std::string reason;
2021         buf = nboUnpackUShort (msg, rejcode); // filler for now
2022         buf = nboUnpackString (buf, buffer, MessageLen);
2023         buffer[MessageLen - 1] = '\0';
2024         reason = buffer;
2025         printError(reason);
2026         serverError = true;
2027         break;
2028     }
2029 
2030     case MsgNegotiateFlags:
2031     {
2032         if (len > 0)
2033         {
2034             dumpMissingFlag((const char *)msg, len);
2035             break;
2036         }
2037         serverLink->send(MsgWantSettings, 0, NULL);
2038         break;
2039     }
2040 
2041     case MsgGameSettings:
2042     {
2043         if (worldBuilder)
2044             delete worldBuilder;
2045         worldBuilder = new WorldBuilder;
2046         worldBuilder->unpackGameSettings(msg);
2047         serverLink->send(MsgWantWHash, 0, NULL);
2048         HUDDialogStack::get()->setFailedMessage("Requesting World Hash...");
2049         break;
2050     }
2051 
2052     case MsgCacheURL:
2053     {
2054         char *cacheURL = new char[len];
2055         nboUnpackString(msg, cacheURL, len);
2056         worldUrl = cacheURL;
2057         delete [] cacheURL;
2058         break;
2059     }
2060 
2061     case MsgWantWHash:
2062     {
2063         char *hexDigest = new char[len];
2064         nboUnpackString(msg, hexDigest, len);
2065         isCacheTemp = hexDigest[0] == 't';
2066         md5Digest = &hexDigest[1];
2067 
2068         worldDownLoader->start(hexDigest);
2069         delete [] hexDigest;
2070         break;
2071     }
2072 
2073     case MsgGetWorld:
2074     {
2075         // create world
2076         uint32_t bytesLeft;
2077         const void *buf = nboUnpackUInt(msg, bytesLeft);
2078         bool last = processWorldChunk(buf, len - 4, bytesLeft);
2079         if (!last)
2080         {
2081             char message[MaxPacketLen];
2082             // ask for next chunk
2083             worldPtr += len - 4;
2084             nboPackUInt(message, worldPtr);
2085             serverLink->send(MsgGetWorld, sizeof(uint32_t), message);
2086             break;
2087         }
2088         if (cacheOut)
2089             delete cacheOut;
2090         cacheOut = NULL;
2091         loadCachedWorld();
2092         if (isCacheTemp)
2093             markOld(worldCachePath);
2094         break;
2095     }
2096 
2097     case MsgGameTime:
2098     {
2099         GameTime::unpack(msg);
2100         GameTime::update();
2101         break;
2102     }
2103 
2104     case MsgTimeUpdate:
2105     {
2106         int32_t timeLeft;
2107         msg = nboUnpackInt(msg, timeLeft);
2108         hud->setTimeLeft(timeLeft);
2109         if (timeLeft == 0)
2110         {
2111             if (myTank->getTeam() != ObserverTeam)
2112                 gameOver = true;
2113             myTank->explodeTank();
2114             controlPanel->addMessage("Time Expired");
2115             hud->setAlert(0, "Time Expired", 10.0f, true);
2116             controlPanel->addMessage("GAME OVER");
2117             hud->setAlert(1, "GAME OVER", 10.0f, true);
2118 #ifdef ROBOT
2119             for (int i = 0; i < numRobots; i++)
2120                 if (robots[i])
2121                     robots[i]->explodeTank();
2122 #endif
2123         }
2124         else if (timeLeft < 0)
2125         {
2126             controlPanel->addMessage("Game Paused");
2127             hud->setAlert(0, "Game Paused", 10.0f, true);
2128         }
2129         break;
2130     }
2131 
2132     case MsgScoreOver:
2133     {
2134         // unpack packet
2135         PlayerId id;
2136         uint16_t team;
2137         msg = nboUnpackUByte(msg, id);
2138         msg = nboUnpackUShort(msg, team);
2139         Player* _player = lookupPlayer(id);
2140 
2141         // make a message
2142         std::string msg2;
2143         if (team == (uint16_t)NoTeam)
2144         {
2145             // a player won
2146             if (_player)
2147             {
2148                 msg2 = _player->getCallSign();
2149                 msg2 += " (";
2150                 msg2 += Team::getName(_player->getTeam());
2151                 msg2 += ")";
2152             }
2153             else
2154                 msg2 = "[unknown player]";
2155         }
2156         else
2157         {
2158             // a team won
2159             msg2 = Team::getName(TeamColor(team));
2160         }
2161         msg2 += " won the game";
2162 
2163         if (myTank->getTeam() != ObserverTeam)
2164             gameOver = true;
2165         hud->setTimeLeft((uint32_t)~0);
2166         myTank->explodeTank();
2167         controlPanel->addMessage(msg2);
2168         hud->setAlert(0, msg2.c_str(), 10.0f, true);
2169 #ifdef ROBOT
2170         for (int i = 0; i < numRobots; i++)
2171             if (robots[i])
2172                 robots[i]->explodeTank();
2173 #endif
2174         break;
2175     }
2176 
2177     case MsgAddPlayer:
2178     {
2179         PlayerId id;
2180         msg = nboUnpackUByte(msg, id);
2181 #if defined(FIXME) && defined(ROBOT)
2182         saveRobotInfo(id, msg);
2183 #endif
2184         if (id == myTank->getId())
2185         {
2186             // it's me!  should be the end of updates
2187             enteringServer(msg);
2188         }
2189         else
2190         {
2191             addPlayer(id, msg, entered);
2192             checkScores = true;
2193 
2194             // update the tank flags when in replay mode.
2195             if (myTank->getId() >= 200)
2196                 setTankFlags();
2197         }
2198         break;
2199     }
2200 
2201     case MsgRemovePlayer:
2202     {
2203         PlayerId id;
2204         msg = nboUnpackUByte(msg, id);
2205         if (removePlayer (id))
2206             checkScores = true;
2207         break;
2208     }
2209 
2210     case MsgFlagUpdate:
2211     {
2212         uint16_t count;
2213         uint16_t flagIndex;
2214         uint32_t offset = 0;
2215         msg = nboUnpackUShort(msg, count);
2216         offset += 2;
2217         for (int i = 0; i < count; i++)
2218         {
2219             if (offset >= len)
2220                 break;
2221 
2222             msg = nboUnpackUShort(msg, flagIndex);
2223             msg = world->getFlag(int(flagIndex)).unpack(msg);
2224             offset += FlagPLen;
2225 
2226             world->initFlag(int(flagIndex));
2227         }
2228         break;
2229     }
2230 
2231     case MsgTeamUpdate:
2232     {
2233         uint8_t  numTeams;
2234         uint16_t team;
2235 
2236         msg = nboUnpackUByte(msg,numTeams);
2237         for (int i = 0; i < numTeams; i++)
2238         {
2239             msg = nboUnpackUShort(msg, team);
2240             msg = teams[int(team)].unpack(msg);
2241         }
2242         checkScores = true;
2243         break;
2244     }
2245 
2246     case MsgAlive:
2247     {
2248         PlayerId id;
2249         float pos[3], forward;
2250         msg = nboUnpackUByte(msg, id);
2251         msg = nboUnpackVector(msg, pos);
2252         msg = nboUnpackFloat(msg, forward);
2253         int playerIndex = lookupPlayerIndex(id);
2254 
2255         if ((playerIndex >= 0) || (playerIndex == -2))
2256         {
2257             static const float zero[3] = { 0.0f, 0.0f, 0.0f };
2258             Player* tank = getPlayerByIndex(playerIndex);
2259             if (tank == myTank)
2260             {
2261                 wasRabbit = tank->getTeam() == RabbitTeam;
2262                 myTank->restart(pos, forward);
2263                 firstLife = false;
2264                 justJoined = false;
2265                 if (!myTank->isAutoPilot())
2266                     mainWindow->warpMouse();
2267                 hud->setAltitudeTape(World::getWorld()->allowJumping());
2268 #ifdef ROBOT
2269             }
2270             else if (tank->getPlayerType() == ComputerPlayer)
2271             {
2272                 for (int r = 0; r < numRobots; r++)
2273                 {
2274                     if (robots[r] && robots[r]->getId() == playerIndex)
2275                     {
2276                         robots[r]->restart(pos,forward);
2277                         setRobotTarget(robots[r]);
2278                         break;
2279                     }
2280                 }
2281 #endif
2282             }
2283 
2284             tank->setDeathEffect(NULL);
2285             if (SceneRenderer::instance().useQuality() >= 2)
2286             {
2287                 if (((tank != myTank)
2288                         && ((ROAM.getMode() != Roaming::roamViewFP)
2289                             || (tank != ROAM.getTargetTank())))
2290                         || BZDB.isTrue("enableLocalSpawnEffect"))
2291                 {
2292                     if (myTank->getFlag() == Flags::Colorblindness)
2293                     {
2294                         static float cbColor[4] = {1,1,1,1};
2295                         EFFECTS.addSpawnEffect(cbColor, pos);
2296                     }
2297                     else
2298                         EFFECTS.addSpawnEffect(tank->getColor(), pos);
2299                 }
2300             }
2301             tank->setStatus(PlayerState::Alive);
2302             tank->move(pos, forward);
2303             tank->setVelocity(zero);
2304             tank->setAngularVelocity(0.0f);
2305             tank->setDeadReckoning();
2306             tank->spawnEffect();
2307             if (tank == myTank)
2308                 myTank->setSpawning(false);
2309             playSound(SFX_POP, pos, true, isViewTank(tank));
2310         }
2311 
2312         break;
2313     }
2314 
2315     case MsgAutoPilot:
2316     {
2317         PlayerId id;
2318         msg = nboUnpackUByte(msg, id);
2319         uint8_t autopilot;
2320         nboUnpackUByte(msg, autopilot);
2321         Player* tank = lookupPlayer(id);
2322         if (!tank) break;
2323         tank->setAutoPilot(autopilot != 0);
2324         addMessage(tank, autopilot ? "Roger taking controls" : "Roger releasing controls");
2325         break;
2326     }
2327 
2328     case MsgPause:
2329     {
2330         PlayerId id;
2331         msg = nboUnpackUByte(msg, id);
2332         uint8_t Pause;
2333         nboUnpackUByte(msg, Pause);
2334         Player* tank = lookupPlayer(id);
2335         if (!tank)
2336             break;
2337 
2338         tank->setPausedMessageState(Pause == 0);
2339         addMessage(tank, Pause ? "has paused" : "has unpaused" );
2340         break;
2341     }
2342 
2343     case MsgKilled:
2344     {
2345         PlayerId victim, killer;
2346         FlagType* flagType;
2347         int16_t shotId, reason;
2348         int phydrv = -1;
2349         msg = nboUnpackUByte(msg, victim);
2350         msg = nboUnpackUByte(msg, killer);
2351         msg = nboUnpackShort(msg, reason);
2352         msg = nboUnpackShort(msg, shotId);
2353         msg = FlagType::unpack(msg, flagType);
2354         if (reason == (int16_t)PhysicsDriverDeath)
2355         {
2356             int32_t inPhyDrv;
2357             msg = nboUnpackInt(msg, inPhyDrv);
2358             phydrv = int(inPhyDrv);
2359         }
2360         BaseLocalPlayer* victimLocal = getLocalPlayer(victim);
2361         BaseLocalPlayer* killerLocal = getLocalPlayer(killer);
2362         Player* victimPlayer = lookupPlayer(victim);
2363         Player* killerPlayer = lookupPlayer(killer);
2364 
2365         if (victimPlayer)
2366             victimPlayer->reportedHits++;
2367 #ifdef ROBOT
2368         if (victimPlayer == myTank)
2369         {
2370             // uh oh, i'm dead
2371             if (myTank->isAlive())
2372             {
2373                 serverLink->sendDropFlag(myTank->getPosition());
2374                 handleMyTankKilled(reason);
2375             }
2376         }
2377 #endif
2378         if (victimLocal)
2379         {
2380             // uh oh, local player is dead
2381             if (victimLocal->isAlive())
2382                 gotBlowedUp(victimLocal, GotKilledMsg, killer);
2383         }
2384         else if (victimPlayer)
2385         {
2386             victimPlayer->setExplode(TimeKeeper::getTick());
2387             const float* pos = victimPlayer->getPosition();
2388             const bool localView = isViewTank(victimPlayer);
2389             if (reason == GotRunOver)
2390                 playSound(SFX_RUNOVER, pos, killerLocal == myTank, localView);
2391             else
2392                 playSound(SFX_EXPLOSION, pos, killerLocal == myTank, localView);
2393             float explodePos[3];
2394             explodePos[0] = pos[0];
2395             explodePos[1] = pos[1];
2396             explodePos[2] = pos[2] + victimPlayer->getMuzzleHeight();
2397 
2398             // TODO hook this back up for 2.4.4 or later
2399             TankDeathOverride* death = NULL;
2400             EFFECTS.addDeathEffect(victimPlayer->getColor(), pos, victimPlayer->getAngle(),reason,victimPlayer,flagType);
2401 
2402             victimPlayer->setDeathEffect(death);
2403 
2404             if (!death || death->ShowExplosion())
2405                 addTankExplosion(explodePos);
2406         }
2407 
2408         if (killerLocal)
2409         {
2410             // local player did it
2411             if (shotId >= 0)
2412             {
2413                 // terminate the shot
2414                 killerLocal->endShot(shotId, true);
2415             }
2416             if (victimPlayer && killerLocal != victimPlayer)
2417             {
2418                 if ((victimPlayer->getTeam() == killerLocal->getTeam()) &&
2419                         (killerLocal->getTeam() != RogueTeam) && !(killerPlayer == myTank && wasRabbit)
2420                         && World::getWorld()->allowTeams())
2421                 {
2422                     // teamkill
2423                     if (killerPlayer == myTank)
2424                     {
2425                         hud->setAlert(1, "Don't kill teammates!!!", 3.0f, true);
2426                         playLocalSound(SFX_KILL_TEAM);
2427                         sendMeaCulpa(victimPlayer->getId());
2428                     }
2429                 }
2430                 else
2431                 {
2432                     // enemy
2433                     if (myTank->isAutoPilot())
2434                     {
2435                         if (killerPlayer)
2436                         {
2437                             const ShotPath* shot = killerPlayer->getShot(int(shotId));
2438                             if (shot != NULL)
2439                                 teachAutoPilot(shot->getFlag(), 1);
2440                         }
2441                     }
2442                 }
2443             }
2444         }
2445         else if (killerPlayer)
2446         {
2447             const ShotPath* shot = killerPlayer->getShot(int(shotId));
2448             if (shot && !shot->isStoppedByHit())
2449                 killerPlayer->addHitToStats(shot->getFlag());
2450         }
2451 
2452         // handle my personal score against other players
2453         if ((killerPlayer == myTank || victimPlayer == myTank) &&
2454                 !(killerPlayer == myTank && victimPlayer == myTank))
2455         {
2456             if (killerLocal == myTank)
2457             {
2458                 if (victimPlayer)
2459                     victimPlayer->changeLocalScore(1, 0, 0);
2460                 myTank->setNemesis(victimPlayer);
2461             }
2462             else
2463             {
2464                 if (killerPlayer)
2465                     killerPlayer->changeLocalScore(0, 1, killerPlayer->getTeam() == victimPlayer->getTeam() ? 1 : 0);
2466                 myTank->setNemesis(killerPlayer);
2467             }
2468         }
2469 
2470         // handle self-destructions
2471         if (killerPlayer == victimPlayer && killerPlayer)
2472             killerPlayer->changeSelfKills(1);
2473 
2474         // add message
2475         if (human && victimPlayer)
2476         {
2477             std::string message(ColorStrings[WhiteColor]);
2478             if (killerPlayer == victimPlayer)
2479             {
2480                 message += "blew myself up";
2481                 addMessage(victimPlayer, message);
2482             }
2483             else if (killer >= LastRealPlayer)
2484             {
2485                 message += "destroyed by the server";
2486                 addMessage(victimPlayer, message);
2487             }
2488             else if (!killerPlayer)
2489             {
2490                 message += "destroyed by a (GHOST)";
2491                 addMessage(victimPlayer, message);
2492             }
2493             else if (reason == WaterDeath)
2494             {
2495                 message += "fell in the water";
2496                 addMessage(victimPlayer, message);
2497             }
2498             else if (reason == PhysicsDriverDeath)
2499             {
2500                 const PhysicsDriver* driver = PHYDRVMGR.getDriver(phydrv);
2501                 if (driver == NULL)
2502                     message += "Unknown Deadly Obstacle";
2503                 else
2504                     message += driver->getDeathMsg();
2505                 addMessage(victimPlayer, message);
2506             }
2507             else
2508             {
2509                 std::string playerStr;
2510                 if (World::getWorld()->allowTeams() && (killerPlayer->getTeam() == victimPlayer->getTeam()) &&
2511                         (killerPlayer->getTeam() != RogueTeam) &&
2512                         (killerPlayer->getTeam() != ObserverTeam))
2513                     playerStr += "teammate ";
2514                 if (victimPlayer == myTank)
2515                 {
2516                     if (BZDB.get("killerhighlight") == "1")
2517                         playerStr += ColorStrings[PulsatingColor];
2518                     else if (BZDB.get("killerhighlight") == "2")
2519                         playerStr += ColorStrings[UnderlineColor];
2520                 }
2521                 TeamColor color = killerPlayer->getTeam();
2522                 playerStr += Team::getAnsiCode(color);
2523                 playerStr += killerPlayer->getCallSign();
2524 
2525                 if (victimPlayer == myTank)
2526                     playerStr += ColorStrings[ResetColor];
2527                 playerStr += ColorStrings[WhiteColor];
2528 
2529                 // Give more informative kill messages
2530                 if (flagType == Flags::Laser)
2531                     message += "was fried by " + playerStr + "'s laser";
2532                 else if (flagType == Flags::GuidedMissile)
2533                     message += "was destroyed by " + playerStr + "'s guided missile";
2534                 else if (flagType == Flags::ShockWave)
2535                     message += "felt the effects of " + playerStr + "'s shockwave";
2536                 else if (flagType == Flags::InvisibleBullet)
2537                     message += "didn't see " + playerStr + "'s bullet";
2538                 else if (flagType == Flags::MachineGun)
2539                     message += "was turned into swiss cheese by " + playerStr + "'s machine gun";
2540                 else if (flagType == Flags::SuperBullet)
2541                     message += "got skewered by " + playerStr + "'s super bullet";
2542                 else
2543                     message += "killed by " + playerStr;
2544                 addMessage(victimPlayer, message, 3, killerPlayer==myTank);
2545             }
2546         }
2547 
2548         if (World::getWorld()->allowTeams())  // geno only works in team games :)
2549         {
2550             // blow up if killer has genocide flag and i'm on same team as victim
2551             // (and we're not rogues, unless in rabbit mode)
2552             if (human && killerPlayer && victimPlayer && victimPlayer != myTank &&
2553                     (victimPlayer->getTeam() == myTank->getTeam()) &&
2554                     (myTank->getTeam() != RogueTeam) && shotId >= 0)
2555             {
2556                 // now see if shot was fired with a GenocideFlag
2557                 const ShotPath* shot = killerPlayer->getShot(int(shotId));
2558                 if (shot && shot->getFlag() == Flags::Genocide)
2559                     gotBlowedUp(myTank, GenocideEffect, killerPlayer->getId());
2560             }
2561 
2562 #ifdef ROBOT
2563             // blow up robots on victim's team if shot was genocide
2564             if (killerPlayer && victimPlayer && shotId >= 0)
2565             {
2566                 const ShotPath* shot = killerPlayer->getShot(int(shotId));
2567                 if (shot && shot->getFlag() == Flags::Genocide)
2568                     for (int i = 0; i < numRobots; i++)
2569                         if (robots[i] && victimPlayer != robots[i] &&
2570                                 victimPlayer->getTeam() == robots[i]->getTeam() &&
2571                                 robots[i]->getTeam() != RogueTeam)
2572                             gotBlowedUp(robots[i], GenocideEffect, killerPlayer->getId());
2573             }
2574 #endif
2575         }
2576 
2577         checkScores = true;
2578         break;
2579     }
2580 
2581     case MsgGrabFlag:
2582     {
2583         // ROBOT -- FIXME -- robots don't grab flag at the moment
2584         PlayerId id;
2585         uint16_t flagIndex;
2586         msg = nboUnpackUByte(msg, id);
2587         msg = nboUnpackUShort(msg, flagIndex);
2588         msg = world->getFlag(int(flagIndex)).unpack(msg);
2589         Player* tank = lookupPlayer(id);
2590         if (!tank) break;
2591 
2592         // player now has flag
2593         tank->setFlag(world->getFlag(flagIndex).type);
2594         if (tank == myTank)
2595         {
2596             // grabbed flag
2597             playLocalSound(myTank->getFlag()->endurance != FlagSticky ?
2598                            SFX_GRAB_FLAG : SFX_GRAB_BAD);
2599             updateFlag(myTank->getFlag());
2600         }
2601         else if (isViewTank(tank))
2602         {
2603             playLocalSound(tank->getFlag()->endurance != FlagSticky ?
2604                            SFX_GRAB_FLAG : SFX_GRAB_BAD);
2605         }
2606         else if (myTank->getTeam() != RabbitTeam && tank &&
2607                  tank->getTeam() != myTank->getTeam() &&
2608                  world->getFlag(flagIndex).type->flagTeam == myTank->getTeam())
2609         {
2610             hud->setAlert(1, "Flag Alert!!!", 3.0f, true);
2611             playLocalSound(SFX_ALERT);
2612         }
2613         else
2614         {
2615             FlagType* fd = world->getFlag(flagIndex).type;
2616             if ( fd->flagTeam != NoTeam
2617                     && fd->flagTeam != tank->getTeam()
2618                     && ((tank && (tank->getTeam() == myTank->getTeam())))
2619                     && (Team::isColorTeam(myTank->getTeam())))
2620             {
2621                 hud->setAlert(1, "Team Grab!!!", 3.0f, false);
2622                 const float* pos = tank->getPosition();
2623                 playWorldSound(SFX_TEAMGRAB, pos, false);
2624             }
2625         }
2626         std::string message("grabbed ");
2627         message += tank->getFlag()->flagName;
2628         message += " flag";
2629         addMessage(tank, message);
2630         break;
2631     }
2632 
2633     case MsgDropFlag:
2634     {
2635         PlayerId id;
2636         uint16_t flagIndex;
2637         msg = nboUnpackUByte(msg, id);
2638         msg = nboUnpackUShort(msg, flagIndex);
2639         msg = world->getFlag(int(flagIndex)).unpack(msg);
2640         Player* tank = lookupPlayer(id);
2641         if (!tank) break;
2642         handleFlagDropped(tank);
2643         break;
2644     }
2645 
2646     case MsgCaptureFlag:
2647     {
2648         PlayerId id;
2649         uint16_t flagIndex, team;
2650         msg = nboUnpackUByte(msg, id);
2651         msg = nboUnpackUShort(msg, flagIndex);
2652         msg = nboUnpackUShort(msg, team);
2653         Player* capturer = lookupPlayer(id);
2654         if (flagIndex >= world->getMaxFlags())
2655             break;
2656         Flag capturedFlag = world->getFlag(int(flagIndex));
2657         if (capturedFlag.type == Flags::Null)
2658             break;
2659         int capturedTeam = world->getFlag(int(flagIndex)).type->flagTeam;
2660 
2661         // player no longer has flag
2662         if (capturer)
2663         {
2664             capturer->setFlag(Flags::Null);
2665             if (capturer == myTank)
2666                 updateFlag(Flags::Null);
2667 
2668             // add message
2669             if (int(capturer->getTeam()) == capturedTeam)
2670             {
2671                 std::string message("took my flag into ");
2672                 message += Team::getName(TeamColor(team));
2673                 message += " territory";
2674                 addMessage(capturer, message);
2675                 if (capturer == myTank)
2676                 {
2677                     hud->setAlert(1, "Don't capture your own flag!!!", 3.0f, true);
2678                     playLocalSound( SFX_KILL_TEAM );
2679                     sendMeaCulpa(TeamToPlayerId(myTank->getTeam()));
2680                 }
2681             }
2682             else
2683             {
2684                 std::string message("captured ");
2685                 message += Team::getName(TeamColor(capturedTeam));
2686                 message += "'s flag";
2687                 addMessage(capturer, message);
2688             }
2689         }
2690 
2691         // play sound -- if my team is same as captured flag then my team lost,
2692         // but if I'm on the same team as the capturer then my team won.
2693         if (capturedTeam == int(myTank->getTeam()))
2694             playLocalSound(SFX_LOSE);
2695         else if (capturer && capturer->getTeam() == myTank->getTeam())
2696             playLocalSound(SFX_CAPTURE);
2697 
2698 
2699         // blow up if my team flag captured
2700         if (capturedTeam == int(myTank->getTeam()))
2701             gotBlowedUp(myTank, GotCaptured, id);
2702 #ifdef ROBOT
2703         //kill all my robots if they are on the captured team
2704         for (int r = 0; r < numRobots; r++)
2705         {
2706             if (robots[r] && robots[r]->getTeam() == capturedTeam)
2707                 gotBlowedUp(robots[r], GotCaptured, robots[r]->getId());
2708         }
2709 #endif
2710 
2711         // everybody who's alive on capture team will be blowing up
2712         // but we're not going to get an individual notification for
2713         // each of them, so add an explosion for each now.  don't
2714         // include me, though;  I already blew myself up.
2715         for (int i = 0; i < curMaxPlayers; i++)
2716         {
2717             if (remotePlayers[i] &&
2718                     remotePlayers[i]->isAlive() &&
2719                     remotePlayers[i]->getTeam() == capturedTeam)
2720             {
2721                 const float* pos = remotePlayers[i]->getPosition();
2722                 playWorldSound(SFX_EXPLOSION, pos, false);
2723                 float explodePos[3];
2724                 explodePos[0] = pos[0];
2725                 explodePos[1] = pos[1];
2726                 explodePos[2] = pos[2] + remotePlayers[i]->getMuzzleHeight();
2727 
2728                 // todo hook this back up for 2.4.4. or later
2729                 TankDeathOverride *death = NULL;
2730                 EFFECTS.addDeathEffect(remotePlayers[i]->getColor(), pos, remotePlayers[i]->getAngle(),GotCaptured,remotePlayers[i],
2731                                        NULL);
2732 
2733                 remotePlayers[i]->setDeathEffect(death);
2734 
2735                 if (!death || death->ShowExplosion())
2736                     addTankExplosion(explodePos);
2737             }
2738         }
2739 
2740         checkScores = true;
2741         break;
2742     }
2743 
2744     case MsgNewRabbit:
2745     {
2746         PlayerId id;
2747         msg = nboUnpackUByte(msg, id);
2748         Player *rabbit = lookupPlayer(id);
2749 
2750         for (int i = 0; i < curMaxPlayers; i++)
2751         {
2752             if (remotePlayers[i])
2753                 remotePlayers[i]->setHunted(false);
2754             if (i != id && remotePlayers[i] && remotePlayers[i]->getTeam() != HunterTeam
2755                     && remotePlayers[i]->getTeam() != ObserverTeam)
2756                 remotePlayers[i]->changeTeam(HunterTeam);
2757         }
2758 
2759         if (rabbit != NULL)
2760         {
2761             rabbit->changeTeam(RabbitTeam);
2762             if (rabbit == myTank)
2763             {
2764                 wasRabbit = true;
2765                 if (myTank->isPaused())
2766                     serverLink->sendNewRabbit();
2767                 else
2768                 {
2769                     hud->setAlert(0, "You are now the rabbit.", 10.0f, false);
2770                     playLocalSound(SFX_HUNT_SELECT);
2771                 }
2772                 scoreboard->setHuntState(ScoreboardRenderer::HUNT_NONE);
2773             }
2774             else if (myTank->getTeam() != ObserverTeam)
2775             {
2776                 myTank->changeTeam(HunterTeam);
2777                 if (myTank->isPaused() || myTank->isAlive())
2778                     wasRabbit = false;
2779                 rabbit->setHunted(true);
2780                 scoreboard->setHuntState(ScoreboardRenderer::HUNT_ENABLED);
2781             }
2782 
2783             addMessage(rabbit, "is now the rabbit", 3, true);
2784         }
2785 
2786 #ifdef ROBOT
2787         for (int r = 0; r < numRobots; r++)
2788             if (robots[r])
2789             {
2790                 if (robots[r]->getId() == id)
2791                     robots[r]->changeTeam(RabbitTeam);
2792                 else
2793                     robots[r]->changeTeam(HunterTeam);
2794             }
2795 #endif
2796         break;
2797     }
2798 
2799     case MsgShotBegin:
2800     {
2801         FiringInfo firingInfo;
2802         msg = firingInfo.unpack(msg);
2803 
2804         const int shooterid = firingInfo.shot.player;
2805         RemotePlayer* shooter = remotePlayers[shooterid];
2806 
2807         if (shooterid != ServerPlayer)
2808         {
2809             if (shooter && remotePlayers[shooterid]->getId() == shooterid)
2810             {
2811                 shooter->addShot(firingInfo);
2812 
2813                 if (SceneRenderer::instance().useQuality() >= 2)
2814                 {
2815                     float shotPos[3];
2816                     shooter->getMuzzle(shotPos);
2817 
2818                     // if you are driving with a tank in observer mode
2819                     // and do not want local shot effects,
2820                     // disable shot effects for that specific tank
2821                     if ((ROAM.getMode() != Roaming::roamViewFP)
2822                             || (!ROAM.getTargetTank())
2823                             || (shooterid != ROAM.getTargetTank()->getId())
2824                             || BZDB.isTrue("enableLocalShotEffect"))
2825                     {
2826                         EFFECTS.addShotEffect(shooter->getColor(), shotPos,
2827                                               shooter->getAngle(),
2828                                               shooter->getVelocity());
2829                     }
2830                 }
2831             }
2832             else
2833                 break;
2834         }
2835         else
2836             World::getWorld()->getWorldWeapons()->addShot(firingInfo);
2837 
2838         if (human)
2839         {
2840             const float* pos = firingInfo.shot.pos;
2841             const bool importance = false;
2842             const bool localSound = isViewTank(shooter);
2843             if (firingInfo.flagType == Flags::ShockWave)
2844                 playSound(SFX_SHOCK, pos, importance, localSound);
2845             else if (firingInfo.flagType == Flags::Laser)
2846                 playSound(SFX_LASER, pos, importance, localSound);
2847             else if (firingInfo.flagType == Flags::GuidedMissile)
2848                 playSound(SFX_MISSILE, pos, importance, localSound);
2849             else if (firingInfo.flagType == Flags::Thief)
2850                 playSound(SFX_THIEF, pos, importance, localSound);
2851             else
2852                 playSound(SFX_FIRE, pos, importance, localSound);
2853         }
2854         break;
2855     }
2856 
2857     case MsgShotEnd:
2858     {
2859         PlayerId id;
2860         int16_t shotId;
2861         uint16_t reason;
2862         msg = nboUnpackUByte(msg, id);
2863         msg = nboUnpackShort(msg, shotId);
2864         msg = nboUnpackUShort(msg, reason);
2865         BaseLocalPlayer* localPlayer = getLocalPlayer(id);
2866 
2867         if (localPlayer)
2868             localPlayer->endShot(int(shotId), false, reason == 0);
2869         else
2870         {
2871             Player *pl = lookupPlayer(id);
2872             if (pl)
2873                 pl->endShot(int(shotId), false, reason == 0);
2874         }
2875         break;
2876     }
2877 
2878     case MsgHandicap:
2879     {
2880         PlayerId id;
2881         uint8_t numHandicaps;
2882         int16_t handicap;
2883         msg = nboUnpackUByte(msg, numHandicaps);
2884         for (uint8_t s = 0; s < numHandicaps; s++)
2885         {
2886             msg = nboUnpackUByte(msg, id);
2887             msg = nboUnpackShort(msg, handicap);
2888 
2889             Player *sPlayer = getLocalPlayer(id);
2890             if (!sPlayer)
2891             {
2892                 int i = lookupPlayerIndex(id);
2893                 if (i >= 0)
2894                     sPlayer = remotePlayers[i];
2895                 else
2896                     logDebugMessage(1, "Received handicap update for unknown player!\n");
2897             }
2898             if (sPlayer)
2899             {
2900                 // a relative score of -_handicapScoreDiff points will provide maximum handicap
2901                 float normalizedHandicap = float(handicap)
2902                                            / std::max(1.0f, BZDB.eval(StateDatabase::BZDB_HANDICAPSCOREDIFF));
2903 
2904                 /* limit how much of a handicap is afforded, and only provide
2905                  * handicap advantages instead of disadvantages.
2906                  */
2907                 if (normalizedHandicap > 1.0f)
2908                     // advantage
2909                     normalizedHandicap  = 1.0f;
2910                 else if (normalizedHandicap < 0.0f)
2911                     // disadvantage
2912                     normalizedHandicap  = 0.0f;
2913 
2914                 sPlayer->setHandicap(normalizedHandicap);
2915             }
2916         }
2917         break;
2918     }
2919 
2920     case MsgScore:
2921     {
2922         uint8_t numScores;
2923         PlayerId id;
2924         uint16_t wins, losses, tks;
2925         msg = nboUnpackUByte(msg, numScores);
2926 
2927         for (uint8_t s = 0; s < numScores; s++)
2928         {
2929             msg = nboUnpackUByte(msg, id);
2930             msg = nboUnpackUShort(msg, wins);
2931             msg = nboUnpackUShort(msg, losses);
2932             msg = nboUnpackUShort(msg, tks);
2933 
2934             Player *sPlayer = NULL;
2935             if (id == myTank->getId())
2936                 sPlayer = myTank;
2937             else
2938             {
2939                 int i = lookupPlayerIndex(id);
2940                 if (i >= 0)
2941                     sPlayer = remotePlayers[i];
2942                 else
2943                     logDebugMessage(1,"Received score update for unknown player!\n");
2944             }
2945             if (sPlayer)
2946                 sPlayer->changeScore(wins - sPlayer->getWins(),
2947                                      losses - sPlayer->getLosses(),
2948                                      tks - sPlayer->getTeamKills());
2949         }
2950         break;
2951     }
2952 
2953     case MsgSetVar:
2954     {
2955         msg = handleMsgSetVars(msg);
2956         break;
2957     }
2958 
2959     case MsgTeleport:
2960     {
2961         PlayerId id;
2962         uint16_t from, to;
2963         msg = nboUnpackUByte(msg, id);
2964         msg = nboUnpackUShort(msg, from);
2965         msg = nboUnpackUShort(msg, to);
2966         Player* tank = lookupPlayer(id);
2967         if (tank && tank != myTank)
2968         {
2969             int face;
2970             const Teleporter* teleporter = world->getTeleporter(int(to), face);
2971             if (teleporter)
2972             {
2973                 const float* pos = teleporter->getPosition();
2974                 tank->setTeleport(TimeKeeper::getTick(), short(from), short(to));
2975                 playWorldSound(SFX_TELEPORT, pos);
2976             }
2977         }
2978         break;
2979     }
2980 
2981     case MsgTransferFlag:
2982     {
2983         PlayerId fromId, toId;
2984         unsigned short flagIndex;
2985         msg = nboUnpackUByte(msg, fromId);
2986         msg = nboUnpackUByte(msg, toId);
2987         msg = nboUnpackUShort(msg, flagIndex);
2988         msg = world->getFlag(int(flagIndex)).unpack(msg);
2989         Player* fromTank = lookupPlayer(fromId);
2990         Player* toTank = lookupPlayer(toId);
2991         handleFlagTransferred( fromTank, toTank, flagIndex);
2992         break;
2993     }
2994 
2995 
2996     case MsgMessage:
2997     {
2998         PlayerId src;
2999         PlayerId dst;
3000         uint8_t type;
3001         msg = nboUnpackUByte(msg, src);
3002         msg = nboUnpackUByte(msg, dst);
3003         msg = nboUnpackUByte(msg, type);
3004 
3005         // Only bother processing the message if we know how to handle it
3006         if (MessageType(type) != ChatMessage && MessageType(type) != ActionMessage)
3007             break;
3008 
3009         Player* srcPlayer = lookupPlayer(src);
3010         Player* dstPlayer = lookupPlayer(dst);
3011         TeamColor dstTeam = PlayerIdToTeam(dst);
3012         bool toAll = (dst == AllPlayers);
3013         bool fromServer = (src == ServerPlayer);
3014         bool toAdmin = (dst == AdminPlayers);
3015         std::string dstName;
3016 
3017         const std::string srcName = fromServer ? "SERVER" : (srcPlayer ? srcPlayer->getCallSign() : "(UNKNOWN)");
3018         if (dstPlayer)
3019             dstName = dstPlayer->getCallSign();
3020         else if (toAdmin)
3021             dstName = "Admin";
3022         else
3023             dstName = "(UNKNOWN)";
3024         std::string fullMsg;
3025 
3026         bool ignore = false;
3027         unsigned int i;
3028         if (!fromServer)
3029         {
3030             for (i = 0; i < silencePlayers.size(); i++)
3031             {
3032                 const std::string &silenceCallSign = silencePlayers[i];
3033                 if (srcName == silenceCallSign || "*" == silenceCallSign || ("-" == silenceCallSign && srcPlayer
3034                         && !srcPlayer->isRegistered()))
3035                 {
3036                     ignore = true;
3037                     break;
3038                 }
3039             }
3040         }
3041 
3042         if (ignore)
3043         {
3044 #ifdef DEBUG
3045             // to verify working
3046             std::string msg2 = "Ignored Msg from " + srcName;
3047             if (silencePlayers[i] == "*")
3048                 msg2 += " because all chat is ignored";
3049             else if (silencePlayers[i] == "-")
3050                 msg2 += " because chat from unregistered players is ignored";
3051             addMessage(NULL,msg2);
3052 #endif
3053             break;
3054         }
3055 
3056         std::string origText;
3057         // if filtering is turned on, filter away the goo
3058         if (wordfilter != NULL)
3059         {
3060             char filtered[MessageLen];
3061             strncpy(filtered, (const char *)msg, MessageLen - 1);
3062             filtered[MessageLen - 1] = '\0';
3063             if (wordfilter->filter(filtered))
3064                 msg = filtered;
3065             origText = stripAnsiCodes(std::string((const char*)msg));   // use filtered[] while it is in scope
3066         }
3067         else
3068             origText = stripAnsiCodes(std::string((const char*)msg));
3069 
3070         std::string text = BundleMgr::getCurrentBundle()->getLocalString(origText);
3071 
3072         if (toAll || toAdmin || srcPlayer == myTank  || dstPlayer == myTank ||
3073                 dstTeam == myTank->getTeam())
3074         {
3075             // message is for me
3076             std::string colorStr;
3077             if (srcPlayer == NULL)
3078                 colorStr += ColorStrings[RogueTeam];
3079             else
3080             {
3081                 const PlayerId pid = srcPlayer->getId();
3082                 if (pid < 200)
3083                 {
3084                     if (srcPlayer && srcPlayer->getTeam() != NoTeam)
3085                         colorStr += Team::getAnsiCode(srcPlayer->getTeam());
3086                     else
3087                         colorStr += ColorStrings[RogueTeam];
3088                 }
3089                 else if (pid == ServerPlayer)
3090                     colorStr += ColorStrings[YellowColor];
3091                 else
3092                 {
3093                     colorStr += ColorStrings[CyanColor]; // replay observers
3094                 }
3095             }
3096 
3097             fullMsg += colorStr;
3098 
3099             // direct message to or from me
3100             if (dstPlayer)
3101             {
3102                 //if (fromServer && (origText == "You are now an administrator!"
3103                 //             || origText == "Password Accepted, welcome back."))
3104                 //admin = true;
3105 
3106                 // talking to myself? that's strange
3107                 if (dstPlayer == myTank && srcPlayer == myTank)
3108                     fullMsg = text;
3109                 else
3110                 {
3111                     if (BZDB.get("killerhighlight") == "1")
3112                         fullMsg += ColorStrings[PulsatingColor];
3113                     else if (BZDB.get("killerhighlight") == "2")
3114                         fullMsg += ColorStrings[UnderlineColor];
3115 
3116                     if (srcPlayer == myTank)
3117                     {
3118                         if (MessageType(type) == ActionMessage)
3119                             fullMsg += "[->" + dstName + "][" + srcName + " " + text + "]";
3120                         else
3121                         {
3122                             fullMsg += "[->" + dstName + "]";
3123                             fullMsg += ColorStrings[ResetColor] + " ";
3124                             fullMsg += ColorStrings[CyanColor] + text;
3125                         }
3126                     }
3127                     else
3128                     {
3129                         if (MessageType(type) == ActionMessage)
3130                             fullMsg += "[" + srcName + " " + text + "]";
3131                         else
3132                         {
3133                             fullMsg += "[" + srcName + "->]";
3134                             fullMsg += ColorStrings[ResetColor] + " ";
3135                             fullMsg += ColorStrings[CyanColor] + text;
3136                         }
3137 
3138                         if (srcPlayer)
3139                             myTank->setRecipient(srcPlayer);
3140 
3141                         // play a sound on a private message not from self or server
3142 
3143                         bool playSound = !fromServer;
3144                         if (BZDB.isSet("beepOnServerMsg") && BZDB.isTrue("beepOnServerMsg"))
3145                             playSound = true;
3146 
3147                         if (playSound)
3148                         {
3149                             static TimeKeeper lastMsg = TimeKeeper::getSunGenesisTime();
3150                             if (TimeKeeper::getTick() - lastMsg > 2.0f)
3151                                 playLocalSound( SFX_MESSAGE_PRIVATE );
3152                             lastMsg = TimeKeeper::getTick();
3153                         }
3154                     }
3155                 }
3156             }
3157             else
3158             {
3159                 // team / admin message
3160                 if (toAdmin)
3161                 {
3162 
3163                     // play a sound on a private message not from self or server
3164                     if (!fromServer)
3165                     {
3166                         static TimeKeeper lastMsg = TimeKeeper::getSunGenesisTime();
3167                         if (TimeKeeper::getTick() - lastMsg > 2.0f)
3168                             playLocalSound( SFX_MESSAGE_ADMIN );
3169                         lastMsg = TimeKeeper::getTick();
3170                     }
3171 
3172                     fullMsg += "[Admin] ";
3173                 }
3174 
3175                 if (dstTeam != NoTeam)
3176                 {
3177 #ifdef BWSUPPORT
3178                     fullMsg = "[to ";
3179                     fullMsg += Team::getName(TeamColor(dstTeam));
3180                     fullMsg += "] ";
3181 #else
3182                     fullMsg += "[Team] ";
3183 #endif
3184 
3185                     // play a sound if I didn't send the message
3186                     if (srcPlayer != myTank)
3187                     {
3188                         static TimeKeeper lastMsg = TimeKeeper::getSunGenesisTime();
3189                         if (TimeKeeper::getTick() - lastMsg > 2.0f)
3190                             playLocalSound(SFX_MESSAGE_TEAM);
3191                         lastMsg = TimeKeeper::getTick();
3192                     }
3193                 }
3194 
3195                 // display action messages differently
3196                 if (MessageType(type) == ActionMessage)
3197                     fullMsg += srcName + " " + text;
3198                 else
3199                     fullMsg += srcName + colorStr + ": " + ColorStrings[CyanColor] + text;
3200             }
3201 
3202             std::string oldcolor = "";
3203             if (!srcPlayer || srcPlayer->getTeam() == NoTeam)
3204                 oldcolor = ColorStrings[RogueTeam];
3205             else if (srcPlayer->getTeam() == ObserverTeam)
3206                 oldcolor = ColorStrings[CyanColor];
3207             else
3208                 oldcolor = Team::getAnsiCode(srcPlayer->getTeam());
3209             if (fromServer)
3210                 addMessage(NULL, fullMsg, 2, false, oldcolor.c_str());
3211             else
3212                 addMessage(NULL, fullMsg, 1, false, oldcolor.c_str());
3213 
3214             if (!srcPlayer || srcPlayer!=myTank)
3215                 hud->setAlert(0, fullMsg.c_str(), 3.0f, false);
3216         }
3217         break;
3218     }
3219 
3220     case MsgReplayReset:
3221     {
3222         int i;
3223         unsigned char lastPlayer;
3224         msg = nboUnpackUByte(msg, lastPlayer);
3225 
3226         // remove players up to 'lastPlayer'
3227         // any PlayerId above lastPlayer is a replay observers
3228         for (i=0 ; i<lastPlayer ; i++)
3229         {
3230             if (removePlayer (i))
3231                 checkScores = true;
3232         }
3233 
3234         // remove all of the flags
3235         for (i=0 ; i<numFlags; i++)
3236         {
3237             Flag& flag = world->getFlag(i);
3238             flag.owner = (PlayerId) -1;
3239             flag.status = FlagNoExist;
3240             world->initFlag (i);
3241         }
3242         break;
3243     }
3244 
3245     case MsgAdminInfo:
3246     {
3247         uint8_t numIPs;
3248         msg = nboUnpackUByte(msg, numIPs);
3249 
3250         /* if we're getting this, we have playerlist perm */
3251         myTank->setPlayerList(true);
3252 
3253         // replacement for the normal MsgAddPlayer message
3254         if (numIPs == 1)
3255         {
3256             uint8_t ipsize;
3257             uint8_t index;
3258             Address ip;
3259             const void* tmpMsg = msg; // leave 'msg' pointing at the first entry
3260 
3261             tmpMsg = nboUnpackUByte(tmpMsg, ipsize);
3262             tmpMsg = nboUnpackUByte(tmpMsg, index);
3263             tmpMsg = ip.unpack(tmpMsg);
3264             int playerIndex = lookupPlayerIndex(index);
3265             Player* tank = getPlayerByIndex(playerIndex);
3266             if (!tank)
3267                 break;
3268 
3269             std::string message("joining as ");
3270             if (tank->getTeam() == ObserverTeam)
3271                 message += "an observer";
3272             else
3273             {
3274                 switch (tank->getPlayerType())
3275                 {
3276                 case TankPlayer:
3277                     message += "a tank";
3278                     break;
3279                 case ComputerPlayer:
3280                     message += "a robot tank";
3281                     break;
3282                 default:
3283                     message += "an unknown type";
3284                     break;
3285                 }
3286             }
3287             message += " from " + ip.getDotNotation();
3288             tank->setIpAddress(ip);
3289             addMessage(tank, message);
3290         }
3291 
3292         // print fancy version to be easily found
3293         if ((numIPs != 1) || (BZDB.evalInt("showips") > 0))
3294         {
3295             uint8_t playerId;
3296             uint8_t addrlen;
3297             Address addr;
3298 
3299             for (int i = 0; i < numIPs; i++)
3300             {
3301                 msg = nboUnpackUByte(msg, addrlen);
3302                 msg = nboUnpackUByte(msg, playerId);
3303                 msg = addr.unpack(msg);
3304 
3305                 int playerIndex = lookupPlayerIndex(playerId);
3306                 Player *_player = getPlayerByIndex(playerIndex);
3307                 if (!_player) continue;
3308                 printIpInfo(_player, addr, "(join)");
3309                 _player->setIpAddress(addr); // save for the signoff message
3310             } // end for loop
3311         }
3312         break;
3313     }
3314 
3315     case MsgFlagType:
3316     {
3317         FlagType* typ = NULL;
3318         FlagType::unpackCustom(msg, typ);
3319         logDebugMessage(1, "Got custom flag type from server: %s\n", typ->information().c_str());
3320         break;
3321     }
3322 
3323     case MsgPlayerInfo:
3324     {
3325         uint8_t numPlayers;
3326         int i;
3327         msg = nboUnpackUByte(msg, numPlayers);
3328         for (i = 0; i < numPlayers; ++i)
3329         {
3330             PlayerId id;
3331             msg = nboUnpackUByte(msg, id);
3332             Player *p = lookupPlayer(id);
3333             uint8_t info;
3334             // parse player info bitfield
3335             msg = nboUnpackUByte(msg, info);
3336             if (!p)
3337                 continue;
3338             p->setAdmin((info & IsAdmin) != 0);
3339             p->setRegistered((info & IsRegistered) != 0);
3340             p->setVerified((info & IsVerified) != 0);
3341         }
3342         break;
3343     }
3344 
3345     // inter-player relayed message
3346     case MsgPlayerUpdate:
3347     case MsgPlayerUpdateSmall:
3348     case MsgGMUpdate:
3349     case MsgLagPing:
3350         handlePlayerMessage(code, 0, msg);
3351         break;
3352     }
3353 
3354     if (checkScores) updateHighScores();
3355 }
3356 
3357 //
3358 // player message handling
3359 //
3360 
handlePlayerMessage(uint16_t code,uint16_t,const void * msg)3361 static void     handlePlayerMessage(uint16_t code, uint16_t,
3362                                     const void* msg)
3363 {
3364     switch (code)
3365     {
3366     case MsgPlayerUpdate:
3367     case MsgPlayerUpdateSmall:
3368     {
3369         float timestamp; // could be used to enhance deadreckoning, but isn't for now
3370         PlayerId id;
3371         int32_t order;
3372         const void *buf = msg;
3373         buf = nboUnpackFloat(buf, timestamp);
3374         buf = nboUnpackUByte(buf, id);
3375         Player* tank = lookupPlayer(id);
3376         if (!tank || tank == myTank) break;
3377         nboUnpackInt(buf, order); // peek! don't update the msg pointer
3378         if (order <= tank->getOrder()) break;
3379         short oldStatus = tank->getStatus();
3380         tank->unpack(msg, code);
3381         short newStatus = tank->getStatus();
3382         if ((oldStatus & short(PlayerState::Paused)) !=
3383                 (newStatus & short(PlayerState::Paused)))
3384             addMessage(tank, (tank->getStatus() & PlayerState::Paused) ?
3385                        "Paused" : "Resumed");
3386         if ((oldStatus & short(PlayerState::Exploding)) == 0 &&
3387                 (newStatus & short(PlayerState::Exploding)) != 0)
3388         {
3389             // player has started exploding and we haven't gotten killed
3390             // message yet -- set explosion now, play sound later (when we
3391             // get killed message).  status is already !Alive so make player
3392             // alive again, then call setExplode to kill him.
3393             tank->setStatus(newStatus | short(PlayerState::Alive));
3394             tank->setExplode(TimeKeeper::getTick());
3395             // ROBOT -- play explosion now
3396         }
3397         break;
3398     }
3399 
3400     case MsgGMUpdate:
3401     {
3402         ShotUpdate shot;
3403         msg = shot.unpack(msg);
3404         Player* tank = lookupPlayer(shot.player);
3405         if (!tank || tank == myTank) break;
3406         RemotePlayer* remoteTank = (RemotePlayer*)tank;
3407         RemoteShotPath* shotPath =
3408             (RemoteShotPath*)remoteTank->getShot(shot.id);
3409         if (shotPath) shotPath->update(shot, code, msg);
3410         PlayerId targetId;
3411         msg = nboUnpackUByte(msg, targetId);
3412         Player* targetTank = lookupPlayer(targetId);
3413         if (targetTank && (targetTank == myTank) && (myTank->isAlive()))
3414         {
3415             static TimeKeeper lastLockMsg;
3416             if (TimeKeeper::getTick() - lastLockMsg > 0.75)
3417             {
3418                 playWorldSound(SFX_LOCK, shot.pos);
3419                 lastLockMsg=TimeKeeper::getTick();
3420                 addMessage(tank, "locked on me");
3421             }
3422         }
3423         break;
3424     }
3425 
3426     // just echo lag ping message
3427     case MsgLagPing:
3428         serverLink->send(MsgLagPing,2,msg);
3429         break;
3430     }
3431 }
3432 
3433 //
3434 // message handling
3435 //
3436 
doMessages()3437 static void     doMessages()
3438 {
3439     char msg[MaxPacketLen];
3440     uint16_t code, len;
3441     int e = 0;
3442     // handle server messages
3443     if (serverLink)
3444     {
3445 
3446         while (!serverError && (e = serverLink->read(code, len, msg, 0)) == 1)
3447             handleServerMessage(true, code, len, msg);
3448         if (e == -2)
3449         {
3450             printError("Server communication error");
3451             serverError = true;
3452             return;
3453         }
3454     }
3455 
3456 #ifdef ROBOT
3457     for (int i = 0; i < numRobots; i++)
3458     {
3459         while (robotServer[i] && robotServer[i]->read(code, len, msg, 0) == 1)
3460             ;
3461         if (code == MsgKilled || code == MsgShotBegin || code == MsgShotEnd)
3462             handleServerMessage(false, code, len, msg);
3463     }
3464 #endif
3465 }
3466 
updateFlags(float dt)3467 static void     updateFlags(float dt)
3468 {
3469     for (int i = 0; i < numFlags; i++)
3470     {
3471         Flag& flag = world->getFlag(i);
3472         if (flag.status == FlagOnTank)
3473         {
3474             // position flag on top of tank
3475             Player* tank = lookupPlayer(flag.owner);
3476             if (tank)
3477             {
3478                 const float* pos = tank->getPosition();
3479                 flag.position[0] = pos[0];
3480                 flag.position[1] = pos[1];
3481                 flag.position[2] = pos[2] + tank->getDimensions()[2];
3482             }
3483         }
3484         world->updateFlag(i, dt);
3485     }
3486     FlagSceneNode::waveFlag(dt);
3487 }
3488 
addExplosion(const float * _pos,float size,float duration,bool grounded)3489 bool            addExplosion(const float* _pos,
3490                              float size, float duration, bool grounded)
3491 {
3492     // ignore if no prototypes available;
3493     if (prototypeExplosions.empty())
3494         return false;
3495 
3496     // don't show explosions if quality is low
3497     if (sceneRenderer->useQuality() < 1) return false;
3498 
3499     // don't add explosion if blending or texture mapping are off
3500     if (!BZDBCache::blend || !BZDBCache::texture)
3501         return false;
3502 
3503     // pick a random prototype explosion
3504     const int index = (int)(bzfrand() * (float)prototypeExplosions.size());
3505 
3506     // make a copy and initialize it
3507     BillboardSceneNode* newExplosion = prototypeExplosions[index]->copy();
3508     GLfloat pos[3];
3509     pos[0] = _pos[0];
3510     pos[1] = _pos[1];
3511     pos[2] = _pos[2];
3512     newExplosion->move(pos);
3513     newExplosion->setSize(size);
3514     newExplosion->setDuration(duration);
3515     newExplosion->setAngle((float)(2.0 * M_PI * bzfrand()));
3516     newExplosion->setLight();
3517     newExplosion->setLightColor(1.0f, 0.8f, 0.5f);
3518     newExplosion->setLightAttenuation(0.05f, 0.0f, 0.03f);
3519     newExplosion->setLightScaling(size / BZDBCache::tankLength);
3520     newExplosion->setLightFadeStartTime(0.7f * duration);
3521     if (grounded)
3522         newExplosion->setGroundLight(true);
3523 
3524     // add copy to list of current explosions
3525     explosions.push_back(newExplosion);
3526 
3527     // the rest of the stuff is for tank explosions
3528     if (size < (3.0f * BZDBCache::tankLength))
3529         return true;
3530 
3531     // bring on the noise, a tank blew up
3532     int boom = (int) (bzfrand() * 8.0) + 3;
3533     const float lightGain = (float)boom + 1.0f;
3534 
3535     // turn up the volume
3536     newExplosion->setLightColor(1.0f * lightGain,
3537                                 0.8f * lightGain,
3538                                 0.5f * lightGain);
3539     while (boom--)
3540     {
3541         // pick a random prototype explosion
3542         const int idx = (int)(bzfrand() * (float)prototypeExplosions.size());
3543 
3544         // make a copy and initialize it
3545         BillboardSceneNode* newExpl = prototypeExplosions[idx]->copy();
3546         GLfloat explPos[3];
3547         explPos[0] = _pos[0]+(float)(bzfrand()*12.0 - 6.0);
3548         explPos[1] = _pos[1]+(float)(bzfrand()*12.0 - 6.0);
3549         explPos[2] = _pos[2]+(float)(bzfrand()*10.0);
3550         newExpl->move(explPos);
3551         newExpl->setSize(size);
3552         newExpl->setDuration(duration);
3553         newExpl->setAngle((float)(2.0 * M_PI * bzfrand()));
3554 
3555         // add copy to list of current explosions
3556         explosions.push_back(newExpl);
3557     }
3558 
3559     return true;
3560 }
3561 
addTankExplosion(const float * pos)3562 void            addTankExplosion(const float* pos)
3563 {
3564     addExplosion(pos, BZDB.eval(StateDatabase::BZDB_TANKEXPLOSIONSIZE), 1.2f, false);
3565 }
3566 
addShotExplosion(const float * pos)3567 void            addShotExplosion(const float* pos)
3568 {
3569     // only play explosion sound if you see an explosion
3570     if (addExplosion(pos, 1.2f * BZDBCache::tankLength, 0.8f, false))
3571         playWorldSound(SFX_SHOT_BOOM, pos);
3572 }
3573 
addShotPuff(const float * pos,float azimuth,float elevation)3574 void            addShotPuff(const float* pos, float azimuth, float elevation)
3575 {
3576     bool useClasicPuff  = false;
3577 
3578     if (BZDB.evalInt("gmPuffEffect") == 1)
3579         useClasicPuff = true;
3580 
3581     if (useClasicPuff)
3582     {
3583         addExplosion(pos, 0.3f * BZDBCache::tankLength, 0.8f, true);
3584         return;
3585     }
3586 
3587     float rots[2] = {azimuth,elevation};
3588     EFFECTS.addGMPuffEffect(pos, rots, NULL);
3589 }
3590 
3591 // process pending input events
processInputEvents(float maxProcessingTime)3592 void           processInputEvents(float maxProcessingTime)
3593 {
3594     if (mainWindow && display)
3595     {
3596         TimeKeeper start = TimeKeeper::getCurrent();
3597         while (display->isEventPending() &&
3598                 !CommandsStandard::isQuit() &&
3599                 (TimeKeeper::getCurrent() - start < maxProcessingTime))
3600         {
3601             // process one event
3602             doEvent(display);
3603         }
3604     }
3605 }
3606 
updateExplosions(float dt)3607 static void     updateExplosions(float dt)
3608 {
3609     // update time of all explosions
3610     int i;
3611     const int count = explosions.size();
3612     for (i = 0; i < count; i++)
3613         explosions[i]->updateTime(dt);
3614 
3615     // reap expired explosions
3616     for (i = count - 1; i >= 0; i--)
3617     {
3618         if (explosions[i]->isAtEnd())
3619         {
3620             delete explosions[i];
3621             std::vector<BillboardSceneNode*>::iterator it = explosions.begin();
3622             for (int j = 0; j < i; j++)
3623                 ++it;
3624             explosions.erase(it);
3625         }
3626     }
3627 }
3628 
addExplosions(SceneDatabase * scene)3629 static void     addExplosions(SceneDatabase* scene)
3630 {
3631     const int count = explosions.size();
3632     for (int i = 0; i < count; i++)
3633         scene->addDynamicNode(explosions[i]);
3634 }
3635 
3636 #ifdef ROBOT
handleMyTankKilled(int reason)3637 static void     handleMyTankKilled(int reason)
3638 {
3639     // restore the sound, this happens when paused tank dies
3640     if (savedVolume != -1)
3641     {
3642         setSoundVolume(savedVolume);
3643         savedVolume = -1;
3644     }
3645 
3646     // blow me up
3647     myTank->explodeTank();
3648     if (reason == GotRunOver)
3649         playLocalSound(SFX_RUNOVER);
3650     else
3651         playLocalSound(SFX_DIE);
3652 }
3653 #endif
3654 
handleMsgSetVars(const void * msg)3655 static const void *handleMsgSetVars(const void *msg)
3656 {
3657     uint16_t numVars;
3658     uint8_t nameLen = 0, valueLen = 0;
3659 
3660     char name[MaxPacketLen];
3661     char value[MaxPacketLen];
3662 
3663     msg = nboUnpackUShort(msg, numVars);
3664     for (int i = 0; i < numVars; i++)
3665     {
3666         msg = nboUnpackUByte(msg, nameLen);
3667         msg = nboUnpackString(msg, name, nameLen);
3668         name[nameLen] = '\0';
3669 
3670         msg = nboUnpackUByte(msg, valueLen);
3671         msg = nboUnpackString(msg, value, valueLen);
3672         value[valueLen] = '\0';
3673 
3674         if ((name[0] != '_') && (name[0] != '$'))
3675         {
3676             logDebugMessage(1, "Server BZDB change blocked: '%s' = '%s'\n",
3677                             name, value);
3678         }
3679         else
3680         {
3681             BZDB.set(name, value);
3682             BZDB.setPersistent(name, false);
3683             BZDB.setPermission(name, StateDatabase::Locked);
3684         }
3685     }
3686     return msg;
3687 }
3688 
handleFlagDropped(Player * tank)3689 void handleFlagDropped(Player* tank)
3690 {
3691     // skip it if player doesn't actually have a flag
3692     if (tank->getFlag() == Flags::Null) return;
3693 
3694     if (tank == myTank)
3695     {
3696         // make sure the player must reload after theft
3697         if (tank->getFlag() == Flags::Thief)
3698             myTank->forceReload(BZDB.eval(StateDatabase::BZDB_THIEFDROPTIME));
3699         //drop lock if i had GM
3700         if (myTank->getFlag() == Flags::GuidedMissile)
3701             myTank->setTarget(NULL);
3702 
3703         // update display and play sound effects
3704         playLocalSound(SFX_DROP_FLAG);
3705         updateFlag(Flags::Null);
3706     }
3707     else if (isViewTank(tank))
3708         playLocalSound(SFX_DROP_FLAG);
3709 
3710     // add message
3711     std::string message("dropped ");
3712     message += tank->getFlag()->flagName;
3713     message += " flag";
3714     addMessage(tank, message);
3715 
3716     // player no longer has flag
3717     tank->setFlag(Flags::Null);
3718 }
3719 
handleFlagTransferred(Player * fromTank,Player * toTank,int flagIndex)3720 static void handleFlagTransferred( Player *fromTank, Player *toTank, int flagIndex)
3721 {
3722     Flag f = world->getFlag(flagIndex);
3723 
3724     fromTank->setFlag(Flags::Null);
3725     toTank->setFlag(f.type);
3726 
3727     if ((fromTank == myTank) || (toTank == myTank))
3728         updateFlag(myTank->getFlag());
3729 
3730     const float *pos = toTank->getPosition();
3731     if (f.type->flagTeam != ::NoTeam)
3732     {
3733         if ((toTank->getTeam() == myTank->getTeam()) && (f.type->flagTeam != myTank->getTeam()))
3734             playWorldSound(SFX_TEAMGRAB, pos);
3735         else if ((fromTank->getTeam() == myTank->getTeam()) && (f.type->flagTeam == myTank->getTeam()))
3736         {
3737             hud->setAlert(1, "Flag Alert!!!", 3.0f, true);
3738             playLocalSound(SFX_ALERT);
3739         }
3740     }
3741 
3742     std::string message("stole ");
3743     message += fromTank->getCallSign();
3744     message += "'s flag";
3745     addMessage(toTank, message);
3746 }
3747 
gotBlowedUp(BaseLocalPlayer * tank,BlowedUpReason reason,PlayerId killer,const ShotPath * hit,int phydrv)3748 static bool     gotBlowedUp(BaseLocalPlayer* tank,
3749                             BlowedUpReason reason,
3750                             PlayerId killer,
3751                             const ShotPath* hit, int phydrv)
3752 {
3753     if (tank && (tank->getTeam() == ObserverTeam || !tank->isAlive()))
3754         return false;
3755 
3756     int shotId = -1;
3757     FlagType* flagType = Flags::Null;
3758     if (hit)
3759     {
3760         shotId = hit->getShotId();
3761         flagType = hit->getFlag();
3762     }
3763 
3764     // you can't take it with you
3765     const FlagType* flag = tank->getFlag();
3766     if (flag != Flags::Null)
3767     {
3768         if (myTank->isAutoPilot())
3769             teachAutoPilot( myTank->getFlag(), -1 );
3770 
3771         // tell other players I've dropped my flag
3772         lookupServer(tank)->sendDropFlag(tank->getPosition());
3773 
3774         // drop it
3775         handleFlagDropped(tank);
3776     }
3777 
3778     // restore the sound, this happens when paused tank dies
3779     // (genocide or team flag captured)
3780     if (savedVolume != -1)
3781     {
3782         setSoundVolume(savedVolume);
3783         savedVolume = -1;
3784     }
3785 
3786     // take care of explosion business -- don't want to wait for
3787     // round trip of killed message.  waiting would simplify things,
3788     // but the delay (2-3 frames usually) can really fool and irritate
3789     // the player.  we have to be careful to ignore our own Killed
3790     // message when it gets back to us -- do this by ignoring killed
3791     // message if we're already dead.
3792     // don't die if we had the shield flag and we've been shot.
3793     if (reason != GotShot || flag != Flags::Shield)
3794     {
3795         // blow me up
3796 
3797         // todo hook this back up for 2.4.4. or later
3798         TankDeathOverride *death = NULL;
3799         EFFECTS.addDeathEffect(tank->getColor(), tank->getPosition(), tank->getAngle(),reason,tank, flagType);
3800 
3801         tank->setDeathEffect(death);
3802         tank->explodeTank();
3803 
3804         if (isViewTank(tank))
3805         {
3806             if (reason == GotRunOver)
3807                 playLocalSound(SFX_RUNOVER);
3808             else
3809                 playLocalSound(SFX_DIE);
3810             ForceFeedback::death();
3811         }
3812         else
3813         {
3814             const float* pos = tank->getPosition();
3815             if (reason == GotRunOver)
3816             {
3817                 playWorldSound(SFX_RUNOVER, pos,
3818                                getLocalPlayer(killer) == myTank);
3819             }
3820             else
3821             {
3822                 playWorldSound(SFX_EXPLOSION, pos,
3823                                getLocalPlayer(killer) == myTank);
3824             }
3825         }
3826 
3827         if (tank != myTank &&(!death || death->ShowExplosion()))
3828         {
3829             const float* pos = tank->getPosition();
3830             float explodePos[3];
3831             explodePos[0] = pos[0];
3832             explodePos[1] = pos[1];
3833             explodePos[2] = pos[2] + tank->getMuzzleHeight();
3834             addTankExplosion(explodePos);
3835         }
3836 
3837         // tell server I'm dead in case it doesn't already know
3838         if (reason == GotShot || reason == GotRunOver ||
3839                 reason == GenocideEffect || reason == SelfDestruct ||
3840                 reason == WaterDeath || reason == DeathTouch)
3841             lookupServer(tank)->sendKilled(killer, reason, shotId, flagType, phydrv);
3842     }
3843 
3844     // print reason if it's my tank
3845     if ((tank == myTank) &&
3846             (((reason < LastReason) && blowedUpMessage[reason]) ||
3847              (reason == PhysicsDriverDeath)))
3848     {
3849 
3850         std::string blowedUpNotice;
3851         if (reason < LastReason)
3852             blowedUpNotice = blowedUpMessage[reason];
3853         else if (reason == PhysicsDriverDeath)
3854         {
3855             const PhysicsDriver* driver = PHYDRVMGR.getDriver(phydrv);
3856             if (driver)
3857                 blowedUpNotice = driver->getDeathMsg();
3858             else
3859                 blowedUpNotice = "Killed by unknown obstacle";
3860         }
3861         else
3862             blowedUpNotice = "Invalid reason";
3863 
3864         // first, check if i'm the culprit
3865         if (reason == GotShot && getLocalPlayer(killer) == myTank)
3866             blowedUpNotice = "Shot myself";
3867         else
3868         {
3869             // 1-4 are messages sent when the player dies because of someone else
3870             if (reason >= GotShot && reason <= GenocideEffect)
3871             {
3872                 Player *killerPlayer = lookupPlayer(killer);
3873                 if (!killerPlayer)
3874                     blowedUpNotice = "Killed by the server";
3875                 else
3876                 {
3877 
3878                     // matching the team-display style of other kill messages
3879                     TeamColor team = killerPlayer->getTeam();
3880                     if (hit)
3881                         team = hit->getTeam();
3882                     if (World::getWorld()->allowTeams() && (myTank->getTeam() == team) && (team != RogueTeam) && (team != ObserverTeam))
3883                     {
3884                         blowedUpNotice += "teammate " ;
3885                         blowedUpNotice += killerPlayer->getCallSign();
3886                     }
3887                     else
3888                     {
3889                         blowedUpNotice += killerPlayer->getCallSign();
3890                         blowedUpNotice += " (";
3891                         blowedUpNotice += Team::getName(killerPlayer->getTeam());
3892                         blowedUpNotice += ")";
3893                         if (flagType != Flags::Null)
3894                         {
3895                             blowedUpNotice += " with ";
3896                             blowedUpNotice += flagType->flagAbbv;
3897                         }
3898                     }
3899                 }
3900             }
3901         }
3902         hud->setAlert(0, blowedUpNotice.c_str(), 4.0f, true);
3903         controlPanel->addMessage(blowedUpNotice);
3904     }
3905 
3906     // make sure shot is terminated locally (if not globally) so it can't
3907     // hit me again if I had the shield flag.  this is important for the
3908     // shots that aren't stopped by a hit and so may stick around to hit
3909     // me on the next update, making the shield useless.
3910     return (reason == GotShot && flag == Flags::Shield && shotId != -1);
3911 }
3912 
checkEnvironment()3913 static void     checkEnvironment()
3914 {
3915     if (!myTank) return;
3916 
3917     if (myTank->getTeam() == ObserverTeam )
3918     {
3919         if (BZDB.evalInt("showVelocities") <= 2)
3920             return;
3921 
3922         // Check for an observed tanks hit.
3923         Player *target = ROAM.getTargetTank();
3924         const ShotPath* hit = NULL;
3925         FlagType* flagd;
3926         float minTime = Infinity;
3927         int i;
3928 
3929         // Always a possibility of failure
3930         if (target == NULL)
3931             return;
3932 
3933         if (ROAM.getMode() != Roaming::roamViewFP)
3934             // Only works if we are driving with the target
3935             return;
3936 
3937         if (!target->isAlive() || target->isPaused())
3938             // If he's dead or paused, don't bother checking
3939             return;
3940 
3941         flagd = target->getFlag();
3942         if ((flagd == Flags::Narrow) || (flagd == Flags::Tiny))
3943             // Don't bother trying to figure this out with a narrow or tiny flag yet.
3944             return;
3945 
3946         myTank->checkHit(myTank, hit, minTime);
3947         for (i = 0; i < curMaxPlayers; i++)
3948             if (remotePlayers[i])
3949                 myTank->checkHit(remotePlayers[i], hit, minTime);
3950 
3951         if (!hit)
3952             return;
3953 
3954         Player* hitter = lookupPlayer(hit->getPlayer());
3955         std::ostringstream smsg;
3956         if (hitter->getId() == target->getId())
3957             return;
3958 
3959         // Don't report collisions when teammates can't be killed.
3960         // This is required because checkHit() tests as if we were observer.
3961         TeamColor team = hitter->getTeam();
3962         if (!World::getWorld()->allowTeamKills() && team == target->getTeam() &&
3963                 team != RogueTeam && team != ObserverTeam)
3964             return;
3965 
3966         smsg << "local collision with "
3967              << hit->getShotId()
3968              << " from "
3969              << hitter->getCallSign()
3970              << std::endl;
3971         addMessage(target, smsg.str());
3972 
3973         if (target->hitMap.find(hit->getShotId()) == target->hitMap.end())
3974             target->computedHits++;
3975 
3976         target->hitMap[hit->getShotId()] = true;
3977         return;
3978     }
3979 
3980     // skip this if i'm dead or paused
3981     if (!myTank->isAlive() || myTank->isPaused()) return;
3982 
3983     FlagType* flagd = myTank->getFlag();
3984     if (flagd->flagTeam != NoTeam)
3985     {
3986         // have I captured a flag?
3987         TeamColor base = world->whoseBase(myTank->getPosition());
3988         TeamColor team = myTank->getTeam();
3989         if ((base != NoTeam) &&
3990                 ((flagd->flagTeam == team && base != team) ||
3991                  (flagd->flagTeam != team && base == team)))
3992             serverLink->sendCaptureFlag(base);
3993     }
3994     else if (flagd == Flags::Null && (myTank->getLocation() == LocalPlayer::OnGround ||
3995                                       myTank->getLocation() == LocalPlayer::OnBuilding))
3996     {
3997         // Don't grab too fast
3998         static TimeKeeper lastGrabSent;
3999         if (TimeKeeper::getTick()-lastGrabSent > 0.2)
4000         {
4001             // grab any and all flags i'm driving over
4002             const float* tpos = myTank->getPosition();
4003             const float radius = myTank->getRadius();
4004             const float radius2 = (radius + BZDBCache::flagRadius) * (radius + BZDBCache::flagRadius);
4005             for (int i = 0; i < numFlags; i++)
4006             {
4007                 if (world->getFlag(i).type == Flags::Null || world->getFlag(i).status != FlagOnGround)
4008                     continue;
4009                 const float* fpos = world->getFlag(i).position;
4010                 if ((fabs(tpos[2] - fpos[2]) < 0.1f) && ((tpos[0] - fpos[0]) * (tpos[0] - fpos[0]) +
4011                         (tpos[1] - fpos[1]) * (tpos[1] - fpos[1]) < radius2))
4012                 {
4013                     serverLink->sendGrabFlag(i);
4014                     lastGrabSent=TimeKeeper::getTick();
4015                 }
4016             }
4017         }
4018     }
4019 
4020     // see if i've been shot
4021     const ShotPath* hit = NULL;
4022     float minTime = Infinity;
4023 
4024     myTank->checkHit(myTank, hit, minTime);
4025     int i;
4026     for (i = 0; i < curMaxPlayers; i++)
4027         if (remotePlayers[i])
4028             myTank->checkHit(remotePlayers[i], hit, minTime);
4029 
4030     // Check Server Shots
4031     myTank->checkHit( World::getWorld()->getWorldWeapons(), hit, minTime);
4032 
4033     // used later
4034     float waterLevel = World::getWorld()->getWaterLevel();
4035 
4036     if (hit)
4037     {
4038         // i got shot!  terminate the shot that hit me and blow up.
4039         // force shot to terminate locally immediately (no server round trip);
4040         // this is to ensure that we don't get shot again by the same shot
4041         // after dropping our shield flag.
4042         if (hit->isStoppedByHit())
4043             serverLink->sendEndShot(hit->getPlayer(), hit->getShotId(), 1);
4044 
4045         FlagType* killerFlag = hit->getFlag();
4046         bool stopShot;
4047 
4048         if (killerFlag == Flags::Thief)
4049         {
4050             if (myTank->getFlag() != Flags::Null)
4051                 serverLink->sendTransferFlag(myTank->getId(), hit->getPlayer());
4052             stopShot = true;
4053         }
4054         else
4055             stopShot = gotBlowedUp(myTank, GotShot, hit->getPlayer(), hit);
4056 
4057         if (stopShot || hit->isStoppedByHit())
4058         {
4059             Player* hitter = lookupPlayer(hit->getPlayer());
4060             if (hitter) hitter->endShot(hit->getShotId());
4061         }
4062     }
4063     // if not dead yet, see if i'm sitting on death
4064     else if (myTank->getDeathPhysicsDriver() >= 0)
4065     {
4066         gotBlowedUp(myTank, DeathTouch, ServerPlayer, NULL,
4067                     myTank->getDeathPhysicsDriver());
4068     }
4069     // if not dead yet, see if i've dropped below the death level
4070     else if ((waterLevel > 0.0f) && (myTank->getPosition()[2] <= waterLevel))
4071         gotBlowedUp(myTank, WaterDeath, ServerPlayer);
4072     // if not dead yet, see if i got run over by the steamroller
4073     else
4074     {
4075         const float* myPos = myTank->getPosition();
4076         const float myRadius = myTank->getRadius();
4077         for (i = 0; i < curMaxPlayers; i++)
4078         {
4079             if (remotePlayers[i] && !remotePlayers[i]->isPaused() &&
4080                     ((remotePlayers[i]->getFlag() == Flags::Steamroller) ||
4081                      ((myPos[2] < 0.0f) && remotePlayers[i]->isAlive() &&
4082                       !remotePlayers[i]->isPhantomZoned())))
4083             {
4084                 const float* pos = remotePlayers[i]->getPosition();
4085                 if (pos[2] < 0.0f) continue;
4086                 if (remotePlayers[i]->getTeam() != RogueTeam && !World::getWorld()->allowTeamKills() &&
4087                         remotePlayers[i]->getTeam() == myTank->getTeam()) continue;
4088                 if (!myTank->isPhantomZoned())
4089                 {
4090                     const float radius = myRadius +
4091                                          BZDB.eval(StateDatabase::BZDB_SRRADIUSMULT) * remotePlayers[i]->getRadius();
4092                     const float distSquared =
4093                         hypotf(hypotf(myPos[0] - pos[0],
4094                                       myPos[1] - pos[1]), (myPos[2] - pos[2]) * 2.0f);
4095                     if (distSquared < radius)
4096                         gotBlowedUp(myTank, GotRunOver, remotePlayers[i]->getId());
4097                 }
4098             }
4099         }
4100     }
4101 }
4102 
inLookRange(float angle,float distance,float bestDistance,RemotePlayer * player)4103 bool inLookRange(float angle, float distance, float bestDistance, RemotePlayer *player)
4104 {
4105     // usually about 17 degrees
4106     if (angle >= BZDB.eval(StateDatabase::BZDB_TARGETINGANGLE))
4107         return false;
4108 
4109 
4110     if (distance > BZDB.eval(StateDatabase::BZDB_TARGETINGDISTANCE) || distance > bestDistance)
4111         return false;
4112 
4113     if (myTank->getFlag() == Flags::Blindness)
4114         return false;
4115 
4116     if (player->getFlag() == Flags::Stealth ||
4117             player->getFlag() == Flags::Cloaking)
4118         return myTank->getFlag() == Flags::Seer;
4119 
4120     return true;
4121 }
4122 
isKillable(const Player * target)4123 static bool isKillable(const Player *target)
4124 {
4125     if (target == myTank)
4126         return false;
4127     if (target->getTeam() == RogueTeam)
4128         return true;
4129     if (myTank->getFlag() == Flags::Colorblindness)
4130         return true;
4131     if (! World::getWorld()->allowTeamKills() || ! World::getWorld()->allowTeams())
4132         return true;
4133     if (target->getTeam() != myTank->getTeam())
4134         return true;
4135 
4136     return false;
4137 }
4138 
isFriendly(const Player * target)4139 static bool isFriendly(const Player *target)
4140 {
4141     if (target == myTank)
4142         return true;
4143 
4144     if (!World::getWorld()->allowTeams())
4145         return false;
4146 
4147     if (target->getTeam() == RogueTeam)
4148         return false;
4149     if (myTank->getFlag() == Flags::Colorblindness)
4150         return false;
4151 
4152     return target->getTeam() == myTank->getTeam();
4153 }
4154 
setLookAtMarker(void)4155 void setLookAtMarker(void)
4156 {
4157     // get info about my tank
4158     const float c = cosf(- myTank->getAngle());
4159     const float s = sinf(- myTank->getAngle());
4160     const float *p = myTank->getPosition();
4161     const fvec3 myPos(p[0],p[1],p[2]);
4162 
4163     // initialize best target
4164     Player *bestTarget = NULL;
4165     float bestDistance = Infinity;
4166 
4167     // figure out which tank is centered in my sights
4168     for (int i = 0; i < curMaxPlayers; i++)
4169     {
4170         if (!remotePlayers[i] || !remotePlayers[i]->isAlive())
4171             continue;
4172 
4173         // compute position in my local coordinate system
4174         const fvec3 rPos(remotePlayers[i]->getPosition()[0],remotePlayers[i]->getPosition()[1],
4175                          remotePlayers[i]->getPosition()[2]);
4176         const float x = (c * (rPos.x - myPos.x)) - (s * (rPos.y - myPos.y));
4177         const float y = (s * (rPos.x - myPos.x)) + (c * (rPos.y - myPos.y));
4178 
4179         // ignore things behind me
4180         if (x < 0.0f)
4181             continue;
4182 
4183         // get distance and sin(angle) from directly forward
4184         const float d = hypotf(x, y);
4185         const float a = fabsf(y / d);
4186 
4187 
4188         if (inLookRange(a, d, bestDistance, remotePlayers[i]))
4189         {
4190             // check and see if we can cast a ray from our point to the object
4191             fvec3 vec = rPos - myPos;
4192 
4193             Ray ray = Ray(myPos, vec);
4194 
4195             // get the list of objects that fall in this ray
4196             const ObsList *olist = COLLISIONMGR.rayTest (&ray, d);
4197 
4198             bool blocked = false;
4199             if (olist && olist->count > 0)
4200             {
4201                 for (int o = 0; o < olist->count; o++)
4202                 {
4203                     const Obstacle *obs = olist->list[o];
4204                     const float timet = obs->intersect(ray);
4205                     if (timet > 1.0f)
4206                     {
4207                         blocked = true;
4208                         break;
4209                     }
4210                 }
4211             }
4212 
4213             // if there is nothing between us then go and add it to the list
4214             if (!blocked)
4215             {
4216                 // is it better?
4217                 bestTarget = remotePlayers[i];
4218                 bestDistance = d;
4219             }
4220         }
4221     }
4222 
4223     if (!bestTarget)
4224         return;
4225 
4226     if (myTank->getFlag() == Flags::Blindness)
4227         return;
4228 
4229     std::string label = bestTarget->getCallSign();
4230     if (bestTarget->getFlag() != Flags::Null)
4231     {
4232         std::string flagName = bestTarget->getFlag()->flagAbbv;
4233         label += std::string("(") + flagName + std::string(")");
4234     }
4235 
4236     // Color enhanced marker depending on Local and RemotePlayer's Flag
4237 
4238     TeamColor markercolor = bestTarget->getTeam();
4239 
4240     if (bestTarget->getFlag() == Flags::Masquerade &&
4241             myTank->getFlag() != Flags::Seer)
4242         markercolor = myTank->getTeam();
4243 
4244     if (myTank->getFlag() == Flags::Colorblindness)
4245         markercolor = RogueTeam;
4246 
4247     hud->AddEnhancedNamedMarker(Float3ToVec3(bestTarget->getPosition()),
4248                                 Float3ToVec4(Team::getTankColor(markercolor)),
4249                                 label, isFriendly(bestTarget), 2.0f);
4250 }
4251 
tankHasShotType(const Player * tank,const FlagType * ft)4252 static inline bool tankHasShotType(const Player* tank, const FlagType* ft)
4253 {
4254     const int maxShots = tank->getMaxShots();
4255     for (int i = 0; i < maxShots; i++)
4256     {
4257         const ShotPath* sp = tank->getShot(i);
4258         if ((sp != NULL) && (sp->getFlag() == ft))
4259             return true;
4260     }
4261     return false;
4262 }
4263 
setTarget()4264 void setTarget()
4265 {
4266     // get info about my tank
4267     const float c = cosf(-myTank->getAngle());
4268     const float s = sinf(-myTank->getAngle());
4269     const float x0 = myTank->getPosition()[0];
4270     const float y0 = myTank->getPosition()[1];
4271 
4272     // initialize best target
4273     Player* bestTarget = NULL;
4274     float bestDistance = Infinity;
4275     bool lockedOn = false;
4276 
4277     // figure out which tank is centered in my sights
4278     for (int i = 0; i < curMaxPlayers; i++)
4279     {
4280         if (!remotePlayers[i] || !remotePlayers[i]->isAlive()) continue;
4281 
4282         // compute position in my local coordinate system
4283         const float* pos = remotePlayers[i]->getPosition();
4284         const float x = c * (pos[0] - x0) - s * (pos[1] - y0);
4285         const float y = s * (pos[0] - x0) + c * (pos[1] - y0);
4286 
4287         // ignore things behind me
4288         if (x < 0.0f) continue;
4289 
4290         // get distance and sin(angle) from directly forward
4291         const float d = hypotf(x, y);
4292         const float a = fabsf(y / d);
4293 
4294         // see if it's inside lock-on angle (if we're trying to lock-on)
4295         if (a < BZDB.eval(StateDatabase::BZDB_LOCKONANGLE) &&   // about 8.5 degrees
4296                 ((myTank->getFlag() == Flags::GuidedMissile) ||     // am i locking on?
4297                  tankHasShotType(myTank, Flags::GuidedMissile)) &&
4298                 remotePlayers[i]->getFlag() != Flags::Stealth &&        // can't lock on stealth
4299                 !remotePlayers[i]->isPaused() &&                // can't lock on paused
4300                 !remotePlayers[i]->isNotResponding() &&         // can't lock on not responding
4301                 d < bestDistance)                   // is it better?
4302         {
4303             bestTarget = remotePlayers[i];
4304             bestDistance = d;
4305             lockedOn = true;
4306         }
4307         else if (a < BZDB.eval(StateDatabase::BZDB_TARGETINGANGLE) &&               // about 17 degrees
4308                  ((remotePlayers[i]->getFlag() != Flags::Stealth) || (myTank->getFlag() == Flags::Seer))
4309                  && // can't "see" stealth unless have seer
4310                  d < bestDistance && !lockedOn)         // is it better?
4311         {
4312             bestTarget = remotePlayers[i];
4313             bestDistance = d;
4314         }
4315     }
4316     if (!lockedOn) myTank->setTarget(NULL);
4317     if (!bestTarget) return;
4318 
4319     const bool forbidIdentify = BZDB.isTrue("_forbidIdentify");
4320 
4321     if (lockedOn)
4322     {
4323         myTank->setTarget(bestTarget);
4324         myTank->setNemesis(bestTarget);
4325 
4326         std::string msg("Locked on ");
4327         if (!forbidIdentify)
4328         {
4329             msg += bestTarget->getCallSign();
4330             msg += " (";
4331             msg += Team::getName(bestTarget->getTeam());
4332             if (bestTarget->getFlag() != Flags::Null)
4333             {
4334                 msg += ") with ";
4335                 msg += bestTarget->getFlag()->flagName;
4336             }
4337             else
4338                 msg += ")";
4339         }
4340         hud->setAlert(1, msg.c_str(), 2.0f, 1);
4341         msg = ColorStrings[DefaultColor] + msg;
4342         addMessage(NULL, msg);
4343     }
4344     else if (forbidIdentify)
4345     {
4346         if (sentForbidIdentify == 10 || sentForbidIdentify == 0)
4347             addMessage(NULL, "'identify' disabled on this server");
4348         if (sentForbidIdentify == 10)
4349             sentForbidIdentify = 0;
4350         if (sentForbidIdentify < 10)
4351             sentForbidIdentify++;
4352     }
4353     else if (myTank->getFlag() == Flags::Colorblindness)
4354     {
4355         std::string msg("Looking at a tank");
4356         hud->setAlert(1, msg.c_str(), 2.0f, 0);
4357         msg = ColorStrings[DefaultColor] + msg;
4358         addMessage(NULL, msg);
4359     }
4360     else
4361     {
4362         std::string msg("Looking at ");
4363         msg += bestTarget->getCallSign();
4364         msg += " (";
4365         msg += Team::getName(bestTarget->getTeam());
4366         msg += ")";
4367         if (bestTarget->getFlag() != Flags::Null)
4368         {
4369             msg += " with ";
4370             msg += bestTarget->getFlag()->flagName;
4371         }
4372         hud->setAlert(1, msg.c_str(), 2.0f, 0);
4373         msg = ColorStrings[DefaultColor] + msg;
4374         addMessage(NULL, msg);
4375         myTank->setNemesis(bestTarget);
4376     }
4377 }
4378 
setHuntTarget()4379 static void setHuntTarget()
4380 {
4381     if (BZDB.isTrue("_forbidHunting"))
4382         return;
4383 
4384     // get info about my tank
4385     const float c = cosf(-myTank->getAngle());
4386     const float s = sinf(-myTank->getAngle());
4387     const float x0 = myTank->getPosition()[0];
4388     const float y0 = myTank->getPosition()[1];
4389 
4390     // initialize best target
4391     Player* bestTarget = NULL;
4392     float bestDistance = Infinity;
4393     bool lockedOn = false;
4394 
4395     // figure out which tank is centered in my sights
4396     for (int i = 0; i < curMaxPlayers; i++)
4397     {
4398         if (!remotePlayers[i] || !remotePlayers[i]->isAlive()) continue;
4399 
4400         // compute position in my local coordinate system
4401         const float* pos = remotePlayers[i]->getPosition();
4402         const float x = c * (pos[0] - x0) - s * (pos[1] - y0);
4403         const float y = s * (pos[0] - x0) + c * (pos[1] - y0);
4404 
4405         // ignore things behind me
4406         if (x < 0.0f) continue;
4407 
4408         // get distance and sin(angle) from directly forward
4409         const float d = hypotf(x, y);
4410         const float a = fabsf(y / d);
4411 
4412         // see if it's inside lock-on angle (if we're trying to lock-on)
4413         if (a < BZDB.eval(StateDatabase::BZDB_LOCKONANGLE) &&                   // about 8.5 degrees
4414                 myTank->getFlag() == Flags::GuidedMissile &&    // am i locking on?
4415                 remotePlayers[i]->getFlag() != Flags::Stealth &&    // can't lock on stealth
4416                 !remotePlayers[i]->isPaused() &&            // can't lock on paused
4417                 !remotePlayers[i]->isNotResponding() &&     // can't lock on not responding
4418                 d < bestDistance)               // is it better?
4419         {
4420             bestTarget = remotePlayers[i];
4421             bestDistance = d;
4422             lockedOn = true;
4423         }
4424         else if (a < BZDB.eval(StateDatabase::BZDB_TARGETINGANGLE) &&               // about 17 degrees
4425                  ((remotePlayers[i]->getFlag() != Flags::Stealth) || (myTank->getFlag() == Flags::Seer))
4426                  && // can't "see" stealth unless have seer
4427                  d < bestDistance && !lockedOn)         // is it better?
4428         {
4429             bestTarget = remotePlayers[i];
4430             bestDistance = d;
4431         }
4432     }
4433     if (!bestTarget) return;
4434 
4435 
4436     if (bestTarget->isHunted() && myTank->getFlag() != Flags::Blindness &&
4437             myTank->getFlag() != Flags::Colorblindness &&
4438             bestTarget->getFlag() != Flags::Stealth)
4439     {
4440         if (myTank->getTarget() == NULL)   // Don't interfere with GM lock display
4441         {
4442             std::string msg("SPOTTED: ");
4443             msg += bestTarget->getCallSign();
4444             msg += " (";
4445             msg += Team::getName(bestTarget->getTeam());
4446             if (bestTarget->getFlag() != Flags::Null)
4447             {
4448                 msg += ") with ";
4449                 msg += bestTarget->getFlag()->flagName;
4450             }
4451             else
4452                 msg += ")";
4453             hud->setAlert(1, msg.c_str(), 2.0f, 0);
4454         }
4455         if (!pulse.isOn())
4456         {
4457             const float* bestTargetPosition = bestTarget->getPosition();
4458             playWorldSound(SFX_HUNT, bestTargetPosition);
4459             pulse.setClock(1.0f);
4460         }
4461     }
4462 }
4463 
updateDaylight(double offset,SceneRenderer & renderer)4464 static void     updateDaylight(double offset, SceneRenderer& renderer)
4465 {
4466     static const double SecondsInDay = 86400.0;
4467 
4468     // update sun, moon & sky
4469     renderer.setTimeOfDay(unixEpoch + offset / SecondsInDay);
4470 }
4471 
4472 #ifdef ROBOT
4473 
4474 //
4475 // some robot stuff
4476 //
4477 
4478 static std::vector<BzfRegion*>  obstacleList;  // for robots
4479 
addObstacle(std::vector<BzfRegion * > & rgnList,const Obstacle & obstacle)4480 static void     addObstacle(std::vector<BzfRegion*>& rgnList, const Obstacle& obstacle)
4481 {
4482     float p[4][2];
4483     const float* c = obstacle.getPosition();
4484     const float tankRadius = BZDBCache::tankRadius;
4485 
4486     if (BZDBCache::tankHeight < c[2])
4487         return;
4488 
4489     const float a = obstacle.getRotation();
4490     const float w = obstacle.getWidth() + tankRadius;
4491     const float h = obstacle.getBreadth() + tankRadius;
4492     const float xx =  w * cosf(a);
4493     const float xy =  w * sinf(a);
4494     const float yx = -h * sinf(a);
4495     const float yy =  h * cosf(a);
4496     p[0][0] = c[0] - xx - yx;
4497     p[0][1] = c[1] - xy - yy;
4498     p[1][0] = c[0] + xx - yx;
4499     p[1][1] = c[1] + xy - yy;
4500     p[2][0] = c[0] + xx + yx;
4501     p[2][1] = c[1] + xy + yy;
4502     p[3][0] = c[0] - xx + yx;
4503     p[3][1] = c[1] - xy + yy;
4504 
4505     int numRegions = rgnList.size();
4506     for (int k = 0; k < numRegions; k++)
4507     {
4508         BzfRegion* region = rgnList[k];
4509         int side[4];
4510         if ((side[0] = region->classify(p[0], p[1])) == 1 ||
4511                 (side[1] = region->classify(p[1], p[2])) == 1 ||
4512                 (side[2] = region->classify(p[2], p[3])) == 1 ||
4513                 (side[3] = region->classify(p[3], p[0])) == 1)
4514             continue;
4515         if (side[0] == -1 && side[1] == -1 && side[2] == -1 && side[3] == -1)
4516         {
4517             rgnList[k] = rgnList[numRegions-1];
4518             rgnList[numRegions-1] = rgnList[rgnList.size()-1];
4519             rgnList.pop_back();
4520             numRegions--;
4521             k--;
4522             delete region;
4523             continue;
4524         }
4525         for (int j = 0; j < 4; j++)
4526         {
4527             if (side[j] == -1) continue;      // to inside
4528             // split
4529             const float* p1 = p[j];
4530             const float* p2 = p[(j+1)&3];
4531             BzfRegion* newRegion = region->orphanSplitRegion(p2, p1);
4532             if (!newRegion) continue;     // no split
4533             if (region != rgnList[k]) rgnList.push_back(region);
4534             region = newRegion;
4535         }
4536         if (region != rgnList[k]) delete region;
4537     }
4538 }
4539 
makeObstacleList()4540 static void     makeObstacleList()
4541 {
4542     const float tankRadius = BZDBCache::tankRadius;
4543     int i;
4544     const int count = obstacleList.size();
4545     for (i = 0; i < count; i++)
4546         delete obstacleList[i];
4547     obstacleList.clear();
4548 
4549     // FIXME -- shouldn't hard code game area
4550     float gameArea[4][2];
4551     float worldSize = BZDBCache::worldSize;
4552     gameArea[0][0] = -0.5f * worldSize + tankRadius;
4553     gameArea[0][1] = -0.5f * worldSize + tankRadius;
4554     gameArea[1][0] =  0.5f * worldSize - tankRadius;
4555     gameArea[1][1] = -0.5f * worldSize + tankRadius;
4556     gameArea[2][0] =  0.5f * worldSize - tankRadius;
4557     gameArea[2][1] =  0.5f * worldSize - tankRadius;
4558     gameArea[3][0] = -0.5f * worldSize + tankRadius;
4559     gameArea[3][1] =  0.5f * worldSize - tankRadius;
4560     obstacleList.push_back(new BzfRegion(4, gameArea));
4561 
4562     const ObstacleList& boxes = OBSTACLEMGR.getBoxes();
4563     const int numBoxes = boxes.size();
4564     for (i = 0; i < numBoxes; i++)
4565         addObstacle(obstacleList, *boxes[i]);
4566     const ObstacleList& pyramids = OBSTACLEMGR.getPyrs();
4567     const int numPyramids = pyramids.size();
4568     for (i = 0; i < numPyramids; i++)
4569         addObstacle(obstacleList, *pyramids[i]);
4570     const ObstacleList& teleporters = OBSTACLEMGR.getTeles();
4571     const int numTeleporters = teleporters.size();
4572     for (i = 0; i < numTeleporters; i++)
4573         addObstacle(obstacleList, *teleporters[i]);
4574     const ObstacleList& meshes = OBSTACLEMGR.getMeshes();
4575     const int numMeshes = meshes.size();
4576     for (i = 0; i < numMeshes; i++)
4577         addObstacle(obstacleList, *meshes[i]);
4578     if (World::getWorld()->allowTeamFlags())
4579     {
4580         const ObstacleList& bases = OBSTACLEMGR.getBases();
4581         const int numBases = bases.size();
4582         for (i = 0; i < numBases; i++)
4583         {
4584             const BaseBuilding* base = (const BaseBuilding*) bases[i];
4585             if ((base->getHeight() != 0.0f) || (base->getPosition()[2] != 0.0f))
4586                 addObstacle(obstacleList, *base);
4587         }
4588     }
4589 }
4590 
setRobotTarget(RobotPlayer * robot)4591 static void     setRobotTarget(RobotPlayer* robot)
4592 {
4593     Player* bestTarget = NULL;
4594     float bestPriority = 0.0f;
4595     for (int j = 0; j < curMaxPlayers; j++)
4596         if (remotePlayers[j] && remotePlayers[j]->getId() != robot->getId() &&
4597                 remotePlayers[j]->isAlive() && robot->validTeamTarget(remotePlayers[j]))
4598         {
4599 
4600             if (remotePlayers[j]->isPhantomZoned() && !robot->isPhantomZoned())
4601                 continue;
4602 
4603             if (World::getWorld()->allowTeamFlags() &&
4604                     ((robot->getTeam() == RedTeam && remotePlayers[j]->getFlag() == Flags::RedTeam) ||
4605                      (robot->getTeam() == GreenTeam && remotePlayers[j]->getFlag() == Flags::GreenTeam) ||
4606                      (robot->getTeam() == BlueTeam && remotePlayers[j]->getFlag() == Flags::BlueTeam) ||
4607                      (robot->getTeam() == PurpleTeam && remotePlayers[j]->getFlag() == Flags::PurpleTeam)))
4608             {
4609                 bestTarget = remotePlayers[j];
4610                 break;
4611             }
4612 
4613             const float priority = robot->getTargetPriority(remotePlayers[j]);
4614             if (priority > bestPriority)
4615             {
4616                 bestTarget = remotePlayers[j];
4617                 bestPriority = priority;
4618             }
4619         }
4620     if (myTank->isAlive() &&
4621             ((robot->getTeam() == RogueTeam) ||  robot->validTeamTarget(myTank)))
4622     {
4623         const float priority = robot->getTargetPriority(myTank);
4624         if (priority > bestPriority)
4625             bestTarget = myTank;
4626     }
4627     robot->setTarget(bestTarget);
4628 }
4629 
updateRobots(float dt)4630 static void     updateRobots(float dt)
4631 {
4632     static float newTargetTimeout = 1.0f;
4633     static float clock = 0.0f;
4634     bool pickTarget = false;
4635     int i;
4636 
4637     // see if we should look for new targets
4638     clock += dt;
4639     if (clock > newTargetTimeout)
4640     {
4641         while (clock > newTargetTimeout)
4642             clock -= newTargetTimeout;
4643         pickTarget = true;
4644     }
4645 
4646     // start dead robots
4647     for (i = 0; i < numRobots; i++)
4648     {
4649         if (!gameOver && robots[i]
4650                 && !robots[i]->isAlive() && !robots[i]->isExploding() && pickTarget)
4651             robotServer[i]->sendAlive();
4652     }
4653 
4654     // retarget robots
4655     for (i = 0; i < numRobots; i++)
4656     {
4657         if (robots[i] && robots[i]->isAlive()
4658                 && (pickTarget || !robots[i]->getTarget()
4659                     || !robots[i]->getTarget()->isAlive()))
4660             setRobotTarget(robots[i]);
4661     }
4662 
4663     // do updates
4664     for (i = 0; i < numRobots; i++)
4665         if (robots[i])
4666             robots[i]->update();
4667 }
4668 
4669 
checkEnvironment(RobotPlayer * tank)4670 static void     checkEnvironment(RobotPlayer* tank)
4671 {
4672     // skip this if i'm dead or paused
4673     if (!tank->isAlive() || tank->isPaused()) return;
4674 
4675     // see if i've been shot
4676     const ShotPath* hit = NULL;
4677     float minTime = Infinity;
4678     tank->checkHit(myTank, hit, minTime);
4679     int i;
4680     for (i = 0; i < curMaxPlayers; i++)
4681     {
4682         if (remotePlayers[i] && remotePlayers[i]->getId() != tank->getId())
4683             tank->checkHit(remotePlayers[i], hit, minTime);
4684     }
4685 
4686     // Check Server Shots
4687     tank->checkHit( World::getWorld()->getWorldWeapons(), hit, minTime);
4688 
4689     float waterLevel = World::getWorld()->getWaterLevel();
4690 
4691     if (hit)
4692     {
4693         // i got shot!  terminate the shot that hit me and blow up.
4694         // force shot to terminate locally immediately (no server round trip);
4695         // this is to ensure that we don't get shot again by the same shot
4696         // after dropping our shield flag.
4697         if (hit->isStoppedByHit())
4698             lookupServer(tank)->sendEndShot(hit->getPlayer(), hit->getShotId(), 1);
4699 
4700         FlagType* killerFlag = hit->getFlag();
4701         bool stopShot;
4702 
4703         if (killerFlag == Flags::Thief)
4704         {
4705             if (tank->getFlag() != Flags::Null)
4706                 serverLink->sendTransferFlag(tank->getId(), hit->getPlayer());
4707             stopShot = true;
4708         }
4709         else
4710             stopShot = gotBlowedUp(tank, GotShot, hit->getPlayer(), hit);
4711 
4712         if (stopShot || hit->isStoppedByHit())
4713         {
4714             Player* hitter = lookupPlayer(hit->getPlayer());
4715             if (hitter) hitter->endShot(hit->getShotId());
4716         }
4717     }
4718     // if not dead yet, see if i'm sitting on death
4719     else if (tank->getDeathPhysicsDriver() >= 0)
4720     {
4721         gotBlowedUp(tank, DeathTouch, ServerPlayer, NULL,
4722                     tank->getDeathPhysicsDriver());
4723     }
4724     // if not dead yet, see if the robot dropped below the death level
4725     else if ((waterLevel > 0.0f) && (tank->getPosition()[2] <= waterLevel))
4726         gotBlowedUp(tank, WaterDeath, ServerPlayer);
4727 
4728     // if not dead yet, see if i got run over by the steamroller
4729     else
4730     {
4731         bool dead = false;
4732         const float* myPos = tank->getPosition();
4733         const float myRadius = tank->getRadius();
4734         if (((myTank->getFlag() == Flags::Steamroller) ||
4735                 ((tank->getFlag() == Flags::Burrow) && myTank->isAlive() &&
4736                  !myTank->isPhantomZoned()))
4737                 && !myTank->isPaused())
4738         {
4739             const float* pos = myTank->getPosition();
4740             if (pos[2] >= 0.0f)
4741             {
4742                 const float radius = myRadius +
4743                                      (BZDB.eval(StateDatabase::BZDB_SRRADIUSMULT) * myTank->getRadius());
4744                 const float distSquared =
4745                     hypotf(hypotf(myPos[0] - pos[0],
4746                                   myPos[1] - pos[1]), (myPos[2] - pos[2]) * 2.0f);
4747                 if (distSquared < radius)
4748                 {
4749                     gotBlowedUp(tank, GotRunOver, myTank->getId());
4750                     dead = true;
4751                 }
4752             }
4753         }
4754         for (i = 0; !dead && i < curMaxPlayers; i++)
4755         {
4756             if (remotePlayers[i] && !remotePlayers[i]->isPaused() &&
4757                     ((remotePlayers[i]->getFlag() == Flags::Steamroller) ||
4758                      ((tank->getFlag() == Flags::Burrow) && remotePlayers[i]->isAlive() &&
4759                       !remotePlayers[i]->isPhantomZoned())))
4760             {
4761                 const float* pos = remotePlayers[i]->getPosition();
4762                 if (pos[2] < 0.0f) continue;
4763                 const float radius = myRadius +
4764                                      (BZDB.eval(StateDatabase::BZDB_SRRADIUSMULT) * remotePlayers[i]->getRadius());
4765                 const float distSquared =
4766                     hypotf(hypotf(myPos[0] - pos[0],
4767                                   myPos[1] - pos[1]), (myPos[2] - pos[2]) * 2.0f);
4768                 if (distSquared < radius)
4769                 {
4770                     gotBlowedUp(tank, GotRunOver, remotePlayers[i]->getId());
4771                     dead = true;
4772                 }
4773             }
4774         }
4775     }
4776 }
4777 
checkEnvironmentForRobots()4778 static void     checkEnvironmentForRobots()
4779 {
4780     for (int i = 0; i < numRobots; i++)
4781         if (robots[i])
4782             checkEnvironment(robots[i]);
4783 }
4784 
sendRobotUpdates()4785 static void     sendRobotUpdates()
4786 {
4787     for (int i = 0; i < numRobots; i++)
4788         if (robots[i] && robotServer[i] && robots[i]->isDeadReckoningWrong())
4789             robotServer[i]->sendPlayerUpdate(robots[i]);
4790 }
4791 
addRobots()4792 static void     addRobots()
4793 {
4794     uint16_t code, len;
4795     char msg[MaxPacketLen];
4796     char callsign[CallSignLen];
4797     int i, j;
4798 
4799     // add solo robots only when the server allows them
4800     if (BZDB.isTrue(StateDatabase::BZDB_DISABLEBOTS))
4801     {
4802         numRobots = 0;
4803         if (numRobotTanks > 0)
4804             addMessage(NULL, "Solo robots are prohibited on this server.");
4805         return;
4806     }
4807 
4808     for (i = 0, j = 0; i < numRobotTanks; i++)
4809     {
4810         robotServer[j] = new ServerLink(serverNetworkAddress, startupInfo.serverPort);
4811         if (!robotServer[j] || robotServer[j]->getState() != ServerLink::Okay)
4812         {
4813             delete robotServer[j];
4814             continue;
4815         }
4816         else
4817         {
4818             snprintf(callsign, CallSignLen, "%.29s%2.2x", myTank->getCallSign(), (short int)j);
4819             robots[j] = new RobotPlayer(robotServer[j]->getId(), callsign,
4820                                         robotServer[j], myTank->getMotto());
4821             robots[j]->setTeam(AutomaticTeam);
4822             robotServer[j]->sendEnter(ComputerPlayer, robots[j]->getTeam(),
4823                                       robots[j]->getCallSign(),
4824                                       robots[j]->getMotto(), "");
4825         }
4826         j++;
4827     }
4828     numRobots = j;
4829 
4830     for (j = 0; j < numRobots; j++)
4831     {
4832         // wait for response
4833         if (robotServer[j] && (robotServer[j]->read(code, len, msg, -1) < 0 || code != MsgAccept))
4834         {
4835             delete robots[j];
4836             delete robotServer[j];
4837             robots[j] = NULL;
4838             robotServer[j] = NULL;
4839         }
4840     }
4841 
4842     int k;
4843     // packing
4844     for (k = 0, j = 0; j < numRobots; j++)
4845     {
4846         if (k != j)
4847         {
4848             robotServer[k] = robotServer[j];
4849             robots[k]      = robots[j];
4850         }
4851         if (robotServer[j])
4852             k++;
4853     }
4854     numRobots = k;
4855 
4856     if (numRobots > 0)
4857     {
4858         makeObstacleList();
4859         RobotPlayer::setObstacleList(&obstacleList);
4860     }
4861 }
4862 
4863 #endif
4864 
4865 
setTankFlags()4866 static void setTankFlags()
4867 {
4868     // scan through flags and, for flags on
4869     // tanks, tell the tank about its flag.
4870     const int maxFlags = world->getMaxFlags();
4871     for (int i = 0; i < maxFlags; i++)
4872     {
4873         const Flag& flag = world->getFlag(i);
4874         if (flag.status == FlagOnTank)
4875         {
4876             for (int j = 0; j < curMaxPlayers; j++)
4877             {
4878                 if (remotePlayers[j] && remotePlayers[j]->getId() == flag.owner)
4879                 {
4880                     remotePlayers[j]->setFlag(flag.type);
4881                     break;
4882                 }
4883             }
4884         }
4885     }
4886 }
4887 
4888 
enteringServer(const void * buf)4889 static void enteringServer(const void *buf)
4890 {
4891     // the server sends back the team the player was joined to
4892     const void *tmpbuf = buf;
4893     uint16_t team, type, wins, losses, tks;
4894     char callsign[CallSignLen];
4895     char motto[MottoLen];
4896     tmpbuf = nboUnpackUShort(tmpbuf, type);
4897     tmpbuf = nboUnpackUShort(tmpbuf, team);
4898     tmpbuf = nboUnpackUShort(tmpbuf, wins);           // not used
4899     tmpbuf = nboUnpackUShort(tmpbuf, losses);         // not used
4900     tmpbuf = nboUnpackUShort(tmpbuf, tks);            // not used
4901     tmpbuf = nboUnpackString(tmpbuf, callsign, CallSignLen);  // not used
4902     tmpbuf = nboUnpackString(tmpbuf, motto, MottoLen);
4903 
4904     // if server assigns us a different team, display a message
4905     std::string teamMsg;
4906     if (myTank->getTeam() != AutomaticTeam)
4907     {
4908         teamMsg = TextUtils::format("%s team was unavailable, you were joined ",
4909                                     Team::getName(myTank->getTeam()));
4910         if ((TeamColor)team == ObserverTeam)
4911             teamMsg += "as an Observer";
4912         else
4913         {
4914             teamMsg += TextUtils::format("to the %s",
4915                                          Team::getName((TeamColor)team));
4916         }
4917     }
4918     else
4919     {
4920         if ((TeamColor)team == ObserverTeam)
4921             teamMsg = "You were joined as an observer";
4922         else
4923         {
4924             if (team != RogueTeam)
4925                 teamMsg = TextUtils::format("You joined the %s",
4926                                             Team::getName((TeamColor)team));
4927             else
4928                 teamMsg = TextUtils::format("You joined as a %s",
4929                                             Team::getName((TeamColor)team));
4930         }
4931     }
4932     if (myTank->getTeam() != (TeamColor)team)
4933     {
4934         myTank->setTeam((TeamColor)team);
4935         hud->setAlert(1, teamMsg.c_str(), 8.0f,
4936                       (TeamColor)team==ObserverTeam?true:false);
4937         addMessage(NULL, teamMsg.c_str(), 3, true);
4938     }
4939 
4940     // observer colors are actually cyan, make them black
4941     const bool observer = (myTank->getTeam() == ObserverTeam);
4942     const GLfloat* borderColor;
4943     if (observer)
4944     {
4945         static const GLfloat black[4] = {0.0f, 0.0f, 0.0f, 1.0f};
4946         borderColor = black;
4947     }
4948     else
4949         borderColor = Team::getRadarColor(myTank->getTeam());
4950     controlPanel->setControlColor(borderColor);
4951     radar->setControlColor(borderColor);
4952 
4953     if ((myTank->getTeam() == ObserverTeam) || devDriving)
4954     {
4955         const std::string roamStr = BZDB.get("roamView");
4956         Roaming::RoamingView roamView = ROAM.parseView(roamStr);
4957         if (roamView <= Roaming::roamViewDisabled)
4958             roamView = Roaming::roamViewFP;
4959         ROAM.setMode(roamView);
4960         //    ROAM.resetCamera();
4961     }
4962     else
4963         ROAM.setMode(Roaming::roamViewDisabled);
4964 
4965     myTank->setMotto(motto);  // use motto provided by the server
4966 
4967     setTankFlags();
4968 
4969     // clear now invalid token
4970     startupInfo.token[0] = '\0';
4971 
4972     // add robot tanks
4973 #if defined(ROBOT)
4974     addRobots();
4975 #endif
4976 
4977     // resize background and adjust time (this is needed even if we
4978     // don't sync with the server)
4979     sceneRenderer->getBackground()->resize();
4980     float syncTime = BZDB.eval(StateDatabase::BZDB_SYNCTIME);
4981     if (syncTime < 0.0f)
4982         updateDaylight(epochOffset, *sceneRenderer);
4983     else
4984     {
4985         epochOffset = (double)syncTime;
4986         updateDaylight(epochOffset, *sceneRenderer);
4987     }
4988     lastEpochOffset = epochOffset;
4989 
4990     // restore the sound
4991     if (savedVolume != -1)
4992     {
4993         setSoundVolume(savedVolume);
4994         savedVolume = -1;
4995     }
4996 
4997     // initialize some other stuff
4998     updateFlag(Flags::Null);
4999     updateHighScores();
5000     hud->setHeading(myTank->getAngle());
5001     hud->setAltitude(myTank->getPosition()[2]);
5002     hud->setTimeLeft((uint32_t)~0);
5003     fireButton = false;
5004     firstLife = true;
5005 
5006     BZDB.setBool("displayMainFlags", true);
5007     BZDB.setBool("displayRadarFlags", true);
5008     BZDB.setBool("displayRadar", true);
5009     BZDB.setBool("displayConsole", true);
5010 
5011     entered = true;
5012 }
5013 
cleanWorldCache()5014 static void cleanWorldCache()
5015 {
5016     // setup the world cache limit
5017     int cacheLimit = (10 * 1024 * 1024);
5018     if (BZDB.isSet("worldCacheLimit"))
5019     {
5020         const int dbCacheLimit = BZDB.evalInt("worldCacheLimit");
5021         // the old limit was 100 Kbytes, too small
5022         if (dbCacheLimit == (100 * 1024))
5023             BZDB.setInt("worldCacheLimit", cacheLimit);
5024         else
5025             cacheLimit = dbCacheLimit;
5026     }
5027     else
5028         BZDB.setInt("worldCacheLimit", cacheLimit);
5029 
5030     const std::string worldPath = getCacheDirName();
5031 
5032     while (true)
5033     {
5034         char *oldestFile = NULL;
5035         int oldestSize = 0;
5036         int totalSize = 0;
5037 
5038 #ifdef _WIN32
5039         std::string pattern = worldPath + "*.bwc";
5040         WIN32_FIND_DATA findData;
5041         HANDLE h = FindFirstFile(pattern.c_str(), &findData);
5042         if (h != INVALID_HANDLE_VALUE)
5043         {
5044             FILETIME oldestTime;
5045             while (FindNextFile(h, &findData))
5046             {
5047                 if ((oldestFile == NULL) ||
5048                         (CompareFileTime(&oldestTime, &findData.ftLastAccessTime) > 0))
5049                 {
5050                     if (oldestFile)
5051                         free(oldestFile);
5052                     oldestFile = strdup(findData.cFileName);
5053                     oldestSize = findData.nFileSizeLow;
5054                     oldestTime = findData.ftLastAccessTime;
5055                 }
5056                 totalSize += findData.nFileSizeLow;
5057             }
5058             FindClose(h);
5059         }
5060 #else
5061         DIR *directory = opendir(worldPath.c_str());
5062         if (directory)
5063         {
5064             struct dirent* contents;
5065             struct stat statbuf;
5066             time_t oldestTime = 0;
5067             while ((contents = readdir(directory)))
5068             {
5069                 const std::string filename = contents->d_name;
5070                 const std::string fullname = worldPath + filename;
5071                 stat(fullname.c_str(), &statbuf);
5072                 if (S_ISREG(statbuf.st_mode) && (filename.size() > 4) &&
5073                         (filename.substr(filename.size() - 4) == ".bwc"))
5074                 {
5075                     if ((oldestFile == NULL) || (statbuf.st_atime < oldestTime))
5076                     {
5077                         if (oldestFile)
5078                             free(oldestFile);
5079                         oldestFile = strdup(contents->d_name);
5080                         oldestSize = statbuf.st_size;
5081                         oldestTime = statbuf.st_atime;
5082                     }
5083                     totalSize += statbuf.st_size;
5084                 }
5085             }
5086             closedir(directory);
5087         }
5088 #endif
5089 
5090         // any valid cache files?
5091         if (oldestFile == NULL)
5092             return;
5093 
5094         // is the cache small enough?
5095         if (totalSize < cacheLimit)
5096         {
5097             if (oldestFile != NULL)
5098             {
5099                 free(oldestFile);
5100                 oldestFile = NULL;
5101             }
5102             return;
5103         }
5104 
5105         // remove the oldest file
5106         logDebugMessage(1,"cleanWorldCache: removed %s\n", oldestFile);
5107         remove((worldPath + oldestFile).c_str());
5108         free(oldestFile);
5109         totalSize -= oldestSize;
5110     }
5111 }
5112 
5113 
markOld(std::string & fileName)5114 static void markOld(std::string &fileName)
5115 {
5116 #ifdef _WIN32
5117     FILETIME ft;
5118     HANDLE h = CreateFile(fileName.c_str(),
5119                           FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA, 0, NULL,
5120                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
5121     if (h != INVALID_HANDLE_VALUE)
5122     {
5123         SYSTEMTIME st;
5124         memset(&st, 0, sizeof(st));
5125         st.wYear = 1900;
5126         st.wMonth = 1;
5127         st.wDay = 1;
5128         SystemTimeToFileTime(&st, &ft);
5129         SetFileTime(h, &ft, &ft, &ft);
5130         GetLastError();
5131         CloseHandle(h);
5132     }
5133 #else
5134     struct utimbuf times;
5135     times.actime = 0;
5136     times.modtime = 0;
5137     utime(fileName.c_str(), &times);
5138 #endif
5139 }
5140 
5141 
sendFlagNegotiation()5142 static void sendFlagNegotiation()
5143 {
5144     char msg[MaxPacketLen];
5145     FlagTypeMap::iterator i;
5146     char *buf = msg;
5147 
5148     /* Send MsgNegotiateFlags to the server with
5149      * the abbreviations for all the flags we support.
5150      */
5151     for (i = FlagType::getFlagMap().begin();
5152             i != FlagType::getFlagMap().end();
5153             ++i)
5154         buf = (char*) i->second->pack(buf);
5155     serverLink->send(MsgNegotiateFlags, buf - msg, msg);
5156 }
5157 
5158 
5159 #if defined(FIXME) && defined(ROBOT)
saveRobotInfo(Playerid id,void * msg)5160 static void saveRobotInfo(Playerid id, void *msg)
5161 {
5162     for (int i = 0; i < numRobots; i++)
5163         if (robots[i])
5164         {
5165             void *tmpbuf = msg;
5166             uint16_t team, type, wins, losses, tks;
5167             char callsign[CallSignLen];
5168             char motto[MottoLen];
5169             tmpbuf = nboUnpackUShort(tmpbuf, type);
5170             tmpbuf = nboUnpackUShort(tmpbuf, team);
5171             tmpbuf = nboUnpackUShort(tmpbuf, wins);
5172             tmpbuf = nboUnpackUShort(tmpbuf, losses);
5173             tmpbuf = nboUnpackUShort(tmpbuf, tks);
5174             tmpbuf = nboUnpackString(tmpbuf, callsign, CallSignLen);
5175             tmpbuf = nboUnpackString(tmpbuf, motto, MottoLen);
5176             std::cerr << "id " << id.port << ':' <<
5177                       id.number << ':' <<
5178                       callsign << ' ' <<
5179                       robots[i]->getId().port << ':' <<
5180                       robots[i]->getId().number << ':' <<
5181                       robots[i]->getCallsign() << std::endl;
5182             if (strncmp(robots[i]->getCallSign(), callsign, CallSignLen))
5183             {
5184                 // check for real robot id
5185                 char buffer[100];
5186                 snprintf(buffer, 100, "id test %p %p %p %8.8x %8.8x\n",
5187                          robots[i], tmpbuf, msg, *(int *)tmpbuf, *((int *)tmpbuf + 1));
5188                 std::cerr << buffer;
5189                 if (tmpbuf < (char *)msg + len)
5190                 {
5191                     PlayerId id;
5192                     tmpbuf = nboUnpackUByte(tmpbuf, id);
5193                     robots[i]->id.serverHost = id.serverHost;
5194                     robots[i]->id.port = id.port;
5195                     robots[i]->id.number = id.number;
5196                     robots[i]->server->send(MsgIdAck, 0, NULL);
5197                 }
5198             }
5199         }
5200 }
5201 #endif
5202 
resetServerVar(const std::string & name,void *)5203 static void resetServerVar(const std::string& name, void*)
5204 {
5205     // reset server-side variables
5206     if (BZDB.getPermission(name) == StateDatabase::Locked)
5207     {
5208         const std::string defval = BZDB.getDefault(name);
5209         BZDB.set(name, defval);
5210     }
5211 }
5212 
leaveGame()5213 void        leaveGame()
5214 {
5215     entered = false;
5216     joiningGame = false;
5217 
5218     // no more radar
5219     //  radar->setWorld(NULL);
5220 
5221     //  controlPanel->setRadarRenderer(NULL);
5222     /*
5223       delete radar;
5224       radar = NULL;
5225     */
5226 
5227 #if defined(ROBOT)
5228     // shut down robot connections
5229     int i;
5230     for (i = 0; i < numRobots; i++)
5231     {
5232         if (robots[i] && robotServer[i])
5233             robotServer[i]->send(MsgExit, 0, NULL);
5234         delete robots[i];
5235         delete robotServer[i];
5236         robots[i] = NULL;
5237         robotServer[i] = NULL;
5238     }
5239     numRobots = 0;
5240 
5241     const int count = obstacleList.size();
5242     for (i = 0; i < count; i++)
5243         delete obstacleList[i];
5244     obstacleList.clear();
5245 #endif
5246 
5247     // my tank goes away
5248     const bool sayGoodbye = (myTank != NULL);
5249     LocalPlayer::setMyTank(NULL);
5250     delete myTank;
5251     myTank = NULL;
5252 
5253     // reset the daylight time
5254     const bool syncTime = (BZDB.eval(StateDatabase::BZDB_SYNCTIME) >= 0.0f);
5255     const bool fixedTime = BZDB.isSet("fixedTime");
5256     if (syncTime)
5257     {
5258         // return to the desired user time
5259         epochOffset = userTimeEpochOffset;
5260     }
5261     else if (fixedTime)
5262     {
5263         // save the current user time
5264         userTimeEpochOffset = epochOffset;
5265     }
5266     else
5267     {
5268         // revert back to when the client was started?
5269         epochOffset = userTimeEpochOffset;
5270     }
5271     updateDaylight(epochOffset, *sceneRenderer);
5272     lastEpochOffset = epochOffset;
5273     BZDB.set(StateDatabase::BZDB_SYNCTIME,
5274              BZDB.getDefault(StateDatabase::BZDB_SYNCTIME));
5275 
5276     // flush downloaded textures (before the BzMaterials are nuked)
5277     Downloads::removeTextures();
5278 
5279     // delete world
5280     World::setWorld(NULL);
5281     delete world;
5282     world = NULL;
5283     teams = NULL;
5284     curMaxPlayers = 0;
5285     numFlags = 0;
5286     remotePlayers = NULL;
5287 
5288     // update UI
5289     hud->setPlaying(false);
5290     hud->setCracks(false);
5291     hud->setPlayerHasHighScore(false);
5292     hud->setTeamHasHighScore(false);
5293     hud->setHeading(0.0f);
5294     hud->setAltitude(0.0f);
5295     hud->setAltitudeTape(false);
5296 
5297     // shut down server connection
5298     if (sayGoodbye) serverLink->send(MsgExit, 0, NULL);
5299     ServerLink::setServer(NULL);
5300     delete serverLink;
5301     serverLink = NULL;
5302     serverNetworkAddress = Address();
5303 
5304     // reset viewpoint
5305     float eyePoint[3], targetPoint[3];
5306     eyePoint[0] = 0.0f;
5307     eyePoint[1] = 0.0f;
5308     eyePoint[2] = 0.0f + BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT);
5309     targetPoint[0] = eyePoint[0] - 1.0f;
5310     targetPoint[1] = eyePoint[1] + 0.0f;
5311     targetPoint[2] = eyePoint[2] + 0.0f;
5312     sceneRenderer->getViewFrustum().setProjection((float)(60.0 * M_PI / 180.0),
5313             NearPlaneNormal,
5314             FarPlaneDefault,
5315             FarDeepPlaneDefault,
5316             mainWindow->getWidth(),
5317             mainWindow->getHeight(),
5318             mainWindow->getViewHeight());
5319     sceneRenderer->getViewFrustum().setView(eyePoint, targetPoint);
5320 
5321     // reset some flags
5322     gameOver = false;
5323     serverError = false;
5324     serverDied = false;
5325 
5326     // delete scene database (after the world has been destroyed)
5327     sceneRenderer->setSceneDatabase(NULL);
5328 
5329     // reset the BZDB variables
5330     BZDB.iterate(resetServerVar, NULL);
5331 
5332     // purge any custom flags we may have accumulated
5333     Flags::clearCustomFlags();
5334 
5335     return;
5336 }
5337 
5338 
joinInternetGame()5339 static void joinInternetGame()
5340 {
5341     // check server address
5342     if (serverNetworkAddress.isAny())
5343     {
5344         HUDDialogStack::get()->setFailedMessage("Server not found");
5345         return;
5346     }
5347 
5348     // check for a local server block
5349     ServerAccessList.reload();
5350     std::vector<std::string> nameAndIp;
5351     nameAndIp.push_back(startupInfo.serverName);
5352     nameAndIp.push_back(serverNetworkAddress.getDotNotation());
5353     if (!ServerAccessList.authorized(nameAndIp))
5354     {
5355         HUDDialogStack::get()->setFailedMessage("Server Access Denied Locally");
5356         std::string msg = ColorStrings[WhiteColor];
5357         msg += "NOTE: ";
5358         msg += ColorStrings[GreyColor];
5359         msg += "server access is controlled by ";
5360         msg += ColorStrings[YellowColor];
5361         msg += ServerAccessList.getFilePath();
5362         addMessage(NULL, msg);
5363         return;
5364     }
5365 
5366     // open server
5367     ServerLink* _serverLink = new ServerLink(serverNetworkAddress,
5368             startupInfo.serverPort);
5369 
5370 #if defined(ROBOT)
5371     numRobots = 0;
5372 #endif
5373 
5374     serverLink = _serverLink;
5375 
5376     // assume everything's okay for now
5377     serverDied = false;
5378     serverError = false;
5379 
5380     if (!serverLink)
5381     {
5382         HUDDialogStack::get()->setFailedMessage("Memory error");
5383         return;
5384     }
5385 
5386     // printError("Join Game");
5387     // check server
5388     if (serverLink->getState() != ServerLink::Okay)
5389     {
5390         switch (serverLink->getState())
5391         {
5392         case ServerLink::BadVersion:
5393         {
5394             char versionError[37];
5395             snprintf(versionError, sizeof(versionError), "Incompatible server version %s", serverLink->getVersion());
5396             HUDDialogStack::get()->setFailedMessage(versionError);
5397             break;
5398         }
5399 
5400         // you got banned
5401         case ServerLink::Refused:
5402         {
5403             const std::string& rejmsg = serverLink->getRejectionMessage();
5404 
5405             // add to the HUD
5406             std::string msg = ColorStrings[RedColor];
5407             msg += "You have been banned from this server";
5408             HUDDialogStack::get()->setFailedMessage(msg.c_str());
5409 
5410             // add to the console
5411             msg = ColorStrings[RedColor];
5412             msg += "You have been banned from this server:";
5413             addMessage(NULL, msg);
5414             msg = ColorStrings[YellowColor];
5415             msg += rejmsg;
5416             addMessage(NULL, msg);
5417 
5418             break;
5419         }
5420 
5421         case ServerLink::Rejected:
5422             // the server is probably full or the game is over.  if not then
5423             // the server is having network problems.
5424             HUDDialogStack::get()->setFailedMessage
5425             ("Game is full or over.  Try again later.");
5426             break;
5427 
5428         case ServerLink::SocketError:
5429             HUDDialogStack::get()->setFailedMessage("Error connecting to server.");
5430             break;
5431 
5432         case ServerLink::CrippledVersion:
5433             // can't connect to (otherwise compatible) non-crippled server
5434             HUDDialogStack::get()->setFailedMessage
5435             ("Cannot connect to full version server.");
5436             break;
5437 
5438         default:
5439             HUDDialogStack::get()->setFailedMessage
5440             (TextUtils::format
5441              ("Internal error connecting to server (error code %d).",
5442               serverLink->getState()).c_str());
5443             break;
5444         }
5445         return;
5446     }
5447 
5448     // use parallel UDP if desired and using server relay
5449     if (startupInfo.useUDPconnection)
5450         serverLink->sendUDPlinkRequest();
5451     else
5452         printError("No UDP connection, see Options to enable.");
5453 
5454     HUDDialogStack::get()->setFailedMessage("Connection Established...");
5455 
5456     sendFlagNegotiation();
5457     joiningGame = true;
5458     scoreboard->huntReset();
5459     GameTime::reset();
5460 }
5461 
5462 
addVarToAutoComplete(const std::string & name,void * UNUSED (userData))5463 static void addVarToAutoComplete(const std::string& name, void* UNUSED(userData))
5464 {
5465     if ((name.size() <= 0) || (name[0] != '_'))
5466     {
5467         return; // we're skipping "poll"
5468     }
5469     if (BZDB.getPermission(name) == StateDatabase::Server)
5470         completer.registerWord(name);
5471     return;
5472 }
5473 
joinInternetGame2()5474 static void joinInternetGame2()
5475 {
5476     justJoined = true;
5477 
5478     HUDDialogStack::get()->setFailedMessage("Entering game...");
5479 
5480     ServerLink::setServer(serverLink);
5481     World::setWorld(world);
5482 
5483     // prep teams
5484     teams = world->getTeams();
5485 
5486     // prep players
5487     curMaxPlayers = 0;
5488     remotePlayers = world->getPlayers();
5489 
5490     // reset the autocompleter
5491     completer.setDefaults();
5492     BZDB.iterate(addVarToAutoComplete, NULL);
5493 
5494     // prep flags
5495     numFlags = world->getMaxFlags();
5496 
5497     // make scene database
5498     setSceneDatabase();
5499     mainWindow->getWindow()->yieldCurrent();
5500 
5501     // make radar
5502     //  radar = new RadarRenderer(*sceneRenderer, *world);
5503     //  mainWindow->getWindow()->yieldCurrent();
5504     radar->setWorld(world);
5505     controlPanel->setRadarRenderer(radar);
5506     controlPanel->resize();
5507 
5508     // make local player
5509     myTank = new LocalPlayer(serverLink->getId(), startupInfo.callsign,
5510                              startupInfo.motto);
5511     myTank->setTeam(startupInfo.team);
5512     LocalPlayer::setMyTank(myTank);
5513 
5514     if (world->allowRabbit() && myTank->getTeam() != ObserverTeam)
5515         myTank->setTeam(HunterTeam);
5516 
5517     // tell server we want to join
5518     serverLink->sendEnter(TankPlayer, myTank->getTeam(),
5519                           myTank->getCallSign(),
5520                           myTank->getMotto(),
5521                           startupInfo.token);
5522     startupInfo.token[0] = '\0';
5523 
5524     // hopefully it worked!  pop all the menus.
5525     HUDDialogStack* stack = HUDDialogStack::get();
5526     while (stack->isActive())
5527         stack->pop();
5528     joiningGame = false;
5529 }
5530 
5531 
renderDialog()5532 static void     renderDialog()
5533 {
5534     if (HUDDialogStack::get()->isActive())
5535     {
5536         const int width = mainWindow->getWidth();
5537         const int height = mainWindow->getHeight();
5538         const int ox = mainWindow->getOriginX();
5539         const int oy = mainWindow->getOriginY();
5540         glScissor(ox, oy, width, height);
5541         glMatrixMode(GL_PROJECTION);
5542         mainWindow->setProjectionPlay();
5543         glMatrixMode(GL_MODELVIEW);
5544         glPushMatrix();
5545         glLoadIdentity();
5546         OpenGLGState::resetState();
5547         HUDDialogStack::get()->render();
5548         glPopMatrix();
5549     }
5550 }
5551 
5552 
checkDirtyControlPanel(ControlPanel * cp)5553 static void checkDirtyControlPanel(ControlPanel *cp)
5554 {
5555     if (cp)
5556     {
5557         if (HUDDialogStack::get()->isActive())
5558             cp->invalidate();
5559     }
5560     return;
5561 }
5562 
5563 
renderRoamMouse()5564 static void renderRoamMouse()
5565 {
5566     if (!ROAM.isRoaming() ||
5567             !myTank || (myTank->getTeam() != ObserverTeam) ||
5568             !(leftMouseButton || rightMouseButton || middleMouseButton))
5569         return;
5570 
5571     const int sx = mainWindow->getWidth();
5572     const int sy = mainWindow->getHeight();
5573     const int ox = mainWindow->getOriginX();
5574     const int oy = mainWindow->getOriginY();
5575     int mx, my;
5576     mainWindow->getWindow()->getMouse(mx, my);
5577     my = sy - my - 1; // flip the y axis
5578     const int xc = ox + (sx / 2);
5579     const int y2 = oy + (mainWindow->getViewHeight() / 2);
5580     const int yc = (sy - y2 - 1); // flip the y axis
5581 
5582     glPushAttrib(GL_ALL_ATTRIB_BITS);
5583 
5584     glScissor(ox, oy, sx, sy);
5585     glMatrixMode(GL_PROJECTION);
5586     glPushMatrix();
5587     mainWindow->setProjectionPlay();
5588     glMatrixMode(GL_MODELVIEW);
5589     glPushMatrix();
5590     glLoadIdentity();
5591 
5592     glShadeModel(GL_SMOOTH);
5593     glEnable(GL_BLEND);
5594     glEnable(GL_LINE_SMOOTH);
5595     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
5596 
5597     static const float color0[4] = { 0.0f, 0.0f, 0.0f, 0.1f };
5598     static const float color1[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
5599 
5600     glLineWidth(1.49f);
5601     glBegin(GL_LINES);
5602     glColor4fv(color0);
5603     glVertex2i(xc, yc);
5604     glColor4fv(color1);
5605     glVertex2i(mx, my);
5606     glEnd();
5607 
5608     glMatrixMode(GL_PROJECTION);
5609     glPopMatrix();
5610     glMatrixMode(GL_MODELVIEW);
5611     glPopMatrix();
5612 
5613     glPopAttrib();
5614 }
5615 
5616 
drawUI()5617 static void drawUI()
5618 {
5619     // setup the triangle counts  (FIXME: hackish)
5620     if (showFPS && showDrawTime)
5621     {
5622         hud->setFrameTriangleCount(sceneRenderer->getFrameTriangleCount());
5623         // NOTE:  the radar triangle count is actually from the previous frame
5624         if (radar)
5625             hud->setFrameRadarTriangleCount(radar->getFrameTriangleCount());
5626         else
5627             hud->setFrameRadarTriangleCount(0);
5628     }
5629     else
5630     {
5631         hud->setFrameTriangleCount(0);
5632         hud->setFrameRadarTriangleCount(0);
5633     }
5634 
5635     // update the HUD (player list, alerts)
5636     if (World::getWorld() && hud)
5637         hud->render(*sceneRenderer);
5638 
5639     // draw the control panel
5640     if (controlPanel)
5641         controlPanel->render(*sceneRenderer);
5642 
5643     // draw the radar
5644     if (radar)
5645     {
5646         const bool showBlankRadar = !myTank || (myTank && myTank->isPaused());
5647         const bool observer = myTank && (myTank->getTeam() == ObserverTeam);
5648         radar->render(*sceneRenderer, showBlankRadar, observer);
5649     }
5650 
5651     // update the HUD (menus)
5652     renderDialog();
5653 
5654     // render the drag-line
5655     renderRoamMouse();
5656 
5657     return;
5658 }
5659 
5660 
5661 //
5662 // stuff to draw a frame
5663 //
5664 
trackPlayerShot(Player * target,float * eyePoint,float * targetPoint)5665 static bool trackPlayerShot(Player* target,
5666                             float* eyePoint, float* targetPoint)
5667 {
5668     // follow the first shot
5669     if (BZDB.isTrue("trackShots"))
5670     {
5671         const int maxShots = target->getMaxShots();
5672         const ShotPath* sp = NULL;
5673         // look for the oldest active shot
5674         float remaining = +MAXFLOAT;
5675         for (int s = 0; s < maxShots; s++)
5676         {
5677             const ShotPath* spTmp = target->getShot(s);
5678             if (spTmp != NULL)
5679             {
5680                 const float t = float(spTmp->getReloadTime() -
5681                                       (spTmp->getCurrentTime() - spTmp->getStartTime()));
5682                 if ((t > 0.0f) && (t < remaining))
5683                 {
5684                     sp = spTmp;
5685                     remaining = t;
5686                 }
5687             }
5688         }
5689         if (sp != NULL)
5690         {
5691             const float* pos = sp->getPosition();
5692             const float* vel = sp->getVelocity();
5693             const float speed = sqrtf(vel[0]*vel[0] + vel[1]*vel[1] + vel[2]*vel[2]);
5694             if (speed > 0.0f)
5695             {
5696                 const float ilen = 1.0f / speed;
5697                 const float dir[3] = {ilen * vel[0], ilen * vel[1], ilen * vel[2]};
5698                 float topDir[3] = {1.0f, 0.0f, 0.0f};
5699                 const float hlen = sqrtf(dir[0]*dir[0] + dir[1]*dir[1]);
5700                 if (hlen > 0.0f)
5701                 {
5702                     topDir[2] = hlen;
5703                     const float hfactor = -fabsf(dir[2] / hlen);
5704                     topDir[0] = hfactor * dir[0];
5705                     topDir[1] = hfactor * dir[1];
5706                 }
5707                 const float offset = -10.0f;
5708                 const float tOffset = +2.0f;
5709                 eyePoint[0] = pos[0] + (offset * dir[0]) + (tOffset * topDir[0]);
5710                 eyePoint[1] = pos[1] + (offset * dir[1]) + (tOffset * topDir[1]);
5711                 eyePoint[2] = pos[2] + (offset * dir[2]) + (tOffset * topDir[2]);
5712                 targetPoint[0] = eyePoint[0] + dir[0];
5713                 targetPoint[1] = eyePoint[1] + dir[1];
5714                 targetPoint[2] = eyePoint[2] + dir[2];
5715                 return true;
5716             }
5717         }
5718     }
5719     return false;
5720 }
5721 
setupNearPlane()5722 static void setupNearPlane()
5723 {
5724     NearPlane = NearPlaneNormal;
5725 
5726     const bool showTreads = BZDB.isTrue("showTreads");
5727     if (!showTreads || !myTank)
5728         return;
5729 
5730     const Player* tank = myTank;
5731     if (ROAM.isRoaming())
5732     {
5733         if (ROAM.getMode() != Roaming::roamViewFP)
5734             return;
5735         if (!devDriving)
5736             tank = ROAM.getTargetTank();
5737     }
5738     if (tank == NULL)
5739         return;
5740 
5741     const float halfLength = 0.5f * BZDBCache::tankLength;
5742     const float length = tank->getDimensions()[1];
5743     if (fabsf(length - halfLength) > 0.1f)
5744         NearPlane = NearPlaneClose;
5745 
5746     return;
5747 }
5748 
5749 
setupFarPlane()5750 static void setupFarPlane()
5751 {
5752     FarPlane = FarPlaneScale * BZDBCache::worldSize;
5753     FarPlaneCull = false;
5754     FarDeepPlane = FarPlane * FarDeepPlaneScale;
5755 
5756     const bool mapFog = BZDB.get(StateDatabase::BZDB_FOGMODE) != "none";
5757 
5758     float farDist = FarPlane;
5759 
5760     if (BZDB.get("_cullDist") == "fog")
5761     {
5762         if (mapFog && !BZDB.isTrue("_fogNoSky"))
5763         {
5764             const float fogMargin = 1.01f;
5765             const std::string& fogMode = BZDB.get("_fogMode");
5766             if (fogMode == "linear")
5767                 farDist = fogMargin * BZDB.eval("_fogEnd");
5768             else
5769             {
5770                 const float density = BZDB.eval("_fogDensity");
5771                 if (density > 0.0f)
5772                 {
5773                     const float fogFactor = 0.01f;
5774                     if (fogMode == "exp2")
5775                         farDist = fogMargin * sqrtf(-logf(fogFactor)) / density;
5776                     else   // default to 'exp'
5777                         farDist = fogMargin * -logf(fogFactor) / density;
5778                 }
5779                 else
5780                 {
5781                     // default far plane
5782                 }
5783             }
5784         }
5785         else
5786         {
5787             // default far plane
5788         }
5789     }
5790     else
5791     {
5792         const float dist = BZDB.eval("_cullDist");
5793         if (dist > 0.0f)
5794             farDist = dist;
5795         else
5796         {
5797             // default far plane
5798         }
5799     }
5800 
5801     if (farDist < FarPlane)
5802     {
5803         FarPlane = farDist;
5804         FarPlaneCull = true;
5805     }
5806 
5807     return;
5808 }
5809 
5810 
drawFrame(const float dt)5811 void drawFrame(const float dt)
5812 {
5813     // get view type (constant for entire game)
5814     static SceneRenderer::ViewType viewType = sceneRenderer->getViewType();
5815     // get media object
5816     static BzfMedia* media = PlatformFactory::getMedia();
5817 
5818     static const float    defaultPos[3] = { 0.0f, 0.0f, 0.0f };
5819     static const float    defaultDir[3] = { 1.0f, 0.0f, 0.0f };
5820     static int frameCount = 0;
5821     static float cumTime = 0.0f;
5822 
5823     const float* myTankPos;
5824     const float* myTankDir;
5825     GLfloat fov;
5826     GLfloat eyePoint[3];
5827     GLfloat targetPoint[3];
5828 
5829     checkDirtyControlPanel(controlPanel);
5830 
5831     if (!unmapped)
5832     {
5833         // compute fps
5834         frameCount++;
5835         cumTime += float(dt);
5836         if (cumTime >= 2.0)
5837         {
5838             if (showFPS) hud->setFPS(float(frameCount) / cumTime);
5839             cumTime = 0.00000001f;
5840             frameCount = 0;
5841         }
5842 
5843         // drift clouds
5844         sceneRenderer->getBackground()->addCloudDrift(1.0f * dt, 0.731f * dt);
5845 
5846         // get tank camera info
5847         float muzzleHeight;
5848         if (!myTank)
5849         {
5850             myTankPos = defaultPos;
5851             myTankDir = defaultDir;
5852             muzzleHeight = BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT);
5853             fov = BZDB.eval("defaultFOV");
5854         }
5855         else
5856         {
5857             myTankPos = myTank->getPosition();
5858             myTankDir = myTank->getForward();
5859             muzzleHeight = myTank->getMuzzleHeight();
5860 
5861             if (myTank->getFlag() == Flags::WideAngle)
5862                 fov = 120.0f;
5863             else
5864                 fov = BZDB.eval("displayFOV");
5865             if (viewType == SceneRenderer::ThreeChannel)
5866                 fov *= 0.75f;
5867         }
5868         fov *= (float)(M_PI / 180.0);
5869 
5870         // set projection and view
5871         eyePoint[0] = myTankPos[0];
5872         eyePoint[1] = myTankPos[1];
5873         eyePoint[2] = myTankPos[2] + muzzleHeight;
5874         targetPoint[0] = eyePoint[0] + myTankDir[0];
5875         targetPoint[1] = eyePoint[1] + myTankDir[1];
5876         targetPoint[2] = eyePoint[2] + myTankDir[2];
5877 
5878         if (devDriving || ROAM.isRoaming())
5879         {
5880             hud->setAltitude(-1.0f);
5881             float roamViewAngle;
5882             const Roaming::RoamingCamera* roam = ROAM.getCamera();
5883             if (!(ROAM.getMode() == Roaming::roamViewFree) &&
5884                     (ROAM.getTargetTank() || (devDriving && myTank)))
5885             {
5886                 Player *target;
5887                 if (!devDriving)
5888                     target = ROAM.getTargetTank();
5889                 else
5890                     target = myTank;
5891                 const float *targetTankDir = target->getForward();
5892                 // fixed camera tracking target
5893                 if (ROAM.getMode() == Roaming::roamViewTrack)
5894                 {
5895                     eyePoint[0] = roam->pos[0];
5896                     eyePoint[1] = roam->pos[1];
5897                     eyePoint[2] = roam->pos[2];
5898                     targetPoint[0] = target->getPosition()[0];
5899                     targetPoint[1] = target->getPosition()[1];
5900                     targetPoint[2] = target->getPosition()[2] +
5901                                      target->getMuzzleHeight();
5902                 }
5903                 // camera following target
5904                 else if (ROAM.getMode() == Roaming::roamViewFollow)
5905                 {
5906                     if (!trackPlayerShot(target, eyePoint, targetPoint))
5907                     {
5908                         const bool slowKB = BZDB.isTrue("slowKeyboard");
5909                         if (slowKB == (BZDB.eval("roamSmoothTime") < 0.0f))
5910                         {
5911                             eyePoint[0] = target->getPosition()[0] - targetTankDir[0] * 40;
5912                             eyePoint[1] = target->getPosition()[1] - targetTankDir[1] * 40;
5913                             eyePoint[2] = target->getPosition()[2] + muzzleHeight * 6;
5914                             targetPoint[0] = target->getPosition()[0];
5915                             targetPoint[1] = target->getPosition()[1];
5916                             targetPoint[2] = target->getPosition()[2];
5917                         }
5918                         else
5919                         {
5920                             // the same as for the roamViewTrack mode
5921                             eyePoint[0] = roam->pos[0];
5922                             eyePoint[1] = roam->pos[1];
5923                             eyePoint[2] = roam->pos[2];
5924                             targetPoint[0] = target->getPosition()[0];
5925                             targetPoint[1] = target->getPosition()[1];
5926                             targetPoint[2] = target->getPosition()[2] +
5927                                              target->getMuzzleHeight();
5928                             if (BZDB.isSet("followOffsetZ"))
5929                                 targetPoint[2] += BZDB.eval("followOffsetZ");
5930                         }
5931                     }
5932                 }
5933                 // target's view
5934                 else if (ROAM.getMode() == Roaming::roamViewFP)
5935                 {
5936                     if (!trackPlayerShot(target, eyePoint, targetPoint))
5937                     {
5938                         eyePoint[0] = target->getPosition()[0];
5939                         eyePoint[1] = target->getPosition()[1];
5940                         eyePoint[2] = target->getPosition()[2] + target->getMuzzleHeight();
5941                         targetPoint[0] = eyePoint[0] + targetTankDir[0];
5942                         targetPoint[1] = eyePoint[1] + targetTankDir[1];
5943                         targetPoint[2] = eyePoint[2] + targetTankDir[2];
5944                         hud->setAltitude(target->getPosition()[2]);
5945                     }
5946                 }
5947                 // track team flag
5948                 else if (ROAM.getMode() == Roaming::roamViewFlag)
5949                 {
5950                     Flag* targetFlag = ROAM.getTargetFlag();
5951                     eyePoint[0] = roam->pos[0];
5952                     eyePoint[1] = roam->pos[1];
5953                     eyePoint[2] = roam->pos[2];
5954                     targetPoint[0] = targetFlag->position[0];
5955                     targetPoint[1] = targetFlag->position[1];
5956                     targetPoint[2] = targetFlag->position[2];
5957                     if (targetFlag->status != FlagOnTank)
5958                         targetPoint[2] += muzzleHeight;
5959                     else
5960                     {
5961                         targetPoint[2] -= (BZDBCache::tankHeight -
5962                                            BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT));
5963                     }
5964                 }
5965                 roamViewAngle = (float) (atan2(targetPoint[1]-eyePoint[1],
5966                                                targetPoint[0]-eyePoint[0]) * 180.0f / M_PI);
5967             }
5968             // free Roaming
5969             else
5970             {
5971                 float dir[3];
5972                 dir[0] = cosf((float)(roam->phi * M_PI / 180.0)) * cosf((float)(roam->theta * M_PI / 180.0));
5973                 dir[1] = cosf((float)(roam->phi * M_PI / 180.0)) * sinf((float)(roam->theta * M_PI / 180.0));
5974                 dir[2] = sinf((float)(roam->phi * M_PI / 180.0));
5975                 eyePoint[0] = roam->pos[0];
5976                 eyePoint[1] = roam->pos[1];
5977                 eyePoint[2] = roam->pos[2];
5978                 targetPoint[0] = eyePoint[0] + dir[0];
5979                 targetPoint[1] = eyePoint[1] + dir[1];
5980                 targetPoint[2] = eyePoint[2] + dir[2];
5981                 roamViewAngle = roam->theta;
5982             }
5983             if (!devDriving)
5984             {
5985                 float virtPos[] = {eyePoint[0], eyePoint[1], 0};
5986                 if (myTank)
5987                     myTank->move(virtPos, (float)(roamViewAngle * M_PI / 180.0));
5988             }
5989             fov = (float)(roam->zoom * M_PI / 180.0);
5990             moveSoundReceiver(eyePoint[0], eyePoint[1], eyePoint[2], 0.0, false);
5991         }
5992 
5993         // only use a close plane for drawing in the
5994         // cockpit, and even then only for odd sized tanks
5995         setupNearPlane();
5996 
5997         // based on fog and _cullDist
5998         setupFarPlane();
5999 
6000         ViewFrustum& viewFrustum = sceneRenderer->getViewFrustum();
6001 
6002         viewFrustum.setProjection(fov, NearPlane, FarPlane, FarDeepPlane,
6003                                   mainWindow->getWidth(),
6004                                   mainWindow->getHeight(),
6005                                   mainWindow->getViewHeight());
6006         viewFrustum.setFarPlaneCull(FarPlaneCull);
6007 
6008         viewFrustum.setView(eyePoint, targetPoint);
6009 
6010         // add dynamic nodes
6011         SceneDatabase* scene = sceneRenderer->getSceneDatabase();
6012         if (scene && myTank)
6013         {
6014 
6015             int i;
6016             const bool seerView = (myTank->getFlag() == Flags::Seer);
6017             const bool showTreads = BZDB.isTrue("showTreads");
6018 
6019             // add my tank if required
6020             const bool inCockpit = (!devDriving || (ROAM.getMode() == Roaming::roamViewFP));
6021             const bool showMyTreads = showTreads ||
6022                                       (devDriving && (ROAM.getMode() != Roaming::roamViewFP));
6023             myTank->addToScene(scene, myTank->getTeam(),
6024                                inCockpit, seerView,
6025                                showMyTreads, showMyTreads /*showIDL*/);
6026 
6027             // add my shells
6028             myTank->addShots(scene, false);
6029 
6030             // add server shells
6031             if (world)
6032                 world->getWorldWeapons()->addShots(scene, false);
6033 
6034             // add antidote flag
6035             myTank->addAntidote(scene);
6036 
6037             // add flags
6038             world->addFlags(scene, seerView);
6039 
6040 
6041             // add other tanks and shells
6042             for (i = 0; i < curMaxPlayers; i++)
6043             {
6044                 if (remotePlayers[i])
6045                 {
6046                     const bool colorblind = (myTank->getFlag() == Flags::Colorblindness);
6047                     remotePlayers[i]->addShots(scene, colorblind);
6048 
6049                     TeamColor effectiveTeam = RogueTeam;
6050                     if (!colorblind)
6051                     {
6052                         if ((remotePlayers[i]->getFlag() == Flags::Masquerade)
6053                                 && (myTank->getFlag() != Flags::Seer)
6054                                 && (myTank->getTeam() != ObserverTeam))
6055                             effectiveTeam = myTank->getTeam();
6056                         else
6057                             effectiveTeam = remotePlayers[i]->getTeam();
6058                     }
6059 
6060                     const bool inCockpt  = ROAM.isRoaming() && !devDriving &&
6061                                            (ROAM.getMode() == Roaming::roamViewFP) &&
6062                                            ROAM.getTargetTank() &&
6063                                            (ROAM.getTargetTank()->getId() == i);
6064                     const bool showPlayer = !inCockpt || showTreads;
6065 
6066                     // add player tank if required
6067                     remotePlayers[i]->addToScene(scene, effectiveTeam,
6068                                                  inCockpt, seerView,
6069                                                  showPlayer, showPlayer /*showIDL*/);
6070                 }
6071             }
6072 
6073             // add explosions
6074             addExplosions(scene);
6075 
6076             // if inside a building, add some eighth dimension scene nodes.
6077             const std::vector<const Obstacle*>& list = myTank->getInsideBuildings();
6078             for (unsigned int n = 0; n < list.size(); n++)
6079             {
6080                 const Obstacle* obs = list[n];
6081                 const int nodeCount = obs->getInsideSceneNodeCount();
6082                 SceneNode** nodeList = obs->getInsideSceneNodeList();
6083                 for (int o = 0; o < nodeCount; o++)
6084                     scene->addDynamicNode(nodeList[o]);
6085             }
6086         }
6087 
6088         // turn blanking and inversion on/off as appropriate
6089         sceneRenderer->setBlank(myTank && (myTank->isPaused() ||
6090                                            myTank->getFlag() == Flags::Blindness));
6091         sceneRenderer->setInvert(myTank && myTank->isPhantomZoned());
6092 
6093         // turn on scene dimming when showing menu, when we're dead
6094         // and no longer exploding, or when we are in a building.
6095         bool insideDim = false;
6096         if (myTank)
6097         {
6098             const float hnp = 0.5f * NearPlane; // half near plane distance
6099             const float* eye = viewFrustum.getEye();
6100             const float* dir = viewFrustum.getDirection();
6101             float clipPos[3];
6102             clipPos[0] = eye[0] + (dir[0] * hnp);
6103             clipPos[1] = eye[1] + (dir[1] * hnp);
6104             clipPos[2] = eye[2];
6105             const Obstacle* obs;
6106             obs = world->inBuilding(clipPos, myTank->getAngle(), hnp, 0.0f, 0.0f);
6107             if (obs != NULL)
6108                 insideDim = true;
6109         }
6110         sceneRenderer->setDim(HUDDialogStack::get()->isActive() || insideDim ||
6111                               ((myTank && !ROAM.isRoaming() && !devDriving) &&
6112                                !myTank->isAlive() && !myTank->isExploding()));
6113 
6114         // turn on panel dimming when showing the menu (both radar and chat)
6115         if (HUDDialogStack::get()->isActive())
6116         {
6117             if (controlPanel)
6118                 controlPanel->setDimming(0.8f);
6119             if (radar)
6120                 radar->setDimming(0.8f);
6121         }
6122         else
6123         {
6124             if (controlPanel)
6125                 controlPanel->setDimming(0.0f);
6126             if (radar)
6127                 radar->setDimming(0.0f);
6128         }
6129 
6130         // set hud state
6131         hud->setDim(HUDDialogStack::get()->isActive());
6132         hud->setPlaying(myTank && (myTank->isAlive() && !myTank->isPaused()));
6133         hud->setRoaming(ROAM.isRoaming());
6134         hud->setCracks(myTank && !firstLife && !justJoined && !myTank->isAlive());
6135 
6136         // get frame start time
6137         if (showDrawTime)
6138         {
6139 #if defined(DEBUG_RENDERING)
6140             // get an accurate measure of frame time (at expense of frame rate)
6141             if (BZDB.isTrue("glFinish"))
6142                 glFinish();
6143 #endif
6144             media->stopwatch(true);
6145         }
6146 
6147         // so observers can have enhanced radar
6148         if (ROAM.isRoaming() && myTank && !devDriving)
6149         {
6150             if (ROAM.getMode() == Roaming::roamViewFP && ROAM.getTargetTank())
6151                 myTank->setZpos(ROAM.getTargetTank()->getPosition()[2]);
6152             else
6153                 myTank->setZpos(ROAM.getCamera()->pos[2]);
6154         }
6155 
6156         // let the hud save off the view matrix so it can do view projections
6157         if (hud)
6158         {
6159             hud->saveMatrixes(viewFrustum.getViewMatrix(),
6160                               viewFrustum.getProjectionMatrix());
6161         }
6162 
6163         // draw frame
6164         if (viewType == SceneRenderer::ThreeChannel)
6165         {
6166             // draw center channel
6167             sceneRenderer->render(false);
6168             drawUI();
6169 
6170             // set up for drawing left channel
6171             mainWindow->setQuadrant(MainWindow::LowerLeft);
6172             // FIXME -- this assumes up is along +z
6173             const float cFOV = cosf(fov);
6174             const float sFOV = sinf(fov);
6175             targetPoint[0] = eyePoint[0] + cFOV*myTankDir[0] - sFOV*myTankDir[1];
6176             targetPoint[1] = eyePoint[1] + cFOV*myTankDir[1] + sFOV*myTankDir[0];
6177             targetPoint[2] = eyePoint[2] + myTankDir[2];
6178             viewFrustum.setView(eyePoint, targetPoint);
6179 
6180             // draw left channel
6181             sceneRenderer->render(false, true, true);
6182 
6183             // set up for drawing right channel
6184             mainWindow->setQuadrant(MainWindow::LowerRight);
6185             // FIXME -- this assumes up is along +z
6186             targetPoint[0] = eyePoint[0] + cFOV*myTankDir[0] + sFOV*myTankDir[1];
6187             targetPoint[1] = eyePoint[1] + cFOV*myTankDir[1] - sFOV*myTankDir[0];
6188             targetPoint[2] = eyePoint[2] + myTankDir[2];
6189             viewFrustum.setView(eyePoint, targetPoint);
6190 
6191             // draw right channel
6192             sceneRenderer->render(true, true, true);
6193 
6194 #if defined(DEBUG_RENDERING)
6195             // set up for drawing rear channel
6196             mainWindow->setQuadrant(MainWindow::UpperLeft);
6197             // FIXME -- this assumes up is along +z
6198             targetPoint[0] = eyePoint[0] - myTankDir[0];
6199             targetPoint[1] = eyePoint[1] - myTankDir[1];
6200             targetPoint[2] = eyePoint[2] + myTankDir[2];
6201             viewFrustum.setView(eyePoint, targetPoint);
6202 
6203             // draw rear channel
6204             sceneRenderer->render(true, true, true);
6205 #endif
6206             // back to center channel
6207             mainWindow->setQuadrant(MainWindow::UpperRight);
6208         }
6209         else if (viewType == SceneRenderer::Stacked)
6210         {
6211             float EyeDisplacement = 0.25f * BZDBCache::tankWidth;
6212             float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE);
6213             if (BZDB.isSet("eyesep"))
6214                 EyeDisplacement = BZDB.eval("eyesep");
6215             if (BZDB.isSet("focal"))
6216                 FocalPlane = BZDB.eval("focal");
6217 
6218             // setup view for left eye
6219             viewFrustum.setOffset(EyeDisplacement, FocalPlane);
6220 
6221             // draw left eye's view
6222             sceneRenderer->render(false);
6223             drawUI();
6224 
6225             // set up view for right eye
6226             mainWindow->setQuadrant(MainWindow::UpperHalf);
6227             viewFrustum.setOffset(-EyeDisplacement, FocalPlane);
6228 
6229             // draw right eye's view
6230             sceneRenderer->render(true, true);
6231             drawUI();
6232 
6233             // draw common stuff
6234 
6235             // back to left channel
6236             mainWindow->setQuadrant(MainWindow::LowerHalf);
6237         }
6238         else if (viewType == SceneRenderer::Stereo)
6239         {
6240             float EyeDisplacement = 0.25f * BZDBCache::tankWidth;
6241             float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE);
6242             if (BZDB.isSet("eyesep"))
6243                 EyeDisplacement = BZDB.eval("eyesep");
6244             if (BZDB.isSet("focal"))
6245                 FocalPlane = BZDB.eval("focal");
6246 
6247             // setup view for left eye
6248 #ifdef USE_GL_STEREO
6249             glDrawBuffer(GL_BACK_LEFT);
6250 #endif
6251             viewFrustum.setOffset(EyeDisplacement, FocalPlane);
6252 
6253             // draw left eye's view
6254             sceneRenderer->render(false);
6255 #ifndef USE_GL_STEREO
6256             drawUI();
6257 #endif
6258 
6259             // set up view for right eye
6260 #ifdef USE_GL_STEREO
6261             glDrawBuffer(GL_BACK_RIGHT);
6262 #else
6263             mainWindow->setQuadrant(MainWindow::UpperLeft);
6264 #endif
6265             viewFrustum.setOffset(-EyeDisplacement, FocalPlane);
6266 
6267             // draw right eye's view
6268             sceneRenderer->render(true, true);
6269 #ifndef USE_GL_STEREO
6270             drawUI();
6271 #endif
6272 
6273             // draw common stuff
6274 #ifdef USE_GL_STEREO
6275             glDrawBuffer(GL_BACK);
6276             drawUI();
6277 #endif
6278 
6279 #ifndef USE_GL_STEREO
6280             // back to left channel
6281             mainWindow->setQuadrant(MainWindow::UpperRight);
6282 #endif
6283         }
6284         else if (viewType == SceneRenderer::Anaglyph)
6285         {
6286             float EyeDisplacement = 0.25f * BZDBCache::tankWidth;
6287             float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE);
6288             if (BZDB.isSet("eyesep"))
6289                 EyeDisplacement = BZDB.eval("eyesep");
6290             if (BZDB.isSet("focal"))
6291                 FocalPlane = BZDB.eval("focal");
6292 
6293             // setup view for left eye
6294             glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
6295             viewFrustum.setOffset(EyeDisplacement, FocalPlane);
6296 
6297             // draw left eye's view
6298             sceneRenderer->render(false);
6299             drawUI();
6300 
6301             // set up view for right eye
6302             glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
6303             // for red/blue to somewhat work ...
6304             //glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
6305             viewFrustum.setOffset(-EyeDisplacement, FocalPlane);
6306 
6307             // draw right eye's view
6308             sceneRenderer->render(true, true);
6309             drawUI();
6310         }
6311         else if (viewType == SceneRenderer::Interlaced)
6312         {
6313             float EyeDisplacement = 0.25f * BZDBCache::tankWidth;
6314             float FocalPlane = BZDB.eval(StateDatabase::BZDB_BOXBASE);
6315             const int width = mainWindow->getWidth();
6316             const int height = mainWindow->getHeight();
6317             if (BZDB.isSet("eyesep"))
6318                 EyeDisplacement = BZDB.eval("eyesep");
6319             if (BZDB.isSet("focal"))
6320                 FocalPlane = BZDB.eval("focal");
6321 
6322             if (BZDBCache::stencilShadows)
6323             {
6324                 BZDB.set("stencilShadows", "0");
6325                 addMessage(NULL, "Disabled stencilShadows for interlaced mode");
6326             }
6327 
6328             OpenGLGState::resetState();
6329             // enable stencil test
6330             glEnable(GL_STENCIL_TEST);
6331 
6332             // clear stencil
6333             glClearStencil(0);
6334             // Clear color and stencil buffer
6335             glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
6336             // All drawing commands fail the stencil test, and are not
6337             // drawn, but increment the value in the stencil buffer.
6338             glStencilFunc(GL_NEVER, 0x0, 0x0);
6339             glStencilOp(GL_INCR, GL_INCR, GL_INCR);
6340             glColor3f(1.0f, 1.0f, 1.0f);
6341             for (int y=0; y<=height; y+=2)
6342             {
6343                 glBegin(GL_LINES);
6344                 glVertex2i(0, y);
6345                 glVertex2i(width, y);
6346                 glEnd();
6347             }
6348 
6349             // draw except where the stencil pattern is 0x1
6350             // do not change the stencil buffer
6351             glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
6352             glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
6353             // setup view for left eye
6354             viewFrustum.setOffset(EyeDisplacement, FocalPlane);
6355             // draw left eye's view
6356             sceneRenderer->render(false);
6357 
6358             // draw where the stencil pattern is 0x1
6359             // do not change the stencil buffer
6360             glStencilFunc(GL_EQUAL, 0x1, 0x1);
6361             glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
6362             // set up view for right eye
6363             viewFrustum.setOffset(-EyeDisplacement, FocalPlane);
6364             // draw right eye's view
6365             sceneRenderer->render(true, true);
6366 
6367             glStencilFunc(GL_ALWAYS, 0x1, 0x1);
6368             glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
6369             drawUI();
6370 
6371         }
6372         else
6373         {
6374             // bind the multisample framebuffer, if enabled
6375             bool useMultisampling = OpenGLGState::getMaxSamples() > 1 && BZDB.evalInt("multisample") > 1;
6376             if(useMultisampling)
6377             {
6378                 glFramebuffer.checkState(mainWindow->getWidth(), mainWindow->getHeight(), BZDB.evalInt("multisample"));
6379                 glBindFramebuffer(GL_FRAMEBUFFER, glFramebuffer.getFramebuffer());
6380             }
6381 
6382             // normal rendering
6383             sceneRenderer->render();
6384 
6385             // blit the multisample framebuffer (if enabled) to the main framebuffer
6386             if(useMultisampling)
6387             {
6388                 glBindFramebuffer(GL_READ_FRAMEBUFFER, glFramebuffer.getFramebuffer());
6389                 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
6390                 glBlitFramebuffer(0, 0, mainWindow->getWidth(), mainWindow->getHeight(),
6391                                   0, 0, mainWindow->getWidth(), mainWindow->getHeight(),
6392                                   GL_COLOR_BUFFER_BIT, GL_NEAREST);
6393                 glBindFramebuffer(GL_FRAMEBUFFER, 0);
6394             }
6395 
6396             // draw other stuff
6397             drawUI();
6398         }
6399 
6400 
6401         // get frame end time
6402         if (showDrawTime)
6403         {
6404 #if defined(DEBUG_RENDERING)
6405             // get an accurate measure of frame time (at expense of frame rate)
6406             if (BZDB.isTrue("glFinish"))
6407                 glFinish();
6408 #endif
6409             hud->setDrawTime((float)media->stopwatch(false));
6410         }
6411 
6412         // draw a fake cursor if requested.  this is mostly intended for
6413         // pass through 3D cards that don't have cursor support.
6414         if (BZDB.isTrue("fakecursor"))
6415         {
6416             int mx, my;
6417             const int width = mainWindow->getWidth();
6418             const int height = mainWindow->getHeight();
6419             const int ox = mainWindow->getOriginX();
6420             const int oy = mainWindow->getOriginY();
6421             mainWindow->getWindow()->getMouse(mx, my);
6422             my = height - my - 1;
6423 
6424             glScissor(ox, oy, width, height);
6425             glMatrixMode(GL_PROJECTION);
6426             mainWindow->setProjectionPlay();
6427             glMatrixMode(GL_MODELVIEW);
6428             glPushMatrix();
6429             glLoadIdentity();
6430 
6431             glColor3f(0.0f, 0.0f, 0.0f);
6432             glRecti(mx - 8, my - 2, mx - 2, my + 2);
6433             glRecti(mx + 2, my - 2, mx + 8, my + 2);
6434             glRecti(mx - 2, my - 8, mx + 2, my - 2);
6435             glRecti(mx - 2, my + 2, mx + 2, my + 8);
6436 
6437             glColor3f(1.0f, 1.0f, 1.0f);
6438             glRecti(mx - 7, my - 1, mx - 3, my + 1);
6439             glRecti(mx + 3, my - 1, mx + 7, my + 1);
6440             glRecti(mx - 1, my - 7, mx + 1, my - 3);
6441             glRecti(mx - 1, my + 3, mx + 1, my + 7);
6442 
6443             glPopMatrix();
6444         }
6445 
6446         mainWindow->getWindow()->swapBuffers();
6447 
6448         // remove dynamic nodes from this frame
6449         if (scene)
6450             scene->removeDynamicNodes();
6451 
6452     }
6453     else
6454     {
6455         // wait around a little to avoid spinning the CPU when iconified
6456         TimeKeeper::sleep(0.05f);
6457     }
6458 }
6459 
6460 
6461 //============================================================================//
6462 
roamSmoothFollow(Roaming::RoamingCamera & deltaCamera)6463 static void roamSmoothFollow(Roaming::RoamingCamera& deltaCamera)
6464 {
6465     Player* p = ROAM.getTargetTank();
6466     if (!p)
6467         return;
6468 
6469     const float dist   = BZDB.eval("followDist");
6470     const float height = BZDB.eval("followHeight");
6471     const float speedX = BZDB.eval("followSpeedX");
6472     const float speedY = BZDB.eval("followSpeedY");
6473     const float speedZ = BZDB.eval("followSpeedZ");
6474 
6475     const float* pos = p->getPosition();
6476     const float* fwd = p->getForward();
6477     const float target[3] =
6478     {
6479         pos[0] - (fwd[0] * dist),
6480         pos[1] - (fwd[1] * dist),
6481         pos[2] + height
6482     };
6483     const float* current = ROAM.getCamera()->pos;
6484     const float delta[3] =
6485     {
6486         target[0] - current[0],
6487         target[1] - current[1],
6488         target[2] - current[2]
6489     };
6490 
6491     const float theta = ROAM.getCamera()->theta;
6492     const float c = cosf(theta * (float)(M_PI / 180.0f));
6493     const float s = sinf(theta * (float)(M_PI / 180.0f));
6494     const float f[2] = { +c, +s };
6495     const float r[2] = { +s, -c };
6496 
6497     deltaCamera.pos[0] = +speedX * ((delta[0] * f[0]) + (delta[1] * f[1]));
6498     deltaCamera.pos[1] = -speedY * ((delta[0] * r[0]) + (delta[1] * r[1]));
6499     deltaCamera.pos[2] = +speedZ * delta[2];
6500     deltaCamera.theta = 0.0f;
6501     deltaCamera.phi = 0.0f;
6502 }
6503 
6504 
6505 enum MouseButtonBits
6506 {
6507     leftMouseBit   = (1 << 0),
6508     rightMouseBit  = (1 << 1),
6509     middleMouseBit = (1 << 2)
6510 };
6511 
6512 enum MouseCtrlType
6513 {
6514     NoCtrl,
6515     ShiftX, // left/right
6516     ShiftY, // backwards/forewards
6517     ShiftZ, // up/down
6518     SpinX,  // tilt (phi)
6519     SpinY,  // -- not used --
6520     SpinZ   // heading (theta)
6521 };
6522 
6523 struct MouseCtrlPair
6524 {
6525     MouseCtrlType x;
6526     MouseCtrlType y;
6527 };
6528 
6529 static const MouseCtrlPair mouseCtrlMap[8] =
6530 {
6531 //  X       Y
6532     { NoCtrl, NoCtrl }, // . . .
6533     { SpinZ,  ShiftY }, // L . .
6534     { SpinZ,  SpinX  }, // . R .
6535     { ShiftX, ShiftY }, // L R .
6536     { ShiftX, ShiftZ }, // . . M
6537     { SpinZ,  ShiftZ }, // L . M
6538     { SpinZ,  ShiftZ }, // . R M
6539     { ShiftX, ShiftY }  // L R M
6540 };
6541 
6542 
setupRoamingCamera(float dt)6543 static void setupRoamingCamera(float dt)
6544 {
6545 
6546     static Roaming::RoamingCamera prevDeltaCamera;
6547     static bool inited = false;
6548     static int prevMouseBits = 0;
6549     int currMouseBits = (leftMouseButton   ? leftMouseBit   : 0) |
6550                         (rightMouseButton  ? rightMouseBit  : 0) |
6551                         (middleMouseButton ? middleMouseBit : 0);
6552 
6553     if (!inited)
6554     {
6555         memset(&prevDeltaCamera, 0, sizeof(Roaming::RoamingCamera));
6556         inited = true;
6557     }
6558 
6559     Roaming::RoamingCamera deltaCamera;
6560     memset(&deltaCamera, 0, sizeof(Roaming::RoamingCamera));
6561 
6562     // move roaming camera
6563     if (myTank)
6564     {
6565         int mx, my;
6566         mainWindow->getMousePosition(mx, my);
6567 
6568         const MouseCtrlPair currCtrl = mouseCtrlMap[currMouseBits];
6569         const MouseCtrlPair prevCtrl = mouseCtrlMap[prevMouseBits];
6570 
6571         if (currCtrl.x == prevCtrl.x)
6572         {
6573             if (currCtrl.y != prevCtrl.y)
6574             {
6575                 mainWindow->warpMouseCenterY();
6576                 my = 0;
6577             }
6578         }
6579         else if (currCtrl.y == prevCtrl.y)
6580         {
6581             mainWindow->warpMouseCenterX();
6582             mx = 0;
6583         }
6584         else
6585         {
6586             mainWindow->warpMouse();
6587             mx = my = 0;
6588         }
6589 
6590         if (currMouseBits != 0)
6591         {
6592             // mouse control
6593             const float spinMult  =  -100.0f;
6594             const float shiftMult = -1.25f * BZDBCache::worldSize;
6595             const int wx = mainWindow->getWidth();
6596             const int wy = mainWindow->getViewHeight();
6597             const int ws = (wx < wy) ? wx : wy;
6598             const float wf = 1.0f / (float(ws * ws) * 0.25f);
6599             const float sx = float(mx * abs(mx)) * wf;
6600             const float sy = float(my * abs(my)) * wf;
6601             switch (currCtrl.x)
6602             {
6603             case SpinZ:
6604             {
6605                 deltaCamera.theta  = spinMult  * sx;
6606                 break;
6607             }
6608             case ShiftX:
6609             {
6610                 deltaCamera.pos[1] = shiftMult * sx;
6611                 break;
6612             }
6613             default:
6614             {
6615                 break;
6616             }
6617             }
6618             switch (currCtrl.y)
6619             {
6620             case SpinX:
6621             {
6622                 deltaCamera.phi    =  spinMult * sy;
6623                 break;
6624             }
6625             case ShiftY:
6626             {
6627                 deltaCamera.pos[0] = shiftMult * sy;
6628                 break;
6629             }
6630             case ShiftZ:
6631             {
6632                 deltaCamera.pos[2] = shiftMult * sy;
6633                 break;
6634             }
6635             default:
6636             {
6637                 break;
6638             }
6639             }
6640         }
6641         else
6642         {
6643             // keyboard control
6644             bool control = ((shiftKeyStatus & BzfKeyEvent::ControlKey) != 0);
6645             bool alt     = ((shiftKeyStatus & BzfKeyEvent::AltKey) != 0);
6646             bool shift   = ((shiftKeyStatus & BzfKeyEvent::ShiftKey) != 0);
6647             if (display->hasGetKeyMode())
6648                 display->getModState (shift, control, alt);
6649             if (!control && !shift)
6650                 deltaCamera.pos[0] = (float)(4 * myTank->getSpeed()) * BZDBCache::tankSpeed;
6651             if (alt)
6652                 deltaCamera.pos[1] = (float)(4 * myTank->getRotation()) * BZDBCache::tankSpeed;
6653             else
6654                 deltaCamera.theta  = ROAM.getZoom() * (float)myTank->getRotation();
6655             if (control)
6656                 deltaCamera.phi    = -2.0f * ROAM.getZoom() / 3.0f * (float)myTank->getSpeed();
6657             if (shift)
6658                 deltaCamera.pos[2] = (float)(4 * myTank->getSpeed()) * BZDBCache::tankSpeed;
6659         }
6660     }
6661 
6662     // adjust for slow keyboard
6663     float st = BZDB.eval("roamSmoothTime");
6664     if (BZDB.isTrue("slowKeyboard") != (st < 0.0f))
6665     {
6666         if (ROAM.getMode() == Roaming::roamViewFollow)
6667             roamSmoothFollow(deltaCamera);
6668         st = fabsf(st);
6669         if (st < 0.1f)
6670             st = 0.1f;
6671         const float at = (dt / st);
6672         const float bt = 1.0f - at;
6673         deltaCamera.pos[0] = (at * deltaCamera.pos[0]) + (bt * prevDeltaCamera.pos[0]);
6674         deltaCamera.pos[1] = (at * deltaCamera.pos[1]) + (bt * prevDeltaCamera.pos[1]);
6675         deltaCamera.pos[2] = (at * deltaCamera.pos[2]) + (bt * prevDeltaCamera.pos[2]);
6676         deltaCamera.theta  = (at * deltaCamera.theta)  + (bt * prevDeltaCamera.theta);
6677         deltaCamera.phi    = (at * deltaCamera.phi)    + (bt * prevDeltaCamera.phi);
6678     }
6679 
6680     deltaCamera.zoom = roamDZoom;
6681 
6682     ROAM.updatePosition(&deltaCamera, dt);
6683 
6684     // copy the old delta values
6685     memcpy(&prevDeltaCamera, &deltaCamera, sizeof(Roaming::RoamingCamera));
6686 
6687     prevMouseBits = currMouseBits;
6688 
6689     return;
6690 }
6691 
6692 
6693 //============================================================================//
6694 
prepareTheHUD()6695 static void     prepareTheHUD()
6696 {
6697     // prep the HUD
6698     if (myTank)
6699     {
6700         const float* myPos = myTank->getPosition();
6701         hud->setHeading(myTank->getAngle());
6702         hud->setAltitude(myPos[2]);
6703         if (world->allowTeamFlags())
6704         {
6705             const float* myTeamColor = Team::getTankColor(myTank->getTeam());
6706             // markers for my team flag
6707             for (int i = 0; i < numFlags; i++)
6708             {
6709                 Flag& flag = world->getFlag(i);
6710                 if ((flag.type->flagTeam == myTank->getTeam())
6711                         && ((flag.status != FlagOnTank) ||
6712                             (flag.owner != myTank->getId())))
6713                 {
6714                     const float* flagPos = flag.position;
6715                     float heading = atan2f(flagPos[1] - myPos[1],flagPos[0] - myPos[0]);
6716                     hud->addMarker(heading, myTeamColor);
6717                     hud->AddEnhancedMarker(Float3ToVec3(flagPos), Float3ToVec4(myTeamColor),
6718                                            false, BZDBCache::flagPoleSize * 2.0f);
6719                 }
6720             }
6721         }
6722         if (myTank->getAntidoteLocation())
6723         {
6724             // marker for my antidote flag
6725             const GLfloat* antidotePos = myTank->getAntidoteLocation();
6726             float heading = atan2f(antidotePos[1] - myPos[1],
6727                                    antidotePos[0] - myPos[0]);
6728             const float antidoteColor[] = {1.0f, 1.0f, 0.0f,1.0f};
6729             hud->addMarker(heading, antidoteColor);
6730             hud->AddEnhancedMarker(Float3ToVec3(antidotePos), Float4ToVec4(antidoteColor), false,
6731                                    BZDBCache::flagPoleSize * 2.0f);
6732         }
6733     }
6734     return;
6735 }
6736 
6737 
updatePauseCountdown(float dt)6738 static void     updatePauseCountdown(float dt)
6739 {
6740     if (!myTank)
6741         pauseCountdown = 0.0f;
6742     if (pauseCountdown > 0.0f && !myTank->isAlive())
6743     {
6744         pauseCountdown = 0.0f;
6745         hud->setAlert(1, NULL, 0.0f, true);
6746     }
6747     if (pauseCountdown > 0.0f)
6748     {
6749         const int oldPauseCountdown = (int)(pauseCountdown + 0.99f);
6750         pauseCountdown -= dt;
6751         if (pauseCountdown <= 0.0f)
6752         {
6753 
6754             /* make sure it is really safe to pause..  since the server
6755              * might make us drop our flag, make sure the player is on the
6756              * ground and not in a building.  prevents getting kicked
6757              * later for being in places we shouldn't without holding the
6758              * right flags.
6759              */
6760             if (myTank->getLocation() == LocalPlayer::InBuilding)
6761             {
6762                 // custom message when trying to pause while in a building
6763                 // (could get stuck on un-pause if flag is taken/lost)
6764                 hud->setAlert(1, "Can't pause while inside a building", 1.0f, false);
6765 
6766             }
6767             else if (myTank->getLocation() == LocalPlayer::InAir)
6768             {
6769                 // custom message when trying to pause when jumping/falling
6770                 hud->setAlert(1, "Can't pause when you are in the air", 1.0f, false);
6771 
6772             }
6773             else if (myTank->getLocation() != LocalPlayer::OnGround &&
6774                      myTank->getLocation() != LocalPlayer::OnBuilding)
6775             {
6776                 // catch-all message when trying to pause when you should not
6777                 hud->setAlert(1, "Unable to pause right now", 1.0f, false);
6778 
6779             }
6780             else if (myTank->isPhantomZoned())
6781             {
6782                 // custom message when trying to pause while zoned
6783                 hud->setAlert(1, "Can't pause when you are in the phantom zone", 1.0f, false);
6784 
6785             }
6786             else
6787             {
6788                 // okay, now we pause.  first drop any team flag we may have.
6789                 const FlagType* flagd = myTank->getFlag();
6790                 if (flagd->flagTeam != NoTeam)
6791                     serverLink->sendDropFlag(myTank->getPosition());
6792 
6793                 if (World::getWorld()->allowRabbit() && (myTank->getTeam() == RabbitTeam))
6794                     serverLink->sendNewRabbit();
6795 
6796                 // now actually pause
6797                 myTank->setPause(true);
6798                 hud->setAlert(1, NULL, 0.0f, true);
6799                 controlPanel->addMessage("Paused");
6800 
6801                 // turn off the sound
6802                 if (savedVolume == -1)
6803                 {
6804                     savedVolume = getSoundVolume();
6805                     setSoundVolume(0);
6806                 }
6807 
6808                 // ungrab mouse
6809                 mainWindow->ungrabMouse();
6810             }
6811         }
6812         else if ((int)(pauseCountdown + 0.99f) != oldPauseCountdown &&
6813                  !pausedByUnmap)
6814         {
6815             // update countdown alert
6816             char msgBuf[40];
6817             sprintf(msgBuf, "Pausing in %d", (int)(pauseCountdown + 0.99f));
6818             hud->setAlert(1, msgBuf, 1.0f, false);
6819         }
6820     }
6821     return;
6822 }
6823 
6824 
updateDestructCountdown(float dt)6825 static void     updateDestructCountdown(float dt)
6826 {
6827     if (!myTank)
6828         destructCountdown = 0.0f;
6829     if (destructCountdown > 0.0f && !myTank->isAlive())
6830     {
6831         destructCountdown = 0.0f;
6832         hud->setAlert(1, NULL, 0.0f, true);
6833     }
6834     if (destructCountdown > 0.0f)
6835     {
6836         const int oldDestructCountdown = (int)(destructCountdown + 0.99f);
6837         destructCountdown -= dt;
6838         if (destructCountdown <= 0.0f)
6839         {
6840             // now actually destruct
6841             gotBlowedUp( myTank, SelfDestruct, myTank->getId() );
6842 
6843             hud->setAlert(1, NULL, 0.0f, true);
6844         }
6845         else if ((int)(destructCountdown + 0.99f) != oldDestructCountdown)
6846         {
6847             // update countdown alert
6848             char msgBuf[40];
6849             sprintf(msgBuf, "Self Destructing in %d", (int)(destructCountdown + 0.99f));
6850             hud->setAlert(1, msgBuf, 1.0f, false);
6851         }
6852     }
6853     return;
6854 }
6855 
6856 
6857 //
6858 // main playing loop
6859 //
6860 
playingLoop()6861 static void     playingLoop()
6862 {
6863     int i;
6864 
6865     // main loop
6866     while (!CommandsStandard::isQuit())
6867     {
6868 
6869         BZDBCache::update();
6870 
6871         // set this step game time
6872         GameTime::setStepTime();
6873 
6874         // get delta time
6875         TimeKeeper prevTime = TimeKeeper::getTick();
6876         TimeKeeper::setTick();
6877         const float dt = float(TimeKeeper::getTick() - prevTime);
6878 
6879         mainWindow->getWindow()->yieldCurrent();
6880 
6881         // see if the world collision grid needs to be updated
6882         if (world)
6883             world->checkCollisionManager();
6884 
6885         mainWindow->getWindow()->yieldCurrent();
6886 
6887         // try to join a game if requested.  do this *before* handling
6888         // events so we do a redraw after the request is posted and
6889         // before we actually try to join.
6890         if (joinRequested)
6891         {
6892             // if already connected to a game then first sign off
6893             if (myTank) leaveGame();
6894 
6895             // get token if we need to (have a password but no token)
6896             if ((startupInfo.token[0] == '\0')
6897                     && (startupInfo.password[0] != '\0'))
6898             {
6899                 ServerList* serverList = new ServerList;
6900                 serverList->startServerPings(&startupInfo);
6901                 // wait no more than 10 seconds for a token
6902                 for (int j = 0; j < 40; j++)
6903                 {
6904                     serverList->checkEchos(getStartupInfo());
6905                     cURLManager::perform();
6906                     if (startupInfo.token[0] != '\0') break;
6907                     TimeKeeper::sleep(0.25f);
6908                 }
6909                 delete serverList;
6910             }
6911             // don't let the bad token specifier slip through to the server,
6912             // just erase it
6913             if (strcmp(startupInfo.token, "badtoken") == 0)
6914                 startupInfo.token[0] = '\0';
6915 
6916             ares->queryHost(startupInfo.serverName);
6917             waitingDNS = true;
6918 
6919             // don't try again
6920             joinRequested = false;
6921         }
6922 
6923         if (waitingDNS)
6924         {
6925             fd_set readers, writers;
6926             int nfds = -1;
6927             struct timeval timeout;
6928             timeout.tv_sec  = 0;
6929             timeout.tv_usec = 0;
6930             FD_ZERO(&readers);
6931             FD_ZERO(&writers);
6932             ares->setFd(&readers, &writers, nfds);
6933             nfds = select(nfds + 1, (fd_set*)&readers, (fd_set*)&writers, 0,
6934                           &timeout);
6935             ares->process(&readers, &writers);
6936 
6937             struct in_addr inAddress;
6938             AresHandler::ResolutionStatus status = ares->getHostAddress(&inAddress);
6939             if (status == AresHandler::Failed)
6940             {
6941                 HUDDialogStack::get()->setFailedMessage("Server not found");
6942                 waitingDNS = false;
6943             }
6944             else if (status == AresHandler::HbNSucceeded)
6945             {
6946                 // now try connecting
6947                 serverNetworkAddress = Address(inAddress);
6948                 joinInternetGame();
6949                 waitingDNS = false;
6950             }
6951         }
6952         mainWindow->getWindow()->yieldCurrent();
6953 
6954         // handle pending events for some small fraction of time
6955         clockAdjust = 0.0f;
6956         processInputEvents(0.1f);
6957 
6958         if (mainWindow->haveJoystick())
6959         {
6960             static const BzfKeyEvent::Button button_map[] =
6961             {
6962                 BzfKeyEvent::BZ_Button_1,
6963                 BzfKeyEvent::BZ_Button_2,
6964                 BzfKeyEvent::BZ_Button_3,
6965                 BzfKeyEvent::BZ_Button_4,
6966                 BzfKeyEvent::BZ_Button_5,
6967                 BzfKeyEvent::BZ_Button_6,
6968                 BzfKeyEvent::BZ_Button_7,
6969                 BzfKeyEvent::BZ_Button_8,
6970                 BzfKeyEvent::BZ_Button_9,
6971                 BzfKeyEvent::BZ_Button_10,
6972                 BzfKeyEvent::BZ_Button_11,
6973                 BzfKeyEvent::BZ_Button_12,
6974                 BzfKeyEvent::BZ_Button_13,
6975                 BzfKeyEvent::BZ_Button_14,
6976                 BzfKeyEvent::BZ_Button_15,
6977                 BzfKeyEvent::BZ_Button_16,
6978                 BzfKeyEvent::BZ_Button_17,
6979                 BzfKeyEvent::BZ_Button_18,
6980                 BzfKeyEvent::BZ_Button_19,
6981                 BzfKeyEvent::BZ_Button_20,
6982                 BzfKeyEvent::BZ_Button_21,
6983                 BzfKeyEvent::BZ_Button_22,
6984                 BzfKeyEvent::BZ_Button_23,
6985                 BzfKeyEvent::BZ_Button_24,
6986                 BzfKeyEvent::BZ_Button_25,
6987                 BzfKeyEvent::BZ_Button_26,
6988                 BzfKeyEvent::BZ_Button_27,
6989                 BzfKeyEvent::BZ_Button_28,
6990                 BzfKeyEvent::BZ_Button_29,
6991                 BzfKeyEvent::BZ_Button_30,
6992                 BzfKeyEvent::BZ_Button_31,
6993                 BzfKeyEvent::BZ_Button_32,
6994             };
6995 
6996             static unsigned long old_buttons = 0;
6997             const int button_count = bzcountof(button_map);
6998             unsigned long new_buttons = mainWindow->getJoyButtonSet();
6999             if (old_buttons != new_buttons)
7000                 for (int j = 0; j < button_count; j++)
7001                 {
7002                     if ((old_buttons & (1<<j)) != (new_buttons & (1<<j)))
7003                     {
7004                         BzfKeyEvent ev;
7005                         ev.button = button_map[j];
7006                         ev.ascii = 0;
7007                         ev.shift = 0;
7008                         doKey(ev, (new_buttons & (1<<j)) != 0);
7009                     }
7010                 }
7011             old_buttons = new_buttons;
7012 
7013             static const BzfKeyEvent::Button hat_map[] =
7014             {
7015                 BzfKeyEvent::BZ_Hatswitch_1_upleft,
7016                 BzfKeyEvent::BZ_Hatswitch_1_up,
7017                 BzfKeyEvent::BZ_Hatswitch_1_upright,
7018                 BzfKeyEvent::BZ_Hatswitch_1_right,
7019                 BzfKeyEvent::BZ_Hatswitch_1_downright,
7020                 BzfKeyEvent::BZ_Hatswitch_1_down,
7021                 BzfKeyEvent::BZ_Hatswitch_1_downleft,
7022                 BzfKeyEvent::BZ_Hatswitch_1_left,
7023                 BzfKeyEvent::BZ_Hatswitch_2_upleft,
7024                 BzfKeyEvent::BZ_Hatswitch_2_up,
7025                 BzfKeyEvent::BZ_Hatswitch_2_upright,
7026                 BzfKeyEvent::BZ_Hatswitch_2_right,
7027                 BzfKeyEvent::BZ_Hatswitch_2_downright,
7028                 BzfKeyEvent::BZ_Hatswitch_2_down,
7029                 BzfKeyEvent::BZ_Hatswitch_2_downleft,
7030                 BzfKeyEvent::BZ_Hatswitch_2_left,
7031                 BzfKeyEvent::BZ_Hatswitch_3_upleft,
7032                 BzfKeyEvent::BZ_Hatswitch_3_up,
7033                 BzfKeyEvent::BZ_Hatswitch_3_upright,
7034                 BzfKeyEvent::BZ_Hatswitch_3_right,
7035                 BzfKeyEvent::BZ_Hatswitch_3_downright,
7036                 BzfKeyEvent::BZ_Hatswitch_3_down,
7037                 BzfKeyEvent::BZ_Hatswitch_3_downleft,
7038                 BzfKeyEvent::BZ_Hatswitch_3_left,
7039                 BzfKeyEvent::BZ_Hatswitch_4_upleft,
7040                 BzfKeyEvent::BZ_Hatswitch_4_up,
7041                 BzfKeyEvent::BZ_Hatswitch_4_upright,
7042                 BzfKeyEvent::BZ_Hatswitch_4_right,
7043                 BzfKeyEvent::BZ_Hatswitch_4_downright,
7044                 BzfKeyEvent::BZ_Hatswitch_4_down,
7045                 BzfKeyEvent::BZ_Hatswitch_4_downleft,
7046                 BzfKeyEvent::BZ_Hatswitch_4_left,
7047             };
7048 
7049             //  Evdev //   SDL  //      DX     //     atan2    // buttons //
7050             //--------//--------//-------------//--------------//---------//
7051             //   -1   //  9 1 3 // 315   0  45 // -135 -90 -45 // 0  1  2 //
7052             // -1 0 1 //  8 0 2 // 270   ?  90 //  180   0   0 // 7 -1  3 //
7053             //    1   // 12 4 6 // 225 180 135 //  135  90  45 // 6  5  4 //
7054 
7055             const int max_hats = 4;
7056             const int num_buttons = bzcountof(hat_map) / max_hats; // 8
7057             int num_hats = mainWindow->getNumHats();
7058             if (num_hats > max_hats) num_hats = max_hats; // num_hats min= max_hats;
7059             static std::vector<int> hats(max_hats, -1);
7060             const float variance = 360 / num_buttons / 2; // 45/2 or 22.5
7061             BzfKeyEvent ev; // must be out here because of false doKey
7062             ev.ascii = 0;
7063             ev.shift = 0;
7064             for (int hat = 0; hat < num_hats; hat++)
7065             {
7066                 float hatX, hatY;
7067                 mainWindow->getJoyHat(hat, hatX, hatY);
7068                 if (hatX == 0 && hatY == 0)
7069                 {
7070                     if (hats[hat] != -1)
7071                     {
7072                         doKey(ev, false); // unset when centered
7073                         hats[hat] = -1;
7074                     }
7075                 }
7076                 else
7077                 {
7078                     int button = -1; // buttons are counted clockwise to left
7079                     float angle = atan2(hatY, hatX) * 180 / (float)M_PI;
7080                     for (int b = -1; b < num_buttons; b++)
7081                     {
7082                         float testangle = -180 + 2 * variance * (b + 1); // -180 to 180 by 45
7083                         if (testangle - variance <= angle && angle < testangle + variance)
7084                         {
7085                             button = b; // 0 to 7
7086                             if (b == -1) button = num_buttons - 1; // 7
7087                             if (button != hats[hat])
7088                             {
7089                                 if (hats[hat] != -1)
7090                                     doKey(ev, false); // unset when spinning
7091                                 ev.button = hat_map[button + hat * num_buttons];
7092                                 doKey(ev, true);
7093                                 hats[hat] = button;
7094                             }
7095                         }
7096                     }
7097                 }
7098             }
7099         }
7100 
7101         mainWindow->getWindow()->yieldCurrent();
7102 
7103         // invoke callbacks
7104         callPlayingCallbacks();
7105 
7106         mainWindow->getWindow()->yieldCurrent();
7107 
7108         // quick out
7109         if (CommandsStandard::isQuit())
7110             break;
7111 
7112         // if server died then leave the game (note that this may cause
7113         // further server errors but that's okay).
7114         if (serverError ||
7115                 (serverLink && serverLink->getState() == ServerLink::Hungup))
7116         {
7117             // if we haven't reported the death yet then do so now
7118             if (serverDied ||
7119                     (serverLink && serverLink->getState() == ServerLink::Hungup))
7120                 printError("Server has unexpectedly disconnected");
7121             leaveGame();
7122         }
7123 
7124         // update time of day -- update sun and sky every few seconds
7125         float syncTime = BZDB.eval(StateDatabase::BZDB_SYNCTIME);
7126         if (syncTime < 0.0f)
7127         {
7128             if (!BZDB.isSet("fixedTime"))
7129                 epochOffset += (double)dt;
7130             epochOffset += (double)(50.0f * dt * clockAdjust);
7131         }
7132         else
7133         {
7134             epochOffset = (double)syncTime;
7135             lastEpochOffset += (double)dt;
7136         }
7137         if (fabs(epochOffset - lastEpochOffset) >= 4.0)
7138         {
7139             updateDaylight(epochOffset, *sceneRenderer);
7140             lastEpochOffset = epochOffset;
7141         }
7142 
7143         // update the wind
7144         if (world)
7145             world->updateWind(dt);
7146 
7147         // move roaming camera
7148         if (ROAM.isRoaming())
7149         {
7150             setupRoamingCamera(dt);
7151             ROAM.buildRoamingLabel();
7152         }
7153 
7154         // update test video format timer
7155         if (testVideoFormatTimer > 0.0f)
7156         {
7157             testVideoFormatTimer -= dt;
7158             if (testVideoFormatTimer <= 0.0f)
7159             {
7160                 testVideoFormatTimer = 0.0f;
7161                 setVideoFormat(testVideoPrevFormat);
7162             }
7163         }
7164 
7165         // update the countdowns
7166         updatePauseCountdown(dt);
7167         updateDestructCountdown(dt);
7168 
7169         // notify if input changed
7170         if ((myTank != NULL) && (myTank->queryInputChange() == true))
7171         {
7172             controlPanel->addMessage(
7173                 LocalPlayer::getInputMethodName(myTank->getInputMethod()) + " movement");
7174         }
7175 
7176         // update other tank's shots
7177         for (i = 0; i < curMaxPlayers; i++)
7178         {
7179             if (remotePlayers[i])
7180                 remotePlayers[i]->updateShots(dt);
7181         }
7182 
7183         // update servers shots
7184         const World *_world = World::getWorld();
7185         if (_world)
7186             _world->getWorldWeapons()->updateShots(dt);
7187 
7188         // update track marks  (before any tanks are moved)
7189         TrackMarks::update(dt);
7190 
7191         // do dead reckoning on remote players
7192         for (i = 0; i < curMaxPlayers; i++)
7193         {
7194             if (remotePlayers[i])
7195             {
7196                 const bool wasNotResponding = remotePlayers[i]->isNotResponding();
7197                 remotePlayers[i]->doDeadReckoning();
7198                 const bool isNotResponding = remotePlayers[i]->isNotResponding();
7199                 if (!wasNotResponding && isNotResponding)
7200                     addMessage(remotePlayers[i], "not responding");
7201                 else if (wasNotResponding && !isNotResponding)
7202                     addMessage(remotePlayers[i], "okay");
7203             }
7204         }
7205 
7206         // do motion
7207         if (myTank)
7208         {
7209             if (myTank->isAlive() && !myTank->isPaused())
7210             {
7211                 doMotion();
7212                 if (scoreboard->getHuntState()==ScoreboardRenderer::HUNT_ENABLED)
7213                 {
7214                     setHuntTarget(); //spot hunt target
7215                 }
7216                 if (myTank->getTeam() != ObserverTeam &&
7217                         ((fireButton && myTank->getFlag() == Flags::MachineGun) ||
7218                          (myTank->getFlag() == Flags::TriggerHappy)))
7219                     myTank->fireShot();
7220 
7221                 setLookAtMarker();
7222 
7223                 // see if we have a target, if so lock on to the bastage
7224                 const Player* targetdPlayer = myTank->getTarget();
7225                 if (targetdPlayer && targetdPlayer->isAlive() && targetdPlayer->getFlag() != Flags::Stealth)
7226                 {
7227                     hud->AddLockOnMarker(Float3ToVec3(myTank->getTarget()->getPosition()),
7228                                          myTank->getTarget()->getCallSign(),
7229                                          !isKillable(myTank->getTarget()));
7230                 }
7231                 else // if we should not have a target, force that target to be cleared
7232                     myTank->setTarget(NULL);
7233 
7234             }
7235             else
7236             {
7237                 int mx, my;
7238                 mainWindow->getMousePosition(mx, my);
7239             }
7240             myTank->update();
7241         }
7242 
7243 #ifdef ROBOT
7244         if (entered)
7245             updateRobots(dt);
7246 #endif
7247 
7248         // check for flags and hits
7249         checkEnvironment();
7250 
7251 #ifdef ROBOT
7252         if (entered)
7253             checkEnvironmentForRobots();
7254 #endif
7255 
7256         // adjust properties based on flags (dimensions, cloaking, etc...)
7257         if (myTank)
7258             myTank->updateTank(dt, true);
7259         for (i = 0; i < curMaxPlayers; i++)
7260         {
7261             if (remotePlayers[i])
7262                 remotePlayers[i]->updateTank(dt, false);
7263         }
7264 
7265         // reposition flags
7266         updateFlags(dt);
7267 
7268         // update explosion animations
7269         updateExplosions(dt);
7270 
7271         // update mesh animations
7272         if (world)
7273             world->updateAnimations(dt);
7274 
7275         // prep the HUD
7276         prepareTheHUD();
7277 
7278         // draw the frame
7279         drawFrame(dt);
7280 
7281         // play the sounds
7282         updateSound();
7283 
7284 
7285         bool sendUpdate = myTank && myTank->isDeadReckoningWrong();
7286         if (myTank && myTank->getTeam() == ObserverTeam)
7287         {
7288             if (BZDB.isTrue("sendObserverHeartbeat"))
7289             {
7290                 double heartbeatTime = BZDB.isSet("observerHeartbeat")
7291                                        ? BZDB.eval("observerHeartbeat") : 30.0f;
7292                 if (lastObserverUpdateTime + heartbeatTime < TimeKeeper::getTick().getSeconds())
7293                 {
7294                     lastObserverUpdateTime = TimeKeeper::getTick().getSeconds();
7295                     sendUpdate = true;
7296                 }
7297                 else
7298                     sendUpdate = false;
7299             }
7300             else
7301                 sendUpdate = false;
7302         }
7303         // send my data
7304         if ( sendUpdate)
7305         {
7306             // also calls setDeadReckoning()
7307             serverLink->sendPlayerUpdate(myTank);
7308         }
7309 
7310 #ifdef ROBOT
7311         if (entered)
7312             sendRobotUpdates();
7313 #endif
7314 
7315         FlagSceneNode::freeFlag();
7316 
7317         cURLManager::perform();
7318 
7319         // check if we are waiting for initial texture downloading
7320         if (Downloads::requestFinalized())
7321         {
7322             // downloading is terminated. go!
7323             Downloads::finalizeDownloads();
7324             if (downloadingInitialTexture)
7325             {
7326                 joinInternetGame2();
7327                 downloadingInitialTexture = false;
7328             }
7329             else
7330                 setSceneDatabase();
7331         }
7332 
7333         // limit the fps to save battery life by minimizing cpu usage
7334         if (BZDB.evalInt("saveEnergy") == 1)
7335         {
7336             static TimeKeeper lastTime = TimeKeeper::getCurrent();
7337             float fpsLimit = BZDB.eval("fpsLimit");
7338             if (fpsLimit < 15 || isnan(fpsLimit))
7339                 fpsLimit = 15;
7340             TimeKeeper nextTime(lastTime);
7341             nextTime += 1.0f / fpsLimit;
7342             float remaining;
7343             while (1)
7344             {
7345                 remaining = (float)(nextTime - TimeKeeper::getCurrent());
7346                 if (remaining > 1.0f)
7347                     break;
7348                 if (remaining <= 0.0f)
7349                     break;
7350                 // Instead of sleeping try to handle network packets
7351                 char msg[MaxPacketLen];
7352                 uint16_t code, len;
7353 
7354                 // handle server messages
7355                 if (serverLink && !serverError)
7356                 {
7357                     int e = 0;
7358                     e = serverLink->read(code, len, msg, int(remaining * 1000.0f));
7359                     if (e == 1)
7360                         handleServerMessage(true, code, len, msg);
7361                     if (e == -2)
7362                     {
7363                         printError("Server communication error");
7364                         serverError = true;
7365                         break;
7366                     }
7367                 }
7368                 else
7369                 {
7370                     TimeKeeper::sleep(remaining);
7371                     break;
7372                 }
7373             }
7374             lastTime = TimeKeeper::getCurrent();
7375         } // end energy saver check
7376 
7377         // handle incoming packets
7378         doMessages();
7379 
7380     } // end main client loop
7381 }
7382 
7383 
7384 //
7385 // game initialization
7386 //
7387 
defaultErrorCallback(const char * msg)7388 static void     defaultErrorCallback(const char* msg)
7389 {
7390     std::string message = ColorStrings[RedColor];
7391     message += msg;
7392     controlPanel->addMessage(message);
7393 }
7394 
startupErrorCallback(const char * msg)7395 static void     startupErrorCallback(const char* msg)
7396 {
7397     controlPanel->addMessage(msg);
7398     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
7399     glClear(GL_COLOR_BUFFER_BIT);
7400     controlPanel->render(*sceneRenderer);
7401     mainWindow->getWindow()->swapBuffers();
7402 }
7403 
7404 
startPlaying(BzfDisplay * _display,SceneRenderer & renderer)7405 void            startPlaying(BzfDisplay* _display,
7406                              SceneRenderer& renderer)
7407 {
7408     // initalization
7409     display = _display;
7410     sceneRenderer = &renderer;
7411     mainWindow = &sceneRenderer->getWindow();
7412 
7413     lastObserverUpdateTime = TimeKeeper::getTick().getSeconds();
7414 
7415     // register some commands
7416     for (unsigned int c = 0; c < bzcountof(commandList); ++c)
7417         CMDMGR.add(commandList[c].name, commandList[c].func, commandList[c].help);
7418 
7419     // initialize the tank display lists
7420     // (do this before calling SceneRenderer::render())
7421     TankGeometryMgr::init();
7422     SphereLodSceneNode::init();
7423 
7424     // make control panel
7425     ControlPanel _controlPanel(*mainWindow, *sceneRenderer);
7426     controlPanel = &_controlPanel;
7427 
7428     // make the radar
7429     RadarRenderer _radar(*sceneRenderer, world);
7430     radar = &_radar;
7431 
7432     // tie the radar to the control panel
7433     controlPanel->setRadarRenderer(radar);
7434 
7435     // tell the control panel how many frame buffers there are.  we
7436     // cheat when drawing the control panel, not drawing it if it
7437     // hasn't changed.  that only works if we've filled all the
7438     // frame buffers (e.g. front and back buffers) with the correct
7439     // data.
7440     // FIXME -- assuming the contents of any frame buffer except the
7441     // front buffer are anything but garbage violates the OpenGL
7442     // spec.  we really should redraw the control panel every frame
7443     // but this works on every system so far.
7444     {
7445         int n = 3;  // assume triple buffering
7446         switch (sceneRenderer->getViewType())
7447         {
7448         case SceneRenderer::Stacked:
7449         case SceneRenderer::Stereo:
7450 #ifndef USE_GL_STEREO
7451             // control panel drawn twice per frame
7452             n *= 2;
7453 #endif
7454             break;
7455 
7456         case SceneRenderer::ThreeChannel:
7457         default:
7458             // only one copy of control panel visible
7459             break;
7460         }
7461         controlPanel->setNumberOfFrameBuffers(n);
7462     }
7463 
7464     // if no configuration go into a decent setup for a modern machine
7465     if (!startupInfo.hasConfiguration)
7466     {
7467         BZDB.set("texture", "1");
7468         sceneRenderer->setQuality(3);
7469         TextureManager::instance().setMaxFilter(OpenGLTexture::Max);
7470     }
7471 
7472 // should we grab the mouse?
7473 #if defined(DEBUG)      // don't grab for debug builds
7474     setGrabMouse(false);
7475 #elif defined(__linux__)      // linux usually has a virtual root window so grab mouse always
7476     setGrabMouse(true);
7477 #else
7478     if (!BZDB.isSet("_window")) // otherwise, grab if fullscreen.
7479         setGrabMouse(true);
7480 #endif
7481 
7482     // show window and clear it immediately
7483     mainWindow->showWindow(true);
7484     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
7485     glDisable(GL_SCISSOR_TEST);
7486     glClear(GL_COLOR_BUFFER_BIT);
7487     mainWindow->getWindow()->swapBuffers();
7488 
7489     // resize and draw basic stuff
7490     glClear(GL_COLOR_BUFFER_BIT);
7491     glEnable(GL_SCISSOR_TEST);
7492     controlPanel->resize();
7493     sceneRenderer->render();
7494     controlPanel->render(*sceneRenderer);
7495     mainWindow->getWindow()->swapBuffers();
7496 
7497     // startup error callback adds message to control panel and
7498     // forces an immediate redraw.
7499     setErrorCallback(startupErrorCallback);
7500 
7501     // initialize epoch offset (time)
7502     userTimeEpochOffset = (double)mktime(&userTime);
7503     epochOffset = userTimeEpochOffset;
7504     updateDaylight(epochOffset, *sceneRenderer);
7505     lastEpochOffset = epochOffset;
7506 
7507     // catch kill signals before changing video mode so we can
7508     // put it back even if we die.  ignore a few signals.
7509     bzSignal(SIGILL, SIG_PF(dying));
7510     bzSignal(SIGABRT, SIG_PF(dying));
7511     bzSignal(SIGSEGV, SIG_PF(dying));
7512     bzSignal(SIGTERM, SIG_PF(suicide));
7513 #if !defined(_WIN32)
7514     if (bzSignal(SIGINT, SIG_IGN) != SIG_IGN)
7515         bzSignal(SIGINT, SIG_PF(suicide));
7516     bzSignal(SIGPIPE, SIG_PF(hangup));
7517     bzSignal(SIGHUP, SIG_IGN);
7518     if (bzSignal(SIGQUIT, SIG_IGN) != SIG_IGN)
7519         bzSignal(SIGQUIT, SIG_PF(dying));
7520 #ifndef GUSI_20
7521     bzSignal(SIGBUS, SIG_PF(dying));
7522 #endif
7523     bzSignal(SIGUSR1, SIG_IGN);
7524     bzSignal(SIGUSR2, SIG_IGN);
7525 #endif /* !defined(_WIN32) */
7526 
7527     std::string videoFormat;
7528     int format = -1;
7529     if (BZDB.isSet("resolution"))
7530     {
7531         videoFormat = BZDB.get("resolution");
7532         if (videoFormat.length() != 0)
7533             format = display->findResolution(videoFormat.c_str());
7534     };
7535     // set the resolution (only if in full screen mode)
7536     if (!BZDB.isSet("_window") && BZDB.isSet("resolution"))
7537     {
7538         if (videoFormat.length() != 0)
7539         {
7540             if (display->isValidResolution(format) &&
7541                     display->getResolution() != format &&
7542                     display->setResolution(format))
7543             {
7544 
7545                 // handle resize
7546                 if (BZDB.isSet("geometry"))
7547                 {
7548                     int w, h, x, y, count;
7549                     char xs, ys;
7550                     count = sscanf(BZDB.get("geometry").c_str(),
7551                                    "%dx%d%c%d%c%d", &w, &h, &xs, &x, &ys, &y);
7552                     if (w < 256) w = 256;
7553                     if (h < 192) h = 192;
7554                     if (count == 6)
7555                     {
7556                         if (xs == '-') x = display->getWidth() - x - w;
7557                         if (ys == '-') y = display->getHeight() - y - h;
7558                         mainWindow->setPosition(x, y);
7559                     }
7560                     mainWindow->setSize(w, h);
7561                 }
7562                 else
7563                     mainWindow->setFullscreen();
7564 
7565                 // more resize handling
7566                 mainWindow->getWindow()->callResizeCallbacks();
7567                 mainWindow->warpMouse();
7568             }
7569         }
7570     }
7571     // otherwise, use the default resolution if we do switch to fullscreen
7572     else
7573         display->setDefaultResolution();
7574 
7575     // grab mouse if we should
7576     if (shouldGrabMouse())
7577         mainWindow->grabMouse();
7578 
7579     // draw again
7580     glClear(GL_COLOR_BUFFER_BIT);
7581     sceneRenderer->render();
7582     controlPanel->render(*sceneRenderer);
7583     mainWindow->getWindow()->swapBuffers();
7584     mainWindow->getWindow()->yieldCurrent();
7585 
7586     // make heads up display
7587     HUDRenderer _hud(display, renderer);
7588     hud = &_hud;
7589     scoreboard = hud->getScoreboard();
7590 
7591     // initialize control panel and hud
7592     updateFlag(Flags::Null);
7593     updateHighScores();
7594     notifyBzfKeyMapChanged();
7595 
7596     // make background renderer
7597     BackgroundRenderer background;
7598     sceneRenderer->setBackground(&background);
7599 
7600     static const GLfloat  zero[3] = { 0.0f, 0.0f, 0.0f };
7601 
7602     TextureManager &tm = TextureManager::instance();
7603 
7604     bool done = false;
7605     int explostion = 1;
7606     while (!done)
7607     {
7608         char text[256];
7609         sprintf(text, "explode%d", explostion);
7610 
7611         int tex = tm.getTextureID(text, false);
7612 
7613         if (tex < 0)
7614             done = true;
7615         else
7616         {
7617             // make explosion scene node
7618             BillboardSceneNode* explosion = new BillboardSceneNode(zero);
7619             explosion->setTexture(tex);
7620             explosion->setTextureAnimation(8, 8);
7621 
7622             // add it to list of prototype explosions
7623             prototypeExplosions.push_back(explosion);
7624             explostion++;
7625         }
7626     }
7627 
7628     // let other stuff do initialization
7629     sceneBuilder = new SceneDatabaseBuilder(sceneRenderer);
7630     World::init();
7631 
7632     // prepare dialogs
7633     mainMenu = new MainMenu;
7634 
7635     // normal error callback (doesn't force a redraw)
7636     setErrorCallback(defaultErrorCallback);
7637 
7638     // print debugging info
7639     {
7640         // Application version
7641         logDebugMessage(1,"BZFlag version:   %s\n", getAppVersion());
7642 
7643         // Protocol version
7644         logDebugMessage(1,"BZFlag protocol:  %s\n", getProtocolVersion());
7645 
7646         // OpenGL Driver Information
7647         logDebugMessage(1,"OpenGL vendor:    %s\n", (const char*)glGetString(GL_VENDOR));
7648         logDebugMessage(1,"OpenGL version:   %s\n", (const char*)glGetString(GL_VERSION));
7649         logDebugMessage(1,"OpenGL renderer:  %s\n", (const char*)glGetString(GL_RENDERER));
7650 
7651         // Depth Buffer bitplanes
7652         GLint zDepth;
7653         glGetIntegerv(GL_DEPTH_BITS, &zDepth);
7654         logDebugMessage(1,"Depth Buffer:     %i bitplanes\n", zDepth);
7655     }
7656 
7657     // windows version can be very helpful in debug logs
7658 #ifdef _WIN32
7659 #pragma warning(disable: 4996)
7660     if (debugLevel >= 1)
7661     {
7662         OSVERSIONINFO info;
7663         ZeroMemory(&info, sizeof(OSVERSIONINFO));
7664         info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
7665         GetVersionEx(&info);
7666         logDebugMessage(1,"Running on Windows %s%d.%d %s\n",
7667                         (info.dwPlatformId == VER_PLATFORM_WIN32_NT) ? "NT " : "",
7668                         info.dwMajorVersion, info.dwMinorVersion,
7669                         info.szCSDVersion);
7670     }
7671 #endif
7672 
7673     // send informative header to the console
7674     {
7675         std::string tmpString;
7676 
7677         controlPanel->addMessage("");
7678         // print app version
7679         tmpString = ColorStrings[RedColor];
7680         tmpString += "BZFlag version: ";
7681         tmpString += getAppVersion();
7682         tmpString += " (";
7683         tmpString += getProtocolVersion();
7684         tmpString += ")";
7685         controlPanel->addMessage(tmpString);
7686         // print copyright
7687         tmpString = ColorStrings[YellowColor];
7688         tmpString += bzfcopyright;
7689         controlPanel->addMessage(tmpString);
7690         // print license
7691         tmpString = ColorStrings[CyanColor];
7692         tmpString += "Distributed under the terms of the LGPL or MPL";
7693         controlPanel->addMessage(tmpString);
7694         // print author
7695         tmpString = ColorStrings[GreenColor];
7696         tmpString += "Author: Chris Schoeneman <crs23@bigfoot.com>";
7697         controlPanel->addMessage(tmpString);
7698         // print maintainer
7699         tmpString = ColorStrings[CyanColor];
7700         tmpString += "Maintainer: Tim Riker <Tim@Rikers.org>";
7701         controlPanel->addMessage(tmpString);
7702         // print audio driver
7703         std::string audioStr;
7704         PlatformFactory::getMedia()->audioDriver(audioStr);
7705         if (tmpString != "")
7706         {
7707             tmpString = ColorStrings[BlueColor];
7708             tmpString += "Audio Driver: " + audioStr;
7709             controlPanel->addMessage(tmpString);
7710         }
7711         // print GL renderer
7712         tmpString = ColorStrings[PurpleColor];
7713         tmpString += "OpenGL Driver: ";
7714         tmpString += (const char*)glGetString(GL_RENDERER);
7715         controlPanel->addMessage(tmpString);
7716     }
7717 
7718     // get current MOTD
7719     if (!BZDB.isTrue("disableMOTD"))
7720     {
7721         motd = new MessageOfTheDay;
7722         motd->getURL(BZDB.get("motdServer"));
7723     }
7724 
7725     // inform user of silencePlayers on startup
7726     for (unsigned int j = 0; j < silencePlayers.size(); j ++)
7727     {
7728         std::string aString = silencePlayers[j];
7729         aString += " Silenced";
7730         if (silencePlayers[j] == "*")
7731             aString = "Silenced All Msgs";
7732         else if (silencePlayers[j] == "-")
7733             aString = "Silenced Unregistered Players";
7734         controlPanel->addMessage(aString);
7735     }
7736 
7737     // enter game if we have all the info we need, otherwise
7738     // pop up main menu
7739     if (startupInfo.autoConnect &&
7740             startupInfo.callsign[0] && startupInfo.serverName[0])
7741     {
7742         joinRequested    = true;
7743         // show join menu to see connection errors
7744         mainMenu->createControls();
7745         HUDDialogStack::get()->push(mainMenu);
7746         mainMenu->execute();
7747         HUDui::setFocus(HUDui::getFocus()->getNext()); // select "Connect"
7748         HUDDialogStack::get()->top()->execute(); // show "Trying Automatic Connection..."
7749 
7750     }
7751     else
7752     {
7753         mainMenu->createControls();
7754         HUDDialogStack::get()->push(mainMenu);
7755     }
7756 
7757     if (BZDB.isTrue("fakecursor"))
7758         mainWindow->getWindow()->hideMouse();
7759 
7760     // start timing
7761     TimeKeeper::setTick();
7762     updateDaylight(epochOffset, *sceneRenderer);
7763 
7764     worldDownLoader = new WorldDownLoader;
7765 
7766     // start game loop
7767     playingLoop();
7768 
7769     delete worldDownLoader;
7770 
7771     // restore the sound.  if we don't do this then we'll save the
7772     // wrong volume when we dump out the configuration file if the
7773     // app exits when the game is paused.
7774     if (savedVolume != -1)
7775     {
7776         setSoundVolume(savedVolume);
7777         savedVolume = -1;
7778     }
7779 
7780     // hide window
7781     mainWindow->showWindow(false);
7782 
7783     // clean up
7784     TankGeometryMgr::kill();
7785     SphereLodSceneNode::kill();
7786     if (resourceDownloader)
7787         delete resourceDownloader;
7788     delete motd;
7789     for (unsigned int ext = 0; ext < prototypeExplosions.size(); ext++)
7790         delete prototypeExplosions[ext];
7791     prototypeExplosions.clear();
7792     leaveGame();
7793     setErrorCallback(NULL);
7794     while (HUDDialogStack::get()->isActive())
7795         HUDDialogStack::get()->pop();
7796     delete mainMenu;
7797     delete sceneBuilder;
7798     sceneRenderer->setBackground(NULL);
7799     sceneRenderer->setSceneDatabase(NULL);
7800     World::done();
7801     mainWindow = NULL;
7802     sceneRenderer = NULL;
7803     display = NULL;
7804     cleanWorldCache();
7805 }
7806 
7807 // Local Variables: ***
7808 // mode: C++ ***
7809 // tab-width: 4 ***
7810 // c-basic-offset: 4 ***
7811 // indent-tabs-mode: nil ***
7812 // End: ***
7813 // ex: shiftwidth=4 tabstop=4
7814