1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1998-2000 by DooM Legacy Team.
4 // Copyright (C) 1999-2020 by Sonic Team Junior.
5 //
6 // This program is free software distributed under the
7 // terms of the GNU General Public License, version 2.
8 // See the 'LICENSE' file for more details.
9 //-----------------------------------------------------------------------------
10 /// \file  d_netcmd.c
11 /// \brief host/client network commands
12 ///        commands are executed through the command buffer
13 ///	       like console commands, other miscellaneous commands (at the end)
14 
15 #include "doomdef.h"
16 
17 #include "console.h"
18 #include "command.h"
19 #include "i_system.h"
20 #include "g_game.h"
21 #include "hu_stuff.h"
22 #include "g_input.h"
23 #include "m_menu.h"
24 #include "r_local.h"
25 #include "r_skins.h"
26 #include "p_local.h"
27 #include "p_setup.h"
28 #include "s_sound.h"
29 #include "i_sound.h"
30 #include "m_misc.h"
31 #include "am_map.h"
32 #include "byteptr.h"
33 #include "d_netfil.h"
34 #include "p_spec.h"
35 #include "m_cheat.h"
36 #include "d_clisrv.h"
37 #include "d_net.h"
38 #include "v_video.h"
39 #include "d_main.h"
40 #include "m_random.h"
41 #include "f_finale.h"
42 #include "filesrch.h"
43 #include "mserv.h"
44 #include "z_zone.h"
45 #include "lua_script.h"
46 #include "lua_hook.h"
47 #include "m_cond.h"
48 #include "m_anigif.h"
49 #include "md5.h"
50 
51 #ifdef NETGAME_DEVMODE
52 #define CV_RESTRICT CV_NETVAR
53 #else
54 #define CV_RESTRICT 0
55 #endif
56 
57 // ------
58 // protos
59 // ------
60 
61 static void Got_NameAndColor(UINT8 **cp, INT32 playernum);
62 static void Got_WeaponPref(UINT8 **cp, INT32 playernum);
63 static void Got_Mapcmd(UINT8 **cp, INT32 playernum);
64 static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum);
65 static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum);
66 static void Got_Addfilecmd(UINT8 **cp, INT32 playernum);
67 static void Got_Pause(UINT8 **cp, INT32 playernum);
68 static void Got_Suicide(UINT8 **cp, INT32 playernum);
69 static void Got_RandomSeed(UINT8 **cp, INT32 playernum);
70 static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum);
71 static void Got_Teamchange(UINT8 **cp, INT32 playernum);
72 static void Got_Clearscores(UINT8 **cp, INT32 playernum);
73 
74 static void PointLimit_OnChange(void);
75 static void TimeLimit_OnChange(void);
76 static void NumLaps_OnChange(void);
77 static void BaseNumLaps_OnChange(void);
78 static void Mute_OnChange(void);
79 
80 static void Hidetime_OnChange(void);
81 
82 static void AutoBalance_OnChange(void);
83 static void TeamScramble_OnChange(void);
84 
85 static void NetTimeout_OnChange(void);
86 static void JoinTimeout_OnChange(void);
87 
88 static void CoopStarposts_OnChange(void);
89 static void CoopLives_OnChange(void);
90 static void ExitMove_OnChange(void);
91 
92 static void Ringslinger_OnChange(void);
93 static void Gravity_OnChange(void);
94 static void ForceSkin_OnChange(void);
95 
96 static void Name_OnChange(void);
97 static void Name2_OnChange(void);
98 static void Skin_OnChange(void);
99 static void Skin2_OnChange(void);
100 static void Color_OnChange(void);
101 static void Color2_OnChange(void);
102 static void DummyConsvar_OnChange(void);
103 static void SoundTest_OnChange(void);
104 
105 #ifdef NETGAME_DEVMODE
106 static void Fishcake_OnChange(void);
107 #endif
108 
109 static void Command_Playdemo_f(void);
110 static void Command_Timedemo_f(void);
111 static void Command_Stopdemo_f(void);
112 static void Command_StartMovie_f(void);
113 static void Command_StopMovie_f(void);
114 static void Command_Map_f(void);
115 static void Command_ResetCamera_f(void);
116 
117 static void Command_Addfile(void);
118 static void Command_ListWADS_f(void);
119 static void Command_RunSOC(void);
120 static void Command_Pause(void);
121 static void Command_Suicide(void);
122 
123 static void Command_Version_f(void);
124 #ifdef UPDATE_ALERT
125 static void Command_ModDetails_f(void);
126 #endif
127 static void Command_ShowGametype_f(void);
128 FUNCNORETURN static ATTRNORETURN void Command_Quit_f(void);
129 static void Command_Playintro_f(void);
130 
131 static void Command_Displayplayer_f(void);
132 
133 static void Command_ExitLevel_f(void);
134 static void Command_Showmap_f(void);
135 static void Command_Mapmd5_f(void);
136 
137 static void Command_Teamchange_f(void);
138 static void Command_Teamchange2_f(void);
139 static void Command_ServerTeamChange_f(void);
140 
141 static void Command_Clearscores_f(void);
142 
143 // Remote Administration
144 static void Command_Changepassword_f(void);
145 static void Command_Login_f(void);
146 static void Got_Verification(UINT8 **cp, INT32 playernum);
147 static void Got_Removal(UINT8 **cp, INT32 playernum);
148 static void Command_Verify_f(void);
149 static void Command_RemoveAdmin_f(void);
150 static void Command_MotD_f(void);
151 static void Got_MotD_f(UINT8 **cp, INT32 playernum);
152 
153 static void Command_ShowScores_f(void);
154 static void Command_ShowTime_f(void);
155 
156 static void Command_Isgamemodified_f(void);
157 static void Command_Cheats_f(void);
158 #ifdef _DEBUG
159 static void Command_Togglemodified_f(void);
160 static void Command_Archivetest_f(void);
161 #endif
162 
163 // =========================================================================
164 //                           CLIENT VARIABLES
165 // =========================================================================
166 
167 void SendWeaponPref(void);
168 void SendWeaponPref2(void);
169 
170 static CV_PossibleValue_t usemouse_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Force"}, {0, NULL}};
171 #if (defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)
172 static CV_PossibleValue_t mouse2port_cons_t[] = {{0, "/dev/gpmdata"}, {1, "/dev/ttyS0"},
173 	{2, "/dev/ttyS1"}, {3, "/dev/ttyS2"}, {4, "/dev/ttyS3"}, {0, NULL}};
174 #else
175 static CV_PossibleValue_t mouse2port_cons_t[] = {{1, "COM1"}, {2, "COM2"}, {3, "COM3"}, {4, "COM4"},
176 	{0, NULL}};
177 #endif
178 
179 #ifdef LJOYSTICK
180 static CV_PossibleValue_t joyport_cons_t[] = {{1, "/dev/js0"}, {2, "/dev/js1"}, {3, "/dev/js2"},
181 	{4, "/dev/js3"}, {0, NULL}};
182 #else
183 // accept whatever value - it is in fact the joystick device number
184 #define usejoystick_cons_t NULL
185 #endif
186 
187 static CV_PossibleValue_t teamscramble_cons_t[] = {{0, "Off"}, {1, "Random"}, {2, "Points"}, {0, NULL}};
188 
189 static CV_PossibleValue_t startingliveslimit_cons_t[] = {{1, "MIN"}, {99, "MAX"}, {0, NULL}};
190 static CV_PossibleValue_t sleeping_cons_t[] = {{-1, "MIN"}, {1000/TICRATE, "MAX"}, {0, NULL}};
191 static CV_PossibleValue_t competitionboxes_cons_t[] = {{0, "Normal"}, {1, "Mystery"}, //{2, "Teleport"},
192 	{3, "None"}, {0, NULL}};
193 
194 static CV_PossibleValue_t matchboxes_cons_t[] = {{0, "Normal"}, {1, "Mystery"}, {2, "Unchanging"},
195 	{3, "None"}, {0, NULL}};
196 
197 static CV_PossibleValue_t chances_cons_t[] = {{0, "MIN"}, {9, "MAX"}, {0, NULL}};
198 static CV_PossibleValue_t pause_cons_t[] = {{0, "Server"}, {1, "All"}, {0, NULL}};
199 
200 consvar_t cv_showinputjoy = CVAR_INIT ("showinputjoy", "Off", 0, CV_OnOff, NULL);
201 
202 #ifdef NETGAME_DEVMODE
203 static consvar_t cv_fishcake = CVAR_INIT ("fishcake", "Off", CV_CALL|CV_NOSHOWHELP|CV_RESTRICT, CV_OnOff, Fishcake_OnChange);
204 #endif
205 static consvar_t cv_dummyconsvar = CVAR_INIT ("dummyconsvar", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, DummyConsvar_OnChange);
206 
207 consvar_t cv_restrictskinchange = CVAR_INIT ("restrictskinchange", "Yes", CV_SAVE|CV_NETVAR|CV_CHEAT, CV_YesNo, NULL);
208 consvar_t cv_allowteamchange = CVAR_INIT ("allowteamchange", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL);
209 
210 consvar_t cv_startinglives = CVAR_INIT ("startinglives", "3", CV_SAVE|CV_NETVAR|CV_CHEAT, startingliveslimit_cons_t, NULL);
211 
212 static CV_PossibleValue_t respawntime_cons_t[] = {{1, "MIN"}, {30, "MAX"}, {0, "Off"}, {0, NULL}};
213 consvar_t cv_respawntime = CVAR_INIT ("respawndelay", "3", CV_SAVE|CV_NETVAR|CV_CHEAT, respawntime_cons_t, NULL);
214 
215 consvar_t cv_competitionboxes = CVAR_INIT ("competitionboxes", "Mystery", CV_SAVE|CV_NETVAR|CV_CHEAT, competitionboxes_cons_t, NULL);
216 
217 static CV_PossibleValue_t seenames_cons_t[] = {{0, "Off"}, {1, "Colorless"}, {2, "Team"}, {3, "Ally/Foe"}, {0, NULL}};
218 consvar_t cv_seenames = CVAR_INIT ("seenames", "Ally/Foe", CV_SAVE, seenames_cons_t, 0);
219 consvar_t cv_allowseenames = CVAR_INIT ("allowseenames", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL);
220 
221 // names
222 consvar_t cv_playername = CVAR_INIT ("name", "Sonic", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name_OnChange);
223 consvar_t cv_playername2 = CVAR_INIT ("name2", "Tails", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name2_OnChange);
224 // player colors
225 UINT16 lastgoodcolor = SKINCOLOR_BLUE, lastgoodcolor2 = SKINCOLOR_BLUE;
226 consvar_t cv_playercolor = CVAR_INIT ("color", "Blue", CV_CALL|CV_NOINIT, Color_cons_t, Color_OnChange);
227 consvar_t cv_playercolor2 = CVAR_INIT ("color2", "Orange", CV_CALL|CV_NOINIT, Color_cons_t, Color2_OnChange);
228 // player's skin, saved for commodity, when using a favorite skins wad..
229 consvar_t cv_skin = CVAR_INIT ("skin", DEFAULTSKIN, CV_CALL|CV_NOINIT, NULL, Skin_OnChange);
230 consvar_t cv_skin2 = CVAR_INIT ("skin2", DEFAULTSKIN2, CV_CALL|CV_NOINIT, NULL, Skin2_OnChange);
231 
232 // saved versions of the above six
233 consvar_t cv_defaultplayercolor = CVAR_INIT ("defaultcolor", "Blue", CV_SAVE, Color_cons_t, NULL);
234 consvar_t cv_defaultplayercolor2 = CVAR_INIT ("defaultcolor2", "Orange", CV_SAVE, Color_cons_t, NULL);
235 consvar_t cv_defaultskin = CVAR_INIT ("defaultskin", DEFAULTSKIN, CV_SAVE, NULL, NULL);
236 consvar_t cv_defaultskin2 = CVAR_INIT ("defaultskin2", DEFAULTSKIN2, CV_SAVE, NULL, NULL);
237 
238 consvar_t cv_skipmapcheck = CVAR_INIT ("skipmapcheck", "Off", CV_SAVE, CV_OnOff, NULL);
239 
240 INT32 cv_debug;
241 
242 consvar_t cv_usemouse = CVAR_INIT ("use_mouse", "On", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse);
243 consvar_t cv_usemouse2 = CVAR_INIT ("use_mouse2", "Off", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse2);
244 
245 consvar_t cv_usejoystick = CVAR_INIT ("use_gamepad", "1", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick);
246 consvar_t cv_usejoystick2 = CVAR_INIT ("use_gamepad2", "2", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick2);
247 #if (defined (LJOYSTICK) || defined (HAVE_SDL))
248 #ifdef LJOYSTICK
249 consvar_t cv_joyport = CVAR_INIT ("padport", "/dev/js0", CV_SAVE, joyport_cons_t, NULL);
250 consvar_t cv_joyport2 = CVAR_INIT ("padport2", "/dev/js0", CV_SAVE, joyport_cons_t, NULL); //Alam: for later
251 #endif
252 consvar_t cv_joyscale = CVAR_INIT ("padscale", "1", CV_SAVE|CV_CALL, NULL, I_JoyScale);
253 consvar_t cv_joyscale2 = CVAR_INIT ("padscale2", "1", CV_SAVE|CV_CALL, NULL, I_JoyScale2);
254 #else
255 consvar_t cv_joyscale = CVAR_INIT ("padscale", "1", CV_SAVE|CV_HIDEN, NULL, NULL); //Alam: Dummy for save
256 consvar_t cv_joyscale2 = CVAR_INIT ("padscale2", "1", CV_SAVE|CV_HIDEN, NULL, NULL); //Alam: Dummy for save
257 #endif
258 #if (defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)
259 consvar_t cv_mouse2port = CVAR_INIT ("mouse2port", "/dev/gpmdata", CV_SAVE, mouse2port_cons_t, NULL);
260 consvar_t cv_mouse2opt = CVAR_INIT ("mouse2opt", "0", CV_SAVE, NULL, NULL);
261 #else
262 consvar_t cv_mouse2port = CVAR_INIT ("mouse2port", "COM2", CV_SAVE, mouse2port_cons_t, NULL);
263 #endif
264 
265 consvar_t cv_matchboxes = CVAR_INIT ("matchboxes", "Normal", CV_SAVE|CV_NETVAR|CV_CHEAT, matchboxes_cons_t, NULL);
266 consvar_t cv_specialrings = CVAR_INIT ("specialrings", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
267 consvar_t cv_powerstones = CVAR_INIT ("powerstones", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
268 
269 consvar_t cv_recycler =      CVAR_INIT ("tv_recycler",      "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
270 consvar_t cv_teleporters =   CVAR_INIT ("tv_teleporter",    "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
271 consvar_t cv_superring =     CVAR_INIT ("tv_superring",     "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
272 consvar_t cv_supersneakers = CVAR_INIT ("tv_supersneaker",  "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
273 consvar_t cv_invincibility = CVAR_INIT ("tv_invincibility", "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
274 consvar_t cv_jumpshield =    CVAR_INIT ("tv_jumpshield",    "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
275 consvar_t cv_watershield =   CVAR_INIT ("tv_watershield",   "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
276 consvar_t cv_ringshield =    CVAR_INIT ("tv_ringshield",    "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
277 consvar_t cv_forceshield =   CVAR_INIT ("tv_forceshield",   "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
278 consvar_t cv_bombshield =    CVAR_INIT ("tv_bombshield",    "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
279 consvar_t cv_1up =           CVAR_INIT ("tv_1up",           "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
280 consvar_t cv_eggmanbox =     CVAR_INIT ("tv_eggman",        "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
281 
282 consvar_t cv_ringslinger = CVAR_INIT ("ringslinger", "No", CV_NETVAR|CV_NOSHOWHELP|CV_CALL|CV_CHEAT, CV_YesNo, Ringslinger_OnChange);
283 consvar_t cv_gravity = CVAR_INIT ("gravity", "0.5", CV_RESTRICT|CV_FLOAT|CV_CALL, NULL, Gravity_OnChange);
284 
285 consvar_t cv_soundtest = CVAR_INIT ("soundtest", "0", CV_CALL, NULL, SoundTest_OnChange);
286 
287 static CV_PossibleValue_t minitimelimit_cons_t[] = {{15, "MIN"}, {9999, "MAX"}, {0, NULL}};
288 consvar_t cv_countdowntime = CVAR_INIT ("countdowntime", "60", CV_SAVE|CV_NETVAR|CV_CHEAT, minitimelimit_cons_t, NULL);
289 
290 consvar_t cv_touchtag = CVAR_INIT ("touchtag", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
291 consvar_t cv_hidetime = CVAR_INIT ("hidetime", "30", CV_SAVE|CV_NETVAR|CV_CALL, minitimelimit_cons_t, Hidetime_OnChange);
292 
293 consvar_t cv_autobalance = CVAR_INIT ("autobalance", "Off", CV_SAVE|CV_NETVAR|CV_CALL, CV_OnOff, AutoBalance_OnChange);
294 consvar_t cv_teamscramble = CVAR_INIT ("teamscramble", "Off", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, teamscramble_cons_t, TeamScramble_OnChange);
295 consvar_t cv_scrambleonchange = CVAR_INIT ("scrambleonchange", "Off", CV_SAVE|CV_NETVAR, teamscramble_cons_t, NULL);
296 
297 consvar_t cv_friendlyfire = CVAR_INIT ("friendlyfire", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
298 consvar_t cv_itemfinder = CVAR_INIT ("itemfinder", "Off", CV_CALL, CV_OnOff, ItemFinder_OnChange);
299 
300 // Scoring type options
301 consvar_t cv_overtime = CVAR_INIT ("overtime", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL);
302 
303 consvar_t cv_rollingdemos = CVAR_INIT ("rollingdemos", "On", CV_SAVE, CV_OnOff, NULL);
304 
305 static CV_PossibleValue_t timetic_cons_t[] = {{0, "Classic"}, {1, "Centiseconds"}, {2, "Mania"}, {3, "Tics"}, {0, NULL}};
306 consvar_t cv_timetic = CVAR_INIT ("timerres", "Classic", CV_SAVE, timetic_cons_t, NULL);
307 
308 static CV_PossibleValue_t powerupdisplay_cons_t[] = {{0, "Never"}, {1, "First-person only"}, {2, "Always"}, {0, NULL}};
309 consvar_t cv_powerupdisplay = CVAR_INIT ("powerupdisplay", "First-person only", CV_SAVE, powerupdisplay_cons_t, NULL);
310 
311 static CV_PossibleValue_t pointlimit_cons_t[] = {{1, "MIN"}, {MAXSCORE, "MAX"}, {0, "None"}, {0, NULL}};
312 consvar_t cv_pointlimit = CVAR_INIT ("pointlimit", "None", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, pointlimit_cons_t, PointLimit_OnChange);
313 static CV_PossibleValue_t timelimit_cons_t[] = {{1, "MIN"}, {30, "MAX"}, {0, "None"}, {0, NULL}};
314 consvar_t cv_timelimit = CVAR_INIT ("timelimit", "None", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, timelimit_cons_t, TimeLimit_OnChange);
315 static CV_PossibleValue_t numlaps_cons_t[] = {{1, "MIN"}, {50, "MAX"}, {0, NULL}};
316 consvar_t cv_numlaps = CVAR_INIT ("numlaps", "4", CV_NETVAR|CV_CALL|CV_NOINIT, numlaps_cons_t, NumLaps_OnChange);
317 static CV_PossibleValue_t basenumlaps_cons_t[] = {{1, "MIN"}, {50, "MAX"}, {0, "Map default"}, {0, NULL}};
318 consvar_t cv_basenumlaps = CVAR_INIT ("basenumlaps", "Map default", CV_SAVE|CV_NETVAR|CV_CALL|CV_CHEAT, basenumlaps_cons_t, BaseNumLaps_OnChange);
319 
320 // Point and time limits for every gametype
321 INT32 pointlimits[NUMGAMETYPES];
322 INT32 timelimits[NUMGAMETYPES];
323 
324 // log elemental hazards -- not a netvar, is local to current player
325 consvar_t cv_hazardlog = CVAR_INIT ("hazardlog", "Yes", 0, CV_YesNo, NULL);
326 
327 consvar_t cv_forceskin = CVAR_INIT ("forceskin", "None", CV_NETVAR|CV_CALL|CV_CHEAT, NULL, ForceSkin_OnChange);
328 consvar_t cv_downloading = CVAR_INIT ("downloading", "On", 0, CV_OnOff, NULL);
329 consvar_t cv_allowexitlevel = CVAR_INIT ("allowexitlevel", "No", CV_SAVE|CV_NETVAR, CV_YesNo, NULL);
330 
331 consvar_t cv_killingdead = CVAR_INIT ("killingdead", "Off", CV_NETVAR, CV_OnOff, NULL);
332 
333 consvar_t cv_netstat = CVAR_INIT ("netstat", "Off", 0, CV_OnOff, NULL); // show bandwidth statistics
334 static CV_PossibleValue_t nettimeout_cons_t[] = {{TICRATE/7, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
335 consvar_t cv_nettimeout = CVAR_INIT ("nettimeout", "350", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange);
336 static CV_PossibleValue_t jointimeout_cons_t[] = {{5*TICRATE, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
337 consvar_t cv_jointimeout = CVAR_INIT ("jointimeout", "350", CV_CALL|CV_SAVE|CV_NETVAR, jointimeout_cons_t, JoinTimeout_OnChange);
338 consvar_t cv_maxping = CVAR_INIT ("maxping", "0", CV_SAVE|CV_NETVAR, CV_Unsigned, NULL);
339 
340 static CV_PossibleValue_t pingtimeout_cons_t[] = {{8, "MIN"}, {120, "MAX"}, {0, NULL}};
341 consvar_t cv_pingtimeout = CVAR_INIT ("pingtimeout", "10", CV_SAVE|CV_NETVAR, pingtimeout_cons_t, NULL);
342 
343 // show your ping on the HUD next to framerate. Defaults to warning only (shows up if your ping is > maxping)
344 static CV_PossibleValue_t showping_cons_t[] = {{0, "Off"}, {1, "Always"}, {2, "Warning"}, {0, NULL}};
345 consvar_t cv_showping = CVAR_INIT ("showping", "Warning", CV_SAVE, showping_cons_t, NULL);
346 
347 // Intermission time Tails 04-19-2002
348 static CV_PossibleValue_t inttime_cons_t[] = {{0, "MIN"}, {3600, "MAX"}, {0, NULL}};
349 consvar_t cv_inttime = CVAR_INIT ("inttime", "10", CV_SAVE|CV_NETVAR, inttime_cons_t, NULL);
350 
351 static CV_PossibleValue_t coopstarposts_cons_t[] = {{0, "Per-player"}, {1, "Shared"}, {2, "Teamwork"}, {0, NULL}};
352 consvar_t cv_coopstarposts = CVAR_INIT ("coopstarposts", "Per-player", CV_SAVE|CV_NETVAR|CV_CALL, coopstarposts_cons_t, CoopStarposts_OnChange);
353 
354 static CV_PossibleValue_t cooplives_cons_t[] = {{0, "Infinite"}, {1, "Per-player"}, {2, "Avoid Game Over"}, {3, "Single pool"}, {0, NULL}};
355 consvar_t cv_cooplives = CVAR_INIT ("cooplives", "Avoid Game Over", CV_SAVE|CV_NETVAR|CV_CALL|CV_CHEAT, cooplives_cons_t, CoopLives_OnChange);
356 
357 static CV_PossibleValue_t advancemap_cons_t[] = {{0, "Off"}, {1, "Next"}, {2, "Random"}, {0, NULL}};
358 consvar_t cv_advancemap = CVAR_INIT ("advancemap", "Next", CV_SAVE|CV_NETVAR, advancemap_cons_t, NULL);
359 
360 static CV_PossibleValue_t playersforexit_cons_t[] = {{0, "One"}, {1, "1/4"}, {2, "Half"}, {3, "3/4"}, {4, "All"}, {0, NULL}};
361 consvar_t cv_playersforexit = CVAR_INIT ("playersforexit", "All", CV_SAVE|CV_NETVAR, playersforexit_cons_t, NULL);
362 
363 consvar_t cv_exitmove = CVAR_INIT ("exitmove", "On", CV_SAVE|CV_NETVAR|CV_CALL, CV_OnOff, ExitMove_OnChange);
364 
365 consvar_t cv_runscripts = CVAR_INIT ("runscripts", "Yes", 0, CV_YesNo, NULL);
366 
367 consvar_t cv_pause = CVAR_INIT ("pausepermission", "Server", CV_SAVE|CV_NETVAR, pause_cons_t, NULL);
368 consvar_t cv_mute = CVAR_INIT ("mute", "Off", CV_NETVAR|CV_CALL, CV_OnOff, Mute_OnChange);
369 
370 consvar_t cv_sleep = CVAR_INIT ("cpusleep", "1", CV_SAVE, sleeping_cons_t, NULL);
371 
372 static CV_PossibleValue_t perfstats_cons_t[] = {
373 	{0, "Off"}, {1, "Rendering"}, {2, "Logic"}, {3, "ThinkFrame"}, {0, NULL}};
374 consvar_t cv_perfstats = CVAR_INIT ("perfstats", "Off", 0, perfstats_cons_t, NULL);
375 consvar_t cv_freedemocamera = CVAR_INIT("freedemocamera", "Off", CV_SAVE, CV_OnOff, NULL);
376 
377 char timedemo_name[256];
378 boolean timedemo_csv;
379 char timedemo_csv_id[256];
380 boolean timedemo_quit;
381 
382 INT16 gametype = GT_COOP;
383 UINT32 gametyperules = 0;
384 INT16 gametypecount = (GT_CTF + 1);
385 
386 boolean splitscreen = false;
387 boolean circuitmap = false;
388 INT32 adminplayers[MAXPLAYERS];
389 
390 /// \warning Keep this up-to-date if you add/remove/rename net text commands
391 const char *netxcmdnames[MAXNETXCMD - 1] =
392 {
393 	"NAMEANDCOLOR",
394 	"WEAPONPREF",
395 	"KICK",
396 	"NETVAR",
397 	"SAY",
398 	"MAP",
399 	"EXITLEVEL",
400 	"ADDFILE",
401 	"PAUSE",
402 	"ADDPLAYER",
403 	"TEAMCHANGE",
404 	"CLEARSCORES",
405 	"LOGIN",
406 	"VERIFIED",
407 	"RANDOMSEED",
408 	"RUNSOC",
409 	"REQADDFILE",
410 	"DELFILE", // replace next time we add an XD
411 	"SETMOTD",
412 	"SUICIDE",
413 	"LUACMD",
414 	"LUAVAR",
415 	"LUAFILE"
416 };
417 
418 // =========================================================================
419 //                           SERVER STARTUP
420 // =========================================================================
421 
422 /** Registers server commands and variables.
423   * Anything required by a dedicated server should probably go here.
424   *
425   * \sa D_RegisterClientCommands
426   */
D_RegisterServerCommands(void)427 void D_RegisterServerCommands(void)
428 {
429 	INT32 i;
430 
431 	for (i = 0; i < NUMGAMETYPES; i++)
432 	{
433 		gametype_cons_t[i].value = i;
434 		gametype_cons_t[i].strvalue = Gametype_Names[i];
435 	}
436 	gametype_cons_t[NUMGAMETYPES].value = 0;
437 	gametype_cons_t[NUMGAMETYPES].strvalue = NULL;
438 
439 	RegisterNetXCmd(XD_NAMEANDCOLOR, Got_NameAndColor);
440 	RegisterNetXCmd(XD_WEAPONPREF, Got_WeaponPref);
441 	RegisterNetXCmd(XD_MAP, Got_Mapcmd);
442 	RegisterNetXCmd(XD_EXITLEVEL, Got_ExitLevelcmd);
443 	RegisterNetXCmd(XD_ADDFILE, Got_Addfilecmd);
444 	RegisterNetXCmd(XD_REQADDFILE, Got_RequestAddfilecmd);
445 	RegisterNetXCmd(XD_PAUSE, Got_Pause);
446 	RegisterNetXCmd(XD_SUICIDE, Got_Suicide);
447 	RegisterNetXCmd(XD_RUNSOC, Got_RunSOCcmd);
448 	RegisterNetXCmd(XD_LUACMD, Got_Luacmd);
449 	RegisterNetXCmd(XD_LUAFILE, Got_LuaFile);
450 
451 	// Remote Administration
452 	COM_AddCommand("password", Command_Changepassword_f);
453 	COM_AddCommand("login", Command_Login_f); // useful in dedicated to kick off remote admin
454 	COM_AddCommand("promote", Command_Verify_f);
455 	RegisterNetXCmd(XD_VERIFIED, Got_Verification);
456 	COM_AddCommand("demote", Command_RemoveAdmin_f);
457 	RegisterNetXCmd(XD_DEMOTED, Got_Removal);
458 
459 	COM_AddCommand("motd", Command_MotD_f);
460 	RegisterNetXCmd(XD_SETMOTD, Got_MotD_f); // For remote admin
461 
462 	RegisterNetXCmd(XD_TEAMCHANGE, Got_Teamchange);
463 	COM_AddCommand("serverchangeteam", Command_ServerTeamChange_f);
464 
465 	RegisterNetXCmd(XD_CLEARSCORES, Got_Clearscores);
466 	COM_AddCommand("clearscores", Command_Clearscores_f);
467 	COM_AddCommand("map", Command_Map_f);
468 
469 	COM_AddCommand("exitgame", Command_ExitGame_f);
470 	COM_AddCommand("retry", Command_Retry_f);
471 	COM_AddCommand("exitlevel", Command_ExitLevel_f);
472 	COM_AddCommand("showmap", Command_Showmap_f);
473 	COM_AddCommand("mapmd5", Command_Mapmd5_f);
474 
475 	COM_AddCommand("addfile", Command_Addfile);
476 	COM_AddCommand("listwad", Command_ListWADS_f);
477 
478 	COM_AddCommand("runsoc", Command_RunSOC);
479 	COM_AddCommand("pause", Command_Pause);
480 	COM_AddCommand("suicide", Command_Suicide);
481 
482 	COM_AddCommand("gametype", Command_ShowGametype_f);
483 	COM_AddCommand("version", Command_Version_f);
484 #ifdef UPDATE_ALERT
485 	COM_AddCommand("mod_details", Command_ModDetails_f);
486 #endif
487 	COM_AddCommand("quit", Command_Quit_f);
488 
489 	COM_AddCommand("saveconfig", Command_SaveConfig_f);
490 	COM_AddCommand("loadconfig", Command_LoadConfig_f);
491 	COM_AddCommand("changeconfig", Command_ChangeConfig_f);
492 	COM_AddCommand("isgamemodified", Command_Isgamemodified_f); // test
493 	COM_AddCommand("showscores", Command_ShowScores_f);
494 	COM_AddCommand("showtime", Command_ShowTime_f);
495 	COM_AddCommand("cheats", Command_Cheats_f); // test
496 #ifdef _DEBUG
497 	COM_AddCommand("togglemodified", Command_Togglemodified_f);
498 	COM_AddCommand("archivetest", Command_Archivetest_f);
499 #endif
500 
501 	COM_AddCommand("downloads", Command_Downloads_f);
502 
503 	// for master server connection
504 	AddMServCommands();
505 
506 	// p_mobj.c
507 	CV_RegisterVar(&cv_itemrespawntime);
508 	CV_RegisterVar(&cv_itemrespawn);
509 	CV_RegisterVar(&cv_flagtime);
510 
511 	// misc
512 	CV_RegisterVar(&cv_friendlyfire);
513 	CV_RegisterVar(&cv_pointlimit);
514 	CV_RegisterVar(&cv_numlaps);
515 	CV_RegisterVar(&cv_basenumlaps);
516 
517 	CV_RegisterVar(&cv_hazardlog);
518 
519 	CV_RegisterVar(&cv_autobalance);
520 	CV_RegisterVar(&cv_teamscramble);
521 	CV_RegisterVar(&cv_scrambleonchange);
522 
523 	CV_RegisterVar(&cv_touchtag);
524 	CV_RegisterVar(&cv_hidetime);
525 
526 	CV_RegisterVar(&cv_inttime);
527 	CV_RegisterVar(&cv_advancemap);
528 	CV_RegisterVar(&cv_playersforexit);
529 	CV_RegisterVar(&cv_exitmove);
530 	CV_RegisterVar(&cv_timelimit);
531 	CV_RegisterVar(&cv_playbackspeed);
532 	CV_RegisterVar(&cv_forceskin);
533 	CV_RegisterVar(&cv_downloading);
534 
535 	CV_RegisterVar(&cv_coopstarposts);
536 	CV_RegisterVar(&cv_cooplives);
537 
538 	CV_RegisterVar(&cv_specialrings);
539 	CV_RegisterVar(&cv_powerstones);
540 	CV_RegisterVar(&cv_competitionboxes);
541 	CV_RegisterVar(&cv_matchboxes);
542 
543 	CV_RegisterVar(&cv_recycler);
544 	CV_RegisterVar(&cv_teleporters);
545 	CV_RegisterVar(&cv_superring);
546 	CV_RegisterVar(&cv_supersneakers);
547 	CV_RegisterVar(&cv_invincibility);
548 	CV_RegisterVar(&cv_jumpshield);
549 	CV_RegisterVar(&cv_watershield);
550 	CV_RegisterVar(&cv_ringshield);
551 	CV_RegisterVar(&cv_forceshield);
552 	CV_RegisterVar(&cv_bombshield);
553 	CV_RegisterVar(&cv_1up);
554 	CV_RegisterVar(&cv_eggmanbox);
555 
556 	CV_RegisterVar(&cv_ringslinger);
557 
558 	CV_RegisterVar(&cv_startinglives);
559 	CV_RegisterVar(&cv_countdowntime);
560 	CV_RegisterVar(&cv_runscripts);
561 	CV_RegisterVar(&cv_overtime);
562 	CV_RegisterVar(&cv_pause);
563 	CV_RegisterVar(&cv_mute);
564 
565 	RegisterNetXCmd(XD_RANDOMSEED, Got_RandomSeed);
566 
567 	CV_RegisterVar(&cv_allowexitlevel);
568 	CV_RegisterVar(&cv_restrictskinchange);
569 	CV_RegisterVar(&cv_allowteamchange);
570 	CV_RegisterVar(&cv_respawntime);
571 	CV_RegisterVar(&cv_killingdead);
572 
573 	// d_clisrv
574 	CV_RegisterVar(&cv_maxplayers);
575 	CV_RegisterVar(&cv_joindelay);
576 	CV_RegisterVar(&cv_rejointimeout);
577 	CV_RegisterVar(&cv_resynchattempts);
578 	CV_RegisterVar(&cv_maxsend);
579 	CV_RegisterVar(&cv_noticedownload);
580 	CV_RegisterVar(&cv_downloadspeed);
581 #ifndef NONET
582 	CV_RegisterVar(&cv_allownewplayer);
583 	CV_RegisterVar(&cv_joinnextround);
584 	CV_RegisterVar(&cv_showjoinaddress);
585 	CV_RegisterVar(&cv_blamecfail);
586 #endif
587 
588 	COM_AddCommand("ping", Command_Ping_f);
589 	CV_RegisterVar(&cv_nettimeout);
590 	CV_RegisterVar(&cv_jointimeout);
591 
592 	CV_RegisterVar(&cv_skipmapcheck);
593 	CV_RegisterVar(&cv_sleep);
594 	CV_RegisterVar(&cv_maxping);
595 	CV_RegisterVar(&cv_pingtimeout);
596 	CV_RegisterVar(&cv_showping);
597 
598 	CV_RegisterVar(&cv_allowseenames);
599 
600 	CV_RegisterVar(&cv_dummyconsvar);
601 }
602 
603 // =========================================================================
604 //                           CLIENT STARTUP
605 // =========================================================================
606 
607 /** Registers client commands and variables.
608   * Nothing needed for a dedicated server should be registered here.
609   *
610   * \sa D_RegisterServerCommands
611   */
D_RegisterClientCommands(void)612 void D_RegisterClientCommands(void)
613 {
614 	INT32 i;
615 
616 	for (i = 0; i < MAXSKINCOLORS; i++)
617 	{
618 		Color_cons_t[i].value = i;
619 		Color_cons_t[i].strvalue = skincolors[i].name;
620 	}
621 	Color_cons_t[MAXSKINCOLORS].value = 0;
622 	Color_cons_t[MAXSKINCOLORS].strvalue = NULL;
623 
624 	// Set default player names
625 	// Monster Iestyn (12/08/19): not sure where else I could have actually put this, but oh well
626 	for (i = 0; i < MAXPLAYERS; i++)
627 		sprintf(player_names[i], "Player %d", 1 + i);
628 
629 	if (dedicated)
630 		return;
631 
632 	COM_AddCommand("numthinkers", Command_Numthinkers_f);
633 	COM_AddCommand("countmobjs", Command_CountMobjs_f);
634 
635 	COM_AddCommand("changeteam", Command_Teamchange_f);
636 	COM_AddCommand("changeteam2", Command_Teamchange2_f);
637 
638 	COM_AddCommand("playdemo", Command_Playdemo_f);
639 	COM_AddCommand("timedemo", Command_Timedemo_f);
640 	COM_AddCommand("stopdemo", Command_Stopdemo_f);
641 	COM_AddCommand("playintro", Command_Playintro_f);
642 
643 	COM_AddCommand("resetcamera", Command_ResetCamera_f);
644 
645 	COM_AddCommand("setcontrol", Command_Setcontrol_f);
646 	COM_AddCommand("setcontrol2", Command_Setcontrol2_f);
647 
648 	COM_AddCommand("screenshot", M_ScreenShot);
649 	COM_AddCommand("startmovie", Command_StartMovie_f);
650 	COM_AddCommand("stopmovie", Command_StopMovie_f);
651 
652 	CV_RegisterVar(&cv_screenshot_option);
653 	CV_RegisterVar(&cv_screenshot_folder);
654 	CV_RegisterVar(&cv_screenshot_colorprofile);
655 	CV_RegisterVar(&cv_moviemode);
656 	CV_RegisterVar(&cv_movie_option);
657 	CV_RegisterVar(&cv_movie_folder);
658 	// PNG variables
659 	CV_RegisterVar(&cv_zlib_level);
660 	CV_RegisterVar(&cv_zlib_memory);
661 	CV_RegisterVar(&cv_zlib_strategy);
662 	CV_RegisterVar(&cv_zlib_window_bits);
663 	// APNG variables
664 	CV_RegisterVar(&cv_zlib_levela);
665 	CV_RegisterVar(&cv_zlib_memorya);
666 	CV_RegisterVar(&cv_zlib_strategya);
667 	CV_RegisterVar(&cv_zlib_window_bitsa);
668 	CV_RegisterVar(&cv_apng_delay);
669 	CV_RegisterVar(&cv_apng_downscale);
670 	// GIF variables
671 	CV_RegisterVar(&cv_gif_optimize);
672 	CV_RegisterVar(&cv_gif_downscale);
673 	CV_RegisterVar(&cv_gif_dynamicdelay);
674 	CV_RegisterVar(&cv_gif_localcolortable);
675 
676 	// register these so it is saved to config
677 	CV_RegisterVar(&cv_playername);
678 	CV_RegisterVar(&cv_playercolor);
679 	CV_RegisterVar(&cv_skin); // r_things.c (skin NAME)
680 	// secondary player (splitscreen)
681 	CV_RegisterVar(&cv_playername2);
682 	CV_RegisterVar(&cv_playercolor2);
683 	CV_RegisterVar(&cv_skin2);
684 	// saved versions of the above six
685 	CV_RegisterVar(&cv_defaultplayercolor);
686 	CV_RegisterVar(&cv_defaultskin);
687 	CV_RegisterVar(&cv_defaultplayercolor2);
688 	CV_RegisterVar(&cv_defaultskin2);
689 
690 	CV_RegisterVar(&cv_seenames);
691 	CV_RegisterVar(&cv_rollingdemos);
692 	CV_RegisterVar(&cv_netstat);
693 	CV_RegisterVar(&cv_netticbuffer);
694 
695 #ifdef NETGAME_DEVMODE
696 	CV_RegisterVar(&cv_fishcake);
697 #endif
698 
699 	// HUD
700 	CV_RegisterVar(&cv_timetic);
701 	CV_RegisterVar(&cv_powerupdisplay);
702 	CV_RegisterVar(&cv_itemfinder);
703 	CV_RegisterVar(&cv_showinputjoy);
704 
705 	// time attack ghost options are also saved to config
706 	CV_RegisterVar(&cv_ghost_bestscore);
707 	CV_RegisterVar(&cv_ghost_besttime);
708 	CV_RegisterVar(&cv_ghost_bestrings);
709 	CV_RegisterVar(&cv_ghost_last);
710 	CV_RegisterVar(&cv_ghost_guest);
711 
712 	COM_AddCommand("displayplayer", Command_Displayplayer_f);
713 
714 	// FIXME: not to be here.. but needs be done for config loading
715 	CV_RegisterVar(&cv_globalgamma);
716 	CV_RegisterVar(&cv_globalsaturation);
717 
718 	CV_RegisterVar(&cv_rhue);
719 	CV_RegisterVar(&cv_yhue);
720 	CV_RegisterVar(&cv_ghue);
721 	CV_RegisterVar(&cv_chue);
722 	CV_RegisterVar(&cv_bhue);
723 	CV_RegisterVar(&cv_mhue);
724 
725 	CV_RegisterVar(&cv_rgamma);
726 	CV_RegisterVar(&cv_ygamma);
727 	CV_RegisterVar(&cv_ggamma);
728 	CV_RegisterVar(&cv_cgamma);
729 	CV_RegisterVar(&cv_bgamma);
730 	CV_RegisterVar(&cv_mgamma);
731 
732 	CV_RegisterVar(&cv_rsaturation);
733 	CV_RegisterVar(&cv_ysaturation);
734 	CV_RegisterVar(&cv_gsaturation);
735 	CV_RegisterVar(&cv_csaturation);
736 	CV_RegisterVar(&cv_bsaturation);
737 	CV_RegisterVar(&cv_msaturation);
738 
739 	// m_menu.c
740 	CV_RegisterVar(&cv_compactscoreboard);
741 	CV_RegisterVar(&cv_chatheight);
742 	CV_RegisterVar(&cv_chatwidth);
743 	CV_RegisterVar(&cv_chattime);
744 	CV_RegisterVar(&cv_chatspamprotection);
745 	CV_RegisterVar(&cv_chatbacktint);
746 	CV_RegisterVar(&cv_consolechat);
747 	CV_RegisterVar(&cv_chatnotifications);
748 	CV_RegisterVar(&cv_crosshair);
749 	CV_RegisterVar(&cv_crosshair2);
750 	CV_RegisterVar(&cv_alwaysfreelook);
751 	CV_RegisterVar(&cv_alwaysfreelook2);
752 	CV_RegisterVar(&cv_chasefreelook);
753 	CV_RegisterVar(&cv_chasefreelook2);
754 	CV_RegisterVar(&cv_tutorialprompt);
755 	CV_RegisterVar(&cv_showfocuslost);
756 	CV_RegisterVar(&cv_pauseifunfocused);
757 
758 	// g_input.c
759 	CV_RegisterVar(&cv_sideaxis);
760 	CV_RegisterVar(&cv_sideaxis2);
761 	CV_RegisterVar(&cv_turnaxis);
762 	CV_RegisterVar(&cv_turnaxis2);
763 	CV_RegisterVar(&cv_moveaxis);
764 	CV_RegisterVar(&cv_moveaxis2);
765 	CV_RegisterVar(&cv_lookaxis);
766 	CV_RegisterVar(&cv_lookaxis2);
767 	CV_RegisterVar(&cv_jumpaxis);
768 	CV_RegisterVar(&cv_jumpaxis2);
769 	CV_RegisterVar(&cv_spinaxis);
770 	CV_RegisterVar(&cv_spinaxis2);
771 	CV_RegisterVar(&cv_fireaxis);
772 	CV_RegisterVar(&cv_fireaxis2);
773 	CV_RegisterVar(&cv_firenaxis);
774 	CV_RegisterVar(&cv_firenaxis2);
775 	CV_RegisterVar(&cv_deadzone);
776 	CV_RegisterVar(&cv_deadzone2);
777 	CV_RegisterVar(&cv_digitaldeadzone);
778 	CV_RegisterVar(&cv_digitaldeadzone2);
779 
780 	// filesrch.c
781 	CV_RegisterVar(&cv_addons_option);
782 	CV_RegisterVar(&cv_addons_folder);
783 	CV_RegisterVar(&cv_addons_md5);
784 	CV_RegisterVar(&cv_addons_showall);
785 	CV_RegisterVar(&cv_addons_search_type);
786 	CV_RegisterVar(&cv_addons_search_case);
787 
788 	// WARNING: the order is important when initialising mouse2
789 	// we need the mouse2port
790 	CV_RegisterVar(&cv_mouse2port);
791 #if (defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)
792 	CV_RegisterVar(&cv_mouse2opt);
793 #endif
794 	CV_RegisterVar(&cv_controlperkey);
795 
796 	CV_RegisterVar(&cv_usemouse);
797 	CV_RegisterVar(&cv_usemouse2);
798 	CV_RegisterVar(&cv_invertmouse);
799 	CV_RegisterVar(&cv_invertmouse2);
800 	CV_RegisterVar(&cv_mousesens);
801 	CV_RegisterVar(&cv_mousesens2);
802 	CV_RegisterVar(&cv_mouseysens);
803 	CV_RegisterVar(&cv_mouseysens2);
804 	CV_RegisterVar(&cv_mousemove);
805 	CV_RegisterVar(&cv_mousemove2);
806 
807 	CV_RegisterVar(&cv_usejoystick);
808 	CV_RegisterVar(&cv_usejoystick2);
809 #ifdef LJOYSTICK
810 	CV_RegisterVar(&cv_joyport);
811 	CV_RegisterVar(&cv_joyport2);
812 #endif
813 	CV_RegisterVar(&cv_joyscale);
814 	CV_RegisterVar(&cv_joyscale2);
815 
816 	// Analog Control
817 	CV_RegisterVar(&cv_analog[0]);
818 	CV_RegisterVar(&cv_analog[1]);
819 	CV_RegisterVar(&cv_useranalog[0]);
820 	CV_RegisterVar(&cv_useranalog[1]);
821 
822 	// deez New User eXperiences
823 	CV_RegisterVar(&cv_directionchar[0]);
824 	CV_RegisterVar(&cv_directionchar[1]);
825 	CV_RegisterVar(&cv_autobrake);
826 	CV_RegisterVar(&cv_autobrake2);
827 
828 	// hi here's some new controls
829 	CV_RegisterVar(&cv_cam_shiftfacing[0]);
830 	CV_RegisterVar(&cv_cam_shiftfacing[1]);
831 	CV_RegisterVar(&cv_cam_turnfacing[0]);
832 	CV_RegisterVar(&cv_cam_turnfacing[1]);
833 	CV_RegisterVar(&cv_cam_turnfacingability[0]);
834 	CV_RegisterVar(&cv_cam_turnfacingability[1]);
835 	CV_RegisterVar(&cv_cam_turnfacingspindash[0]);
836 	CV_RegisterVar(&cv_cam_turnfacingspindash[1]);
837 	CV_RegisterVar(&cv_cam_turnfacinginput[0]);
838 	CV_RegisterVar(&cv_cam_turnfacinginput[1]);
839 	CV_RegisterVar(&cv_cam_centertoggle[0]);
840 	CV_RegisterVar(&cv_cam_centertoggle[1]);
841 	CV_RegisterVar(&cv_cam_lockedinput[0]);
842 	CV_RegisterVar(&cv_cam_lockedinput[1]);
843 	CV_RegisterVar(&cv_cam_lockonboss[0]);
844 	CV_RegisterVar(&cv_cam_lockonboss[1]);
845 
846 	// s_sound.c
847 	CV_RegisterVar(&cv_soundvolume);
848 	CV_RegisterVar(&cv_closedcaptioning);
849 	CV_RegisterVar(&cv_digmusicvolume);
850 	CV_RegisterVar(&cv_midimusicvolume);
851 	CV_RegisterVar(&cv_numChannels);
852 
853 	// screen.c
854 	CV_RegisterVar(&cv_fullscreen);
855 	CV_RegisterVar(&cv_renderview);
856 	CV_RegisterVar(&cv_renderer);
857 	CV_RegisterVar(&cv_scr_depth);
858 	CV_RegisterVar(&cv_scr_width);
859 	CV_RegisterVar(&cv_scr_height);
860 
861 	CV_RegisterVar(&cv_soundtest);
862 
863 	CV_RegisterVar(&cv_perfstats);
864 
865 	// ingame object placing
866 	COM_AddCommand("objectplace", Command_ObjectPlace_f);
867 	COM_AddCommand("writethings", Command_Writethings_f);
868 	CV_RegisterVar(&cv_speed);
869 	CV_RegisterVar(&cv_opflags);
870 	CV_RegisterVar(&cv_ophoopflags);
871 	CV_RegisterVar(&cv_mapthingnum);
872 //	CV_RegisterVar(&cv_grid);
873 //	CV_RegisterVar(&cv_snapto);
874 
875 	CV_RegisterVar(&cv_freedemocamera);
876 
877 	// add cheat commands
878 	COM_AddCommand("noclip", Command_CheatNoClip_f);
879 	COM_AddCommand("god", Command_CheatGod_f);
880 	COM_AddCommand("notarget", Command_CheatNoTarget_f);
881 	COM_AddCommand("getallemeralds", Command_Getallemeralds_f);
882 	COM_AddCommand("resetemeralds", Command_Resetemeralds_f);
883 	COM_AddCommand("setrings", Command_Setrings_f);
884 	COM_AddCommand("setlives", Command_Setlives_f);
885 	COM_AddCommand("setcontinues", Command_Setcontinues_f);
886 	COM_AddCommand("devmode", Command_Devmode_f);
887 	COM_AddCommand("savecheckpoint", Command_Savecheckpoint_f);
888 	COM_AddCommand("scale", Command_Scale_f);
889 	COM_AddCommand("gravflip", Command_Gravflip_f);
890 	COM_AddCommand("hurtme", Command_Hurtme_f);
891 	COM_AddCommand("jumptoaxis", Command_JumpToAxis_f);
892 	COM_AddCommand("charability", Command_Charability_f);
893 	COM_AddCommand("charspeed", Command_Charspeed_f);
894 	COM_AddCommand("teleport", Command_Teleport_f);
895 	COM_AddCommand("rteleport", Command_RTeleport_f);
896 	COM_AddCommand("skynum", Command_Skynum_f);
897 	COM_AddCommand("weather", Command_Weather_f);
898 	COM_AddCommand("toggletwod", Command_Toggletwod_f);
899 #ifdef _DEBUG
900 	COM_AddCommand("causecfail", Command_CauseCfail_f);
901 #endif
902 #ifdef LUA_ALLOW_BYTECODE
903 	COM_AddCommand("dumplua", Command_Dumplua_f);
904 #endif
905 }
906 
907 /** Checks if a name (as received from another player) is okay.
908   * A name is okay if it is no fewer than 1 and no more than ::MAXPLAYERNAME
909   * chars long (not including NUL), it does not begin or end with a space,
910   * it does not contain non-printing characters (according to isprint(), which
911   * allows space), it does not start with a digit, and no other player is
912   * currently using it.
913   * \param name      Name to check.
914   * \param playernum Player who wants the name, so we can check if they already
915   *                  have it, and let them keep it if so.
916   * \sa CleanupPlayerName, SetPlayerName, Got_NameAndColor
917   * \author Graue <graue@oceanbase.org>
918   */
EnsurePlayerNameIsGood(char * name,INT32 playernum)919 boolean EnsurePlayerNameIsGood(char *name, INT32 playernum)
920 {
921 	INT32 ix;
922 
923 	if (strlen(name) == 0 || strlen(name) > MAXPLAYERNAME)
924 		return false; // Empty or too long.
925 	if (name[0] == ' ' || name[strlen(name)-1] == ' ')
926 		return false; // Starts or ends with a space.
927 	if (isdigit(name[0]))
928 		return false; // Starts with a digit.
929 	if (name[0] == '@' || name[0] == '~')
930 		return false; // Starts with an admin symbol.
931 
932 	// Check if it contains a non-printing character.
933 	// Note: ANSI C isprint() considers space a printing character.
934 	// Also don't allow semicolons, since they are used as
935 	// console command separators.
936 
937 	// Also, anything over 0x80 is disallowed too, since compilers love to
938 	// differ on whether they're printable characters or not.
939 	for (ix = 0; name[ix] != '\0'; ix++)
940 		if (!isprint(name[ix]) || name[ix] == ';' || (UINT8)(name[ix]) >= 0x80)
941 			return false;
942 
943 	// Check if a player is currently using the name, case-insensitively.
944 	for (ix = 0; ix < MAXPLAYERS; ix++)
945 	{
946 		if (ix != playernum && playeringame[ix]
947 			&& strcasecmp(name, player_names[ix]) == 0)
948 		{
949 			// We shouldn't kick people out just because
950 			// they joined the game with the same name
951 			// as someone else -- modify the name instead.
952 			size_t len = strlen(name);
953 
954 			// Recursion!
955 			// Slowly strip characters off the end of the
956 			// name until we no longer have a duplicate.
957 			if (len > 1)
958 			{
959 				name[len-1] = '\0';
960 				if (!EnsurePlayerNameIsGood (name, playernum))
961 					return false;
962 			}
963 			else if (len == 1) // Agh!
964 			{
965 				// Last ditch effort...
966 				sprintf(name, "%d", M_RandomKey(10));
967 				if (!EnsurePlayerNameIsGood (name, playernum))
968 					return false;
969 			}
970 			else
971 				return false;
972 		}
973 	}
974 
975 	return true;
976 }
977 
978 /** Cleans up a local player's name before sending a name change.
979   * Spaces at the beginning or end of the name are removed. Then if the new
980   * name is identical to another player's name, ignoring case, the name change
981   * is canceled, and the name in cv_playername.value or cv_playername2.value
982   * is restored to what it was before.
983   *
984   * We assume that if playernum is ::consoleplayer or ::secondarydisplayplayer
985   * the console variable ::cv_playername or ::cv_playername2 respectively is
986   * already set to newname. However, the player name table is assumed to
987   * contain the old name.
988   *
989   * \param playernum Player number who has requested a name change.
990   *                  Should be ::consoleplayer or ::secondarydisplayplayer.
991   * \param newname   New name for that player; should already be in
992   *                  ::cv_playername or ::cv_playername2 if player is the
993   *                  console or secondary display player, respectively.
994   * \sa cv_playername, cv_playername2, SendNameAndColor, SendNameAndColor2,
995   *     SetPlayerName
996   * \author Graue <graue@oceanbase.org>
997   */
CleanupPlayerName(INT32 playernum,const char * newname)998 void CleanupPlayerName(INT32 playernum, const char *newname)
999 {
1000 	char *buf;
1001 	char *p;
1002 	char *tmpname = NULL;
1003 	INT32 i;
1004 	boolean namefailed = true;
1005 
1006 	buf = Z_StrDup(newname);
1007 
1008 	do
1009 	{
1010 		p = buf;
1011 
1012 		while (*p == ' ')
1013 			p++; // remove leading spaces
1014 
1015 		if (strlen(p) == 0)
1016 			break; // empty names not allowed
1017 
1018 		if (isdigit(*p))
1019 			break; // names starting with digits not allowed
1020 
1021 		if (*p == '@' || *p == '~')
1022 			break; // names that start with @ or ~ (admin symbols) not allowed
1023 
1024 		tmpname = p;
1025 
1026 		do
1027 		{
1028 			/* from EnsurePlayerNameIsGood */
1029 			if (!isprint(*p) || *p == ';' || (UINT8)*p >= 0x80)
1030 				break;
1031 		}
1032 		while (*++p) ;
1033 
1034 		if (*p)/* bad char found */
1035 			break;
1036 
1037 		// Remove trailing spaces.
1038 		p = &tmpname[strlen(tmpname)-1]; // last character
1039 		while (*p == ' ' && p >= tmpname)
1040 		{
1041 			*p = '\0';
1042 			p--;
1043 		}
1044 
1045 		if (strlen(tmpname) == 0)
1046 			break; // another case of an empty name
1047 
1048 		// Truncate name if it's too long (max MAXPLAYERNAME chars
1049 		// excluding NUL).
1050 		if (strlen(tmpname) > MAXPLAYERNAME)
1051 			tmpname[MAXPLAYERNAME] = '\0';
1052 
1053 		// Remove trailing spaces again.
1054 		p = &tmpname[strlen(tmpname)-1]; // last character
1055 		while (*p == ' ' && p >= tmpname)
1056 		{
1057 			*p = '\0';
1058 			p--;
1059 		}
1060 
1061 		// no stealing another player's name
1062 		for (i = 0; i < MAXPLAYERS; i++)
1063 		{
1064 			if (i != playernum && playeringame[i]
1065 				&& strcasecmp(tmpname, player_names[i]) == 0)
1066 			{
1067 				break;
1068 			}
1069 		}
1070 
1071 		if (i < MAXPLAYERS)
1072 			break;
1073 
1074 		// name is okay then
1075 		namefailed = false;
1076 	} while (0);
1077 
1078 	if (namefailed)
1079 		tmpname = player_names[playernum];
1080 
1081 	// set consvars whether namefailed or not, because even if it succeeded,
1082 	// spaces may have been removed
1083 	if (playernum == consoleplayer)
1084 		CV_StealthSet(&cv_playername, tmpname);
1085 	else if (playernum == secondarydisplayplayer
1086 		|| (!netgame && playernum == 1))
1087 	{
1088 		CV_StealthSet(&cv_playername2, tmpname);
1089 	}
1090 	else I_Assert(((void)"CleanupPlayerName used on non-local player", 0));
1091 
1092 	Z_Free(buf);
1093 }
1094 
1095 /** Sets a player's name, if it is good.
1096   * If the name is not good (indicating a modified or buggy client), it is not
1097   * set, and if we are the server in a netgame, the player responsible is
1098   * kicked with a consistency failure.
1099   *
1100   * This function prints a message indicating the name change, unless the game
1101   * is currently showing the intro, e.g. when processing autoexec.cfg.
1102   *
1103   * \param playernum Player number who has requested a name change.
1104   * \param newname   New name for that player. Should be good, but won't
1105   *                  necessarily be if the client is maliciously modified or
1106   *                  buggy.
1107   * \sa CleanupPlayerName, EnsurePlayerNameIsGood
1108   * \author Graue <graue@oceanbase.org>
1109   */
SetPlayerName(INT32 playernum,char * newname)1110 static void SetPlayerName(INT32 playernum, char *newname)
1111 {
1112 	if (EnsurePlayerNameIsGood(newname, playernum))
1113 	{
1114 		if (strcasecmp(newname, player_names[playernum]) != 0)
1115 		{
1116 			if (netgame)
1117 				HU_AddChatText(va("\x82*%s renamed to %s", player_names[playernum], newname), false);
1118 
1119 			player_name_changes[playernum]++;
1120 
1121 			strcpy(player_names[playernum], newname);
1122 		}
1123 	}
1124 	else
1125 	{
1126 		CONS_Printf(M_GetText("Player %d sent a bad name change\n"), playernum+1);
1127 		if (server && netgame)
1128 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
1129 	}
1130 }
1131 
CanChangeSkin(INT32 playernum)1132 UINT8 CanChangeSkin(INT32 playernum)
1133 {
1134 	// Of course we can change if we're not playing
1135 	if (!Playing() || !addedtogame)
1136 		return true;
1137 
1138 	// Force skin in effect.
1139 	if ((cv_forceskin.value != -1) || (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0'))
1140 		return false;
1141 
1142 	// Can change skin in intermission and whatnot.
1143 	if (gamestate != GS_LEVEL)
1144 		return true;
1145 
1146 	// Server has skin change restrictions.
1147 	if (cv_restrictskinchange.value)
1148 	{
1149 		if (gametyperules & GTR_FRIENDLY)
1150 			return true;
1151 
1152 		// Can change skin during initial countdown.
1153 		if ((gametyperules & GTR_RACE) && leveltime < 4*TICRATE)
1154 			return true;
1155 
1156 		if (G_TagGametype())
1157 		{
1158 			// Can change skin during hidetime.
1159 			if (leveltime < hidetime * TICRATE)
1160 				return true;
1161 
1162 			// IT players can always change skins to persue players hiding in character only locations.
1163 			if (players[playernum].pflags & PF_TAGIT)
1164 				return true;
1165 		}
1166 
1167 		if (players[playernum].spectator || players[playernum].playerstate == PST_DEAD || players[playernum].playerstate == PST_REBORN)
1168 			return true;
1169 
1170 		return false;
1171 	}
1172 
1173 	return true;
1174 }
1175 
ForceAllSkins(INT32 forcedskin)1176 static void ForceAllSkins(INT32 forcedskin)
1177 {
1178 	INT32 i;
1179 	for (i = 0; i < MAXPLAYERS; ++i)
1180 	{
1181 		if (!playeringame[i])
1182 			continue;
1183 
1184 		SetPlayerSkinByNum(i, forcedskin);
1185 
1186 		// If it's me (or my brother), set appropriate skin value in cv_skin/cv_skin2
1187 		if (!dedicated) // But don't do this for dedicated servers, of course.
1188 		{
1189 			if (i == consoleplayer)
1190 				CV_StealthSet(&cv_skin, skins[forcedskin].name);
1191 			else if (i == secondarydisplayplayer)
1192 				CV_StealthSet(&cv_skin2, skins[forcedskin].name);
1193 		}
1194 	}
1195 }
1196 
1197 static INT32 snacpending = 0, snac2pending = 0, chmappending = 0;
1198 
1199 // name, color, or skin has changed
1200 //
SendNameAndColor(void)1201 static void SendNameAndColor(void)
1202 {
1203 	char buf[MAXPLAYERNAME+6];
1204 	char *p;
1205 
1206 	p = buf;
1207 
1208 	// normal player colors
1209 	if (G_GametypeHasTeams())
1210 	{
1211 		if (players[consoleplayer].ctfteam == 1 && cv_playercolor.value != skincolor_redteam)
1212 			CV_StealthSetValue(&cv_playercolor, skincolor_redteam);
1213 		else if (players[consoleplayer].ctfteam == 2 && cv_playercolor.value != skincolor_blueteam)
1214 			CV_StealthSetValue(&cv_playercolor, skincolor_blueteam);
1215 	}
1216 
1217 	// don't allow inaccessible colors
1218 	if (!skincolors[cv_playercolor.value].accessible)
1219 	{
1220 		if (players[consoleplayer].skincolor && skincolors[players[consoleplayer].skincolor].accessible)
1221 			CV_StealthSetValue(&cv_playercolor, players[consoleplayer].skincolor);
1222 		else if (skincolors[atoi(cv_playercolor.defaultvalue)].accessible)
1223 			CV_StealthSet(&cv_playercolor, cv_playercolor.defaultvalue);
1224 		else if (skins[players[consoleplayer].skin].prefcolor && skincolors[skins[players[consoleplayer].skin].prefcolor].accessible)
1225 			CV_StealthSetValue(&cv_playercolor, skins[players[consoleplayer].skin].prefcolor);
1226 		else {
1227 			UINT16 i = 0;
1228 			while (i<numskincolors && !skincolors[i].accessible) i++;
1229 			CV_StealthSetValue(&cv_playercolor, (i != numskincolors) ? i : SKINCOLOR_BLUE);
1230 		}
1231 	}
1232 
1233 	if (!strcmp(cv_playername.string, player_names[consoleplayer])
1234 	&& cv_playercolor.value == players[consoleplayer].skincolor
1235 	&& !strcmp(cv_skin.string, skins[players[consoleplayer].skin].name))
1236 		return;
1237 
1238 	players[consoleplayer].availabilities = R_GetSkinAvailabilities();
1239 
1240 	// We'll handle it later if we're not playing.
1241 	if (!Playing())
1242 		return;
1243 
1244 	// If you're not in a netgame, merely update the skin, color, and name.
1245 	if (!netgame)
1246 	{
1247 		INT32 foundskin;
1248 
1249 		CleanupPlayerName(consoleplayer, cv_playername.zstring);
1250 		strcpy(player_names[consoleplayer], cv_playername.zstring);
1251 
1252 		players[consoleplayer].skincolor = cv_playercolor.value;
1253 
1254 		if (players[consoleplayer].mo && !players[consoleplayer].powers[pw_dye])
1255 			players[consoleplayer].mo->color = players[consoleplayer].skincolor;
1256 
1257 		if (metalrecording)
1258 		{ // Starring Metal Sonic as themselves, obviously.
1259 			SetPlayerSkinByNum(consoleplayer, 5);
1260 			CV_StealthSet(&cv_skin, skins[5].name);
1261 		}
1262 		else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUsable(consoleplayer, foundskin))
1263 		{
1264 			//boolean notsame;
1265 
1266 			cv_skin.value = foundskin;
1267 
1268 			//notsame = (cv_skin.value != players[consoleplayer].skin);
1269 
1270 			SetPlayerSkin(consoleplayer, cv_skin.string);
1271 			CV_StealthSet(&cv_skin, skins[cv_skin.value].name);
1272 
1273 			/*if (notsame)
1274 			{
1275 				CV_StealthSetValue(&cv_playercolor, skins[cv_skin.value].prefcolor);
1276 
1277 				players[consoleplayer].skincolor = cv_playercolor.value % numskincolors;
1278 
1279 				if (players[consoleplayer].mo)
1280 					players[consoleplayer].mo->color = (UINT16)players[consoleplayer].skincolor;
1281 			}*/
1282 		}
1283 		else
1284 		{
1285 			cv_skin.value = players[consoleplayer].skin;
1286 			CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
1287 			// will always be same as current
1288 			SetPlayerSkin(consoleplayer, cv_skin.string);
1289 		}
1290 
1291 		return;
1292 	}
1293 
1294 	snacpending++;
1295 
1296 	// Don't change name if muted
1297 	if (player_name_changes[consoleplayer] >= MAXNAMECHANGES)
1298 	{
1299 		CV_StealthSet(&cv_playername, player_names[consoleplayer]);
1300 		HU_AddChatText("\x85*You must wait to change your name again", false);
1301 	}
1302 	else if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
1303 		CV_StealthSet(&cv_playername, player_names[consoleplayer]);
1304 	else // Cleanup name if changing it
1305 		CleanupPlayerName(consoleplayer, cv_playername.zstring);
1306 
1307 	// Don't change skin if the server doesn't want you to.
1308 	if (!CanChangeSkin(consoleplayer))
1309 		CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
1310 
1311 	// check if player has the skin loaded (cv_skin may have
1312 	// the name of a skin that was available in the previous game)
1313 	cv_skin.value = R_SkinAvailable(cv_skin.string);
1314 	if ((cv_skin.value < 0) || !R_SkinUsable(consoleplayer, cv_skin.value))
1315 	{
1316 		CV_StealthSet(&cv_skin, DEFAULTSKIN);
1317 		cv_skin.value = 0;
1318 	}
1319 
1320 	// Finally write out the complete packet and send it off.
1321 	WRITESTRINGN(p, cv_playername.zstring, MAXPLAYERNAME);
1322 	WRITEUINT32(p, (UINT32)players[consoleplayer].availabilities);
1323 	WRITEUINT16(p, (UINT16)cv_playercolor.value);
1324 	WRITEUINT8(p, (UINT8)cv_skin.value);
1325 	SendNetXCmd(XD_NAMEANDCOLOR, buf, p - buf);
1326 }
1327 
1328 // splitscreen
SendNameAndColor2(void)1329 static void SendNameAndColor2(void)
1330 {
1331 	INT32 secondplaya;
1332 
1333 	if (!splitscreen && !botingame)
1334 		return; // can happen if skin2/color2/name2 changed
1335 
1336 	if (secondarydisplayplayer != consoleplayer)
1337 		secondplaya = secondarydisplayplayer;
1338 	else // HACK
1339 		secondplaya = 1;
1340 
1341 	// normal player colors
1342 	if (G_GametypeHasTeams())
1343 	{
1344 		if (players[secondplaya].ctfteam == 1 && cv_playercolor2.value != skincolor_redteam)
1345 			CV_StealthSetValue(&cv_playercolor2, skincolor_redteam);
1346 		else if (players[secondplaya].ctfteam == 2 && cv_playercolor2.value != skincolor_blueteam)
1347 			CV_StealthSetValue(&cv_playercolor2, skincolor_blueteam);
1348 	}
1349 
1350 	// don't allow inaccessible colors
1351 	if (!skincolors[cv_playercolor2.value].accessible)
1352 	{
1353 		if (players[secondplaya].skincolor && skincolors[players[secondplaya].skincolor].accessible)
1354 			CV_StealthSetValue(&cv_playercolor2, players[secondplaya].skincolor);
1355 		else if (skincolors[atoi(cv_playercolor2.defaultvalue)].accessible)
1356 			CV_StealthSet(&cv_playercolor, cv_playercolor2.defaultvalue);
1357 		else if (skins[players[secondplaya].skin].prefcolor && skincolors[skins[players[secondplaya].skin].prefcolor].accessible)
1358 			CV_StealthSetValue(&cv_playercolor2, skins[players[secondplaya].skin].prefcolor);
1359 		else {
1360 			UINT16 i = 0;
1361 			while (i<numskincolors && !skincolors[i].accessible) i++;
1362 			CV_StealthSetValue(&cv_playercolor2, (i != numskincolors) ? i : SKINCOLOR_BLUE);
1363 		}
1364 	}
1365 
1366 	players[secondplaya].availabilities = R_GetSkinAvailabilities();
1367 
1368 	// We'll handle it later if we're not playing.
1369 	if (!Playing())
1370 		return;
1371 
1372 	// If you're not in a netgame, merely update the skin, color, and name.
1373 	if (botingame)
1374 	{
1375 		players[secondplaya].skincolor = botcolor;
1376 		if (players[secondplaya].mo && !players[secondplaya].powers[pw_dye])
1377 			players[secondplaya].mo->color = players[secondplaya].skincolor;
1378 
1379 		SetPlayerSkinByNum(secondplaya, botskin-1);
1380 		return;
1381 	}
1382 	else if (!netgame)
1383 	{
1384 		INT32 foundskin;
1385 
1386 		CleanupPlayerName(secondplaya, cv_playername2.zstring);
1387 		strcpy(player_names[secondplaya], cv_playername2.zstring);
1388 
1389 		// don't use secondarydisplayplayer: the second player must be 1
1390 		players[secondplaya].skincolor = cv_playercolor2.value;
1391 		if (players[secondplaya].mo && !players[secondplaya].powers[pw_dye])
1392 			players[secondplaya].mo->color = players[secondplaya].skincolor;
1393 
1394 		if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // Server wants everyone to use the same player
1395 		{
1396 			const INT32 forcedskin = cv_forceskin.value;
1397 
1398 			SetPlayerSkinByNum(secondplaya, forcedskin);
1399 			CV_StealthSet(&cv_skin2, skins[forcedskin].name);
1400 		}
1401 		else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUsable(secondplaya, foundskin))
1402 		{
1403 			//boolean notsame;
1404 
1405 			cv_skin2.value = foundskin;
1406 
1407 			//notsame = (cv_skin2.value != players[secondplaya].skin);
1408 
1409 			SetPlayerSkin(secondplaya, cv_skin2.string);
1410 			CV_StealthSet(&cv_skin2, skins[cv_skin2.value].name);
1411 
1412 			/*if (notsame)
1413 			{
1414 				CV_StealthSetValue(&cv_playercolor2, skins[players[secondplaya].skin].prefcolor);
1415 
1416 				players[secondplaya].skincolor = cv_playercolor2.value % numskincolors;
1417 
1418 				if (players[secondplaya].mo)
1419 					players[secondplaya].mo->color = players[secondplaya].skincolor;
1420 			}*/
1421 		}
1422 		else
1423 		{
1424 			cv_skin2.value = players[secondplaya].skin;
1425 			CV_StealthSet(&cv_skin2, skins[players[secondplaya].skin].name);
1426 			// will always be same as current
1427 			SetPlayerSkin(secondplaya, cv_skin2.string);
1428 		}
1429 		return;
1430 	}
1431 
1432 	// Don't actually send anything because splitscreen isn't actually allowed in netgames anyway!
1433 }
1434 
Got_NameAndColor(UINT8 ** cp,INT32 playernum)1435 static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
1436 {
1437 	player_t *p = &players[playernum];
1438 	char name[MAXPLAYERNAME+1];
1439 	UINT16 color;
1440 	UINT8 skin;
1441 
1442 #ifdef PARANOIA
1443 	if (playernum < 0 || playernum > MAXPLAYERS)
1444 		I_Error("There is no player %d!", playernum);
1445 #endif
1446 
1447 	if (playernum == consoleplayer)
1448 		snacpending--;
1449 	else if (playernum == secondarydisplayplayer)
1450 		snac2pending--;
1451 
1452 #ifdef PARANOIA
1453 	if (snacpending < 0 || snac2pending < 0)
1454 		I_Error("snacpending negative!");
1455 #endif
1456 
1457 	READSTRINGN(*cp, name, MAXPLAYERNAME);
1458 	p->availabilities = READUINT32(*cp);
1459 	color = READUINT16(*cp);
1460 	skin = READUINT8(*cp);
1461 
1462 	// set name
1463 	if (player_name_changes[playernum] < MAXNAMECHANGES)
1464 	{
1465 		if (strcasecmp(player_names[playernum], name) != 0)
1466 			SetPlayerName(playernum, name);
1467 	}
1468 
1469 	// set color
1470 	p->skincolor = color % numskincolors;
1471 	if (p->mo)
1472 		p->mo->color = (UINT16)p->skincolor;
1473 
1474 	// normal player colors
1475 	if (server && (p != &players[consoleplayer] && p != &players[secondarydisplayplayer]))
1476 	{
1477 		boolean kick = false;
1478 		INT32 s;
1479 
1480 		// team colors
1481 		if (G_GametypeHasTeams())
1482 		{
1483 			if (p->ctfteam == 1 && p->skincolor != skincolor_redteam)
1484 				kick = true;
1485 			else if (p->ctfteam == 2 && p->skincolor != skincolor_blueteam)
1486 				kick = true;
1487 		}
1488 
1489 		// don't allow inaccessible colors
1490 		if (skincolors[p->skincolor].accessible == false)
1491 			kick = true;
1492 
1493 		// availabilities
1494 		for (s = 0; s < MAXSKINS; s++)
1495 		{
1496 			if (!skins[s].availability && (p->availabilities & (1 << s)))
1497 			{
1498 				kick = true;
1499 				break;
1500 			}
1501 		}
1502 
1503 		if (kick)
1504 		{
1505 			CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s (team: %d), color: %d)\n"), player_names[playernum], p->ctfteam, p->skincolor);
1506 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
1507 			return;
1508 		}
1509 	}
1510 
1511 	// set skin
1512 	if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // Server wants everyone to use the same player
1513 	{
1514 		const INT32 forcedskin = cv_forceskin.value;
1515 		SetPlayerSkinByNum(playernum, forcedskin);
1516 
1517 		if (playernum == consoleplayer)
1518 			CV_StealthSet(&cv_skin, skins[forcedskin].name);
1519 		else if (playernum == secondarydisplayplayer)
1520 			CV_StealthSet(&cv_skin2, skins[forcedskin].name);
1521 	}
1522 	else
1523 		SetPlayerSkinByNum(playernum, skin);
1524 }
1525 
SendWeaponPref(void)1526 void SendWeaponPref(void)
1527 {
1528 	UINT8 buf[1];
1529 
1530 	buf[0] = 0;
1531 	if (cv_flipcam.value)
1532 		buf[0] |= 1;
1533 	if (cv_analog[0].value && cv_directionchar[0].value != 2)
1534 		buf[0] |= 2;
1535 	if (cv_directionchar[0].value == 1)
1536 		buf[0] |= 4;
1537 	if (cv_autobrake.value)
1538 		buf[0] |= 8;
1539 	SendNetXCmd(XD_WEAPONPREF, buf, 1);
1540 }
1541 
SendWeaponPref2(void)1542 void SendWeaponPref2(void)
1543 {
1544 	UINT8 buf[1];
1545 
1546 	buf[0] = 0;
1547 	if (cv_flipcam2.value)
1548 		buf[0] |= 1;
1549 	if (cv_analog[1].value && cv_directionchar[1].value != 2)
1550 		buf[0] |= 2;
1551 	if (cv_directionchar[1].value == 1)
1552 		buf[0] |= 4;
1553 	if (cv_autobrake2.value)
1554 		buf[0] |= 8;
1555 	SendNetXCmd2(XD_WEAPONPREF, buf, 1);
1556 }
1557 
Got_WeaponPref(UINT8 ** cp,INT32 playernum)1558 static void Got_WeaponPref(UINT8 **cp,INT32 playernum)
1559 {
1560 	UINT8 prefs = READUINT8(*cp);
1561 
1562 	players[playernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE);
1563 	if (prefs & 1)
1564 		players[playernum].pflags |= PF_FLIPCAM;
1565 	if (prefs & 2)
1566 		players[playernum].pflags |= PF_ANALOGMODE;
1567 	if (prefs & 4)
1568 		players[playernum].pflags |= PF_DIRECTIONCHAR;
1569 	if (prefs & 8)
1570 		players[playernum].pflags |= PF_AUTOBRAKE;
1571 }
1572 
D_SendPlayerConfig(void)1573 void D_SendPlayerConfig(void)
1574 {
1575 	SendNameAndColor();
1576 	if (splitscreen || botingame)
1577 		SendNameAndColor2();
1578 	SendWeaponPref();
1579 	if (splitscreen)
1580 		SendWeaponPref2();
1581 }
1582 
1583 // Only works for displayplayer, sorry!
Command_ResetCamera_f(void)1584 static void Command_ResetCamera_f(void)
1585 {
1586 	P_ResetCamera(&players[displayplayer], &camera);
1587 }
1588 
1589 // ========================================================================
1590 
1591 // play a demo, add .lmp for external demos
1592 // eg: playdemo demo1 plays the internal game demo
1593 //
1594 // UINT8 *demofile; // demo file buffer
Command_Playdemo_f(void)1595 static void Command_Playdemo_f(void)
1596 {
1597 	char name[256];
1598 
1599 	if (COM_Argc() != 2)
1600 	{
1601 		CONS_Printf(M_GetText("playdemo <demoname>: playback a demo\n"));
1602 		return;
1603 	}
1604 
1605 	if (netgame)
1606 	{
1607 		CONS_Printf(M_GetText("You can't play a demo while in a netgame.\n"));
1608 		return;
1609 	}
1610 
1611 	// disconnect from server here?
1612 	if (demoplayback)
1613 		G_StopDemo();
1614 	if (metalplayback)
1615 		G_StopMetalDemo();
1616 
1617 	// open the demo file
1618 	strcpy(name, COM_Argv(1));
1619 	// dont add .lmp so internal game demos can be played
1620 
1621 	CONS_Printf(M_GetText("Playing back demo '%s'.\n"), name);
1622 
1623 	// Internal if no extension, external if one exists
1624 	// If external, convert the file name to a path in SRB2's home directory
1625 	if (FIL_CheckExtension(name))
1626 		G_DoPlayDemo(va("%s"PATHSEP"%s", srb2home, name));
1627 	else
1628 		G_DoPlayDemo(name);
1629 }
1630 
Command_Timedemo_f(void)1631 static void Command_Timedemo_f(void)
1632 {
1633 	size_t i = 0;
1634 
1635 	if (COM_Argc() < 2)
1636 	{
1637 		CONS_Printf(M_GetText("timedemo <demoname> [-csv [<trialid>]] [-quit]: time a demo\n"));
1638 		return;
1639 	}
1640 
1641 	if (netgame)
1642 	{
1643 		CONS_Printf(M_GetText("You can't play a demo while in a netgame.\n"));
1644 		return;
1645 	}
1646 
1647 	// disconnect from server here?
1648 	if (demoplayback)
1649 		G_StopDemo();
1650 	if (metalplayback)
1651 		G_StopMetalDemo();
1652 
1653 	// open the demo file
1654 	strcpy (timedemo_name, COM_Argv(1));
1655 	// dont add .lmp so internal game demos can be played
1656 
1657 	// print timedemo results as CSV?
1658 	i = COM_CheckParm("-csv");
1659 	timedemo_csv = (i > 0);
1660 	if (COM_CheckParm("-quit") != i + 1)
1661 		strcpy(timedemo_csv_id, COM_Argv(i + 1)); // user-defined string to identify row
1662 	else
1663 		timedemo_csv_id[0] = 0;
1664 
1665 	// exit after the timedemo?
1666 	timedemo_quit = (COM_CheckParm("-quit") > 0);
1667 
1668 	CONS_Printf(M_GetText("Timing demo '%s'.\n"), timedemo_name);
1669 
1670 	G_TimeDemo(timedemo_name);
1671 }
1672 
1673 // stop current demo
Command_Stopdemo_f(void)1674 static void Command_Stopdemo_f(void)
1675 {
1676 	G_CheckDemoStatus();
1677 	CONS_Printf(M_GetText("Stopped demo.\n"));
1678 }
1679 
Command_StartMovie_f(void)1680 static void Command_StartMovie_f(void)
1681 {
1682 	M_StartMovie();
1683 }
1684 
Command_StopMovie_f(void)1685 static void Command_StopMovie_f(void)
1686 {
1687 	M_StopMovie();
1688 }
1689 
1690 INT32 mapchangepending = 0;
1691 
1692 /** Runs a map change.
1693   * The supplied data are assumed to be good. If provided by a user, they will
1694   * have already been checked in Command_Map_f().
1695   *
1696   * Do \b NOT call this function directly from a menu! M_Responder() is called
1697   * from within the event processing loop, and this function calls
1698   * SV_SpawnServer(), which calls CL_ConnectToServer(), which gives you "Press
1699   * ESC to abort", which calls I_GetKey(), which adds an event. In other words,
1700   * 63 old events will get reexecuted, with ridiculous results. Just don't do
1701   * it (without setting delay to 1, which is the current solution).
1702   *
1703   * \param mapnum          Map number to change to.
1704   * \param gametype        Gametype to switch to.
1705   * \param pultmode        Is this 'Ultimate Mode'?
1706   * \param resetplayers    1 to reset player scores and lives and such, 0 not to.
1707   * \param delay           Determines how the function will be executed: 0 to do
1708   *                        it all right now (must not be done from a menu), 1 to
1709   *                        do step one and prepare step two, 2 to do step two.
1710   * \param skipprecutscene To skip the precutscence or not?
1711   * \sa D_GameTypeChanged, Command_Map_f
1712   * \author Graue <graue@oceanbase.org>
1713   */
D_MapChange(INT32 mapnum,INT32 newgametype,boolean pultmode,boolean resetplayers,INT32 delay,boolean skipprecutscene,boolean FLS)1714 void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean resetplayers, INT32 delay, boolean skipprecutscene, boolean FLS)
1715 {
1716 	static char buf[2+MAX_WADPATH+1+4];
1717 	static char *buf_p = buf;
1718 	// The supplied data are assumed to be good.
1719 	I_Assert(delay >= 0 && delay <= 2);
1720 	if (mapnum != -1)
1721 	{
1722 		CV_SetValue(&cv_nextmap, mapnum);
1723 		// Kick bot from special stages
1724 		if (botskin)
1725 		{
1726 			if (G_IsSpecialStage(mapnum) || (mapheaderinfo[mapnum-1] && (mapheaderinfo[mapnum-1]->typeoflevel & TOL_NIGHTS)))
1727 			{
1728 				if (botingame)
1729 				{
1730 					//CL_RemoveSplitscreenPlayer();
1731 					botingame = false;
1732 					playeringame[1] = false;
1733 				}
1734 			}
1735 			else if (!botingame)
1736 			{
1737 				//CL_AddSplitscreenPlayer();
1738 				botingame = true;
1739 				secondarydisplayplayer = 1;
1740 				playeringame[1] = true;
1741 				players[1].bot = 1;
1742 				SendNameAndColor2();
1743 			}
1744 		}
1745 	}
1746 	CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d ultmode=%d resetplayers=%d delay=%d skipprecutscene=%d\n",
1747 	           mapnum, newgametype, pultmode, resetplayers, delay, skipprecutscene);
1748 	if ((netgame || multiplayer) && !((gametype == newgametype) && (gametypedefaultrules[newgametype] & GTR_CAMPAIGN)))
1749 		FLS = false;
1750 
1751 	if (delay != 2)
1752 	{
1753 		UINT8 flags = 0;
1754 		const char *mapname = G_BuildMapName(mapnum);
1755 		I_Assert(W_CheckNumForName(mapname) != LUMPERROR);
1756 		buf_p = buf;
1757 		if (pultmode)
1758 			flags |= 1;
1759 		if (!resetplayers)
1760 			flags |= 1<<1;
1761 		if (skipprecutscene)
1762 			flags |= 1<<2;
1763 		if (FLS)
1764 			flags |= 1<<3;
1765 		WRITEUINT8(buf_p, flags);
1766 
1767 		// new gametype value
1768 		WRITEUINT8(buf_p, newgametype);
1769 
1770 		WRITESTRINGN(buf_p, mapname, MAX_WADPATH);
1771 	}
1772 
1773 	if (delay == 1)
1774 		mapchangepending = 1;
1775 	else
1776 	{
1777 		mapchangepending = 0;
1778 		// spawn the server if needed
1779 		// reset players if there is a new one
1780 		if (!IsPlayerAdmin(consoleplayer))
1781 		{
1782 			if (SV_SpawnServer())
1783 				buf[0] &= ~(1<<1);
1784 			if (!Playing()) // you failed to start a server somehow, so cancel the map change
1785 				return;
1786 		}
1787 
1788 		chmappending++;
1789 		if (netgame)
1790 			WRITEUINT32(buf_p, M_RandomizedSeed()); // random seed
1791 		SendNetXCmd(XD_MAP, buf, buf_p - buf);
1792 	}
1793 }
1794 
1795 static char *
ConcatCommandArgv(int start,int end)1796 ConcatCommandArgv (int start, int end)
1797 {
1798 	char *final;
1799 
1800 	size_t size;
1801 
1802 	int i;
1803 	char *p;
1804 
1805 	size = 0;
1806 
1807 	for (i = start; i < end; ++i)
1808 	{
1809 		/*
1810 		one space after each argument, but terminating
1811 		character on final argument
1812 		*/
1813 		size += strlen(COM_Argv(i)) + 1;
1814 	}
1815 
1816 	final = ZZ_Alloc(size);
1817 	p = final;
1818 
1819 	--end;/* handle the final argument separately */
1820 	for (i = start; i < end; ++i)
1821 	{
1822 		p += sprintf(p, "%s ", COM_Argv(i));
1823 	}
1824 	/* at this point "end" is actually the last argument's position */
1825 	strcpy(p, COM_Argv(end));
1826 
1827 	return final;
1828 }
1829 
1830 //
1831 // Warp to map code.
1832 // Called either from map <mapname> console command, or idclev cheat.
1833 //
1834 // Largely rewritten by James.
1835 //
Command_Map_f(void)1836 static void Command_Map_f(void)
1837 {
1838 	size_t first_option;
1839 	size_t option_force;
1840 	size_t option_gametype;
1841 	const char *gametypename;
1842 	boolean newresetplayers;
1843 
1844 	boolean mustmodifygame;
1845 
1846 	INT32 newmapnum;
1847 
1848 	char   *    mapname;
1849 	char   *realmapname = NULL;
1850 
1851 	INT32 newgametype = gametype;
1852 
1853 	INT32 d;
1854 
1855 	if (client && !IsPlayerAdmin(consoleplayer))
1856 	{
1857 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
1858 		return;
1859 	}
1860 
1861 	option_force    =   COM_CheckPartialParm("-f");
1862 	option_gametype =   COM_CheckPartialParm("-g");
1863 	newresetplayers = ! COM_CheckParm("-noresetplayers");
1864 
1865 	mustmodifygame =
1866 		!( netgame     || multiplayer ) &&
1867 		(!modifiedgame || savemoddata );
1868 
1869 	if (mustmodifygame && !option_force)
1870 	{
1871 		/* May want to be more descriptive? */
1872 		CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
1873 		return;
1874 	}
1875 
1876 	if (!newresetplayers && !cv_debug)
1877 	{
1878 		CONS_Printf(M_GetText("DEVMODE must be enabled.\n"));
1879 		return;
1880 	}
1881 
1882 	if (option_gametype)
1883 	{
1884 		if (!multiplayer)
1885 		{
1886 			CONS_Printf(M_GetText(
1887 						"You can't switch gametypes in single player!\n"));
1888 			return;
1889 		}
1890 		else if (COM_Argc() < option_gametype + 2)/* no argument after? */
1891 		{
1892 			CONS_Alert(CONS_ERROR,
1893 					"No gametype name follows parameter '%s'.\n",
1894 					COM_Argv(option_gametype));
1895 			return;
1896 		}
1897 	}
1898 
1899 	if (!( first_option = COM_FirstOption() ))
1900 		first_option = COM_Argc();
1901 
1902 	if (first_option < 2)
1903 	{
1904 		/* I'm going over the fucking lines and I DON'T CAREEEEE */
1905 		CONS_Printf("map <name / [MAP]code / number> [-gametype <type>] [-force]:\n");
1906 		CONS_Printf(M_GetText(
1907 					"Warp to a map, by its name, two character code, with optional \"MAP\" prefix, or by its number (though why would you).\n"
1908 					"All parameters are case-insensitive and may be abbreviated.\n"));
1909 		return;
1910 	}
1911 
1912 	mapname = ConcatCommandArgv(1, first_option);
1913 
1914 	newmapnum = G_FindMapByNameOrCode(mapname, &realmapname);
1915 
1916 	if (newmapnum == 0)
1917 	{
1918 		CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname);
1919 		Z_Free(mapname);
1920 		return;
1921 	}
1922 
1923 	if (mustmodifygame && option_force)
1924 	{
1925 		G_SetGameModified(false);
1926 	}
1927 
1928 	// new gametype value
1929 	// use current one by default
1930 	if (option_gametype)
1931 	{
1932 		gametypename = COM_Argv(option_gametype + 1);
1933 
1934 		newgametype = G_GetGametypeByName(gametypename);
1935 
1936 		if (newgametype == -1) // reached end of the list with no match
1937 		{
1938 			/* Did they give us a gametype number? That's okay too! */
1939 			if (isdigit(gametypename[0]))
1940 			{
1941 				d = atoi(gametypename);
1942 				if (d >= 0 && d < gametypecount)
1943 					newgametype = d;
1944 				else
1945 				{
1946 					CONS_Alert(CONS_ERROR,
1947 							"Gametype number %d is out of range. Use a number between"
1948 							" 0 and %d inclusive. ...Or just use the name. :v\n",
1949 							d,
1950 							gametypecount-1);
1951 					Z_Free(realmapname);
1952 					Z_Free(mapname);
1953 					return;
1954 				}
1955 			}
1956 			else
1957 			{
1958 				CONS_Alert(CONS_ERROR,
1959 						"'%s' is not a gametype.\n",
1960 						gametypename);
1961 				Z_Free(realmapname);
1962 				Z_Free(mapname);
1963 				return;
1964 			}
1965 		}
1966 	}
1967 
1968 	// don't use a gametype the map doesn't support
1969 	if (cv_debug || option_force || cv_skipmapcheck.value)
1970 		fromlevelselect = false; // The player wants us to trek on anyway.  Do so.
1971 	// G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer
1972 	else
1973 	{
1974 		if (!(
1975 					mapheaderinfo[newmapnum-1] &&
1976 					mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype)
1977 		))
1978 		{
1979 			CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum),
1980 				(multiplayer ? gametype_cons_t[newgametype].strvalue : "Single Player"));
1981 			Z_Free(realmapname);
1982 			Z_Free(mapname);
1983 			return;
1984 		}
1985 		else
1986 		{
1987 			fromlevelselect =
1988 				( netgame || multiplayer ) &&
1989 				newgametype == gametype    &&
1990 				gametypedefaultrules[newgametype] & GTR_CAMPAIGN;
1991 		}
1992 	}
1993 
1994 	// Prevent warping to locked levels
1995 	// ... unless you're in a dedicated server.  Yes, technically this means you can view any level by
1996 	// running a dedicated server and joining it yourself, but that's better than making dedicated server's
1997 	// lives hell.
1998 	if (!dedicated && M_MapLocked(newmapnum))
1999 	{
2000 		CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n"));
2001 		Z_Free(realmapname);
2002 		Z_Free(mapname);
2003 		return;
2004 	}
2005 
2006 	// Ultimate Mode only in SP via menu
2007 	if (netgame || multiplayer)
2008 		ultimatemode = false;
2009 
2010 	if (tutorialmode && tutorialgcs)
2011 	{
2012 		G_CopyControls(gamecontrol, gamecontroldefault[gcs_custom], gcl_tutorial_full, num_gcl_tutorial_full); // using gcs_custom as temp storage
2013 		CV_SetValue(&cv_usemouse, tutorialusemouse);
2014 		CV_SetValue(&cv_alwaysfreelook, tutorialfreelook);
2015 		CV_SetValue(&cv_mousemove, tutorialmousemove);
2016 		CV_SetValue(&cv_analog[0], tutorialanalog);
2017 	}
2018 	tutorialmode = false; // warping takes us out of tutorial mode
2019 
2020 	D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, fromlevelselect);
2021 
2022 	Z_Free(realmapname);
2023 }
2024 
2025 /** Receives a map command and changes the map.
2026   *
2027   * \param cp        Data buffer.
2028   * \param playernum Player number responsible for the message. Should be
2029   *                  ::serverplayer or ::adminplayer.
2030   * \sa D_MapChange
2031   */
Got_Mapcmd(UINT8 ** cp,INT32 playernum)2032 static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
2033 {
2034 	char mapname[MAX_WADPATH+1];
2035 	UINT8 flags;
2036 	INT32 resetplayer = 1, lastgametype;
2037 	UINT8 skipprecutscene, FLS;
2038 	INT16 mapnumber;
2039 
2040 	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
2041 	{
2042 		CONS_Alert(CONS_WARNING, M_GetText("Illegal map change received from %s\n"), player_names[playernum]);
2043 		if (server)
2044 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
2045 		return;
2046 	}
2047 
2048 	if (chmappending)
2049 		chmappending--;
2050 
2051 	flags = READUINT8(*cp);
2052 
2053 	ultimatemode = ((flags & 1) != 0);
2054 	if (netgame || multiplayer)
2055 		ultimatemode = false;
2056 
2057 	resetplayer = ((flags & (1<<1)) == 0);
2058 
2059 	lastgametype = gametype;
2060 	gametype = READUINT8(*cp);
2061 	G_SetGametype(gametype); // I fear putting that macro as an argument
2062 
2063 	if (gametype < 0 || gametype >= gametypecount)
2064 		gametype = lastgametype;
2065 	else if (gametype != lastgametype)
2066 		D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype
2067 
2068 	skipprecutscene = ((flags & (1<<2)) != 0);
2069 
2070 	FLS = ((flags & (1<<3)) != 0);
2071 
2072 	READSTRINGN(*cp, mapname, MAX_WADPATH);
2073 
2074 	if (netgame)
2075 		P_SetRandSeed(READUINT32(*cp));
2076 
2077 	if (!skipprecutscene)
2078 	{
2079 		DEBFILE(va("Warping to %s [resetplayer=%d lastgametype=%d gametype=%d cpnd=%d]\n",
2080 			mapname, resetplayer, lastgametype, gametype, chmappending));
2081 		CONS_Printf(M_GetText("Speeding off to level...\n"));
2082 	}
2083 
2084 	if (demoplayback && !timingdemo)
2085 		precache = false;
2086 
2087 	if (resetplayer && !FLS)
2088 	{
2089 		emeralds = 0;
2090 		memset(&luabanks, 0, sizeof(luabanks));
2091 	}
2092 
2093 	if (modeattacking)
2094 	{
2095 		SetPlayerSkinByNum(0, cv_chooseskin.value-1);
2096 		players[0].skincolor = skins[players[0].skin].prefcolor;
2097 		CV_StealthSetValue(&cv_playercolor, players[0].skincolor);
2098 	}
2099 
2100 	mapnumber = M_MapNumber(mapname[3], mapname[4]);
2101 	LUAh_MapChange(mapnumber);
2102 
2103 	G_InitNew(ultimatemode, mapname, resetplayer, skipprecutscene, FLS);
2104 	if (demoplayback && !timingdemo)
2105 		precache = true;
2106 	if (timingdemo)
2107 		G_DoneLevelLoad();
2108 
2109 	if (metalrecording)
2110 		G_BeginMetal();
2111 	if (demorecording) // Okay, level loaded, character spawned and skinned,
2112 		G_BeginRecording(); // I AM NOW READY TO RECORD.
2113 	demo_start = true;
2114 }
2115 
Command_Pause(void)2116 static void Command_Pause(void)
2117 {
2118 	UINT8 buf[2];
2119 	UINT8 *cp = buf;
2120 
2121 	if (COM_Argc() > 1)
2122 		WRITEUINT8(cp, (char)(atoi(COM_Argv(1)) != 0));
2123 	else
2124 		WRITEUINT8(cp, (char)(!paused));
2125 
2126 	if (dedicated)
2127 		WRITEUINT8(cp, 1);
2128 	else
2129 		WRITEUINT8(cp, 0);
2130 
2131 	if (cv_pause.value || server || (IsPlayerAdmin(consoleplayer)))
2132 	{
2133 		if (modeattacking || !(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) || (marathonmode && gamestate == GS_INTERMISSION))
2134 		{
2135 			CONS_Printf(M_GetText("You can't pause here.\n"));
2136 			return;
2137 		}
2138 		SendNetXCmd(XD_PAUSE, &buf, 2);
2139 	}
2140 	else
2141 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
2142 }
2143 
Got_Pause(UINT8 ** cp,INT32 playernum)2144 static void Got_Pause(UINT8 **cp, INT32 playernum)
2145 {
2146 	UINT8 dedicatedpause = false;
2147 	const char *playername;
2148 
2149 	if (netgame && !cv_pause.value && playernum != serverplayer && !IsPlayerAdmin(playernum))
2150 	{
2151 		CONS_Alert(CONS_WARNING, M_GetText("Illegal pause command received from %s\n"), player_names[playernum]);
2152 		if (server)
2153 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
2154 		return;
2155 	}
2156 
2157 	if (modeattacking)
2158 		return;
2159 
2160 	paused = READUINT8(*cp);
2161 	dedicatedpause = READUINT8(*cp);
2162 
2163 	if (!demoplayback)
2164 	{
2165 		if (netgame)
2166 		{
2167 			if (dedicatedpause)
2168 				playername = "SERVER";
2169 			else
2170 				playername = player_names[playernum];
2171 
2172 			if (paused)
2173 				CONS_Printf(M_GetText("Game paused by %s\n"), playername);
2174 			else
2175 				CONS_Printf(M_GetText("Game unpaused by %s\n"), playername);
2176 		}
2177 
2178 		if (paused)
2179 		{
2180 			if (!menuactive || netgame)
2181 				S_PauseAudio();
2182 		}
2183 		else
2184 			S_ResumeAudio();
2185 	}
2186 
2187 	I_UpdateMouseGrab();
2188 }
2189 
2190 // Command for stuck characters in netgames, griefing, etc.
Command_Suicide(void)2191 static void Command_Suicide(void)
2192 {
2193 	UINT8 buf[4];
2194 	UINT8 *cp = buf;
2195 
2196 	if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
2197 	{
2198 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
2199 		return;
2200 	}
2201 
2202 	if (!G_PlatformGametype())
2203 	{
2204 		CONS_Printf(M_GetText("You may only use this in co-op, race, and competition!\n"));
2205 		return;
2206 	}
2207 
2208 	// Retry is quicker.  Probably should force people to use it.
2209 	if (!(netgame || multiplayer))
2210 	{
2211 		CONS_Printf(M_GetText("You can't use this in Single Player! Use \"retry\" instead.\n"));
2212 		return;
2213 	}
2214 
2215 	WRITEINT32(cp, consoleplayer);
2216 	SendNetXCmd(XD_SUICIDE, &buf, 4);
2217 }
2218 
Got_Suicide(UINT8 ** cp,INT32 playernum)2219 static void Got_Suicide(UINT8 **cp, INT32 playernum)
2220 {
2221 	INT32 suicideplayer = READINT32(*cp);
2222 
2223 	// You can't suicide someone else.  Nice try, there.
2224 	if (suicideplayer != playernum || (!G_PlatformGametype()))
2225 	{
2226 		CONS_Alert(CONS_WARNING, M_GetText("Illegal suicide command received from %s\n"), player_names[playernum]);
2227 		if (server)
2228 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
2229 		return;
2230 	}
2231 
2232 	if (players[suicideplayer].mo)
2233 		P_DamageMobj(players[suicideplayer].mo, NULL, NULL, 1, DMG_INSTAKILL);
2234 }
2235 
2236 /** Deals with an ::XD_RANDOMSEED message in a netgame.
2237   * These messages set the position of the random number LUT and are crucial to
2238   * correct synchronization.
2239   *
2240   * Such a message should only ever come from the ::serverplayer. If it comes
2241   * from any other player, it is ignored.
2242   *
2243   * \param cp        Data buffer.
2244   * \param playernum Player responsible for the message. Must be ::serverplayer.
2245   * \author Graue <graue@oceanbase.org>
2246   */
Got_RandomSeed(UINT8 ** cp,INT32 playernum)2247 static void Got_RandomSeed(UINT8 **cp, INT32 playernum)
2248 {
2249 	UINT32 seed;
2250 
2251 	seed = READUINT32(*cp);
2252 
2253 	if (playernum != serverplayer) // it's not from the server, wtf?
2254 		return;
2255 
2256 	P_SetRandSeed(seed);
2257 }
2258 
2259 /** Clears all players' scores in a netgame.
2260   * Only the server or a remote admin can use this command, for obvious reasons.
2261   *
2262   * \sa XD_CLEARSCORES, Got_Clearscores
2263   * \author SSNTails <http://www.ssntails.org>
2264   */
Command_Clearscores_f(void)2265 static void Command_Clearscores_f(void)
2266 {
2267 	if (!(server || (IsPlayerAdmin(consoleplayer))))
2268 		return;
2269 
2270 	SendNetXCmd(XD_CLEARSCORES, NULL, 1);
2271 }
2272 
2273 /** Handles an ::XD_CLEARSCORES message, which resets all players' scores in a
2274   * netgame to zero.
2275   *
2276   * \param cp        Data buffer.
2277   * \param playernum Player responsible for the message. Must be ::serverplayer
2278   *                  or ::adminplayer.
2279   * \sa XD_CLEARSCORES, Command_Clearscores_f
2280   * \author SSNTails <http://www.ssntails.org>
2281   */
Got_Clearscores(UINT8 ** cp,INT32 playernum)2282 static void Got_Clearscores(UINT8 **cp, INT32 playernum)
2283 {
2284 	INT32 i;
2285 
2286 	(void)cp;
2287 	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
2288 	{
2289 		CONS_Alert(CONS_WARNING, M_GetText("Illegal clear scores command received from %s\n"), player_names[playernum]);
2290 		if (server)
2291 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
2292 		return;
2293 	}
2294 
2295 	for (i = 0; i < MAXPLAYERS; i++)
2296 		players[i].score = 0;
2297 
2298 	CONS_Printf(M_GetText("Scores have been reset by the server.\n"));
2299 }
2300 
2301 // Team changing functions
Command_Teamchange_f(void)2302 static void Command_Teamchange_f(void)
2303 {
2304 	changeteam_union NetPacket;
2305 	boolean error = false;
2306 	UINT16 usvalue;
2307 	NetPacket.value.l = NetPacket.value.b = 0;
2308 
2309 	//      0         1
2310 	// changeteam  <color>
2311 
2312 	if (COM_Argc() <= 1)
2313 	{
2314 		if (G_GametypeHasTeams())
2315 			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "red, blue or spectator");
2316 		else if (G_GametypeHasSpectators())
2317 			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "spectator or playing");
2318 		else
2319 			CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
2320 		return;
2321 	}
2322 
2323 	if (G_GametypeHasTeams())
2324 	{
2325 		if (!strcasecmp(COM_Argv(1), "red") || !strcasecmp(COM_Argv(1), "1"))
2326 			NetPacket.packet.newteam = 1;
2327 		else if (!strcasecmp(COM_Argv(1), "blue") || !strcasecmp(COM_Argv(1), "2"))
2328 			NetPacket.packet.newteam = 2;
2329 		else if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0"))
2330 			NetPacket.packet.newteam = 0;
2331 		else
2332 			error = true;
2333 	}
2334 	else if (G_GametypeHasSpectators())
2335 	{
2336 		if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0"))
2337 			NetPacket.packet.newteam = 0;
2338 		else if (!strcasecmp(COM_Argv(1), "playing") || !strcasecmp(COM_Argv(1), "1"))
2339 			NetPacket.packet.newteam = 3;
2340 		else
2341 			error = true;
2342 	}
2343 	else
2344 	{
2345 		CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
2346 		return;
2347 	}
2348 
2349 	if (error)
2350 	{
2351 		if (G_GametypeHasTeams())
2352 			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "red, blue or spectator");
2353 		else if (G_GametypeHasSpectators())
2354 			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "spectator or playing");
2355 		return;
2356 	}
2357 
2358 	if (G_GametypeHasTeams())
2359 	{
2360 		if (NetPacket.packet.newteam == (unsigned)players[consoleplayer].ctfteam ||
2361 			(players[consoleplayer].spectator && !NetPacket.packet.newteam))
2362 			error = true;
2363 	}
2364 	else if (G_GametypeHasSpectators())
2365 	{
2366 		if ((players[consoleplayer].spectator && !NetPacket.packet.newteam) ||
2367 			(!players[consoleplayer].spectator && NetPacket.packet.newteam == 3))
2368 			error = true;
2369 	}
2370 #ifdef PARANOIA
2371 	else
2372 		I_Error("Invalid gametype after initial checks!");
2373 #endif
2374 
2375 	if (error)
2376 	{
2377 		CONS_Alert(CONS_NOTICE, M_GetText("You're already on that team!\n"));
2378 		return;
2379 	}
2380 
2381 	if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
2382 	{
2383 		CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n"));
2384 		return;
2385 	}
2386 
2387 	//additional check for hide and seek. Don't allow change of status after hidetime ends.
2388 	if ((gametyperules & GTR_HIDEFROZEN) && leveltime >= (hidetime * TICRATE))
2389 	{
2390 		CONS_Alert(CONS_NOTICE, M_GetText("Hiding time expired; no Hide and Seek status changes allowed!\n"));
2391 		return;
2392 	}
2393 
2394 	usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
2395 	SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
2396 }
2397 
Command_Teamchange2_f(void)2398 static void Command_Teamchange2_f(void)
2399 {
2400 	changeteam_union NetPacket;
2401 	boolean error = false;
2402 	UINT16 usvalue;
2403 	NetPacket.value.l = NetPacket.value.b = 0;
2404 
2405 	//      0         1
2406 	// changeteam2 <color>
2407 
2408 	if (COM_Argc() <= 1)
2409 	{
2410 		if (G_GametypeHasTeams())
2411 			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "red, blue or spectator");
2412 		else if (G_GametypeHasSpectators())
2413 			CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "spectator or playing");
2414 		else
2415 			CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
2416 		return;
2417 	}
2418 
2419 	if (G_GametypeHasTeams())
2420 	{
2421 		if (!strcasecmp(COM_Argv(1), "red") || !strcasecmp(COM_Argv(1), "1"))
2422 			NetPacket.packet.newteam = 1;
2423 		else if (!strcasecmp(COM_Argv(1), "blue") || !strcasecmp(COM_Argv(1), "2"))
2424 			NetPacket.packet.newteam = 2;
2425 		else if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0"))
2426 			NetPacket.packet.newteam = 0;
2427 		else
2428 			error = true;
2429 	}
2430 	else if (G_GametypeHasSpectators())
2431 	{
2432 		if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0"))
2433 			NetPacket.packet.newteam = 0;
2434 		else if (!strcasecmp(COM_Argv(1), "playing") || !strcasecmp(COM_Argv(1), "1"))
2435 			NetPacket.packet.newteam = 3;
2436 		else
2437 			error = true;
2438 	}
2439 
2440 	else
2441 	{
2442 		CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
2443 		return;
2444 	}
2445 
2446 	if (error)
2447 	{
2448 		if (G_GametypeHasTeams())
2449 			CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "red, blue or spectator");
2450 		else if (G_GametypeHasSpectators())
2451 			CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "spectator or playing");
2452 		return;
2453 	}
2454 
2455 	if (G_GametypeHasTeams())
2456 	{
2457 		if (NetPacket.packet.newteam == (unsigned)players[secondarydisplayplayer].ctfteam ||
2458 			(players[secondarydisplayplayer].spectator && !NetPacket.packet.newteam))
2459 			error = true;
2460 	}
2461 	else if (G_GametypeHasSpectators())
2462 	{
2463 		if ((players[secondarydisplayplayer].spectator && !NetPacket.packet.newteam) ||
2464 			(!players[secondarydisplayplayer].spectator && NetPacket.packet.newteam == 3))
2465 			error = true;
2466 	}
2467 #ifdef PARANOIA
2468 	else
2469 		I_Error("Invalid gametype after initial checks!");
2470 #endif
2471 
2472 	if (error)
2473 	{
2474 		CONS_Alert(CONS_NOTICE, M_GetText("You're already on that team!\n"));
2475 		return;
2476 	}
2477 
2478 	if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
2479 	{
2480 		CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n"));
2481 		return;
2482 	}
2483 
2484 	//additional check for hide and seek. Don't allow change of status after hidetime ends.
2485 	if ((gametyperules & GTR_HIDEFROZEN) && leveltime >= (hidetime * TICRATE))
2486 	{
2487 		CONS_Alert(CONS_NOTICE, M_GetText("Hiding time expired; no Hide and Seek status changes allowed!\n"));
2488 		return;
2489 	}
2490 
2491 	usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
2492 	SendNetXCmd2(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
2493 }
2494 
Command_ServerTeamChange_f(void)2495 static void Command_ServerTeamChange_f(void)
2496 {
2497 	changeteam_union NetPacket;
2498 	boolean error = false;
2499 	UINT16 usvalue;
2500 	NetPacket.value.l = NetPacket.value.b = 0;
2501 
2502 	if (!(server || (IsPlayerAdmin(consoleplayer))))
2503 	{
2504 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
2505 		return;
2506 	}
2507 
2508 	//        0              1         2
2509 	// serverchangeteam <playernum>  <team>
2510 
2511 	if (COM_Argc() < 3)
2512 	{
2513 		if (G_TagGametype())
2514 			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "it, notit, playing, or spectator");
2515 		else if (G_GametypeHasTeams())
2516 			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
2517 		else if (G_GametypeHasSpectators())
2518 			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "spectator or playing");
2519 		else
2520 			CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
2521 		return;
2522 	}
2523 
2524 	if (G_TagGametype())
2525 	{
2526 		if (!strcasecmp(COM_Argv(2), "it") || !strcasecmp(COM_Argv(2), "1"))
2527 			NetPacket.packet.newteam = 1;
2528 		else if (!strcasecmp(COM_Argv(2), "notit") || !strcasecmp(COM_Argv(2), "2"))
2529 			NetPacket.packet.newteam = 2;
2530 		else if (!strcasecmp(COM_Argv(2), "playing") || !strcasecmp(COM_Argv(2), "3"))
2531 			NetPacket.packet.newteam = 3;
2532 		else if (!strcasecmp(COM_Argv(2), "spectator") || !strcasecmp(COM_Argv(2), "0"))
2533 			NetPacket.packet.newteam = 0;
2534 		else
2535 			error = true;
2536 	}
2537 	else if (G_GametypeHasTeams())
2538 	{
2539 		if (!strcasecmp(COM_Argv(2), "red") || !strcasecmp(COM_Argv(2), "1"))
2540 			NetPacket.packet.newteam = 1;
2541 		else if (!strcasecmp(COM_Argv(2), "blue") || !strcasecmp(COM_Argv(2), "2"))
2542 			NetPacket.packet.newteam = 2;
2543 		else if (!strcasecmp(COM_Argv(2), "spectator") || !strcasecmp(COM_Argv(2), "0"))
2544 			NetPacket.packet.newteam = 0;
2545 		else
2546 			error = true;
2547 	}
2548 	else if (G_GametypeHasSpectators())
2549 	{
2550 		if (!strcasecmp(COM_Argv(2), "spectator") || !strcasecmp(COM_Argv(2), "0"))
2551 			NetPacket.packet.newteam = 0;
2552 		else if (!strcasecmp(COM_Argv(2), "playing") || !strcasecmp(COM_Argv(2), "1"))
2553 			NetPacket.packet.newteam = 3;
2554 		else
2555 			error = true;
2556 	}
2557 	else
2558 	{
2559 		CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
2560 		return;
2561 	}
2562 
2563 	if (error)
2564 	{
2565 		if (G_TagGametype())
2566 			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "it, notit, playing, or spectator");
2567 		else if (G_GametypeHasTeams())
2568 			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
2569 		else if (G_GametypeHasSpectators())
2570 			CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "spectator or playing");
2571 		return;
2572 	}
2573 
2574 	NetPacket.packet.playernum = atoi(COM_Argv(1));
2575 
2576 	if (!playeringame[NetPacket.packet.playernum])
2577 	{
2578 		CONS_Alert(CONS_NOTICE, M_GetText("There is no player %d!\n"), NetPacket.packet.playernum);
2579 		return;
2580 	}
2581 
2582 	if (G_TagGametype())
2583 	{
2584 		if (( (players[NetPacket.packet.playernum].pflags & PF_TAGIT) && NetPacket.packet.newteam == 1) ||
2585 			(!(players[NetPacket.packet.playernum].pflags & PF_TAGIT) && NetPacket.packet.newteam == 2) ||
2586 			( players[NetPacket.packet.playernum].spectator && !NetPacket.packet.newteam) ||
2587 			(!players[NetPacket.packet.playernum].spectator && NetPacket.packet.newteam == 3))
2588 			error = true;
2589 	}
2590 	else if (G_GametypeHasTeams())
2591 	{
2592 		if (NetPacket.packet.newteam == (unsigned)players[NetPacket.packet.playernum].ctfteam ||
2593 			(players[NetPacket.packet.playernum].spectator && !NetPacket.packet.newteam))
2594 			error = true;
2595 	}
2596 	else if (G_GametypeHasSpectators())
2597 	{
2598 		if ((players[NetPacket.packet.playernum].spectator && !NetPacket.packet.newteam) ||
2599 			(!players[NetPacket.packet.playernum].spectator && NetPacket.packet.newteam == 3))
2600 			error = true;
2601 	}
2602 #ifdef PARANOIA
2603 	else
2604 		I_Error("Invalid gametype after initial checks!");
2605 #endif
2606 
2607 	if (error)
2608 	{
2609 		CONS_Alert(CONS_NOTICE, M_GetText("That player is already on that team!\n"));
2610 		return;
2611 	}
2612 
2613 	//additional check for hide and seek. Don't allow change of status after hidetime ends.
2614 	if ((gametyperules & GTR_HIDEFROZEN) && leveltime >= (hidetime * TICRATE))
2615 	{
2616 		CONS_Alert(CONS_NOTICE, M_GetText("Hiding time expired; no Hide and Seek status changes allowed!\n"));
2617 		return;
2618 	}
2619 
2620 	NetPacket.packet.verification = true; // This signals that it's a server change
2621 
2622 	usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
2623 	SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
2624 }
2625 
2626 //todo: This and the other teamchange functions are getting too long and messy. Needs cleaning.
Got_Teamchange(UINT8 ** cp,INT32 playernum)2627 static void Got_Teamchange(UINT8 **cp, INT32 playernum)
2628 {
2629 	changeteam_union NetPacket;
2630 	boolean error = false;
2631 	NetPacket.value.l = NetPacket.value.b = READINT16(*cp);
2632 
2633 	if (!G_GametypeHasTeams() && !G_GametypeHasSpectators()) //Make sure you're in the right gametype.
2634 	{
2635 		// this should never happen unless the client is hacked/buggy
2636 		CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
2637 		if (server)
2638 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
2639 	}
2640 
2641 	if (NetPacket.packet.verification) // Special marker that the server sent the request
2642 	{
2643 		if (playernum != serverplayer && (!IsPlayerAdmin(playernum)))
2644 		{
2645 			CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
2646 			if (server)
2647 				SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
2648 			return;
2649 		}
2650 		playernum = NetPacket.packet.playernum;
2651 	}
2652 
2653 	// Prevent multiple changes in one go.
2654 	if (G_TagGametype())
2655 	{
2656 		if (((players[playernum].pflags & PF_TAGIT) && NetPacket.packet.newteam == 1) ||
2657 			(!(players[playernum].pflags & PF_TAGIT) && NetPacket.packet.newteam == 2) ||
2658 			(players[playernum].spectator && NetPacket.packet.newteam == 0) ||
2659 			(!players[playernum].spectator && NetPacket.packet.newteam == 3))
2660 			return;
2661 	}
2662 	else if (G_GametypeHasTeams())
2663 	{
2664 		if ((NetPacket.packet.newteam && (NetPacket.packet.newteam == (unsigned)players[playernum].ctfteam)) ||
2665 			(players[playernum].spectator && !NetPacket.packet.newteam))
2666 			return;
2667 	}
2668 	else if (G_GametypeHasSpectators())
2669 	{
2670 		if ((players[playernum].spectator && !NetPacket.packet.newteam) ||
2671 			(!players[playernum].spectator && NetPacket.packet.newteam == 3))
2672 			return;
2673 	}
2674 	else
2675 	{
2676 		if (playernum != serverplayer && (!IsPlayerAdmin(playernum)))
2677 		{
2678 			CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
2679 			if (server)
2680 				SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
2681 		}
2682 		return;
2683 	}
2684 
2685 	// Don't switch team, just go away, please, go awaayyyy, aaauuauugghhhghgh
2686 	if (!LUAh_TeamSwitch(&players[playernum], NetPacket.packet.newteam, players[playernum].spectator, NetPacket.packet.autobalance, NetPacket.packet.scrambled))
2687 		return;
2688 
2689 	//no status changes after hidetime
2690 	if ((gametyperules & GTR_HIDEFROZEN) && (leveltime >= (hidetime * TICRATE)))
2691 		error = true;
2692 
2693 	//Make sure that the right team number is sent. Keep in mind that normal clients cannot change to certain teams in certain gametypes.
2694 	switch (gametype)
2695 	{
2696 	case GT_HIDEANDSEEK:
2697 		//no status changes after hidetime
2698 		if (leveltime >= (hidetime * TICRATE))
2699 		{
2700 			error = true;
2701 			break;
2702 		}
2703 		/* FALLTHRU */
2704 	case GT_TAG:
2705 		switch (NetPacket.packet.newteam)
2706 		{
2707 		case 0:
2708 			break;
2709 		case 1: case 2:
2710 			if (!NetPacket.packet.verification)
2711 				error = true; //Only admin can change player's IT status' in tag.
2712 			break;
2713 		case 3: //Join game via console.
2714 			if (!NetPacket.packet.verification && !cv_allowteamchange.value)
2715 				error = true;
2716 			break;
2717 		}
2718 
2719 		break;
2720 	default:
2721 #ifdef PARANOIA
2722 		if (!G_GametypeHasTeams() && !G_GametypeHasSpectators())
2723 			I_Error("Invalid gametype after initial checks!");
2724 #endif
2725 
2726 		if (!cv_allowteamchange.value)
2727 		{
2728 			if (!NetPacket.packet.verification && NetPacket.packet.newteam)
2729 				error = true; //Only admin can change status, unless changing to spectator.
2730 		}
2731 		break; //Otherwise, you don't need special permissions.
2732 	}
2733 
2734 	if (server && ((NetPacket.packet.newteam < 0 || NetPacket.packet.newteam > 3) || error))
2735 	{
2736 		CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
2737 		SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
2738 	}
2739 
2740 	//Safety first!
2741 	if (players[playernum].mo)
2742 	{
2743 		if (!players[playernum].spectator)
2744 			P_DamageMobj(players[playernum].mo, NULL, NULL, 1, DMG_INSTAKILL);
2745 		else
2746 		{
2747 			P_RemoveMobj(players[playernum].mo);
2748 			players[playernum].mo = NULL;
2749 			players[playernum].playerstate = PST_REBORN;
2750 		}
2751 	}
2752 	else
2753 		players[playernum].playerstate = PST_REBORN;
2754 
2755 	//Now that we've done our error checking and killed the player
2756 	//if necessary, put the player on the correct team/status.
2757 	if (G_TagGametype())
2758 	{
2759 		if (!NetPacket.packet.newteam)
2760 		{
2761 			players[playernum].spectator = true;
2762 			players[playernum].pflags &= ~PF_TAGIT;
2763 			players[playernum].pflags &= ~PF_GAMETYPEOVER;
2764 		}
2765 		else if (NetPacket.packet.newteam != 3) // .newteam == 1 or 2.
2766 		{
2767 			players[playernum].spectator = false;
2768 			players[playernum].pflags &= ~PF_GAMETYPEOVER; //Just in case.
2769 
2770 			if (NetPacket.packet.newteam == 1) //Make the player IT.
2771 				players[playernum].pflags |= PF_TAGIT;
2772 			else
2773 				players[playernum].pflags &= ~PF_TAGIT;
2774 		}
2775 		else // Just join the game.
2776 		{
2777 			players[playernum].spectator = false;
2778 
2779 			//If joining after hidetime in normal tag, default to being IT.
2780 			if (((gametyperules & (GTR_TAG|GTR_HIDEFROZEN)) == GTR_TAG) && (leveltime > (hidetime * TICRATE)))
2781 			{
2782 				NetPacket.packet.newteam = 1; //minor hack, causes the "is it" message to be printed later.
2783 				players[playernum].pflags |= PF_TAGIT; //make the player IT.
2784 			}
2785 		}
2786 	}
2787 	else if (G_GametypeHasTeams())
2788 	{
2789 		if (!NetPacket.packet.newteam)
2790 		{
2791 			players[playernum].ctfteam = 0;
2792 			players[playernum].spectator = true;
2793 		}
2794 		else
2795 		{
2796 			players[playernum].ctfteam = NetPacket.packet.newteam;
2797 			players[playernum].spectator = false;
2798 		}
2799 	}
2800 	else if (G_GametypeHasSpectators())
2801 	{
2802 		if (!NetPacket.packet.newteam)
2803 			players[playernum].spectator = true;
2804 		else
2805 			players[playernum].spectator = false;
2806 	}
2807 
2808 	if (NetPacket.packet.autobalance)
2809 	{
2810 		if (NetPacket.packet.newteam == 1)
2811 			CONS_Printf(M_GetText("%s was autobalanced to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80');
2812 		else if (NetPacket.packet.newteam == 2)
2813 			CONS_Printf(M_GetText("%s was autobalanced to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80');
2814 	}
2815 	else if (NetPacket.packet.scrambled)
2816 	{
2817 		if (NetPacket.packet.newteam == 1)
2818 			CONS_Printf(M_GetText("%s was scrambled to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80');
2819 		else if (NetPacket.packet.newteam == 2)
2820 			CONS_Printf(M_GetText("%s was scrambled to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80');
2821 	}
2822 	else if (NetPacket.packet.newteam == 1)
2823 	{
2824 		if (G_TagGametype())
2825 			CONS_Printf(M_GetText("%s is now IT!\n"), player_names[playernum]);
2826 		else
2827 			CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80');
2828 	}
2829 	else if (NetPacket.packet.newteam == 2)
2830 	{
2831 		if (G_TagGametype())
2832 			CONS_Printf(M_GetText("%s is no longer IT!\n"), player_names[playernum]);
2833 		else
2834 			CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80');
2835 	}
2836 	else if (NetPacket.packet.newteam == 3)
2837 		CONS_Printf(M_GetText("%s entered the game.\n"), player_names[playernum]);
2838 	else
2839 		CONS_Printf(M_GetText("%s became a spectator.\n"), player_names[playernum]);
2840 
2841 	//reset view if you are changed, or viewing someone who was changed.
2842 	if (playernum == consoleplayer || displayplayer == playernum)
2843 	{
2844 		// Call ViewpointSwitch hooks here.
2845 		// The viewpoint was forcibly changed.
2846 		if (displayplayer != consoleplayer) // You're already viewing yourself. No big deal.
2847 			LUAh_ViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true);
2848 		displayplayer = consoleplayer;
2849 	}
2850 
2851 	if (G_GametypeHasTeams())
2852 	{
2853 		if (NetPacket.packet.newteam)
2854 		{
2855 			if (playernum == consoleplayer) //CTF and Team Match colors.
2856 				CV_SetValue(&cv_playercolor, NetPacket.packet.newteam + 5);
2857 			else if (playernum == secondarydisplayplayer)
2858 				CV_SetValue(&cv_playercolor2, NetPacket.packet.newteam + 5);
2859 		}
2860 	}
2861 
2862 	// In tag, check to see if you still have a game.
2863 	if (G_TagGametype())
2864 		P_CheckSurvivors();
2865 }
2866 
2867 //
2868 // Attempts to make password system a little sane without
2869 // rewriting the entire goddamn XD_file system
2870 //
2871 #define BASESALT "basepasswordstorage"
2872 
D_SetPassword(const char * pw)2873 void D_SetPassword(const char *pw)
2874 {
2875 	D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &adminpassmd5);
2876 	adminpasswordset = true;
2877 }
2878 
2879 // Remote Administration
Command_Changepassword_f(void)2880 static void Command_Changepassword_f(void)
2881 {
2882 #ifdef NOMD5
2883 	// If we have no MD5 support then completely disable XD_LOGIN responses for security.
2884 	CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n");
2885 #else
2886 	if (client) // cannot change remotely
2887 	{
2888 		CONS_Printf(M_GetText("Only the server can use this.\n"));
2889 		return;
2890 	}
2891 
2892 	if (COM_Argc() != 2)
2893 	{
2894 		CONS_Printf(M_GetText("password <password>: change remote admin password\n"));
2895 		return;
2896 	}
2897 
2898 	D_SetPassword(COM_Argv(1));
2899 	CONS_Printf(M_GetText("Password set.\n"));
2900 #endif
2901 }
2902 
Command_Login_f(void)2903 static void Command_Login_f(void)
2904 {
2905 #ifdef NOMD5
2906 	// If we have no MD5 support then completely disable XD_LOGIN responses for security.
2907 	CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n");
2908 #else
2909 	const char *pw;
2910 
2911 	if (!netgame)
2912 	{
2913 		CONS_Printf(M_GetText("This only works in a netgame.\n"));
2914 		return;
2915 	}
2916 
2917 	// If the server uses login, it will effectively just remove admin privileges
2918 	// from whoever has them. This is good.
2919 	if (COM_Argc() != 2)
2920 	{
2921 		CONS_Printf(M_GetText("login <password>: Administrator login\n"));
2922 		return;
2923 	}
2924 
2925 	pw = COM_Argv(1);
2926 
2927 	// Do the base pass to get what the server has (or should?)
2928 	D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &netbuffer->u.md5sum);
2929 
2930 	// Do the final pass to get the comparison the server will come up with
2931 	D_MD5PasswordPass(netbuffer->u.md5sum, 16, va("PNUM%02d", consoleplayer), &netbuffer->u.md5sum);
2932 
2933 	CONS_Printf(M_GetText("Sending login... (Notice only given if password is correct.)\n"));
2934 
2935 	netbuffer->packettype = PT_LOGIN;
2936 	HSendPacket(servernode, true, 0, 16);
2937 #endif
2938 }
2939 
IsPlayerAdmin(INT32 playernum)2940 boolean IsPlayerAdmin(INT32 playernum)
2941 {
2942 	INT32 i;
2943 	for (i = 0; i < MAXPLAYERS; i++)
2944 		if (playernum == adminplayers[i])
2945 			return true;
2946 
2947 	return false;
2948 }
2949 
SetAdminPlayer(INT32 playernum)2950 void SetAdminPlayer(INT32 playernum)
2951 {
2952 	INT32 i;
2953 	for (i = 0; i < MAXPLAYERS; i++)
2954 	{
2955 		if (playernum == adminplayers[i])
2956 			return; // Player is already admin
2957 
2958 		if (adminplayers[i] == -1)
2959 		{
2960 			adminplayers[i] = playernum; // Set the player to a free spot
2961 			break; // End the loop now. If it keeps going, the same player might get assigned to two slots.
2962 		}
2963 
2964 
2965 	}
2966 }
2967 
ClearAdminPlayers(void)2968 void ClearAdminPlayers(void)
2969 {
2970 	INT32 i;
2971 	for (i = 0; i < MAXPLAYERS; i++)
2972 		adminplayers[i] = -1;
2973 }
2974 
RemoveAdminPlayer(INT32 playernum)2975 void RemoveAdminPlayer(INT32 playernum)
2976 {
2977 	INT32 i;
2978 	for (i = 0; i < MAXPLAYERS; i++)
2979 		if (playernum == adminplayers[i])
2980 			adminplayers[i] = -1;
2981 }
2982 
Command_Verify_f(void)2983 static void Command_Verify_f(void)
2984 {
2985 	char buf[8]; // Should be plenty
2986 	char *temp;
2987 	INT32 playernum;
2988 
2989 	if (client)
2990 	{
2991 		CONS_Printf(M_GetText("Only the server can use this.\n"));
2992 		return;
2993 	}
2994 
2995 	if (!netgame)
2996 	{
2997 		CONS_Printf(M_GetText("This only works in a netgame.\n"));
2998 		return;
2999 	}
3000 
3001 	if (COM_Argc() != 2)
3002 	{
3003 		CONS_Printf(M_GetText("promote <playernum>: give admin privileges to a player\n"));
3004 		return;
3005 	}
3006 
3007 	strlcpy(buf, COM_Argv(1), sizeof (buf));
3008 
3009 	playernum = atoi(buf);
3010 
3011 	temp = buf;
3012 
3013 	WRITEUINT8(temp, playernum);
3014 
3015 	if (playeringame[playernum])
3016 		SendNetXCmd(XD_VERIFIED, buf, 1);
3017 }
3018 
Got_Verification(UINT8 ** cp,INT32 playernum)3019 static void Got_Verification(UINT8 **cp, INT32 playernum)
3020 {
3021 	INT16 num = READUINT8(*cp);
3022 
3023 	if (playernum != serverplayer) // it's not from the server (hacker or bug)
3024 	{
3025 		CONS_Alert(CONS_WARNING, M_GetText("Illegal verification received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]);
3026 		if (server)
3027 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
3028 		return;
3029 	}
3030 
3031 	SetAdminPlayer(num);
3032 
3033 	if (num != consoleplayer)
3034 		return;
3035 
3036 	CONS_Printf(M_GetText("You are now a server administrator.\n"));
3037 }
3038 
Command_RemoveAdmin_f(void)3039 static void Command_RemoveAdmin_f(void)
3040 {
3041 	char buf[8]; // Should be plenty
3042 	char *temp;
3043 	INT32 playernum;
3044 
3045 	if (client)
3046 	{
3047 		CONS_Printf(M_GetText("Only the server can use this.\n"));
3048 		return;
3049 	}
3050 
3051 	if (COM_Argc() != 2)
3052 	{
3053 		CONS_Printf(M_GetText("demote <playernum>: remove admin privileges from a player\n"));
3054 		return;
3055 	}
3056 
3057 	strlcpy(buf, COM_Argv(1), sizeof(buf));
3058 
3059 	playernum = atoi(buf);
3060 
3061 	temp = buf;
3062 
3063 	WRITEUINT8(temp, playernum);
3064 
3065 	if (playeringame[playernum])
3066 		SendNetXCmd(XD_DEMOTED, buf, 1);
3067 }
3068 
Got_Removal(UINT8 ** cp,INT32 playernum)3069 static void Got_Removal(UINT8 **cp, INT32 playernum)
3070 {
3071 	INT16 num = READUINT8(*cp);
3072 
3073 	if (playernum != serverplayer) // it's not from the server (hacker or bug)
3074 	{
3075 		CONS_Alert(CONS_WARNING, M_GetText("Illegal demotion received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]);
3076 		if (server)
3077 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
3078 		return;
3079 	}
3080 
3081 	RemoveAdminPlayer(num);
3082 
3083 	if (num != consoleplayer)
3084 		return;
3085 
3086 	CONS_Printf(M_GetText("You are no longer a server administrator.\n"));
3087 }
3088 
Command_MotD_f(void)3089 static void Command_MotD_f(void)
3090 {
3091 	size_t i, j;
3092 	char *mymotd;
3093 
3094 	if ((j = COM_Argc()) < 2)
3095 	{
3096 		CONS_Printf(M_GetText("motd <message>: Set a message that clients see upon join.\n"));
3097 		return;
3098 	}
3099 
3100 	if (!(server || (IsPlayerAdmin(consoleplayer))))
3101 	{
3102 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
3103 		return;
3104 	}
3105 
3106 	mymotd = Z_Malloc(sizeof(motd), PU_STATIC, NULL);
3107 
3108 	strlcpy(mymotd, COM_Argv(1), sizeof motd);
3109 	for (i = 2; i < j; i++)
3110 	{
3111 		strlcat(mymotd, " ", sizeof motd);
3112 		strlcat(mymotd, COM_Argv(i), sizeof motd);
3113 	}
3114 
3115 	// Disallow non-printing characters and semicolons.
3116 	for (i = 0; mymotd[i] != '\0'; i++)
3117 		if (!isprint(mymotd[i]) || mymotd[i] == ';')
3118 		{
3119 			Z_Free(mymotd);
3120 			return;
3121 		}
3122 
3123 	if ((netgame || multiplayer) && client)
3124 		SendNetXCmd(XD_SETMOTD, mymotd, i); // send the actual size of the motd string, not the full buffer's size
3125 	else
3126 	{
3127 		strcpy(motd, mymotd);
3128 		CONS_Printf(M_GetText("Message of the day set.\n"));
3129 	}
3130 
3131 	Z_Free(mymotd);
3132 }
3133 
Got_MotD_f(UINT8 ** cp,INT32 playernum)3134 static void Got_MotD_f(UINT8 **cp, INT32 playernum)
3135 {
3136 	char *mymotd = Z_Malloc(sizeof(motd), PU_STATIC, NULL);
3137 	INT32 i;
3138 	boolean kick = false;
3139 
3140 	READSTRINGN(*cp, mymotd, sizeof(motd));
3141 
3142 	// Disallow non-printing characters and semicolons.
3143 	for (i = 0; mymotd[i] != '\0'; i++)
3144 		if (!isprint(mymotd[i]) || mymotd[i] == ';')
3145 			kick = true;
3146 
3147 	if ((playernum != serverplayer && !IsPlayerAdmin(playernum)) || kick)
3148 	{
3149 		CONS_Alert(CONS_WARNING, M_GetText("Illegal motd change received from %s\n"), player_names[playernum]);
3150 		if (server)
3151 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
3152 		Z_Free(mymotd);
3153 		return;
3154 	}
3155 
3156 	strcpy(motd, mymotd);
3157 
3158 	CONS_Printf(M_GetText("Message of the day set.\n"));
3159 
3160 	Z_Free(mymotd);
3161 }
3162 
Command_RunSOC(void)3163 static void Command_RunSOC(void)
3164 {
3165 	const char *fn;
3166 	char buf[255];
3167 	size_t length = 0;
3168 
3169 	if (COM_Argc() != 2)
3170 	{
3171 		CONS_Printf(M_GetText("runsoc <socfile.soc> or <lumpname>: run a soc\n"));
3172 		return;
3173 	}
3174 	else
3175 		fn = COM_Argv(1);
3176 
3177 	if (netgame && !(server || IsPlayerAdmin(consoleplayer)))
3178 	{
3179 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
3180 		return;
3181 	}
3182 
3183 	if (!(netgame || multiplayer))
3184 	{
3185 		if (!P_RunSOC(fn))
3186 			CONS_Printf(M_GetText("Could not find SOC.\n"));
3187 		else
3188 			G_SetGameModified(multiplayer);
3189 		return;
3190 	}
3191 
3192 	nameonly(strcpy(buf, fn));
3193 	length = strlen(buf)+1;
3194 
3195 	SendNetXCmd(XD_RUNSOC, buf, length);
3196 }
3197 
Got_RunSOCcmd(UINT8 ** cp,INT32 playernum)3198 static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum)
3199 {
3200 	char filename[256];
3201 	filestatus_t ncs = FS_NOTFOUND;
3202 
3203 	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
3204 	{
3205 		CONS_Alert(CONS_WARNING, M_GetText("Illegal runsoc command received from %s\n"), player_names[playernum]);
3206 		if (server)
3207 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
3208 		return;
3209 	}
3210 
3211 	READSTRINGN(*cp, filename, 255);
3212 
3213 	// Maybe add md5 support?
3214 	if (strstr(filename, ".soc") != NULL)
3215 	{
3216 		ncs = findfile(filename,NULL,true);
3217 
3218 		if (ncs != FS_FOUND)
3219 		{
3220 			Command_ExitGame_f();
3221 			if (ncs == FS_NOTFOUND)
3222 			{
3223 				CONS_Printf(M_GetText("The server tried to add %s,\nbut you don't have this file.\nYou need to find it in order\nto play on this server.\n"), filename);
3224 				M_StartMessage(va("The server added a file\n(%s)\nthat you do not have.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
3225 			}
3226 			else
3227 			{
3228 				CONS_Printf(M_GetText("Unknown error finding soc file (%s) the server added.\n"), filename);
3229 				M_StartMessage(va("Unknown error trying to load a file\nthat the server added\n(%s).\n\nPress ESC\n",filename), NULL, MM_NOTHING);
3230 			}
3231 			return;
3232 		}
3233 	}
3234 
3235 	P_RunSOC(filename);
3236 	G_SetGameModified(true);
3237 }
3238 
3239 /** Adds a pwad at runtime.
3240   * Searches for sounds, maps, music, new images.
3241   */
Command_Addfile(void)3242 static void Command_Addfile(void)
3243 {
3244 	size_t argc = COM_Argc(); // amount of arguments total
3245 	size_t curarg; // current argument index
3246 
3247 	const char *addedfiles[argc]; // list of filenames already processed
3248 	size_t numfilesadded = 0; // the amount of filenames processed
3249 
3250 	if (argc < 2)
3251 	{
3252 		CONS_Printf(M_GetText("addfile <filename.pk3/wad/lua/soc> [filename2...] [...]: Load add-ons\n"));
3253 		return;
3254 	}
3255 
3256 	// start at one to skip command name
3257 	for (curarg = 1; curarg < argc; curarg++)
3258 	{
3259 		const char *fn, *p;
3260 		char buf[256];
3261 		char *buf_p = buf;
3262 		INT32 i;
3263 		size_t ii;
3264 		int musiconly; // W_VerifyNMUSlumps isn't boolean
3265 		boolean fileadded = false;
3266 
3267 		fn = COM_Argv(curarg);
3268 
3269 		// For the amount of filenames previously processed...
3270 		for (ii = 0; ii < numfilesadded; ii++)
3271 		{
3272 			// If this is one of them, don't try to add it.
3273 			if (!strcmp(fn, addedfiles[ii]))
3274 			{
3275 				fileadded = true;
3276 				break;
3277 			}
3278 		}
3279 
3280 		// If we've added this one, skip to the next one.
3281 		if (fileadded)
3282 		{
3283 			CONS_Alert(CONS_WARNING, M_GetText("Already processed %s, skipping\n"), fn);
3284 			continue;
3285 		}
3286 
3287 		// Disallow non-printing characters and semicolons.
3288 		for (i = 0; fn[i] != '\0'; i++)
3289 			if (!isprint(fn[i]) || fn[i] == ';')
3290 				return;
3291 
3292 		musiconly = W_VerifyNMUSlumps(fn, false);
3293 
3294 		if (musiconly == -1)
3295 		{
3296 			addedfiles[numfilesadded++] = fn;
3297 			continue;
3298 		}
3299 
3300 		if (!musiconly)
3301 		{
3302 			// ... But only so long as they contain nothing more then music and sprites.
3303 			if (netgame && !(server || IsPlayerAdmin(consoleplayer)))
3304 			{
3305 				CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
3306 				continue;
3307 			}
3308 			G_SetGameModified(multiplayer);
3309 		}
3310 
3311 		// Add file on your client directly if it is trivial, or you aren't in a netgame.
3312 		if (!(netgame || multiplayer) || musiconly)
3313 		{
3314 			P_AddWadFile(fn);
3315 			addedfiles[numfilesadded++] = fn;
3316 			continue;
3317 		}
3318 
3319 		p = fn+strlen(fn);
3320 		while(--p >= fn)
3321 			if (*p == '\\' || *p == '/' || *p == ':')
3322 				break;
3323 		++p;
3324 
3325 		// check total packet size and no of files currently loaded
3326 		// See W_LoadWadFile in w_wad.c
3327 		if ((numwadfiles >= MAX_WADFILES)
3328 		|| ((packetsizetally + nameonlylength(fn) + 22) > MAXFILENEEDED*sizeof(UINT8)))
3329 		{
3330 			CONS_Alert(CONS_ERROR, M_GetText("Too many files loaded to add %s\n"), fn);
3331 			return;
3332 		}
3333 
3334 		WRITESTRINGN(buf_p,p,240);
3335 
3336 		// calculate and check md5
3337 		{
3338 			UINT8 md5sum[16];
3339 #ifdef NOMD5
3340 			memset(md5sum,0,16);
3341 #else
3342 			FILE *fhandle;
3343 
3344 			if ((fhandle = W_OpenWadFile(&fn, true)) != NULL)
3345 			{
3346 				tic_t t = I_GetTime();
3347 				CONS_Debug(DBG_SETUP, "Making MD5 for %s\n",fn);
3348 				md5_stream(fhandle, md5sum);
3349 				CONS_Debug(DBG_SETUP, "MD5 calc for %s took %f second\n", fn, (float)(I_GetTime() - t)/TICRATE);
3350 				fclose(fhandle);
3351 			}
3352 			else // file not found
3353 				continue;
3354 
3355 			for (i = 0; i < numwadfiles; i++)
3356 			{
3357 				if (!memcmp(wadfiles[i]->md5sum, md5sum, 16))
3358 				{
3359 					CONS_Alert(CONS_ERROR, M_GetText("%s is already loaded\n"), fn);
3360 					continue;
3361 				}
3362 			}
3363 #endif
3364 			WRITEMEM(buf_p, md5sum, 16);
3365 		}
3366 
3367 		addedfiles[numfilesadded++] = fn;
3368 
3369 		if (IsPlayerAdmin(consoleplayer) && (!server)) // Request to add file
3370 			SendNetXCmd(XD_REQADDFILE, buf, buf_p - buf);
3371 		else
3372 			SendNetXCmd(XD_ADDFILE, buf, buf_p - buf);
3373 	}
3374 }
3375 
Got_RequestAddfilecmd(UINT8 ** cp,INT32 playernum)3376 static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
3377 {
3378 	char filename[241];
3379 	filestatus_t ncs = FS_NOTFOUND;
3380 	UINT8 md5sum[16];
3381 	boolean kick = false;
3382 	boolean toomany = false;
3383 	INT32 i,j;
3384 
3385 	READSTRINGN(*cp, filename, 240);
3386 	READMEM(*cp, md5sum, 16);
3387 
3388 	// Only the server processes this message.
3389 	if (client)
3390 		return;
3391 
3392 	// Disallow non-printing characters and semicolons.
3393 	for (i = 0; filename[i] != '\0'; i++)
3394 		if (!isprint(filename[i]) || filename[i] == ';')
3395 			kick = true;
3396 
3397 	if ((playernum != serverplayer && !IsPlayerAdmin(playernum)) || kick)
3398 	{
3399 		CONS_Alert(CONS_WARNING, M_GetText("Illegal addfile command received from %s\n"), player_names[playernum]);
3400 		SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
3401 		return;
3402 	}
3403 
3404 	// See W_LoadWadFile in w_wad.c
3405 	if ((numwadfiles >= MAX_WADFILES)
3406 	|| ((packetsizetally + nameonlylength(filename) + 22) > MAXFILENEEDED*sizeof(UINT8)))
3407 		toomany = true;
3408 	else
3409 		ncs = findfile(filename,md5sum,true);
3410 
3411 	if (ncs != FS_FOUND || toomany)
3412 	{
3413 		char message[256];
3414 
3415 		if (toomany)
3416 			sprintf(message, M_GetText("Too many files loaded to add %s\n"), filename);
3417 		else if (ncs == FS_NOTFOUND)
3418 			sprintf(message, M_GetText("The server doesn't have %s\n"), filename);
3419 		else if (ncs == FS_MD5SUMBAD)
3420 			sprintf(message, M_GetText("Checksum mismatch on %s\n"), filename);
3421 		else
3422 			sprintf(message, M_GetText("Unknown error finding wad file (%s)\n"), filename);
3423 
3424 		CONS_Printf("%s",message);
3425 
3426 		for (j = 0; j < MAXPLAYERS; j++)
3427 			if (adminplayers[j])
3428 				COM_BufAddText(va("sayto %d %s", adminplayers[j], message));
3429 
3430 		return;
3431 	}
3432 
3433 	COM_BufAddText(va("addfile %s\n", filename));
3434 }
3435 
Got_Addfilecmd(UINT8 ** cp,INT32 playernum)3436 static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
3437 {
3438 	char filename[241];
3439 	filestatus_t ncs = FS_NOTFOUND;
3440 	UINT8 md5sum[16];
3441 
3442 	READSTRINGN(*cp, filename, 240);
3443 	READMEM(*cp, md5sum, 16);
3444 
3445 	if (playernum != serverplayer)
3446 	{
3447 		CONS_Alert(CONS_WARNING, M_GetText("Illegal addfile command received from %s\n"), player_names[playernum]);
3448 		if (server)
3449 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
3450 		return;
3451 	}
3452 
3453 	ncs = findfile(filename,md5sum,true);
3454 
3455 	if (ncs != FS_FOUND || !P_AddWadFile(filename))
3456 	{
3457 		Command_ExitGame_f();
3458 		if (ncs == FS_FOUND)
3459 		{
3460 			CONS_Printf(M_GetText("The server tried to add %s,\nbut you have too many files added.\nRestart the game to clear loaded files\nand play on this server."), filename);
3461 			M_StartMessage(va("The server added a file \n(%s)\nbut you have too many files added.\nRestart the game to clear loaded files.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
3462 		}
3463 		else if (ncs == FS_NOTFOUND)
3464 		{
3465 			CONS_Printf(M_GetText("The server tried to add %s,\nbut you don't have this file.\nYou need to find it in order\nto play on this server."), filename);
3466 			M_StartMessage(va("The server added a file \n(%s)\nthat you do not have.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
3467 		}
3468 		else if (ncs == FS_MD5SUMBAD)
3469 		{
3470 			CONS_Printf(M_GetText("Checksum mismatch while loading %s.\nMake sure you have the copy of\nthis file that the server has.\n"), filename);
3471 			M_StartMessage(va("Checksum mismatch while loading \n%s.\nThe server seems to have a\ndifferent version of this file.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
3472 		}
3473 		else
3474 		{
3475 			CONS_Printf(M_GetText("Unknown error finding wad file (%s) the server added.\n"), filename);
3476 			M_StartMessage(va("Unknown error trying to load a file\nthat the server added \n(%s).\n\nPress ESC\n",filename), NULL, MM_NOTHING);
3477 		}
3478 		return;
3479 	}
3480 
3481 	G_SetGameModified(true);
3482 }
3483 
Command_ListWADS_f(void)3484 static void Command_ListWADS_f(void)
3485 {
3486 	INT32 i = numwadfiles;
3487 	char *tempname;
3488 	CONS_Printf(M_GetText("There are %d wads loaded:\n"),numwadfiles);
3489 	for (i--; i >= 0; i--)
3490 	{
3491 		nameonly(tempname = va("%s", wadfiles[i]->filename));
3492 		if (!i)
3493 			CONS_Printf("\x82 IWAD\x80: %s\n", tempname);
3494 		else if (i <= mainwads)
3495 			CONS_Printf("\x82 * %.2d\x80: %s\n", i, tempname);
3496 		else if (!wadfiles[i]->important)
3497 			CONS_Printf("\x86   %.2d: %s\n", i, tempname);
3498 		else
3499 			CONS_Printf("   %.2d: %s\n", i, tempname);
3500 	}
3501 }
3502 
3503 // =========================================================================
3504 //                            MISC. COMMANDS
3505 // =========================================================================
3506 
3507 /** Prints program version.
3508   */
Command_Version_f(void)3509 static void Command_Version_f(void)
3510 {
3511 #ifdef DEVELOP
3512 	CONS_Printf("Sonic Robo Blast 2 %s-%s (%s %s) ", compbranch, comprevision, compdate, comptime);
3513 #else
3514 	CONS_Printf("Sonic Robo Blast 2 %s (%s %s %s %s) ", VERSIONSTRING, compdate, comptime, comprevision, compbranch);
3515 #endif
3516 
3517 	// Base library
3518 #if defined( HAVE_SDL)
3519 	CONS_Printf("SDL ");
3520 #elif defined(_WINDOWS)
3521 	CONS_Printf("DD ");
3522 #endif
3523 
3524 	// OS
3525 	// Would be nice to use SDL_GetPlatform for this
3526 #if defined (_WIN32) || defined (_WIN64)
3527 	CONS_Printf("Windows ");
3528 #elif defined(__linux__)
3529 	CONS_Printf("Linux ");
3530 #elif defined(MACOSX)
3531 	CONS_Printf("macOS ");
3532 #elif defined(UNIXCOMMON)
3533 	CONS_Printf("Unix (Common) ");
3534 #else
3535 	CONS_Printf("Other OS ");
3536 #endif
3537 
3538 	// Bitness
3539 	if (sizeof(void*) == 4)
3540 		CONS_Printf("32-bit ");
3541 	else if (sizeof(void*) == 8)
3542 		CONS_Printf("64-bit ");
3543 	else // 16-bit? 128-bit?
3544 		CONS_Printf("Bits Unknown ");
3545 
3546 	// No ASM?
3547 #ifdef NOASM
3548 	CONS_Printf("\x85" "NOASM " "\x80");
3549 #endif
3550 
3551 	// Debug build
3552 #ifdef _DEBUG
3553 	CONS_Printf("\x85" "DEBUG " "\x80");
3554 #endif
3555 
3556 	// DEVELOP build
3557 #ifdef DEVELOP
3558 	CONS_Printf("\x87" "DEVELOP " "\x80");
3559 #endif
3560 
3561 	CONS_Printf("\n");
3562 }
3563 
3564 #ifdef UPDATE_ALERT
Command_ModDetails_f(void)3565 static void Command_ModDetails_f(void)
3566 {
3567 	CONS_Printf(M_GetText("Mod ID: %d\nMod Version: %d\nCode Base:%d\n"), MODID, MODVERSION, CODEBASE);
3568 }
3569 #endif
3570 
3571 // Returns current gametype being used.
3572 //
Command_ShowGametype_f(void)3573 static void Command_ShowGametype_f(void)
3574 {
3575 	const char *gametypestr = NULL;
3576 
3577 	if (!(netgame || multiplayer)) // print "Single player" instead of "Co-op"
3578 	{
3579 		CONS_Printf(M_GetText("Current gametype is %s\n"), M_GetText("Single player"));
3580 		return;
3581 	}
3582 
3583 	// get name string for current gametype
3584 	if (gametype >= 0 && gametype < gametypecount)
3585 		gametypestr = Gametype_Names[gametype];
3586 
3587 	if (gametypestr)
3588 		CONS_Printf(M_GetText("Current gametype is %s\n"), gametypestr);
3589 	else // string for current gametype was not found above (should never happen)
3590 		CONS_Printf(M_GetText("Unknown gametype set (%d)\n"), gametype);
3591 }
3592 
3593 /** Plays the intro.
3594   */
Command_Playintro_f(void)3595 static void Command_Playintro_f(void)
3596 {
3597 	if (netgame)
3598 		return;
3599 
3600 	if (dirmenu)
3601 		closefilemenu(true);
3602 
3603 	F_StartIntro();
3604 }
3605 
3606 /** Quits the game immediately.
3607   */
Command_Quit_f(void)3608 FUNCNORETURN static ATTRNORETURN void Command_Quit_f(void)
3609 {
3610 	LUAh_GameQuit(true);
3611 	I_Quit();
3612 }
3613 
ItemFinder_OnChange(void)3614 void ItemFinder_OnChange(void)
3615 {
3616 	if (!cv_itemfinder.value)
3617 		return; // it's fine.
3618 
3619 	if (!M_SecretUnlocked(SECRET_ITEMFINDER))
3620 	{
3621 		CONS_Printf(M_GetText("You haven't earned this yet.\n"));
3622 		CV_StealthSetValue(&cv_itemfinder, 0);
3623 		return;
3624 	}
3625 	else if (netgame || multiplayer)
3626 	{
3627 		CONS_Printf(M_GetText("This only works in single player.\n"));
3628 		CV_StealthSetValue(&cv_itemfinder, 0);
3629 		return;
3630 	}
3631 }
3632 
3633 /** Deals with a pointlimit change by printing the change to the console.
3634   * If the gametype is single player, cooperative, or race, the pointlimit is
3635   * silently disabled again.
3636   *
3637   * Timelimit and pointlimit can be used at the same time.
3638   *
3639   * We don't check immediately for the pointlimit having been reached,
3640   * because you would get "caught" when turning it up in the menu.
3641   * \sa cv_pointlimit, TimeLimit_OnChange
3642   * \author Graue <graue@oceanbase.org>
3643   */
PointLimit_OnChange(void)3644 static void PointLimit_OnChange(void)
3645 {
3646 	// Don't allow pointlimit in Single Player/Co-Op/Race!
3647 	if (server && Playing() && !(gametyperules & GTR_POINTLIMIT))
3648 	{
3649 		if (cv_pointlimit.value)
3650 			CV_StealthSetValue(&cv_pointlimit, 0);
3651 		return;
3652 	}
3653 
3654 	if (cv_pointlimit.value)
3655 	{
3656 		CONS_Printf(M_GetText("Levels will end after %s scores %d point%s.\n"),
3657 			G_GametypeHasTeams() ? M_GetText("a team") : M_GetText("someone"),
3658 			cv_pointlimit.value,
3659 			cv_pointlimit.value > 1 ? "s" : "");
3660 	}
3661 	else if (netgame || multiplayer)
3662 		CONS_Printf(M_GetText("Point limit disabled\n"));
3663 }
3664 
NumLaps_OnChange(void)3665 static void NumLaps_OnChange(void)
3666 {
3667 	// Just don't be verbose
3668 	if ((gametyperules & (GTR_RACE|GTR_LIVES)) == GTR_RACE)
3669 		CONS_Printf(M_GetText("Number of laps set to %d\n"), cv_numlaps.value);
3670 }
3671 
NetTimeout_OnChange(void)3672 static void NetTimeout_OnChange(void)
3673 {
3674 	connectiontimeout = (tic_t)cv_nettimeout.value;
3675 }
3676 
JoinTimeout_OnChange(void)3677 static void JoinTimeout_OnChange(void)
3678 {
3679 	jointimeout = (tic_t)cv_jointimeout.value;
3680 }
3681 
CoopStarposts_OnChange(void)3682 static void CoopStarposts_OnChange(void)
3683 {
3684 	INT32 i;
3685 
3686 	if (!(netgame || multiplayer) || !G_GametypeUsesCoopStarposts())
3687 		return;
3688 
3689 	switch (cv_coopstarposts.value)
3690 	{
3691 		case 0:
3692 			CONS_Printf(M_GetText("Starposts are now per-player.\n"));
3693 			break;
3694 		case 1:
3695 			CONS_Printf(M_GetText("Starposts are now shared between players.\n"));
3696 			break;
3697 		case 2:
3698 			CONS_Printf(M_GetText("Players now only spawn when starposts are hit.\n"));
3699 			return;
3700 	}
3701 
3702 	if (G_IsSpecialStage(gamemap))
3703 		return;
3704 
3705 	for (i = 0; i < MAXPLAYERS; i++)
3706 	{
3707 		if (!playeringame[i])
3708 			continue;
3709 
3710 		if (!players[i].spectator)
3711 			continue;
3712 
3713 		if (players[i].lives <= 0)
3714 			continue;
3715 
3716 		break;
3717 	}
3718 
3719 	if (i == MAXPLAYERS)
3720 		return;
3721 
3722 	for (i = 0; i < MAXPLAYERS; i++)
3723 	{
3724 		if (!playeringame[i])
3725 			continue;
3726 
3727 		if (!players[i].spectator)
3728 			continue;
3729 
3730 		if (players[i].lives <= 0 && (cv_cooplives.value == 1))
3731 			continue;
3732 
3733 		P_SpectatorJoinGame(&players[i]);
3734 	}
3735 }
3736 
CoopLives_OnChange(void)3737 static void CoopLives_OnChange(void)
3738 {
3739 	INT32 i;
3740 
3741 	if (!(netgame || multiplayer) || !G_GametypeUsesCoopLives())
3742 		return;
3743 
3744 	switch (cv_cooplives.value)
3745 	{
3746 		case 0:
3747 			CONS_Printf(M_GetText("Players can now respawn indefinitely.\n"));
3748 			break;
3749 		case 1:
3750 			CONS_Printf(M_GetText("Lives are now per-player.\n"));
3751 			return;
3752 		case 2:
3753 			CONS_Printf(M_GetText("Players can now steal lives to avoid game over.\n"));
3754 			break;
3755 		case 3:
3756 			CONS_Printf(M_GetText("Lives are now shared between players.\n"));
3757 			break;
3758 	}
3759 
3760 	if (cv_coopstarposts.value == 2)
3761 		return;
3762 
3763 	for (i = 0; i < MAXPLAYERS; i++)
3764 	{
3765 		if (!playeringame[i])
3766 			continue;
3767 
3768 		if (!players[i].spectator)
3769 			continue;
3770 
3771 		if (players[i].lives > 0)
3772 			continue;
3773 
3774 		P_SpectatorJoinGame(&players[i]);
3775 	}
3776 }
3777 
ExitMove_OnChange(void)3778 static void ExitMove_OnChange(void)
3779 {
3780 	UINT8 i;
3781 
3782 	if (!(netgame || multiplayer) || !(gametyperules & GTR_FRIENDLY))
3783 		return;
3784 
3785 	if (cv_exitmove.value)
3786 	{
3787 		for (i = 0; i < MAXPLAYERS; ++i)
3788 			if (playeringame[i] && players[i].mo)
3789 			{
3790 				if (players[i].mo->target && players[i].mo->target->type == MT_SIGN)
3791 					P_SetTarget(&players[i].mo->target, NULL);
3792 
3793 				if (players[i].pflags & PF_FINISHED)
3794 					P_GiveFinishFlags(&players[i]);
3795 			}
3796 
3797 		CONS_Printf(M_GetText("Players can now move after completing the level.\n"));
3798 	}
3799 	else
3800 		CONS_Printf(M_GetText("Players can no longer move after completing the level.\n"));
3801 }
3802 
3803 UINT32 timelimitintics = 0;
3804 
3805 /** Deals with a timelimit change by printing the change to the console.
3806   * If the gametype is single player, cooperative, or race, the timelimit is
3807   * silently disabled again.
3808   *
3809   * Timelimit and pointlimit can be used at the same time.
3810   *
3811   * \sa cv_timelimit, PointLimit_OnChange
3812   */
TimeLimit_OnChange(void)3813 static void TimeLimit_OnChange(void)
3814 {
3815 	// Don't allow timelimit in Single Player/Co-Op/Race!
3816 	if (server && Playing() && cv_timelimit.value != 0 && !(gametyperules & GTR_TIMELIMIT))
3817 	{
3818 		CV_SetValue(&cv_timelimit, 0);
3819 		return;
3820 	}
3821 
3822 	if (cv_timelimit.value != 0)
3823 	{
3824 		CONS_Printf(M_GetText("Levels will end after %d minute%s.\n"),cv_timelimit.value,cv_timelimit.value == 1 ? "" : "s"); // Graue 11-17-2003
3825 		timelimitintics = cv_timelimit.value * 60 * TICRATE;
3826 
3827 		//add hidetime for tag too!
3828 		if (G_TagGametype())
3829 			timelimitintics += hidetime * TICRATE;
3830 
3831 		// Note the deliberate absence of any code preventing
3832 		//   pointlimit and timelimit from being set simultaneously.
3833 		// Some people might like to use them together. It works.
3834 	}
3835 	else if (netgame || multiplayer)
3836 		CONS_Printf(M_GetText("Time limit disabled\n"));
3837 }
3838 
3839 /** Adjusts certain settings to match a changed gametype.
3840   *
3841   * \param lastgametype The gametype we were playing before now.
3842   * \sa D_MapChange
3843   * \author Graue <graue@oceanbase.org>
3844   * \todo Get rid of the hardcoded stuff, ugly stuff, etc.
3845   */
D_GameTypeChanged(INT32 lastgametype)3846 void D_GameTypeChanged(INT32 lastgametype)
3847 {
3848 	if (netgame)
3849 	{
3850 		const char *oldgt = NULL, *newgt = NULL;
3851 
3852 		if (lastgametype >= 0 && lastgametype < gametypecount)
3853 			oldgt = Gametype_Names[lastgametype];
3854 		if (gametype >= 0 && lastgametype < gametypecount)
3855 			newgt = Gametype_Names[gametype];
3856 
3857 		if (oldgt && newgt)
3858 			CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), oldgt, newgt);
3859 	}
3860 	// Only do the following as the server, not as remote admin.
3861 	// There will always be a server, and this only needs to be done once.
3862 	if (server && (multiplayer || netgame))
3863 	{
3864 		if (gametype == GT_COMPETITION)
3865 			CV_SetValue(&cv_itemrespawn, 0);
3866 		else if (!cv_itemrespawn.changed || lastgametype == GT_COMPETITION)
3867 			CV_SetValue(&cv_itemrespawn, 1);
3868 
3869 		switch (gametype)
3870 		{
3871 			case GT_COOP:
3872 				if (!cv_itemrespawntime.changed)
3873 					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
3874 				break;
3875 			case GT_MATCH:
3876 			case GT_TEAMMATCH:
3877 				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
3878 				{
3879 					// default settings for match: timelimit 10 mins, no pointlimit
3880 					CV_SetValue(&cv_pointlimit, 0);
3881 					CV_SetValue(&cv_timelimit, 10);
3882 				}
3883 				if (!cv_itemrespawntime.changed)
3884 					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
3885 				break;
3886 			case GT_TAG:
3887 			case GT_HIDEANDSEEK:
3888 				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
3889 				{
3890 					// default settings for tag: 5 mins, no pointlimit
3891 					// Note that tag mode also uses an alternate timing mechanism in tandem with timelimit.
3892 					CV_SetValue(&cv_timelimit, 5);
3893 					CV_SetValue(&cv_pointlimit, 0);
3894 				}
3895 				if (!cv_itemrespawntime.changed)
3896 					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
3897 				break;
3898 			case GT_CTF:
3899 				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
3900 				{
3901 					// default settings for CTF: no timelimit, pointlimit 5
3902 					CV_SetValue(&cv_timelimit, 0);
3903 					CV_SetValue(&cv_pointlimit, 5);
3904 				}
3905 				if (!cv_itemrespawntime.changed)
3906 					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
3907 				break;
3908 			default:
3909 				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
3910 				{
3911 					CV_SetValue(&cv_timelimit, timelimits[gametype]);
3912 					CV_SetValue(&cv_pointlimit, pointlimits[gametype]);
3913 				}
3914 				if (!cv_itemrespawntime.changed)
3915 					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
3916 				break;
3917 		}
3918 	}
3919 	else if (!multiplayer && !netgame)
3920 	{
3921 		G_SetGametype(GT_COOP);
3922 		// These shouldn't matter anymore
3923 		//CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue);
3924 		//CV_SetValue(&cv_itemrespawn, 0);
3925 	}
3926 
3927 	// reset timelimit and pointlimit in race/coop, prevent stupid cheats
3928 	if (server)
3929 	{
3930 		if (!(gametyperules & GTR_POINTLIMIT))
3931 		{
3932 			if (cv_timelimit.value)
3933 				CV_SetValue(&cv_timelimit, 0);
3934 			if (cv_pointlimit.value)
3935 				CV_SetValue(&cv_pointlimit, 0);
3936 		}
3937 		else if ((cv_pointlimit.changed || cv_timelimit.changed) && cv_pointlimit.value)
3938 		{
3939 			if (lastgametype != GT_CTF && gametype == GT_CTF)
3940 				CV_SetValue(&cv_pointlimit, cv_pointlimit.value / 500);
3941 			else if (lastgametype == GT_CTF && gametype != GT_CTF)
3942 				CV_SetValue(&cv_pointlimit, cv_pointlimit.value * 500);
3943 		}
3944 	}
3945 
3946 	// When swapping to a gametype that supports spectators,
3947 	// make everyone a spectator initially.
3948 	// Averted with GTR_NOSPECTATORSPAWN.
3949 	if (!splitscreen && (G_GametypeHasSpectators()))
3950 	{
3951 		INT32 i;
3952 		for (i = 0; i < MAXPLAYERS; i++)
3953 			if (playeringame[i])
3954 			{
3955 				players[i].ctfteam = 0;
3956 				players[i].spectator = (gametyperules & GTR_NOSPECTATORSPAWN) ? false : true;
3957 			}
3958 	}
3959 
3960 	// don't retain teams in other modes or between changes from ctf to team match.
3961 	// also, stop any and all forms of team scrambling that might otherwise take place.
3962 	if (G_GametypeHasTeams())
3963 	{
3964 		INT32 i;
3965 		for (i = 0; i < MAXPLAYERS; i++)
3966 			if (playeringame[i])
3967 				players[i].ctfteam = 0;
3968 
3969 		if (server || (IsPlayerAdmin(consoleplayer)))
3970 		{
3971 			CV_StealthSetValue(&cv_teamscramble, 0);
3972 			teamscramble = 0;
3973 		}
3974 	}
3975 }
3976 
Ringslinger_OnChange(void)3977 static void Ringslinger_OnChange(void)
3978 {
3979 	if (!M_SecretUnlocked(SECRET_PANDORA) && !netgame && cv_ringslinger.value && !cv_debug)
3980 	{
3981 		CONS_Printf(M_GetText("You haven't earned this yet.\n"));
3982 		CV_StealthSetValue(&cv_ringslinger, 0);
3983 		return;
3984 	}
3985 
3986 	if (cv_ringslinger.value) // Only if it's been turned on
3987 		G_SetGameModified(multiplayer);
3988 }
3989 
Gravity_OnChange(void)3990 static void Gravity_OnChange(void)
3991 {
3992 	if (!M_SecretUnlocked(SECRET_PANDORA) && !netgame && !cv_debug
3993 		&& strcmp(cv_gravity.string, cv_gravity.defaultvalue))
3994 	{
3995 		CONS_Printf(M_GetText("You haven't earned this yet.\n"));
3996 		CV_StealthSet(&cv_gravity, cv_gravity.defaultvalue);
3997 		return;
3998 	}
3999 #ifndef NETGAME_GRAVITY
4000 	if(netgame)
4001 	{
4002 		CV_StealthSet(&cv_gravity, cv_gravity.defaultvalue);
4003 		return;
4004 	}
4005 #endif
4006 
4007 	if (!CV_IsSetToDefault(&cv_gravity))
4008 		G_SetGameModified(multiplayer);
4009 	gravity = cv_gravity.value;
4010 }
4011 
SoundTest_OnChange(void)4012 static void SoundTest_OnChange(void)
4013 {
4014 	INT32 sfxfreeint = (INT32)sfxfree;
4015 	if (cv_soundtest.value < 0)
4016 	{
4017 		CV_SetValue(&cv_soundtest, sfxfreeint-1);
4018 		return;
4019 	}
4020 
4021 	if (cv_soundtest.value >= sfxfreeint)
4022 	{
4023 		CV_SetValue(&cv_soundtest, 0);
4024 		return;
4025 	}
4026 
4027 	S_StopSounds();
4028 	S_StartSound(NULL, cv_soundtest.value);
4029 }
4030 
AutoBalance_OnChange(void)4031 static void AutoBalance_OnChange(void)
4032 {
4033 	autobalance = (INT16)cv_autobalance.value;
4034 }
4035 
TeamScramble_OnChange(void)4036 static void TeamScramble_OnChange(void)
4037 {
4038 	INT16 i = 0, j = 0, playercount = 0;
4039 	boolean repick = true;
4040 	INT32 blue = 0, red = 0;
4041 	INT32 maxcomposition = 0;
4042 	INT16 newteam = 0;
4043 	INT32 retries = 0;
4044 	boolean success = false;
4045 
4046 	// Don't trigger outside level or intermission!
4047 	if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
4048 		return;
4049 
4050 	if (!cv_teamscramble.value)
4051 		teamscramble = 0;
4052 
4053 	if (!G_GametypeHasTeams() && (server || IsPlayerAdmin(consoleplayer)))
4054 	{
4055 		CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
4056 		CV_StealthSetValue(&cv_teamscramble, 0);
4057 		return;
4058 	}
4059 
4060 	// If a team scramble is already in progress, do not allow another one to be started!
4061 	if (teamscramble)
4062 		return;
4063 
4064 retryscramble:
4065 
4066 	// Clear related global variables. These will get used again in p_tick.c/y_inter.c as the teams are scrambled.
4067 	memset(&scrambleplayers, 0, sizeof(scrambleplayers));
4068 	memset(&scrambleteams, 0, sizeof(scrambleplayers));
4069 	scrambletotal = scramblecount = 0;
4070 	blue = red = maxcomposition = newteam = playercount = 0;
4071 	repick = true;
4072 
4073 	// Put each player's node in the array.
4074 	for (i = 0; i < MAXPLAYERS; i++)
4075 	{
4076 		if (playeringame[i] && !players[i].spectator)
4077 		{
4078 			scrambleplayers[playercount] = i;
4079 			playercount++;
4080 		}
4081 	}
4082 
4083 	if (playercount < 2)
4084 	{
4085 		CV_StealthSetValue(&cv_teamscramble, 0);
4086 		return; // Don't scramble one or zero players.
4087 	}
4088 
4089 	// Randomly place players on teams.
4090 	if (cv_teamscramble.value == 1)
4091 	{
4092 		maxcomposition = playercount / 2;
4093 
4094 		// Now randomly assign players to teams.
4095 		// If the teams get out of hand, assign the rest to the other team.
4096 		for (i = 0; i < playercount; i++)
4097 		{
4098 			if (repick)
4099 				newteam = (INT16)((M_RandomByte() % 2) + 1);
4100 
4101 			// One team has the most players they can get, assign the rest to the other team.
4102 			if (red == maxcomposition || blue == maxcomposition)
4103 			{
4104 				if (red == maxcomposition)
4105 					newteam = 2;
4106 				else //if (blue == maxcomposition)
4107 					newteam = 1;
4108 
4109 				repick = false;
4110 			}
4111 
4112 			scrambleteams[i] = newteam;
4113 
4114 			if (newteam == 1)
4115 				red++;
4116 			else
4117 				blue++;
4118 		}
4119 	}
4120 	else if (cv_teamscramble.value == 2) // Same as before, except split teams based on current score.
4121 	{
4122 		// Now, sort the array based on points scored.
4123 		for (i = 1; i < playercount; i++)
4124 		{
4125 			for (j = i; j < playercount; j++)
4126 			{
4127 				INT16 tempplayer = 0;
4128 
4129 				if ((players[scrambleplayers[i-1]].score > players[scrambleplayers[j]].score))
4130 				{
4131 					tempplayer = scrambleplayers[i-1];
4132 					scrambleplayers[i-1] = scrambleplayers[j];
4133 					scrambleplayers[j] = tempplayer;
4134 				}
4135 			}
4136 		}
4137 
4138 		// Now assign players to teams based on score. Scramble in pairs.
4139 		// If there is an odd number, one team will end up with the unlucky slob who has no points. =(
4140 		for (i = 0; i < playercount; i++)
4141 		{
4142 			if (repick)
4143 			{
4144 				newteam = (INT16)((M_RandomByte() % 2) + 1);
4145 				repick = false;
4146 			}
4147 			else if (i != 2) // Mystic's secret sauce - ABBA is better than ABAB, so team B doesn't get worse players all around
4148 			{
4149 				// We will only randomly pick the team for the first guy.
4150 				// Otherwise, just alternate back and forth, distributing players.
4151 				newteam = 3 - newteam;
4152 			}
4153 
4154 			scrambleteams[i] = newteam;
4155 		}
4156 	}
4157 
4158 	// Check to see if our random selection actually
4159 	// changed anybody. If not, we run through and try again.
4160 	for (i = 0; i < playercount; i++)
4161 	{
4162 		if (players[scrambleplayers[i]].ctfteam != scrambleteams[i])
4163 			success = true;
4164 	}
4165 
4166 	if (!success && retries < 5)
4167 	{
4168 		retries++;
4169 		goto retryscramble; //try again
4170 	}
4171 
4172 	// Display a witty message, but only during scrambles specifically triggered by an admin.
4173 	if (cv_teamscramble.value)
4174 	{
4175 		scrambletotal = playercount;
4176 		teamscramble = (INT16)cv_teamscramble.value;
4177 
4178 		if (!(gamestate == GS_INTERMISSION && cv_scrambleonchange.value))
4179 			CONS_Printf(M_GetText("Teams will be scrambled next round.\n"));
4180 	}
4181 }
4182 
Hidetime_OnChange(void)4183 static void Hidetime_OnChange(void)
4184 {
4185 	if (Playing() && G_TagGametype() && leveltime >= (hidetime * TICRATE))
4186 	{
4187 		// Don't allow hidetime changes after it expires.
4188 		CV_StealthSetValue(&cv_hidetime, hidetime);
4189 		return;
4190 	}
4191 	hidetime = cv_hidetime.value;
4192 
4193 	//uh oh, gotta change timelimitintics now too
4194 	if (G_TagGametype())
4195 		timelimitintics = (cv_timelimit.value * 60 * TICRATE) + (hidetime * TICRATE);
4196 }
4197 
Command_Showmap_f(void)4198 static void Command_Showmap_f(void)
4199 {
4200 	if (gamestate == GS_LEVEL)
4201 	{
4202 		if (mapheaderinfo[gamemap-1]->actnum)
4203 			CONS_Printf("%s (%d): %s %d\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->actnum);
4204 		else
4205 			CONS_Printf("%s (%d): %s\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl);
4206 	}
4207 	else
4208 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
4209 }
4210 
Command_Mapmd5_f(void)4211 static void Command_Mapmd5_f(void)
4212 {
4213 	if (gamestate == GS_LEVEL)
4214 	{
4215 		INT32 i;
4216 		char md5tmp[33];
4217 		for (i = 0; i < 16; ++i)
4218 			sprintf(&md5tmp[i*2], "%02x", mapmd5[i]);
4219 		CONS_Printf("%s: %s\n", G_BuildMapName(gamemap), md5tmp);
4220 	}
4221 	else
4222 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
4223 }
4224 
Command_ExitLevel_f(void)4225 static void Command_ExitLevel_f(void)
4226 {
4227 	if (!(netgame || (multiplayer && gametype != GT_COOP)) && !cv_debug)
4228 		CONS_Printf(M_GetText("This only works in a netgame.\n"));
4229 	else if (!(server || (IsPlayerAdmin(consoleplayer))))
4230 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
4231 	else if (( gamestate != GS_LEVEL && gamestate != GS_CREDITS ) || demoplayback)
4232 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
4233 	else
4234 		SendNetXCmd(XD_EXITLEVEL, NULL, 0);
4235 }
4236 
Got_ExitLevelcmd(UINT8 ** cp,INT32 playernum)4237 static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)
4238 {
4239 	(void)cp;
4240 
4241 	// Ignore duplicate XD_EXITLEVEL commands.
4242 	if (gameaction == ga_completed)
4243 		return;
4244 
4245 	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
4246 	{
4247 		CONS_Alert(CONS_WARNING, M_GetText("Illegal exitlevel command received from %s\n"), player_names[playernum]);
4248 		if (server)
4249 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
4250 		return;
4251 	}
4252 
4253 	G_ExitLevel();
4254 }
4255 
4256 /** Prints the number of the displayplayer.
4257   *
4258   * \todo Possibly remove this; it was useful for debugging at one point.
4259   */
Command_Displayplayer_f(void)4260 static void Command_Displayplayer_f(void)
4261 {
4262 	CONS_Printf(M_GetText("Displayplayer is %d\n"), displayplayer);
4263 }
4264 
4265 /** Quits a game and returns to the title screen.
4266   *
4267   */
Command_ExitGame_f(void)4268 void Command_ExitGame_f(void)
4269 {
4270 	INT32 i;
4271 
4272 	LUAh_GameQuit(false);
4273 
4274 	D_QuitNetGame();
4275 	CL_Reset();
4276 	CV_ClearChangedFlags();
4277 
4278 	for (i = 0; i < MAXPLAYERS; i++)
4279 		CL_ClearPlayer(i);
4280 
4281 	players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p
4282 
4283 	splitscreen = false;
4284 	SplitScreen_OnChange();
4285 	botingame = false;
4286 	botskin = 0;
4287 	cv_debug = 0;
4288 	emeralds = 0;
4289 	memset(&luabanks, 0, sizeof(luabanks));
4290 
4291 	if (dirmenu)
4292 		closefilemenu(true);
4293 
4294 	if (!modeattacking)
4295 		D_StartTitle();
4296 }
4297 
Command_Retry_f(void)4298 void Command_Retry_f(void)
4299 {
4300 	if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
4301 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
4302 	else if (netgame || multiplayer)
4303 		CONS_Printf(M_GetText("This only works in single player.\n"));
4304 	else if (!&players[consoleplayer] || players[consoleplayer].lives <= 1)
4305 		CONS_Printf(M_GetText("You can't retry without any lives remaining!\n"));
4306 	else if (G_IsSpecialStage(gamemap))
4307 		CONS_Printf(M_GetText("You can't retry special stages!\n"));
4308 	else
4309 	{
4310 		M_ClearMenus(true);
4311 		G_SetRetryFlag();
4312 	}
4313 }
4314 
4315 #ifdef NETGAME_DEVMODE
4316 // Allow the use of devmode in netgames.
Fishcake_OnChange(void)4317 static void Fishcake_OnChange(void)
4318 {
4319 	cv_debug = cv_fishcake.value;
4320 	// consvar_t's get changed to default when registered
4321 	// so don't make modifiedgame always on!
4322 	if (cv_debug)
4323 	{
4324 		G_SetGameModified(multiplayer);
4325 	}
4326 
4327 	else if (cv_debug != cv_fishcake.value)
4328 		CV_SetValue(&cv_fishcake, cv_debug);
4329 }
4330 #endif
4331 
4332 /** Reports to the console whether or not the game has been modified.
4333   *
4334   * \todo Make it obvious, so a console command won't be necessary.
4335   * \sa modifiedgame
4336   * \author Graue <graue@oceanbase.org>
4337   */
Command_Isgamemodified_f(void)4338 static void Command_Isgamemodified_f(void)
4339 {
4340 	if (savemoddata)
4341 		CONS_Printf(M_GetText("modifiedgame is true, but you can save emblem and time data in this mod.\n"));
4342 	else if (modifiedgame)
4343 		CONS_Printf(M_GetText("modifiedgame is true, extras will not be unlocked\n"));
4344 	else
4345 		CONS_Printf(M_GetText("modifiedgame is false, you can unlock extras\n"));
4346 }
4347 
Command_Cheats_f(void)4348 static void Command_Cheats_f(void)
4349 {
4350 	if (COM_CheckParm("off"))
4351 	{
4352 		if (!(server || (IsPlayerAdmin(consoleplayer))))
4353 			CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
4354 		else
4355 			CV_ResetCheatNetVars();
4356 		return;
4357 	}
4358 
4359 	if (CV_CheatsEnabled())
4360 	{
4361 		CONS_Printf(M_GetText("At least one CHEAT-marked variable has been changed -- Cheats are enabled.\n"));
4362 		if (server || (IsPlayerAdmin(consoleplayer)))
4363 			CONS_Printf(M_GetText("Type CHEATS OFF to reset all cheat variables to default.\n"));
4364 	}
4365 	else
4366 		CONS_Printf(M_GetText("No CHEAT-marked variables are changed -- Cheats are disabled.\n"));
4367 }
4368 
4369 #ifdef _DEBUG
Command_Togglemodified_f(void)4370 static void Command_Togglemodified_f(void)
4371 {
4372 	modifiedgame = !modifiedgame;
4373 }
4374 
4375 extern UINT8 *save_p;
Command_Archivetest_f(void)4376 static void Command_Archivetest_f(void)
4377 {
4378 	UINT8 *buf;
4379 	UINT32 i, wrote;
4380 	thinker_t *th;
4381 	if (gamestate != GS_LEVEL)
4382 	{
4383 		CONS_Printf("This command only works in-game, you dummy.\n");
4384 		return;
4385 	}
4386 
4387 	// assign mobjnum
4388 	i = 1;
4389 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
4390 		if (th->function.acp1 != (actionf_p1)P_RemoveThinkerDelayed)
4391 			((mobj_t *)th)->mobjnum = i++;
4392 
4393 	// allocate buffer
4394 	buf = save_p = ZZ_Alloc(1024);
4395 
4396 	// test archive
4397 	CONS_Printf("LUA_Archive...\n");
4398 	LUA_Archive();
4399 	WRITEUINT8(save_p, 0x7F);
4400 	wrote = (UINT32)(save_p-buf);
4401 
4402 	// clear Lua state, so we can really see what happens!
4403 	CONS_Printf("Clearing state!\n");
4404 	LUA_ClearExtVars();
4405 
4406 	// test unarchive
4407 	save_p = buf;
4408 	CONS_Printf("LUA_UnArchive...\n");
4409 	LUA_UnArchive();
4410 	i = READUINT8(save_p);
4411 	if (i != 0x7F || wrote != (UINT32)(save_p-buf))
4412 		CONS_Printf("Savegame corrupted. (write %u, read %u)\n", wrote, (UINT32)(save_p-buf));
4413 
4414 	// free buffer
4415 	Z_Free(buf);
4416 	CONS_Printf("Done. No crash.\n");
4417 }
4418 #endif
4419 
4420 /** Makes a change to ::cv_forceskin take effect immediately.
4421   *
4422   * \todo Move the enforcement code out of SendNameAndColor() so this hack
4423   *       isn't needed.
4424   * \sa Command_SetForcedSkin_f, cv_forceskin, forcedskin
4425   * \author Graue <graue@oceanbase.org>
4426   */
ForceSkin_OnChange(void)4427 static void ForceSkin_OnChange(void)
4428 {
4429 	if ((server || IsPlayerAdmin(consoleplayer)) && (cv_forceskin.value < -1 || cv_forceskin.value >= numskins))
4430 	{
4431 		if (cv_forceskin.value == -2)
4432 			CV_SetValue(&cv_forceskin, numskins-1);
4433 		else
4434 		{
4435 			// hack because I can't restrict this and still allow added skins to be used with forceskin.
4436 			if (!menuactive)
4437 				CONS_Printf(M_GetText("Valid skin numbers are 0 to %d (-1 disables)\n"), numskins - 1);
4438 			CV_SetValue(&cv_forceskin, -1);
4439 		}
4440 		return;
4441 	}
4442 
4443 	// NOT in SP, silly!
4444 	if (!(netgame || multiplayer))
4445 		return;
4446 
4447 	if (cv_forceskin.value < 0)
4448 		CONS_Printf("The server has lifted the forced skin restrictions.\n");
4449 	else
4450 	{
4451 		CONS_Printf("The server is restricting all players to skin \"%s\".\n",skins[cv_forceskin.value].name);
4452 		ForceAllSkins(cv_forceskin.value);
4453 	}
4454 }
4455 
4456 //Allows the player's name to be changed if cv_mute is off.
Name_OnChange(void)4457 static void Name_OnChange(void)
4458 {
4459 	if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
4460 	{
4461 		CONS_Alert(CONS_NOTICE, M_GetText("You may not change your name when chat is muted.\n"));
4462 		CV_StealthSet(&cv_playername, player_names[consoleplayer]);
4463 	}
4464 	else
4465 		SendNameAndColor();
4466 
4467 }
4468 
Name2_OnChange(void)4469 static void Name2_OnChange(void)
4470 {
4471 	if (cv_mute.value) //Secondary player can't be admin.
4472 	{
4473 		CONS_Alert(CONS_NOTICE, M_GetText("You may not change your name when chat is muted.\n"));
4474 		CV_StealthSet(&cv_playername2, player_names[secondarydisplayplayer]);
4475 	}
4476 	else
4477 		SendNameAndColor2();
4478 }
4479 
4480 /** Sends a skin change for the console player, unless that player is moving.
4481   * \sa cv_skin, Skin2_OnChange, Color_OnChange
4482   * \author Graue <graue@oceanbase.org>
4483   */
Skin_OnChange(void)4484 static void Skin_OnChange(void)
4485 {
4486 	if (!Playing())
4487 		return; // do whatever you want
4488 
4489 	if (!(cv_debug || devparm) && !(multiplayer || netgame) // In single player.
4490 		&& (gamestate != GS_WAITINGPLAYERS)) // allows command line -warp x +skin y
4491 	{
4492 		CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
4493 		return;
4494 	}
4495 
4496 	if (CanChangeSkin(consoleplayer) && !P_PlayerMoving(consoleplayer))
4497 		SendNameAndColor();
4498 	else
4499 	{
4500 		CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n"));
4501 		CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
4502 	}
4503 }
4504 
4505 /** Sends a skin change for the secondary splitscreen player, unless that
4506   * player is moving.
4507   * \sa cv_skin2, Skin_OnChange, Color2_OnChange
4508   * \author Graue <graue@oceanbase.org>
4509   */
Skin2_OnChange(void)4510 static void Skin2_OnChange(void)
4511 {
4512 	if (!Playing() || !splitscreen)
4513 		return; // do whatever you want
4514 
4515 	if (CanChangeSkin(secondarydisplayplayer) && !P_PlayerMoving(secondarydisplayplayer))
4516 		SendNameAndColor2();
4517 	else
4518 	{
4519 		CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n"));
4520 		CV_StealthSet(&cv_skin2, skins[players[secondarydisplayplayer].skin].name);
4521 	}
4522 }
4523 
4524 /** Sends a color change for the console player, unless that player is moving.
4525   * \sa cv_playercolor, Color2_OnChange, Skin_OnChange
4526   * \author Graue <graue@oceanbase.org>
4527   */
Color_OnChange(void)4528 static void Color_OnChange(void)
4529 {
4530 	if (!Playing()) {
4531 		if (!cv_playercolor.value || !skincolors[cv_playercolor.value].accessible)
4532 			CV_StealthSetValue(&cv_playercolor, lastgoodcolor);
4533 	}
4534 	else
4535 	{
4536 		if (!(cv_debug || devparm) && !(multiplayer || netgame)) // In single player.
4537 		{
4538 			CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
4539 			return;
4540 		}
4541 
4542 		if (!P_PlayerMoving(consoleplayer) && skincolors[players[consoleplayer].skincolor].accessible == true)
4543 		{
4544 			// Color change menu scrolling fix is no longer necessary
4545 			SendNameAndColor();
4546 		}
4547 		else
4548 		{
4549 			CV_StealthSetValue(&cv_playercolor,
4550 				players[consoleplayer].skincolor);
4551 		}
4552 	}
4553 	lastgoodcolor = cv_playercolor.value;
4554 }
4555 
4556 /** Sends a color change for the secondary splitscreen player, unless that
4557   * player is moving.
4558   * \sa cv_playercolor2, Color_OnChange, Skin2_OnChange
4559   * \author Graue <graue@oceanbase.org>
4560   */
Color2_OnChange(void)4561 static void Color2_OnChange(void)
4562 {
4563 	if (!Playing() || !splitscreen)
4564 	{
4565 		if (!cv_playercolor2.value || !skincolors[cv_playercolor2.value].accessible)
4566 			CV_StealthSetValue(&cv_playercolor2, lastgoodcolor2);
4567 	}
4568 	else
4569 	{
4570 		if (!P_PlayerMoving(secondarydisplayplayer) && skincolors[players[secondarydisplayplayer].skincolor].accessible == true)
4571 		{
4572 			// Color change menu scrolling fix is no longer necessary
4573 			SendNameAndColor2();
4574 		}
4575 		else
4576 		{
4577 			CV_StealthSetValue(&cv_playercolor2,
4578 				players[secondarydisplayplayer].skincolor);
4579 		}
4580 	}
4581 	lastgoodcolor2 = cv_playercolor2.value;
4582 }
4583 
4584 /** Displays the result of the chat being muted or unmuted.
4585   * The server or remote admin should already know and be able to talk
4586   * regardless, so this is only displayed to clients.
4587   *
4588   * \sa cv_mute
4589   * \author Graue <graue@oceanbase.org>
4590   */
Mute_OnChange(void)4591 static void Mute_OnChange(void)
4592 {
4593 	if (server || (IsPlayerAdmin(consoleplayer)))
4594 		return;
4595 
4596 	if (cv_mute.value)
4597 		CONS_Printf(M_GetText("Chat has been muted.\n"));
4598 	else
4599 		CONS_Printf(M_GetText("Chat is no longer muted.\n"));
4600 }
4601 
4602 /** Hack to clear all changed flags after game start.
4603   * A lot of code (written by dummies, obviously) uses COM_BufAddText() to run
4604   * commands and change consvars, especially on game start. This is problematic
4605   * because CV_ClearChangedFlags() needs to get called on game start \b after
4606   * all those commands are run.
4607   *
4608   * Here's how it's done: the last thing in COM_BufAddText() is "dummyconsvar
4609   * 1", so we end up here, where dummyconsvar is reset to 0 and all the changed
4610   * flags are set to 0.
4611   *
4612   * \todo Fix the aforementioned code and make this hack unnecessary.
4613   * \sa cv_dummyconsvar
4614   * \author Graue <graue@oceanbase.org>
4615   */
DummyConsvar_OnChange(void)4616 static void DummyConsvar_OnChange(void)
4617 {
4618 	if (cv_dummyconsvar.value == 1)
4619 	{
4620 		CV_SetValue(&cv_dummyconsvar, 0);
4621 		CV_ClearChangedFlags();
4622 	}
4623 }
4624 
Command_ShowScores_f(void)4625 static void Command_ShowScores_f(void)
4626 {
4627 	UINT8 i;
4628 
4629 	if (!(netgame || multiplayer))
4630 	{
4631 		CONS_Printf(M_GetText("This only works in a netgame.\n"));
4632 		return;
4633 	}
4634 
4635 	for (i = 0; i < MAXPLAYERS; i++)
4636 	{
4637 		if (playeringame[i])
4638 			// FIXME: %lu? what's wrong with %u? ~Callum (produces warnings...)
4639 			CONS_Printf(M_GetText("%s's score is %u\n"), player_names[i], players[i].score);
4640 	}
4641 	CONS_Printf(M_GetText("The pointlimit is %d\n"), cv_pointlimit.value);
4642 
4643 }
4644 
Command_ShowTime_f(void)4645 static void Command_ShowTime_f(void)
4646 {
4647 	if (!(netgame || multiplayer))
4648 	{
4649 		CONS_Printf(M_GetText("This only works in a netgame.\n"));
4650 		return;
4651 	}
4652 
4653 	CONS_Printf(M_GetText("The current time is %f.\nThe timelimit is %f\n"), (double)leveltime/TICRATE, (double)timelimitintics/TICRATE);
4654 }
4655 
BaseNumLaps_OnChange(void)4656 static void BaseNumLaps_OnChange(void)
4657 {
4658 	if ((gametyperules & (GTR_RACE|GTR_LIVES)) == GTR_RACE)
4659 	{
4660 		if (cv_basenumlaps.value)
4661 			CONS_Printf(M_GetText("Number of laps will be changed to map defaults next round.\n"));
4662 		else
4663 			CONS_Printf(M_GetText("Number of laps will be changed to %d next round.\n"), cv_basenumlaps.value);
4664 	}
4665 }
4666