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 ¬e)
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(), ×);
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