when using localskins and going over the skinlimit this should use numskins not numallskins there no i dont feel like supporting localskins here the other skin commands also dont care
5963 lines
171 KiB
C
5963 lines
171 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
|
// Copyright (C) 1999-2018 by Sonic Team Junior.
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file d_netcmd.c
|
|
/// \brief host/client network commands
|
|
/// commands are executed through the command buffer
|
|
/// like console commands, other miscellaneous commands (at the end)
|
|
|
|
#include "doomdef.h"
|
|
|
|
#include "console.h"
|
|
#include "command.h"
|
|
#include "dehacked.h"
|
|
#include "i_time.h"
|
|
#include "i_system.h"
|
|
#include "g_game.h"
|
|
#include "hu_stuff.h"
|
|
#include "m_emotes.h"
|
|
#include "g_input.h"
|
|
#include "m_menu.h"
|
|
#include "r_local.h"
|
|
#include "r_things.h"
|
|
#include "p_local.h"
|
|
#include "p_setup.h"
|
|
#include "s_sound.h"
|
|
#include "i_sound.h"
|
|
#include "m_misc.h"
|
|
#include "am_map.h"
|
|
#include "byteptr.h"
|
|
#include "d_netfil.h"
|
|
#include "p_spec.h"
|
|
#include "m_cheat.h"
|
|
#include "d_clisrv.h"
|
|
#include "d_main.h"
|
|
#include "m_random.h"
|
|
#include "f_finale.h"
|
|
#include "v_video.h"
|
|
#include "filesrch.h"
|
|
#include "mserv.h"
|
|
#include "md5.h"
|
|
#include "z_zone.h"
|
|
#include "lua_script.h"
|
|
#include "lua_hook.h"
|
|
#include "m_cond.h"
|
|
#include "m_anigif.h"
|
|
#include "k_kart.h" // SRB2kart
|
|
#include "y_inter.h"
|
|
#include "fastcmp.h"
|
|
#include "m_perfstats.h"
|
|
|
|
#include "r_data.h"
|
|
#include "r_skins.h"
|
|
|
|
#ifdef NETGAME_DEVMODE
|
|
#define CV_RESTRICT CV_NETVAR
|
|
#else
|
|
#define CV_RESTRICT 0
|
|
#endif
|
|
|
|
#ifdef HAVE_DISCORDRPC
|
|
#include "discord.h"
|
|
#endif
|
|
|
|
// ------
|
|
// protos
|
|
// ------
|
|
|
|
static void Got_NameAndColor(const UINT8 **cp, INT32 playernum);
|
|
static void Got_WeaponPref(const UINT8 **cp, INT32 playernum);
|
|
static void Got_Mapcmd(const UINT8 **cp, INT32 playernum);
|
|
static void Got_ExitLevelcmd(const UINT8 **cp, INT32 playernum);
|
|
static void Got_SetupVotecmd(const UINT8 **cp, INT32 playernum);
|
|
static void Got_ModifyVotecmd(const UINT8 **cp, INT32 playernum);
|
|
static void Got_PickVotecmd(const UINT8 **cp, INT32 playernum);
|
|
static void Got_RequestAddfilecmd(const UINT8 **cp, INT32 playernum);
|
|
static void Got_Addfilecmd(const UINT8 **cp, INT32 playernum);
|
|
static void Got_Pause(const UINT8 **cp, INT32 playernum);
|
|
static void Got_Respawn(const UINT8 **cp, INT32 playernum);
|
|
static void Got_RandomSeed(const UINT8 **cp, INT32 playernum);
|
|
static void Got_RunSOCcmd(const UINT8 **cp, INT32 playernum);
|
|
static void Got_Teamchange(const UINT8 **cp, INT32 playernum);
|
|
static void Got_Clearscores(const UINT8 **cp, INT32 playernum);
|
|
static void Got_DiscordInfo(const UINT8 **cp, INT32 playernum);
|
|
|
|
static void PointLimit_OnChange(void);
|
|
static void TimeLimit_OnChange(void);
|
|
static void NumLaps_OnChange(void);
|
|
static void Mute_OnChange(void);
|
|
|
|
static void Hidetime_OnChange(void);
|
|
|
|
static void AutoBalance_OnChange(void);
|
|
static void TeamScramble_OnChange(void);
|
|
|
|
static void NetTimeout_OnChange(void);
|
|
static void JoinTimeout_OnChange(void);
|
|
|
|
static void Ringslinger_OnChange(void);
|
|
static void Gravity_OnChange(void);
|
|
static void ForceSkin_OnChange(void);
|
|
|
|
static void Name_OnChange(void);
|
|
static void Name2_OnChange(void);
|
|
static void Name3_OnChange(void);
|
|
static void Name4_OnChange(void);
|
|
static void Skin_OnChange(void);
|
|
static void Skin2_OnChange(void);
|
|
static void Skin3_OnChange(void);
|
|
static void Skin4_OnChange(void);
|
|
static void Color_OnChange(void);
|
|
static void Color2_OnChange(void);
|
|
static void Color3_OnChange(void);
|
|
static void Color4_OnChange(void);
|
|
static void DummyConsvar_OnChange(void);
|
|
static void SoundTest_OnChange(void);
|
|
|
|
static void BaseNumLaps_OnChange(void);
|
|
static void KartFrantic_OnChange(void);
|
|
static void KartSpeed_OnChange(void);
|
|
static void KartEncore_OnChange(void);
|
|
static void KartComeback_OnChange(void);
|
|
static void KartEliminateLast_OnChange(void);
|
|
|
|
#ifdef NETGAME_DEVMODE
|
|
static void Fishcake_OnChange(void);
|
|
#endif
|
|
|
|
static void Command_Playdemo_f(void);
|
|
static void Command_Timedemo_f(void);
|
|
static void Command_Stopdemo_f(void);
|
|
static void Command_StartMovie_f(void);
|
|
static void Command_StopMovie_f(void);
|
|
static void Command_Map_f(void);
|
|
static void Command_ResetCamera_f(void);
|
|
|
|
static void Command_View_f (void);
|
|
static void Command_SetViews_f(void);
|
|
|
|
static void Command_Addfilelocal(void);
|
|
static void Command_Addfile(void);
|
|
static void Command_Addskins(void);
|
|
static void Command_GLocalSkin(void);
|
|
static void Command_ListWADS_f(void);
|
|
static void Command_LocateLump_f(void);
|
|
static void Command_ListDoomednums_f(void);
|
|
static void Command_ListUnusedSprites_f(void);
|
|
static void Command_ListUnusedMapSlots_f(void);
|
|
static void Command_RunSOC(void);
|
|
static void Command_Pause(void);
|
|
static void Command_Respawn(void);
|
|
|
|
static void Command_ReplayMarker(void);
|
|
|
|
static void Command_Version_f(void);
|
|
#ifdef UPDATE_ALERT
|
|
static void Command_ModDetails_f(void);
|
|
#endif
|
|
static void Command_ShowGametype_f(void);
|
|
FUNCNORETURN static ATTRNORETURN void Command_Quit_f(void);
|
|
static void Command_Playintro_f(void);
|
|
|
|
static void Command_Displayplayer_f(void);
|
|
|
|
static void Command_ExitLevel_f(void);
|
|
static void Command_Showmap_f(void);
|
|
static void Command_Mapmd5_f(void);
|
|
|
|
static void Command_Teamchange_f(void);
|
|
static void Command_Teamchange2_f(void);
|
|
static void Command_Teamchange3_f(void);
|
|
static void Command_Teamchange4_f(void);
|
|
|
|
static void Command_ServerTeamChange_f(void);
|
|
|
|
static void Command_Clearscores_f(void);
|
|
|
|
// Remote Administration
|
|
static void Command_Changepassword_f(void);
|
|
static void Command_Login_f(void);
|
|
static void Got_Login(const UINT8 **cp, INT32 playernum);
|
|
static void Got_Verification(const UINT8 **cp, INT32 playernum);
|
|
static void Got_Removal(const UINT8 **cp, INT32 playernum);
|
|
static void Command_Verify_f(void);
|
|
static void Command_RemoveAdmin_f(void);
|
|
static void Command_MotD_f(void);
|
|
static void Got_MotD_f(const UINT8 **cp, INT32 playernum);
|
|
|
|
static void Command_ShowScores_f(void);
|
|
static void Command_ShowTime_f(void);
|
|
|
|
static void Command_Isgamemodified_f(void);
|
|
static void Command_Cheats_f(void);
|
|
|
|
static void Command_ListSkins(void);
|
|
static void Command_SkinSearch(void);
|
|
|
|
|
|
#ifdef _DEBUG
|
|
static void Command_Togglemodified_f(void);
|
|
static void Command_Archivetest_f(void);
|
|
#endif
|
|
|
|
// =========================================================================
|
|
// CLIENT VARIABLES
|
|
// =========================================================================
|
|
|
|
static CV_PossibleValue_t usemouse_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Force"}, {0, NULL}};
|
|
|
|
static CV_PossibleValue_t autobalance_cons_t[] = {{0, "MIN"}, {4, "MAX"}, {0, NULL}};
|
|
static CV_PossibleValue_t teamscramble_cons_t[] = {{0, "Off"}, {1, "Random"}, {2, "Points"}, {0, NULL}};
|
|
|
|
static CV_PossibleValue_t startingliveslimit_cons_t[] = {{1, "MIN"}, {99, "MAX"}, {0, NULL}};
|
|
static CV_PossibleValue_t sleeping_cons_t[] = {{0, "MIN"}, {1000/TICRATE, "MAX"}, {0, NULL}};
|
|
static CV_PossibleValue_t competitionboxes_cons_t[] = {{0, "Normal"}, {1, "Random"}, {2, "Teleports"}, {3, "None"}, {0, NULL}};
|
|
static CV_PossibleValue_t matchboxes_cons_t[] = {{0, "Normal"}, {1, "Random"}, {2, "Non-Random"}, {3, "None"}, {0, NULL}};
|
|
|
|
//static CV_PossibleValue_t chances_cons_t[] = {{0, "MIN"}, {9, "MAX"}, {0, NULL}};
|
|
static CV_PossibleValue_t match_scoring_cons_t[] = {{0, "Normal"}, {1, "Classic"}, {0, NULL}};
|
|
static CV_PossibleValue_t pause_cons_t[] = {{0, "Server"}, {1, "All"}, {0, NULL}};
|
|
|
|
static CV_PossibleValue_t timetic_cons_t[] = {{0, "Normal"}, {1, "Tics"}, {2, "Centiseconds"}, {0, NULL}};
|
|
|
|
#ifdef NETGAME_DEVMODE
|
|
static consvar_t cv_fishcake = {"fishcake", "Off", CV_CALL|CV_NOSHOWHELP|CV_RESTRICT, CV_OnOff, Fishcake_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
#endif
|
|
static consvar_t cv_dummyconsvar = {"dummyconsvar", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff,
|
|
DummyConsvar_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_restrictskinchange = {"restrictskinchange", "No", CV_NETVAR|CV_CHEAT, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_allowteamchange = {"allowteamchange", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t ingamecap_cons_t[] = {{0, "MIN"}, {MAXPLAYERS-1, "MAX"}, {0, NULL}};
|
|
consvar_t cv_ingamecap = {"ingamecap", "0", CV_NETVAR, ingamecap_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t spectatorreentry_cons_t[] = {{0, "MIN"}, {10*60, "MAX"}, {0, NULL}};
|
|
consvar_t cv_spectatorreentry = {"spectatorreentry", "30", CV_NETVAR, spectatorreentry_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t antigrief_cons_t[] = {{20, "MIN"}, {180, "MAX"}, {0, "Off"}, {0, NULL}};
|
|
consvar_t cv_antigrief = {"antigrief", "30", CV_NETVAR, antigrief_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_startinglives = {"startinglives", "3", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, startingliveslimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t respawntime_cons_t[] = {{0, "MIN"}, {30, "MAX"}, {0, NULL}};
|
|
consvar_t cv_respawntime = {"respawndelay", "1", CV_NETVAR|CV_CHEAT, respawntime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_competitionboxes = {"competitionboxes", "Random", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, competitionboxes_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
#ifdef SEENAMES
|
|
static CV_PossibleValue_t seenames_cons_t[] = {{0, "Off"}, {1, "Colorless"}, {2, "Team"}, {3, "Ally/Foe"}, {0, NULL}};
|
|
consvar_t cv_seenames = {"seenames", "Off", CV_SAVE, seenames_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_allowseenames = {"allowseenames", "No", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
#endif
|
|
|
|
// these are just meant to be saved to the config
|
|
consvar_t cv_playername = {"name", "Sonic", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_playername2 = {"name2", "Tails", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name2_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_playername3 = {"name3", "Knuckles", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name3_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_playername4 = {"name4", "Dr. Eggman", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name4_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
// player colors
|
|
consvar_t cv_playercolor = {"color", "Blue", CV_SAVE|CV_CALL|CV_NOINIT, Color_cons_t, Color_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_playercolor2 = {"color2", "Orange", CV_SAVE|CV_CALL|CV_NOINIT, Color_cons_t, Color2_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_playercolor3 = {"color3", "Red", CV_SAVE|CV_CALL|CV_NOINIT, Color_cons_t, Color3_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_playercolor4 = {"color4", "Red", CV_SAVE|CV_CALL|CV_NOINIT, Color_cons_t, Color4_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
// player's skin, saved for commodity, when using a favorite skins wad..
|
|
consvar_t cv_skin = {"skin", DEFAULTSKIN, CV_SAVE|CV_CALL|CV_NOINIT, NULL, Skin_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_skin2 = {"skin2", DEFAULTSKIN2, CV_SAVE|CV_CALL|CV_NOINIT, NULL, Skin2_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_skin3 = {"skin3", DEFAULTSKIN3, CV_SAVE|CV_CALL|CV_NOINIT, NULL, Skin3_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_skin4 = {"skin4", DEFAULTSKIN4, CV_SAVE|CV_CALL|CV_NOINIT, NULL, Skin4_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
// haha I've beaten you now, ONLINE
|
|
consvar_t cv_localskin = {"internal___localskin", "none", CV_HIDEN, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_showlocalskinmenus = {"showlocalskinmenus", "Yes", CV_SAVE|CV_CALL, CV_YesNo, ShowLocalskinMenu_Onchange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_skipmapcheck = {"skipmapcheck", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
INT32 cv_debug = 0;
|
|
|
|
static void UseMouse_OnChange(void);
|
|
consvar_t cv_usemouse = {"use_mouse", "Off", CV_SAVE|CV_CALL, usemouse_cons_t, UseMouse_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_mousevisible = {"mousevisible", "Off", CV_SAVE|CV_CALL, CV_OnOff, UseMouse_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static void UseMouse_OnChange(void)
|
|
{
|
|
GameFocus_menu_Onchange();
|
|
I_StartupMouse();
|
|
}
|
|
|
|
//WTF
|
|
consvar_t cv_mouseturn = {"mouseturn", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
// Lagless camera! Yay!
|
|
consvar_t cv_laglesscam = {"lagless_camera", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
// makes the camera use the saved angleturn in replays instead of the players angle
|
|
// more accurate to how the recording player seen things in game, but may be jittery if they encountered net issues
|
|
consvar_t cv_demoangturn = {"useangleturninreplays", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_verticallook[MAXSPLITSCREENPLAYERS] = {
|
|
{"verticallook", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL},
|
|
{"verticallook2", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL},
|
|
{"verticallook3", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL},
|
|
{"verticallook4", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}
|
|
};
|
|
|
|
consvar_t cv_usejoystick[MAXSPLITSCREENPLAYERS] = {
|
|
{"use_joystick", "1", CV_SAVE|CV_CALL, NULL, I_InitJoystick1, 0, NULL, NULL, 0, 0, NULL},
|
|
{"use_joystick2", "2", CV_SAVE|CV_CALL, NULL, I_InitJoystick2, 0, NULL, NULL, 0, 0, NULL},
|
|
{"use_joystick3", "3", CV_SAVE|CV_CALL, NULL, I_InitJoystick3, 0, NULL, NULL, 0, 0, NULL},
|
|
{"use_joystick4", "4", CV_SAVE|CV_CALL, NULL, I_InitJoystick4, 0, NULL, NULL, 0, 0, NULL}
|
|
};
|
|
|
|
#if defined (HAVE_SDL)
|
|
consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS] = {
|
|
{"joyscale", "1", CV_SAVE|CV_CALL, NULL, I_JoyScale, 0, NULL, NULL, 0, 0, NULL},
|
|
{"joyscale2", "1", CV_SAVE|CV_CALL, NULL, I_JoyScale2, 0, NULL, NULL, 0, 0, NULL},
|
|
{"joyscale3", "1", CV_SAVE|CV_CALL, NULL, I_JoyScale3, 0, NULL, NULL, 0, 0, NULL},
|
|
{"joyscale4", "1", CV_SAVE|CV_CALL, NULL, I_JoyScale4, 0, NULL, NULL, 0, 0, NULL}
|
|
};
|
|
#else
|
|
consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS] = { //Alam: Dummy for save
|
|
{"joyscale", "1", CV_SAVE|CV_HIDEN, NULL, NULL, 0, NULL, NULL, 0, 0, NULL},
|
|
{"joyscale2", "1", CV_SAVE|CV_HIDEN, NULL, NULL, 0, NULL, NULL, 0, 0, NULL},
|
|
{"joyscale3", "1", CV_SAVE|CV_HIDEN, NULL, NULL, 0, NULL, NULL, 0, 0, NULL},
|
|
{"joyscale4", "1", CV_SAVE|CV_HIDEN, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}
|
|
};
|
|
#endif
|
|
|
|
consvar_t cv_matchboxes = {"matchboxes", "Normal", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, matchboxes_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_specialrings = {"specialrings", "On", CV_NETVAR|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_powerstones = {"powerstones", "On", CV_NETVAR|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
// SRB2kart
|
|
consvar_t cv_sneaker = {"sneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_rocketsneaker = {"rocketsneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_invincibility = {"invincibility", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_banana = {"banana", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_eggmanmonitor = {"eggmanmonitor", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_orbinaut = {"orbinaut", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_jawz = {"jawz", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_mine = {"mine", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_ballhog = {"ballhog", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_selfpropelledbomb = {"selfpropelledbomb", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_grow = {"grow", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_shrink = {"shrink", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_thundershield = {"thundershield", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_hyudoro = {"hyudoro", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_pogospring = {"pogospring", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_kitchensink = {"kitchensink", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_triplesneaker = {"triplesneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_triplebanana = {"triplebanana", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_decabanana = {"decabanana", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_tripleorbinaut = {"tripleorbinaut", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_quadorbinaut = {"quadorbinaut", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_dualjawz = {"dualjawz", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
//Save server ip
|
|
consvar_t cv_lastserver = {"lastserver", "", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t kartminimap_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}};
|
|
consvar_t cv_kartminimap = {"kartminimap", "4", CV_SAVE, kartminimap_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_kartcheck = {"kartcheck", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
static CV_PossibleValue_t kartinvinsfx_cons_t[] = {{0, "Music"}, {1, "SFX"}, {0, NULL}};
|
|
consvar_t cv_kartinvinsfx = {"kartinvinsfx", "SFX", CV_SAVE, kartinvinsfx_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_kartspeed = {"kartspeed", "Normal", CV_NETVAR|CV_CALL|CV_NOINIT, kartspeed_cons_t, KartSpeed_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
static CV_PossibleValue_t kartbumpers_cons_t[] = {{1, "MIN"}, {12, "MAX"}, {0, NULL}};
|
|
consvar_t cv_kartbumpers = {"kartbumpers", "3", CV_NETVAR|CV_CHEAT, kartbumpers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_kartfrantic = {"kartfrantic", "Off", CV_NETVAR|CV_CHEAT|CV_CALL|CV_NOINIT, CV_OnOff, KartFrantic_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_kartcomeback = {"kartcomeback", "On", CV_NETVAR|CV_CHEAT|CV_CALL|CV_NOINIT, CV_OnOff, KartComeback_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_kartencore = {"kartencore", "Off", CV_NETVAR|CV_CALL|CV_NOINIT, CV_OnOff, KartEncore_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
static CV_PossibleValue_t kartvoterulechanges_cons_t[] = {{0, "Never"}, {1, "Sometimes"}, {2, "Frequent"}, {3, "Always"}, {0, NULL}};
|
|
consvar_t cv_kartvoterulechanges = {"kartvoterulechanges", "Frequent", CV_NETVAR, kartvoterulechanges_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
static CV_PossibleValue_t kartgametypepreference_cons_t[] = {{-1, "None"}, {GT_RACE, "Race"}, {GT_MATCH, "Battle"}, {0, NULL}};
|
|
consvar_t cv_kartgametypepreference = {"kartgametypepreference", "None", CV_NETVAR, kartgametypepreference_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
static CV_PossibleValue_t kartspeedometer_cons_t[] = {{0, "Off"}, {1, "Kilometers"}, {2, "Miles"}, {3, "Fracunits"}, {4, "Percent"}, {0, NULL}};
|
|
consvar_t cv_kartspeedometer = {"kartdisplayspeed", "Off", CV_SAVE, kartspeedometer_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; // use tics in display
|
|
static CV_PossibleValue_t kartvoices_cons_t[] = {{0, "Never"}, {1, "Tasteful"}, {2, "Meme"}, {0, NULL}};
|
|
consvar_t cv_kartvoices = {"kartvoices", "Tasteful", CV_SAVE, kartvoices_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_karthitemdialog = {"karthitemdialog", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_karteliminatelast = {"karteliminatelast", "Yes", CV_NETVAR|CV_CHEAT|CV_CALL, CV_YesNo, KartEliminateLast_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t kartdebugitem_cons_t[] = {{-1, "MIN"}, {NUMKARTITEMS-1, "MAX"}, {0, NULL}};
|
|
consvar_t cv_kartdebugitem = {"kartdebugitem", "0", CV_NETVAR|CV_CHEAT, kartdebugitem_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
static CV_PossibleValue_t kartdebugamount_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}};
|
|
consvar_t cv_kartdebugamount = {"kartdebugamount", "1", CV_NETVAR|CV_CHEAT, kartdebugamount_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_kartdebugshrink = {"kartdebugshrink", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_kartdebugdistribution = {"kartdebugdistribution", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_kartdebughuddrop = {"kartdebughuddrop", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_kartdebugcheckpoint = {"kartdebugcheckpoint", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_kartdebugnodes = {"kartdebugnodes", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_kartdebugcolorize = {"kartdebugcolorize", "Off", CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t votetime_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, NULL}};
|
|
consvar_t cv_votetime = {"votetime", "20", CV_NETVAR, votetime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_ringslinger = {"ringslinger", "No", CV_NETVAR|CV_NOSHOWHELP|CV_CALL|CV_CHEAT, CV_YesNo,
|
|
Ringslinger_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_gravity = {"gravity", "0.8", CV_RESTRICT|CV_FLOAT|CV_CALL, NULL, Gravity_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_soundtest = {"soundtest", "0", CV_CALL, NULL, SoundTest_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t minitimelimit_cons_t[] = {{15, "MIN"}, {9999, "MAX"}, {0, NULL}};
|
|
consvar_t cv_countdowntime = {"countdowntime", "30", CV_NETVAR|CV_CHEAT, minitimelimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_touchtag = {"touchtag", "Off", CV_NETVAR|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_hidetime = {"hidetime", "30", CV_NETVAR|CV_CALL|CV_NOSHOWHELP, minitimelimit_cons_t, Hidetime_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_autobalance = {"autobalance", "0", CV_NETVAR|CV_CALL, autobalance_cons_t, AutoBalance_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_teamscramble = {"teamscramble", "Off", CV_NETVAR|CV_CALL|CV_NOINIT, teamscramble_cons_t, TeamScramble_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_scrambleonchange = {"scrambleonchange", "Off", CV_NETVAR, teamscramble_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_friendlyfire = {"friendlyfire", "Off", CV_NETVAR|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_itemfinder = {"itemfinder", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, ItemFinder_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
// Scoring type options
|
|
consvar_t cv_match_scoring = {"matchscoring", "Normal", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, match_scoring_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_overtime = {"overtime", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_rollingdemos = {"rollingdemos", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_timetic = {"timerres", "Normal", CV_SAVE|CV_NOSHOWHELP, timetic_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; // use tics in display
|
|
|
|
static CV_PossibleValue_t pointlimit_cons_t[] = {{0, "MIN"}, {999999990, "MAX"}, {0, NULL}};
|
|
consvar_t cv_pointlimit = {"pointlimit", "0", CV_NETVAR|CV_CALL|CV_NOINIT, pointlimit_cons_t,
|
|
PointLimit_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
static CV_PossibleValue_t timelimit_cons_t[] = {{0, "MIN"}, {30, "MAX"}, {0, NULL}};
|
|
consvar_t cv_timelimit = {"timelimit", "0", CV_NETVAR|CV_CALL|CV_NOINIT, timelimit_cons_t,
|
|
TimeLimit_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
static CV_PossibleValue_t numlaps_cons_t[] = {{1, "MIN"}, {50, "MAX"}, {0, NULL}};
|
|
consvar_t cv_numlaps = {"numlaps", "3", CV_NETVAR|CV_CALL|CV_NOINIT, numlaps_cons_t,
|
|
NumLaps_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
static CV_PossibleValue_t basenumlaps_cons_t[] = {{1, "MIN"}, {50, "MAX"}, {0, "Map default"}, {0, NULL}};
|
|
consvar_t cv_basenumlaps = {"basenumlaps", "Map default", CV_NETVAR|CV_CALL|CV_CHEAT, basenumlaps_cons_t, BaseNumLaps_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_forceskin = {"forceskin", "Off", CV_NETVAR|CV_CALL|CV_CHEAT, Forceskin_cons_t, ForceSkin_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_fakelocalskin = {"fakelocalskin", "None", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_downloading = {"downloading", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_allowexitlevel = {"allowexitlevel", "No", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_killingdead = {"killingdead", "Off", CV_NETVAR|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_netstat = {"netstat", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; // show bandwidth statistics
|
|
static CV_PossibleValue_t nettimeout_cons_t[] = {{TICRATE/7, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
|
|
consvar_t cv_nettimeout = {"nettimeout", "210", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_jointimeout = {"jointimeout", "210", CV_CALL|CV_SAVE, nettimeout_cons_t, JoinTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_maxping = {"maxdelay", "20", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t pingtimeout_cons_t[] = {{8, "MIN"}, {120, "MAX"}, {0, NULL}};
|
|
consvar_t cv_pingtimeout = {"maxdelaytimeout", "10", CV_SAVE, pingtimeout_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
// show your ping on the HUD next to framerate. Defaults to warning only (shows up if your ping is > maxping)
|
|
static CV_PossibleValue_t showping_cons_t[] = {{0, "Off"}, {1, "Always"}, {2, "Warning"}, {0, NULL}};
|
|
consvar_t cv_showping = {"showping", "Always", CV_SAVE, showping_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t pingmeasurement_cons_t[] = {{0, "Frames"}, {1, "Milliseconds"}, {0, NULL}};
|
|
consvar_t cv_pingmeasurement = {"pingmeasurement", "Frames", CV_SAVE, pingmeasurement_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_pingicon = {"pingicon", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t cv_pingstyle_cons_t[] = {{0, "New"}, {1, "Old"}, {0, NULL}};
|
|
consvar_t cv_pingstyle = {"pingstyle", "New", CV_SAVE, cv_pingstyle_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_luaimmersion = {"luaimmersion", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_showviewpointtext = {"showviewpointtext", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_showdownloadprompt = {"showdownloadprompt", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
// Intermission time Tails 04-19-2002
|
|
static CV_PossibleValue_t inttime_cons_t[] = {{0, "MIN"}, {3600, "MAX"}, {0, NULL}};
|
|
consvar_t cv_inttime = {"inttime", "20", CV_NETVAR, inttime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t advancemap_cons_t[] = {{0, "Same"}, {1, "Next"}, {2, "Random"}, {3, "Vote"}, {0, NULL}};
|
|
consvar_t cv_advancemap = {"advancemap", "Vote", CV_NETVAR, advancemap_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
static CV_PossibleValue_t playersforexit_cons_t[] = {{0, "One"}, {1, "All"}, {0, NULL}};
|
|
consvar_t cv_playersforexit = {"playersforexit", "One", CV_NETVAR, playersforexit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_runscripts = {"runscripts", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_pause = {"pausepermission", "Server", CV_NETVAR, pause_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_mute = {"mute", "Off", CV_NETVAR|CV_CALL, CV_OnOff, Mute_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_sleep = {"cpusleep", "1", CV_SAVE, sleeping_cons_t, NULL, -1, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t skinselectspin_cons_t[] = {
|
|
{0, "Off"}, {1, "Slow"}, {2, "2"}, {3, "3"}, {4, "4"}, {5, "5"}, {6, "6"}, {7, "7"}, {8, "8"}, {9, "9"}, {10, "Fast"}, {SKINSELECTSPIN_PAIN, "Pain"}, {0, NULL}};
|
|
consvar_t cv_skinselectspin = {"skinselectspin", "5", CV_SAVE, skinselectspin_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t perfstats_cons_t[] = {
|
|
{0, "Off"}, {1, "Rendering"}, {2, "Logic"}, {3, "ThinkFrame"}, {4, "PreThinkFrame"}, {5, "PostThinkFrame"}, {0, NULL}};
|
|
consvar_t cv_perfstats = {"perfstats", "Off", CV_CALL, perfstats_cons_t, PS_PerfStats_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_ps_thinkframe_page = {"ps_thinkframe_page", "1", CV_CALL, CV_Natural, PS_ThinkFrame_Page_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t ps_samplesize_cons_t[] = {
|
|
{1, "MIN"}, {1000, "MAX"}, {0, NULL}};
|
|
consvar_t cv_ps_samplesize = {"ps_samplesize", "1", CV_CALL, ps_samplesize_cons_t, PS_SampleSize_OnChange, 0, NULL, NULL, 0, 0, NULL};
|
|
static CV_PossibleValue_t ps_descriptor_cons_t[] = {
|
|
{1, "Average"}, {2, "SD"}, {3, "Minimum"}, {4, "Maximum"}, {0, NULL}};
|
|
consvar_t cv_ps_descriptor = {"ps_descriptor", "Average", 0, ps_descriptor_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
// only there to better keep track of it globally
|
|
consvar_t cv_director = {"director", "Off", CV_HIDEN, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_kartdebugdirector = {"debugdirector", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
consvar_t cv_showdirectorhud = {"showdirectorhud", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
consvar_t cv_showtrackaddon = {"showtrackaddon", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t skinselectmenu_t[] = {{SKINMENUTYPE_SCROLL, "Scroll"}, {SKINMENUTYPE_2D, "2d"}, {SKINMENUTYPE_GRID, "Grid"}, {SKINMENUTYPE_EXTENDED, "Extended"}, {0, NULL}};
|
|
consvar_t cv_skinselectmenu = {"skinselectmenu", "Extended", CV_SAVE, skinselectmenu_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
static CV_PossibleValue_t skinselectgridsort_t[] ={
|
|
{ SKINMENUSORT_REALNAME, "Real name" },
|
|
{ SKINMENUSORT_NAME, "Internal name" },
|
|
{ SKINMENUSORT_SPEED, "Speed" },
|
|
{ SKINMENUSORT_WEIGHT, "Weight" },
|
|
{ SKINMENUSORT_PREFCOLOR, "Preferred Color" }, // get stickbugged lol
|
|
{ SKINMENUSORT_ID, "ID" },
|
|
{ 0, NULL }
|
|
};
|
|
consvar_t cv_skinselectgridsort ={ "skinselectgridsort", "Real name", CV_SAVE|CV_CALL|CV_NOINIT, skinselectgridsort_t, sortSkinGrid, 0, NULL, NULL, 0, 0, NULL };
|
|
|
|
static CV_PossibleValue_t betainterscreen_t[] = {{0, "Off"}, {1, "On"}, {2, "NoBG"}, {0, NULL}};
|
|
consvar_t cv_betainterscreen = {"betaintermissionscreen", "Off", CV_SAVE, betainterscreen_t, NULL, 0, NULL, NULL, 0, 0, NULL};
|
|
|
|
INT16 gametype = GT_RACE; // SRB2kart
|
|
boolean forceresetplayers = false;
|
|
boolean deferencoremode = false;
|
|
UINT8 splitscreen = 0;
|
|
boolean circuitmap = true; // SRB2kart
|
|
INT32 adminplayers[MAXPLAYERS] = {};
|
|
|
|
/// \warning Keep this up-to-date if you add/remove/rename net text commands
|
|
const char *netxcmdnames[MAXNETXCMD - 1] =
|
|
{
|
|
"NAMEANDCOLOR",
|
|
"WEAPONPREF",
|
|
"KICK",
|
|
"NETVAR",
|
|
"SAY",
|
|
"MAP",
|
|
"EXITLEVEL",
|
|
"ADDFILE",
|
|
"PAUSE",
|
|
"ADDPLAYER",
|
|
"TEAMCHANGE",
|
|
"CLEARSCORES",
|
|
"LOGIN",
|
|
"VERIFIED",
|
|
"RANDOMSEED",
|
|
"RUNSOC",
|
|
"REQADDFILE",
|
|
"DELFILE", // sigh well this prob needs to be still here to not screw up maxnetxcmd
|
|
"SETMOTD",
|
|
"RESPAWN",
|
|
"DEMOTED",
|
|
"SETUPVOTE",
|
|
"MODIFYVOTE",
|
|
"PICKVOTE",
|
|
"REMOVEPLAYER",
|
|
"DISCORD",
|
|
"LUACMD",
|
|
"LUAVAR"
|
|
};
|
|
|
|
// =========================================================================
|
|
// SERVER STARTUP
|
|
// =========================================================================
|
|
|
|
/** Registers server commands and variables.
|
|
* Anything required by a dedicated server should probably go here.
|
|
*
|
|
* \sa D_RegisterClientCommands
|
|
*/
|
|
void D_RegisterServerCommands(void)
|
|
{
|
|
INT32 i;
|
|
Forceskin_cons_t[0].value = -1;
|
|
Forceskin_cons_t[0].strvalue = "Off";
|
|
|
|
for (i = 0; i < NUMGAMETYPES; i++)
|
|
{
|
|
gametype_cons_t[i].value = i;
|
|
gametype_cons_t[i].strvalue = Gametype_Names[i];
|
|
}
|
|
gametype_cons_t[NUMGAMETYPES].value = 0;
|
|
gametype_cons_t[NUMGAMETYPES].strvalue = NULL;
|
|
|
|
// Set the values to 0/NULL, it will be overwritten later when a skin is assigned to the slot.
|
|
for (i = 1; i < MAXSKINS; i++)
|
|
{
|
|
Forceskin_cons_t[i].value = 0;
|
|
Forceskin_cons_t[i].strvalue = NULL;
|
|
}
|
|
RegisterNetXCmd(XD_NAMEANDCOLOR, Got_NameAndColor);
|
|
RegisterNetXCmd(XD_WEAPONPREF, Got_WeaponPref);
|
|
RegisterNetXCmd(XD_MAP, Got_Mapcmd);
|
|
RegisterNetXCmd(XD_EXITLEVEL, Got_ExitLevelcmd);
|
|
RegisterNetXCmd(XD_ADDFILE, Got_Addfilecmd);
|
|
RegisterNetXCmd(XD_REQADDFILE, Got_RequestAddfilecmd);
|
|
RegisterNetXCmd(XD_PAUSE, Got_Pause);
|
|
RegisterNetXCmd(XD_RESPAWN, Got_Respawn);
|
|
RegisterNetXCmd(XD_RUNSOC, Got_RunSOCcmd);
|
|
RegisterNetXCmd(XD_LUACMD, Got_Luacmd);
|
|
|
|
RegisterNetXCmd(XD_SETUPVOTE, Got_SetupVotecmd);
|
|
RegisterNetXCmd(XD_MODIFYVOTE, Got_ModifyVotecmd);
|
|
RegisterNetXCmd(XD_PICKVOTE, Got_PickVotecmd);
|
|
|
|
// Remote Administration
|
|
COM_AddCommand("password", Command_Changepassword_f);
|
|
RegisterNetXCmd(XD_LOGIN, Got_Login);
|
|
COM_AddCommand("login", Command_Login_f); // useful in dedicated to kick off remote admin
|
|
COM_AddCommand("promote", Command_Verify_f);
|
|
RegisterNetXCmd(XD_VERIFIED, Got_Verification);
|
|
COM_AddCommand("demote", Command_RemoveAdmin_f);
|
|
RegisterNetXCmd(XD_DEMOTED, Got_Removal);
|
|
|
|
COM_AddCommand("motd", Command_MotD_f);
|
|
RegisterNetXCmd(XD_SETMOTD, Got_MotD_f); // For remote admin
|
|
|
|
RegisterNetXCmd(XD_TEAMCHANGE, Got_Teamchange);
|
|
COM_AddCommand("serverchangeteam", Command_ServerTeamChange_f);
|
|
|
|
RegisterNetXCmd(XD_CLEARSCORES, Got_Clearscores);
|
|
COM_AddCommand("clearscores", Command_Clearscores_f);
|
|
COM_AddCommand("map", Command_Map_f);
|
|
|
|
COM_AddCommand("exitgame", Command_ExitGame_f);
|
|
COM_AddCommand("retry", Command_Retry_f);
|
|
COM_AddCommand("exitlevel", Command_ExitLevel_f);
|
|
COM_AddCommand("showmap", Command_Showmap_f);
|
|
COM_AddCommand("mapmd5", Command_Mapmd5_f);
|
|
|
|
COM_AddCommand("addfilelocal", Command_Addfilelocal);
|
|
COM_AddCommand("addfile", Command_Addfile);
|
|
COM_AddCommand("addskins", Command_Addskins);
|
|
COM_AddCommand("listwad", Command_ListWADS_f);
|
|
COM_AddCommand("locatelump", Command_LocateLump_f);
|
|
COM_AddCommand("listmapthings", Command_ListDoomednums_f);
|
|
COM_AddCommand("listunusedsprites", Command_ListUnusedSprites_f);
|
|
COM_AddCommand("listunusedmapslots", Command_ListUnusedMapSlots_f);
|
|
|
|
COM_AddCommand("runsoc", Command_RunSOC);
|
|
COM_AddCommand("pause", Command_Pause);
|
|
COM_AddCommand("respawn", Command_Respawn);
|
|
|
|
COM_AddCommand("gametype", Command_ShowGametype_f);
|
|
COM_AddCommand("version", Command_Version_f);
|
|
#ifdef UPDATE_ALERT
|
|
COM_AddCommand("mod_details", Command_ModDetails_f);
|
|
#endif
|
|
COM_AddCommand("quit", Command_Quit_f);
|
|
|
|
COM_AddCommand("saveconfig", Command_SaveConfig_f);
|
|
COM_AddCommand("loadconfig", Command_LoadConfig_f);
|
|
COM_AddCommand("changeconfig", Command_ChangeConfig_f);
|
|
COM_AddCommand("isgamemodified", Command_Isgamemodified_f); // test
|
|
COM_AddCommand("showscores", Command_ShowScores_f);
|
|
COM_AddCommand("showtime", Command_ShowTime_f);
|
|
COM_AddCommand("cheats", Command_Cheats_f); // test
|
|
#ifdef _DEBUG
|
|
COM_AddCommand("togglemodified", Command_Togglemodified_f);
|
|
COM_AddCommand("archivetest", Command_Archivetest_f);
|
|
#endif
|
|
|
|
COM_AddCommand("replaymarker", Command_ReplayMarker);
|
|
|
|
// for master server connection
|
|
AddMServCommands();
|
|
|
|
// p_mobj.c
|
|
CV_RegisterVar(&cv_itemrespawntime);
|
|
CV_RegisterVar(&cv_itemrespawn);
|
|
CV_RegisterVar(&cv_flagtime);
|
|
CV_RegisterVar(&cv_suddendeath);
|
|
|
|
// misc
|
|
CV_RegisterVar(&cv_friendlyfire);
|
|
CV_RegisterVar(&cv_pointlimit);
|
|
CV_RegisterVar(&cv_numlaps);
|
|
CV_RegisterVar(&cv_basenumlaps);
|
|
|
|
CV_RegisterVar(&cv_autobalance);
|
|
CV_RegisterVar(&cv_teamscramble);
|
|
CV_RegisterVar(&cv_scrambleonchange);
|
|
|
|
CV_RegisterVar(&cv_touchtag);
|
|
CV_RegisterVar(&cv_hidetime);
|
|
|
|
CV_RegisterVar(&cv_inttime);
|
|
CV_RegisterVar(&cv_advancemap);
|
|
CV_RegisterVar(&cv_playersforexit);
|
|
CV_RegisterVar(&cv_timelimit);
|
|
CV_RegisterVar(&cv_playbackspeed);
|
|
CV_RegisterVar(&cv_forceskin);
|
|
CV_RegisterVar(&cv_downloading);
|
|
|
|
CV_RegisterVar(&cv_specialrings);
|
|
CV_RegisterVar(&cv_powerstones);
|
|
CV_RegisterVar(&cv_competitionboxes);
|
|
CV_RegisterVar(&cv_matchboxes);
|
|
|
|
K_RegisterServerKartStuff(); // SRB2kart
|
|
|
|
CV_RegisterVar(&cv_ringslinger);
|
|
|
|
CV_RegisterVar(&cv_startinglives);
|
|
CV_RegisterVar(&cv_countdowntime);
|
|
CV_RegisterVar(&cv_runscripts);
|
|
CV_RegisterVar(&cv_match_scoring);
|
|
CV_RegisterVar(&cv_overtime);
|
|
CV_RegisterVar(&cv_pause);
|
|
CV_RegisterVar(&cv_mute);
|
|
|
|
RegisterNetXCmd(XD_RANDOMSEED, Got_RandomSeed);
|
|
|
|
CV_RegisterVar(&cv_allowexitlevel);
|
|
CV_RegisterVar(&cv_restrictskinchange);
|
|
CV_RegisterVar(&cv_allowteamchange);
|
|
CV_RegisterVar(&cv_ingamecap);
|
|
CV_RegisterVar(&cv_spectatorreentry);
|
|
CV_RegisterVar(&cv_antigrief);
|
|
CV_RegisterVar(&cv_respawntime);
|
|
CV_RegisterVar(&cv_killingdead);
|
|
|
|
// d_clisrv
|
|
#ifdef SATURNJOIN
|
|
CV_RegisterVar(&cv_allownewsaturnplayer); // need to register it before cv_maxplayers && cv_allownewplayer
|
|
#endif
|
|
CV_RegisterVar(&cv_maxplayers);
|
|
CV_RegisterVar(&cv_resynchattempts);
|
|
#ifdef SATURNPAK
|
|
CV_RegisterVar(&cv_gamestateattempts);
|
|
CV_RegisterVar(&cv_resynchcooldown);
|
|
#endif
|
|
CV_RegisterVar(&cv_maxsend);
|
|
CV_RegisterVar(&cv_noticedownload);
|
|
CV_RegisterVar(&cv_downloadspeed);
|
|
CV_RegisterVar(&cv_connectawaittime);
|
|
CV_RegisterVar(&cv_httpsource);
|
|
CV_RegisterVar(&cv_allownewplayer);
|
|
|
|
CV_RegisterVar(&cv_joinrefusemessage);
|
|
|
|
CV_RegisterVar(&cv_chatlogsize);
|
|
|
|
CV_RegisterVar(&cv_showjoinaddress);
|
|
CV_RegisterVar(&cv_shownodeip);
|
|
CV_RegisterVar(&cv_blamecfail);
|
|
|
|
COM_AddCommand("ping", Command_Ping_f);
|
|
CV_RegisterVar(&cv_nettimeout);
|
|
CV_RegisterVar(&cv_jointimeout);
|
|
CV_RegisterVar(&cv_kicktime);
|
|
CV_RegisterVar(&cv_skipmapcheck);
|
|
CV_RegisterVar(&cv_sleep);
|
|
CV_RegisterVar(&cv_maxping);
|
|
CV_RegisterVar(&cv_pingtimeout);
|
|
CV_RegisterVar(&cv_showping);
|
|
CV_RegisterVar(&cv_pingmeasurement);
|
|
|
|
CV_RegisterVar(&cv_usefakeseed);
|
|
CV_RegisterVar(&cv_fakeseedname);
|
|
|
|
#ifdef SEENAMES
|
|
CV_RegisterVar(&cv_allowseenames);
|
|
#endif
|
|
|
|
CV_RegisterVar(&cv_dummyconsvar);
|
|
|
|
#ifdef USE_STUN
|
|
CV_RegisterVar(&cv_stunserver);
|
|
#endif
|
|
|
|
CV_RegisterVar(&cv_discordinvites);
|
|
RegisterNetXCmd(XD_DISCORD, Got_DiscordInfo);
|
|
|
|
CV_RegisterVar(&cv_recordmultiplayerdemos);
|
|
CV_RegisterVar(&cv_netdemosyncquality);
|
|
CV_RegisterVar(&cv_maxdemosize);
|
|
CV_RegisterVar(&cv_demochangemap);
|
|
}
|
|
|
|
// =========================================================================
|
|
// CLIENT STARTUP
|
|
// =========================================================================
|
|
|
|
/** Registers client commands and variables.
|
|
* Nothing needed for a dedicated server should be registered here.
|
|
*
|
|
* \sa D_RegisterServerCommands
|
|
*/
|
|
void D_RegisterClientCommands(void)
|
|
{
|
|
INT32 i;
|
|
|
|
for (i = 0; i < MAXSKINCOLORS; i++)
|
|
{
|
|
Color_cons_t[i].value = i;
|
|
Color_cons_t[i].strvalue = KartColor_Names[i]; // SRB2kart
|
|
}
|
|
Color_cons_t[MAXSKINCOLORS].value = 0;
|
|
Color_cons_t[MAXSKINCOLORS].strvalue = NULL;
|
|
|
|
if (dedicated)
|
|
return;
|
|
|
|
COM_AddCommand("numthinkers", Command_Numthinkers_f);
|
|
COM_AddCommand("countmobjs", Command_CountMobjs_f);
|
|
|
|
COM_AddCommand("changeteam", Command_Teamchange_f);
|
|
COM_AddCommand("changeteam2", Command_Teamchange2_f);
|
|
COM_AddCommand("changeteam3", Command_Teamchange3_f);
|
|
COM_AddCommand("changeteam4", Command_Teamchange4_f);
|
|
|
|
COM_AddCommand("playdemo", Command_Playdemo_f);
|
|
COM_AddCommand("timedemo", Command_Timedemo_f);
|
|
COM_AddCommand("stopdemo", Command_Stopdemo_f);
|
|
COM_AddCommand("playintro", Command_Playintro_f);
|
|
|
|
COM_AddCommand("resetcamera", Command_ResetCamera_f);
|
|
|
|
COM_AddCommand("view", Command_View_f);
|
|
COM_AddCommand("view2", Command_View_f);
|
|
COM_AddCommand("view3", Command_View_f);
|
|
COM_AddCommand("view4", Command_View_f);
|
|
|
|
COM_AddCommand("setviews", Command_SetViews_f);
|
|
|
|
COM_AddCommand("setcontrol", Command_Setcontrol_f);
|
|
COM_AddCommand("setcontrol2", Command_Setcontrol2_f);
|
|
COM_AddCommand("setcontrol3", Command_Setcontrol3_f);
|
|
COM_AddCommand("setcontrol4", Command_Setcontrol4_f);
|
|
|
|
COM_AddCommand("screenshot", M_ScreenShot);
|
|
COM_AddCommand("startmovie", Command_StartMovie_f);
|
|
COM_AddCommand("stopmovie", Command_StopMovie_f);
|
|
COM_AddCommand("minigen", M_MinimapGenerate);
|
|
|
|
COM_AddCommand("localskin", Command_GLocalSkin);
|
|
|
|
K_RegisterClientKartStuff(); // SRB2kart
|
|
|
|
CV_RegisterVar(&cv_screenshot_option);
|
|
CV_RegisterVar(&cv_screenshot_folder);
|
|
CV_RegisterVar(&cv_moviemode);
|
|
CV_RegisterVar(&cv_movie_option);
|
|
CV_RegisterVar(&cv_movie_folder);
|
|
// PNG variables
|
|
CV_RegisterVar(&cv_zlib_level);
|
|
CV_RegisterVar(&cv_zlib_memory);
|
|
CV_RegisterVar(&cv_zlib_strategy);
|
|
CV_RegisterVar(&cv_zlib_window_bits);
|
|
// GIF variables
|
|
CV_RegisterVar(&cv_gif_optimize);
|
|
CV_RegisterVar(&cv_gif_downscale);
|
|
CV_RegisterVar(&cv_gif_dynamicdelay);
|
|
|
|
#ifdef WALLSPLATS
|
|
CV_RegisterVar(&cv_splats);
|
|
#endif
|
|
|
|
// register these so it is saved to config
|
|
CV_RegisterVar(&cv_playername);
|
|
CV_RegisterVar(&cv_playercolor);
|
|
CV_RegisterVar(&cv_skin); // r_things.c (skin NAME)
|
|
CV_RegisterVar(&cv_localskin);
|
|
// secondary player (splitscreen)
|
|
CV_RegisterVar(&cv_playername2);
|
|
CV_RegisterVar(&cv_playercolor2);
|
|
CV_RegisterVar(&cv_skin2);
|
|
// third player
|
|
CV_RegisterVar(&cv_playername3);
|
|
CV_RegisterVar(&cv_playercolor3);
|
|
CV_RegisterVar(&cv_skin3);
|
|
// fourth player
|
|
CV_RegisterVar(&cv_playername4);
|
|
CV_RegisterVar(&cv_playercolor4);
|
|
CV_RegisterVar(&cv_skin4);
|
|
// preferred number of players
|
|
CV_RegisterVar(&cv_splitplayers);
|
|
|
|
CV_RegisterVar(&cv_skinselectmenu);
|
|
CV_RegisterVar(&cv_skinselectgridsort);
|
|
|
|
#ifdef SEENAMES
|
|
CV_RegisterVar(&cv_seenames);
|
|
#endif
|
|
|
|
CV_RegisterVar(&cv_rollingdemos);
|
|
|
|
CV_RegisterVar(&cv_netstat);
|
|
CV_RegisterVar(&cv_netticbuffer);
|
|
CV_RegisterVar(&cv_mindelay);
|
|
CV_RegisterVar(&cv_gentlemens);
|
|
CV_RegisterVar(&cv_serverinfoscreen);
|
|
CV_RegisterVar(&cv_serveraddoninfo);
|
|
|
|
#ifdef NETGAME_DEVMODE
|
|
CV_RegisterVar(&cv_fishcake);
|
|
#endif
|
|
|
|
// HUD
|
|
CV_RegisterVar(&cv_timetic);
|
|
CV_RegisterVar(&cv_itemfinder);
|
|
|
|
CV_RegisterVar(&cv_pingicon);
|
|
CV_RegisterVar(&cv_pingstyle);
|
|
|
|
CV_RegisterVar(&cv_cechotoggle);
|
|
|
|
CV_RegisterVar(&cv_keyboardlayout);
|
|
|
|
CV_RegisterVar(&cv_emotes);
|
|
|
|
// time attack ghost options are also saved to config
|
|
CV_RegisterVar(&cv_ghost_besttime);
|
|
CV_RegisterVar(&cv_ghost_bestlap);
|
|
CV_RegisterVar(&cv_ghost_last);
|
|
CV_RegisterVar(&cv_ghost_guest);
|
|
CV_RegisterVar(&cv_ghost_staff);
|
|
|
|
COM_AddCommand("displayplayer", Command_Displayplayer_f);
|
|
|
|
CV_RegisterVar(&cv_palette);
|
|
CV_RegisterVar(&cv_palettenum);
|
|
|
|
// m_menu.c
|
|
CV_RegisterVar(&cv_chatheight);
|
|
CV_RegisterVar(&cv_chatwidth);
|
|
CV_RegisterVar(&cv_chattime);
|
|
CV_RegisterVar(&cv_chatspamprotection);
|
|
CV_RegisterVar(&cv_consolechat);
|
|
CV_RegisterVar(&cv_chatnotifications);
|
|
CV_RegisterVar(&cv_chat_xoffset);
|
|
CV_RegisterVar(&cv_chat_yoffset);
|
|
CV_RegisterVar(&cv_chat_showlimit);
|
|
CV_RegisterVar(&cv_chatbacktint);
|
|
CV_RegisterVar(&cv_chatcentertext);
|
|
CV_RegisterVar(&cv_songcredits);
|
|
CV_RegisterVar(&cv_pausesongcredits);
|
|
CV_RegisterVar(&cv_showfreeplay);
|
|
CV_RegisterVar(&cv_skinselectspin);
|
|
CV_RegisterVar(&cv_showallmaps);
|
|
|
|
CV_RegisterVar(&cv_growmusic);
|
|
CV_RegisterVar(&cv_supermusic);
|
|
|
|
CV_RegisterVar(&cv_showtrackaddon);
|
|
CV_RegisterVar(&cv_showviewpointtext);
|
|
CV_RegisterVar(&cv_showdownloadprompt);
|
|
|
|
CV_RegisterVar(&cv_director);
|
|
CV_RegisterVar(&cv_kartdebugdirector);
|
|
CV_RegisterVar(&cv_showdirectorhud);
|
|
|
|
CV_RegisterVar(&cv_luaimmersion);
|
|
CV_RegisterVar(&cv_fakelocalskin);
|
|
|
|
CV_RegisterVar(&cv_showlocalskinmenus);
|
|
|
|
CV_RegisterVar(&cv_replaysearchrate);
|
|
CV_RegisterVar(&cv_showfocuslost);
|
|
CV_RegisterVar(&cv_pauseifunfocused);
|
|
|
|
// filesrch.c
|
|
CV_RegisterVar(&cv_addons_option);
|
|
CV_RegisterVar(&cv_addons_folder);
|
|
CV_RegisterVar(&cv_addons_md5);
|
|
CV_RegisterVar(&cv_addons_showall);
|
|
CV_RegisterVar(&cv_addons_search_type);
|
|
CV_RegisterVar(&cv_addons_search_case);
|
|
|
|
// g_input.c
|
|
CV_RegisterVar(&cv_controlperkey);
|
|
|
|
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
|
|
{
|
|
CV_RegisterVar(&cv_turnsmooth[i]);
|
|
|
|
CV_RegisterVar(&cv_turnaxis[i]);
|
|
CV_RegisterVar(&cv_moveaxis[i]);
|
|
CV_RegisterVar(&cv_camturnaxis[i]);
|
|
CV_RegisterVar(&cv_camstrafeaxis[i]);
|
|
CV_RegisterVar(&cv_brakeaxis[i]);
|
|
CV_RegisterVar(&cv_aimaxis[i]);
|
|
CV_RegisterVar(&cv_lookaxis[i]);
|
|
CV_RegisterVar(&cv_fireaxis[i]);
|
|
CV_RegisterVar(&cv_driftaxis[i]);
|
|
CV_RegisterVar(&cv_lookbackaxis[i]);
|
|
CV_RegisterVar(&cv_custom1axis[i]);
|
|
CV_RegisterVar(&cv_custom2axis[i]);
|
|
CV_RegisterVar(&cv_custom3axis[i]);
|
|
CV_RegisterVar(&cv_xdeadzone[i]);
|
|
CV_RegisterVar(&cv_ydeadzone[i]);
|
|
|
|
CV_RegisterVar(&cv_litesteer[i]);
|
|
|
|
CV_RegisterVar(&cv_autoaccel[i]);
|
|
|
|
CV_RegisterVar(&cv_usejoystick[i]);
|
|
CV_RegisterVar(&cv_joyscale[i]);
|
|
CV_RegisterVar(&cv_gamepadled[i]);
|
|
|
|
CV_RegisterVar(&cv_rumble[i]);
|
|
CV_RegisterVar(&cv_rumble_strength[i]);
|
|
CV_RegisterVar(&cv_rumble_spinout[i]);
|
|
CV_RegisterVar(&cv_rumble_sneakerboost[i]);
|
|
CV_RegisterVar(&cv_rumble_offroad[i]);
|
|
CV_RegisterVar(&cv_rumble_bananadrag[i]);
|
|
CV_RegisterVar(&cv_rumble_stairjank[i]);
|
|
CV_RegisterVar(&cv_rumble_brakedrift[i]);
|
|
CV_RegisterVar(&cv_rumble_driftcharge[i]);
|
|
}
|
|
|
|
CV_RegisterVar(&cv_gamepadifunfocused);
|
|
CV_RegisterVar(&cv_gamepadledifunfocused);
|
|
CV_RegisterVar(&cv_rumbleifunfocused);
|
|
|
|
CV_RegisterVar(&cv_usemouse);
|
|
CV_RegisterVar(&cv_invertmouse);
|
|
CV_RegisterVar(&cv_mousexsens);
|
|
CV_RegisterVar(&cv_mouseysens);
|
|
CV_RegisterVar(&cv_mousevisible);
|
|
|
|
// s_sound.c
|
|
CV_RegisterVar(&cv_soundvolume);
|
|
CV_RegisterVar(&cv_digmusicvolume);
|
|
#ifndef NO_MIDI
|
|
CV_RegisterVar(&cv_midimusicvolume);
|
|
#endif
|
|
CV_RegisterVar(&cv_numChannels);
|
|
CV_RegisterVar(&cv_audbuffersize);
|
|
|
|
#ifdef HAVE_OPENMPT
|
|
CV_RegisterVar(&cv_modfilter);
|
|
CV_RegisterVar(&cv_stereosep);
|
|
CV_RegisterVar(&cv_amigafilter);
|
|
#if OPENMPT_API_VERSION_MAJOR < 1 && OPENMPT_API_VERSION_MINOR > 4
|
|
CV_RegisterVar(&cv_amigatype);
|
|
#endif
|
|
#endif
|
|
|
|
// screen.c
|
|
CV_RegisterVar(&cv_fullscreen);
|
|
CV_RegisterVar(&cv_renderview);
|
|
CV_RegisterVar(&cv_frameskip);
|
|
CV_RegisterVar(&cv_vhseffect);
|
|
CV_RegisterVar(&cv_shittyscreen);
|
|
CV_RegisterVar(&cv_scr_width);
|
|
CV_RegisterVar(&cv_scr_height);
|
|
|
|
CV_RegisterVar(&cv_parallelsoftware);
|
|
CV_RegisterVar(&cv_paralleldrawmasked);
|
|
|
|
CV_RegisterVar(&cv_soundtest);
|
|
|
|
CV_RegisterVar(&cv_perfstats);
|
|
CV_RegisterVar(&cv_ps_thinkframe_page);
|
|
CV_RegisterVar(&cv_ps_samplesize);
|
|
CV_RegisterVar(&cv_ps_descriptor);
|
|
|
|
//Value used to store last server player has joined
|
|
CV_RegisterVar(&cv_lastserver);
|
|
|
|
CV_RegisterVar(&cv_showmusicfilename);
|
|
|
|
CV_RegisterVar(&cv_betainterscreen);
|
|
|
|
CV_RegisterVar(&cv_laglesscam);
|
|
CV_RegisterVar(&cv_demoangturn);
|
|
|
|
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
|
|
{
|
|
CV_RegisterVar(&cv_verticallook[i]);
|
|
}
|
|
|
|
CV_RegisterVar(&cv_resyncdemo);
|
|
CV_RegisterVar(&cv_demodateformat);
|
|
|
|
CV_RegisterVar(&cv_showspecstuff);
|
|
|
|
// ingame object placing
|
|
COM_AddCommand("objectplace", Command_ObjectPlace_f);
|
|
COM_AddCommand("writethings", Command_Writethings_f);
|
|
CV_RegisterVar(&cv_speed);
|
|
CV_RegisterVar(&cv_opflags);
|
|
CV_RegisterVar(&cv_mapthingnum);
|
|
|
|
// add cheat commands
|
|
COM_AddCommand("noclip", Command_CheatNoClip_f);
|
|
COM_AddCommand("god", Command_CheatGod_f);
|
|
COM_AddCommand("notarget", Command_CheatNoTarget_f);
|
|
COM_AddCommand("devmode", Command_Devmode_f);
|
|
COM_AddCommand("savecheckpoint", Command_Savecheckpoint_f);
|
|
COM_AddCommand("scale", Command_Scale_f);
|
|
COM_AddCommand("gravflip", Command_Gravflip_f);
|
|
COM_AddCommand("hurtme", Command_Hurtme_f);
|
|
COM_AddCommand("teleport", Command_Teleport_f);
|
|
COM_AddCommand("rteleport", Command_RTeleport_f);
|
|
COM_AddCommand("skynum", Command_Skynum_f);
|
|
COM_AddCommand("weather", Command_Weather_f);
|
|
#ifdef _DEBUG
|
|
COM_AddCommand("causecfail", Command_CauseCfail_f);
|
|
#endif
|
|
#if defined(LUA_ALLOW_BYTECODE)
|
|
COM_AddCommand("dumplua", Command_Dumplua_f);
|
|
#endif
|
|
|
|
#ifdef HAVE_DISCORDRPC
|
|
CV_RegisterVar(&cv_discordrp);
|
|
CV_RegisterVar(&cv_discordstreamer);
|
|
CV_RegisterVar(&cv_discordasks);
|
|
#endif
|
|
|
|
COM_AddCommand("listskins", Command_ListSkins);
|
|
COM_AddCommand("skinsearch", Command_SkinSearch);
|
|
}
|
|
|
|
/**
|
|
* Searches the skins list for a skin of the "realname", if found, changes
|
|
* the CVAR value to be the "name" of the skin.
|
|
* \sa cv_skin, Skin_OnChange, Skin2_OnChange
|
|
* \author karen
|
|
*/
|
|
static void Skin_FindRealNameSkin(consvar_t *cvar)
|
|
{
|
|
INT32 matches[MAXSKINS] = {0}; // Skin numbers of matching skins
|
|
INT32 matches_pos[MAXSKINS] = {0}; // Index of matching substring
|
|
INT32 nummatches = 0;
|
|
|
|
INT32 i;
|
|
const char *value = cvar->string;
|
|
|
|
for (i = 0; i < numskins; i++)
|
|
{
|
|
// Perfect match with some skin name, no need to find matching realname
|
|
if (strncasecmp(value, skins[i].name, sizeof skins[i].name) == 0)
|
|
return;
|
|
|
|
const char *match = strcasestr(skins[i].realname, value);
|
|
if (match != NULL)
|
|
{
|
|
matches[nummatches] = i;
|
|
matches_pos[nummatches] = match - skins[i].realname;
|
|
++nummatches;
|
|
}
|
|
}
|
|
|
|
if (nummatches == 1)
|
|
{
|
|
// Change the cvar to be the value of the name.
|
|
CV_StealthSet(cvar, skins[matches[0]].name);
|
|
}
|
|
else if (nummatches > 1)
|
|
{
|
|
size_t query_length = strlen(value);
|
|
|
|
char start[SKINNAMESIZE+1] = {0}; // Start of name, printed with normal color
|
|
char match[SKINNAMESIZE+1] = {0}; // Matching part of name, highlighted in red
|
|
const char *end = NULL; // End of the name, printed with normal color. Can be just a pointer into realname part
|
|
|
|
CONS_Printf("\x86Multiple matches found:\n");
|
|
|
|
for (i = 0; i < nummatches; ++i)
|
|
{
|
|
const char *realname = skins[matches[i]].realname;
|
|
|
|
if (matches_pos[i] == 0)
|
|
start[0] = 0;
|
|
else
|
|
{
|
|
memcpy(start, realname, matches_pos[i]);
|
|
start[matches_pos[i]] = 0;
|
|
}
|
|
|
|
memcpy(match, realname+matches_pos[i], query_length);
|
|
match[matches_pos[i] + query_length] = 0;
|
|
|
|
end = realname + matches_pos[i] + query_length;
|
|
|
|
CONS_Printf("%s\x85%s\x80%s (%s)\n", start, match, end, skins[matches[i]].name);
|
|
}
|
|
|
|
CONS_Printf("\x86Try be more specific\n");
|
|
}
|
|
else
|
|
{
|
|
CONS_Printf("\x86Skin not found\n");
|
|
}
|
|
}
|
|
|
|
|
|
/** Checks if a name (as received from another player) is okay.
|
|
* A name is okay if it is no fewer than 1 and no more than ::MAXPLAYERNAME
|
|
* chars long (not including NUL), it does not begin or end with a space,
|
|
* it does not contain non-printing characters (according to isprint(), which
|
|
* allows space), it does not start with a digit, and no other player is
|
|
* currently using it.
|
|
* \param name Name to check.
|
|
* \param playernum Player who wants the name, so we can check if they already
|
|
* have it, and let them keep it if so.
|
|
* \sa CleanupPlayerName, SetPlayerName, Got_NameAndColor
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
|
|
static boolean AllowedPlayerNameChar(char ch)
|
|
{
|
|
if (!isprint(ch) || ch == ';' || ch == '"' || (UINT8)(ch) >= 0x80)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static boolean EnsurePlayerNameIsGood(char *name, INT32 playernum)
|
|
{
|
|
size_t ix, len = strlen(name);
|
|
|
|
if (len == 0 || len > MAXPLAYERNAME)
|
|
return false; // Empty or too long.
|
|
if (name[0] == ' ')
|
|
return false; // Starts with a space.
|
|
if (isdigit(name[0]))
|
|
return false; // Starts with a digit.
|
|
if (name[0] == '@' || name[0] == '~')
|
|
return false; // Starts with an admin symbol.
|
|
|
|
// Clean up trailing whitespace.
|
|
while (len && name[len-1] == ' ')
|
|
{
|
|
name[len-1] = '\0';
|
|
len--;
|
|
}
|
|
if (len == 0)
|
|
return false;
|
|
|
|
// Check if it contains a non-printing character.
|
|
// Note: ANSI C isprint() considers space a printing character.
|
|
// Also don't allow semicolons, since they are used as
|
|
// console command separators.
|
|
|
|
// Also, anything over 0x80 is disallowed too, since compilers love to
|
|
// differ on whether they're printable characters or not.
|
|
for (ix = 0; ix < len; ix++)
|
|
if (!AllowedPlayerNameChar(name[ix]))
|
|
return false;
|
|
|
|
// Check if a player is currently using the name, case-insensitively.
|
|
for (ix = 0; ix < MAXPLAYERS; ix++)
|
|
{
|
|
if (ix != (size_t)playernum && playeringame[ix]
|
|
&& fasticmp(name, player_names[ix]))
|
|
{
|
|
// We shouldn't kick people out just because
|
|
// they joined the game with the same name
|
|
// as someone else -- modify the name instead.
|
|
|
|
// Recursion!
|
|
// Slowly strip characters off the end of the
|
|
// name until we no longer have a duplicate.
|
|
if (len > 1)
|
|
{
|
|
name[len-1] = '\0';
|
|
if (!EnsurePlayerNameIsGood(name, playernum))
|
|
return false;
|
|
}
|
|
else if (len == 1) // Agh!
|
|
{
|
|
// Last ditch effort...
|
|
sprintf(name, "%d", M_RandomKey(10));
|
|
if (!EnsurePlayerNameIsGood(name, playernum))
|
|
return false;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Cleans up a local player's name before sending a name change.
|
|
* Spaces at the beginning or end of the name are removed. Then if the new
|
|
* name is identical to another player's name, ignoring case, the name change
|
|
* is canceled, and the name in cv_playername.value or cv_playername2.value
|
|
* is restored to what it was before.
|
|
*
|
|
* We assume that if playernum is ::consoleplayer or ::secondarydisplayplayer
|
|
* (unless clientjoin is true, a necessary evil)
|
|
* the console variable ::cv_playername to ::cv_playername4 respectively is
|
|
* already set to newname. However, the player name table is assumed to
|
|
* contain the old name.
|
|
*
|
|
* \param playernum Player number who has requested a name change.
|
|
* Should be ::consoleplayer or ::secondarydisplayplayer.
|
|
* \param newname New name for that player; should already be in
|
|
* ::cv_playername or ::cv_playername2 if player is the
|
|
* console or secondary display player, respectively.
|
|
* \sa cv_playername, cv_playername2, SendNameAndColor, SendNameAndColor2,
|
|
* SetPlayerName
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void CleanupPlayerName(INT32 playernum, const char *newname)
|
|
{
|
|
char *buf;
|
|
char *p;
|
|
char *tmpname = NULL;
|
|
INT32 i;
|
|
boolean namefailed = true;
|
|
boolean clientjoin = !!(playernum >= MAXPLAYERS);
|
|
|
|
if (clientjoin)
|
|
playernum -= MAXPLAYERS;
|
|
|
|
buf = Z_StrDup(newname);
|
|
|
|
do
|
|
{
|
|
p = buf;
|
|
|
|
while (*p == ' ')
|
|
p++; // remove leading spaces
|
|
|
|
if (strlen(p) == 0)
|
|
break; // empty names not allowed
|
|
|
|
if (isdigit(*p))
|
|
break; // names starting with digits not allowed
|
|
|
|
if (*p == '@' || *p == '~')
|
|
break; // names that start with @ or ~ (admin symbols) not allowed
|
|
|
|
tmpname = p;
|
|
|
|
do
|
|
{
|
|
if (!AllowedPlayerNameChar(*p))
|
|
break;
|
|
}
|
|
while (*++p) ;
|
|
|
|
if (*p)/* bad char found */
|
|
break;
|
|
|
|
// Remove trailing spaces.
|
|
p = &tmpname[strlen(tmpname)-1]; // last character
|
|
while (*p == ' ' && p >= tmpname)
|
|
{
|
|
*p = '\0';
|
|
p--;
|
|
}
|
|
|
|
if (strlen(tmpname) == 0)
|
|
break; // another case of an empty name
|
|
|
|
// Truncate name if it's too long (max MAXPLAYERNAME chars
|
|
// excluding NUL).
|
|
if (strlen(tmpname) > MAXPLAYERNAME)
|
|
tmpname[MAXPLAYERNAME] = '\0';
|
|
|
|
// Remove trailing spaces again.
|
|
p = &tmpname[strlen(tmpname)-1]; // last character
|
|
while (*p == ' ' && p >= tmpname)
|
|
{
|
|
*p = '\0';
|
|
p--;
|
|
}
|
|
|
|
// no stealing another player's name
|
|
if (!clientjoin)
|
|
{
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (i != playernum && playeringame[i]
|
|
&& fasticmp(tmpname, player_names[i]))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i < MAXPLAYERS)
|
|
break;
|
|
}
|
|
|
|
// name is okay then
|
|
namefailed = false;
|
|
} while (0);
|
|
|
|
if (namefailed)
|
|
tmpname = player_names[playernum];
|
|
|
|
// set consvars whether namefailed or not, because even if it succeeded,
|
|
// spaces may have been removed
|
|
if (clientjoin)
|
|
{
|
|
consvar_t *namevars[] = {&cv_playername, &cv_playername2, &cv_playername3, &cv_playername4};
|
|
CV_StealthSet(namevars[playernum], tmpname);
|
|
}
|
|
else
|
|
{
|
|
if (playernum == consoleplayer)
|
|
CV_StealthSet(&cv_playername, tmpname);
|
|
else if (playernum == displayplayers[1] || (!netgame && playernum == 1))
|
|
CV_StealthSet(&cv_playername2, tmpname);
|
|
else if (playernum == displayplayers[2] || (!netgame && playernum == 2))
|
|
CV_StealthSet(&cv_playername3, tmpname);
|
|
else if (playernum == displayplayers[3] || (!netgame && playernum == 3))
|
|
CV_StealthSet(&cv_playername4, tmpname);
|
|
else I_Assert(((void)"CleanupPlayerName used on non-local player", 0));
|
|
}
|
|
|
|
Z_Free(buf);
|
|
}
|
|
|
|
/** Sets a player's name, if it is good.
|
|
* If the name is not good (indicating a modified or buggy client), it is not
|
|
* set, and if we are the server in a netgame, the player responsible is
|
|
* kicked with a consistency failure.
|
|
*
|
|
* This function prints a message indicating the name change, unless the game
|
|
* is currently showing the intro, e.g. when processing autoexec.cfg.
|
|
*
|
|
* \param playernum Player number who has requested a name change.
|
|
* \param newname New name for that player. Should be good, but won't
|
|
* necessarily be if the client is maliciously modified or
|
|
* buggy.
|
|
* \sa CleanupPlayerName, EnsurePlayerNameIsGood
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void SetPlayerName(INT32 playernum, char *newname)
|
|
{
|
|
if (EnsurePlayerNameIsGood(newname, playernum))
|
|
{
|
|
if (!fasticmp(newname, player_names[playernum]))
|
|
{
|
|
if (netgame)
|
|
HU_AddChatText(va("\x82*%s renamed to %s", player_names[playernum], newname), false);
|
|
|
|
player_name_changes[playernum]++;
|
|
|
|
strcpy(player_names[playernum], newname);
|
|
demo_extradata[playernum] |= DXD_NAME;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CONS_Printf(M_GetText("Player %d sent a bad name change\n"), playernum+1);
|
|
|
|
if (server && netgame)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
}
|
|
}
|
|
|
|
UINT8 CanChangeSkin(INT32 playernum)
|
|
{
|
|
// Of course we can change if we're not playing
|
|
if (!Playing() || !addedtogame)
|
|
return true;
|
|
|
|
// Force skin in effect.
|
|
if (client && (cv_forceskin.value != -1) && !(IsPlayerAdmin(playernum) && serverplayer == -1))
|
|
return false;
|
|
|
|
// Can change skin in intermission and whatnot.
|
|
if (gamestate != GS_LEVEL)
|
|
return true;
|
|
|
|
// Server has skin change restrictions.
|
|
if (cv_restrictskinchange.value)
|
|
{
|
|
if (gametype == GT_COOP)
|
|
return true;
|
|
|
|
// Can change skin during initial countdown.
|
|
if (leveltime < starttime)
|
|
return true;
|
|
|
|
if (G_TagGametype())
|
|
{
|
|
// Can change skin during hidetime.
|
|
if (leveltime < hidetime * TICRATE)
|
|
return true;
|
|
|
|
// IT players can always change skins to persue players hiding in character only locations.
|
|
if (players[playernum].pflags & PF_TAGIT)
|
|
return true;
|
|
}
|
|
|
|
if (players[playernum].spectator ||
|
|
players[playernum].playerstate == PST_DEAD ||
|
|
players[playernum].playerstate == PST_REBORN)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void ForceAllSkins(INT32 forcedskin)
|
|
{
|
|
INT32 i;
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
|
{
|
|
if (!playeringame[i])
|
|
continue;
|
|
|
|
SetPlayerSkinByNum(i, forcedskin);
|
|
|
|
// If it's me (or my brother (or my sister (or my trusty pet dog))), set appropriate skin value in cv_skin
|
|
if (!dedicated) // But don't do this for dedicated servers, of course.
|
|
{
|
|
if (i == consoleplayer)
|
|
CV_StealthSet(&cv_skin, skins[forcedskin].name);
|
|
else if (i == displayplayers[1])
|
|
CV_StealthSet(&cv_skin2, skins[forcedskin].name);
|
|
else if (i == displayplayers[2])
|
|
CV_StealthSet(&cv_skin3, skins[forcedskin].name);
|
|
else if (i == displayplayers[3])
|
|
CV_StealthSet(&cv_skin4, skins[forcedskin].name);
|
|
}
|
|
}
|
|
}
|
|
|
|
static INT32 chmappending;
|
|
#ifdef PARANOIA
|
|
static INT32 snacpending[MAXSPLITSCREENPLAYERS];
|
|
#endif
|
|
|
|
// wonky compat, idk if anything uses this....
|
|
#define playerisbot (splitplayer == 1 && botingame)
|
|
|
|
// name, color, or skin has changed
|
|
//
|
|
static void SendNameAndColor(UINT8 splitplayer)
|
|
{
|
|
char buf[MAXPLAYERNAME+2];
|
|
char *p;
|
|
|
|
p = buf;
|
|
|
|
// splitscreen
|
|
if (splitscreen < splitplayer && !playerisbot)
|
|
return;
|
|
|
|
const INT32 pnum = P_GetLocalPlayerNumForNum(splitplayer);
|
|
|
|
player_t *player = &players[pnum];
|
|
|
|
// TODO: make those cvars arrays
|
|
consvar_t *playercolor, *playername, *playerskin;
|
|
consvar_t *colorvars[] = {&cv_playercolor, &cv_playercolor2, &cv_playercolor3, &cv_playercolor4};
|
|
consvar_t *namevars[] = {&cv_playername, &cv_playername2, &cv_playername3, &cv_playername4};
|
|
consvar_t *skinvars[] = {&cv_skin, &cv_skin2, &cv_skin3, &cv_skin4};
|
|
|
|
playercolor = colorvars[splitplayer];
|
|
playername = namevars[splitplayer];
|
|
playerskin = skinvars[splitplayer];
|
|
|
|
// normal player colors
|
|
if (G_GametypeHasTeams())
|
|
{
|
|
if (player->ctfteam == 1 && playercolor->value != skincolor_redteam)
|
|
CV_StealthSetValue(playercolor, skincolor_redteam);
|
|
else if (player->ctfteam == 2 && playercolor->value != skincolor_blueteam)
|
|
CV_StealthSetValue(playercolor, skincolor_blueteam);
|
|
}
|
|
|
|
// never allow the color "none"
|
|
if (!playercolor->value)
|
|
{
|
|
if (player->skincolor)
|
|
CV_StealthSetValue(playercolor, player->skincolor);
|
|
else if (skins[player->skin].prefcolor)
|
|
CV_StealthSetValue(playercolor, skins[player->skin].prefcolor);
|
|
else
|
|
CV_StealthSet(playercolor, playercolor->defaultvalue);
|
|
}
|
|
|
|
// We'll handle it later if we're not playing.
|
|
if (!Playing())
|
|
return;
|
|
|
|
if (fastcmp(playername->string, player_names[pnum])
|
|
&& playercolor->value == player->skincolor
|
|
&& fastcmp(playerskin->string, skins[player->skin].name))
|
|
return;
|
|
|
|
if (playerisbot)
|
|
{
|
|
players[pnum].skincolor = botcolor;
|
|
if (players[pnum].mo)
|
|
players[pnum].mo->color = players[pnum].skincolor;
|
|
SetPlayerSkinByNum(pnum, botskin-1);
|
|
return;
|
|
}
|
|
|
|
// If you're not in a netgame, merely update the skin, color, and name.
|
|
if (!netgame)
|
|
{
|
|
INT32 foundskin;
|
|
|
|
CleanupPlayerName(pnum, playername->zstring);
|
|
strcpy(player_names[pnum], playername->zstring);
|
|
|
|
player->skincolor = playercolor->value;
|
|
|
|
if (player->mo)
|
|
player->mo->color = player->skincolor;
|
|
|
|
if ((foundskin = R_SkinAvailable(playerskin->string)) != -1)
|
|
{
|
|
playerskin->value = foundskin;
|
|
SetPlayerSkin(pnum, playerskin->string);
|
|
CV_StealthSet(playerskin, skins[playerskin->value].name);
|
|
}
|
|
else
|
|
{
|
|
playerskin->value = player->skin;
|
|
CV_StealthSet(playerskin, skins[player->skin].name);
|
|
// will always be same as current
|
|
SetPlayerSkin(pnum, playerskin->string);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#ifdef PARANOIA
|
|
snacpending[splitplayer]++;
|
|
#endif
|
|
|
|
// Don't change name if muted
|
|
if (player_name_changes[pnum] >= MAXNAMECHANGES)
|
|
{
|
|
CV_StealthSet(playername, player_names[pnum]);
|
|
HU_AddChatText("\x85*You must wait to change your name again", false);
|
|
}
|
|
else if (cv_mute.value && !(server || IsPlayerAdmin(pnum)))
|
|
CV_StealthSet(playername, player_names[pnum]);
|
|
else // Cleanup name if changing it
|
|
CleanupPlayerName(pnum, playername->zstring);
|
|
|
|
// Don't change skin if the server doesn't want you to.
|
|
if (!CanChangeSkin(pnum))
|
|
CV_StealthSet(playerskin, skins[player->skin].name);
|
|
|
|
// check if player has the skin loaded (cv_skin may have
|
|
// the name of a skin that was available in the previous game)
|
|
playerskin->value = R_SkinAvailable(playerskin->string);
|
|
if (playerskin->value < 0)
|
|
{
|
|
INT32 skinnum = player->skin;
|
|
CV_StealthSet(playerskin, skins[skinnum].name);
|
|
playerskin->value = skinnum;
|
|
}
|
|
|
|
// Finally write out the complete packet and send it off.
|
|
WRITESTRINGN(p, playername->zstring, MAXPLAYERNAME);
|
|
WRITEUINT8(p, (UINT8)playercolor->value);
|
|
WRITEUINT8(p, (UINT8)playerskin->value);
|
|
SendNetXCmdForPlayer(splitplayer, XD_NAMEANDCOLOR, buf, p - buf);
|
|
}
|
|
|
|
static void Got_NameAndColor(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
player_t *player;
|
|
char name[MAXPLAYERNAME+1] = {};
|
|
UINT8 color, skin;
|
|
|
|
#ifdef PARANOIA
|
|
if (playernum < 0 || playernum > MAXPLAYERS)
|
|
I_Error("There is no player %d!", playernum);
|
|
#endif
|
|
|
|
player = &players[playernum];
|
|
|
|
#ifdef PARANOIA
|
|
if (P_IsLocalPlayer(player))
|
|
{
|
|
const INT32 localpnum = P_GetLocalPlayerNumForPlayer(player);
|
|
snacpending[localpnum]--;
|
|
|
|
if (snacpending[localpnum] < 0)
|
|
I_Error("snacpending negative for player %d!", localpnum);
|
|
}
|
|
#endif
|
|
|
|
READSTRINGN(*cp, name, MAXPLAYERNAME);
|
|
color = READUINT8(*cp);
|
|
skin = READUINT8(*cp);
|
|
|
|
// set name
|
|
if (player_name_changes[playernum] < MAXNAMECHANGES)
|
|
{
|
|
if (!fasticmp(player_names[playernum], name))
|
|
SetPlayerName(playernum, name);
|
|
}
|
|
|
|
// set color
|
|
player->skincolor = color % MAXSKINCOLORS;
|
|
if (player->mo)
|
|
player->mo->color = (UINT8)player->skincolor;
|
|
demo_extradata[playernum] |= DXD_COLOR;
|
|
|
|
// normal player colors
|
|
if (server && !P_IsLocalPlayer(player))
|
|
{
|
|
boolean kick = false;
|
|
|
|
// team colors
|
|
if (G_GametypeHasTeams())
|
|
{
|
|
if (player->ctfteam == 1 && player->skincolor != skincolor_redteam)
|
|
kick = true;
|
|
else if (player->ctfteam == 2 && player->skincolor != skincolor_blueteam)
|
|
kick = true;
|
|
}
|
|
|
|
// don't allow color "none"
|
|
if (!player->skincolor)
|
|
kick = true;
|
|
|
|
if (kick)
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s (team: %d), color: %d)\n"), player_names[playernum], player->ctfteam, player->skincolor);
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// set skin
|
|
if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // Server wants everyone to use the same player
|
|
{
|
|
const INT32 forcedskin = cv_forceskin.value;
|
|
SetPlayerSkinByNum(playernum, forcedskin);
|
|
|
|
// TODO: make cv_skin an array
|
|
if (playernum == consoleplayer)
|
|
CV_StealthSet(&cv_skin, skins[forcedskin].name);
|
|
else if (playernum == displayplayers[1])
|
|
CV_StealthSet(&cv_skin2, skins[forcedskin].name);
|
|
else if (playernum == displayplayers[2])
|
|
CV_StealthSet(&cv_skin3, skins[forcedskin].name);
|
|
else if (playernum == displayplayers[3])
|
|
CV_StealthSet(&cv_skin4, skins[forcedskin].name);
|
|
}
|
|
else
|
|
SetPlayerSkinByNum(playernum, skin);
|
|
}
|
|
|
|
void SendWeaponPref(UINT8 splitplayer)
|
|
{
|
|
UINT8 buf[1];
|
|
|
|
buf[0] = 0;
|
|
|
|
if (cv_flipcam[splitplayer].value)
|
|
buf[0] |= 1;
|
|
|
|
SendNetXCmdForPlayer(splitplayer, XD_WEAPONPREF, buf, 1);
|
|
}
|
|
|
|
static void Got_WeaponPref(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
player_t *player = &players[playernum];
|
|
|
|
UINT8 prefs = READUINT8(*cp);
|
|
|
|
player->pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE);
|
|
if (prefs & 1)
|
|
player->pflags |= PF_FLIPCAM;
|
|
if (prefs & 2)
|
|
player->pflags |= PF_ANALOGMODE;
|
|
}
|
|
|
|
void D_SendPlayerConfig(UINT8 splitplayer)
|
|
{
|
|
SendNameAndColor(splitplayer);
|
|
SendWeaponPref(splitplayer);
|
|
}
|
|
|
|
// Only works for displayplayer, sorry!
|
|
static void Command_ResetCamera_f(void)
|
|
{
|
|
P_ResetCamera(&players[displayplayers[0]], &camera[0]);
|
|
}
|
|
|
|
/* Consider replacing nametonum with this */
|
|
INT32 D_LookupPlayer(const char *s)
|
|
{
|
|
INT32 playernum;
|
|
|
|
if (*s == '0')/* clever way to bypass atoi */
|
|
return 0;
|
|
|
|
if ((playernum = atoi(s)))
|
|
{
|
|
playernum = max(min(playernum, MAXPLAYERS-1), 0); /* not out of range */
|
|
return playernum;
|
|
}
|
|
|
|
const size_t len = strlen(s);
|
|
|
|
for (playernum = 0; playernum < MAXPLAYERS; ++playernum)
|
|
{
|
|
/* Match name case-insensitively: fully, or partially the start. */
|
|
if (playeringame[playernum] && (strnicmp(player_names[playernum], s, len) == 0))
|
|
{
|
|
return playernum;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static INT32 FindPlayerByPlace(INT32 place)
|
|
{
|
|
INT32 playernum;
|
|
|
|
for (playernum = 0; playernum < MAXPLAYERS; ++playernum)
|
|
{
|
|
if (!playeringame[playernum])
|
|
continue;
|
|
|
|
if (players[playernum].kartstuff[k_position] == place)
|
|
{
|
|
return playernum;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
//
|
|
// GetViewablePlayerPlaceRange
|
|
// Return in first and last, that player available to view, sorted by placement
|
|
// in the race.
|
|
//
|
|
static void GetViewablePlayerPlaceRange(INT32 *first, INT32 *last)
|
|
{
|
|
INT32 i;
|
|
INT32 place;
|
|
|
|
(*first) = MAXPLAYERS;
|
|
(*last) = 0;
|
|
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
|
{
|
|
if (G_CouldView(i))
|
|
{
|
|
place = players[i].kartstuff[k_position];
|
|
if (place < (*first))
|
|
(*first) = place;
|
|
if (place > (*last))
|
|
(*last) = place;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define PRINTVIEWPOINT( pre,suf ) \
|
|
CONS_Printf(pre"viewing \x84(%d) \x83%s\x80"suf".\n",\
|
|
(*displayplayerp), player_names[(*displayplayerp)]);
|
|
static void Command_View_f(void)
|
|
{
|
|
INT32 *displayplayerp;
|
|
INT32 olddisplayplayer;
|
|
int viewnum;
|
|
const char *playerparam;
|
|
INT32 placenum;
|
|
INT32 playernum;
|
|
INT32 firstplace, lastplace;
|
|
char c;
|
|
/* easy peasy */
|
|
c = COM_Argv(0)[strlen(COM_Argv(0))-1];/* may be digit */
|
|
switch (c)
|
|
{
|
|
case '2': viewnum = 2; break;
|
|
case '3': viewnum = 3; break;
|
|
case '4': viewnum = 4; break;
|
|
default: viewnum = 1;
|
|
}
|
|
|
|
if (viewnum > 1 && !( multiplayer && demo.playback ))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, "You must be viewing a multiplayer replay to use this.\n");
|
|
return;
|
|
}
|
|
|
|
if (camera[viewnum-1].freecam)
|
|
return;
|
|
|
|
displayplayerp = &displayplayers[viewnum-1];
|
|
|
|
if (COM_Argc() > 1)/* switch to player */
|
|
{
|
|
playerparam = COM_Argv(1);
|
|
|
|
if (playerparam[0] == '#')/* search by placement */
|
|
{
|
|
placenum = atoi(&playerparam[1]);
|
|
playernum = FindPlayerByPlace(placenum);
|
|
|
|
if (playernum == -1 || !G_CouldView(playernum))
|
|
{
|
|
GetViewablePlayerPlaceRange(&firstplace, &lastplace);
|
|
|
|
if (playernum == -1)
|
|
{
|
|
CONS_Alert(CONS_WARNING, "There is no player in that place! ");
|
|
}
|
|
else
|
|
{
|
|
CONS_Alert(CONS_WARNING,
|
|
"That player cannot be viewed currently! "
|
|
"The first player that you can view is \x82#%d\x80; ",
|
|
firstplace);
|
|
}
|
|
|
|
CONS_Printf("Last place is \x82#%d\x80.\n", lastplace);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (( playernum = D_LookupPlayer(COM_Argv(1)) ) == -1)
|
|
{
|
|
CONS_Alert(CONS_WARNING, "There is no player by that name!\n");
|
|
return;
|
|
}
|
|
if (!playeringame[playernum])
|
|
{
|
|
CONS_Alert(CONS_WARNING, "There is no player using that slot!\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
olddisplayplayer = (*displayplayerp);
|
|
G_ResetView(viewnum, playernum, false);
|
|
|
|
/* The player we wanted was corrected to who it already was. */
|
|
if ((*displayplayerp) == olddisplayplayer)
|
|
return;
|
|
|
|
if ((*displayplayerp) != playernum)/* differ parameter */
|
|
{
|
|
/* skipped some */
|
|
CONS_Alert(CONS_NOTICE, "That player cannot be viewed currently.\n");
|
|
PRINTVIEWPOINT ("Now "," instead")
|
|
}
|
|
else
|
|
PRINTVIEWPOINT ("Now ",)
|
|
}
|
|
else/* print current view */
|
|
{
|
|
if (splitscreen < viewnum-1)/* We can't see those guys! */
|
|
return;
|
|
PRINTVIEWPOINT ("Currently ",)
|
|
}
|
|
}
|
|
#undef PRINTVIEWPOINT
|
|
|
|
static void Command_SetViews_f(void)
|
|
{
|
|
UINT8 splits;
|
|
UINT8 newsplits;
|
|
|
|
if (!(demo.playback && multiplayer))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, "You must be viewing a multiplayer replay to use this.\n");
|
|
return;
|
|
}
|
|
|
|
if (COM_Argc() != 2)
|
|
{
|
|
CONS_Printf("setviews <views>: set the number of split screens\n");
|
|
return;
|
|
}
|
|
|
|
splits = splitscreen+1;
|
|
|
|
newsplits = atoi(COM_Argv(1));
|
|
newsplits = min(max(newsplits, 1), 4);
|
|
if (newsplits > splits)
|
|
G_AdjustView(newsplits, 0, true);
|
|
else
|
|
{
|
|
splitscreen = newsplits-1;
|
|
R_ExecuteSetViewSize();
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
|
|
// play a demo, add .lmp for external demos
|
|
// eg: playdemo demo1 plays the internal game demo
|
|
//
|
|
// UINT8 *demofile; // demo file buffer
|
|
static void Command_Playdemo_f(void)
|
|
{
|
|
char name[256];
|
|
|
|
if (COM_Argc() < 2)
|
|
{
|
|
CONS_Printf("playdemo <demoname> [-addfiles / -force]:\n");
|
|
CONS_Printf(M_GetText(
|
|
"Play back a demo file. The full path from your Kart directory must be given.\n\n"
|
|
|
|
"* With \"-addfiles\", any required files are added from a list contained within the demo file.\n"
|
|
"* With \"-force\", the demo is played even if the necessary files have not been added.\n"));
|
|
return;
|
|
}
|
|
|
|
if (netgame)
|
|
{
|
|
CONS_Printf(M_GetText("You can't play a demo while in a netgame.\n"));
|
|
return;
|
|
}
|
|
|
|
if (!demo.playback && gamestate == GS_LEVEL) // special case: allow starting another demo while watching a demo
|
|
{
|
|
CONS_Printf(M_GetText("You can't play a demo while in a game.\n"));
|
|
return;
|
|
}
|
|
|
|
// turn off any open menus
|
|
M_ClearMenus(true);
|
|
|
|
// disconnect from server here?
|
|
if (demo.playback)
|
|
G_StopDemo();
|
|
|
|
// open the demo file
|
|
strcpy(name, COM_Argv(1));
|
|
// dont add .lmp so internal game demos can be played
|
|
|
|
CONS_Printf(M_GetText("Playing back demo '%s'.\n"), name);
|
|
|
|
demo.loadfiles = fastcmp(COM_Argv(2), "-addfiles");
|
|
demo.ignorefiles = fastcmp(COM_Argv(2), "-force");
|
|
|
|
// Internal if no extension, external if one exists
|
|
// If external, convert the file name to a path in SRB2's home directory
|
|
if (FIL_CheckExtension(name))
|
|
G_DoPlayDemo(va("%s"PATHSEP"%s", srb2home, name));
|
|
else
|
|
G_DoPlayDemo(name);
|
|
}
|
|
|
|
static void Command_Timedemo_f(void)
|
|
{
|
|
char name[256];
|
|
|
|
if (COM_Argc() != 2)
|
|
{
|
|
CONS_Printf(M_GetText("timedemo <demoname>: time a demo\n"));
|
|
return;
|
|
}
|
|
|
|
if (netgame)
|
|
{
|
|
CONS_Printf(M_GetText("You can't play a demo while in a netgame.\n"));
|
|
return;
|
|
}
|
|
|
|
if (!demo.playback && gamestate == GS_LEVEL) // special case: allow starting another demo while watching a demo
|
|
{
|
|
CONS_Printf(M_GetText("You can't time a demo while in a game.\n"));
|
|
return;
|
|
}
|
|
|
|
// turn off any open menus
|
|
M_ClearMenus(true);
|
|
|
|
// disconnect from server here?
|
|
if (demo.playback)
|
|
G_StopDemo();
|
|
|
|
// open the demo file
|
|
strcpy (name, COM_Argv(1));
|
|
// dont add .lmp so internal game demos can be played
|
|
|
|
CONS_Printf(M_GetText("Timing demo '%s'.\n"), name);
|
|
|
|
G_TimeDemo(name);
|
|
}
|
|
|
|
// stop current demo
|
|
static void Command_Stopdemo_f(void)
|
|
{
|
|
G_CheckDemoStatus();
|
|
CONS_Printf(M_GetText("Stopped demo.\n"));
|
|
}
|
|
|
|
static void Command_StartMovie_f(void)
|
|
{
|
|
M_StartMovie();
|
|
}
|
|
|
|
static void Command_StopMovie_f(void)
|
|
{
|
|
M_StopMovie();
|
|
}
|
|
|
|
INT32 mapchangepending = 0;
|
|
|
|
/** Runs a map change.
|
|
* The supplied data are assumed to be good. If provided by a user, they will
|
|
* have already been checked in Command_Map_f().
|
|
*
|
|
* Do \b NOT call this function directly from a menu! M_Responder() is called
|
|
* from within the event processing loop, and this function calls
|
|
* SV_SpawnServer(), which calls CL_ConnectToServer(), which gives you "Press
|
|
* ESC to abort", which calls I_GetKey(), which adds an event. In other words,
|
|
* 63 old events will get reexecuted, with ridiculous results. Just don't do
|
|
* it (without setting delay to 1, which is the current solution).
|
|
*
|
|
* \param mapnum Map number to change to.
|
|
* \param gametype Gametype to switch to.
|
|
* \param pencoremode Is this 'Encore Mode'?
|
|
* \param resetplayers 1 to reset player scores and lives and such, 0 not to.
|
|
* \param delay Determines how the function will be executed: 0 to do
|
|
* it all right now (must not be done from a menu), 1 to
|
|
* do step one and prepare step two, 2 to do step two.
|
|
* \param skipprecutscene To skip the precutscence or not?
|
|
* \sa D_GameTypeChanged, Command_Map_f
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean resetplayers, INT32 delay, boolean skipprecutscene, boolean FLS)
|
|
{
|
|
static char buf[2+MAX_WADPATH+1+4];
|
|
static char *buf_p = buf;
|
|
|
|
// The supplied data are assumed to be good.
|
|
I_Assert(delay >= 0 && delay <= 2);
|
|
|
|
CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d encoremode=%d resetplayers=%d delay=%d skipprecutscene=%d\n",
|
|
mapnum, newgametype, pencoremode, resetplayers, delay, skipprecutscene);
|
|
|
|
if (netgame || multiplayer)
|
|
FLS = false;
|
|
|
|
if (delay != 2)
|
|
{
|
|
UINT8 flags = 0;
|
|
const char *mapname = G_BuildMapName(mapnum);
|
|
|
|
I_Assert(W_CheckNumForName(mapname) != LUMPERROR);
|
|
|
|
buf_p = buf;
|
|
if (pencoremode)
|
|
flags |= 1;
|
|
if (!resetplayers)
|
|
flags |= 1<<1;
|
|
if (skipprecutscene)
|
|
flags |= 1<<2;
|
|
if (FLS)
|
|
flags |= 1<<3;
|
|
WRITEUINT8(buf_p, flags);
|
|
|
|
// new gametype value
|
|
WRITEUINT8(buf_p, newgametype);
|
|
|
|
WRITESTRINGN(buf_p, mapname, MAX_WADPATH);
|
|
}
|
|
|
|
if (delay == 1)
|
|
mapchangepending = 1;
|
|
else
|
|
{
|
|
mapchangepending = 0;
|
|
// spawn the server if needed
|
|
// reset players if there is a new one
|
|
if (!IsPlayerAdmin(consoleplayer))
|
|
{
|
|
if (SV_SpawnServer())
|
|
buf[0] &= ~(1<<1);
|
|
if (!Playing()) // you failed to start a server somehow, so cancel the map change
|
|
return;
|
|
}
|
|
|
|
// Kick bot from special stages
|
|
if (botskin)
|
|
{
|
|
if (!botingame)
|
|
{
|
|
botingame = true;
|
|
displayplayers[1] = 1;
|
|
playeringame[1] = true;
|
|
players[1].bot = 1;
|
|
SendNameAndColor(1);
|
|
}
|
|
}
|
|
|
|
chmappending++;
|
|
if (netgame)
|
|
WRITEUINT32(buf_p, M_RandomizedSeed()); // random seed
|
|
SendNetXCmd(XD_MAP, buf, buf_p - buf);
|
|
}
|
|
}
|
|
|
|
void D_SetupVote(void)
|
|
{
|
|
UINT8 buf[5*2]; // four UINT16 maps (at twice the width of a UINT8), and two gametypes
|
|
UINT8 *p = buf;
|
|
INT32 i;
|
|
UINT8 gt = (cv_kartgametypepreference.value == -1) ? gametype : cv_kartgametypepreference.value;
|
|
UINT8 secondgt = G_SometimesGetDifferentGametype(gt);
|
|
INT16 votebuffer[4] = {-1,-1,-1, 0};
|
|
INT16 luamaps[4] = {0, 0, 0, 0};
|
|
|
|
LUA_HookSetupVote(luamaps, sizeof(luamaps)/sizeof(luamaps[0]), gt, secondgt);
|
|
|
|
// Correct secondgt if needed
|
|
if (luamaps[2])
|
|
{
|
|
UINT8 typeoflevel = mapheaderinfo[luamaps[2]-1]->typeoflevel;
|
|
if ((typeoflevel & G_TOLFlag(secondgt&(~0x80))) == 0)
|
|
secondgt = ((typeoflevel & TOL_RACE) ? GT_RACE : GT_MATCH)|(secondgt&0x80);
|
|
}
|
|
|
|
if (cv_kartencore.value && gt == GT_RACE)
|
|
WRITEUINT8(p, (gt|0x80));
|
|
else
|
|
WRITEUINT8(p, gt);
|
|
WRITEUINT8(p, secondgt);
|
|
secondgt &= ~0x80;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
UINT16 m;
|
|
if (luamaps[i])
|
|
m = luamaps[i]-1;
|
|
else if (i == 2) // sometimes a different gametype
|
|
m = G_RandMap(G_TOLFlag(secondgt), prevmap, false, 0, true, votebuffer);
|
|
else if (i >= 3) // unknown-random and force-unknown MAP HELL
|
|
m = G_RandMap(G_TOLFlag(gt), prevmap, false, (i-2), (i < 4), votebuffer);
|
|
else
|
|
m = G_RandMap(G_TOLFlag(gt), prevmap, false, 0, true, votebuffer);
|
|
if (i < 3)
|
|
votebuffer[min(i, 2)] = m; // min() is a dumb workaround for gcc 4.4 array-bounds error
|
|
WRITEUINT16(p, m);
|
|
}
|
|
|
|
SendNetXCmd(XD_SETUPVOTE, buf, p - buf);
|
|
}
|
|
|
|
void D_ModifyClientVote(SINT8 voted, UINT8 splitplayer)
|
|
{
|
|
char buf[2];
|
|
char *p = buf;
|
|
|
|
WRITESINT8(p, voted);
|
|
WRITEUINT8(p, splitplayer);
|
|
SendNetXCmd(XD_MODIFYVOTE, &buf, 2);
|
|
}
|
|
|
|
void D_PickVote(void)
|
|
{
|
|
char buf[2];
|
|
char* p = buf;
|
|
SINT8 temppicks[MAXPLAYERS];
|
|
SINT8 templevels[MAXPLAYERS];
|
|
SINT8 votecompare = -1;
|
|
UINT8 numvotes = 0, key = 0;
|
|
INT32 i;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i] || players[i].spectator)
|
|
continue;
|
|
|
|
if (votes[i] != -1)
|
|
{
|
|
temppicks[numvotes] = i;
|
|
templevels[numvotes] = votes[i];
|
|
numvotes++;
|
|
if (votecompare == -1)
|
|
votecompare = votes[i];
|
|
}
|
|
}
|
|
|
|
key = M_RandomKey(numvotes);
|
|
|
|
if (numvotes > 0)
|
|
{
|
|
WRITESINT8(p, temppicks[key]);
|
|
WRITESINT8(p, templevels[key]);
|
|
}
|
|
else
|
|
{
|
|
WRITESINT8(p, -1);
|
|
WRITESINT8(p, 0);
|
|
}
|
|
|
|
SendNetXCmd(XD_PICKVOTE, &buf, 2);
|
|
}
|
|
|
|
static char *ConcatCommandArgv(int start, int end)
|
|
{
|
|
char *final;
|
|
|
|
size_t size;
|
|
|
|
int i;
|
|
char *p;
|
|
|
|
size = 0;
|
|
|
|
for (i = start; i < end; ++i)
|
|
{
|
|
/*
|
|
one space after each argument, but terminating
|
|
character on final argument
|
|
*/
|
|
size += strlen(COM_Argv(i)) + 1;
|
|
}
|
|
|
|
p = final = ZZ_Alloc(size);
|
|
|
|
if (!p)
|
|
I_Error("ConcatCommandArgv: Out of memory!\n");
|
|
|
|
--end;/* handle the final argument separately */
|
|
|
|
for (i = start; i < end; ++i)
|
|
{
|
|
p += sprintf(p, "%s ", COM_Argv(i));
|
|
}
|
|
|
|
/* at this point "end" is actually the last argument's position */
|
|
strcpy(p, COM_Argv(end));
|
|
|
|
return final;
|
|
}
|
|
|
|
|
|
static tic_t last_map_cmd = 0;
|
|
//
|
|
// Warp to map code.
|
|
// Called either from map <mapname> console command, or idclev cheat.
|
|
//
|
|
// Largely rewritten by James.
|
|
//
|
|
// Added small cooldown which prevents from spamming map command and crashing
|
|
// game. It is not wery good fix but oh well i can't find yet where are mobj's
|
|
// freed/allocated between map loads and why they crash game - Indev
|
|
//
|
|
static void Command_Map_f(void)
|
|
{
|
|
if (I_GetTime() - last_map_cmd < 20)
|
|
{
|
|
CONS_Alert(CONS_WARNING, "Map command is used too frequently!\n");
|
|
return;
|
|
}
|
|
|
|
last_map_cmd = I_GetTime();
|
|
|
|
size_t first_option;
|
|
size_t option_force;
|
|
size_t option_gametype;
|
|
size_t option_encore;
|
|
const char *gametypename;
|
|
boolean newresetplayers;
|
|
|
|
boolean mustmodifygame;
|
|
|
|
INT32 newmapnum;
|
|
|
|
CLEANUP(Z_Pfree) char * mapname = NULL;
|
|
CLEANUP(Z_Pfree) char *realmapname = NULL;
|
|
|
|
INT32 newgametype = gametype;
|
|
boolean newencoremode = cv_kartencore.value;
|
|
|
|
INT32 d;
|
|
|
|
if (client && !IsPlayerAdmin(consoleplayer))
|
|
{
|
|
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
|
|
return;
|
|
}
|
|
|
|
option_force = COM_CheckPartialParm("-f");
|
|
option_gametype = COM_CheckPartialParm("-g");
|
|
option_encore = COM_CheckPartialParm("-e");
|
|
newresetplayers = ! COM_CheckParm("-noresetplayers");
|
|
|
|
mustmodifygame = !( netgame || multiplayer || majormods );
|
|
|
|
if (mustmodifygame && !option_force)
|
|
{
|
|
/* May want to be more descriptive? */
|
|
CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
|
|
return;
|
|
}
|
|
|
|
if (!newresetplayers && !cv_debug)
|
|
{
|
|
CONS_Printf(M_GetText("DEVMODE must be enabled.\n"));
|
|
return;
|
|
}
|
|
|
|
if (option_gametype)
|
|
{
|
|
if (!multiplayer)
|
|
{
|
|
CONS_Printf(M_GetText("You can't switch gametypes in single player!\n"));
|
|
return;
|
|
}
|
|
else if (COM_Argc() < option_gametype + 2)/* no argument after? */
|
|
{
|
|
CONS_Alert(CONS_ERROR,
|
|
"No gametype name follows parameter '%s'.\n",
|
|
COM_Argv(option_gametype));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!( first_option = COM_FirstOption() ))
|
|
first_option = COM_Argc();
|
|
|
|
if (first_option < 2)
|
|
{
|
|
/* I'm going over the fucking lines and I DON'T CAREEEEE */
|
|
CONS_Printf("map <name / [MAP]code / number> [-gametype <type>] [-encore] [-force]:\n");
|
|
CONS_Printf(M_GetText(
|
|
"Warp to a map, by its name, two character code, with optional \"MAP\" prefix, or by its number (though why would you).\n"
|
|
"All parameters are case-insensitive and may be abbreviated.\n"));
|
|
return;
|
|
}
|
|
|
|
mapname = ConcatCommandArgv(1, first_option);
|
|
|
|
newmapnum = G_FindMapByNameOrCode(mapname, &realmapname);
|
|
|
|
if (newmapnum == 0)
|
|
{
|
|
CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname);
|
|
return;
|
|
}
|
|
|
|
if (mustmodifygame && option_force)
|
|
{
|
|
G_SetGameModified(false, true);
|
|
}
|
|
|
|
// new gametype value
|
|
// use current one by default
|
|
if (option_gametype)
|
|
{
|
|
gametypename = COM_Argv(option_gametype + 1);
|
|
|
|
newgametype = G_GetGametypeByName(gametypename);
|
|
|
|
if (newgametype == -1) // reached end of the list with no match
|
|
{
|
|
/* Did they give us a gametype number? That's okay too! */
|
|
if (isdigit(gametypename[0]))
|
|
{
|
|
d = atoi(gametypename);
|
|
if (d >= 0 && d < NUMGAMETYPES)
|
|
newgametype = d;
|
|
else
|
|
{
|
|
CONS_Alert(CONS_ERROR,
|
|
"Gametype number %d is out of range. Use a number between"
|
|
" 0 and %d inclusive. ...Or just use the name. :v\n",
|
|
d,
|
|
NUMGAMETYPES-1);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CONS_Alert(CONS_ERROR,
|
|
"'%s' is not a gametype.\n",
|
|
gametypename);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!option_force && newgametype == gametype) // SRB2Kart
|
|
newresetplayers = false; // if not forcing and gametypes is the same
|
|
|
|
// don't use a gametype the map doesn't support
|
|
if (cv_debug || option_force || cv_skipmapcheck.value)
|
|
fromlevelselect = false; // The player wants us to trek on anyway. Do so.
|
|
// G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer
|
|
else
|
|
{
|
|
if (mapheaderinfo[newmapnum-1] == NULL)
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Invalid mapheaderinfo for Course %s (%s)\n"), realmapname, G_BuildMapName(newmapnum));
|
|
return;
|
|
}
|
|
|
|
if (!(mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype)))
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Course %s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum),
|
|
(multiplayer ? gametype_cons_t[newgametype].strvalue : "Single Player"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Prevent warping to locked levels
|
|
// ... unless you're in a dedicated server. Yes, technically this means you can view any level by
|
|
// running a dedicated server and joining it yourself, but that's better than making dedicated server's
|
|
// lives hell.
|
|
if (!dedicated && M_MapLocked(newmapnum))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n"));
|
|
return;
|
|
}
|
|
|
|
if (option_encore)
|
|
{
|
|
newencoremode = ! newencoremode;
|
|
if (! M_SecretUnlocked(SECRET_ENCORE) && newencoremode)
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (cv_demochangemap.value && demo.recording)
|
|
{
|
|
// spend atleast 40 seconds in one map
|
|
if (demo.savemode != DSM_NOTSAVING && (timeinmap > (TICRATE * 40)) && ((cv_demochangemap.value == 2 && newmapnum == gamemap) || newmapnum != gamemap))
|
|
G_SaveDemo();
|
|
else
|
|
G_ResetDemoRecording();
|
|
}
|
|
|
|
fromlevelselect = false;
|
|
|
|
M_ClearMenus(true);
|
|
|
|
D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, false);
|
|
}
|
|
|
|
/** Receives a map command and changes the map.
|
|
*
|
|
* \param cp Data buffer.
|
|
* \param playernum Player number responsible for the message. Should be
|
|
* ::serverplayer or ::adminplayer.
|
|
* \sa D_MapChange
|
|
*/
|
|
static void Got_Mapcmd(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
char mapname[MAX_WADPATH+1];
|
|
UINT8 flags;
|
|
INT32 resetplayer = 1, lastgametype;
|
|
UINT8 skipprecutscene, FLS;
|
|
boolean pencoremode;
|
|
//INT16 mapnumber;
|
|
|
|
forceresetplayers = deferencoremode = false;
|
|
|
|
if (playernum != serverplayer && !IsPlayerAdmin(playernum))
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal map change received from %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (chmappending)
|
|
chmappending--;
|
|
|
|
flags = READUINT8(*cp);
|
|
|
|
pencoremode = ((flags & 1) != 0);
|
|
|
|
resetplayer = ((flags & (1<<1)) == 0);
|
|
|
|
lastgametype = gametype;
|
|
gametype = READUINT8(*cp);
|
|
|
|
if (gametype < 0 || gametype >= NUMGAMETYPES)
|
|
gametype = lastgametype;
|
|
else if (gametype != lastgametype)
|
|
D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype
|
|
|
|
if (!G_RaceGametype())
|
|
pencoremode = false;
|
|
|
|
skipprecutscene = ((flags & (1<<2)) != 0);
|
|
|
|
FLS = ((flags & (1<<3)) != 0);
|
|
|
|
READSTRINGN(*cp, mapname, MAX_WADPATH);
|
|
|
|
if (netgame)
|
|
P_SetRandSeed(READUINT32(*cp));
|
|
|
|
if (!skipprecutscene)
|
|
{
|
|
DEBFILE(va("Warping to %s [resetplayer=%d lastgametype=%d gametype=%d cpnd=%d]\n",
|
|
mapname, resetplayer, lastgametype, gametype, chmappending));
|
|
CON_LogMessage(M_GetText("Speeding off to level...\n"));
|
|
}
|
|
|
|
if (resetplayer)
|
|
{
|
|
if (!FLS || (netgame || multiplayer))
|
|
emeralds = 0;
|
|
}
|
|
|
|
if (modeattacking) // i remember moving this here in internal fixed a heisenbug so
|
|
SetPlayerSkinByNum(0, cv_chooseskin.value-1);
|
|
|
|
//mapnumber = M_MapNumber(mapname[3], mapname[4]);
|
|
//LUA_HookInt(mapnumber, HOOK(MapChange));
|
|
|
|
demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING;
|
|
demo.savebutton = 0;
|
|
G_InitNew(pencoremode, mapname, resetplayer, skipprecutscene);
|
|
|
|
if (demo.timing)
|
|
G_DoneLevelLoad();
|
|
|
|
if (demo.recording) // Okay, level loaded, character spawned and skinned,
|
|
G_BeginRecording(); // I AM NOW READY TO RECORD.
|
|
demo.deferstart = true;
|
|
|
|
#ifdef HAVE_DISCORDRPC
|
|
DRPC_UpdatePresence();
|
|
#endif
|
|
}
|
|
|
|
static void Command_Pause(void)
|
|
{
|
|
UINT8 buf[2];
|
|
UINT8 *cp = buf;
|
|
|
|
if (COM_Argc() > 1)
|
|
WRITEUINT8(cp, (char)(atoi(COM_Argv(1)) != 0));
|
|
else
|
|
WRITEUINT8(cp, (char)(!paused));
|
|
|
|
if (dedicated)
|
|
WRITEUINT8(cp, 1);
|
|
else
|
|
WRITEUINT8(cp, 0);
|
|
|
|
if (cv_pause.value || server || (IsPlayerAdmin(consoleplayer)))
|
|
{
|
|
if (!paused && (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING || gamestate == GS_WAITINGPLAYERS)))
|
|
{
|
|
CONS_Printf(M_GetText("You can't pause here.\n"));
|
|
return;
|
|
}
|
|
else if (modeattacking) // in time attack, pausing restarts the map
|
|
{
|
|
M_ModeAttackRetry(0); // directly call from m_menu;
|
|
return;
|
|
}
|
|
|
|
SendNetXCmd(XD_PAUSE, &buf, 2);
|
|
}
|
|
else
|
|
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
|
|
}
|
|
|
|
static void Got_Pause(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
UINT8 dedicatedpause = false;
|
|
const char *playername;
|
|
|
|
if (netgame && !cv_pause.value && playernum != serverplayer && !IsPlayerAdmin(playernum))
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal pause command received from %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (modeattacking && !demo.playback)
|
|
return;
|
|
|
|
paused = READUINT8(*cp);
|
|
dedicatedpause = READUINT8(*cp);
|
|
|
|
if (!demo.playback)
|
|
{
|
|
if (netgame)
|
|
{
|
|
if (dedicatedpause)
|
|
playername = "SERVER";
|
|
else
|
|
playername = player_names[playernum];
|
|
|
|
if (paused)
|
|
CONS_Printf(M_GetText("Game paused by %s\n"), playername);
|
|
else
|
|
CONS_Printf(M_GetText("Game unpaused by %s\n"), playername);
|
|
}
|
|
|
|
if (paused)
|
|
{
|
|
if ((!menuactive || netgame) && (!cv_pausemusic.value))
|
|
S_PauseAudio();
|
|
}
|
|
else
|
|
S_ResumeAudio();
|
|
}
|
|
|
|
G_ResetAllDeviceRumbles();
|
|
}
|
|
|
|
static void Command_ReplayMarker(void)
|
|
{
|
|
if (gamestate != GS_LEVEL)
|
|
{
|
|
CONS_Printf(M_GetText("You must be in a level to use this.\n"));
|
|
return;
|
|
}
|
|
if (demo.savemode == DSM_WILLAUTOSAVE)
|
|
{
|
|
demo.savemode = DSM_NOTSAVING;
|
|
CONS_Printf("Replay unmarked.\n");
|
|
}
|
|
else
|
|
{
|
|
demo.savemode = DSM_WILLAUTOSAVE;
|
|
|
|
int adjustedleveltime = leveltime - starttime;
|
|
|
|
if (adjustedleveltime < 0)
|
|
adjustedleveltime = 0;
|
|
|
|
char *title = G_BuildMapTitle(gamemap);
|
|
|
|
if (title)
|
|
{
|
|
snprintf(demo.titlename, 64, "%s [%i:%02d/%.5s]", title, G_TicsToMinutes(adjustedleveltime, false), G_TicsToSeconds(adjustedleveltime), modeattacking ? "Record Attack" : connectedservername);
|
|
Z_Free(title);
|
|
}
|
|
else
|
|
snprintf(demo.titlename, 64, "[%i:%02d/%.5s]", G_TicsToMinutes(adjustedleveltime, false), G_TicsToSeconds(adjustedleveltime), modeattacking ? "Record Attack" : connectedservername);
|
|
|
|
CONS_Printf("Replay will be saved!\n");
|
|
}
|
|
}
|
|
|
|
// Command for stuck characters in netgames, griefing, etc.
|
|
static void Command_Respawn(void)
|
|
{
|
|
UINT8 buf[4];
|
|
UINT8 *cp = buf;
|
|
|
|
WRITEINT32(cp, consoleplayer);
|
|
|
|
if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING))
|
|
{
|
|
CONS_Printf(M_GetText("You must be in a level to use this.\n"));
|
|
return;
|
|
}
|
|
|
|
if (players[consoleplayer].mo && !P_IsObjectOnGround(players[consoleplayer].mo)) // KART: Nice try, but no, you won't be cheesing spb anymore.
|
|
{
|
|
CONS_Printf(M_GetText("You must be on the floor to use this.\n"));
|
|
return;
|
|
}
|
|
|
|
if (players[consoleplayer].mo && (players[consoleplayer].kartstuff[k_spinouttimer] || spbplace == players[consoleplayer].kartstuff[k_position])) // KART: Nice try, but no, you won't be cheesing spb anymore (x2)
|
|
{
|
|
CONS_Printf(M_GetText("Nice try.\n"));
|
|
return;
|
|
}
|
|
|
|
SendNetXCmd(XD_RESPAWN, &buf, 4);
|
|
}
|
|
|
|
static void Got_Respawn(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
INT32 respawnplayer = READINT32(*cp);
|
|
|
|
// You can't respawn someone else. Nice try, there.
|
|
if (respawnplayer != playernum || players[respawnplayer].kartstuff[k_spinouttimer] || spbplace == players[respawnplayer].kartstuff[k_position]) // srb2kart: "|| (!G_RaceGametype())"
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal respawn command received from %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// incase the above checks were modified to allow sending a respawn on these occasions:
|
|
if (players[respawnplayer].mo && !P_IsObjectOnGround(players[respawnplayer].mo))
|
|
return;
|
|
|
|
if (players[respawnplayer].mo)
|
|
P_DamageMobj(players[respawnplayer].mo, NULL, NULL, DMG_INSTAKILL);
|
|
demo_extradata[playernum] |= DXD_RESPAWN;
|
|
}
|
|
|
|
/** Deals with an ::XD_RANDOMSEED message in a netgame.
|
|
* These messages set the position of the random number LUT and are crucial to
|
|
* correct synchronization.
|
|
*
|
|
* Such a message should only ever come from the ::serverplayer. If it comes
|
|
* from any other player, it is ignored.
|
|
*
|
|
* \param cp Data buffer.
|
|
* \param playernum Player responsible for the message. Must be ::serverplayer.
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void Got_RandomSeed(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
UINT32 seed;
|
|
|
|
seed = READUINT32(*cp);
|
|
|
|
if (playernum != serverplayer) // it's not from the server, wtf?
|
|
return;
|
|
|
|
P_SetRandSeed(seed);
|
|
}
|
|
|
|
/** Clears all players' scores in a netgame.
|
|
* Only the server or a remote admin can use this command, for obvious reasons.
|
|
*
|
|
* \sa XD_CLEARSCORES, Got_Clearscores
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
static void Command_Clearscores_f(void)
|
|
{
|
|
if (!(server || (IsPlayerAdmin(consoleplayer))))
|
|
return;
|
|
|
|
SendNetXCmd(XD_CLEARSCORES, NULL, 1);
|
|
}
|
|
|
|
/** Handles an ::XD_CLEARSCORES message, which resets all players' scores in a
|
|
* netgame to zero.
|
|
*
|
|
* \param cp Data buffer.
|
|
* \param playernum Player responsible for the message. Must be ::serverplayer
|
|
* or ::adminplayer.
|
|
* \sa XD_CLEARSCORES, Command_Clearscores_f
|
|
* \author SSNTails <http://www.ssntails.org>
|
|
*/
|
|
static void Got_Clearscores(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
INT32 i;
|
|
|
|
(void)cp;
|
|
if (playernum != serverplayer && !IsPlayerAdmin(playernum))
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal clear scores command received from %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
players[i].score = 0;
|
|
|
|
CONS_Printf(M_GetText("Scores have been reset by the server.\n"));
|
|
}
|
|
|
|
// Team changing functions
|
|
static void Command_Teamchange_f(void)
|
|
{
|
|
changeteam_union NetPacket;
|
|
boolean error = false;
|
|
UINT16 usvalue;
|
|
NetPacket.value.l = NetPacket.value.b = 0;
|
|
|
|
// 0 1
|
|
// changeteam <color>
|
|
|
|
if (COM_Argc() <= 1)
|
|
{
|
|
if (G_GametypeHasTeams())
|
|
CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "red, blue or spectator");
|
|
else if (G_GametypeHasSpectators())
|
|
CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "spectator or playing");
|
|
else
|
|
CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
|
|
return;
|
|
}
|
|
|
|
if (G_GametypeHasTeams())
|
|
{
|
|
if (fasticmp(COM_Argv(1), "red") || fasticmp(COM_Argv(1), "1"))
|
|
NetPacket.packet.newteam = 1;
|
|
else if (fasticmp(COM_Argv(1), "blue") || fasticmp(COM_Argv(1), "2"))
|
|
NetPacket.packet.newteam = 2;
|
|
else if (fasticmp(COM_Argv(1), "spectator") || fasticmp(COM_Argv(1), "0"))
|
|
NetPacket.packet.newteam = 0;
|
|
else
|
|
error = true;
|
|
}
|
|
else if (G_GametypeHasSpectators())
|
|
{
|
|
if (fasticmp(COM_Argv(1), "spectator") || fasticmp(COM_Argv(1), "0"))
|
|
NetPacket.packet.newteam = 0;
|
|
else if (fasticmp(COM_Argv(1), "playing") || fasticmp(COM_Argv(1), "1"))
|
|
NetPacket.packet.newteam = 3;
|
|
else
|
|
error = true;
|
|
}
|
|
else
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
|
|
return;
|
|
}
|
|
|
|
if (error)
|
|
{
|
|
if (G_GametypeHasTeams())
|
|
CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "red, blue or spectator");
|
|
else if (G_GametypeHasSpectators())
|
|
CONS_Printf(M_GetText("changeteam <team>: switch to a new team (%s)\n"), "spectator or playing");
|
|
return;
|
|
}
|
|
|
|
if (players[consoleplayer].spectator)
|
|
error = !(NetPacket.packet.newteam || (players[consoleplayer].pflags & PF_WANTSTOJOIN)); // :lancer:
|
|
else if (G_GametypeHasTeams())
|
|
error = (NetPacket.packet.newteam == (unsigned)players[consoleplayer].ctfteam);
|
|
else if (G_GametypeHasSpectators() && !players[consoleplayer].spectator)
|
|
error = (NetPacket.packet.newteam == 3);
|
|
#ifdef PARANOIA
|
|
else
|
|
I_Error("Invalid gametype after initial checks!");
|
|
#endif
|
|
|
|
if (error)
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You're already on that team!\n"));
|
|
return;
|
|
}
|
|
|
|
if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n"));
|
|
return;
|
|
}
|
|
|
|
//additional check for hide and seek. Don't allow change of status after hidetime ends.
|
|
if (gametype == GT_HIDEANDSEEK && leveltime >= (hidetime * TICRATE))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("Hiding time expired; no Hide and Seek status changes allowed!\n"));
|
|
return;
|
|
}
|
|
|
|
usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
|
|
SendNetXCmdForPlayer(0, XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
|
|
}
|
|
|
|
static void Command_Teamchange2_f(void)
|
|
{
|
|
changeteam_union NetPacket;
|
|
boolean error = false;
|
|
UINT16 usvalue;
|
|
NetPacket.value.l = NetPacket.value.b = 0;
|
|
|
|
// 0 1
|
|
// changeteam2 <color>
|
|
|
|
if (COM_Argc() <= 1)
|
|
{
|
|
if (G_GametypeHasTeams())
|
|
CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "red, blue or spectator");
|
|
else if (G_GametypeHasSpectators())
|
|
CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "spectator or playing");
|
|
else
|
|
CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
|
|
return;
|
|
}
|
|
|
|
if (G_GametypeHasTeams())
|
|
{
|
|
if (fasticmp(COM_Argv(1), "red") || fasticmp(COM_Argv(1), "1"))
|
|
NetPacket.packet.newteam = 1;
|
|
else if (fasticmp(COM_Argv(1), "blue") || fasticmp(COM_Argv(1), "2"))
|
|
NetPacket.packet.newteam = 2;
|
|
else if (fasticmp(COM_Argv(1), "spectator") || fasticmp(COM_Argv(1), "0"))
|
|
NetPacket.packet.newteam = 0;
|
|
else
|
|
error = true;
|
|
}
|
|
else if (G_GametypeHasSpectators())
|
|
{
|
|
if (fasticmp(COM_Argv(1), "spectator") || fasticmp(COM_Argv(1), "0"))
|
|
NetPacket.packet.newteam = 0;
|
|
else if (fasticmp(COM_Argv(1), "playing") || fasticmp(COM_Argv(1), "1"))
|
|
NetPacket.packet.newteam = 3;
|
|
else
|
|
error = true;
|
|
}
|
|
|
|
else
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
|
|
return;
|
|
}
|
|
|
|
if (error)
|
|
{
|
|
if (G_GametypeHasTeams())
|
|
CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "red, blue or spectator");
|
|
else if (G_GametypeHasSpectators())
|
|
CONS_Printf(M_GetText("changeteam2 <team>: switch to a new team (%s)\n"), "spectator or playing");
|
|
return;
|
|
}
|
|
|
|
if (players[displayplayers[1]].spectator)
|
|
error = !(NetPacket.packet.newteam || (players[displayplayers[1]].pflags & PF_WANTSTOJOIN));
|
|
else if (G_GametypeHasTeams())
|
|
error = (NetPacket.packet.newteam == (unsigned)players[displayplayers[1]].ctfteam);
|
|
else if (G_GametypeHasSpectators() && !players[displayplayers[1]].spectator)
|
|
error = (NetPacket.packet.newteam == 3);
|
|
#ifdef PARANOIA
|
|
else
|
|
I_Error("Invalid gametype after initial checks!");
|
|
#endif
|
|
|
|
if (error)
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You're already on that team!\n"));
|
|
return;
|
|
}
|
|
|
|
if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n"));
|
|
return;
|
|
}
|
|
|
|
//additional check for hide and seek. Don't allow change of status after hidetime ends.
|
|
if (gametype == GT_HIDEANDSEEK && leveltime >= (hidetime * TICRATE))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("Hiding time expired; no Hide and Seek status changes allowed!\n"));
|
|
return;
|
|
}
|
|
|
|
usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
|
|
SendNetXCmdForPlayer(1, XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
|
|
}
|
|
|
|
static void Command_Teamchange3_f(void)
|
|
{
|
|
changeteam_union NetPacket;
|
|
boolean error = false;
|
|
UINT16 usvalue;
|
|
NetPacket.value.l = NetPacket.value.b = 0;
|
|
|
|
// 0 1
|
|
// changeteam3 <color>
|
|
|
|
if (COM_Argc() <= 1)
|
|
{
|
|
if (G_GametypeHasTeams())
|
|
CONS_Printf(M_GetText("changeteam3 <team>: switch to a new team (%s)\n"), "red, blue or spectator");
|
|
else if (G_GametypeHasSpectators())
|
|
CONS_Printf(M_GetText("changeteam3 <team>: switch to a new team (%s)\n"), "spectator or playing");
|
|
else
|
|
CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
|
|
return;
|
|
}
|
|
|
|
if (G_GametypeHasTeams())
|
|
{
|
|
if (fasticmp(COM_Argv(1), "red") || fasticmp(COM_Argv(1), "1"))
|
|
NetPacket.packet.newteam = 1;
|
|
else if (fasticmp(COM_Argv(1), "blue") || fasticmp(COM_Argv(1), "2"))
|
|
NetPacket.packet.newteam = 2;
|
|
else if (fasticmp(COM_Argv(1), "spectator") || fasticmp(COM_Argv(1), "0"))
|
|
NetPacket.packet.newteam = 0;
|
|
else
|
|
error = true;
|
|
}
|
|
else if (G_GametypeHasSpectators())
|
|
{
|
|
if (fasticmp(COM_Argv(1), "spectator") || fasticmp(COM_Argv(1), "0"))
|
|
NetPacket.packet.newteam = 0;
|
|
else if (fasticmp(COM_Argv(1), "playing") || fasticmp(COM_Argv(1), "1"))
|
|
NetPacket.packet.newteam = 3;
|
|
else
|
|
error = true;
|
|
}
|
|
|
|
else
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
|
|
return;
|
|
}
|
|
|
|
if (error)
|
|
{
|
|
if (G_GametypeHasTeams())
|
|
CONS_Printf(M_GetText("changeteam3 <team>: switch to a new team (%s)\n"), "red, blue or spectator");
|
|
else if (G_GametypeHasSpectators())
|
|
CONS_Printf(M_GetText("changeteam3 <team>: switch to a new team (%s)\n"), "spectator or playing");
|
|
return;
|
|
}
|
|
|
|
if (players[displayplayers[2]].spectator)
|
|
error = !(NetPacket.packet.newteam || (players[displayplayers[2]].pflags & PF_WANTSTOJOIN));
|
|
else if (G_GametypeHasTeams())
|
|
error = (NetPacket.packet.newteam == (unsigned)players[displayplayers[2]].ctfteam);
|
|
else if (G_GametypeHasSpectators() && !players[displayplayers[2]].spectator)
|
|
error = (NetPacket.packet.newteam == 3);
|
|
#ifdef PARANOIA
|
|
else
|
|
I_Error("Invalid gametype after initial checks!");
|
|
#endif
|
|
|
|
if (error)
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You're already on that team!\n"));
|
|
return;
|
|
}
|
|
|
|
if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n"));
|
|
return;
|
|
}
|
|
|
|
//additional check for hide and seek. Don't allow change of status after hidetime ends.
|
|
if (gametype == GT_HIDEANDSEEK && leveltime >= (hidetime * TICRATE))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("Hiding time expired; no Hide and Seek status changes allowed!\n"));
|
|
return;
|
|
}
|
|
|
|
usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
|
|
SendNetXCmdForPlayer(2, XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
|
|
}
|
|
|
|
static void Command_Teamchange4_f(void)
|
|
{
|
|
changeteam_union NetPacket;
|
|
boolean error = false;
|
|
UINT16 usvalue;
|
|
NetPacket.value.l = NetPacket.value.b = 0;
|
|
|
|
// 0 1
|
|
// changeteam4 <color>
|
|
|
|
if (COM_Argc() <= 1)
|
|
{
|
|
if (G_GametypeHasTeams())
|
|
CONS_Printf(M_GetText("changeteam4 <team>: switch to a new team (%s)\n"), "red, blue or spectator");
|
|
else if (G_GametypeHasSpectators())
|
|
CONS_Printf(M_GetText("changeteam4 <team>: switch to a new team (%s)\n"), "spectator or playing");
|
|
else
|
|
CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
|
|
return;
|
|
}
|
|
|
|
if (G_GametypeHasTeams())
|
|
{
|
|
if (fasticmp(COM_Argv(1), "red") || fasticmp(COM_Argv(1), "1"))
|
|
NetPacket.packet.newteam = 1;
|
|
else if (fasticmp(COM_Argv(1), "blue") || fasticmp(COM_Argv(1), "2"))
|
|
NetPacket.packet.newteam = 2;
|
|
else if (fasticmp(COM_Argv(1), "spectator") || fasticmp(COM_Argv(1), "0"))
|
|
NetPacket.packet.newteam = 0;
|
|
else
|
|
error = true;
|
|
}
|
|
else if (G_GametypeHasSpectators())
|
|
{
|
|
if (fasticmp(COM_Argv(1), "spectator") || fasticmp(COM_Argv(1), "0"))
|
|
NetPacket.packet.newteam = 0;
|
|
else if (fasticmp(COM_Argv(1), "playing") || fasticmp(COM_Argv(1), "1"))
|
|
NetPacket.packet.newteam = 3;
|
|
else
|
|
error = true;
|
|
}
|
|
|
|
else
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
|
|
return;
|
|
}
|
|
|
|
if (error)
|
|
{
|
|
if (G_GametypeHasTeams())
|
|
CONS_Printf(M_GetText("changeteam4 <team>: switch to a new team (%s)\n"), "red, blue or spectator");
|
|
else if (G_GametypeHasSpectators())
|
|
CONS_Printf(M_GetText("changeteam4 <team>: switch to a new team (%s)\n"), "spectator or playing");
|
|
return;
|
|
}
|
|
|
|
if (players[displayplayers[3]].spectator)
|
|
error = !(NetPacket.packet.newteam || (players[displayplayers[3]].pflags & PF_WANTSTOJOIN));
|
|
else if (G_GametypeHasTeams())
|
|
error = (NetPacket.packet.newteam == (unsigned)players[displayplayers[3]].ctfteam);
|
|
else if (G_GametypeHasSpectators() && !players[displayplayers[3]].spectator)
|
|
error = (NetPacket.packet.newteam == 3);
|
|
#ifdef PARANOIA
|
|
else
|
|
I_Error("Invalid gametype after initial checks!");
|
|
#endif
|
|
|
|
if (error)
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You're already on that team!\n"));
|
|
return;
|
|
}
|
|
|
|
if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n"));
|
|
return;
|
|
}
|
|
|
|
//additional check for hide and seek. Don't allow change of status after hidetime ends.
|
|
if (gametype == GT_HIDEANDSEEK && leveltime >= (hidetime * TICRATE))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("Hiding time expired; no Hide and Seek status changes allowed!\n"));
|
|
return;
|
|
}
|
|
|
|
usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
|
|
SendNetXCmdForPlayer(3, XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
|
|
}
|
|
|
|
static void Command_ServerTeamChange_f(void)
|
|
{
|
|
changeteam_union NetPacket;
|
|
boolean error = false;
|
|
UINT16 usvalue;
|
|
NetPacket.value.l = NetPacket.value.b = 0;
|
|
|
|
if (!(server || (IsPlayerAdmin(consoleplayer))))
|
|
{
|
|
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
|
|
return;
|
|
}
|
|
|
|
// 0 1 2
|
|
// serverchangeteam <playernum> <team>
|
|
|
|
if (COM_Argc() < 3)
|
|
{
|
|
if (G_TagGametype())
|
|
CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "it, notit, playing, or spectator");
|
|
else if (G_GametypeHasTeams())
|
|
CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
|
|
else if (G_GametypeHasSpectators())
|
|
CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "spectator or playing");
|
|
else
|
|
CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
|
|
return;
|
|
}
|
|
|
|
if (G_TagGametype())
|
|
{
|
|
if (fasticmp(COM_Argv(2), "it") || fasticmp(COM_Argv(2), "1"))
|
|
NetPacket.packet.newteam = 1;
|
|
else if (fasticmp(COM_Argv(2), "notit") || fasticmp(COM_Argv(2), "2"))
|
|
NetPacket.packet.newteam = 2;
|
|
else if (fasticmp(COM_Argv(2), "playing") || fasticmp(COM_Argv(2), "3"))
|
|
NetPacket.packet.newteam = 3;
|
|
else if (fasticmp(COM_Argv(2), "spectator") || fasticmp(COM_Argv(2), "0"))
|
|
NetPacket.packet.newteam = 0;
|
|
else
|
|
error = true;
|
|
}
|
|
else if (G_GametypeHasTeams())
|
|
{
|
|
if (fasticmp(COM_Argv(2), "red") || fasticmp(COM_Argv(2), "1"))
|
|
NetPacket.packet.newteam = 1;
|
|
else if (fasticmp(COM_Argv(2), "blue") || fasticmp(COM_Argv(2), "2"))
|
|
NetPacket.packet.newteam = 2;
|
|
else if (fasticmp(COM_Argv(2), "spectator") || fasticmp(COM_Argv(2), "0"))
|
|
NetPacket.packet.newteam = 0;
|
|
else
|
|
error = true;
|
|
}
|
|
else if (G_GametypeHasSpectators())
|
|
{
|
|
if (fasticmp(COM_Argv(2), "spectator") || fasticmp(COM_Argv(2), "0"))
|
|
NetPacket.packet.newteam = 0;
|
|
else if (fasticmp(COM_Argv(2), "playing") || fasticmp(COM_Argv(2), "1"))
|
|
NetPacket.packet.newteam = 3;
|
|
else
|
|
error = true;
|
|
}
|
|
else
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
|
|
return;
|
|
}
|
|
|
|
if (error)
|
|
{
|
|
if (G_TagGametype())
|
|
CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "it, notit, playing, or spectator");
|
|
else if (G_GametypeHasTeams())
|
|
CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "red, blue or spectator");
|
|
else if (G_GametypeHasSpectators())
|
|
CONS_Printf(M_GetText("serverchangeteam <playernum> <team>: switch player to a new team (%s)\n"), "spectator or playing");
|
|
return;
|
|
}
|
|
|
|
NetPacket.packet.playernum = atoi(COM_Argv(1));
|
|
|
|
if (!playeringame[NetPacket.packet.playernum])
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("There is no player %d!\n"), NetPacket.packet.playernum);
|
|
return;
|
|
}
|
|
|
|
if (G_TagGametype())
|
|
{
|
|
if (( (players[NetPacket.packet.playernum].pflags & PF_TAGIT) && NetPacket.packet.newteam == 1) ||
|
|
(!(players[NetPacket.packet.playernum].pflags & PF_TAGIT) && NetPacket.packet.newteam == 2) ||
|
|
( players[NetPacket.packet.playernum].spectator && !NetPacket.packet.newteam) ||
|
|
(!players[NetPacket.packet.playernum].spectator && NetPacket.packet.newteam == 3))
|
|
error = true;
|
|
}
|
|
else if (G_GametypeHasTeams())
|
|
{
|
|
if (NetPacket.packet.newteam == (unsigned)players[NetPacket.packet.playernum].ctfteam ||
|
|
(players[NetPacket.packet.playernum].spectator && !NetPacket.packet.newteam))
|
|
error = true;
|
|
}
|
|
else if (G_GametypeHasSpectators())
|
|
{
|
|
if ((players[NetPacket.packet.playernum].spectator && !NetPacket.packet.newteam) ||
|
|
(!players[NetPacket.packet.playernum].spectator && NetPacket.packet.newteam == 3))
|
|
error = true;
|
|
}
|
|
#ifdef PARANOIA
|
|
else
|
|
I_Error("Invalid gametype after initial checks!");
|
|
#endif
|
|
|
|
if (error)
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("That player is already on that team!\n"));
|
|
return;
|
|
}
|
|
|
|
//additional check for hide and seek. Don't allow change of status after hidetime ends.
|
|
if (gametype == GT_HIDEANDSEEK && leveltime >= (hidetime * TICRATE))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("Hiding time expired; no Hide and Seek status changes allowed!\n"));
|
|
return;
|
|
}
|
|
|
|
NetPacket.packet.verification = true; // This signals that it's a server change
|
|
|
|
usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
|
|
SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
|
|
}
|
|
|
|
//todo: This and the other teamchange functions are getting too long and messy. Needs cleaning.
|
|
static void Got_Teamchange(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
changeteam_union NetPacket;
|
|
boolean error = false, wasspectator = false;
|
|
NetPacket.value.l = NetPacket.value.b = READINT16(*cp);
|
|
|
|
if (!G_GametypeHasTeams() && !G_GametypeHasSpectators()) //Make sure you're in the right gametype.
|
|
{
|
|
// this should never happen unless the client is hacked/buggy
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
}
|
|
|
|
if (NetPacket.packet.verification) // Special marker that the server sent the request
|
|
{
|
|
if (playernum != serverplayer && (!IsPlayerAdmin(playernum)))
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
playernum = NetPacket.packet.playernum;
|
|
}
|
|
|
|
// Prevent multiple changes in one go.
|
|
if (players[playernum].spectator && !(players[playernum].pflags & PF_WANTSTOJOIN) && !NetPacket.packet.newteam)
|
|
return;
|
|
else if (G_TagGametype())
|
|
{
|
|
if (((players[playernum].pflags & PF_TAGIT) && NetPacket.packet.newteam == 1) ||
|
|
(!(players[playernum].pflags & PF_TAGIT) && NetPacket.packet.newteam == 2) ||
|
|
(!players[playernum].spectator && NetPacket.packet.newteam == 3))
|
|
return;
|
|
}
|
|
else if (G_GametypeHasTeams())
|
|
{
|
|
if (NetPacket.packet.newteam && (NetPacket.packet.newteam == (unsigned)players[playernum].ctfteam))
|
|
return;
|
|
}
|
|
else if (G_GametypeHasSpectators())
|
|
{
|
|
if (!players[playernum].spectator && NetPacket.packet.newteam == 3)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (playernum != serverplayer && (!IsPlayerAdmin(playernum)))
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
//Make sure that the right team number is sent. Keep in mind that normal clients cannot change to certain teams in certain gametypes.
|
|
switch (gametype)
|
|
{
|
|
case GT_HIDEANDSEEK:
|
|
//no status changes after hidetime
|
|
if (leveltime >= (hidetime * TICRATE))
|
|
{
|
|
error = true;
|
|
break;
|
|
}
|
|
/* FALLTHRU */
|
|
case GT_TAG:
|
|
switch (NetPacket.packet.newteam)
|
|
{
|
|
case 0:
|
|
break;
|
|
case 1: case 2:
|
|
if (!NetPacket.packet.verification)
|
|
error = true; //Only admin can change player's IT status' in tag.
|
|
break;
|
|
case 3: //Join game via console.
|
|
if (!NetPacket.packet.verification && !cv_allowteamchange.value)
|
|
error = true;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
#ifdef PARANOIA
|
|
if (!G_GametypeHasTeams() && !G_GametypeHasSpectators())
|
|
I_Error("Invalid gametype after initial checks!");
|
|
#endif
|
|
|
|
if (!cv_allowteamchange.value)
|
|
{
|
|
if (!NetPacket.packet.verification && NetPacket.packet.newteam)
|
|
error = true; //Only admin can change status, unless changing to spectator.
|
|
}
|
|
break; //Otherwise, you don't need special permissions.
|
|
}
|
|
|
|
if (server && ((NetPacket.packet.newteam < 0 || NetPacket.packet.newteam > 3) || error))
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
|
|
//Safety first!
|
|
// (not respawning spectators here...)
|
|
if (!players[playernum].spectator)
|
|
{
|
|
if (players[playernum].mo)
|
|
P_DamageMobj(players[playernum].mo, NULL, NULL, DMG_INSTAKILL);
|
|
else
|
|
players[playernum].playerstate = PST_REBORN;
|
|
}
|
|
else
|
|
wasspectator = true;
|
|
|
|
players[playernum].pflags &= ~PF_WANTSTOJOIN;
|
|
|
|
//Now that we've done our error checking and killed the player
|
|
//if necessary, put the player on the correct team/status.
|
|
if (G_TagGametype())
|
|
{
|
|
if (!NetPacket.packet.newteam)
|
|
{
|
|
players[playernum].spectator = true;
|
|
players[playernum].pflags &= ~PF_TAGIT;
|
|
players[playernum].pflags &= ~PF_TAGGED;
|
|
}
|
|
else if (NetPacket.packet.newteam != 3) // .newteam == 1 or 2.
|
|
{
|
|
players[playernum].pflags |= PF_WANTSTOJOIN; //players[playernum].spectator = false;
|
|
players[playernum].pflags &= ~PF_TAGGED;//Just in case.
|
|
|
|
if (NetPacket.packet.newteam == 1) //Make the player IT.
|
|
players[playernum].pflags |= PF_TAGIT;
|
|
else
|
|
players[playernum].pflags &= ~PF_TAGIT;
|
|
}
|
|
else // Just join the game.
|
|
{
|
|
players[playernum].pflags |= PF_WANTSTOJOIN; //players[playernum].spectator = false;
|
|
|
|
//If joining after hidetime in normal tag, default to being IT.
|
|
if (gametype == GT_TAG && (leveltime > (hidetime * TICRATE)))
|
|
{
|
|
NetPacket.packet.newteam = 1; //minor hack, causes the "is it" message to be printed later.
|
|
players[playernum].pflags |= PF_TAGIT; //make the player IT.
|
|
}
|
|
}
|
|
}
|
|
else if (G_GametypeHasTeams())
|
|
{
|
|
if (!NetPacket.packet.newteam)
|
|
{
|
|
players[playernum].ctfteam = 0;
|
|
players[playernum].spectator = true;
|
|
}
|
|
else
|
|
{
|
|
players[playernum].ctfteam = NetPacket.packet.newteam;
|
|
players[playernum].pflags |= PF_WANTSTOJOIN; //players[playernum].spectator = false;
|
|
}
|
|
}
|
|
else if (G_GametypeHasSpectators())
|
|
{
|
|
if (!NetPacket.packet.newteam)
|
|
players[playernum].spectator = true;
|
|
else
|
|
players[playernum].pflags |= PF_WANTSTOJOIN; //players[playernum].spectator = false;
|
|
}
|
|
|
|
if (NetPacket.packet.autobalance)
|
|
{
|
|
if (NetPacket.packet.newteam == 1)
|
|
CONS_Printf(M_GetText("%s was autobalanced to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80');
|
|
else if (NetPacket.packet.newteam == 2)
|
|
CONS_Printf(M_GetText("%s was autobalanced to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80');
|
|
}
|
|
else if (NetPacket.packet.scrambled)
|
|
{
|
|
if (NetPacket.packet.newteam == 1)
|
|
CONS_Printf(M_GetText("%s was scrambled to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80');
|
|
else if (NetPacket.packet.newteam == 2)
|
|
CONS_Printf(M_GetText("%s was scrambled to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80');
|
|
}
|
|
else if (NetPacket.packet.newteam == 1)
|
|
{
|
|
if (G_TagGametype())
|
|
CONS_Printf(M_GetText("%s is now IT!\n"), player_names[playernum]);
|
|
else
|
|
CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80');
|
|
}
|
|
else if (NetPacket.packet.newteam == 2)
|
|
{
|
|
if (G_TagGametype())
|
|
CONS_Printf(M_GetText("%s is no longer IT!\n"), player_names[playernum]);
|
|
else
|
|
CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80');
|
|
}
|
|
else if (NetPacket.packet.newteam == 0 && !wasspectator)
|
|
HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false); // "entered the game" text was moved to P_SpectatorJoinGame
|
|
|
|
//reset view if you are changed, or viewing someone who was changed.
|
|
if (playernum == consoleplayer || displayplayers[0] == playernum)
|
|
displayplayers[0] = consoleplayer;
|
|
|
|
if (G_GametypeHasTeams())
|
|
{
|
|
if (NetPacket.packet.newteam)
|
|
{
|
|
if (playernum == consoleplayer) //CTF and Team Match colors.
|
|
CV_SetValue(&cv_playercolor, NetPacket.packet.newteam + 5);
|
|
else if (playernum == displayplayers[1])
|
|
CV_SetValue(&cv_playercolor2, NetPacket.packet.newteam + 5);
|
|
else if (playernum == displayplayers[2])
|
|
CV_SetValue(&cv_playercolor3, NetPacket.packet.newteam + 5);
|
|
else if (playernum == displayplayers[3])
|
|
CV_SetValue(&cv_playercolor4, NetPacket.packet.newteam + 5);
|
|
}
|
|
}
|
|
|
|
if (gamestate != GS_LEVEL)
|
|
return;
|
|
|
|
demo_extradata[playernum] |= DXD_PLAYSTATE;
|
|
|
|
// Clear player score and rings if a spectator.
|
|
if (players[playernum].spectator)
|
|
{
|
|
players[playernum].spectatorreentry = (cv_spectatorreentry.value * TICRATE);
|
|
|
|
if (G_BattleGametype()) // SRB2kart
|
|
{
|
|
players[playernum].marescore = 0;
|
|
|
|
if (K_IsPlayerWanted(&players[playernum]))
|
|
K_CalculateBattleWanted();
|
|
}
|
|
|
|
players[playernum].health = 1;
|
|
if (players[playernum].mo)
|
|
players[playernum].mo->health = 1;
|
|
}
|
|
|
|
if (G_BattleGametype())
|
|
K_CheckBumpers(); // SRB2Kart
|
|
else if (G_RaceGametype())
|
|
P_CheckRacers(); // also SRB2Kart
|
|
}
|
|
|
|
//
|
|
// Attempts to make password system a little sane without
|
|
// rewriting the entire goddamn XD_file system
|
|
//
|
|
#include "md5.h"
|
|
static void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest)
|
|
{
|
|
#ifdef NOMD5
|
|
(void)buffer;
|
|
(void)len;
|
|
(void)salt;
|
|
memset(dest, 0, 16);
|
|
#else
|
|
char tmpbuf[256];
|
|
const size_t sl = strlen(salt);
|
|
|
|
if (len > 256-sl)
|
|
len = 256-sl;
|
|
|
|
memcpy(tmpbuf, buffer, len);
|
|
memmove(&tmpbuf[len], salt, sl);
|
|
//strcpy(&tmpbuf[len], salt);
|
|
len += strlen(salt);
|
|
if (len < 256)
|
|
memset(&tmpbuf[len],0,256-len);
|
|
|
|
// Yes, we intentionally md5 the ENTIRE buffer regardless of size...
|
|
md5_buffer(tmpbuf, 256, dest);
|
|
#endif
|
|
}
|
|
|
|
#define BASESALT "basepasswordstorage"
|
|
static UINT8 adminpassmd5[MD5_LEN];
|
|
static boolean adminpasswordset = false;
|
|
|
|
void D_SetPassword(const char *pw)
|
|
{
|
|
D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &adminpassmd5);
|
|
adminpasswordset = true;
|
|
}
|
|
|
|
// Remote Administration
|
|
static void Command_Changepassword_f(void)
|
|
{
|
|
#ifdef NOMD5
|
|
// If we have no MD5 support then completely disable XD_LOGIN responses for security.
|
|
CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n");
|
|
#else
|
|
if (client) // cannot change remotely
|
|
{
|
|
CONS_Printf(M_GetText("Only the server can use this.\n"));
|
|
return;
|
|
}
|
|
|
|
if (COM_Argc() != 2)
|
|
{
|
|
CONS_Printf(M_GetText("password <password>: change remote admin password\n"));
|
|
return;
|
|
}
|
|
|
|
D_SetPassword(COM_Argv(1));
|
|
CONS_Printf(M_GetText("Password set.\n"));
|
|
#endif
|
|
}
|
|
|
|
static void Command_Login_f(void)
|
|
{
|
|
#ifdef NOMD5
|
|
// If we have no MD5 support then completely disable XD_LOGIN responses for security.
|
|
CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n");
|
|
#else
|
|
UINT8 finalmd5[MD5_LEN];
|
|
const char *pw;
|
|
|
|
if (!netgame)
|
|
{
|
|
CONS_Printf(M_GetText("This only works in a netgame.\n"));
|
|
return;
|
|
}
|
|
|
|
// If the server uses login, it will effectively just remove admin privileges
|
|
// from whoever has them. This is good.
|
|
if (COM_Argc() != 2)
|
|
{
|
|
CONS_Printf(M_GetText("login <password>: Administrator login\n"));
|
|
return;
|
|
}
|
|
|
|
pw = COM_Argv(1);
|
|
|
|
// Do the base pass to get what the server has (or should?)
|
|
D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &finalmd5);
|
|
|
|
// Do the final pass to get the comparison the server will come up with
|
|
D_MD5PasswordPass(finalmd5, MD5_LEN, va("PNUM%02d", consoleplayer), &finalmd5);
|
|
|
|
CONS_Printf(M_GetText("Sending login... (Notice only given if password is correct.)\n"));
|
|
|
|
SendNetXCmd(XD_LOGIN, finalmd5, MD5_LEN);
|
|
#endif
|
|
}
|
|
|
|
static void Got_Login(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
#ifdef NOMD5
|
|
// If we have no MD5 support then completely disable XD_LOGIN responses for security.
|
|
(void)cp;
|
|
(void)playernum;
|
|
#else
|
|
UINT8 sentmd5[MD5_LEN], finalmd5[MD5_LEN];
|
|
|
|
READMEM(*cp, sentmd5, MD5_LEN);
|
|
|
|
if (client)
|
|
return;
|
|
|
|
if (!adminpasswordset)
|
|
{
|
|
CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[playernum]);
|
|
return;
|
|
}
|
|
|
|
// Do the final pass to compare with the sent md5
|
|
D_MD5PasswordPass(adminpassmd5, MD5_LEN, va("PNUM%02d", playernum), &finalmd5);
|
|
|
|
if (!memcmp(sentmd5, finalmd5, MD5_LEN))
|
|
{
|
|
CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[playernum]);
|
|
COM_BufInsertText(va("promote %d\n", playernum)); // do this immediately
|
|
}
|
|
else
|
|
CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[playernum]);
|
|
#endif
|
|
}
|
|
|
|
boolean IsPlayerAdmin(INT32 playernum)
|
|
{
|
|
INT32 i;
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
if (playernum == adminplayers[i])
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void SetAdminPlayer(INT32 playernum)
|
|
{
|
|
INT32 i;
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (playernum == adminplayers[i])
|
|
return; // Player is already admin
|
|
|
|
if (adminplayers[i] == -1)
|
|
{
|
|
adminplayers[i] = playernum; // Set the player to a free spot
|
|
break; // End the loop now. If it keeps going, the same player might get assigned to two slots.
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClearAdminPlayers(void)
|
|
{
|
|
INT32 i;
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
adminplayers[i] = -1;
|
|
}
|
|
|
|
void RemoveAdminPlayer(INT32 playernum)
|
|
{
|
|
INT32 i;
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
if (playernum == adminplayers[i])
|
|
adminplayers[i] = -1;
|
|
}
|
|
|
|
static void Command_Verify_f(void)
|
|
{
|
|
char buf[8]; // Should be plenty
|
|
char *temp;
|
|
INT32 playernum;
|
|
|
|
if (client)
|
|
{
|
|
CONS_Printf(M_GetText("Only the server can use this.\n"));
|
|
return;
|
|
}
|
|
|
|
if (!netgame)
|
|
{
|
|
CONS_Printf(M_GetText("This only works in a netgame.\n"));
|
|
return;
|
|
}
|
|
|
|
if (COM_Argc() != 2)
|
|
{
|
|
CONS_Printf(M_GetText("promote <node>: give admin privileges to a node\n"));
|
|
return;
|
|
}
|
|
|
|
strlcpy(buf, COM_Argv(1), sizeof (buf));
|
|
|
|
playernum = atoi(buf);
|
|
|
|
temp = buf;
|
|
|
|
WRITEUINT8(temp, playernum);
|
|
|
|
if (playeringame[playernum])
|
|
SendNetXCmd(XD_VERIFIED, buf, 1);
|
|
}
|
|
|
|
static void Got_Verification(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
INT16 num = READUINT8(*cp);
|
|
|
|
if (playernum != serverplayer) // it's not from the server (hacker or bug)
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal verification received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
SetAdminPlayer(num);
|
|
|
|
if (num != consoleplayer)
|
|
return;
|
|
|
|
CONS_Printf(M_GetText("You are now a server administrator.\n"));
|
|
}
|
|
|
|
static void Command_RemoveAdmin_f(void)
|
|
{
|
|
char buf[8]; // Should be plenty
|
|
char *temp;
|
|
INT32 playernum;
|
|
|
|
if (client)
|
|
{
|
|
CONS_Printf(M_GetText("Only the server can use this.\n"));
|
|
return;
|
|
}
|
|
|
|
if (COM_Argc() != 2)
|
|
{
|
|
CONS_Printf(M_GetText("demote <node>: remove admin privileges from a node\n"));
|
|
return;
|
|
}
|
|
|
|
strlcpy(buf, COM_Argv(1), sizeof(buf));
|
|
|
|
playernum = atoi(buf);
|
|
|
|
temp = buf;
|
|
|
|
WRITEUINT8(temp, playernum);
|
|
|
|
if (playeringame[playernum])
|
|
SendNetXCmd(XD_DEMOTED, buf, 1);
|
|
}
|
|
|
|
static void Got_Removal(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
INT16 num = READUINT8(*cp);
|
|
|
|
if (playernum != serverplayer) // it's not from the server (hacker or bug)
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal demotion received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
RemoveAdminPlayer(num);
|
|
|
|
if (num != consoleplayer)
|
|
return;
|
|
|
|
CONS_Printf(M_GetText("You are no longer a server administrator.\n"));
|
|
}
|
|
|
|
static void Command_MotD_f(void)
|
|
{
|
|
size_t i, j;
|
|
char *mymotd;
|
|
|
|
if ((j = COM_Argc()) < 2)
|
|
{
|
|
CONS_Printf(M_GetText("motd <message>: Set a message that clients see upon join.\n"));
|
|
return;
|
|
}
|
|
|
|
if (!(server || (IsPlayerAdmin(consoleplayer))))
|
|
{
|
|
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
|
|
return;
|
|
}
|
|
|
|
mymotd = Z_Malloc(sizeof(motd), PU_STATIC, NULL);
|
|
|
|
if (!mymotd)
|
|
return;
|
|
//I_Error("Command_MotD_f: Out of memory!\n"); // idk if this aint a bit too much lel
|
|
|
|
strlcpy(mymotd, COM_Argv(1), sizeof(motd));
|
|
|
|
for (i = 2; i < j; i++)
|
|
{
|
|
strlcat(mymotd, " ", sizeof(motd));
|
|
strlcat(mymotd, COM_Argv(i), sizeof(motd));
|
|
}
|
|
|
|
// Disallow non-printing characters and semicolons.
|
|
for (i = 0; mymotd[i] != '\0'; i++)
|
|
{
|
|
if (!isprint(mymotd[i]) || mymotd[i] == ';')
|
|
{
|
|
Z_Free(mymotd);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((netgame || multiplayer) && client)
|
|
{
|
|
SendNetXCmd(XD_SETMOTD, mymotd, i); // send the actual size of the motd string, not the full buffer's size
|
|
}
|
|
else
|
|
{
|
|
strcpy(motd, mymotd);
|
|
CONS_Printf(M_GetText("Message of the day set.\n"));
|
|
}
|
|
|
|
Z_Free(mymotd);
|
|
}
|
|
|
|
static void Got_MotD_f(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
char *mymotd = Z_Malloc(sizeof(motd), PU_STATIC, NULL);
|
|
INT32 i;
|
|
boolean kick = false;
|
|
|
|
READSTRINGN(*cp, mymotd, sizeof(motd));
|
|
|
|
// Disallow non-printing characters and semicolons.
|
|
for (i = 0; mymotd[i] != '\0'; i++)
|
|
if (!isprint(mymotd[i]) || mymotd[i] == ';')
|
|
kick = true;
|
|
|
|
if ((playernum != serverplayer && !IsPlayerAdmin(playernum)) || kick)
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal motd change received from %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
|
|
Z_Free(mymotd);
|
|
return;
|
|
}
|
|
|
|
strcpy(motd, mymotd);
|
|
|
|
CONS_Printf(M_GetText("Message of the day set.\n"));
|
|
|
|
Z_Free(mymotd);
|
|
}
|
|
|
|
static void Command_RunSOC(void)
|
|
{
|
|
const char *fn;
|
|
char buf[255];
|
|
size_t length = 0;
|
|
|
|
if (COM_Argc() != 2)
|
|
{
|
|
CONS_Printf(M_GetText("runsoc <socfile.soc> or <lumpname>: run a soc\n"));
|
|
return;
|
|
}
|
|
else
|
|
fn = COM_Argv(1);
|
|
|
|
if (netgame && !(server || IsPlayerAdmin(consoleplayer)))
|
|
{
|
|
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
|
|
return;
|
|
}
|
|
|
|
if (!(netgame || multiplayer))
|
|
{
|
|
if (!P_RunSOC(fn))
|
|
CONS_Printf(M_GetText("Could not find SOC.\n"));
|
|
else
|
|
G_SetGameModified(multiplayer, false);
|
|
return;
|
|
}
|
|
|
|
nameonly(strcpy(buf, fn));
|
|
length = strlen(buf)+1;
|
|
|
|
SendNetXCmd(XD_RUNSOC, buf, length);
|
|
}
|
|
|
|
static void Got_RunSOCcmd(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
char filename[256];
|
|
filestatus_t ncs = FS_NOTCHECKED;
|
|
|
|
if (playernum != serverplayer && !IsPlayerAdmin(playernum))
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal runsoc command received from %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
READSTRINGN(*cp, filename, 255);
|
|
|
|
// Maybe add md5 support?
|
|
if (strstr(filename, ".soc") != NULL)
|
|
{
|
|
ncs = findfile(filename,NULL,true);
|
|
|
|
if (ncs != FS_FOUND)
|
|
{
|
|
Command_ExitGame_f();
|
|
if (ncs == FS_NOTFOUND)
|
|
{
|
|
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);
|
|
M_StartMessage(va("The server added a file\n(%s)\nthat you do not have.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
|
|
}
|
|
else
|
|
{
|
|
CONS_Printf(M_GetText("Unknown error finding soc file (%s) the server added.\n"), filename);
|
|
M_StartMessage(va("Unknown error trying to load a file\nthat the server added\n(%s).\n\nPress ESC\n",filename), NULL, MM_NOTHING);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
P_RunSOC(filename);
|
|
G_SetGameModified(true, false);
|
|
}
|
|
|
|
static void Command_Addfilelocal(void)
|
|
{
|
|
const char *fn;
|
|
INT32 i;
|
|
|
|
if (COM_Argc() != 2)
|
|
{
|
|
CONS_Printf(M_GetText("addfilelocal <wadfile.wad>: load wad file\n"));
|
|
return;
|
|
}
|
|
else
|
|
fn = COM_Argv(1);
|
|
|
|
// Disallow non-printing characters and semicolons.
|
|
for (i = 0; fn[i] != '\0'; i++)
|
|
if (!isprint(fn[i]) || fn[i] == ';')
|
|
return;
|
|
|
|
// Add any wad file, ignoring checks for if it contains complex things like
|
|
// lua. Great for complex but client-side customizations, like different
|
|
// level cards or anything like that.
|
|
P_AddWadFile(fn, true);
|
|
}
|
|
|
|
static void CloseFileHandle(FILE **fhandle)
|
|
{
|
|
if (*fhandle)
|
|
{
|
|
fclose(*fhandle);
|
|
*fhandle = NULL;
|
|
}
|
|
}
|
|
|
|
/** Adds a pwad at runtime.
|
|
* Searches for sounds, maps, music, new images.
|
|
*/
|
|
static void Command_Addfile(void)
|
|
{
|
|
const char *fn, *p;
|
|
char buf[256];
|
|
char *buf_p = buf;
|
|
INT32 i;
|
|
int musiconly = -1; // W_VerifyNMUSlumps isn't boolean
|
|
|
|
CLEANUP(CloseFileHandle) FILE *fhandle = NULL;
|
|
|
|
if (COM_Argc() != 2)
|
|
{
|
|
CONS_Printf(M_GetText("addfile <wadfile.wad>: load wad file\n"));
|
|
return;
|
|
}
|
|
else
|
|
fn = COM_Argv(1);
|
|
|
|
// Disallow non-printing characters and semicolons.
|
|
for (i = 0; fn[i] != '\0'; i++)
|
|
if (!isprint(fn[i]) || fn[i] == ';')
|
|
return;
|
|
|
|
if ((fhandle = W_OpenWadFile(&fn, true)) != NULL)
|
|
{
|
|
musiconly = W_VerifyNMUSlumps(fn, fhandle, false);
|
|
}
|
|
|
|
if (fhandle == NULL || musiconly == -1)
|
|
return; // file not found
|
|
|
|
if (!musiconly)
|
|
{
|
|
// ... But only so long as they contain nothing more then music and sprites.
|
|
if (netgame && !(server || IsPlayerAdmin(consoleplayer)))
|
|
{
|
|
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
|
|
return;
|
|
}
|
|
G_SetGameModified(multiplayer, false);
|
|
}
|
|
|
|
// Add file on your client directly if it is trivial, or you aren't in a netgame.
|
|
if (!netgame || musiconly)
|
|
{
|
|
P_AddWadFile(fn, false);
|
|
return;
|
|
}
|
|
|
|
p = fn+strlen(fn);
|
|
while(--p >= fn)
|
|
if (*p == '\\' || *p == '/' || *p == ':')
|
|
break;
|
|
++p;
|
|
|
|
// calculate and check md5
|
|
{
|
|
UINT8 md5sum[16];
|
|
#ifdef NOMD5
|
|
memset(md5sum, 0, 16);
|
|
#else
|
|
|
|
{
|
|
tic_t t = I_GetTime();
|
|
CONS_Debug(DBG_SETUP, "Making MD5 for %s\n",fn);
|
|
md5_stream(fhandle, md5sum);
|
|
CONS_Debug(DBG_SETUP, "MD5 calc for %s took %f second\n", fn, (float)(I_GetTime() - t)/TICRATE);
|
|
}
|
|
|
|
for (i = 0; i < numwadfiles; i++)
|
|
{
|
|
if (!memcmp(wadfiles[i]->md5sum, md5sum, 16))
|
|
{
|
|
CONS_Alert(CONS_ERROR, M_GetText("%s is already loaded\n"), fn);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
// Finally okay to write this important data
|
|
WRITESTRINGN(buf_p, p, 240);
|
|
WRITEMEM(buf_p, md5sum, 16);
|
|
}
|
|
|
|
if (IsPlayerAdmin(consoleplayer) && (!server)) // Request to add file
|
|
SendNetXCmd(XD_REQADDFILE, buf, buf_p - buf);
|
|
else
|
|
SendNetXCmd(XD_ADDFILE, buf, buf_p - buf);
|
|
}
|
|
|
|
/** Adds something at runtime.
|
|
*/
|
|
static void Command_Addskins(void)
|
|
{
|
|
CONS_Printf("addskins has been deprecated\nuse addfilelocal instead!\n");
|
|
}
|
|
|
|
static void Command_GLocalSkin(void)
|
|
{
|
|
size_t first_option;
|
|
size_t option_display;
|
|
size_t option_all;
|
|
size_t option_player;
|
|
|
|
option_player = COM_CheckPartialParm("-p");
|
|
option_display = COM_CheckPartialParm("-d");
|
|
option_all = COM_CheckPartialParm("-a");
|
|
|
|
CLEANUP(Z_Pfree) char* fuck = NULL; // local skin name
|
|
|
|
if (!(first_option = COM_FirstOption()))
|
|
first_option = COM_Argc();
|
|
|
|
if (first_option < 2)
|
|
{
|
|
/* holy fucking shit */
|
|
CONS_Printf("localskin <name> [-player <name>] [-display <number>] [-all]:\n");
|
|
CONS_Printf(M_GetText("Set a localskin via its internal name (usually printed on the console).\n\n\
|
|
* Using \"-player\" will set a localskin to a specified player.\n\
|
|
Defaults to yourself.\n\
|
|
* Using \"-display\" will set a localskin to the displayed player.\n\
|
|
Defaults to 0, which is the first player displayed.\n\
|
|
Can go up to 3 for splitscreen.\n\
|
|
* Using \"-all\" will set a localskin to ALL players.\n\
|
|
* \"localskin none\" removes the localskin, just like how\n\
|
|
\"forceskin none\" does.\n"));
|
|
return;
|
|
}
|
|
|
|
fuck = ConcatCommandArgv(1, first_option);
|
|
|
|
const char* player_name = player_names[consoleplayer];
|
|
const char* display_num = "0";
|
|
size_t dnum = 0;
|
|
|
|
if (option_display) // -display
|
|
{
|
|
// handle default: 0
|
|
if (COM_Argc() >= option_display)
|
|
display_num = COM_Argv(option_display + 1);
|
|
|
|
dnum = atoi(display_num);
|
|
if (dnum > 3) // nuh uh
|
|
dnum = 0;
|
|
|
|
SetLocalPlayerSkin(displayplayers[dnum], fuck, NULL);
|
|
|
|
CONS_Printf("Successfully applied localskin to displayed player.\n");
|
|
|
|
return;
|
|
}
|
|
else if (option_all) // -all
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
|
{
|
|
if (!playeringame[i])
|
|
continue;
|
|
SetLocalPlayerSkin(i, fuck, NULL);
|
|
}
|
|
CONS_Printf("Successfully applied localskin to all players.\n");
|
|
|
|
return;
|
|
}
|
|
else // -player or no other arguments
|
|
{
|
|
if (option_player)
|
|
{
|
|
int i;
|
|
player_name = COM_Argv(option_player + 1);
|
|
|
|
for (i = 0; i < MAXPLAYERS; ++i)
|
|
{
|
|
if (fasticmp(player_names[i], player_name))
|
|
SetLocalPlayerSkin(i, fuck, NULL);
|
|
}
|
|
CONS_Printf("Successfully applied localskin to specified player.\n");
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
SetLocalPlayerSkin(consoleplayer, fuck, &cv_localskin);
|
|
CONS_Printf("Successfully applied localskin.\n");
|
|
|
|
return;
|
|
}
|
|
|
|
CONS_Printf("Could not apply localskin.\n");
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void Got_RequestAddfilecmd(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
char filename[241];
|
|
filestatus_t ncs = FS_NOTCHECKED;
|
|
UINT8 md5sum[16];
|
|
boolean kick = false;
|
|
boolean toomany = false;
|
|
INT32 i,j;
|
|
|
|
READSTRINGN(*cp, filename, 240);
|
|
READMEM(*cp, md5sum, 16);
|
|
|
|
// Only the server processes this message.
|
|
if (client)
|
|
return;
|
|
|
|
// Disallow non-printing characters and semicolons.
|
|
for (i = 0; filename[i] != '\0'; i++)
|
|
if (!isprint(filename[i]) || filename[i] == ';')
|
|
kick = true;
|
|
|
|
if ((playernum != serverplayer && !IsPlayerAdmin(playernum)) || kick)
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal addfile command received from %s\n"), player_names[playernum]);
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
return;
|
|
}
|
|
|
|
// See W_LoadWadFile in w_wad.c
|
|
if (numwadfiles >= MAX_WADFILES)
|
|
toomany = true;
|
|
else
|
|
ncs = findfile(filename,md5sum,true);
|
|
|
|
if (ncs != FS_FOUND || toomany)
|
|
{
|
|
char message[275];
|
|
|
|
if (toomany)
|
|
sprintf(message, M_GetText("Too many files loaded to add %s\n"), filename);
|
|
else if (ncs == FS_NOTFOUND)
|
|
sprintf(message, M_GetText("The server doesn't have %s\n"), filename);
|
|
else if (ncs == FS_MD5SUMBAD)
|
|
sprintf(message, M_GetText("Checksum mismatch on %s\n"), filename);
|
|
else
|
|
sprintf(message, M_GetText("Unknown error finding wad file (%s)\n"), filename);
|
|
|
|
CONS_Printf("%s",message);
|
|
|
|
for (j = 0; j < MAXPLAYERS; j++)
|
|
if (adminplayers[j])
|
|
COM_BufAddText(va("sayto %d %s", adminplayers[j], message));
|
|
|
|
return;
|
|
}
|
|
|
|
COM_BufAddText(va("addfile %s\n", filename));
|
|
}
|
|
|
|
static void Got_Addfilecmd(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
char filename[241];
|
|
filestatus_t ncs = FS_NOTCHECKED;
|
|
UINT8 md5sum[16];
|
|
|
|
READSTRINGN(*cp, filename, 240);
|
|
READMEM(*cp, md5sum, 16);
|
|
|
|
if (playernum != serverplayer)
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal addfile command received from %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ncs = findfile(filename, md5sum, true);
|
|
|
|
if (ncs != FS_FOUND || !P_AddWadFile(filename, false))
|
|
{
|
|
Command_ExitGame_f();
|
|
if (ncs == FS_FOUND)
|
|
{
|
|
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);
|
|
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);
|
|
}
|
|
else if (ncs == FS_NOTFOUND)
|
|
{
|
|
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);
|
|
M_StartMessage(va("The server added a file \n(%s)\nthat you do not have.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
|
|
}
|
|
else if (ncs == FS_MD5SUMBAD)
|
|
{
|
|
CONS_Printf(M_GetText("Checksum mismatch while loading %s.\nMake sure you have the copy of\nthis file that the server has.\n"), filename);
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
CONS_Printf(M_GetText("Unknown error finding wad file (%s) the server added.\n"), filename);
|
|
M_StartMessage(va("Unknown error trying to load a file\nthat the server added \n(%s).\n\nPress ESC\n",filename), NULL, MM_NOTHING);
|
|
}
|
|
return;
|
|
}
|
|
|
|
G_SetGameModified(true, false);
|
|
}
|
|
|
|
static void Command_ListWADS_f(void)
|
|
{
|
|
INT32 i = numwadfiles;
|
|
char *tempname;
|
|
CONS_Printf(M_GetText("There are %d wads loaded:\n"),numwadfiles);
|
|
for (i--; i >= 0; i--)
|
|
{
|
|
nameonly(tempname = va("%s", wadfiles[i]->filename));
|
|
if (!i)
|
|
CONS_Printf("\x82 IWAD\x80: %s\n", tempname);
|
|
else if (i <= mainwads)
|
|
CONS_Printf("\x82 * %.2d\x80: %s\n", i, tempname);
|
|
else
|
|
CONS_Printf(" %.2d: %s\n", i, tempname);
|
|
}
|
|
}
|
|
|
|
static void Command_LocateLump_f(void)
|
|
{
|
|
if (COM_Argc() == 1)
|
|
{
|
|
CONS_Printf("Usage: locatelump <lump1>[ <lump2>[ ...]]\n");
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 1; i < COM_Argc(); ++i)
|
|
{
|
|
const char *name = COM_Argv(i);
|
|
|
|
lumpnum_t num = W_CheckNumForName(name);
|
|
|
|
const char *wadname = "(not found)";
|
|
|
|
if (num != LUMPERROR)
|
|
{
|
|
wadname = wadfiles[WADFILENUM(num)]->filename;
|
|
}
|
|
|
|
CONS_Printf("%s - %s\n", name, wadname);
|
|
}
|
|
}
|
|
|
|
static void Command_ListDoomednums_f(void)
|
|
{
|
|
INT16 i, j, k = 0, l = 0;
|
|
INT32 argc = COM_Argc();
|
|
INT16 table[MAXDOOMEDNUM];
|
|
|
|
switch (argc)
|
|
{
|
|
case 1:
|
|
l = MAXDOOMEDNUM;
|
|
break;
|
|
case 3:
|
|
l = atoi(COM_Argv(2));
|
|
if (l < 1 || l > MAXDOOMEDNUM)
|
|
{
|
|
CONS_Printf("arg 2: doomednum \x82""%d \x85out of range (1-4095)\n", k);
|
|
return;
|
|
}
|
|
//FALLTHRU
|
|
case 2:
|
|
k = atoi(COM_Argv(1));
|
|
if (k < 1 || k > MAXDOOMEDNUM)
|
|
{
|
|
CONS_Printf("arg 1: doomednum \x82""%d \x85out of range (1-4095)\n", k);
|
|
return;
|
|
}
|
|
if (!l)
|
|
l = k;
|
|
else if (l < k) // silently and helpfully swap.
|
|
{
|
|
j = k;
|
|
k = l;
|
|
l = j;
|
|
}
|
|
break;
|
|
default:
|
|
CONS_Printf("listmapthings: \x86too many arguments!\n");
|
|
return;
|
|
}
|
|
|
|
// see P_SpawnNonMobjMapThing
|
|
memset(table, 0, sizeof(table));
|
|
for (i = 1; i <= MAXPLAYERS; i++)
|
|
table[i-1] = MT_PLAYER; // playerstarts
|
|
table[33-1] = table[34-1] = table[35-1] = MT_PLAYER; // battle/team starts
|
|
table[750-1] = table[777-1] = table[778-1] = MT_UNKNOWN; // slopes
|
|
for (i = 600; i <= 609; i++)
|
|
table[i-1] = MT_RING; // placement patterns
|
|
table[1705-1] = table[1713-1] = MT_HOOP; // types of hoop
|
|
|
|
CONS_Printf("\x82""Checking for double defines...\n");
|
|
for (i = 1; i < MT_FIRSTFREESLOT+NUMMOBJFREESLOTS; i++)
|
|
{
|
|
j = mobjinfo[i].doomednum;
|
|
if (j < (k ? k : 1) || j > (l ? l : MAXDOOMEDNUM))
|
|
continue;
|
|
if (table[j-1])
|
|
{
|
|
CONS_Printf(" doomednum \x82""%d""\x80 is \x85""double-defined\x80 by ", j);
|
|
if (i < MT_FIRSTFREESLOT)
|
|
{
|
|
CONS_Printf("\x87""hardcode %s <-- MAJOR ERROR\n", MOBJTYPE_LIST[i]);
|
|
continue;
|
|
}
|
|
CONS_Printf("\x81""freeslot MT_""%s\n", FREE_MOBJS[i-MT_FIRSTFREESLOT]);
|
|
continue;
|
|
}
|
|
table[j-1] = i;
|
|
}
|
|
CONS_Printf("\x82Printing doomednum usage...\n");
|
|
if (!k)
|
|
{
|
|
i = 35; // skip MT_PLAYER spam
|
|
CONS_Printf(" doomednums \x82""1-35""\x80 are used by ""\x87""hardcode MT_PLAYER\n");
|
|
}
|
|
else
|
|
i = k-1;
|
|
|
|
for (; i < l; i++)
|
|
{
|
|
if (!table[i])
|
|
{
|
|
if (k)
|
|
{
|
|
CONS_Printf(" doomednum \x82""%d""\x80 is \x83""free!", i+1);
|
|
if (i < 99) // above the humble crawla? how dare you
|
|
CONS_Printf(" (Don't freeslot this low...)");
|
|
CONS_Printf("\n");
|
|
}
|
|
continue;
|
|
}
|
|
CONS_Printf(" doomednum \x82""%d""\x80 is used by ", i+1);
|
|
if (table[i] < MT_FIRSTFREESLOT)
|
|
{
|
|
CONS_Printf("\x87""hardcode %s\n", MOBJTYPE_LIST[table[i]]);
|
|
continue;
|
|
}
|
|
CONS_Printf("\x81""freeslot MT_""%s\n", FREE_MOBJS[table[i]-MT_FIRSTFREESLOT]);
|
|
}
|
|
}
|
|
|
|
#undef MAXDOOMEDNUM
|
|
|
|
void Command_ListUnusedSprites_f(void)
|
|
{
|
|
size_t i, j;
|
|
|
|
CONS_Printf("\x82Printing sprite non-usage...\n");
|
|
|
|
for (i = 0; i < NUMSPRITES; i++)
|
|
{
|
|
if (sprites[i].numframes)
|
|
{
|
|
// We're only showing unused sprites...
|
|
continue;
|
|
}
|
|
|
|
if (i < SPR_FIRSTFREESLOT)
|
|
{
|
|
CONS_Printf(" \x87""hardcode SPR_""%.4s\n", sprnames[i]);
|
|
continue;
|
|
}
|
|
|
|
if (used_spr[(i-SPR_FIRSTFREESLOT)/8] == 0xFF)
|
|
{
|
|
for (j = 0; j < 8; j++)
|
|
{
|
|
CONS_Printf(" \x81""freeslot SPR_""%.4s\n", sprnames[i+j]);
|
|
}
|
|
|
|
i += j;
|
|
}
|
|
|
|
if (used_spr[(i-SPR_FIRSTFREESLOT)/8] & (1<<(i%8)))
|
|
{
|
|
CONS_Printf(" \x81""freeslot SPR_""%.4s\n", sprnames[i]);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Command_ListUnusedMapSlots_f(void)
|
|
{
|
|
INT32 i;
|
|
|
|
CONS_Printf("\x82Printing map slot non-usage...\n");
|
|
|
|
for (i = 0; i < NUMMAPS; i++)
|
|
{
|
|
// is checking this enough?
|
|
if (mapheaderinfo[i])
|
|
continue;
|
|
|
|
CONS_Printf("%s\n", G_BuildMapName(i+1));
|
|
}
|
|
}
|
|
|
|
|
|
// =========================================================================
|
|
// MISC. COMMANDS
|
|
// =========================================================================
|
|
|
|
/** Prints program version.
|
|
*/
|
|
static void Command_Version_f(void)
|
|
{
|
|
#ifdef DEVELOP
|
|
CONS_Printf("SRB2Kart %s-%s (%s %s)\n", compbranch, comprevision, compdate, comptime);
|
|
#else
|
|
CONS_Printf("SRB2Kart %s (%s %s %s)\n", VERSIONSTRING, compdate, comptime, comprevision);
|
|
#endif
|
|
|
|
// Base library
|
|
#if defined( HAVE_SDL)
|
|
CONS_Printf("SDL ");
|
|
#endif
|
|
|
|
// OS
|
|
CONS_Printf("%s ", I_GetSysName());
|
|
|
|
// Bitness
|
|
if (sizeof(void*) == 4)
|
|
CONS_Printf("32-bit ");
|
|
else if (sizeof(void*) == 8)
|
|
CONS_Printf("64-bit ");
|
|
else // 16-bit? 128-bit?
|
|
CONS_Printf("Bits Unknown ");
|
|
|
|
// Debug build
|
|
#ifdef _DEBUG
|
|
CONS_Printf("\x85" "DEBUG " "\x80");
|
|
#endif
|
|
|
|
// DEVELOP build
|
|
#ifdef DEVELOP
|
|
CONS_Printf("\x87" "DEVELOP " "\x80");
|
|
#endif
|
|
|
|
CONS_Printf("\n");
|
|
}
|
|
|
|
#ifdef UPDATE_ALERT
|
|
static void Command_ModDetails_f(void)
|
|
{
|
|
CONS_Printf(M_GetText("Mod ID: %d\nMod Version: %d\nCode Base:%d\n"), MODID, MODVERSION, CODEBASE);
|
|
}
|
|
#endif
|
|
|
|
// Returns current gametype being used.
|
|
//
|
|
static void Command_ShowGametype_f(void)
|
|
{
|
|
const char *gametypestr = NULL;
|
|
|
|
if (!(netgame || multiplayer)) // print "Single player" instead of "Race"
|
|
{
|
|
CONS_Printf(M_GetText("Current gametype is %s\n"), "Single Player");
|
|
return;
|
|
}
|
|
|
|
// get name string for current gametype
|
|
if (gametype >= 0 && gametype < NUMGAMETYPES)
|
|
gametypestr = Gametype_Names[gametype];
|
|
|
|
if (gametypestr)
|
|
CONS_Printf(M_GetText("Current gametype is %s\n"), gametypestr);
|
|
else // string for current gametype was not found above (should never happen)
|
|
CONS_Printf(M_GetText("Unknown gametype set (%d)\n"), gametype);
|
|
}
|
|
|
|
/** Plays the intro.
|
|
*/
|
|
static void Command_Playintro_f(void)
|
|
{
|
|
if (netgame)
|
|
return;
|
|
|
|
if (dirmenu)
|
|
closefilemenu(true);
|
|
|
|
F_StartIntro();
|
|
}
|
|
|
|
/** Quits the game immediately.
|
|
*/
|
|
FUNCNORETURN static ATTRNORETURN void Command_Quit_f(void)
|
|
{
|
|
I_Quit();
|
|
}
|
|
|
|
void ItemFinder_OnChange(void)
|
|
{
|
|
if (!cv_itemfinder.value)
|
|
return; // it's fine.
|
|
|
|
if (!M_SecretUnlocked(SECRET_ITEMFINDER))
|
|
{
|
|
CONS_Printf(M_GetText("You haven't earned this yet.\n"));
|
|
CV_StealthSetValue(&cv_itemfinder, 0);
|
|
return;
|
|
}
|
|
else if (netgame || multiplayer)
|
|
{
|
|
CONS_Printf(M_GetText("This only works in single player.\n"));
|
|
CV_StealthSetValue(&cv_itemfinder, 0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/** Deals with a pointlimit change by printing the change to the console.
|
|
* If the gametype is single player, cooperative, or race, the pointlimit is
|
|
* silently disabled again.
|
|
*
|
|
* Timelimit and pointlimit can be used at the same time.
|
|
*
|
|
* We don't check immediately for the pointlimit having been reached,
|
|
* because you would get "caught" when turning it up in the menu.
|
|
* \sa cv_pointlimit, TimeLimit_OnChange
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void PointLimit_OnChange(void)
|
|
{
|
|
// Don't allow pointlimit in Single Player/Co-Op/Race!
|
|
if (server && Playing() && G_RaceGametype())
|
|
{
|
|
if (cv_pointlimit.value)
|
|
CV_StealthSetValue(&cv_pointlimit, 0);
|
|
return;
|
|
}
|
|
|
|
if (cv_pointlimit.value)
|
|
{
|
|
CONS_Printf(M_GetText("Levels will end after %s scores %d point%s.\n"),
|
|
G_GametypeHasTeams() ? M_GetText("a team") : M_GetText("someone"),
|
|
cv_pointlimit.value,
|
|
cv_pointlimit.value > 1 ? "s" : "");
|
|
}
|
|
else if (netgame || multiplayer)
|
|
CONS_Printf(M_GetText("Point limit disabled\n"));
|
|
}
|
|
|
|
static void NumLaps_OnChange(void)
|
|
{
|
|
if (!G_RaceGametype() || (modeattacking || demo.playback))
|
|
return;
|
|
|
|
if (server && Playing()
|
|
&& (netgame || multiplayer)
|
|
&& (mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE)
|
|
&& (cv_numlaps.value > mapheaderinfo[gamemap - 1]->numlaps))
|
|
CV_StealthSetValue(&cv_numlaps, mapheaderinfo[gamemap - 1]->numlaps);
|
|
|
|
// Just don't be verbose
|
|
CONS_Printf(M_GetText("Number of laps set to %d\n"), cv_numlaps.value);
|
|
}
|
|
|
|
static void NetTimeout_OnChange(void)
|
|
{
|
|
connectiontimeout = (tic_t)cv_nettimeout.value;
|
|
}
|
|
|
|
static void JoinTimeout_OnChange(void)
|
|
{
|
|
jointimeout = (tic_t)cv_jointimeout.value;
|
|
}
|
|
|
|
UINT32 timelimitintics = 0;
|
|
|
|
/** Deals with a timelimit change by printing the change to the console.
|
|
* If the gametype is single player, cooperative, or race, the timelimit is
|
|
* silently disabled again.
|
|
*
|
|
* Timelimit and pointlimit can be used at the same time.
|
|
*
|
|
* \sa cv_timelimit, PointLimit_OnChange
|
|
*/
|
|
static void TimeLimit_OnChange(void)
|
|
{
|
|
// Don't allow timelimit in Single Player/Co-Op/Race!
|
|
if (server && Playing() && cv_timelimit.value != 0 && G_RaceGametype())
|
|
{
|
|
CV_SetValue(&cv_timelimit, 0);
|
|
return;
|
|
}
|
|
|
|
if (cv_timelimit.value != 0)
|
|
{
|
|
CONS_Printf(M_GetText("Levels will end after %d minute%s.\n"),cv_timelimit.value,cv_timelimit.value == 1 ? "" : "s"); // Graue 11-17-2003
|
|
timelimitintics = cv_timelimit.value * 60 * TICRATE;
|
|
|
|
//add hidetime for tag too!
|
|
if (G_TagGametype())
|
|
timelimitintics += hidetime * TICRATE;
|
|
|
|
// Note the deliberate absence of any code preventing
|
|
// pointlimit and timelimit from being set simultaneously.
|
|
// Some people might like to use them together. It works.
|
|
}
|
|
else if (netgame || multiplayer)
|
|
CONS_Printf(M_GetText("Time limit disabled\n"));
|
|
|
|
#ifdef HAVE_DISCORDRPC
|
|
DRPC_UpdatePresence();
|
|
#endif
|
|
}
|
|
|
|
/** Adjusts certain settings to match a changed gametype.
|
|
*
|
|
* \param lastgametype The gametype we were playing before now.
|
|
* \sa D_MapChange
|
|
* \author Graue <graue@oceanbase.org>
|
|
* \todo Get rid of the hardcoded stuff, ugly stuff, etc.
|
|
*/
|
|
void D_GameTypeChanged(INT32 lastgametype)
|
|
{
|
|
if (netgame)
|
|
{
|
|
const char *oldgt = NULL, *newgt = NULL;
|
|
|
|
if (lastgametype >= 0 && lastgametype < NUMGAMETYPES)
|
|
oldgt = Gametype_Names[lastgametype];
|
|
if (gametype >= 0 && lastgametype < NUMGAMETYPES)
|
|
newgt = Gametype_Names[gametype];
|
|
|
|
if (oldgt && newgt)
|
|
CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), oldgt, newgt);
|
|
}
|
|
// Only do the following as the server, not as remote admin.
|
|
// There will always be a server, and this only needs to be done once.
|
|
if (server && (multiplayer || netgame))
|
|
{
|
|
if (gametype == GT_COMPETITION || gametype == GT_COOP)
|
|
CV_SetValue(&cv_itemrespawn, 0);
|
|
else if (!cv_itemrespawn.changed)
|
|
CV_SetValue(&cv_itemrespawn, 1);
|
|
|
|
switch (gametype)
|
|
{
|
|
case GT_MATCH:
|
|
case GT_TEAMMATCH:
|
|
if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
|
|
{
|
|
// default settings for match: no timelimit, no pointlimit
|
|
CV_SetValue(&cv_pointlimit, 0);
|
|
CV_SetValue(&cv_timelimit, 0);
|
|
}
|
|
if (!cv_itemrespawntime.changed)
|
|
CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
|
|
break;
|
|
case GT_TAG:
|
|
case GT_HIDEANDSEEK:
|
|
if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
|
|
{
|
|
// default settings for tag: 5 mins, no pointlimit
|
|
// Note that tag mode also uses an alternate timing mechanism in tandem with timelimit.
|
|
CV_SetValue(&cv_timelimit, 5);
|
|
CV_SetValue(&cv_pointlimit, 0);
|
|
}
|
|
if (!cv_itemrespawntime.changed)
|
|
CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
|
|
break;
|
|
case GT_CTF:
|
|
if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
|
|
{
|
|
// default settings for CTF: no timelimit, pointlimit 5
|
|
CV_SetValue(&cv_timelimit, 0);
|
|
CV_SetValue(&cv_pointlimit, 5);
|
|
}
|
|
if (!cv_itemrespawntime.changed)
|
|
CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
|
|
break;
|
|
}
|
|
}
|
|
else if (!multiplayer && !netgame)
|
|
{
|
|
gametype = GT_RACE; // SRB2kart
|
|
}
|
|
|
|
// reset timelimit and pointlimit in race/coop, prevent stupid cheats
|
|
if (server)
|
|
{
|
|
if (G_RaceGametype())
|
|
{
|
|
if (cv_timelimit.value)
|
|
CV_SetValue(&cv_timelimit, 0);
|
|
if (cv_pointlimit.value)
|
|
CV_SetValue(&cv_pointlimit, 0);
|
|
}
|
|
else if ((cv_pointlimit.changed || cv_timelimit.changed) && cv_pointlimit.value)
|
|
{
|
|
if (lastgametype != GT_CTF && gametype == GT_CTF)
|
|
CV_SetValue(&cv_pointlimit, cv_pointlimit.value / 500);
|
|
else if (lastgametype == GT_CTF && gametype != GT_CTF)
|
|
CV_SetValue(&cv_pointlimit, cv_pointlimit.value * 500);
|
|
}
|
|
}
|
|
|
|
// don't retain teams in other modes or between changes from ctf to team match.
|
|
// also, stop any and all forms of team scrambling that might otherwise take place.
|
|
if (G_GametypeHasTeams())
|
|
{
|
|
INT32 i;
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
if (playeringame[i])
|
|
players[i].ctfteam = 0;
|
|
|
|
if (server || (IsPlayerAdmin(consoleplayer)))
|
|
{
|
|
CV_StealthSetValue(&cv_teamscramble, 0);
|
|
teamscramble = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void Ringslinger_OnChange(void)
|
|
{
|
|
if (!M_SecretUnlocked(SECRET_PANDORA) && !netgame && cv_ringslinger.value && !cv_debug)
|
|
{
|
|
CONS_Printf(M_GetText("You haven't earned this yet.\n"));
|
|
CV_StealthSetValue(&cv_ringslinger, 0);
|
|
return;
|
|
}
|
|
|
|
if (cv_ringslinger.value) // Only if it's been turned on
|
|
G_SetGameModified(multiplayer, true);
|
|
}
|
|
|
|
static void Gravity_OnChange(void)
|
|
{
|
|
if (!M_SecretUnlocked(SECRET_PANDORA) && !netgame && !cv_debug
|
|
&& !fastcmp(cv_gravity.string, cv_gravity.defaultvalue))
|
|
{
|
|
CONS_Printf(M_GetText("You haven't earned this yet.\n"));
|
|
CV_StealthSet(&cv_gravity, cv_gravity.defaultvalue);
|
|
return;
|
|
}
|
|
#ifndef NETGAME_GRAVITY
|
|
if (netgame)
|
|
{
|
|
CV_StealthSet(&cv_gravity, cv_gravity.defaultvalue);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (!CV_IsSetToDefault(&cv_gravity))
|
|
G_SetGameModified(multiplayer, true);
|
|
gravity = cv_gravity.value;
|
|
}
|
|
|
|
static void SoundTest_OnChange(void)
|
|
{
|
|
if (cv_soundtest.value < 0)
|
|
{
|
|
CV_SetValue(&cv_soundtest, NUMSFX-1);
|
|
return;
|
|
}
|
|
|
|
if (cv_soundtest.value >= NUMSFX)
|
|
{
|
|
CV_SetValue(&cv_soundtest, 0);
|
|
return;
|
|
}
|
|
|
|
S_StopSounds();
|
|
S_StartSound(NULL, cv_soundtest.value);
|
|
}
|
|
|
|
static void AutoBalance_OnChange(void)
|
|
{
|
|
autobalance = (INT16)cv_autobalance.value;
|
|
}
|
|
|
|
static void TeamScramble_OnChange(void)
|
|
{
|
|
INT16 i = 0, j = 0, playercount = 0;
|
|
boolean repick = true;
|
|
INT32 blue = 0, red = 0;
|
|
INT32 maxcomposition = 0;
|
|
INT16 newteam = 0;
|
|
INT32 retries = 0;
|
|
boolean success = false;
|
|
|
|
// Don't trigger outside level or intermission!
|
|
if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING))
|
|
return;
|
|
|
|
if (!cv_teamscramble.value)
|
|
teamscramble = 0;
|
|
|
|
if (!G_GametypeHasTeams() && (server || IsPlayerAdmin(consoleplayer)))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
|
|
CV_StealthSetValue(&cv_teamscramble, 0);
|
|
return;
|
|
}
|
|
|
|
// If a team scramble is already in progress, do not allow another one to be started!
|
|
if (teamscramble)
|
|
return;
|
|
|
|
retryscramble:
|
|
|
|
// Clear related global variables. These will get used again in p_tick.c/y_inter.c as the teams are scrambled.
|
|
memset(&scrambleplayers, 0, sizeof(scrambleplayers));
|
|
memset(&scrambleteams, 0, sizeof(scrambleplayers));
|
|
scrambletotal = scramblecount = 0;
|
|
blue = red = newteam = playercount = 0;
|
|
repick = true;
|
|
|
|
// Put each player's node in the array.
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (playeringame[i] && !players[i].spectator)
|
|
{
|
|
scrambleplayers[playercount] = i;
|
|
playercount++;
|
|
}
|
|
}
|
|
|
|
if (playercount < 2)
|
|
{
|
|
CV_StealthSetValue(&cv_teamscramble, 0);
|
|
return; // Don't scramble one or zero players.
|
|
}
|
|
|
|
// Randomly place players on teams.
|
|
if (cv_teamscramble.value == 1)
|
|
{
|
|
maxcomposition = playercount / 2;
|
|
|
|
// Now randomly assign players to teams.
|
|
// If the teams get out of hand, assign the rest to the other team.
|
|
for (i = 0; i < playercount; i++)
|
|
{
|
|
if (repick)
|
|
newteam = (INT16)((M_RandomByte() % 2) + 1);
|
|
|
|
// One team has the most players they can get, assign the rest to the other team.
|
|
if (red == maxcomposition || blue == maxcomposition)
|
|
{
|
|
if (red == maxcomposition)
|
|
newteam = 2;
|
|
else if (blue == maxcomposition)
|
|
newteam = 1;
|
|
|
|
repick = false;
|
|
}
|
|
|
|
scrambleteams[i] = newteam;
|
|
|
|
if (newteam == 1)
|
|
red++;
|
|
else
|
|
blue++;
|
|
}
|
|
}
|
|
else if (cv_teamscramble.value == 2) // Same as before, except split teams based on current score.
|
|
{
|
|
// Now, sort the array based on points scored.
|
|
for (i = 1; i < playercount; i++)
|
|
{
|
|
for (j = i; j < playercount; j++)
|
|
{
|
|
INT16 tempplayer = 0;
|
|
|
|
if ((players[scrambleplayers[i-1]].score > players[scrambleplayers[j]].score))
|
|
{
|
|
tempplayer = scrambleplayers[i-1];
|
|
scrambleplayers[i-1] = scrambleplayers[j];
|
|
scrambleplayers[j] = tempplayer;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now assign players to teams based on score. Scramble in pairs.
|
|
// If there is an odd number, one team will end up with the unlucky slob who has no points. =(
|
|
for (i = 0; i < playercount; i++)
|
|
{
|
|
if (repick)
|
|
{
|
|
newteam = (INT16)((M_RandomByte() % 2) + 1);
|
|
repick = false;
|
|
}
|
|
else
|
|
{
|
|
// We will only randomly pick the team for the first guy.
|
|
// Otherwise, just alternate back and forth, distributing players.
|
|
if (newteam == 1)
|
|
newteam = 2;
|
|
else
|
|
newteam = 1;
|
|
}
|
|
|
|
scrambleteams[i] = newteam;
|
|
}
|
|
}
|
|
|
|
// Check to see if our random selection actually
|
|
// changed anybody. If not, we run through and try again.
|
|
for (i = 0; i < playercount; i++)
|
|
{
|
|
if (players[scrambleplayers[i]].ctfteam != scrambleteams[i])
|
|
success = true;
|
|
}
|
|
|
|
if (!success && retries < 5)
|
|
{
|
|
retries++;
|
|
goto retryscramble; //try again
|
|
}
|
|
|
|
// Display a witty message, but only during scrambles specifically triggered by an admin.
|
|
if (cv_teamscramble.value)
|
|
{
|
|
scrambletotal = playercount;
|
|
teamscramble = (INT16)cv_teamscramble.value;
|
|
|
|
if (!(gamestate == GS_INTERMISSION && cv_scrambleonchange.value))
|
|
CONS_Printf(M_GetText("Teams will be scrambled next round.\n"));
|
|
}
|
|
}
|
|
|
|
static void Hidetime_OnChange(void)
|
|
{
|
|
if (Playing() && G_TagGametype() && leveltime >= (hidetime * TICRATE))
|
|
{
|
|
// Don't allow hidetime changes after it expires.
|
|
CV_StealthSetValue(&cv_hidetime, hidetime);
|
|
return;
|
|
}
|
|
hidetime = cv_hidetime.value;
|
|
|
|
//uh oh, gotta change timelimitintics now too
|
|
if (G_TagGametype())
|
|
timelimitintics = (cv_timelimit.value * 60 * TICRATE) + (hidetime * TICRATE);
|
|
}
|
|
|
|
static void Command_Showmap_f(void)
|
|
{
|
|
if (gamestate == GS_LEVEL)
|
|
{
|
|
if (mapheaderinfo[gamemap-1]->zonttl[0] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
|
|
{
|
|
if (mapheaderinfo[gamemap-1]->actnum[0])
|
|
CONS_Printf("%s (%d): %s %s %s\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->zonttl, mapheaderinfo[gamemap-1]->actnum);
|
|
else
|
|
CONS_Printf("%s (%d): %s %s\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->zonttl);
|
|
}
|
|
else
|
|
{
|
|
if (mapheaderinfo[gamemap-1]->actnum[0])
|
|
CONS_Printf("%s (%d): %s %s\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->actnum);
|
|
else
|
|
CONS_Printf("%s (%d): %s\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl);
|
|
}
|
|
}
|
|
else
|
|
CONS_Printf(M_GetText("You must be in a level to use this.\n"));
|
|
}
|
|
|
|
static void Command_Mapmd5_f(void)
|
|
{
|
|
if (gamestate == GS_LEVEL)
|
|
{
|
|
INT32 i;
|
|
char md5tmp[33];
|
|
for (i = 0; i < 16; ++i)
|
|
sprintf(&md5tmp[i*2], "%02x", mapmd5[i]);
|
|
CONS_Printf("%s: %s\n", G_BuildMapName(gamemap), md5tmp);
|
|
}
|
|
else
|
|
CONS_Printf(M_GetText("You must be in a level to use this.\n"));
|
|
}
|
|
|
|
static void Command_ExitLevel_f(void)
|
|
{
|
|
if (!(netgame || (multiplayer && gametype != GT_COOP)) && !cv_debug)
|
|
CONS_Printf(M_GetText("This only works in a netgame.\n"));
|
|
else if (!(server || (IsPlayerAdmin(consoleplayer))))
|
|
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
|
|
else if (gamestate != GS_LEVEL || demo.playback)
|
|
CONS_Printf(M_GetText("You must be in a level to use this.\n"));
|
|
else
|
|
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
|
|
}
|
|
|
|
static void Got_ExitLevelcmd(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
(void)cp;
|
|
|
|
// Ignore duplicate XD_EXITLEVEL commands.
|
|
if (gameaction == ga_completed)
|
|
return;
|
|
|
|
if (playernum != serverplayer && !IsPlayerAdmin(playernum))
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal exitlevel command received from %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
G_ExitLevel();
|
|
}
|
|
|
|
static void Got_SetupVotecmd(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
INT32 i;
|
|
UINT8 gt, secondgt;
|
|
|
|
if (playernum != serverplayer && !IsPlayerAdmin(playernum))
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal vote setup received from %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Get gametype data.
|
|
gt = (UINT8)READUINT8(*cp);
|
|
secondgt = (UINT8)READUINT8(*cp);
|
|
|
|
// Strip illegal Encore flag.
|
|
if (gt == (GT_MATCH|0x80))
|
|
{
|
|
gt &= ~0x80;
|
|
}
|
|
|
|
// Apply most data.
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
votelevels[i][0] = (UINT16)READUINT16(*cp);
|
|
votelevels[i][1] = gt;
|
|
if (!mapheaderinfo[votelevels[i][0]])
|
|
P_AllocMapHeader(votelevels[i][0]);
|
|
}
|
|
|
|
// Correct third entry's gametype/Encore status.
|
|
votelevels[2][1] = secondgt;
|
|
|
|
// If third entry has an illelegal Encore flag...
|
|
if (secondgt == (GT_MATCH|0x80))
|
|
{
|
|
votelevels[2][1] &= ~0x80;
|
|
// Apply it to the second entry instead, gametype permitting!
|
|
if (gt != GT_MATCH)
|
|
{
|
|
votelevels[1][1] |= 0x80;
|
|
}
|
|
}
|
|
|
|
G_SetGamestate(GS_VOTING);
|
|
Y_StartVote();
|
|
}
|
|
|
|
static void Got_ModifyVotecmd(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
SINT8 voted = READSINT8(*cp);
|
|
UINT8 p = READUINT8(*cp);
|
|
|
|
(void)playernum;
|
|
votes[p] = voted;
|
|
}
|
|
|
|
static void Got_PickVotecmd(const UINT8 **cp, INT32 playernum)
|
|
{
|
|
SINT8 pick = READSINT8(*cp);
|
|
SINT8 level = READSINT8(*cp);
|
|
|
|
if (playernum != serverplayer && !IsPlayerAdmin(playernum))
|
|
{
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal vote setup received from %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
Y_SetupVoteFinish(pick, level);
|
|
}
|
|
|
|
/** Prints the number of displayplayers[0].
|
|
*
|
|
* \todo Possibly remove this; it was useful for debugging at one point.
|
|
*/
|
|
static void Command_Displayplayer_f(void)
|
|
{
|
|
CONS_Printf(M_GetText("Displayplayer is %d\n"), displayplayers[0]);
|
|
}
|
|
|
|
/** Quits a game and returns to the title screen.
|
|
*
|
|
*/
|
|
void Command_ExitGame_f(void)
|
|
{
|
|
INT32 i;
|
|
|
|
if (dedicated)
|
|
{
|
|
CONS_Printf("This command cannot be used on dedicated server\n");
|
|
return;
|
|
}
|
|
|
|
D_QuitNetGame();
|
|
CL_Reset();
|
|
CV_ClearChangedFlags();
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
CL_ClearPlayer(i);
|
|
|
|
splitscreen = 0;
|
|
SplitScreen_OnChange();
|
|
botingame = false;
|
|
botskin = 0;
|
|
cv_debug = 0;
|
|
emeralds = 0;
|
|
automapactive = false;
|
|
|
|
if (dirmenu)
|
|
closefilemenu(true);
|
|
|
|
if (!modeattacking)
|
|
{
|
|
D_StartTitle();
|
|
}
|
|
}
|
|
|
|
void Command_Retry_f(void)
|
|
{
|
|
if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING))
|
|
CONS_Printf(M_GetText("You must be in a level to use this.\n"));
|
|
else if (netgame || multiplayer)
|
|
CONS_Printf(M_GetText("This only works in single player.\n"));
|
|
else
|
|
{
|
|
M_ClearMenus(true);
|
|
G_SetRetryFlag();
|
|
}
|
|
}
|
|
|
|
#ifdef NETGAME_DEVMODE
|
|
// Allow the use of devmode in netgames.
|
|
static void Fishcake_OnChange(void)
|
|
{
|
|
cv_debug = cv_fishcake.value;
|
|
// consvar_t's get changed to default when registered
|
|
// so don't make modifiedgame always on!
|
|
if (cv_debug)
|
|
{
|
|
G_SetGameModified(multiplayer, true);
|
|
}
|
|
|
|
else if (cv_debug != cv_fishcake.value)
|
|
CV_SetValue(&cv_fishcake, cv_debug);
|
|
}
|
|
#endif
|
|
|
|
/** Reports to the console whether or not the game has been modified.
|
|
*
|
|
* \todo Make it obvious, so a console command won't be necessary.
|
|
* \sa modifiedgame
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void Command_Isgamemodified_f(void)
|
|
{
|
|
if (majormods || modifiedgame)
|
|
CONS_Printf("The game has been modified, Record Attack data will be saved to a seperate savegame.\n");
|
|
else
|
|
CONS_Printf("The game has not been modified. You can play Record Attack, earn medals and unlock extras.\n");
|
|
}
|
|
|
|
static void Command_Cheats_f(void)
|
|
{
|
|
if (COM_CheckParm("off"))
|
|
{
|
|
if (!(server || (IsPlayerAdmin(consoleplayer))))
|
|
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
|
|
else
|
|
CV_ResetCheatNetVars();
|
|
return;
|
|
}
|
|
|
|
if (CV_CheatsEnabled())
|
|
{
|
|
CONS_Printf(M_GetText("At least one CHEAT-marked variable has been changed -- Cheats are enabled.\n"));
|
|
if (server || (IsPlayerAdmin(consoleplayer)))
|
|
CONS_Printf(M_GetText("Type CHEATS OFF to reset all cheat variables to default.\n"));
|
|
}
|
|
else
|
|
CONS_Printf(M_GetText("No CHEAT-marked variables are changed -- Cheats are disabled.\n"));
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
static void Command_Togglemodified_f(void)
|
|
{
|
|
modifiedgame = !modifiedgame;
|
|
}
|
|
|
|
static void Command_Archivetest_f(void)
|
|
{
|
|
savebuffer_t save = {0};
|
|
UINT32 i, wrote;
|
|
thinker_t *th;
|
|
if (gamestate != GS_LEVEL)
|
|
{
|
|
CONS_Printf("This command only works in-game, you dummy.\n");
|
|
return;
|
|
}
|
|
|
|
// assign mobjnum
|
|
i = 1;
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
if (th->function == (actionf_p1)P_MobjThinker)
|
|
((mobj_t *)th)->mobjnum = i++;
|
|
|
|
// allocate buffer
|
|
save.buffer = save.p = ZZ_Alloc(1024);
|
|
|
|
// test archive
|
|
CONS_Printf("LUA_Archive...\n");
|
|
LUA_Archive(&save, true);
|
|
WRITEUINT8(save.p, 0x7F);
|
|
wrote = (UINT32)(save.p - save.buffer);
|
|
|
|
// clear Lua state, so we can really see what happens!
|
|
CONS_Printf("Clearing state!\n");
|
|
LUA_ClearExtVars();
|
|
|
|
// test unarchive
|
|
save.p = save.buffer;
|
|
CONS_Printf("LUA_UnArchive...\n");
|
|
LUA_UnArchive(&save, true);
|
|
i = READUINT8(save.p);
|
|
if (i != 0x7F || wrote != (UINT32)(save.p - save.buffer))
|
|
CONS_Printf("Savegame corrupted. (write %u, read %u)\n", wrote, (UINT32)(save.p - save.buffer));
|
|
|
|
// free buffer
|
|
Z_Free(save.buffer);
|
|
CONS_Printf("Done. No crash.\n");
|
|
}
|
|
#endif
|
|
|
|
/** Makes a change to ::cv_forceskin take effect immediately.
|
|
*
|
|
* \sa Command_SetForcedSkin_f, cv_forceskin, forcedskin
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void ForceSkin_OnChange(void)
|
|
{
|
|
// NOT in SP, silly!
|
|
if (!(netgame || multiplayer))
|
|
return;
|
|
|
|
if (cv_forceskin.value < 0)
|
|
CONS_Printf("The server has lifted the forced skin restrictions.\n");
|
|
else
|
|
{
|
|
CONS_Printf("The server is restricting all players to skin \"%s\".\n",cv_forceskin.string);
|
|
ForceAllSkins(cv_forceskin.value);
|
|
}
|
|
}
|
|
|
|
// Allows the player's name to be changed if cv_mute is off.
|
|
static void Name_OnChange(void)
|
|
{
|
|
if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You may not change your name when chat is muted.\n"));
|
|
CV_StealthSet(&cv_playername, player_names[consoleplayer]);
|
|
return;
|
|
}
|
|
|
|
SendNameAndColor(0);
|
|
}
|
|
|
|
static void Name2_OnChange(void)
|
|
{
|
|
if (cv_mute.value) // Secondary player can't be admin.
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You may not change your name when chat is muted.\n"));
|
|
CV_StealthSet(&cv_playername2, player_names[displayplayers[1]]);
|
|
return;
|
|
}
|
|
|
|
SendNameAndColor(1);
|
|
}
|
|
|
|
static void Name3_OnChange(void)
|
|
{
|
|
if (cv_mute.value) // Third player can't be admin.
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You may not change your name when chat is muted.\n"));
|
|
CV_StealthSet(&cv_playername3, player_names[displayplayers[2]]);
|
|
return;
|
|
}
|
|
|
|
SendNameAndColor(2);
|
|
}
|
|
|
|
static void Name4_OnChange(void)
|
|
{
|
|
if (cv_mute.value) // Secondary player can't be admin.
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You may not change your name when chat is muted.\n"));
|
|
CV_StealthSet(&cv_playername4, player_names[displayplayers[3]]);
|
|
return;
|
|
}
|
|
|
|
SendNameAndColor(3);
|
|
}
|
|
|
|
/** Sends a skin change for the console player, unless that player is moving.
|
|
* \sa cv_skin, Skin2_OnChange, Color_OnChange
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void Skin_OnChange(void)
|
|
{
|
|
Skin_FindRealNameSkin(&cv_skin);
|
|
|
|
if (!Playing())
|
|
return; // do whatever you want
|
|
|
|
if (!(cv_debug || devparm) && !(multiplayer || netgame) // In single player.
|
|
&& (gamestate != GS_WAITINGPLAYERS)) // allows command line -warp x +skin y
|
|
{
|
|
CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
|
|
return;
|
|
}
|
|
|
|
if (CanChangeSkin(consoleplayer) && !P_PlayerMoving(consoleplayer))
|
|
SendNameAndColor(0);
|
|
else
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n"));
|
|
CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
|
|
}
|
|
}
|
|
|
|
/** Sends a skin change for the secondary splitscreen player, unless that
|
|
* player is moving.
|
|
* \sa cv_skin2, Skin_OnChange, Color2_OnChange
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void Skin2_OnChange(void)
|
|
{
|
|
Skin_FindRealNameSkin(&cv_skin2);
|
|
|
|
if (!Playing() || !splitscreen)
|
|
return; // do whatever you want
|
|
|
|
if (CanChangeSkin(displayplayers[1]) && !P_PlayerMoving(displayplayers[1]))
|
|
SendNameAndColor(1);
|
|
else
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n"));
|
|
CV_StealthSet(&cv_skin2, skins[players[displayplayers[1]].skin].name);
|
|
}
|
|
}
|
|
|
|
static void Skin3_OnChange(void)
|
|
{
|
|
Skin_FindRealNameSkin(&cv_skin3);
|
|
|
|
if (!Playing() || splitscreen < 2)
|
|
return; // do whatever you want
|
|
|
|
if (CanChangeSkin(displayplayers[2]) && !P_PlayerMoving(displayplayers[2]))
|
|
SendNameAndColor(2);
|
|
else
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n"));
|
|
CV_StealthSet(&cv_skin3, skins[players[displayplayers[2]].skin].name);
|
|
}
|
|
}
|
|
|
|
static void Skin4_OnChange(void)
|
|
{
|
|
Skin_FindRealNameSkin(&cv_skin4);
|
|
|
|
if (!Playing() || splitscreen < 3)
|
|
return; // do whatever you want
|
|
|
|
if (CanChangeSkin(displayplayers[3]) && !P_PlayerMoving(displayplayers[3]))
|
|
SendNameAndColor(3);
|
|
else
|
|
{
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n"));
|
|
CV_StealthSet(&cv_skin4, skins[players[displayplayers[3]].skin].name);
|
|
}
|
|
}
|
|
|
|
static void Command_ListSkins(void)
|
|
{
|
|
int i;
|
|
int page = 0;
|
|
int numpages = numskins / 10 + (numskins % 10 ? 1 : 0);
|
|
int longest_name = 0;
|
|
|
|
if (COM_Argc() > 1)
|
|
{
|
|
if (sscanf(COM_Argv(1), " %d", &page) == 0)
|
|
{
|
|
CONS_Printf("Expected a number.\n");
|
|
return;
|
|
}
|
|
else if (page > numpages)
|
|
{
|
|
CONS_Printf("There are only %d pages.\n", numpages);
|
|
return;
|
|
}
|
|
else if (page < 0)
|
|
{
|
|
CONS_Printf("Page number cannot be negative.\n");
|
|
return;
|
|
}
|
|
else if (page == 0)
|
|
{
|
|
CONS_Printf("There is no 0th page. (What are you a programmer?)\n");
|
|
return;
|
|
}
|
|
|
|
--page;
|
|
}
|
|
|
|
CONS_Printf("Total %d skins. Page (%d/%d)\n", numskins, page + 1, numpages);
|
|
|
|
// Find the longest name and realname in the skins list. (used for alignment)
|
|
for (i = page * 10; i < numskins && i < (page + 1) * 10; i++)
|
|
{
|
|
int length;
|
|
skin_t *skin = &skins[i];
|
|
|
|
if ((length = strlen(skin->name)) > longest_name)
|
|
longest_name = length;
|
|
}
|
|
|
|
for (i = page * 10; i < numskins && i < (page + 1) * 10; i++)
|
|
{
|
|
skin_t *skin = &skins[i];
|
|
|
|
CONS_Printf(
|
|
"%s[%-*s]\x80 %s\n",
|
|
HU_SkinColorToConsoleColor(skin->prefcolor),
|
|
longest_name,
|
|
skin->name,
|
|
skin->realname);
|
|
}
|
|
|
|
if (page + 1 != numpages)
|
|
{
|
|
CONS_Printf("Use \"listskins %d\" to see the next page.\n", page + 2);
|
|
}
|
|
else
|
|
{
|
|
CONS_Printf("There are no more pages.\n");
|
|
}
|
|
}
|
|
|
|
static void Command_SkinSearch(void)
|
|
{
|
|
size_t i;
|
|
UINT16 s;
|
|
UINT16 ic = 0;
|
|
|
|
for (i = 1; i < COM_Argc(); i++)
|
|
{
|
|
for (s = 0; s < numskins; s++)
|
|
{
|
|
skin_t *skininput = &skins[s];
|
|
|
|
if (strcasestr(skininput->realname, COM_Argv(i)))
|
|
{
|
|
ic++;
|
|
CONS_Printf("%d. %s%s:\x80 %s\n", ic, HU_SkinColorToConsoleColor(skininput->prefcolor), skininput->realname, skininput->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
CONS_Printf("Total %d skins.\n", ic);
|
|
}
|
|
|
|
/** Sends a color change for the console player, unless that player is moving.
|
|
* \sa cv_playercolor, Color2_OnChange, Skin_OnChange
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void Color_OnChange(void)
|
|
{
|
|
if (!Playing())
|
|
return; // do whatever you want
|
|
|
|
if (!(cv_debug || devparm) && !(multiplayer || netgame || modeattacking)) // In single player.
|
|
{
|
|
CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
|
|
return;
|
|
}
|
|
|
|
if (!P_PlayerMoving(consoleplayer))
|
|
{
|
|
// Color change menu scrolling fix is no longer necessary
|
|
SendNameAndColor(0);
|
|
}
|
|
else
|
|
{
|
|
CV_StealthSetValue(&cv_playercolor,
|
|
players[consoleplayer].skincolor);
|
|
}
|
|
}
|
|
|
|
/** Sends a color change for the secondary splitscreen player, unless that
|
|
* player is moving.
|
|
* \sa cv_playercolor2, Color_OnChange, Skin2_OnChange
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void Color2_OnChange(void)
|
|
{
|
|
if (!Playing() || !splitscreen)
|
|
return; // do whatever you want
|
|
|
|
if (!P_PlayerMoving(displayplayers[1]))
|
|
{
|
|
// Color change menu scrolling fix is no longer necessary
|
|
SendNameAndColor(1);
|
|
}
|
|
else
|
|
{
|
|
CV_StealthSetValue(&cv_playercolor2,
|
|
players[displayplayers[1]].skincolor);
|
|
}
|
|
}
|
|
|
|
static void Color3_OnChange(void)
|
|
{
|
|
if (!Playing() || splitscreen < 2)
|
|
return; // do whatever you want
|
|
|
|
if (!P_PlayerMoving(displayplayers[2]))
|
|
{
|
|
// Color change menu scrolling fix is no longer necessary
|
|
SendNameAndColor(2);
|
|
}
|
|
else
|
|
{
|
|
CV_StealthSetValue(&cv_playercolor3,
|
|
players[displayplayers[2]].skincolor);
|
|
}
|
|
}
|
|
|
|
static void Color4_OnChange(void)
|
|
{
|
|
if (!Playing() || splitscreen < 3)
|
|
return; // do whatever you want
|
|
|
|
if (!P_PlayerMoving(displayplayers[3]))
|
|
{
|
|
// Color change menu scrolling fix is no longer necessary
|
|
SendNameAndColor(3);
|
|
}
|
|
else
|
|
{
|
|
CV_StealthSetValue(&cv_playercolor4,
|
|
players[displayplayers[3]].skincolor);
|
|
}
|
|
}
|
|
|
|
/** Displays the result of the chat being muted or unmuted.
|
|
* The server or remote admin should already know and be able to talk
|
|
* regardless, so this is only displayed to clients.
|
|
*
|
|
* \sa cv_mute
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void Mute_OnChange(void)
|
|
{
|
|
/*if (server || (IsPlayerAdmin(consoleplayer)))
|
|
return;*/
|
|
// Kinda dumb IMO, you should be able to see confirmation for having muted the chat as the host or admin.
|
|
|
|
if (leveltime <= 1)
|
|
return; // avoid having this notification put in our console / log when we boot the server.
|
|
|
|
if (cv_mute.value)
|
|
HU_AddChatText(M_GetText("\x82*Chat has been muted."), false);
|
|
else
|
|
HU_AddChatText(M_GetText("\x82*Chat is no longer muted."), false);
|
|
}
|
|
|
|
/** Hack to clear all changed flags after game start.
|
|
* A lot of code (written by dummies, obviously) uses COM_BufAddText() to run
|
|
* commands and change consvars, especially on game start. This is problematic
|
|
* because CV_ClearChangedFlags() needs to get called on game start \b after
|
|
* all those commands are run.
|
|
*
|
|
* Here's how it's done: the last thing in COM_BufAddText() is "dummyconsvar
|
|
* 1", so we end up here, where dummyconsvar is reset to 0 and all the changed
|
|
* flags are set to 0.
|
|
*
|
|
* \todo Fix the aforementioned code and make this hack unnecessary.
|
|
* \sa cv_dummyconsvar
|
|
* \author Graue <graue@oceanbase.org>
|
|
*/
|
|
static void DummyConsvar_OnChange(void)
|
|
{
|
|
if (cv_dummyconsvar.value == 1)
|
|
{
|
|
CV_SetValue(&cv_dummyconsvar, 0);
|
|
CV_ClearChangedFlags();
|
|
}
|
|
}
|
|
|
|
static void Command_ShowScores_f(void)
|
|
{
|
|
UINT8 i;
|
|
|
|
if (!(netgame || multiplayer))
|
|
{
|
|
CONS_Printf(M_GetText("This only works in a netgame.\n"));
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (playeringame[i])
|
|
// FIXME: %lu? what's wrong with %u? ~Callum (produces warnings...)
|
|
CONS_Printf(M_GetText("%s's score is %u\n"), player_names[i], players[i].score);
|
|
}
|
|
CONS_Printf(M_GetText("The pointlimit is %d\n"), cv_pointlimit.value);
|
|
|
|
}
|
|
|
|
static void Command_ShowTime_f(void)
|
|
{
|
|
if (!(netgame || multiplayer))
|
|
{
|
|
CONS_Printf(M_GetText("This only works in a netgame.\n"));
|
|
return;
|
|
}
|
|
|
|
CONS_Printf(M_GetText("The current time is %f.\nThe timelimit is %f\n"), (double)leveltime/TICRATE, (double)timelimitintics/TICRATE);
|
|
}
|
|
|
|
// SRB2Kart: On change messages
|
|
static void BaseNumLaps_OnChange(void)
|
|
{
|
|
if (gamestate == GS_LEVEL)
|
|
{
|
|
if (cv_basenumlaps.value)
|
|
CONS_Printf(M_GetText("Number of laps will be changed to %d next round.\n"), cv_basenumlaps.value);
|
|
else
|
|
CONS_Printf(M_GetText("Number of laps will be changed to map defaults next round.\n"));
|
|
}
|
|
}
|
|
|
|
static void KartFrantic_OnChange(void)
|
|
{
|
|
if ((boolean)cv_kartfrantic.value != franticitems && gamestate == GS_LEVEL && leveltime > starttime)
|
|
CONS_Printf(M_GetText("Frantic items will be turned %s next round.\n"), cv_kartfrantic.value ? M_GetText("on") : M_GetText("off"));
|
|
else
|
|
{
|
|
CONS_Printf(M_GetText("Frantic items has been turned %s.\n"), cv_kartfrantic.value ? M_GetText("on") : M_GetText("off"));
|
|
franticitems = (boolean)cv_kartfrantic.value;
|
|
}
|
|
}
|
|
|
|
static void KartSpeed_OnChange(void)
|
|
{
|
|
if (G_RaceGametype())
|
|
{
|
|
if ((UINT8)cv_kartspeed.value != gamespeed && gamestate == GS_LEVEL && leveltime > starttime)
|
|
CONS_Printf(M_GetText("Game speed will be changed to \"%s\" next round.\n"), cv_kartspeed.string);
|
|
else
|
|
{
|
|
CONS_Printf(M_GetText("Game speed has been changed to \"%s\".\n"), cv_kartspeed.string);
|
|
gamespeed = (UINT8)cv_kartspeed.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void KartEncore_OnChange(void)
|
|
{
|
|
if (G_RaceGametype())
|
|
{
|
|
if ((boolean)cv_kartencore.value != encoremode && gamestate == GS_LEVEL /*&& leveltime > starttime*/)
|
|
CONS_Printf(M_GetText("Encore Mode will be turned %s next round.\n"), cv_kartencore.value ? M_GetText("on") : M_GetText("off"));
|
|
else
|
|
CONS_Printf(M_GetText("Encore Mode has been turned %s.\n"), cv_kartencore.value ? M_GetText("on") : M_GetText("off"));
|
|
}
|
|
}
|
|
|
|
static void KartComeback_OnChange(void)
|
|
{
|
|
if (G_BattleGametype())
|
|
{
|
|
if ((boolean)cv_kartcomeback.value != comeback && gamestate == GS_LEVEL && leveltime > starttime)
|
|
CONS_Printf(M_GetText("Karma Comeback will be turned %s next round.\n"), cv_kartcomeback.value ? M_GetText("on") : M_GetText("off"));
|
|
else
|
|
{
|
|
CONS_Printf(M_GetText("Karma Comeback has been turned %s.\n"), cv_kartcomeback.value ? M_GetText("on") : M_GetText("off"));
|
|
comeback = (boolean)cv_kartcomeback.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void KartEliminateLast_OnChange(void)
|
|
{
|
|
if (G_RaceGametype() && cv_karteliminatelast.value)
|
|
P_CheckRacers();
|
|
}
|
|
|
|
void Got_DiscordInfo(const UINT8 **p, INT32 playernum)
|
|
{
|
|
if (playernum != serverplayer /*&& !IsPlayerAdmin(playernum)*/)
|
|
{
|
|
// protect against hacked/buggy client
|
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal Discord info command received from %s\n"), player_names[playernum]);
|
|
if (server)
|
|
{
|
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Don't do anything with the information if we don't have Discord RP support
|
|
#ifdef HAVE_DISCORDRPC
|
|
discordInfo.maxPlayers = READUINT8(*p);
|
|
discordInfo.joinsAllowed = (boolean)READUINT8(*p);
|
|
discordInfo.everyoneCanInvite = (boolean)READUINT8(*p);
|
|
|
|
DRPC_UpdatePresence();
|
|
#else
|
|
(*p) += 3;
|
|
#endif
|
|
}
|