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 "commands.h"
15 
16 /* system implementation headers */
17 #ifndef _WIN32
18 #  include <sys/types.h>
19 #  include <dirent.h>
20 #endif
21 #include <string>
22 #include <vector>
23 #include <cstdlib>
24 
25 /* common implementation headers */
26 #include "BZDBCache.h"
27 #include "TextUtils.h"
28 #include "FileManager.h"
29 #include "DirectoryNames.h"
30 #include "version.h"
31 #include "SceneRenderer.h"
32 #include "bzglob.h"
33 #include "BzPNG.h"
34 
35 /* local implementation headers */
36 #include "LocalPlayer.h"
37 #include "sound.h"
38 #include "ComposeDefaultKey.h"
39 #include "SilenceDefaultKey.h"
40 #include "ServerCommandKey.h"
41 #include "Roaming.h"
42 #include "playing.h"
43 #include "HUDRenderer.h"
44 #include "HUDui.h"
45 
46 /** jump
47  */
48 static std::string cmdJump(const std::string&,
49                            const CommandManager::ArgList& args, bool*);
50 
51 /** fire weapon
52  */
53 static std::string cmdFire(const std::string&,
54                            const CommandManager::ArgList& args, bool*);
55 
56 /** drop a flag
57  */
58 static std::string cmdDrop(const std::string&,
59                            const CommandManager::ArgList& args, bool*);
60 
61 /** toggle radar visibility
62  */
63 static std::string cmdToggleRadar(const std::string&,
64                                   const CommandManager::ArgList& args, bool*);
65 
66 /** toggle console visibility
67  */
68 static std::string cmdToggleConsole(const std::string&,
69                                     const CommandManager::ArgList& args, bool*);
70 
71 /** toggle flags
72  */
73 static std::string cmdToggleFlags(const std::string&,
74                                   const CommandManager::ArgList& args, bool*);
75 
76 /** identify to a server
77  */
78 static std::string cmdIdentify(const std::string&,
79                                const CommandManager::ArgList& args, bool*);
80 
81 /** restart/respawn
82  */
83 static std::string cmdRestart(const std::string&,
84                               const CommandManager::ArgList& args, bool*);
85 
86 /** self-destruct
87  */
88 static std::string cmdDestruct(const std::string&,
89                                const CommandManager::ArgList& args, bool*);
90 
91 /** pause
92  */
93 static std::string cmdPause(const std::string&,
94                             const CommandManager::ArgList& args, bool*);
95 
96 /** select tabbed message
97  */
98 static std::string cmdMessagePanel(const std::string&,
99                                    const CommandManager::ArgList& args, bool*);
100 
101 /** toggle auto-pilot
102  */
103 static std::string cmdAutoPilot(const std::string&,
104                                 const CommandManager::ArgList& args, bool*);
105 
106 /** change radar scale
107  */
108 static std::string cmdRadarZoom(const std::string&,
109                                 const CommandManager::ArgList& args, bool*);
110 
111 /** change view angle
112  */
113 static std::string cmdViewZoom(const std::string&,
114                                const CommandManager::ArgList& args, bool*);
115 
116 /** send
117  */
118 static std::string cmdSend(const std::string&,
119                            const CommandManager::ArgList& args, bool*);
120 
121 /** capture a screenshot
122  */
123 static std::string cmdScreenshot(const std::string&,
124                                  const CommandManager::ArgList& args, bool*);
125 
126 /** time
127  */
128 static std::string cmdTime(const std::string&,
129                            const CommandManager::ArgList& args, bool*);
130 
131 /** roam
132  */
133 static std::string cmdRoam(const std::string&,
134                            const CommandManager::ArgList& args, bool*);
135 
136 /** silence another player
137  */
138 static std::string cmdSilence(const std::string&,
139                               const CommandManager::ArgList& args, bool*);
140 
141 /** perform a server command
142  */
143 static std::string cmdServerCommand(const std::string&,
144                                     const CommandManager::ArgList& args,
145                                     bool*);
146 
147 /** scroll the chat panel
148  */
149 static std::string cmdScrollPanel(const std::string&,
150                                   const CommandManager::ArgList& args, bool*);
151 
152 /** hunt a player
153  */
154 static std::string cmdHunt(const std::string&,
155                            const CommandManager::ArgList& args, bool*);
156 
157 /** hunt another player
158  */
159 static std::string cmdAddHunt(const std::string&,
160                               const CommandManager::ArgList& args, bool*);
161 
162 /** iconify window
163  */
164 static std::string cmdIconify(const std::string&,
165                               const CommandManager::ArgList& args, bool*);
166 
167 /** mouse box size flags
168  */
169 static std::string cmdMouseBox(const std::string&,
170                                const CommandManager::ArgList& args, bool*);
171 
172 /** toggle mouse capture
173  */
174 static std::string cmdMouseGrab(const std::string&,
175                                 const CommandManager::ArgList& args, bool*);
176 
177 /** toggle Full Screen
178  */
179 static std::string cmdToggleFS(const std::string&,
180                                const CommandManager::ArgList& args, bool*);
181 
182 /** cycle to the next radar zoom level
183  */
184 static std::string cmdCycleRadar(const std::string&,
185                                  const CommandManager::ArgList& args, bool*);
186 
187 /** cycle to the next panel tab
188  */
189 static std::string cmdCyclePanel(const std::string&,
190                                  const CommandManager::ArgList& args, bool*);
191 
192 
193 const struct CommandListItem commandList[] =
194 {
195     { "fire",     &cmdFire,       "fire:  fire a shot" },
196     { "jump",     &cmdJump,       "jump:  make player jump" },
197     { "drop",     &cmdDrop,       "drop:  drop the current flag" },
198     { "identify",     &cmdIdentify,       "identify:  identify/lock-on-to player in view" },
199     { "restart",      &cmdRestart,        "restart:  restart playing" },
200     { "destruct",     &cmdDestruct,       "destruct:  self destruct" },
201     { "pause",        &cmdPause,      "pause:  pause/resume" },
202     { "send",     &cmdSend,       "send {all|team|nemesis|recipient|admin}:  start composing a message" },
203     { "screenshot",   &cmdScreenshot,     "screenshot:  take a screenshot" },
204     { "time",     &cmdTime,       "time {forward|backward}:  adjust the current time" },
205     { "roam",     &cmdRoam,       "roam {zoom|cycle} <args>:  roam around" },
206     { "silence",      &cmdSilence,        "silence:  silence/unsilence a player" },
207     { "servercommand",    &cmdServerCommand,  "servercommand:  quick admin" },
208     { "scrollpanel",  &cmdScrollPanel,    "scrollpanel {up|down}:  scroll message panel" },
209     { "hunt",     &cmdHunt,       "hunt:  hunt a specific player" },
210     { "addhunt",      &cmdAddHunt,        "addhunt:  add/modify hunted player(s)" },
211     { "iconify",      &cmdIconify,        "iconify: iconify & pause bzflag" },
212     { "mousebox",     &cmdMouseBox,       "mousebox <size>:  change the mousebox size"},
213     { "mousegrab",    &cmdMouseGrab,      "mousegrab: toggle exclusive mouse mode" },
214     { "fullscreen",   &cmdToggleFS,       "fullscreen: toggle fullscreen mode" },
215     { "autopilot",    &cmdAutoPilot,      "autopilot:  set/unset autopilot bot code" },
216     { "radarZoom",    &cmdRadarZoom,      "radarZoom {in/out}: change maxRadar range"},
217     { "viewZoom",     &cmdViewZoom,       "viewZoom {in/out/toggle}: change view angle" },
218     { "messagepanel", &cmdMessagePanel,   "messagepanel {all|chat|server|misc}:  set message tab" },
219     { "toggleRadar",  &cmdToggleRadar,    "toggleRadar:  toggle radar visibility" },
220     { "toggleConsole",    &cmdToggleConsole,  "toggleConsole:  toggle console visibility" },
221     { "toggleFlags",  &cmdToggleFlags,    "toggleFlags {main|radar}:  turn off/on field radar flags" },
222     { "cycleRadar",   &cmdCycleRadar,     "cycleRadar {level1 [level2 ...] [off]}:  cycle to the next radar zoom level" },
223     { "cyclePanel",   &cmdCyclePanel,     "cyclePanel {left[_off]|right[_off]}:  cycle to the previous or next message panel tab" }
224 };
225 
226 
cmdToggleFS(const std::string &,const CommandManager::ArgList & args,bool *)227 static std::string cmdToggleFS(const std::string&,
228                                const CommandManager::ArgList& args, bool*)
229 {
230     if (args.size() != 0)
231         return "usage: fullscreen";
232     mainWindow->toggleFullscreen();
233     mainWindow->getWindow()->callResizeCallbacks();
234     return std::string();
235 }
236 
cmdMouseBox(const std::string &,const CommandManager::ArgList & args,bool *)237 static std::string cmdMouseBox(const std::string&,
238                                const CommandManager::ArgList& args, bool*)
239 {
240     if (args.size() != 1)
241         return "usage: mousebox <size>";
242     const char* start = args[0].c_str();
243     char* end;
244     const int value = (int) strtol(args[0].c_str(), &end, 10);
245     if (start == end)
246         return "bad number";
247     RENDERER.setMaxMotionFactor(value);
248     return std::string();
249 }
250 
cmdMouseGrab(const std::string &,const CommandManager::ArgList & args,bool *)251 static std::string cmdMouseGrab(const std::string&,
252                                 const CommandManager::ArgList& args, bool*)
253 {
254     if (args.size() != 0)
255         return "usage: mousegrab";
256     const bool grabbing = !(BZDB.isTrue("mousegrab"));
257     BZDB.set("mousegrab", grabbing ? "true" : "false");
258     mainWindow->enableGrabMouse(grabbing);
259     return std::string();
260 }
261 
cmdIconify(const std::string &,const CommandManager::ArgList & args,bool *)262 static std::string cmdIconify(const std::string&,
263                               const CommandManager::ArgList& args, bool*)
264 {
265     if (args.size() != 0)
266         return "usage: iconify";
267 
268     if (!BZDB.isTrue("Win32NoMin"))
269         mainWindow->iconify();
270     return std::string();
271 }
272 
cmdJump(const std::string &,const CommandManager::ArgList & args,bool *)273 static std::string cmdJump(const std::string&,
274                            const CommandManager::ArgList& args, bool*)
275 {
276     if (args.size() != 0)
277         return "usage: jump";
278     LocalPlayer *myTank = LocalPlayer::getMyTank();
279     if (myTank != NULL)
280         myTank->setJump();
281     return std::string();
282 }
283 
cmdToggleFlags(const std::string &,const CommandManager::ArgList & args,bool *)284 static std::string cmdToggleFlags(const std::string&, const
285                                   CommandManager::ArgList& args, bool*)
286 {
287     if (args.size() != 1)
288         return "usage: main|radar";
289     if (args[0] == "main")
290     {
291         CMDMGR.run("toggle displayMainFlags");
292         warnAboutMainFlags();
293     }
294     else if (args[0] == "radar")
295     {
296         CMDMGR.run("toggle displayRadarFlags");
297         warnAboutRadarFlags();
298     }
299     else
300         return "usage: main|radar";
301 
302     return std::string();
303 }
304 
cmdToggleRadar(const std::string &,const CommandManager::ArgList & args,bool *)305 static std::string cmdToggleRadar(const std::string&,
306                                   const CommandManager::ArgList& args, bool*)
307 {
308     if (args.size() != 0)
309         return "usage: toggleRadar";
310 
311     CMDMGR.run("toggle displayRadar");
312 
313     warnAboutRadar();
314 
315     return std::string();
316 }
317 
cmdToggleConsole(const std::string &,const CommandManager::ArgList & args,bool *)318 static std::string cmdToggleConsole(const std::string&,
319                                     const CommandManager::ArgList& args, bool*)
320 {
321     if (args.size() != 0)
322         return "usage: toggleConsole";
323 
324     CMDMGR.run("toggle displayConsole");
325 
326     warnAboutConsole();
327 
328     return std::string();
329 }
330 
331 
cmdFire(const std::string &,const CommandManager::ArgList & args,bool *)332 static std::string cmdFire(const std::string&,
333                            const CommandManager::ArgList& args, bool*)
334 {
335     if (args.size() != 0)
336         return "usage: fire";
337     LocalPlayer *myTank = LocalPlayer::getMyTank();
338     if (fireButton && myTank != NULL && myTank->isAlive()
339             && myTank->getTeam() != ObserverTeam)
340         myTank->fireShot();
341     return std::string();
342 }
343 
cmdDrop(const std::string &,const CommandManager::ArgList & args,bool *)344 static std::string cmdDrop(const std::string&,
345                            const CommandManager::ArgList& args, bool*)
346 {
347     if (args.size() != 0)
348         return "usage: drop";
349     LocalPlayer *myTank = LocalPlayer::getMyTank();
350     if (myTank != NULL)
351     {
352         FlagType* flag = myTank->getFlag();
353         if ((flag != Flags::Null) && !myTank->isPaused() &&
354                 (flag->endurance != FlagSticky) && !myTank->isPhantomZoned() &&
355                 !(flag == Flags::OscillationOverthruster &&
356                   myTank->getLocation() == LocalPlayer::InBuilding))
357         {
358             serverLink->sendDropFlag(myTank->getPosition());
359             // changed: on windows it may happen the MsgDropFlag
360             // never comes back to us, so we drop it right away
361             handleFlagDropped(myTank);
362         }
363     }
364     return std::string();
365 }
366 
cmdIdentify(const std::string &,const CommandManager::ArgList & args,bool *)367 static std::string cmdIdentify(const std::string&,
368                                const CommandManager::ArgList& args, bool*)
369 {
370     if (args.size() != 0)
371         return "usage: identify";
372     LocalPlayer *myTank = LocalPlayer::getMyTank();
373     if (myTank != NULL)
374         if (myTank->isAlive() && !myTank->isPaused())
375             setTarget();
376     return std::string();
377 }
378 
cmdRestart(const std::string &,const CommandManager::ArgList & args,bool *)379 static std::string cmdRestart(const std::string&,
380                               const CommandManager::ArgList& args, bool*)
381 {
382     if (args.size() != 0)
383         return "usage: restart";
384     LocalPlayer *myTank = LocalPlayer::getMyTank();
385     if (myTank != NULL)
386         if (!gameOver && !myTank->isSpawning() && (myTank->getTeam() != ObserverTeam) && !myTank->isAlive()
387                 && !myTank->isExploding())
388         {
389             serverLink->sendAlive();
390             myTank->setSpawning(true);
391             CommandManager::ArgList zoomArgs;
392             std::string resetArg = "reset";
393             zoomArgs.push_back(resetArg);
394             cmdViewZoom("", zoomArgs,NULL);
395         }
396 
397     return std::string();
398 }
399 
cmdDestruct(const std::string &,const CommandManager::ArgList & args,bool *)400 static std::string cmdDestruct(const std::string&,
401                                const CommandManager::ArgList& args, bool*)
402 {
403     if (args.size() != 0)
404         return "usage: destruct";
405     LocalPlayer *myTank = LocalPlayer::getMyTank();
406     if (myTank != NULL)
407     {
408         if (destructCountdown > 0.0f)
409         {
410             destructCountdown = 0.0f;
411             hud->setAlert(1, "Self Destruct cancelled", 1.5f, true);
412         }
413         else
414         {
415             destructCountdown = 5.0f;
416             char msgBuf[40];
417             sprintf(msgBuf, "Self Destructing in %d", (int)(destructCountdown + 0.99f));
418             hud->setAlert(1, msgBuf, 1.0f, false);
419         }
420     }
421     return std::string();
422 }
423 
cmdPause(const std::string &,const CommandManager::ArgList & args,bool *)424 static std::string cmdPause(const std::string&,
425                             const CommandManager::ArgList& args, bool*)
426 {
427     if (args.size() != 0)
428         return "usage: pause";
429 
430     LocalPlayer *myTank = LocalPlayer::getMyTank();
431     if (!pausedByUnmap && myTank && myTank->isAlive() && !myTank->isAutoPilot())
432     {
433         if (myTank->isPaused())
434         {
435             // already paused, so unpause
436             myTank->setPause(false);
437             controlPanel->addMessage("Resumed");
438 
439             // restore the sound
440             if (savedVolume != -1)
441             {
442                 setSoundVolume(savedVolume);
443                 savedVolume = -1;
444             }
445 
446             // grab mouse
447             if (shouldGrabMouse())
448                 mainWindow->grabMouse();
449 
450         }
451         else if (pauseCountdown > 0.0f)
452         {
453             // player aborted pause
454             pauseCountdown = 0.0f;
455             hud->setAlert(1, "Pause cancelled", 1.5f, true);
456 
457         }
458         else if (myTank->getLocation() == LocalPlayer::InBuilding)
459         {
460             // custom message when trying to pause while in a building
461             // (could get stuck on un-pause if flag is taken)
462             hud->setAlert(1, "Can't pause while inside a building", 1.0f, false);
463 
464         }
465         else if (myTank->getLocation() == LocalPlayer::InAir)
466         {
467             // custom message when trying to pause when jumping/falling
468             hud->setAlert(1, "Can't pause when you are in the air", 1.0f, false);
469 
470         }
471         else if (myTank->getLocation() != LocalPlayer::OnGround &&
472                  myTank->getLocation() != LocalPlayer::OnBuilding)
473         {
474             // catch-all message when trying to pause when you should not
475             hud->setAlert(1, "Unable to pause right now", 1.0f, false);
476 
477         }
478         else
479         {
480             // update the pause alert message
481             pauseCountdown = 5.0f;
482             char msgBuf[40];
483             sprintf(msgBuf, "Pausing in %d", (int) (pauseCountdown + 0.99f));
484             hud->setAlert(1, msgBuf, 1.0f, false);
485         }
486     }
487     return std::string();
488 }
489 
cmdAutoPilot(const std::string &,const CommandManager::ArgList & args,bool *)490 static std::string cmdAutoPilot(const std::string&,
491                                 const CommandManager::ArgList& args, bool*)
492 {
493     if (args.size() != 0)
494         return "usage: autopilot";
495 
496     // don't enable autopilot until we've fully joined and checked the value
497     // of the server-side _disableBots
498     if (! BZDB.isSet(StateDatabase::BZDB_DISABLEBOTS))
499         return std::string();
500 
501     LocalPlayer *myTank = LocalPlayer::getMyTank();
502 
503     if (!BZDB.isTrue(StateDatabase::BZDB_TANKWIDTH))
504         return std::string();
505 
506     if ((myTank == NULL) || (myTank->getTeam() == ObserverTeam))
507         return std::string();
508 
509     if (myTank->isAutoPilot())
510     {
511 
512         myTank->activateAutoPilot(false);
513         hud->setAlert(0, "autopilot disabled", 1.0f, true);
514 
515         // grab mouse
516         if (shouldGrabMouse()) mainWindow->grabMouse();
517 
518     }
519     else if (BZDB.isTrue(StateDatabase::BZDB_DISABLEBOTS))
520         hud->setAlert(0, "autopilot not allowed on this server", 1.0f, true);
521     else
522     {
523 
524         // don't enable the AutoPilot if you have within the last 5 secs
525         static TimeKeeper LastAutoPilotEnable = TimeKeeper::getSunGenesisTime();
526         if ((TimeKeeper::getCurrent() - LastAutoPilotEnable) > 5)
527         {
528             // reset timer
529             LastAutoPilotEnable = TimeKeeper::getCurrent();
530 
531             // enable autopilot
532             myTank->activateAutoPilot();
533             hud->setAlert(0, "autopilot enabled", 1.0f, true);
534 
535             // ungrab mouse
536             mainWindow->ungrabMouse();
537         }
538         else
539             controlPanel->addMessage("You may not enable the Autopilot more than once every five seconds.");
540 
541     }
542 
543     return std::string();
544 }
545 
cmdRadarZoom(const std::string &,const CommandManager::ArgList & args,bool *)546 static std::string cmdRadarZoom(const std::string&,
547                                 const CommandManager::ArgList& args, bool*)
548 {
549     if (args.size() != 1)
550         return "usage: radarZoom {in|out}";
551 
552     float range = BZDB.eval("displayRadarRange");
553 
554     if (args[0] == "out")
555     {
556         range *= 1.05f;
557         if (range > 2.0f)
558             range = 2.0f;
559         BZDB.setFloat("displayRadarRange", range);
560     }
561     else if (args[0] == "in")
562     {
563         range /= 1.05f;
564         if (range < 0.005f)
565             range = 0.005f;
566         BZDB.setFloat("displayRadarRange", range);
567     }
568     else
569         return "usage: radarZoom {in|out}";
570 
571     return std::string();
572 }
573 
cmdViewZoom(const std::string &,const CommandManager::ArgList & args,bool *)574 static std::string cmdViewZoom(const std::string&,
575                                const CommandManager::ArgList& args, bool*)
576 {
577     if (args.size() != 1)
578         return "usage: viewZoom {in|out|toggle}";
579 
580     float fov = BZDB.eval("displayFOV");
581     float defFov = BZDB.eval("defaultFOV");
582 
583     if (args[0] == "out")
584     {
585         fov += 1.0f;
586         if (fov > defFov)
587             fov = defFov;
588         BZDB.setFloat("displayFOV", fov);
589     }
590     else if (args[0] == "in")
591     {
592         fov -= 1.0f;
593         if (fov < 15.0f)
594             fov = 15.0f;
595         BZDB.setFloat("displayFOV", fov);
596     }
597     else if (args[0] == "toggle")
598     {
599         if (fov < 15.5f)
600             fov = defFov;
601         else
602             fov = 15.0f;
603         BZDB.setFloat("displayFOV", fov);
604         // also toggle the observer fov
605         if (ROAM.getZoom() != defFov)
606             ROAM.setZoom(defFov);
607         else
608             ROAM.setZoom(15.0f);
609     }
610     else if (args[0] == "reset")
611     {
612         fov = defFov;
613         ROAM.setZoom(defFov);
614         BZDB.setFloat("displayFOV", fov);
615     }
616     else
617         return "usage: viewZoom {in|out|toggle|reset}";
618 
619     return std::string();
620 }
621 
cmdMessagePanel(const std::string &,const CommandManager::ArgList & args,bool *)622 static std::string cmdMessagePanel(const std::string&,
623                                    const CommandManager::ArgList& args, bool*)
624 {
625     if (args.size() != 1)
626         return "usage: messagepanel {all|chat|server|misc}";
627 
628     int oldMode = controlPanel->getMessagesMode();
629     int newMode;
630     if (args[0] == "all")
631         newMode = 0;
632     else if (args[0] == "chat")
633         newMode = 1;
634     else if (args[0] == "server")
635         newMode = 2;
636     else if (args[0] == "misc")
637         newMode = 3;
638     else
639         return "usage: messagepanel {all|chat|server|misc}";
640 
641     if (newMode == oldMode)
642         newMode = -1;
643     controlPanel->setMessagesMode(newMode);
644 
645     return std::string();
646 }
647 
cmdSend(const std::string &,const CommandManager::ArgList & args,bool *)648 static std::string cmdSend(const std::string&,
649                            const CommandManager::ArgList& args, bool*)
650 {
651     static ComposeDefaultKey composeKeyHandler;
652     if (args.size() != 1)
653         return "usage: send {all|team|nemesis|recipient|admin}";
654     LocalPlayer *myTank = LocalPlayer::getMyTank();
655     if (!myTank)
656         return "use send only when connected";
657     std::string composePrompt;
658     if (args[0] == "all")
659     {
660         void* buf = messageMessage;
661         buf = nboPackUByte(buf, AllPlayers);
662         composePrompt = "Send to all: ";
663     }
664     else if (args[0] == "team")
665     {
666         if (World::getWorld()->allowTeams() || myTank->getTeam() == ObserverTeam)
667         {
668             void* buf = messageMessage;
669             buf = nboPackUByte(buf, TeamToPlayerId(myTank->getTeam()));
670             composePrompt = "Send to teammates: ";
671         }
672         else
673         {
674             void* buf = messageMessage;
675             buf = nboPackUByte(buf, AllPlayers);
676             composePrompt = "Send to all: ";
677         }
678     }
679     else if (args[0] == "nemesis")
680     {
681         const Player* nemesis = myTank->getNemesis();
682         if (!nemesis) return std::string();
683 
684         void* buf = messageMessage;
685         buf = nboPackUByte(buf, nemesis->getId());
686         composePrompt = "Send to ";
687         composePrompt += nemesis->getCallSign();
688         composePrompt += ": ";
689     }
690     else if (args[0] == "recipient")
691     {
692         const Player* recipient = myTank->getRecipient();
693         if (!recipient)
694         {
695             for (int i = 0; i < curMaxPlayers; i++)
696             {
697                 if (remotePlayers[i])
698                 {
699                     myTank->setRecipient(remotePlayers[i]);
700                     break;
701                 }
702             }
703         }
704         recipient = myTank->getRecipient();
705         if (recipient)
706         {
707             void* buf = messageMessage;
708             buf = nboPackUByte(buf, recipient->getId());
709             composePrompt = "Send to ";
710             composePrompt += recipient->getCallSign();
711             composePrompt += ": ";
712         }
713     }
714     else if (args[0] == "admin")
715     {
716         void* buf = messageMessage;
717         buf = nboPackUByte(buf, AdminPlayers);
718         composePrompt = "Send to Admin: ";
719 
720     }
721     else
722         return "usage: send {all|team|nemesis|recipient|admin}";
723     messageHistoryIndex = 0;
724     hud->setComposing(composePrompt);
725     HUDui::setDefaultKey(&composeKeyHandler);
726     return std::string();
727 }
728 
729 
730 struct ScreenshotData
731 {
732     std::string renderer;
733     unsigned char* pixels;
734     int xsize;
735     int ysize;
736     int channels;
737 };
738 
739 #ifdef _WIN32
writeScreenshot(void * data)740 static DWORD WINAPI writeScreenshot(void* data)
741 #else
742 static void* writeScreenshot(void* data)
743 #endif
744 {
745     ScreenshotData* ssdata = (ScreenshotData*)data;
746 
747     const std::string dirname = getScreenShotDirName();
748     const std::string prefix  = "bzfi";
749     const std::string ext     = ".png";
750 
751     // scan the directory and start numbering with the filename
752     // that follows the existing filename with the highest snap number
753     int snap = 0;
754 
755 #ifdef _WIN32
756     const std::string pattern = dirname + prefix + "*" + ext;
757     WIN32_FIND_DATA findData;
758     HANDLE h = FindFirstFile(pattern.c_str(), &findData);
759     if (h != INVALID_HANDLE_VALUE)
760     {
761         std::string file = findData.cFileName;
762         snap = atoi((file.substr(file.length() - 8, 4)).c_str());
763         while (FindNextFile(h, &findData))
764         {
765             file = findData.cFileName;
766             const int number = atoi((file.substr(file.length() - 8, 4)).c_str());
767             if (snap < number)
768                 snap = number;
769         }
770     }
771 #else
772     const std::string pattern = prefix + "*" + ext;
773     DIR* directory = opendir(dirname.c_str());
774     if (directory)
775     {
776         struct dirent* contents;
777         std::string file;
778         while ((contents = readdir(directory)))
779         {
780             file = contents->d_name;
781             if (glob_match(pattern, file))
782             {
783                 const int number = atoi((file.substr(file.length() - 8, 4)).c_str());
784                 if (snap < number)
785                     snap = number;
786             }
787         }
788         closedir(directory);
789     }
790 #endif // _WIN32
791 
792     snap++;
793     std::string filename = dirname + prefix + TextUtils::format("%04d", snap) + ext;
794 
795     std::ostream* f = FILEMGR.createDataOutStream(filename.c_str(), true, true);
796 
797     if (f != NULL)
798     {
799         delete(f);
800 
801         const std::string& renderer = ssdata->renderer;
802         unsigned char* pixels       = ssdata->pixels;
803         const int xsize      = ssdata->xsize;
804         const int ysize      = ssdata->ysize;
805         const int channels    = ssdata->channels;
806 
807         // Gamma-correction is preapplied by BZFlag's gamma table
808         // This ignores the PNG gAMA chunk, but so do many viewers (including Mozilla)
809         if (BZDB.isSet("gamma"))
810         {
811             const float gamma = BZDB.eval("gamma");
812             if (gamma != 1.0f)
813             {
814                 unsigned char gammaTable[256];
815                 for (int i = 0; i < 256; i++)
816                 {
817                     const float lum    = float(i) / 256.0f;
818                     const float lumadj = pow(lum, 1.0f / gamma);
819                     gammaTable[i] = (unsigned char) (lumadj * 256);
820                 }
821                 const int pixelCount = (xsize * ysize * channels);
822                 for (int i = 0; i < pixelCount; i++)
823                     pixels[i] = gammaTable[pixels[i]];
824             }
825         }
826 
827         const std::string versionStr = std::string("BZFlag") + getAppVersion();
828         std::vector<BzPNG::Chunk> chunks;
829         chunks.push_back(BzPNG::Chunk("tEXt", "Software", versionStr));
830         chunks.push_back(BzPNG::Chunk("tEXt", "GL Renderer", renderer));
831 
832         char buf[128];
833         if (BzPNG::save(filename, chunks, xsize, ysize, channels, pixels))
834             snprintf(buf, sizeof(buf), "%s: %dx%d", filename.c_str(), xsize, ysize);
835         else
836             snprintf(buf, sizeof(buf), "%s: failed to save", filename.c_str());
837         ControlPanel::addMutexMessage(buf);
838     }
839 
840     delete[] ssdata->pixels;
841     delete ssdata;
842 
843     return NULL;
844 }
845 
cmdScreenshot(const std::string &,const CommandManager::ArgList & args,bool *)846 static std::string cmdScreenshot(const std::string&, const CommandManager::ArgList& args, bool*)
847 {
848     if (args.size() != 0)
849         return "usage: screenshot";
850 
851     ScreenshotData* ssdata = new ScreenshotData;
852     ssdata->renderer += (const char*)(glGetString(GL_VENDOR));
853     ssdata->renderer += ": ";
854     ssdata->renderer += (const char*)(glGetString(GL_RENDERER));
855     ssdata->renderer += " (OpenGL ";
856     ssdata->renderer += (const char*)(glGetString(GL_VERSION));
857     ssdata->renderer += ")";
858     int w = mainWindow->getWidth();
859     int h = mainWindow->getHeight();
860     ssdata->xsize = w;
861     ssdata->ysize = h;
862     ssdata->channels = 3; // GL_RGB
863     ssdata->pixels = new unsigned char[h * w * 3];
864     glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT);
865     glPixelStorei(GL_PACK_ALIGNMENT, 1);
866     glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, ssdata->pixels);
867     glPopClientAttrib();
868 
869 #if defined(HAVE_PTHREADS)
870     pthread_t thread;
871     pthread_attr_t attr;
872 
873     pthread_attr_init(&attr);
874     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
875     pthread_create(&thread, &attr, writeScreenshot, (void *) ssdata);
876     pthread_attr_destroy(&attr);
877 #elif defined(_WIN32)
878     CreateThread(
879         NULL, // Security attributes
880         0, // Stack size (0 -> default)
881         writeScreenshot,
882         ssdata,
883         0, // creation flags (0 -> run immediately)
884         NULL); // thread id return value (NULL -> don't care)
885 #else
886     // no threads?  sucks to be you, but we'll still write the screenshot
887     writeScreenshot(ssdata);
888 #endif
889 
890     return std::string();
891 }
892 
cmdTime(const std::string &,const CommandManager::ArgList & args,bool *)893 static std::string cmdTime(const std::string&,
894                            const CommandManager::ArgList& args, bool*)
895 {
896     // FIXME - time should be moved into BZDB
897     if (args.size() != 1)
898         return "usage: time {forward|backward}";
899     if (args[0] == "forward")
900         clockAdjust += 5.0f * 60.0f;
901     else if (args[0] == "backward")
902         clockAdjust -= 5.0f * 60.0f;
903     else
904         return "usage: time {forward|backward}";
905     return std::string();
906 }
907 
cmdRoam(const std::string &,const CommandManager::ArgList & args,bool *)908 static std::string cmdRoam(const std::string&,
909                            const CommandManager::ArgList& args, bool*)
910 {
911     if (args.size() == 0)
912         return "usage: roam {zoom|cycle} <args>";
913     if (!ROAM.isRoaming())
914         return std::string();
915     if (args[0] == "zoom")
916     {
917         if (args.size() != 2)
918             return "usage: roam zoom {in|out|normal|stop}";
919         if (!roamButton || args[1] == "stop")
920             roamDZoom = 0.0f;
921         else if (args[1] == "out")
922             roamDZoom = +2.0f * BZDBCache::tankSpeed;
923         else if (args[1] == "in")
924             roamDZoom = -2.0f * BZDBCache::tankSpeed;
925         else if (args[1] == "normal")
926             ROAM.setZoom(60.0f);
927         else
928             return "usage: roam zoom {in|out|normal|stop}";
929     }
930     else if (args[0] == "cycle")
931     {
932         if (args.size() != 3)
933             return "usage: roam cycle {type|subject} {forward|backward}";
934         if (args[1] == "type")
935         {
936             if (args[2] == "forward")
937                 ROAM.setMode(Roaming::RoamingView((ROAM.getMode() + 1) % Roaming::roamViewCount));
938             else if (args[2] == "backward")
939             {
940                 int setto = (ROAM.getMode() - 1) % Roaming::roamViewCount;
941                 if (setto < 0) setto += Roaming::roamViewCount;
942                 ROAM.setMode(Roaming::RoamingView(setto));
943             }
944             else
945                 return "usage: roam cycle {type|subject} {forward|backward}";
946         }
947         else if (args[1] == "subject")
948         {
949             if (args[2] == "forward")
950                 ROAM.changeTarget(Roaming::next);
951             else if (args[2] == "backward")
952                 ROAM.changeTarget(Roaming::previous);
953             else
954                 return "usage: roam cycle {type|subject} {forward|backward}";
955         }
956         else
957             return "usage: roam cycle {type|subject} {forward|backward}";
958     }
959     else
960         return "usage: roam {zoom|cycle} <args>";
961     return std::string();
962 }
963 
cmdSilence(const std::string &,const CommandManager::ArgList & args,bool *)964 static std::string cmdSilence(const std::string&,
965                               const CommandManager::ArgList& args, bool*)
966 {
967     static SilenceDefaultKey silenceKeyHandler;
968     if (args.size() != 0)
969         return "usage: silence";
970     messageHistoryIndex = 0;
971     hud->setComposing("[Un]Silence: ");
972     HUDui::setDefaultKey(&silenceKeyHandler);
973     return std::string();
974 }
975 
cmdServerCommand(const std::string &,const CommandManager::ArgList & args,bool *)976 static std::string cmdServerCommand(const std::string&,
977                                     const CommandManager::ArgList& args, bool*)
978 {
979     static ServerCommandKey serverCommandKeyHandler;
980     if (args.size() != 0)
981         return "usage: servercommand";
982     LocalPlayer *myTank = LocalPlayer::getMyTank();
983     if (!myTank)
984         return "use only when connected";
985     static bool prevAdmin = myTank->isAdmin();
986     if (!prevAdmin && myTank->isAdmin()) serverCommandKeyHandler.adminInit();
987     if (prevAdmin && !myTank->isAdmin()) serverCommandKeyHandler.nonAdminInit();
988     prevAdmin = myTank->isAdmin();
989 
990     messageHistoryIndex = 0;
991     serverCommandKeyHandler.init();
992     HUDui::setDefaultKey(&serverCommandKeyHandler);
993     return std::string();
994 }
995 
cmdScrollPanel(const std::string &,const CommandManager::ArgList & args,bool *)996 static std::string cmdScrollPanel(const std::string&,
997                                   const CommandManager::ArgList& args, bool*)
998 {
999     if ((args.size() < 1) || (args.size() > 2))
1000         return "usage: scrollpanel {up|up_page|down|down_page|top|bottom} [count]\n";
1001     int count = 1;
1002     int linecount = 2;
1003     if (args.size() == 2)
1004     {
1005         count = atoi(args[1].c_str());
1006         linecount = count;
1007     }
1008     // whence - (0 = set, 1 = cur, 2 = end)
1009     if (args[0] == "up")
1010         controlPanel->setMessagesOffset(+linecount, 1 /* current */, false);
1011     else if (args[0] == "down")
1012         controlPanel->setMessagesOffset(-linecount, 1 /* current */, false);
1013     else if (args[0] == "up_page")
1014         controlPanel->setMessagesOffset(+count, 1 /* current */, true);
1015     else if (args[0] == "down_page")
1016         controlPanel->setMessagesOffset(-count, 1 /* current */, true);
1017     else if (args[0] == "top")
1018         controlPanel->setMessagesOffset(123456789, 0 /* set */, false);
1019     else if (args[0] == "bottom")
1020         controlPanel->setMessagesOffset(0, 0 /* set */, false);
1021     else if (args[0] == "pause")
1022         controlPanel->togglePaused();
1023     return std::string();
1024 }
1025 
1026 
cmdHunt(const std::string &,const CommandManager::ArgList & args,bool *)1027 static std::string cmdHunt(const std::string&,
1028                            const CommandManager::ArgList& args, bool*)
1029 {
1030     if (args.size() != 0)
1031         return "usage: hunt";
1032     LocalPlayer *myTank = LocalPlayer::getMyTank();
1033     if (!myTank)
1034         return "use only when connected";
1035     hud->getScoreboard()->huntKeyEvent (false);
1036     return std::string();
1037 }
1038 
cmdAddHunt(const std::string &,const CommandManager::ArgList & args,bool *)1039 static std::string cmdAddHunt(const std::string&,
1040                               const CommandManager::ArgList& args, bool*)
1041 {
1042     if (args.size() != 0)
1043         return "usage: addhunt";
1044     LocalPlayer *myTank = LocalPlayer::getMyTank();
1045     if (!myTank)
1046         return "use only when connected";
1047     hud->getScoreboard()->huntKeyEvent (true);
1048     return std::string();
1049 }
1050 
cmdCycleRadar(const std::string &,const CommandManager::ArgList & args,bool *)1051 static std::string cmdCycleRadar(const std::string&,
1052                                  const CommandManager::ArgList& args, bool*)
1053 {
1054     const std::string usageText = "usage: cycleRadar {level1 [level2 ...] [off]}:  cycle to the next radar zoom level";
1055 
1056     if (args.size() == 0)
1057         return usageText;
1058 
1059     std::vector<float> radarLevels;
1060 
1061     for (size_t i = 0; i < args.size(); ++i)
1062         if (args[i] == "off")
1063             radarLevels.push_back(0.0f);
1064         else if (atof(args[i].c_str()) > 0.0f)
1065             radarLevels.push_back((float)atof(args[i].c_str()));
1066         else
1067             return usageText;
1068 
1069     if (radarLevels.size() == 0)
1070         return usageText;
1071 
1072     if (radarLevels.size() == 1)
1073     {
1074         // only one specified... just set it
1075         BZDB.set("displayRadar", radarLevels[0] > 0.0f ? "1" : "0");
1076 
1077         if (radarLevels[0] > 0.0f)
1078             BZDB.setFloat("displayRadarRange", radarLevels[0]);
1079 
1080         return std::string();
1081     }
1082 
1083     static size_t radarLevelIndex = radarLevels.size() - 1;
1084 
1085     // if it's off and the current level is some form of on, turn it back on and set it
1086     if (BZDB.get("displayRadar") == "0" && radarLevels[radarLevelIndex] > 0.0f)
1087     {
1088         BZDB.set("displayRadar", "1");
1089         BZDB.setFloat("displayRadarRange", radarLevels[radarLevelIndex]);
1090 
1091         return std::string();
1092     }
1093 
1094     ++radarLevelIndex;
1095     if (radarLevelIndex >= radarLevels.size())
1096         radarLevelIndex = 0;
1097 
1098     if (radarLevels[radarLevelIndex] == 0.0f)
1099         BZDB.set("displayRadar", "0");
1100     else
1101     {
1102         BZDB.setFloat("displayRadarRange", radarLevels[radarLevelIndex]);
1103         BZDB.set("displayRadar", "1");
1104     }
1105 
1106     return std::string();
1107 }
1108 
cmdCyclePanel(const std::string &,const CommandManager::ArgList & args,bool *)1109 static std::string cmdCyclePanel(const std::string&,
1110                                  const CommandManager::ArgList& args, bool*)
1111 {
1112     if (args.size() != 1)
1113         return "usage: cyclePanel {left[_off]|right[_off]}\n";
1114 
1115     bool forward = args[0] == "right" || args[0] == "right_off";
1116     bool includeOff = args[0] == "left_off" || args[0] == "right_off";
1117 
1118     if (! BZDB.isTrue("displayConsole"))
1119     {
1120         if (forward && includeOff && controlPanel->getMessagesMode() == 3)
1121             // reversed directions... put it back at beginning
1122             controlPanel->setMessagesMode(0);
1123         else if (! forward && includeOff && controlPanel->getMessagesMode() == 0)
1124             // reversed directions... put it back at end
1125             controlPanel->setMessagesMode(3);
1126 
1127         BZDB.setBool("displayConsole", true);
1128     }
1129     else
1130     {
1131         if (controlPanel->getMessagesMode() == 0)
1132         {
1133             controlPanel->setMessagesMode(forward ? 1 : 3);
1134 
1135             if (! forward && includeOff)
1136                 BZDB.setBool("displayConsole", false);
1137         }
1138         else if (controlPanel->getMessagesMode() == 1)
1139             controlPanel->setMessagesMode(forward ? 2 : 0);
1140         else if (controlPanel->getMessagesMode() == 2)
1141             controlPanel->setMessagesMode(forward ? 3 : 1);
1142         else
1143         {
1144             controlPanel->setMessagesMode(forward ? 0 : 2);
1145 
1146             if (forward && includeOff)
1147                 BZDB.setBool("displayConsole", false);
1148         }
1149     }
1150 
1151     return std::string();
1152 }
1153 
1154 
1155 // Local Variables: ***
1156 // mode: C++ ***
1157 // tab-width: 4 ***
1158 // c-basic-offset: 4 ***
1159 // indent-tabs-mode: nil ***
1160 // End: ***
1161 // ex: shiftwidth=4 tabstop=4
1162