]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/world.qc
Merge branch 'Mario/monsters' into 'develop'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / world.qc
index 1165bd303f0e6fef0f903d4ca3ebb0c7188dea99..55195217250186bf498db837ac07b236503f3d1b 100644 (file)
@@ -35,7 +35,6 @@
 #include <server/damage.qh>
 #include <server/gamelog.qh>
 #include <server/hook.qh>
-#include <server/intermission.qh>
 #include <server/ipban.qh>
 #include <server/items/items.qh>
 #include <server/main.qh>
@@ -46,7 +45,6 @@
 #include <server/scores_rules.qh>
 #include <server/spawnpoints.qh>
 #include <server/teamplay.qh>
-#include <server/weapons/common.qh>
 #include <server/weapons/weaponstats.qh>
 
 const float LATENCY_THINKRATE = 10;
@@ -69,7 +67,7 @@ void PingPLReport_Think(entity this)
        {
                WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT);
                WriteByte(MSG_BROADCAST, this.cnt);
-               WriteShort(MSG_BROADCAST, bound(1, CS(e).ping, 32767));
+               WriteShort(MSG_BROADCAST, bound(1, rint(CS(e).ping), 32767));
                WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_packetloss * 255), 255));
                WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_movementloss * 255), 255));
 
@@ -279,7 +277,6 @@ void cvar_changes_init()
                BADCVAR("g_duel_not_dm_maps");
                BADCVAR("g_freezetag");
                BADCVAR("g_freezetag_teams");
-               BADCVAR("g_invasion_teams");
                BADCVAR("g_invasion_type");
                BADCVAR("g_jailbreak");
                BADCVAR("g_jailbreak_teams");
@@ -353,6 +350,7 @@ void cvar_changes_init()
                BADCVAR("g_ctf_flag_glowtrails");
                BADCVAR("g_ctf_dynamiclights");
                BADCVAR("g_ctf_flag_pickup_verbosename");
+               BADCVAR("g_ctf_flagcarrier_auto_helpme_damage");
                BADPRESUFFIX("g_ctf_flag_", "_model");
                BADPRESUFFIX("g_ctf_flag_", "_skin");
                BADCVAR("g_domination_point_leadlimit");
@@ -388,6 +386,7 @@ void cvar_changes_init()
                BADCVAR("sv_minigames");
                BADCVAR("sv_namechangetimer");
                BADCVAR("sv_precacheplayermodels");
+               BADCVAR("sv_qcphysics");
                BADCVAR("sv_radio");
                BADCVAR("sv_stepheight");
                BADCVAR("sv_timeout");
@@ -459,10 +458,11 @@ void cvar_changes_init()
                BADCVAR("g_physics_clientselect");
                BADCVAR("g_pinata");
                BADCVAR("g_powerups");
+               BADCVAR("g_powerups_drop_ondeath");
                BADCVAR("g_player_brightness");
                BADCVAR("g_rocket_flying");
                BADCVAR("g_rocket_flying_disabledelays");
-               BADCVAR("g_spawnshieldtime");
+               BADPREFIX("g_spawnshield");
                BADCVAR("g_start_delay");
                BADCVAR("g_superspectate");
                BADCVAR("g_tdm_teams_override");
@@ -491,8 +491,8 @@ void cvar_changes_init()
                BADCVAR("sv_maxrate");
                BADCVAR("sv_motd");
                BADCVAR("sv_public");
-               BADCVAR("sv_ready_restart");
                BADCVAR("sv_showfps");
+               BADCVAR("sv_showspectators");
                BADCVAR("sv_status_privacy");
                BADCVAR("sv_taunt");
                BADCVAR("sv_vote_call");
@@ -687,6 +687,15 @@ spawnfunc(worldspawn)
 {
        server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated")));
 
+       if (autocvar_sv_termsofservice_url && autocvar_sv_termsofservice_url != "")
+       {
+               strcpy(sv_termsofservice_url_escaped, strreplace(":", "|", autocvar_sv_termsofservice_url));
+       }
+       else
+       {
+               strcpy(sv_termsofservice_url_escaped, "INVALID");
+       }
+
        bool wantrestart = false;
        {
                if (!server_is_dedicated)
@@ -809,11 +818,11 @@ spawnfunc(worldspawn)
 
        if(autocvar_g_campaign)
                CampaignPreInit();
+       else
+               PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode
 
        Map_MarkAsRecent(mapname);
 
-       PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode
-
        InitGameplayMode();
        static_init_late();
        static_init_precache();
@@ -832,52 +841,15 @@ spawnfunc(worldspawn)
 
        WaypointSprite_Init();
 
-       GameLogInit(); // prepare everything
        // NOTE for matchid:
        // changing the logic generating it is okay. But:
        // it HAS to stay <= 64 chars
        // character set: ASCII 33-126 without the following characters: : ; ' " \ $
-       if(autocvar_sv_eventlog)
-       {
-               string num = strftime_s(); // strftime(false, "%s") isn't reliable, see strftime_s description
-               string s = sprintf("%s.%s.%06d", itos(autocvar_sv_eventlog_files_counter), num, floor(random() * 1000000));
-               matchid = strzone(s);
-
-               GameLogEcho(strcat(":gamestart:", GetGametype(), "_", GetMapname(), ":", s));
-               s = ":gameinfo:mutators:LIST";
-
-               MUTATOR_CALLHOOK(BuildMutatorsString, s);
-               s = M_ARGV(0, string);
-
-               // initialiation stuff, not good in the mutator system
-               if(!autocvar_g_use_ammunition)
-                       s = strcat(s, ":no_use_ammunition");
+       // strftime(false, "%s") isn't reliable, see strftime_s description
+       matchid = strzone(sprintf("%d.%s.%06d", autocvar_sv_eventlog_files_counter, strftime_s(), random() * 1000000));
 
-               // initialiation stuff, not good in the mutator system
-               if(autocvar_g_pickup_items == 0)
-                       s = strcat(s, ":no_pickup_items");
-               if(autocvar_g_pickup_items > 0)
-                       s = strcat(s, ":pickup_items");
-
-               // initialiation stuff, not good in the mutator system
-               if(autocvar_g_weaponarena != "0")
-                       s = strcat(s, ":", autocvar_g_weaponarena, " arena");
-
-               // TODO to mutator system
-               if(autocvar_g_norecoil)
-                       s = strcat(s, ":norecoil");
-
-               // TODO to mutator system
-               if(autocvar_g_powerups == 0)
-                       s = strcat(s, ":no_powerups");
-               if(autocvar_g_powerups > 0)
-                       s = strcat(s, ":powerups");
-
-               GameLogEcho(s);
-               GameLogEcho(":gameinfo:end");
-       }
-       else
-               matchid = strzone(ftos(random()));
+       if(autocvar_sv_eventlog)
+               GameLogInit(); // requires matchid to be set
 
        cvar_set("nextmap", "");
 
@@ -891,11 +863,8 @@ spawnfunc(worldspawn)
        MapInfo_Enumerate();
        MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1);
 
-       if(fexists(strcat("scripts/", mapname, ".arena")))
-               cvar_settemp("sv_q3acompat_machineshotgunswap", "1");
-
-       if(fexists(strcat("scripts/", mapname, ".defi")))
-               cvar_settemp("sv_q3defragcompat", "1");
+       q3compat = BITSET(q3compat, Q3COMPAT_ARENA, fexists(strcat("scripts/", mapname, ".arena")));
+       q3compat = BITSET(q3compat, Q3COMPAT_DEFI, fexists(strcat("scripts/", mapname, ".defi")));
 
        if(whichpack(strcat("maps/", mapname, ".cfg")) != "")
        {
@@ -1030,7 +999,7 @@ spawnfunc(light)
        delete(this);
 }
 
-bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance)
+bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance, bool frompos)
 {
     float m = e.dphitcontentsmask;
     e.dphitcontentsmask = goodcontents | badcontents;
@@ -1087,15 +1056,23 @@ bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax
             continue;
 
                // rule 4: we must "see" some spawnpoint or item
-           entity sp = NULL;
-           IL_EACH(g_spawnpoints, checkpvs(mstart, it),
-           {
-               if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
-               {
-                       sp = it;
-                       break;
-               }
-           });
+               entity sp = NULL;
+               if(frompos)
+               {
+                       if((traceline(mstart, e.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
+                               sp = e;
+               }
+               if(!sp)
+               {
+                       IL_EACH(g_spawnpoints, checkpvs(mstart, it),
+                       {
+                               if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
+                               {
+                                       sp = it;
+                                       break;
+                               }
+                       });
+               }
                if(!sp)
                {
                        int items_checked = 0;
@@ -1154,7 +1131,7 @@ bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax
 
 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
 {
-       return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);
+       return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance, false);
 }
 
 /*
@@ -1220,7 +1197,7 @@ void DumpStats(float final)
        FOREACH_CLIENT(IS_REAL_CLIENT(it) || (IS_BOT_CLIENT(it) && autocvar_sv_logscores_bots), {
                s = strcat(":player:see-labels:", GetPlayerScoreString(it, 0), ":");
                s = strcat(s, ftos(rint(time - CS(it).jointime)), ":");
-               if(IS_PLAYER(it) || MUTATOR_CALLHOOK(GetPlayerStatus, it))
+               if(IS_PLAYER(it) || INGAME_JOINED(it))
                        s = strcat(s, ftos(it.team), ":");
                else
                        s = strcat(s, "spectator:");
@@ -1273,6 +1250,7 @@ only called if a time or frag limit has expired
 */
 void NextLevel()
 {
+       cvar_set("_endmatch", "0");
        game_stopped = true;
        intermission_running = true; // game over
 
@@ -1306,10 +1284,18 @@ void NextLevel()
 
        GameLogClose();
 
-       FOREACH_CLIENT(IS_PLAYER(it), {
+       int winner_team = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), {
                FixIntermissionClient(it);
                if(it.winning)
-                       bprint(playername(it.netname, it.team, false), " ^7wins.\n");
+               {
+                       if (teamplay && !winner_team)
+                       {
+                               winner_team = it.team;
+                               bprint(Team_ColorCode(winner_team), Team_ColorName_Upper(winner_team), "^7 team wins the match\n");
+                       }
+                       bprint(playername(it.netname, it.team, false), " ^7wins\n");
+               }
        });
 
        target_music_kill();
@@ -1323,7 +1309,7 @@ void NextLevel()
 }
 
 
-float InitiateSuddenDeath()
+int InitiateSuddenDeath()
 {
        // Check first whether normal overtimes could be added before initiating suddendeath mode
        // - for this timelimit_overtime needs to be >0 of course
@@ -1340,9 +1326,14 @@ float InitiateSuddenDeath()
                if(!checkrules_suddendeathend)
                {
                        if(autocvar_g_campaign)
+                       {
                                checkrules_suddendeathend = time; // no suddendeath in campaign
+                       }
                        else
+                       {
                                checkrules_suddendeathend = time + 60 * autocvar_timelimit_suddendeath;
+                               overtimes = -1;
+                       }
                        if(g_race && !g_race_qualifying)
                                race_StartCompleting();
                }
@@ -1353,6 +1344,7 @@ float InitiateSuddenDeath()
 void InitiateOvertime() // ONLY call this if InitiateSuddenDeath returned true
 {
        ++checkrules_overtimesadded;
+       overtimes = checkrules_overtimesadded;
        //add one more overtime by simply extending the timelimit
        cvar_set("timelimit", ftos(autocvar_timelimit + autocvar_timelimit_overtime));
        Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60);
@@ -1389,13 +1381,13 @@ float GetWinningCode(float fraglimitreached, float equality)
 // set the .winning flag for exactly those players with a given field value
 void SetWinners(.float field, float value)
 {
-       FOREACH_CLIENT(IS_PLAYER(it), { it.winning = (it.(field) == value); });
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { it.winning = (it.(field) == value); });
 }
 
 // set the .winning flag for those players with a given field value
 void AddWinners(.float field, float value)
 {
-       FOREACH_CLIENT(IS_PLAYER(it), {
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), {
                if(it.(field) == value)
                        it.winning = 1;
        });
@@ -1404,7 +1396,7 @@ void AddWinners(.float field, float value)
 // clear the .winning flags
 void ClearWinners()
 {
-       FOREACH_CLIENT(IS_PLAYER(it), { it.winning = 0; });
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { it.winning = 0; });
 }
 
 int fragsleft_last;
@@ -1603,19 +1595,18 @@ void CheckRules_World()
                leadlimit = 0; // no leadlimit for now
        }
 
-       if(timelimit > 0)
-       {
-               timelimit += game_starttime;
-       }
-       else if (timelimit < 0)
+       if (autocvar__endmatch || timelimit < 0)
        {
                // endmatch
                NextLevel();
                return;
        }
 
-       float wantovertime;
-       wantovertime = 0;
+       if(timelimit > 0)
+               timelimit += game_starttime;
+
+       int overtimes_prev = overtimes;
+       int wantovertime = 0;
 
        if(checkrules_suddendeathend)
        {
@@ -1651,7 +1642,7 @@ void CheckRules_World()
                                if(readyplayers || playerswithlaps >= 2)
                                {
                                        checkrules_suddendeathend = 0;
-                                       ReadyRestart(); // go to race
+                                       ReadyRestart(true); // go to race
                                        return;
                                }
                                else
@@ -1701,6 +1692,12 @@ void CheckRules_World()
 
        if(checkrules_status == WINNING_YES)
        {
+               if (overtimes == -1 && overtimes != overtimes_prev)
+               {
+                       // if suddendeathend overtime has just begun, revert it
+                       checkrules_suddendeathend = 0;
+                       overtimes = overtimes_prev;
+               }
                //print("WINNING\n");
                NextLevel();
        }
@@ -1862,25 +1859,25 @@ void readplayerstartcvars()
        else if (s == "all" || s == "1")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "All Weapons";
+               g_weaponarena_list = "All Weapons Arena";
                g_weaponarena_weapons = weapons_all();
        }
        else if (s == "devall")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Dev All Weapons";
+               g_weaponarena_list = "Dev All Weapons Arena";
                g_weaponarena_weapons = weapons_devall();
        }
        else if (s == "most")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Most Weapons";
+               g_weaponarena_list = "Most Weapons Arena";
                g_weaponarena_weapons = weapons_most();
        }
        else if (s == "all_available")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "All Available Weapons";
+               g_weaponarena_list = "All Available Weapons Arena";
 
                // this needs to run after weaponsInMapAll is initialized
                InitializeEntity(NULL, weaponarena_available_all_update, INITPRIO_FINDTARGET);
@@ -1888,7 +1885,7 @@ void readplayerstartcvars()
        else if (s == "devall_available")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Dev All Available Weapons";
+               g_weaponarena_list = "Dev All Available Weapons Arena";
 
                // this needs to run after weaponsInMapAll is initialized
                InitializeEntity(NULL, weaponarena_available_devall_update, INITPRIO_FINDTARGET);
@@ -1896,7 +1893,7 @@ void readplayerstartcvars()
        else if (s == "most_available")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Most Available Weapons";
+               g_weaponarena_list = "Most Available Weapons Arena";
 
                // this needs to run after weaponsInMapAll is initialized
                InitializeEntity(NULL, weaponarena_available_most_update, INITPRIO_FINDTARGET);
@@ -1904,7 +1901,7 @@ void readplayerstartcvars()
        else if (s == "none")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "No Weapons";
+               g_weaponarena_list = "No Weapons Arena";
        }
        else
        {
@@ -1918,10 +1915,13 @@ void readplayerstartcvars()
                        if(wep != WEP_Null)
                        {
                                g_weaponarena_weapons |= (wep.m_wepset);
-                               g_weaponarena_list = strcat(g_weaponarena_list, wep.m_name, " & ");
+                               g_weaponarena_list = strcat(g_weaponarena_list, wep.netname, " & ");
                        }
                }
-               g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
+               if (g_weaponarena_list != "") // remove trailing " & "
+                       g_weaponarena_list = substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3);
+               else // no valid weapon found
+                       g_weaponarena_list = "No Weapons Arena";
        }
 
        if (g_weaponarena)
@@ -1929,6 +1929,7 @@ void readplayerstartcvars()
                g_weapon_stay = 0; // incompatible
                start_weapons = g_weaponarena_weapons;
                start_items |= IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS;
+               g_weaponarena_list = strzone(g_weaponarena_list);
        }
        else
        {
@@ -2119,7 +2120,7 @@ void readlevelcvars()
 
     MUTATOR_CALLHOOK(ReadLevelCvars);
 
-       if (!warmup_stage)
+       if (!warmup_stage && !autocvar_g_campaign)
                game_starttime = time + cvar("g_start_delay");
 
        FOREACH(Weapons, it != WEP_Null, { it.wr_init(it); });
@@ -2211,11 +2212,11 @@ void droptofloor(entity this)
 }
 
 bool autocvar_sv_gameplayfix_multiplethinksperframe = true;
-void RunThink(entity this)
+void RunThink(entity this, float dt)
 {
        // don't let things stay in the past.
        // it is possible to start that way by a trigger with a local time.
-       if(this.nextthink <= 0 || this.nextthink > time + frametime)
+       if(this.nextthink <= 0 || this.nextthink > time + dt)
                return;
 
        float oldtime = time; // do we need to save this?
@@ -2231,7 +2232,7 @@ void RunThink(entity this)
                // we don't want to loop in that case, so exit if the new nextthink is
                // <= the time the qc was told, also exit if it is past the end of the
                // frame
-               if(this.nextthink <= time || this.nextthink > oldtime + frametime || !autocvar_sv_gameplayfix_multiplethinksperframe)
+               if(this.nextthink <= time || this.nextthink > oldtime + dt || !autocvar_sv_gameplayfix_multiplethinksperframe)
                        break;
        }
 
@@ -2261,8 +2262,8 @@ void Physics_Frame()
                        if(it.move_movetype == MOVETYPE_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH)
                                continue; // these movetypes have no regular think function
                        // handle thinking here
-                       if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + frametime)
-                               RunThink(it);
+                       if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + PHYS_INPUT_TIMELENGTH)
+                               RunThink(it, PHYS_INPUT_TIMELENGTH);
                }
        });
 
@@ -2293,9 +2294,10 @@ void EndFrame()
                        STAT(TYPEHIT_TIME, it) = time;
                } else if (e.killsound) {
                        STAT(KILL_TIME, it) = time;
-               } else if (e.damage_dealt) {
+               } else if (e.hitsound_damage_dealt) {
                        STAT(HIT_TIME, it) = time;
-                       STAT(DAMAGE_DEALT_TOTAL, it) += ceil(e.damage_dealt);
+                       // NOTE: this is not accurate as client code doesn't need so much accuracy for its purposes
+                       STAT(HITSOUND_DAMAGE_DEALT_TOTAL, it) += ceil(e.hitsound_damage_dealt);
                }
        });
        // add 1 frametime because after this, engine SV_Physics
@@ -2306,7 +2308,7 @@ void EndFrame()
        float altime = time + frametime * (1 + autocvar_g_antilag_nudge);
        FOREACH_CLIENT(true, {
                it.typehitsound = false;
-               it.damage_dealt = 0;
+               it.hitsound_damage_dealt = 0;
                it.killsound = false;
                antilag_record(it, CS(it), altime);
        });
@@ -2428,6 +2430,8 @@ void Shutdown()
 
                WeaponStats_Shutdown();
                MapInfo_Shutdown();
+
+               strfree(sv_termsofservice_url_escaped);
        }
        else if(world_initialized == 0)
        {