]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/client/main.qc
Q1BSP: use engine default for gl_max_lightmapsize (currently 512)
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / main.qc
index 7acbfe6cbafb20b4b0ee7ec41441e79828153dc7..8116b1d34906af2f8484a0d418a17a10f22f2bdf 100644 (file)
@@ -13,6 +13,7 @@
 #include <client/shownames.qh>
 #include <client/view.qh>
 #include <client/weapons/projectile.qh>
+#include <common/checkextension.qh>
 #include <common/deathtypes/all.qh>
 #include <common/effects/all.inc>
 #include <common/effects/all.qh>
@@ -42,7 +43,7 @@
 // CSQC_Init : Called every time the CSQC code is initialized (essentially at map load)
 // Useful for precaching things
 
-void CSQC_Init()
+void CSQC_Init(float apilevel, string enginename, float engineversion)
 {
        prvm_language = strzone(cvar_string("prvm_language"));
 
@@ -50,6 +51,8 @@ void CSQC_Init()
        LOG_TRACEF("^4CSQC Build information: ^1%s", WATERMARK);
 #endif
 
+       CheckEngineExtensions();
+
        {
                int i = 0;
                for ( ; i < 255; ++i)
@@ -58,8 +61,6 @@ void CSQC_Init()
                maxclients = i;
        }
 
-       ReplicateVars(REPLICATEVARS_SEND_ALL);
-
        // needs to be done so early because of the constants they create
        static_init();
        static_init_late();
@@ -102,6 +103,10 @@ void CSQC_Init()
        if(autocvar_cl_lockview)
                cvar_set("cl_lockview", "0");
 
+       if (cvar_type("_scoreboard_team_selection_available") & CVAR_TYPEFLAG_EXISTS)
+               cvar_settemp("_scoreboard_team_selection_available", "1");
+       cvar_set("_scoreboard_team_selection", "0"); // in case it has been left set to 1
+
        gametype = NULL;
 
        postinit = false;
@@ -127,7 +132,6 @@ void CSQC_Init()
        {
                get_mi_min_max_texcoords(1); // try the CLEVER way first
                minimapname = strcat("gfx/", mi_shortname, "_radar");
-               shortmapname = mi_shortname;
 
                if (precache_pic(minimapname) == "")
                {
@@ -180,13 +184,22 @@ void Shutdown()
                if (!(calledhooks & HOOK_START))
                        localcmd("\n_cl_hook_gamestart nop\n");
                if (!(calledhooks & HOOK_END))
+               {
+                       int gamecount = cvar("cl_matchcount");
                        localcmd("\ncl_hook_gameend\n");
+                       // NOTE: using localcmd here to ensure it's executed AFTER cl_hook_gameend
+                       // earlier versions of the game abuse the hook to set this cvar
+                       localcmd(strcat("cl_matchcount ", itos(gamecount + 1), "\n"));
+                       //cvar_set("cl_matchcount", itos(gamecount + 1));
+               }
        }
 
        localcmd("\ncl_hook_shutdown\n");
 
        localcmd("\n-button12\n");
 
+       strfree(gametype_custom_name);
+
        deactivate_minigame();
        HUD_MinigameMenu_Close(NULL, NULL, NULL);
 
@@ -439,9 +452,25 @@ void PostInit()
 
        TrueAim_Init();
 
+       // this can't be called in CSQC_Init as it'd send cvars too early
+       ReplicateVars_Start();
+
        postinit = true;
 }
 
+void Release_Common_Keys()
+{
+       localcmd("-fire\n");
+       localcmd("-fire2\n");
+       localcmd("-use\n");
+       localcmd("-hook\n");
+       localcmd("-jump\n");
+       localcmd("-forward\n");
+       localcmd("-back\n");
+       localcmd("-moveleft\n");
+       localcmd("-moveright\n");
+}
+
 // CSQC_InputEvent : Used to perform actions based on any key pressed, key released and mouse on the client.
 // Return value should be 1 if CSQC handled the input, otherwise return 0 to have the input passed to the engine.
 // All keys are in ascii.
@@ -454,6 +483,10 @@ float CSQC_InputEvent(int bInputType, float nPrimary, float nSecondary)
        TC(int, bInputType);
        bool override = false;
 
+       override |= HUD_Scoreboard_InputEvent(bInputType, nPrimary, nSecondary);
+       if (override)
+               return true;
+
        override |= HUD_Panel_InputEvent(bInputType, nPrimary, nSecondary);
        if (override)
                return true;
@@ -471,6 +504,40 @@ float CSQC_InputEvent(int bInputType, float nPrimary, float nSecondary)
        if(override)
                return true;
 
+       if(bInputType == 3 || bInputType == 2)
+               return false;
+
+       // at this point bInputType can only be 0 or 1 (key pressed or released)
+       bool key_pressed = (bInputType == 0);
+
+       if(key_pressed) {
+               if(nPrimary == K_ALT) hudShiftState |= S_ALT;
+               if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
+               if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
+               if(nPrimary == K_TAB) hudShiftState |= S_TAB;
+       }
+       else {
+               if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
+               if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
+               if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
+               if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
+       }
+
+       // NOTE: Shift-Escape must be filtered out because it's the hardcoded console shortcut
+       if (nPrimary == K_ESCAPE && !(hudShiftState & S_SHIFT) && key_pressed)
+       {
+               if (hudShiftState & S_TAB)
+               {
+                       Scoreboard_UI_Enable(0);
+                       return true;
+               }
+               if (autocvar_menu_gamemenu && !isdemo() && cvar("_menu_gamemenu_dialog_available"))
+               {
+                       localcmd("\nmenu_showgamemenudialog\n");
+                       return true;
+               }
+       }
+
        return false;
 }
 
@@ -479,7 +546,7 @@ float CSQC_InputEvent(int bInputType, float nPrimary, float nSecondary)
 
 // --------------------------------------------------------------------------
 // BEGIN OPTIONAL CSQC FUNCTIONS
-
+.int survival_status;
 void Ent_RemovePlayerScore(entity this)
 {
        if(this.owner) {
@@ -488,6 +555,11 @@ void Ent_RemovePlayerScore(entity this)
                FOREACH(Scores, true, {
                        this.owner.(scores(it)) = 0; // clear all scores
                });
+               this.owner.ready = 0;
+               this.owner.eliminated = 0;
+               this.owner.colormap = 0;
+               // TODO add a hook to reset this Survival field
+               this.owner.survival_status = 0;
        }
 }
 
@@ -595,6 +667,7 @@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool isnew)
                newspectatee_status = 0;
 
        spectatorbutton_zoom = (f & BIT(2));
+       observe_blocked = (f & BIT(3));
 
        if(f & BIT(4))
        {
@@ -623,9 +696,6 @@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool isnew)
 
        if(newspectatee_status != spectatee_status)
        {
-               // clear race stuff
-               race_laptime = 0;
-               race_checkpointtime = 0;
                hud_dynamic_shake_factor = -1;
                spectatee_status_changed_time = time;
        }
@@ -646,7 +716,6 @@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool isnew)
 NET_HANDLE(ENT_CLIENT_NAGGER, bool isnew)
 {
        make_pure(this);
-       int i, j, b, f;
 
        int nags = ReadByte(); // NAGS NAGS NAGS NAGS NAGS NAGS NADZ NAGS NAGS NAGS
 
@@ -673,20 +742,11 @@ NET_HANDLE(ENT_CLIENT_NAGGER, bool isnew)
                strcpy(vote_called_vote, ReadString());
        }
 
-       if(nags & 1)
-       {
-               for(j = 0; j < maxclients; ++j)
-                       if(playerslots[j])
-                               playerslots[j].ready = true;
-               for(i = 1; i <= maxclients; i += 8)
-               {
-                       f = ReadByte();
-                       for(j = i-1, b = BIT(0); b < BIT(8); b <<= 1, ++j)
-                               if (!(f & b))
-                                       if(playerslots[j])
-                                               playerslots[j].ready = false;
-               }
-       }
+       if(nags & BIT(0))
+               for(int i = 0; i < maxclients;)
+                       for(int f = ReadByte(), b = 0; b < 8 && i < maxclients; ++b, ++i)
+                               if(playerslots[i])
+                                       playerslots[i].ready = f & BIT(b);
 
        return = true;
 
@@ -814,11 +874,13 @@ NET_HANDLE(ENT_CLIENT_SPAWNEVENT, bool is_new)
        {
                this.origin = ReadVector();
 
+               int particlesAndOrSound = ReadByte();
+
                if(is_new)
                {
                        float teamnum = entcs_GetTeam(entnum - 1);
 
-                       if(autocvar_cl_spawn_event_particles)
+                       if(autocvar_cl_spawn_event_particles && (particlesAndOrSound & BIT(0)))
                        {
                                switch(teamnum)
                                {
@@ -829,7 +891,8 @@ NET_HANDLE(ENT_CLIENT_SPAWNEVENT, bool is_new)
                                        default: pointparticles(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1); break;
                                }
                        }
-                       if(autocvar_cl_spawn_event_sound)
+
+                       if(autocvar_cl_spawn_event_sound && (particlesAndOrSound & BIT(1)))
                        {
                                sound(this, CH_TRIGGER, SND_SPAWN, VOL_BASE, ATTEN_NORM);
                        }
@@ -1002,19 +1065,33 @@ bool CSQC_Parse_TempEntity()
        return false;
 }
 
+bool autocvar_r_drawfog;
+bool autocvar_r_fog_exp2;
 string forcefog;
 void Fog_Force()
 {
        if (autocvar_cl_orthoview && autocvar_cl_orthoview_nofog)
-               localcmd("\nr_drawfog 0\n");
+       {
+               if (autocvar_r_drawfog)
+                       cvar_set("r_drawfog", "0");
+       }
        else if (forcefog != "")
-               localcmd(sprintf("\nfog %s\nr_fog_exp2 0\nr_drawfog 1\n", forcefog));
+       {
+               // using cvar_set as it's faster and safer than a command
+               if (!autocvar_r_drawfog)
+                       cvar_set("r_drawfog", "1");
+               if (autocvar_r_fog_exp2)
+                       cvar_set("r_fog_exp2", "0");
+               localcmd(sprintf("\nfog %s\n", forcefog));
+       }
 }
 
+bool net_handle_ServerWelcome();
 NET_HANDLE(ENT_CLIENT_SCORES_INFO, bool isnew)
 {
        make_pure(this);
        gametype = ReadRegistered(Gametypes);
+       strcpy(gametype_custom_name, ReadString());
        teamplay = _MapInfo_GetTeamPlayBool(gametype);
        HUD_ModIcons_SetFunc();
        FOREACH(Scores, true, {
@@ -1026,6 +1103,9 @@ NET_HANDLE(ENT_CLIENT_SCORES_INFO, bool isnew)
                strcpy(teamscores_label(i), ReadString());
                teamscores_flags(i) = ReadByte();
        }
+       bool welcome_msg_too = ReadByte();
+       if (welcome_msg_too)
+               net_handle_ServerWelcome();
        return = true;
        Scoreboard_InitScores();
        Gamemode_Init();
@@ -1115,6 +1195,29 @@ NET_HANDLE(TE_CSQC_RACE, bool isNew)
                                race_penaltyaccumulator = 0;
                                race_laptime = time; // valid
                        }
+
+                       // show checkpoint splits after a run
+                       // specs can switch players for confusing splits, deny service
+                       // if hit 255 finish line or if finish line is 0 or if restarted
+                       if (!entcs_IsSpectating(player_localnum)
+                       && (race_checkpoint == 255
+                       || race_checkpoint < race_checkpoint_splits_previous))
+                       {
+                               LOG_HELP("Checkpoint Splits:\n");
+                               for (int i = 0; i < 255; ++i)
+                               {
+                                       // does this index have a split?
+                                       if (race_checkpoint_splits[i])
+                                       {
+                                               // print it and free it
+                                               LOG_HELP(race_checkpoint_splits[i]);
+                                               strfree(race_checkpoint_splits[i]);
+                                       }
+                               }
+
+                               // allow registering 0th cp in case we start at 254 start line
+                               race_checkpoint_splits_previous = 0;
+                       }
                        break;
 
                case RACE_NET_CHECKPOINT_CLEAR:
@@ -1187,11 +1290,11 @@ NET_HANDLE(TE_CSQC_RACE, bool isNew)
                        race_server_record = ReadInt24_t();
                        break;
                case RACE_NET_SPEED_AWARD:
-                       race_speedaward = ReadInt24_t() * GetSpeedUnitFactor(autocvar_hud_panel_physics_speed_unit);
+                       race_speedaward = ReadInt24_t();
                        strcpy(race_speedaward_holder, ReadString());
                        break;
                case RACE_NET_SPEED_AWARD_BEST:
-                       race_speedaward_alltimebest = ReadInt24_t() * GetSpeedUnitFactor(autocvar_hud_panel_physics_speed_unit);
+                       race_speedaward_alltimebest = ReadInt24_t();
                        strcpy(race_speedaward_alltimebest_holder, ReadString());
                        break;
                case RACE_NET_RANKINGS_CNT:
@@ -1286,6 +1389,176 @@ NET_HANDLE(TE_CSQC_WEAPONCOMPLAIN, bool isNew)
        }
 }
 
+string translate_modifications(string s)
+{
+       return build_mutator_list(s);
+}
+
+string translate_weaponarena(string s)
+{
+       if (s == "") return s;
+       if (s == "All Weapons Arena") return _("All Weapons Arena");
+       if (s == "All Available Weapons Arena") return _("All Available Weapons Arena");
+       if (s == "Most Weapons Arena") return _("Most Weapons Arena");
+       if (s == "Most Available Weapons Arena") return _("Most Available Weapons Arena");
+       if (s == "Dev All Weapons Arena") return s; // development option, do not translate
+       if (s == "Dev All Available Weapons Arena") return s; // development option, do not translate
+       if (s == "No Weapons Arena") return _("No Weapons Arena");
+
+       int n = tokenizebyseparator(s, " & ");
+       string wpn_list = "";
+       for (int i = 0; i < n; ++i)
+       {
+               Weapon wep = Weapon_from_name(argv(i));
+               if (wep == WEP_Null)
+                       LOG_INFO("^3Warning: ^7server sent an invalid weapon name\n");
+               wpn_list = cons_mid(wpn_list, " & ", wep.m_name);
+       }
+       if (wpn_list != "")
+               return sprintf(_("%s Arena"), wpn_list);
+       else
+               return _("No Weapons Arena");
+}
+
+string GetVersionMessage(string hostversion, bool version_mismatch, bool version_check)
+{
+       string xonotic_hostversion = strcat("Xonotic ", hostversion);
+       if (version_mismatch)
+       {
+               if(!version_check)
+                       return strcat(sprintf(_("This is %s"), xonotic_hostversion), "\n^3",
+                               _("Your client version is outdated."), "\n\n\n",
+                               _("### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###"), "\n\n\n",
+                               _("Please update!"));
+               else
+                       return strcat(sprintf(_("This is %s"), xonotic_hostversion), "\n^3",
+                               _("This server is using an outdated Xonotic version."), "\n\n\n",
+                               _("### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###"));
+       }
+       return sprintf(_("Welcome to %s"), xonotic_hostversion);
+}
+
+bool net_handle_ServerWelcome()
+{
+       int flags = ReadByte();
+
+       campaign = flags & 1;
+       if (campaign)
+       {
+               int campaign_level = ReadByte();
+               // Menu can't build the whole campaign message because it lacks getcommandkey and CCR
+               // so we build part of the message here and let the menu insert the level description
+               // (that client doesn't know) by replacing the keyword _LEVEL_DESC
+               string key = getcommandkey(_("jump"), "+jump");
+               string msg = strcat(
+                       CCR("^F1"), sprintf(_("Level %d:"), campaign_level),
+                       sprintf(CCR(" ^BG%s\n\n"), "_LEVEL_DESC"),
+                       sprintf(CCR(_("^BGPress ^F2%s^BG to enter the game")), key));
+               msg = MakeConsoleSafe(strreplace("\n", "\\n", msg));
+               string welcomedialog_args = strcat("CAMPAIGN ", itos(campaign_level), " \"", msg, "\"");
+
+               localcmd("\nmenu_cmd directmenu Welcome ", welcomedialog_args, "\n");
+               return true;
+       }
+
+       strcpy(hostname, ReadString());
+       string hostversion = ReadString();
+       bool version_mismatch = flags & 2;
+       bool version_check = flags & 4;
+       MapInfo_Map_titlestring = ReadString();
+       MapInfo_Map_author = flags & 8 ? ReadString() : "";
+       srv_minplayers = ReadByte();
+       srv_maxplayers = ReadByte();
+       string modifications = translate_modifications(ReadString());
+       string weaponarena_list = translate_weaponarena(ReadString());
+       string cache_mutatormsg = ReadString();
+       string motd = ReadString();
+
+       string msg = GetVersionMessage(hostversion, version_mismatch, version_check);
+
+       msg = strcat(msg, "\n\n", _("Gametype:"), " ^1", MapInfo_Type_ToText(gametype), "\n");
+
+       msg = strcat(msg, "\n", _("Map:"), " ", MapInfo_Map_titlestring, "\n");
+       if (flags & 8)
+               msg = strcat(msg, "^9", _("by:"), " ", MapInfo_Map_author, "\n");
+
+       if (srv_minplayers || srv_maxplayers)
+       {
+               msg = strcat(msg, "\n", _("This match supports"), " ^5");
+               if (srv_minplayers == srv_maxplayers)
+                       msg = strcat(msg, sprintf(_("%d players"), srv_maxplayers), "\n");
+               else if (srv_minplayers && srv_maxplayers)
+                       msg = strcat(msg, sprintf(_("%d to %d players"), srv_minplayers, srv_maxplayers), "\n");
+               else if (srv_maxplayers)
+                       msg = strcat(msg, sprintf(_("%d players maximum"), srv_maxplayers), "\n");
+               else
+                       msg = strcat(msg, sprintf(_("%d players minimum"), srv_minplayers), "\n");
+       }
+
+       modifications = cons_mid(modifications, ", ", weaponarena_list);
+       if(modifications != "")
+               msg = strcat(msg, "\n", _("Active modifications:"), " ^3", modifications, "\n");
+
+       if (cache_mutatormsg != "")
+               msg = strcat(msg, "\n", _("Special gameplay tips:"), " ^7", cache_mutatormsg, "\n");
+       string mutator_msg = "";
+       MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
+       mutator_msg = M_ARGV(0, string);
+       msg = strcat(msg, mutator_msg); // trust that the mutator will do proper formatting
+
+       if (motd != "")
+               msg = strcat(msg, "\n^9↓ ", _("Server's message"), " ↓\n", motd);
+
+       strcpy(welcome_msg, msg);
+       welcome_msg_menu_check_maxtime = time + 1; // wait for menu to load before showing the welcome dialog
+       return true;
+}
+
+void Welcome_Message_Show_Try()
+{
+       if (!welcome_msg_menu_check_maxtime)
+               return;
+
+       // if want dialog check if menu is initialized but for a short time
+       if (cvar("_menu_initialized") == 2 || time > welcome_msg_menu_check_maxtime)
+       {
+               if (cvar("_menu_welcome_dialog_available"))
+               {
+                       string welcomedialog_args = strcat("HOSTNAME \"", hostname, "\"");
+                       string msg = MakeConsoleSafe(strreplace("\n", "\\n", welcome_msg));
+                       welcomedialog_args = strcat(welcomedialog_args, " WELCOME \"", msg, "\"");
+
+                       if (intermission || isdemo() || !autocvar_cl_welcome)
+                       {
+                               if (cvar("_menu_cmd_closemenu_available"))
+                               {
+                                       // initialize the dialog without opening it
+                                       localcmd("\nmenu_cmd closemenu Welcome ", welcomedialog_args, "\n");
+                               }
+                               else
+                               {
+                                       // legacy code for clients with old menus
+                                       // since togglemenu 0 doesn't close the dialog but only hides it,
+                                       // playing back a demo the Welcome dialog will pop up on the first ESC press
+                                       localcmd("\nmenu_cmd directmenu Welcome ", welcomedialog_args, "\n");
+                                       // close it after it's been initialized so it can still be opened manually
+                                       localcmd("\ntogglemenu 0\n");
+                               }
+                       }
+                       else
+                               localcmd("\nmenu_cmd directmenu Welcome ", welcomedialog_args, "\n");
+               }
+
+               strfree(welcome_msg);
+               welcome_msg_menu_check_maxtime = 0;
+       }
+}
+
+NET_HANDLE(TE_CSQC_SERVERWELCOME, bool isNew)
+{
+       return net_handle_ServerWelcome();
+}
+
 string _getcommandkey(string cmd_name, string command, bool forcename)
 {
        string keys;