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