]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/g_world.qc
Merge branch 'master' into TimePath/modules
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_world.qc
index 5e242e48cd04054d005cc06df1272b06fb0316ff..5b63aa4e0c99c3e52389d6c6bf9ae1826895aa8f 100644 (file)
@@ -2,10 +2,10 @@
 
 #include "anticheat.qh"
 #include "antilag.qh"
-#include "bot/bot.qh"
+#include "bot/api.qh"
 #include "campaign.qh"
 #include "cheats.qh"
-#include "cl_client.qh"
+#include "client.qh"
 #include "command/common.qh"
 #include "command/getreplies.qh"
 #include "command/sv_cmd.qh"
@@ -13,7 +13,7 @@
 #include "g_hook.qh"
 #include "ipban.qh"
 #include "mapvoting.qh"
-#include "mutators/all.qh"
+#include "mutators/_mod.qh"
 #include "race.qh"
 #include "scores.qh"
 #include "teamplay.qh"
@@ -32,8 +32,8 @@
 #include "../common/triggers/trigger/secret.qh"
 #include "../common/triggers/target/music.qh"
 #include "../common/util.qh"
-#include "../common/items/all.qh"
-#include "../common/weapons/all.qh"
+#include "../common/items/_mod.qh"
+#include <common/weapons/_all.qh>
 #include "../common/state.qh"
 
 const float LATENCY_THINKRATE = 10;
@@ -129,11 +129,11 @@ void GotoFirstMap(entity this)
 
        if(time < 5)
        {
-               self.nextthink = time;
+               this.nextthink = time;
        }
        else
        {
-               self.nextthink = time + 1;
+               this.nextthink = time + 1;
                LOG_INFO("Waiting for _sv_init being set to 1 by initialization scripts...\n");
        }
 }
@@ -489,7 +489,7 @@ void detect_maptype()
                o.y += random() * (world.maxs.y - world.mins.y);
                o.z += random() * (world.maxs.z - world.mins.z);
 
-               tracebox(o, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), o - '0 0 32768', MOVE_WORLDONLY, world);
+               tracebox(o, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), o - '0 0 32768', MOVE_WORLDONLY, NULL);
                if(trace_fraction == 1)
                        continue;
 
@@ -497,7 +497,7 @@ void detect_maptype()
 
                for(i = 0; i < 64; i += 4)
                {
-                       tracebox(o, '-1 -1 -1' * i, '1 1 1' * i, o - '0 0 32768', MOVE_WORLDONLY, world);
+                       tracebox(o, '-1 -1 -1' * i, '1 1 1' * i, o - '0 0 32768', MOVE_WORLDONLY, NULL);
        if(trace_fraction == 1)
                continue;
                        LOG_INFO(ftos(i), " -> ", vtos(trace_endpos), "\n");
@@ -528,7 +528,7 @@ void RandomSeed_Spawn()
        setthink(randomseed, RandomSeed_Think);
        Net_LinkEntity(randomseed, false, 0, RandomSeed_Send);
 
-       WITHSELF(randomseed, getthink(randomseed)(randomseed)); // sets random seed and nextthink
+       getthink(randomseed)(randomseed); // sets random seed and nextthink
 }
 
 spawnfunc(__init_dedicated_server)
@@ -540,7 +540,7 @@ spawnfunc(__init_dedicated_server)
        cvar_string = cvar_string_normal;
        cvar_set = cvar_set_normal;
 
-       remove = remove_unsafely;
+       delete_fn = remove_unsafely;
 
        entity e = spawn();
        setthink(e, GotoFirstMap);
@@ -548,7 +548,7 @@ spawnfunc(__init_dedicated_server)
 
        e = new(info_player_deathmatch);  // safeguard against player joining
 
-       self.classname = "worldspawn"; // safeguard against various stuff ;)
+       this.classname = "worldspawn"; // safeguard against various stuff ;)
 
        // needs to be done so early because of the constants they create
        static_init();
@@ -646,9 +646,6 @@ spawnfunc(worldspawn)
                }
        }
 
-       float fd, l;
-       string s;
-
        cvar = cvar_normal;
        cvar_string = cvar_string_normal;
        cvar_set = cvar_set_normal;
@@ -657,14 +654,12 @@ spawnfunc(worldspawn)
                error("world already spawned - you may have EXACTLY ONE worldspawn!");
        world_already_spawned = true;
 
-       remove = remove_safely; // during spawning, watch what you remove!
+       delete_fn = remove_safely; // during spawning, watch what you remove!
 
        cvar_changes_init(); // do this very early now so it REALLY matches the server config
 
-       compressShortVector_init();
-
        maxclients = 0;
-       for (entity head = nextent(world); head; head = nextent(head))
+       for (entity head = nextent(NULL); head; head = nextent(head))
        {
                ++maxclients;
        }
@@ -739,7 +734,7 @@ spawnfunc(worldspawn)
        player_count = 0;
        bot_waypoints_for_items = autocvar_g_waypoints_for_items;
        if(bot_waypoints_for_items == 1)
-               if(self.spawnflags & SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS)
+               if(this.spawnflags & SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS)
                        bot_waypoints_for_items = 0;
 
        precache();
@@ -753,14 +748,14 @@ spawnfunc(worldspawn)
        // character set: ASCII 33-126 without the following characters: : ; ' " \ $
        if(autocvar_sv_eventlog)
        {
-               s = sprintf("%d.%s.%06d", itos(autocvar_sv_eventlog_files_counter), strftime(false, "%s"), floor(random() * 1000000));
+               string s = sprintf("%d.%s.%06d", itos(autocvar_sv_eventlog_files_counter), strftime(false, "%s"), floor(random() * 1000000));
                matchid = strzone(s);
 
                GameLogEcho(strcat(":gamestart:", GetGametype(), "_", GetMapname(), ":", s));
                s = ":gameinfo:mutators:LIST";
 
                MUTATOR_CALLHOOK(BuildMutatorsString, s);
-               s = ret_string;
+               s = M_ARGV(0, string);
 
                // initialiation stuff, not good in the mutator system
                if(!autocvar_g_use_ammunition)
@@ -806,12 +801,13 @@ spawnfunc(worldspawn)
 
        if(whichpack(strcat("maps/", mapname, ".cfg")) != "")
        {
-               fd = fopen(strcat("maps/", mapname, ".cfg"), FILE_READ);
+               int fd = fopen(strcat("maps/", mapname, ".cfg"), FILE_READ);
                if(fd != -1)
                {
+                       string s;
                        while((s = fgets(fd)))
                        {
-                               l = tokenize_console(s);
+                               int l = tokenize_console(s);
                                if(l < 2)
                                        continue;
                                if(argv(0) == "cd")
@@ -853,7 +849,7 @@ spawnfunc(worldspawn)
        monsterlist_reply = strzone(getmonsterlist());
        for(int i = 0; i < 10; ++i)
        {
-               s = getrecords(i);
+               string s = getrecords(i);
                if (s)
                        records_reply[i] = strzone(s);
        }
@@ -872,7 +868,7 @@ spawnfunc(worldspawn)
        // fill sv_curl_serverpackages from .serverpackage files
        if (autocvar_sv_curl_serverpackages_auto)
        {
-               s = "csprogs-" WATERMARK ".txt";
+               string s = "csprogs-" WATERMARK ".txt";
                // remove automatically managed files from the list to prevent duplicates
                for (int i = 0, n = tokenize_console(cvar_string("sv_curl_serverpackages")); i < n; ++i)
                {
@@ -884,7 +880,7 @@ spawnfunc(worldspawn)
                }
                // add automatically managed files to the list
                #define X(match) MACRO_BEGIN { \
-                       fd = search_begin(match, true, false); \
+                       int fd = search_begin(match, true, false); \
                        if (fd >= 0) \
                        { \
                                for (int i = 0, j = search_getsize(fd); i < j; ++i) \
@@ -915,15 +911,15 @@ spawnfunc(worldspawn)
        // save it for later
        modname = strzone(modname);
 
-       WinningConditionHelper(); // set worldstatus
+       WinningConditionHelper(this); // set worldstatus
 
        world_initialized = 1;
 }
 
 spawnfunc(light)
 {
-       //makestatic (self); // Who the f___ did that?
-       remove(this);
+       //makestatic (this); // Who the f___ did that?
+       delete(this);
 }
 
 string GetGametype()
@@ -970,10 +966,10 @@ float MapHasRightSize(string map)
                LOG_TRACE("checkwp "); LOG_TRACE(map);
                if(!fexists(strcat("maps/", map, ".waypoints")))
                {
-                       LOG_TRACE(": no waypoints\n");
+                       LOG_TRACE(": no waypoints");
                        return false;
                }
-               LOG_TRACE(": has waypoints\n");
+               LOG_TRACE(": has waypoints");
        }
 
        // open map size restriction file
@@ -988,18 +984,18 @@ float MapHasRightSize(string map)
                fclose(fh);
                if(player_count < mapmin)
                {
-                       LOG_TRACE("not enough\n");
+                       LOG_TRACE("not enough");
                        return false;
                }
                if(player_count > mapmax)
                {
-                       LOG_TRACE("too many\n");
+                       LOG_TRACE("too many");
                        return false;
                }
-               LOG_TRACE("right size\n");
+               LOG_TRACE("right size");
                return true;
        }
-       LOG_TRACE(": not found\n");
+       LOG_TRACE(": not found");
        return true;
 }
 
@@ -1038,7 +1034,7 @@ float Map_Check(float position, float pass)
                return 0;
        }
        else
-               LOG_TRACE( "Couldn't select '", filename, "'..\n" );
+               LOG_DEBUG( "Couldn't select '", filename, "'..." );
 
        return 0;
 }
@@ -1071,7 +1067,7 @@ float() MaplistMethod_Iterate = // usual method
 {
        float pass, i;
 
-       LOG_TRACE("Trying MaplistMethod_Iterate\n");
+       LOG_TRACE("Trying MaplistMethod_Iterate");
 
        for(pass = 1; pass <= 2; ++pass)
        {
@@ -1088,7 +1084,7 @@ float() MaplistMethod_Iterate = // usual method
 
 float() MaplistMethod_Repeat = // fallback method
 {
-       LOG_TRACE("Trying MaplistMethod_Repeat\n");
+       LOG_TRACE("Trying MaplistMethod_Repeat");
 
        if(Map_Check(Map_Current, 2))
                return Map_Current;
@@ -1099,7 +1095,7 @@ float() MaplistMethod_Random = // random map selection
 {
        float i, imax;
 
-       LOG_TRACE("Trying MaplistMethod_Random\n");
+       LOG_TRACE("Trying MaplistMethod_Random");
 
        imax = 42;
 
@@ -1119,7 +1115,7 @@ float(float exponent) MaplistMethod_Shuffle = // more clever shuffling
 {
        float i, j, imax, insertpos;
 
-       LOG_TRACE("Trying MaplistMethod_Shuffle\n");
+       LOG_TRACE("Trying MaplistMethod_Shuffle");
 
        imax = 42;
 
@@ -1131,7 +1127,7 @@ float(float exponent) MaplistMethod_Shuffle = // more clever shuffling
                insertpos = pow(random(), 1 / exponent);       // ]0, 1]
                insertpos = insertpos * (Map_Count - 1);       // ]0, Map_Count - 1]
                insertpos = ceil(insertpos) + 1;               // {2, 3, 4, ..., Map_Count}
-               LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos), "\n");
+               LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos));
 
                // insert the current map there
                newlist = "";
@@ -1296,18 +1292,19 @@ When the player presses attack or jump, change to the next level
 ============
 */
 .float autoscreenshot;
-void IntermissionThink()
-{SELFPARAM();
-       FixIntermissionClient(self);
+void IntermissionThink(entity this)
+{
+       FixIntermissionClient(this);
+       CSQCMODEL_AUTOUPDATE(this); // PlayerPostThink returns before calling this during intermission, so run it here
 
-       float server_screenshot = (autocvar_sv_autoscreenshot && self.cvar_cl_autoscreenshot);
-       float client_screenshot = (self.cvar_cl_autoscreenshot == 2);
+       float server_screenshot = (autocvar_sv_autoscreenshot && this.cvar_cl_autoscreenshot);
+       float client_screenshot = (this.cvar_cl_autoscreenshot == 2);
 
        if( (server_screenshot || client_screenshot)
-               && ((self.autoscreenshot > 0) && (time > self.autoscreenshot)) )
+               && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) )
        {
-               self.autoscreenshot = -1;
-               if(IS_REAL_CLIENT(self)) { stuffcmd(self, sprintf("\nscreenshot screenshots/autoscreenshot/%s-%s.jpg; echo \"^5A screenshot has been taken at request of the server.\"\n", GetMapname(), strftime(false, "%s"))); }
+               this.autoscreenshot = -1;
+               if(IS_REAL_CLIENT(this)) { stuffcmd(this, sprintf("\nscreenshot screenshots/autoscreenshot/%s-%s.jpg; echo \"^5A screenshot has been taken at request of the server.\"\n", GetMapname(), strftime(false, "%s"))); }
                return;
        }
 
@@ -1315,7 +1312,7 @@ void IntermissionThink()
                return;
 
        if(!mapvote_initialized)
-               if (time < intermission_exittime + 10 && !(PHYS_INPUT_BUTTON_ATCK(self) || PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_ATCK2(self) || PHYS_INPUT_BUTTON_HOOK(self) || PHYS_INPUT_BUTTON_USE(self)))
+               if (time < intermission_exittime + 10 && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_ATCK2(this) || PHYS_INPUT_BUTTON_HOOK(this) || PHYS_INPUT_BUTTON_USE(this)))
                        return;
 
        MapVote_Start();
@@ -1335,7 +1332,7 @@ entity FindIntermission()
        local   float cyc;
 
 // look for info_intermission first
-       spot = find (world, classname, "info_intermission");
+       spot = find (NULL, classname, "info_intermission");
        if (spot)
        {       // pick a random one
                cyc = random() * 4;
@@ -1350,22 +1347,22 @@ entity FindIntermission()
        }
 
 // then look for the start position
-       spot = find (world, classname, "info_player_start");
+       spot = find (NULL, classname, "info_player_start");
        if (spot)
                return spot;
 
 // testinfo_player_start is only found in regioned levels
-       spot = find (world, classname, "testplayerstart");
+       spot = find (NULL, classname, "testplayerstart");
        if (spot)
                return spot;
 
 // then look for the start position
-       spot = find (world, classname, "info_player_deathmatch");
+       spot = find (NULL, classname, "info_player_deathmatch");
        if (spot)
                return spot;
 
        //objerror ("FindIntermission: no spot");
-       return world;
+       return NULL;
 }
 */
 
@@ -1421,7 +1418,7 @@ void DumpStats(float final)
                        fputs(file, strcat(s, "\n"));
        }
 
-       s = strcat(":labels:player:", GetPlayerScoreString(world, 0));
+       s = strcat(":labels:player:", GetPlayerScoreString(NULL, 0));
        if(to_console)
                LOG_INFO(s, "\n");
        if(to_eventlog)
@@ -1432,7 +1429,7 @@ void DumpStats(float final)
        FOREACH_CLIENT(IS_REAL_CLIENT(it) || (IS_BOT_CLIENT(it) && autocvar_sv_logscores_bots), LAMBDA(
                s = strcat(":player:see-labels:", GetPlayerScoreString(it, 0), ":");
                s = strcat(s, ftos(rint(time - it.jointime)), ":");
-               if(IS_PLAYER(it) || MUTATOR_CALLHOOK(GetPlayerStatus, it, s))
+               if(IS_PLAYER(it) || MUTATOR_CALLHOOK(GetPlayerStatus, it))
                        s = strcat(s, ftos(it.team), ":");
                else
                        s = strcat(s, "spectator:");
@@ -1487,7 +1484,7 @@ void FixIntermissionClient(entity e)
                e.health = -2342;
                // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not)
                e.solid = SOLID_NOT;
-               e.movetype = MOVETYPE_NONE;
+               set_movetype(e, MOVETYPE_NONE);
                e.takedamage = DAMAGE_NO;
                for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
@@ -1549,7 +1546,7 @@ void NextLevel()
        PlayerStats_GameReport(true);
        WeaponStats_Shutdown();
 
-       Kill_Notification(NOTIF_ALL, world, MSG_CENTER, CPID_Null); // kill all centerprints now
+       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_Null); // kill all centerprints now
 
        if(autocvar_sv_eventlog)
                GameLogEcho(":gameover");
@@ -1562,7 +1559,7 @@ void NextLevel()
                        bprint(it.netname, " ^7wins.\n");
        ));
 
-       WITHSELF(NULL, target_music_kill());
+       target_music_kill();
 
        if(autocvar_g_campaign)
                CampaignPreIntermission();
@@ -1579,8 +1576,8 @@ CheckRules_Player
 Exit deathmatch games upon conditions
 ============
 */
-void CheckRules_Player()
-{SELFPARAM();
+void CheckRules_Player(entity this)
+{
        if (gameover)   // someone else quit the game already
                return;
 
@@ -1626,7 +1623,7 @@ void InitiateOvertime() // ONLY call this if InitiateSuddenDeath returned true
        tl += autocvar_timelimit_overtime;
        cvar_set("timelimit", ftos(tl));
 
-       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60);
+       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60);
 }
 
 float GetWinningCode(float fraglimitreached, float equality)
@@ -1682,7 +1679,7 @@ float WinningCondition_Scores(float limit, float leadlimit)
        float limitreached;
 
        // TODO make everything use THIS winning condition (except LMS)
-       WinningConditionHelper();
+       WinningConditionHelper(NULL);
 
        if(teamplay)
        {
@@ -1717,11 +1714,11 @@ float WinningCondition_Scores(float limit, float leadlimit)
 
                        if (limit)
                        if (leaderfrags == limit - 1)
-                               Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_REMAINING_FRAG_1);
+                               Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_1);
                        else if (leaderfrags == limit - 2)
-                               Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_REMAINING_FRAG_2);
+                               Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_2);
                        else if (leaderfrags == limit - 3)
-                               Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_REMAINING_FRAG_3);
+                               Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_3);
                }
        }
 
@@ -1798,7 +1795,7 @@ float WinningCondition_RanOutOfSpawns()
                        t = NUM_TEAM_3;
                else // if(team4_score)
                        t = NUM_TEAM_4;
-               CheckAllowedTeams(world);
+               CheckAllowedTeams(NULL);
                for(i = 0; i < MAX_TEAMSCORE; ++i)
                {
                        if(t != NUM_TEAM_1) if(c1 >= 0) TeamScore_AddToTeam(NUM_TEAM_1, i, -1000);
@@ -1879,9 +1876,9 @@ void CheckRules_World()
                {
                        checkrules_suddendeathwarning = true;
                        if(g_race && !g_race_qualifying)
-                               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_RACE_FINISHLAP);
+                               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_RACE_FINISHLAP);
                        else
-                               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_FRAG);
+                               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_FRAG);
                }
        }
        else
@@ -1983,11 +1980,89 @@ string GotoMap(string m)
                return "Map switch will happen after scoreboard.";
 }
 
+bool autocvar_sv_gameplayfix_multiplethinksperframe;
+void RunThink(entity this)
+{
+       // 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)
+               return;
+
+       float oldtime = time; // do we need to save this?
+
+       for (int iterations = 0; iterations < 128 && !wasfreed(this); iterations++)
+       {
+               time = max(oldtime, this.nextthink);
+               this.nextthink = 0;
+
+               if(getthink(this))
+                       getthink(this)(this);
+               // mods often set nextthink to time to cause a think every frame,
+               // 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)
+                       break;
+       }
+
+       time = oldtime;
+}
+
+bool autocvar_sv_freezenonclients;
+bool autocvar_sv_gameplayfix_delayprojectiles;
+void Physics_Frame()
+{
+       if(autocvar_sv_freezenonclients)
+               return;
+
+       FOREACH_ENTITY_FLOAT(pure_data, false,
+       {
+               if(IS_CLIENT(it) || it.classname == "" || it.move_movetype == MOVETYPE_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH || it.move_movetype == MOVETYPE_PHYSICS)
+                       continue;
+
+               int mt = it.move_movetype;
 
+               if(mt == MOVETYPE_PUSH || mt == MOVETYPE_FAKEPUSH || mt == MOVETYPE_PHYSICS)
+               {
+                       it.move_qcphysics = false;
+                       set_movetype(it, mt);
+                       continue;
+               }
+
+               set_movetype(it, ((it.move_qcphysics) ? MOVETYPE_NONE : it.move_movetype));
+
+               if(it.move_movetype == MOVETYPE_NONE)
+                       continue;
+
+               if(it.move_qcphysics)
+                       Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false);
+
+               if(it.movetype >= MOVETYPE_USER_FIRST && it.movetype <= MOVETYPE_USER_LAST) // these cases have no think handling
+               {
+                       // handle thinking here
+                       if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + frametime)
+                               RunThink(it);
+               }
+       });
+
+       if(autocvar_sv_gameplayfix_delayprojectiles >= 0)
+               return;
+
+       FOREACH_ENTITY_FLOAT(move_qcphysics, true,
+       {
+               if(IS_CLIENT(it) || is_pure(it) || it.classname == "" || it.move_movetype == MOVETYPE_NONE)
+                       continue;
+               Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false);
+       });
+}
+
+void systems_update();
 void EndFrame()
 {
        anticheat_endframe();
 
+       Physics_Frame();
+
        FOREACH_CLIENT(IS_REAL_CLIENT(it), {
                entity e = IS_SPEC(it) ? it.enemy : it;
                if (e.typehitsound) {
@@ -2008,13 +2083,16 @@ void EndFrame()
                it.damage_dealt = 0;
                antilag_record(it, CS(it), altime);
        });
-       FOREACH_ENTITY_FLAGS(flags, FL_MONSTER, {
+       IL_EACH(g_monsters, true,
+       {
                antilag_record(it, it, altime);
        });
        FOREACH_CLIENT(PS(it), {
                PlayerState s = PS(it);
                s.ps_push(s, it);
        });
+       systems_update();
+       IL_ENDFRAME();
 }
 
 
@@ -2090,7 +2168,7 @@ void Shutdown()
        if(world_initialized > 0)
        {
                world_initialized = 0;
-               LOG_TRACE("Saving persistent data...\n");
+               LOG_TRACE("Saving persistent data...");
                Ban_SaveBans();
 
                // playerstats with unfinished match
@@ -2113,7 +2191,7 @@ void Shutdown()
                CheatShutdown(); // must be after cheatcount check
                db_close(ServerProgsDB);
                db_close(TemporaryDB);
-               LOG_TRACE("Saving persistent data... done!\n");
+               LOG_TRACE("Saving persistent data... done!");
                // tell the bot system the game is ending now
                bot_endgame();