]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_world.qc
9f78b6750431dee07f31e161a69eb4dac954774c
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_world.qc
1 #include "g_world.qh"
2
3 #include "anticheat.qh"
4 #include "antilag.qh"
5 #include "bot/api.qh"
6 #include "campaign.qh"
7 #include "cheats.qh"
8 #include "client.qh"
9 #include "command/common.qh"
10 #include "command/getreplies.qh"
11 #include "command/sv_cmd.qh"
12 #include "command/vote.qh"
13 #include "g_hook.qh"
14 #include <server/gamelog.qh>
15 #include <server/g_damage.qh>
16 #include "ipban.qh"
17 #include "mapvoting.qh"
18 #include <server/mutators/_mod.qh>
19 #include "race.qh"
20 #include "scores.qh"
21 #include "scores_rules.qh"
22 #include "spawnpoints.qh"
23 #include "teamplay.qh"
24 #include "weapons/weaponstats.qh"
25 #include "../common/constants.qh"
26 #include <common/net_linked.qh>
27 #include "../common/deathtypes/all.qh"
28 #include <common/gamemodes/_mod.qh>
29 #include "../common/gamemodes/sv_rules.qh"
30 #include "../common/mapinfo.qh"
31 #include "../common/monsters/_mod.qh"
32 #include "../common/monsters/sv_monsters.qh"
33 #include "../common/vehicles/all.qh"
34 #include "../common/notifications/all.qh"
35 #include "../common/physics/player.qh"
36 #include "../common/playerstats.qh"
37 #include "../common/stats.qh"
38 #include "../common/teams.qh"
39 #include <common/mapobjects/triggers.qh>
40 #include "../common/mapobjects/trigger/secret.qh"
41 #include "../common/mapobjects/target/music.qh"
42 #include "../common/util.qh"
43 #include "../common/items/_mod.qh"
44 #include <common/weapons/_all.qh>
45 #include "../common/state.qh"
46
47 const float LATENCY_THINKRATE = 10;
48 .float latency_sum;
49 .float latency_cnt;
50 .float latency_time;
51 entity pingplreport;
52 void PingPLReport_Think(entity this)
53 {
54         float delta;
55         entity e;
56
57         delta = 3 / maxclients;
58         if(delta < sys_frametime)
59                 delta = 0;
60         this.nextthink = time + delta;
61
62         e = edict_num(this.cnt + 1);
63         if(IS_CLIENT(e) && IS_REAL_CLIENT(e))
64         {
65                 WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT);
66                 WriteByte(MSG_BROADCAST, this.cnt);
67                 WriteShort(MSG_BROADCAST, bound(1, CS(e).ping, 65535));
68                 WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_packetloss * 255), 255));
69                 WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_movementloss * 255), 255));
70
71                 // record latency times for clients throughout the match so we can report it to playerstats
72                 if(time > (CS(e).latency_time + LATENCY_THINKRATE))
73                 {
74                         CS(e).latency_sum += CS(e).ping;
75                         CS(e).latency_cnt += 1;
76                         CS(e).latency_time = time;
77                         //print("sum: ", ftos(CS(e).latency_sum), ", cnt: ", ftos(CS(e).latency_cnt), ", avg: ", ftos(CS(e).latency_sum / CS(e).latency_cnt), ".\n");
78                 }
79         }
80         else
81         {
82                 WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT);
83                 WriteByte(MSG_BROADCAST, this.cnt);
84                 WriteShort(MSG_BROADCAST, 0);
85                 WriteByte(MSG_BROADCAST, 0);
86                 WriteByte(MSG_BROADCAST, 0);
87         }
88         this.cnt = (this.cnt + 1) % maxclients;
89 }
90 void PingPLReport_Spawn()
91 {
92         pingplreport = new_pure(pingplreport);
93         setthink(pingplreport, PingPLReport_Think);
94         pingplreport.nextthink = time;
95 }
96
97 const float SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS = 1;
98 string redirection_target;
99 float world_initialized;
100
101 void SetDefaultAlpha()
102 {
103         if (!MUTATOR_CALLHOOK(SetDefaultAlpha))
104         {
105                 default_player_alpha = autocvar_g_player_alpha;
106                 if(default_player_alpha == 0)
107                         default_player_alpha = 1;
108                 default_weapon_alpha = default_player_alpha;
109         }
110 }
111
112 void GotoFirstMap(entity this)
113 {
114         float n;
115         if(autocvar__sv_init)
116         {
117                 // cvar_set("_sv_init", "0");
118                 // we do NOT set this to 0 any more, so someone "accidentally" changing
119                 // to this "init" map on a dedicated server will cause no permanent
120                 // harm
121                 if(autocvar_g_maplist_shuffle)
122                         ShuffleMaplist();
123                 n = tokenizebyseparator(autocvar_g_maplist, " ");
124                 cvar_set("g_maplist_index", ftos(n - 1)); // jump to map 0 in GotoNextMap
125
126                 MapInfo_Enumerate();
127                 MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
128
129                 if(!DoNextMapOverride(1))
130                         GotoNextMap(1);
131
132                 return;
133         }
134
135         if(time < 5)
136         {
137                 this.nextthink = time;
138         }
139         else
140         {
141                 this.nextthink = time + 1;
142                 LOG_INFO("Waiting for _sv_init being set to 1 by initialization scripts...");
143         }
144 }
145
146 void cvar_changes_init()
147 {
148         float h;
149         string k, v, d;
150         float n, i, adding, pureadding;
151
152         strfree(cvar_changes);
153         strfree(cvar_purechanges);
154         cvar_purechanges_count = 0;
155
156         h = buf_create();
157         buf_cvarlist(h, "", "_"); // exclude all _ cvars as they are temporary
158         n = buf_getsize(h);
159
160         adding = true;
161         pureadding = true;
162
163         for(i = 0; i < n; ++i)
164         {
165                 k = bufstr_get(h, i);
166
167 #define BADPREFIX(p) if(substring(k, 0, strlen(p)) == p) continue
168 #define BADPRESUFFIX(p,s) if(substring(k, 0, strlen(p)) == p && substring(k, -strlen(s), -1) == s) continue
169 #define BADCVAR(p) if(k == p) continue
170
171                 // general excludes and namespaces for server admin used cvars
172                 BADPREFIX("help_"); // PN's server has this listed as changed, let's not rat him out for THAT
173
174                 // internal
175                 BADPREFIX("csqc_");
176                 BADPREFIX("cvar_check_");
177                 BADCVAR("gamecfg");
178                 BADCVAR("g_configversion");
179                 BADCVAR("halflifebsp");
180                 BADCVAR("sv_mapformat_is_quake2");
181                 BADCVAR("sv_mapformat_is_quake3");
182                 BADPREFIX("sv_world");
183
184                 // client
185                 BADPREFIX("chase_");
186                 BADPREFIX("cl_");
187                 BADPREFIX("con_");
188                 BADPREFIX("scoreboard_");
189                 BADPREFIX("g_campaign");
190                 BADPREFIX("g_waypointsprite_");
191                 BADPREFIX("gl_");
192                 BADPREFIX("joy");
193                 BADPREFIX("hud_");
194                 BADPREFIX("m_");
195                 BADPREFIX("menu_");
196                 BADPREFIX("net_slist_");
197                 BADPREFIX("r_");
198                 BADPREFIX("sbar_");
199                 BADPREFIX("scr_");
200                 BADPREFIX("snd_");
201                 BADPREFIX("show");
202                 BADPREFIX("sensitivity");
203                 BADPREFIX("userbind");
204                 BADPREFIX("v_");
205                 BADPREFIX("vid_");
206                 BADPREFIX("crosshair");
207                 BADCVAR("mod_q3bsp_lightmapmergepower");
208                 BADCVAR("mod_q3bsp_nolightmaps");
209                 BADCVAR("fov");
210                 BADCVAR("mastervolume");
211                 BADCVAR("volume");
212                 BADCVAR("bgmvolume");
213                 BADCVAR("in_pitch_min");
214                 BADCVAR("in_pitch_max");
215
216                 // private
217                 BADCVAR("developer");
218                 BADCVAR("log_dest_udp");
219                 BADCVAR("net_address");
220                 BADCVAR("net_address_ipv6");
221                 BADCVAR("port");
222                 BADCVAR("savedgamecfg");
223                 BADCVAR("serverconfig");
224                 BADCVAR("sv_autoscreenshot");
225                 BADCVAR("sv_heartbeatperiod");
226                 BADCVAR("sv_vote_master_password");
227                 BADCVAR("sys_colortranslation");
228                 BADCVAR("sys_specialcharactertranslation");
229                 BADCVAR("timeformat");
230                 BADCVAR("timestamps");
231                 BADCVAR("g_require_stats");
232                 BADPREFIX("developer_");
233                 BADPREFIX("g_ban_");
234                 BADPREFIX("g_banned_list");
235                 BADPREFIX("g_require_stats_");
236                 BADPREFIX("g_chat_flood_");
237                 BADPREFIX("g_ghost_items");
238                 BADPREFIX("g_playerstats_");
239                 BADPREFIX("g_voice_flood_");
240                 BADPREFIX("log_file");
241                 BADPREFIX("quit_");
242                 BADPREFIX("rcon_");
243                 BADPREFIX("sv_allowdownloads");
244                 BADPREFIX("sv_autodemo");
245                 BADPREFIX("sv_curl_");
246                 BADPREFIX("sv_eventlog");
247                 BADPREFIX("sv_logscores_");
248                 BADPREFIX("sv_master");
249                 BADPREFIX("sv_weaponstats_");
250                 BADPREFIX("sv_waypointsprite_");
251                 BADCVAR("rescan_pending");
252
253                 // these can contain player IDs, so better hide
254                 BADPREFIX("g_forced_team_");
255                 BADCVAR("sv_muteban_list");
256                 BADCVAR("sv_voteban_list");
257                 BADCVAR("sv_allow_customplayermodels_idlist");
258                 BADCVAR("sv_allow_customplayermodels_speciallist");
259
260                 // mapinfo
261                 BADCVAR("fraglimit");
262                 BADCVAR("g_arena");
263                 BADCVAR("g_assault");
264                 BADCVAR("g_ca");
265                 BADCVAR("g_ca_teams");
266                 BADCVAR("g_conquest");
267                 BADCVAR("g_conquest_teams");
268                 BADCVAR("g_ctf");
269                 BADCVAR("g_cts");
270                 BADCVAR("g_dotc");
271                 BADCVAR("g_dm");
272                 BADCVAR("g_domination");
273                 BADCVAR("g_domination_default_teams");
274                 BADCVAR("g_duel");
275                 BADCVAR("g_duel_not_dm_maps");
276                 BADCVAR("g_freezetag");
277                 BADCVAR("g_freezetag_teams");
278                 BADCVAR("g_invasion_teams");
279                 BADCVAR("g_invasion_type");
280                 BADCVAR("g_jailbreak");
281                 BADCVAR("g_jailbreak_teams");
282                 BADCVAR("g_keepaway");
283                 BADCVAR("g_keyhunt");
284                 BADCVAR("g_keyhunt_teams");
285                 BADCVAR("g_lms");
286                 BADCVAR("g_nexball");
287                 BADCVAR("g_onslaught");
288                 BADCVAR("g_race");
289                 BADCVAR("g_race_laps_limit");
290                 BADCVAR("g_race_qualifying_timelimit");
291                 BADCVAR("g_race_qualifying_timelimit_override");
292                 BADCVAR("g_runematch");
293                 BADCVAR("g_shootfromeye");
294                 BADCVAR("g_snafu");
295                 BADCVAR("g_survival");
296                 BADCVAR("g_survival_not_dm_maps");
297                 BADCVAR("g_tdm");
298                 BADCVAR("g_tdm_on_dm_maps");
299                 BADCVAR("g_tdm_teams");
300                 BADCVAR("g_vip");
301                 BADCVAR("leadlimit");
302                 BADCVAR("nextmap");
303                 BADCVAR("teamplay");
304                 BADCVAR("timelimit");
305                 BADCVAR("g_mapinfo_settemp_acl");
306                 BADCVAR("g_mapinfo_ignore_warnings");
307                 BADCVAR("g_maplist_ignore_sizes");
308                 BADCVAR("g_maplist_sizes_count_bots");
309
310                 // long
311                 BADCVAR("hostname");
312                 BADCVAR("g_maplist");
313                 BADCVAR("g_maplist_mostrecent");
314                 BADCVAR("sv_motd");
315
316                 v = cvar_string(k);
317                 d = cvar_defstring(k);
318                 if(v == d)
319                         continue;
320
321                 if(adding)
322                 {
323                         cvar_changes = strcat(cvar_changes, k, " \"", v, "\" // \"", d, "\"\n");
324                         if(strlen(cvar_changes) > 16384)
325                         {
326                                 cvar_changes = "// too many settings have been changed to show them here\n";
327                                 adding = 0;
328                         }
329                 }
330
331                 // now check if the changes are actually gameplay relevant
332
333                 // does nothing gameplay relevant
334                 BADCVAR("captureleadlimit_override");
335                 BADCVAR("condump_stripcolors");
336                 BADCVAR("gameversion");
337                 BADCVAR("fs_gamedir");
338                 BADCVAR("g_allow_oldvortexbeam");
339                 BADCVAR("g_balance_kill_delay");
340                 BADCVAR("g_buffs_pickup_anyway");
341                 BADCVAR("g_buffs_randomize");
342                 BADCVAR("g_buffs_randomize_teamplay");
343                 BADCVAR("g_campcheck_distance");
344                 BADCVAR("g_chatsounds");
345                 BADCVAR("g_ca_point_leadlimit");
346                 BADCVAR("g_ca_point_limit");
347                 BADCVAR("g_ctf_captimerecord_always");
348                 BADCVAR("g_ctf_flag_glowtrails");
349                 BADCVAR("g_ctf_dynamiclights");
350                 BADCVAR("g_ctf_flag_pickup_verbosename");
351                 BADPRESUFFIX("g_ctf_flag_", "_model");
352                 BADPRESUFFIX("g_ctf_flag_", "_skin");
353                 BADCVAR("g_domination_point_leadlimit");
354                 BADCVAR("g_forced_respawn");
355                 BADCVAR("g_freezetag_point_leadlimit");
356                 BADCVAR("g_freezetag_point_limit");
357                 BADCVAR("g_glowtrails");
358                 BADCVAR("g_hats");
359                 BADCVAR("g_casings");
360                 BADCVAR("g_invasion_point_limit");
361                 BADCVAR("g_jump_grunt");
362                 BADCVAR("g_keepaway_ballcarrier_effects");
363                 BADCVAR("g_keepawayball_effects");
364                 BADCVAR("g_keyhunt_point_leadlimit");
365                 BADCVAR("g_nexball_goalleadlimit");
366                 BADCVAR("g_new_toys_autoreplace");
367                 BADCVAR("g_new_toys_use_pickupsound");
368                 BADCVAR("g_physics_predictall");
369                 BADCVAR("g_piggyback");
370                 BADCVAR("g_playerclip_collisions");
371                 BADCVAR("g_spawn_alloweffects");
372                 BADCVAR("g_tdm_point_leadlimit");
373                 BADCVAR("g_tdm_point_limit");
374                 BADCVAR("leadlimit_and_fraglimit");
375                 BADCVAR("leadlimit_override");
376                 BADCVAR("pausable");
377                 BADCVAR("sv_announcer");
378                 BADCVAR("sv_checkforpacketsduringsleep");
379                 BADCVAR("sv_damagetext");
380                 BADCVAR("sv_db_saveasdump");
381                 BADCVAR("sv_intermission_cdtrack");
382                 BADCVAR("sv_mapchange_delay");
383                 BADCVAR("sv_minigames");
384                 BADCVAR("sv_namechangetimer");
385                 BADCVAR("sv_precacheplayermodels");
386                 BADCVAR("sv_radio");
387                 BADCVAR("sv_stepheight");
388                 BADCVAR("sv_timeout");
389                 BADCVAR("sv_weapons_modeloverride");
390                 BADCVAR("w_prop_interval");
391                 BADPREFIX("chat_");
392                 BADPREFIX("crypto_");
393                 BADPREFIX("gameversion_");
394                 BADPREFIX("g_chat_");
395                 BADPREFIX("g_ctf_captimerecord_");
396                 BADPREFIX("g_hats_");
397                 BADPREFIX("g_maplist_");
398                 BADPREFIX("g_mod_");
399                 BADPREFIX("g_respawn_");
400                 BADPREFIX("net_");
401                 BADPREFIX("notification_");
402                 BADPREFIX("prvm_");
403                 BADPREFIX("skill_");
404                 BADPREFIX("sv_allow_");
405                 BADPREFIX("sv_cullentities_");
406                 BADPREFIX("sv_maxidle_");
407                 BADPREFIX("sv_minigames_");
408                 BADPREFIX("sv_radio_");
409                 BADPREFIX("sv_timeout_");
410                 BADPREFIX("sv_vote_");
411                 BADPREFIX("timelimit_");
412
413                 // allowed changes to server admins (please sync this to server.cfg)
414                 // vi commands:
415                 //   :/"impure"/,$d
416                 //   :g!,^\/\/[^ /],d
417                 //   :%s,//\([^ ]*\).*,BADCVAR("\1");,
418                 //   :%!sort
419                 // yes, this does contain some redundant stuff, don't really care
420                 BADPREFIX("bot_ai_");
421                 BADCVAR("bot_config_file");
422                 BADCVAR("bot_number");
423                 BADCVAR("bot_prefix");
424                 BADCVAR("bot_suffix");
425                 BADCVAR("capturelimit_override");
426                 BADCVAR("fraglimit_override");
427                 BADCVAR("gametype");
428                 BADCVAR("g_antilag");
429                 BADCVAR("g_balance_teams");
430                 BADCVAR("g_balance_teams_prevent_imbalance");
431                 BADCVAR("g_balance_teams_scorefactor");
432                 BADCVAR("g_ban_sync_trusted_servers");
433                 BADCVAR("g_ban_sync_uri");
434                 BADCVAR("g_buffs");
435                 BADCVAR("g_ca_teams_override");
436                 BADCVAR("g_ctf_fullbrightflags");
437                 BADCVAR("g_ctf_ignore_frags");
438                 BADCVAR("g_ctf_leaderboard");
439                 BADCVAR("g_domination_point_limit");
440                 BADCVAR("g_domination_teams_override");
441                 BADCVAR("g_freezetag_teams_override");
442                 BADCVAR("g_friendlyfire");
443                 BADCVAR("g_fullbrightitems");
444                 BADCVAR("g_fullbrightplayers");
445                 BADCVAR("g_keyhunt_point_limit");
446                 BADCVAR("g_keyhunt_teams_override");
447                 BADCVAR("g_lms_lives_override");
448                 BADCVAR("g_maplist");
449                 BADCVAR("g_maxplayers");
450                 BADCVAR("g_mirrordamage");
451                 BADCVAR("g_nexball_goallimit");
452                 BADCVAR("g_norecoil");
453                 BADCVAR("g_physics_clientselect");
454                 BADCVAR("g_pinata");
455                 BADCVAR("g_powerups");
456                 BADCVAR("g_player_brightness");
457                 BADCVAR("g_rocket_flying");
458                 BADCVAR("g_rocket_flying_disabledelays");
459                 BADCVAR("g_spawnshieldtime");
460                 BADCVAR("g_start_delay");
461                 BADCVAR("g_superspectate");
462                 BADCVAR("g_tdm_teams_override");
463                 BADCVAR("g_warmup");
464                 BADCVAR("g_weapon_stay"); BADPRESUFFIX("g_", "_weapon_stay");
465                 BADCVAR("hostname");
466                 BADCVAR("log_file");
467                 BADCVAR("maxplayers");
468                 BADCVAR("minplayers");
469                 BADCVAR("minplayers_per_team");
470                 BADCVAR("net_address");
471                 BADCVAR("port");
472                 BADCVAR("rcon_password");
473                 BADCVAR("rcon_restricted_commands");
474                 BADCVAR("rcon_restricted_password");
475                 BADCVAR("skill");
476                 BADCVAR("sv_adminnick");
477                 BADCVAR("sv_autoscreenshot");
478                 BADCVAR("sv_autotaunt");
479                 BADCVAR("sv_curl_defaulturl");
480                 BADCVAR("sv_defaultcharacter");
481                 BADCVAR("sv_defaultcharacterskin");
482                 BADCVAR("sv_defaultplayercolors");
483                 BADCVAR("sv_defaultplayermodel");
484                 BADCVAR("sv_defaultplayerskin");
485                 BADCVAR("sv_maxidle");
486                 BADCVAR("sv_maxrate");
487                 BADCVAR("sv_motd");
488                 BADCVAR("sv_public");
489                 BADCVAR("sv_ready_restart");
490                 BADCVAR("sv_status_privacy");
491                 BADCVAR("sv_taunt");
492                 BADCVAR("sv_vote_call");
493                 BADCVAR("sv_vote_commands");
494                 BADCVAR("sv_vote_majority_factor");
495                 BADCVAR("sv_vote_master");
496                 BADCVAR("sv_vote_master_commands");
497                 BADCVAR("sv_vote_master_password");
498                 BADCVAR("sv_vote_simple_majority_factor");
499                 BADCVAR("teamplay_mode");
500                 BADCVAR("timelimit_override");
501                 BADPREFIX("g_warmup_");
502                 BADPREFIX("sv_info_");
503                 BADPREFIX("sv_ready_restart_");
504
505                 // mutators that announce themselves properly to the server browser
506                 BADCVAR("g_instagib");
507                 BADCVAR("g_new_toys");
508                 BADCVAR("g_nix");
509                 BADCVAR("g_grappling_hook");
510                 BADCVAR("g_jetpack");
511
512                 // temporary for testing
513                 // TODO remove before 0.8.3 release
514                 BADCVAR("g_ca_weaponarena");
515                 BADCVAR("g_freezetag_weaponarena");
516                 BADCVAR("g_lms_weaponarena");
517                 BADCVAR("g_ctf_stalemate_time");
518
519                 if(cvar_string("g_mod_balance") == "Testing")
520                 {
521                         // (temporary) while using the Testing balance, any weapon balance cvars are allowed to be changed
522                         BADPREFIX("g_balance_");
523                 }
524
525 #undef BADPRESUFFIX
526 #undef BADPREFIX
527 #undef BADCVAR
528
529                 if(pureadding)
530                 {
531                         cvar_purechanges = strcat(cvar_purechanges, k, " \"", v, "\" // \"", d, "\"\n");
532                         if(strlen(cvar_purechanges) > 16384)
533                         {
534                                 cvar_purechanges = "// too many settings have been changed to show them here\n";
535                                 pureadding = 0;
536                         }
537                 }
538                 ++cvar_purechanges_count;
539                 // WARNING: this variable is used for the server list
540                 // NEVER dare to skip this code!
541                 // Hacks to intentionally appearing as "pure server" even though you DO have
542                 // modified settings may be punished by removal from the server list.
543                 // You can do to the variables cvar_changes and cvar_purechanges all you want,
544                 // though.
545         }
546         buf_del(h);
547         if(cvar_changes == "")
548                 cvar_changes = "// this server runs at default server settings\n";
549         else
550                 cvar_changes = strcat("// this server runs at modified server settings:\n", cvar_changes);
551         cvar_changes = strzone(cvar_changes);
552         if(cvar_purechanges == "")
553                 cvar_purechanges = "// this server runs at default gameplay settings\n";
554         else
555                 cvar_purechanges = strcat("// this server runs at modified gameplay settings:\n", cvar_purechanges);
556         cvar_purechanges = strzone(cvar_purechanges);
557 }
558
559 entity randomseed;
560 bool RandomSeed_Send(entity this, entity to, int sf)
561 {
562         WriteHeader(MSG_ENTITY, ENT_CLIENT_RANDOMSEED);
563         WriteShort(MSG_ENTITY, this.cnt);
564         return true;
565 }
566 void RandomSeed_Think(entity this)
567 {
568         this.cnt = bound(0, floor(random() * 65536), 65535);
569         this.nextthink = time + 5;
570
571         this.SendFlags |= 1;
572 }
573 void RandomSeed_Spawn()
574 {
575         randomseed = new_pure(randomseed);
576         setthink(randomseed, RandomSeed_Think);
577         Net_LinkEntity(randomseed, false, 0, RandomSeed_Send);
578
579         getthink(randomseed)(randomseed); // sets random seed and nextthink
580 }
581
582 spawnfunc(__init_dedicated_server)
583 {
584         // handler for _init/_init map (only for dedicated server initialization)
585
586         world_initialized = -1; // don't complain
587
588         delete_fn = remove_unsafely;
589
590         entity e = spawn();
591         setthink(e, GotoFirstMap);
592         e.nextthink = time; // this is usually 1 at this point
593
594         e = new(info_player_deathmatch);  // safeguard against player joining
595
596     // assign reflectively to avoid "assignment to world" warning
597     for (int i = 0, n = numentityfields(); i < n; ++i) {
598         string k = entityfieldname(i);
599         if (k == "classname") {
600             // safeguard against various stuff ;)
601             putentityfieldstring(i, this, "worldspawn");
602             break;
603         }
604     }
605
606         // needs to be done so early because of the constants they create
607         static_init();
608         static_init_late();
609         static_init_precache();
610
611         IL_PUSH(g_spawnpoints, e); // just incase
612
613         MapInfo_Enumerate();
614         MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
615 }
616
617 void __init_dedicated_server_shutdown() {
618         MapInfo_Shutdown();
619 }
620
621 STATIC_INIT_EARLY(maxclients)
622 {
623         maxclients = 0;
624         for (entity head = nextent(NULL); head; head = nextent(head)) {
625                 ++maxclients;
626         }
627 }
628
629 void default_delayedinit(entity this)
630 {
631         if(!scores_initialized)
632                 ScoreRules_generic();
633 }
634
635 void InitGameplayMode()
636 {
637         VoteReset();
638
639         // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
640         get_mi_min_max(1);
641         // assign reflectively to avoid "assignment to world" warning
642         int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) {
643             string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0';
644             if (v) {
645             putentityfieldstring(i, world, sprintf("%v", v));
646             if (++done == 2) break;
647         }
648         }
649         // currently, NetRadiant's limit is 131072 qu for each side
650         // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu
651         // set the distance according to map size but don't go over the limit to avoid issues with float precision
652         // in case somebody makes extremely large maps
653         max_shot_distance = min(230000, vlen(world.maxs - world.mins));
654
655         MapInfo_LoadMapSettings(mapname);
656         GameRules_teams(false);
657
658         if (!cvar_value_issafe(world.fog))
659         {
660                 LOG_INFO("The current map contains a potentially harmful fog setting, ignored");
661                 world.fog = string_null;
662         }
663         if(MapInfo_Map_fog != "")
664         {
665                 if(MapInfo_Map_fog == "none")
666                         world.fog = string_null;
667                 else
668                         world.fog = strzone(MapInfo_Map_fog);
669         }
670         clientstuff = strzone(MapInfo_Map_clientstuff);
671
672         MapInfo_ClearTemps();
673
674         gamemode_name = MapInfo_Type_ToText(MapInfo_LoadedGametype);
675
676         cache_mutatormsg = strzone("");
677         cache_lastmutatormsg = strzone("");
678
679         InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
680 }
681
682 void Map_MarkAsRecent(string m);
683 float world_already_spawned;
684 spawnfunc(worldspawn)
685 {
686         server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated")));
687
688         bool wantrestart = false;
689         {
690                 if (!server_is_dedicated)
691                 {
692                         // force unloading of server pk3 files when starting a listen server
693                         // localcmd("\nfs_rescan\n"); // FIXME: does more harm than good, has unintended side effects. What we really want is to unload temporary pk3s only
694                         // restore csqc_progname too
695                         string expect = "csprogs.dat";
696                         wantrestart = cvar_string("csqc_progname") != expect;
697                         cvar_set("csqc_progname", expect);
698                 }
699                 else
700                 {
701                         // Try to use versioned csprogs from pk3
702                         // Only ever use versioned csprogs.dat files on dedicated servers;
703                         // we need to reset csqc_progname on clients ourselves, and it's easier if the client's release name is constant
704                         string pk3csprogs = "csprogs-" WATERMARK ".dat";
705                         // This always works; fall back to it if a versioned csprogs.dat is suddenly missing
706                         string select = "csprogs.dat";
707                         if (fexists(pk3csprogs)) select = pk3csprogs;
708                         if (cvar_string("csqc_progname") != select)
709                         {
710                                 cvar_set("csqc_progname", select);
711                                 wantrestart = true;
712                         }
713                         // Check for updates on startup
714                         // We do it this way for atomicity so that connecting clients still match the server progs and don't disconnect
715                         int sentinel = fopen("progs.txt", FILE_READ);
716                         if (sentinel >= 0)
717                         {
718                                 string switchversion = fgets(sentinel);
719                                 fclose(sentinel);
720                                 if (switchversion != "" && switchversion != WATERMARK)
721                                 {
722                                         LOG_INFOF("Switching progs: " WATERMARK " -> %s", switchversion);
723                                         // if it doesn't exist, assume either:
724                                         //   a) the current program was overwritten
725                                         //   b) this is a client only update
726                                         string newprogs = sprintf("progs-%s.dat", switchversion);
727                                         if (fexists(newprogs))
728                                         {
729                                                 cvar_set("sv_progs", newprogs);
730                                                 wantrestart = true;
731                                         }
732                                         string newcsprogs = sprintf("csprogs-%s.dat", switchversion);
733                                         if (fexists(newcsprogs))
734                                         {
735                                                 cvar_set("csqc_progname", newcsprogs);
736                                                 wantrestart = true;
737                                         }
738                                 }
739                         }
740                 }
741                 if (wantrestart)
742                 {
743                         LOG_INFO("Restart requested");
744                         changelevel(mapname);
745                         // let initialization continue, shutdown depends on it
746                 }
747         }
748
749         if(world_already_spawned)
750                 error("world already spawned - you may have EXACTLY ONE worldspawn!");
751         world_already_spawned = true;
752
753         delete_fn = remove_safely; // during spawning, watch what you remove!
754
755         cvar_changes_init(); // do this very early now so it REALLY matches the server config
756
757         // needs to be done so early because of the constants they create
758         static_init();
759
760         ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid));
761
762         TemporaryDB = db_create();
763
764         // 0 normal
765         lightstyle(0, "m");
766
767         // 1 FLICKER (first variety)
768         lightstyle(1, "mmnmmommommnonmmonqnmmo");
769
770         // 2 SLOW STRONG PULSE
771         lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
772
773         // 3 CANDLE (first variety)
774         lightstyle(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
775
776         // 4 FAST STROBE
777         lightstyle(4, "mamamamamama");
778
779         // 5 GENTLE PULSE 1
780         lightstyle(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj");
781
782         // 6 FLICKER (second variety)
783         lightstyle(6, "nmonqnmomnmomomno");
784
785         // 7 CANDLE (second variety)
786         lightstyle(7, "mmmaaaabcdefgmmmmaaaammmaamm");
787
788         // 8 CANDLE (third variety)
789         lightstyle(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
790
791         // 9 SLOW STROBE (fourth variety)
792         lightstyle(9, "aaaaaaaazzzzzzzz");
793
794         // 10 FLUORESCENT FLICKER
795         lightstyle(10, "mmamammmmammamamaaamammma");
796
797         // 11 SLOW PULSE NOT FADE TO BLACK
798         lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
799
800         // styles 32-62 are assigned by the spawnfunc_light program for switchable lights
801
802         // 63 testing
803         lightstyle(63, "a");
804
805         if(autocvar_g_campaign)
806                 CampaignPreInit();
807
808         Map_MarkAsRecent(mapname);
809
810         PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode
811
812         InitGameplayMode();
813         static_init_late();
814         static_init_precache();
815         readlevelcvars();
816         GrappleHookInit();
817
818         GameRules_limit_fallbacks();
819
820         if(warmup_limit == 0)
821                 warmup_limit = (autocvar_timelimit > 0) ? autocvar_timelimit * 60 : autocvar_timelimit;
822
823         player_count = 0;
824         bot_waypoints_for_items = autocvar_g_waypoints_for_items;
825         if(bot_waypoints_for_items == 1)
826                 if(this.spawnflags & SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS)
827                         bot_waypoints_for_items = 0;
828
829         WaypointSprite_Init();
830
831         GameLogInit(); // prepare everything
832         // NOTE for matchid:
833         // changing the logic generating it is okay. But:
834         // it HAS to stay <= 64 chars
835         // character set: ASCII 33-126 without the following characters: : ; ' " \ $
836         if(autocvar_sv_eventlog)
837         {
838                 string s = sprintf("%s.%s.%06d", itos(autocvar_sv_eventlog_files_counter), strftime(false, "%s"), floor(random() * 1000000));
839                 matchid = strzone(s);
840
841                 GameLogEcho(strcat(":gamestart:", GetGametype(), "_", GetMapname(), ":", s));
842                 s = ":gameinfo:mutators:LIST";
843
844                 MUTATOR_CALLHOOK(BuildMutatorsString, s);
845                 s = M_ARGV(0, string);
846
847                 // initialiation stuff, not good in the mutator system
848                 if(!autocvar_g_use_ammunition)
849                         s = strcat(s, ":no_use_ammunition");
850
851                 // initialiation stuff, not good in the mutator system
852                 if(autocvar_g_pickup_items == 0)
853                         s = strcat(s, ":no_pickup_items");
854                 if(autocvar_g_pickup_items > 0)
855                         s = strcat(s, ":pickup_items");
856
857                 // initialiation stuff, not good in the mutator system
858                 if(autocvar_g_weaponarena != "0")
859                         s = strcat(s, ":", autocvar_g_weaponarena, " arena");
860
861                 // TODO to mutator system
862                 if(autocvar_g_norecoil)
863                         s = strcat(s, ":norecoil");
864
865                 // TODO to mutator system
866                 if(autocvar_g_powerups == 0)
867                         s = strcat(s, ":no_powerups");
868                 if(autocvar_g_powerups > 0)
869                         s = strcat(s, ":powerups");
870
871                 GameLogEcho(s);
872                 GameLogEcho(":gameinfo:end");
873         }
874         else
875                 matchid = strzone(ftos(random()));
876
877         cvar_set("nextmap", "");
878
879         SetDefaultAlpha();
880
881         if(autocvar_g_campaign)
882                 CampaignPostInit();
883
884         Ban_LoadBans();
885
886         MapInfo_Enumerate();
887         MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1);
888
889         if(fexists(strcat("scripts/", mapname, ".arena")))
890                 cvar_settemp("sv_q3acompat_machineshotgunswap", "1");
891
892         if(fexists(strcat("scripts/", mapname, ".defi")))
893                 cvar_settemp("sv_q3defragcompat", "1");
894
895         if(whichpack(strcat("maps/", mapname, ".cfg")) != "")
896         {
897                 int fd = fopen(strcat("maps/", mapname, ".cfg"), FILE_READ);
898                 if(fd != -1)
899                 {
900                         string s;
901                         while((s = fgets(fd)))
902                         {
903                                 int l = tokenize_console(s);
904                                 if(l < 2)
905                                         continue;
906                                 if(argv(0) == "cd")
907                                 {
908                                         string trackname = argv(2);
909                                         LOG_INFO("Found ^1UNSUPPORTED^7 cd loop command in .cfg file; put this line in mapinfo instead:");
910                                         LOG_INFO("  cdtrack ", trackname);
911                                         if (cvar_value_issafe(trackname))
912                                         {
913                                                 string newstuff = strcat(clientstuff, "cd loop \"", trackname, "\"\n");
914                                                 strcpy(clientstuff, newstuff);
915                                         }
916                                 }
917                                 else if(argv(0) == "fog")
918                                 {
919                                         LOG_INFO("Found ^1UNSUPPORTED^7 fog command in .cfg file; put this line in worldspawn in the .map/.bsp/.ent file instead:");
920                                         LOG_INFO("  \"fog\" \"", s, "\"");
921                                 }
922                                 else if(argv(0) == "set")
923                                 {
924                                         LOG_INFO("Found ^1UNSUPPORTED^7 set command in .cfg file; put this line in mapinfo instead:");
925                                         LOG_INFO("  clientsettemp_for_type all ", argv(1), " ", argv(2));
926                                 }
927                                 else if(argv(0) != "//")
928                                 {
929                                         LOG_INFO("Found ^1UNSUPPORTED^7 set command in .cfg file; put this line in mapinfo instead:");
930                                         LOG_INFO("  clientsettemp_for_type all ", argv(0), " ", argv(1));
931                                 }
932                         }
933                         fclose(fd);
934                 }
935         }
936
937         WeaponStats_Init();
938
939         Nagger_Init();
940
941         // set up information replies for clients and server to use
942         maplist_reply = strzone(getmaplist());
943         lsmaps_reply = strzone(getlsmaps());
944         monsterlist_reply = strzone(getmonsterlist());
945         for(int i = 0; i < 10; ++i)
946         {
947                 string s = getrecords(i);
948                 if (s)
949                         records_reply[i] = strzone(s);
950         }
951         ladder_reply = strzone(getladder());
952         rankings_reply = strzone(getrankings());
953
954         // begin other init
955         ClientInit_Spawn();
956         RandomSeed_Spawn();
957         PingPLReport_Spawn();
958
959         CheatInit();
960
961         if (!wantrestart) localcmd("\n_sv_hook_gamestart ", GetGametype(), "\n");
962
963         // fill sv_curl_serverpackages from .serverpackage files
964         if (autocvar_sv_curl_serverpackages_auto)
965         {
966                 string s = "csprogs-" WATERMARK ".txt";
967                 // remove automatically managed files from the list to prevent duplicates
968                 for (int i = 0, n = tokenize_console(cvar_string("sv_curl_serverpackages")); i < n; ++i)
969                 {
970                         string pkg = argv(i);
971                         if (startsWith(pkg, "csprogs-")) continue;
972                         if (endsWith(pkg, "-serverpackage.txt")) continue;
973                         if (endsWith(pkg, ".serverpackage")) continue;  // OLD legacy
974                         s = cons(s, pkg);
975                 }
976                 // add automatically managed files to the list
977                 #define X(match) MACRO_BEGIN \
978                         int fd = search_begin(match, true, false); \
979                         if (fd >= 0) \
980                         { \
981                                 for (int i = 0, j = search_getsize(fd); i < j; ++i) \
982                                 { \
983                                         s = cons(s, search_getfilename(fd, i)); \
984                                 } \
985                                 search_end(fd); \
986                         } \
987                 MACRO_END
988                 X("*-serverpackage.txt");
989                 X("*.serverpackage");
990                 #undef X
991                 cvar_set("sv_curl_serverpackages", s);
992         }
993
994         // MOD AUTHORS: change this, and possibly remove a few of the blocks below to ignore certain changes
995         modname = "Xonotic";
996         // physics/balance/config changes that count as mod
997         if(cvar_string("g_mod_physics") != cvar_defstring("g_mod_physics"))
998                 modname = cvar_string("g_mod_physics");
999         if(cvar_string("g_mod_balance") != cvar_defstring("g_mod_balance") && cvar_string("g_mod_balance") != "Testing")
1000                 modname = cvar_string("g_mod_balance");
1001         if(cvar_string("g_mod_config") != cvar_defstring("g_mod_config"))
1002                 modname = cvar_string("g_mod_config");
1003         // extra mutators that deserve to count as mod
1004         MUTATOR_CALLHOOK(SetModname, modname);
1005         modname = M_ARGV(0, string);
1006
1007         // save it for later
1008         modname = strzone(modname);
1009
1010         WinningConditionHelper(this); // set worldstatus
1011
1012         world_initialized = 1;
1013         __spawnfunc_spawn_all();
1014 }
1015
1016 spawnfunc(light)
1017 {
1018         //makestatic (this); // Who the f___ did that?
1019         delete(this);
1020 }
1021
1022 string GetGametype()
1023 {
1024         return MapInfo_Type_ToString(MapInfo_LoadedGametype);
1025 }
1026
1027 string GetMapname()
1028 {
1029         return mapname;
1030 }
1031
1032 float Map_Count, Map_Current;
1033 string Map_Current_Name;
1034
1035 // NOTE: this now expects the map list to be already tokenized and the count in Map_Count
1036 int GetMaplistPosition()
1037 {
1038         string map = GetMapname();
1039         int idx = autocvar_g_maplist_index;
1040
1041         if(idx >= 0)
1042         {
1043                 if(idx < Map_Count)
1044                 {
1045                         if(map == argv(idx))
1046                         {
1047                                 return idx;
1048                         }
1049                 }
1050         }
1051
1052         for(int pos = 0; pos < Map_Count; ++pos)
1053         {
1054                 if(map == argv(pos))
1055                         return pos;
1056         }
1057
1058         // resume normal maplist rotation if current map is not in g_maplist
1059         return idx;
1060 }
1061
1062 bool MapHasRightSize(string map)
1063 {
1064         int minplayers = max(0, floor(autocvar_minplayers));
1065         if (teamplay)
1066                 minplayers = max(0, floor(autocvar_minplayers_per_team) * AvailableTeams());
1067         if (autocvar_g_maplist_check_waypoints
1068                 && (currentbots || autocvar_bot_number || player_count < minplayers))
1069         {
1070                 string checkwp_msg = strcat("checkwp ", map);
1071                 if(!fexists(strcat("maps/", map, ".waypoints")))
1072                 {
1073                         LOG_TRACE(checkwp_msg, ": no waypoints");
1074                         return false;
1075                 }
1076                 LOG_TRACE(checkwp_msg, ": has waypoints");
1077         }
1078
1079         if(autocvar_g_maplist_ignore_sizes)
1080                 return true;
1081
1082         // open map size restriction file
1083         string opensize_msg = strcat("opensize ", map);
1084         float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
1085         int player_limit = ((autocvar_g_maplist_sizes_count_maxplayers) ? GetPlayerLimit() : 0);
1086         int pcount = ((player_limit > 0) ? min(player_count, player_limit) : player_count); // bind it to the player limit so that forced spectators don't influence the limits
1087         if(!autocvar_g_maplist_sizes_count_bots)
1088                 pcount -= currentbots;
1089         if(fh >= 0)
1090         {
1091                 opensize_msg = strcat(opensize_msg, ": ok, ");
1092                 int mapmin = stoi(fgets(fh));
1093                 int mapmax = stoi(fgets(fh));
1094                 fclose(fh);
1095                 if(pcount < mapmin)
1096                 {
1097                         LOG_TRACE(opensize_msg, "not enough");
1098                         return false;
1099                 }
1100                 if(mapmax && pcount > mapmax)
1101                 {
1102                         LOG_TRACE(opensize_msg, "too many");
1103                         return false;
1104                 }
1105                 LOG_TRACE(opensize_msg, "right size");
1106                 return true;
1107         }
1108         LOG_TRACE(opensize_msg, ": not found");
1109         return true;
1110 }
1111
1112 string Map_Filename(float position)
1113 {
1114         return strcat("maps/", argv(position), ".bsp");
1115 }
1116
1117 void Map_MarkAsRecent(string m)
1118 {
1119         cvar_set("g_maplist_mostrecent", strwords(cons(m, autocvar_g_maplist_mostrecent), max(0, autocvar_g_maplist_mostrecent_count)));
1120 }
1121
1122 float Map_IsRecent(string m)
1123 {
1124         return strhasword(autocvar_g_maplist_mostrecent, m);
1125 }
1126
1127 float Map_Check(float position, float pass)
1128 {
1129         string filename;
1130         string map_next;
1131         map_next = argv(position);
1132         if(pass <= 1)
1133         {
1134                 if(Map_IsRecent(map_next))
1135                         return 0;
1136         }
1137         filename = Map_Filename(position);
1138         if(MapInfo_CheckMap(map_next))
1139         {
1140                 if(pass == 2)
1141                         return 1;
1142                 if(MapHasRightSize(map_next))
1143                         return 1;
1144                 return 0;
1145         }
1146         else
1147                 LOG_DEBUG( "Couldn't select '", filename, "'..." );
1148
1149         return 0;
1150 }
1151
1152 void Map_Goto_SetStr(string nextmapname)
1153 {
1154         if(getmapname_stored != "")
1155                 strunzone(getmapname_stored);
1156         if(nextmapname == "")
1157                 getmapname_stored = "";
1158         else
1159                 getmapname_stored = strzone(nextmapname);
1160 }
1161
1162 void Map_Goto_SetFloat(float position)
1163 {
1164         cvar_set("g_maplist_index", ftos(position));
1165         Map_Goto_SetStr(argv(position));
1166 }
1167
1168 void Map_Goto(float reinit)
1169 {
1170         MapInfo_LoadMap(getmapname_stored, reinit);
1171 }
1172
1173 // return codes of map selectors:
1174 //   -1 = temporary failure (that is, try some method that is guaranteed to succeed)
1175 //   -2 = permanent failure
1176 float MaplistMethod_Iterate() // usual method
1177 {
1178         float pass, i;
1179
1180         LOG_TRACE("Trying MaplistMethod_Iterate");
1181
1182         for(pass = 1; pass <= 2; ++pass)
1183         {
1184                 for(i = 1; i < Map_Count; ++i)
1185                 {
1186                         float mapindex;
1187                         mapindex = (i + Map_Current) % Map_Count;
1188                         if(Map_Check(mapindex, pass))
1189                                 return mapindex;
1190                 }
1191         }
1192         return -1;
1193 }
1194
1195 float MaplistMethod_Repeat() // fallback method
1196 {
1197         LOG_TRACE("Trying MaplistMethod_Repeat");
1198
1199         if(Map_Check(Map_Current, 2))
1200                 return Map_Current;
1201         return -2;
1202 }
1203
1204 float MaplistMethod_Random() // random map selection
1205 {
1206         float i, imax;
1207
1208         LOG_TRACE("Trying MaplistMethod_Random");
1209
1210         imax = 42;
1211
1212         for(i = 0; i <= imax; ++i)
1213         {
1214                 float mapindex;
1215                 mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map
1216                 if(Map_Check(mapindex, 1))
1217                         return mapindex;
1218         }
1219         return -1;
1220 }
1221
1222 float MaplistMethod_Shuffle(float exponent) // more clever shuffling
1223 // the exponent sets a bias on the map selection:
1224 // the higher the exponent, the less likely "shortly repeated" same maps are
1225 {
1226         float i, j, imax, insertpos;
1227
1228         LOG_TRACE("Trying MaplistMethod_Shuffle");
1229
1230         imax = 42;
1231
1232         for(i = 0; i <= imax; ++i)
1233         {
1234                 string newlist;
1235
1236                 // now reinsert this at another position
1237                 insertpos = (random() ** (1 / exponent));       // ]0, 1]
1238                 insertpos = insertpos * (Map_Count - 1);       // ]0, Map_Count - 1]
1239                 insertpos = ceil(insertpos) + 1;               // {2, 3, 4, ..., Map_Count}
1240                 LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos));
1241
1242                 // insert the current map there
1243                 newlist = "";
1244                 for(j = 1; j < insertpos; ++j)                 // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above
1245                         newlist = strcat(newlist, " ", argv(j));
1246                 newlist = strcat(newlist, " ", argv(0));       // now insert the just selected map
1247                 for(j = insertpos; j < Map_Count; ++j)         // i == Map_Count: no loop, has just been inserted as last
1248                         newlist = strcat(newlist, " ", argv(j));
1249                 newlist = substring(newlist, 1, strlen(newlist) - 1);
1250                 cvar_set("g_maplist", newlist);
1251                 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
1252
1253                 // NOTE: the selected map has just been inserted at (insertpos-1)th position
1254                 Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working
1255                 if(Map_Check(Map_Current, 1))
1256                         return Map_Current;
1257         }
1258         return -1;
1259 }
1260
1261 void Maplist_Init()
1262 {
1263         float i = Map_Count = 0;
1264         if(autocvar_g_maplist != "")
1265         {
1266                 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
1267                 for (i = 0; i < Map_Count; ++i)
1268                 {
1269                         if (Map_Check(i, 2))
1270                                 break;
1271                 }
1272         }
1273
1274         if (i == Map_Count)
1275         {
1276                 bprint( "Maplist contains no usable maps!  Resetting it to default map list.\n" );
1277                 cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags() | MAPINFO_FLAG_NOAUTOMAPLIST));
1278                 if(autocvar_g_maplist_shuffle)
1279                         ShuffleMaplist();
1280                 if(!server_is_dedicated)
1281                         localcmd("\nmenu_cmd sync\n");
1282                 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
1283         }
1284         if(Map_Count == 0)
1285                 error("empty maplist, cannot select a new map");
1286         Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
1287
1288         strcpy(Map_Current_Name, argv(Map_Current)); // will be automatically freed on exit thanks to DP
1289         // this may or may not be correct, but who cares, in the worst case a map
1290         // isn't chosen in the first pass that should have been
1291 }
1292
1293 string GetNextMap()
1294 {
1295         Maplist_Init();
1296         float nextMap = -1;
1297
1298         if(nextMap == -1)
1299                 if(autocvar_g_maplist_shuffle > 0)
1300                         nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1);
1301
1302         if(nextMap == -1)
1303                 if(autocvar_g_maplist_selectrandom)
1304                         nextMap = MaplistMethod_Random();
1305
1306         if(nextMap == -1)
1307                 nextMap = MaplistMethod_Iterate();
1308
1309         if(nextMap == -1)
1310                 nextMap = MaplistMethod_Repeat();
1311
1312         if(nextMap >= 0)
1313         {
1314                 Map_Goto_SetFloat(nextMap);
1315                 return getmapname_stored;
1316         }
1317
1318         return "";
1319 }
1320
1321 float DoNextMapOverride(float reinit)
1322 {
1323         if(autocvar_g_campaign)
1324         {
1325                 CampaignPostIntermission();
1326                 alreadychangedlevel = true;
1327                 return true;
1328         }
1329         if(autocvar_quit_when_empty)
1330         {
1331                 if(player_count <= currentbots)
1332                 {
1333                         localcmd("quit\n");
1334                         alreadychangedlevel = true;
1335                         return true;
1336                 }
1337         }
1338         if(autocvar_quit_and_redirect != "")
1339         {
1340                 redirection_target = strzone(autocvar_quit_and_redirect);
1341                 alreadychangedlevel = true;
1342                 return true;
1343         }
1344         if (!reinit && autocvar_samelevel) // if samelevel is set, stay on same level
1345         {
1346                 localcmd("restart\n");
1347                 alreadychangedlevel = true;
1348                 return true;
1349         }
1350         if(autocvar_nextmap != "")
1351         {
1352                 string m;
1353                 m = GameTypeVote_MapInfo_FixName(autocvar_nextmap);
1354                 cvar_set("nextmap",m);
1355
1356                 if(!m || gametypevote)
1357                         return false;
1358                 if(autocvar_sv_vote_gametype)
1359                 {
1360                         Map_Goto_SetStr(m);
1361                         return false;
1362                 }
1363
1364                 if(MapInfo_CheckMap(m))
1365                 {
1366                         Map_Goto_SetStr(m);
1367                         Map_Goto(reinit);
1368                         alreadychangedlevel = true;
1369                         return true;
1370                 }
1371         }
1372         if(!reinit && autocvar_lastlevel)
1373         {
1374                 cvar_settemp_restore();
1375                 localcmd("set lastlevel 0\ntogglemenu 1\n");
1376                 alreadychangedlevel = true;
1377                 return true;
1378         }
1379         return false;
1380 }
1381
1382 void GotoNextMap(float reinit)
1383 {
1384         //string nextmap;
1385         //float n, nummaps;
1386         //string s;
1387         if (alreadychangedlevel)
1388                 return;
1389         alreadychangedlevel = true;
1390
1391         string nextMap = GetNextMap();
1392         if(nextMap == "")
1393                 error("Everything is broken - cannot find a next map. Please report this to the developers.");
1394         Map_Goto(reinit);
1395 }
1396
1397
1398 /*
1399 ============
1400 IntermissionThink
1401
1402 When the player presses attack or jump, change to the next level
1403 ============
1404 */
1405 .float autoscreenshot;
1406 void IntermissionThink(entity this)
1407 {
1408         FixIntermissionClient(this);
1409
1410         float server_screenshot = (autocvar_sv_autoscreenshot && CS(this).cvar_cl_autoscreenshot);
1411         float client_screenshot = (CS(this).cvar_cl_autoscreenshot == 2);
1412
1413         if( (server_screenshot || client_screenshot)
1414                 && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) )
1415         {
1416                 this.autoscreenshot = -1;
1417                 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"))); }
1418                 return;
1419         }
1420
1421         if (time < intermission_exittime)
1422                 return;
1423
1424         if(!mapvote_initialized)
1425                 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)))
1426                         return;
1427
1428         MapVote_Start();
1429 }
1430
1431 /*
1432 ===============================================================================
1433
1434 RULES
1435
1436 ===============================================================================
1437 */
1438
1439 void DumpStats(float final)
1440 {
1441         float file;
1442         string s;
1443         float to_console;
1444         float to_eventlog;
1445         float to_file;
1446         float i;
1447
1448         to_console = autocvar_sv_logscores_console;
1449         to_eventlog = autocvar_sv_eventlog;
1450         to_file = autocvar_sv_logscores_file;
1451
1452         if(!final)
1453         {
1454                 to_console = true; // always print printstats replies
1455                 to_eventlog = false; // but never print them to the event log
1456         }
1457
1458         if(to_eventlog)
1459                 if(autocvar_sv_eventlog_console)
1460                         to_console = false; // otherwise we get the output twice
1461
1462         if(final)
1463                 s = ":scores:";
1464         else
1465                 s = ":status:";
1466         s = strcat(s, GetGametype(), "_", GetMapname(), ":", ftos(rint(time)));
1467
1468         if(to_console)
1469                 LOG_INFO(s);
1470         if(to_eventlog)
1471                 GameLogEcho(s);
1472
1473         file = -1;
1474         if(to_file)
1475         {
1476                 file = fopen(autocvar_sv_logscores_filename, FILE_APPEND);
1477                 if(file == -1)
1478                         to_file = false;
1479                 else
1480                         fputs(file, strcat(s, "\n"));
1481         }
1482
1483         s = strcat(":labels:player:", GetPlayerScoreString(NULL, 0));
1484         if(to_console)
1485                 LOG_INFO(s);
1486         if(to_eventlog)
1487                 GameLogEcho(s);
1488         if(to_file)
1489                 fputs(file, strcat(s, "\n"));
1490
1491         FOREACH_CLIENT(IS_REAL_CLIENT(it) || (IS_BOT_CLIENT(it) && autocvar_sv_logscores_bots), {
1492                 s = strcat(":player:see-labels:", GetPlayerScoreString(it, 0), ":");
1493                 s = strcat(s, ftos(rint(time - CS(it).jointime)), ":");
1494                 if(IS_PLAYER(it) || MUTATOR_CALLHOOK(GetPlayerStatus, it))
1495                         s = strcat(s, ftos(it.team), ":");
1496                 else
1497                         s = strcat(s, "spectator:");
1498
1499                 if(to_console)
1500                         LOG_INFO(s, playername(it, false));
1501                 if(to_eventlog)
1502                         GameLogEcho(strcat(s, ftos(it.playerid), ":", playername(it, false)));
1503                 if(to_file)
1504                         fputs(file, strcat(s, playername(it, false), "\n"));
1505         });
1506
1507         if(teamplay)
1508         {
1509                 s = strcat(":labels:teamscores:", GetTeamScoreString(0, 0));
1510                 if(to_console)
1511                         LOG_INFO(s);
1512                 if(to_eventlog)
1513                         GameLogEcho(s);
1514                 if(to_file)
1515                         fputs(file, strcat(s, "\n"));
1516
1517                 for(i = 1; i < 16; ++i)
1518                 {
1519                         s = strcat(":teamscores:see-labels:", GetTeamScoreString(i, 0));
1520                         s = strcat(s, ":", ftos(i));
1521                         if(to_console)
1522                                 LOG_INFO(s);
1523                         if(to_eventlog)
1524                                 GameLogEcho(s);
1525                         if(to_file)
1526                                 fputs(file, strcat(s, "\n"));
1527                 }
1528         }
1529
1530         if(to_console)
1531                 LOG_INFO(":end");
1532         if(to_eventlog)
1533                 GameLogEcho(":end");
1534         if(to_file)
1535         {
1536                 fputs(file, ":end\n");
1537                 fclose(file);
1538         }
1539 }
1540
1541 void FixIntermissionClient(entity e)
1542 {
1543         if(!e.autoscreenshot) // initial call
1544         {
1545                 e.autoscreenshot = time + 0.8;  // used for autoscreenshot
1546                 SetResourceExplicit(e, RES_HEALTH, -2342);
1547                 // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not)
1548                 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1549                 {
1550                     .entity weaponentity = weaponentities[slot];
1551                         if(e.(weaponentity))
1552                         {
1553                                 e.(weaponentity).effects = EF_NODRAW;
1554                                 if (e.(weaponentity).weaponchild)
1555                                         e.(weaponentity).weaponchild.effects = EF_NODRAW;
1556                         }
1557                 }
1558                 if(IS_REAL_CLIENT(e))
1559                 {
1560                         stuffcmd(e, "\nscr_printspeed 1000000\n");
1561                         RandomSelection_Init();
1562                         FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, {
1563                                 RandomSelection_AddString(it, 1, 1);
1564                         });
1565                         if (RandomSelection_chosen_string != "")
1566                         {
1567                                 stuffcmd(e, sprintf("\ncd loop %s\n", RandomSelection_chosen_string));
1568                         }
1569                         msg_entity = e;
1570                         WriteByte(MSG_ONE, SVC_INTERMISSION);
1571                 }
1572         }
1573 }
1574
1575 /*
1576 go to the next level for deathmatch
1577 only called if a time or frag limit has expired
1578 */
1579 void NextLevel()
1580 {
1581         game_stopped = true;
1582         intermission_running = 1; // game over
1583
1584         // enforce a wait time before allowing changelevel
1585         if(player_count > 0)
1586                 intermission_exittime = time + autocvar_sv_mapchange_delay;
1587         else
1588                 intermission_exittime = -1;
1589
1590         /*
1591         WriteByte (MSG_ALL, SVC_CDTRACK);
1592         WriteByte (MSG_ALL, 3);
1593         WriteByte (MSG_ALL, 3);
1594         // done in FixIntermission
1595         */
1596
1597         //pos = FindIntermission ();
1598
1599         VoteReset();
1600
1601         DumpStats(true);
1602
1603         // send statistics
1604         PlayerStats_GameReport(true);
1605         WeaponStats_Shutdown();
1606
1607         Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_Null); // kill all centerprints now
1608
1609         if(autocvar_sv_eventlog)
1610                 GameLogEcho(":gameover");
1611
1612         GameLogClose();
1613
1614         FOREACH_CLIENT(IS_PLAYER(it), {
1615                 FixIntermissionClient(it);
1616                 if(it.winning)
1617                         bprint(playername(it, false), " ^7wins.\n");
1618         });
1619
1620         target_music_kill();
1621
1622         if(autocvar_g_campaign)
1623                 CampaignPreIntermission();
1624
1625         MUTATOR_CALLHOOK(MatchEnd);
1626
1627         localcmd("\nsv_hook_gameend\n");
1628 }
1629
1630
1631 float InitiateSuddenDeath()
1632 {
1633         // Check first whether normal overtimes could be added before initiating suddendeath mode
1634         // - for this timelimit_overtime needs to be >0 of course
1635         // - also check the winning condition calculated in the previous frame and only add normal overtime
1636         //   again, if at the point at which timelimit would be extended again, still no winner was found
1637         if (!autocvar_g_campaign && checkrules_overtimesadded >= 0
1638                 && (checkrules_overtimesadded < autocvar_timelimit_overtimes || autocvar_timelimit_overtimes < 0)
1639                 && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying))
1640         {
1641                 return 1; // need to call InitiateOvertime later
1642         }
1643         else
1644         {
1645                 if(!checkrules_suddendeathend)
1646                 {
1647                         if(autocvar_g_campaign)
1648                                 checkrules_suddendeathend = time; // no suddendeath in campaign
1649                         else
1650                                 checkrules_suddendeathend = time + 60 * autocvar_timelimit_suddendeath;
1651                         if(g_race && !g_race_qualifying)
1652                                 race_StartCompleting();
1653                 }
1654                 return 0;
1655         }
1656 }
1657
1658 void InitiateOvertime() // ONLY call this if InitiateSuddenDeath returned true
1659 {
1660         ++checkrules_overtimesadded;
1661         //add one more overtime by simply extending the timelimit
1662         cvar_set("timelimit", ftos(autocvar_timelimit + autocvar_timelimit_overtime));
1663         Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60);
1664 }
1665
1666 float GetWinningCode(float fraglimitreached, float equality)
1667 {
1668         if(autocvar_g_campaign == 1)
1669         {
1670                 if(fraglimitreached)
1671                         return WINNING_YES;
1672                 else
1673                         return WINNING_NO;
1674         }
1675         else
1676         {
1677                 if(equality)
1678                 {
1679                         if(fraglimitreached)
1680                                 return WINNING_STARTSUDDENDEATHOVERTIME;
1681                         else
1682                                 return WINNING_NEVER;
1683                 }
1684                 else
1685                 {
1686                         if(fraglimitreached)
1687                                 return WINNING_YES;
1688                         else
1689                                 return WINNING_NO;
1690                 }
1691         }
1692 }
1693
1694 // set the .winning flag for exactly those players with a given field value
1695 void SetWinners(.float field, float value)
1696 {
1697         FOREACH_CLIENT(IS_PLAYER(it), { it.winning = (it.(field) == value); });
1698 }
1699
1700 // set the .winning flag for those players with a given field value
1701 void AddWinners(.float field, float value)
1702 {
1703         FOREACH_CLIENT(IS_PLAYER(it), {
1704                 if(it.(field) == value)
1705                         it.winning = 1;
1706         });
1707 }
1708
1709 // clear the .winning flags
1710 void ClearWinners()
1711 {
1712         FOREACH_CLIENT(IS_PLAYER(it), { it.winning = 0; });
1713 }
1714
1715 void ShuffleMaplist()
1716 {
1717         cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
1718 }
1719
1720 int fragsleft_last;
1721 float WinningCondition_Scores(float limit, float leadlimit)
1722 {
1723         // TODO make everything use THIS winning condition (except LMS)
1724         WinningConditionHelper(NULL);
1725
1726         if(teamplay)
1727         {
1728                 for (int i = 1; i < 5; ++i)
1729                 {
1730                         Team_SetTeamScore(Team_GetTeamFromIndex(i),
1731                                 TeamScore_GetCompareValue(Team_IndexToTeam(i)));
1732                 }
1733         }
1734
1735         ClearWinners();
1736         if(WinningConditionHelper_winner)
1737                 WinningConditionHelper_winner.winning = 1;
1738         if(WinningConditionHelper_winnerteam >= 0)
1739                 SetWinners(team, WinningConditionHelper_winnerteam);
1740
1741         if(WinningConditionHelper_lowerisbetter)
1742         {
1743                 WinningConditionHelper_topscore = -WinningConditionHelper_topscore;
1744                 WinningConditionHelper_secondscore = -WinningConditionHelper_secondscore;
1745                 limit = -limit;
1746         }
1747
1748         if(WinningConditionHelper_zeroisworst)
1749                 leadlimit = 0; // not supported in this mode
1750
1751         if(MUTATOR_CALLHOOK(Scores_CountFragsRemaining))
1752         {
1753                 float fragsleft;
1754                 if (checkrules_suddendeathend && time >= checkrules_suddendeathend)
1755                 {
1756                         fragsleft = 1;
1757                 }
1758                 else
1759                 {
1760                         fragsleft = FLOAT_MAX;
1761                         float leadingfragsleft = FLOAT_MAX;
1762                         if (limit)
1763                                 fragsleft = limit - WinningConditionHelper_topscore;
1764                         if (leadlimit)
1765                                 leadingfragsleft = WinningConditionHelper_secondscore + leadlimit - WinningConditionHelper_topscore;
1766
1767                         if (limit && leadlimit && autocvar_leadlimit_and_fraglimit)
1768                                 fragsleft = max(fragsleft, leadingfragsleft);
1769                         else
1770                                 fragsleft = min(fragsleft, leadingfragsleft);
1771                 }
1772
1773                 if (fragsleft_last != fragsleft) // do not announce same remaining frags multiple times
1774                 {
1775                         if (fragsleft == 1)
1776                                 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_1);
1777                         else if (fragsleft == 2)
1778                                 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_2);
1779                         else if (fragsleft == 3)
1780                                 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_REMAINING_FRAG_3);
1781
1782                         fragsleft_last = fragsleft;
1783                 }
1784         }
1785
1786         bool fraglimit_reached = (limit && WinningConditionHelper_topscore >= limit);
1787         bool leadlimit_reached = (leadlimit && WinningConditionHelper_topscore - WinningConditionHelper_secondscore >= leadlimit);
1788
1789         bool limit_reached;
1790         // only respect leadlimit_and_fraglimit when both limits are set or the game will never end
1791         if (limit && leadlimit && autocvar_leadlimit_and_fraglimit)
1792                 limit_reached = (fraglimit_reached && leadlimit_reached);
1793         else
1794                 limit_reached = (fraglimit_reached || leadlimit_reached);
1795
1796         return GetWinningCode(
1797                 WinningConditionHelper_topscore && limit_reached,
1798                 WinningConditionHelper_equality
1799         );
1800 }
1801
1802 float WinningCondition_RanOutOfSpawns()
1803 {
1804         if(have_team_spawns <= 0)
1805                 return WINNING_NO;
1806
1807         if(!autocvar_g_spawn_useallspawns)
1808                 return WINNING_NO;
1809
1810         if(!some_spawn_has_been_used)
1811                 return WINNING_NO;
1812
1813         for (int i = 1; i < 5; ++i)
1814         {
1815                 Team_SetTeamScore(Team_GetTeamFromIndex(i), 0);
1816         }
1817
1818         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
1819         {
1820                 if (Team_IsValidTeam(it.team))
1821                 {
1822                         Team_SetTeamScore(Team_GetTeam(it.team), 1);
1823                 }
1824         });
1825
1826         IL_EACH(g_spawnpoints, true,
1827         {
1828                 if (Team_IsValidTeam(it.team))
1829                 {
1830                         Team_SetTeamScore(Team_GetTeam(it.team), 1);
1831                 }
1832         });
1833
1834         ClearWinners();
1835         float team1_score = Team_GetTeamScore(Team_GetTeamFromIndex(1));
1836         float team2_score = Team_GetTeamScore(Team_GetTeamFromIndex(2));
1837         float team3_score = Team_GetTeamScore(Team_GetTeamFromIndex(3));
1838         float team4_score = Team_GetTeamScore(Team_GetTeamFromIndex(4));
1839         if(team1_score + team2_score + team3_score + team4_score == 0)
1840         {
1841                 checkrules_equality = true;
1842                 return WINNING_YES;
1843         }
1844         else if(team1_score + team2_score + team3_score + team4_score == 1)
1845         {
1846                 float t, i;
1847                 if(team1_score)
1848                         t = 1;
1849                 else if(team2_score)
1850                         t = 2;
1851                 else if(team3_score)
1852                         t = 3;
1853                 else // if(team4_score)
1854                         t = 4;
1855                 entity balance = TeamBalance_CheckAllowedTeams(NULL);
1856                 for(i = 0; i < MAX_TEAMSCORE; ++i)
1857                 {
1858                         for (int j = 1; j <= NUM_TEAMS; ++j)
1859                         {
1860                                 if (t == j)
1861                                 {
1862                                         continue;
1863                                 }
1864                                 if (!TeamBalance_IsTeamAllowed(balance, j))
1865                                 {
1866                                         continue;
1867                                 }
1868                                 TeamScore_AddToTeam(Team_IndexToTeam(j), i, -1000);
1869                         }
1870                 }
1871
1872                 AddWinners(team, t);
1873                 return WINNING_YES;
1874         }
1875         else
1876                 return WINNING_NO;
1877 }
1878
1879 /*
1880 ============
1881 CheckRules_World
1882
1883 Exit deathmatch games upon conditions
1884 ============
1885 */
1886 void CheckRules_World()
1887 {
1888         VoteThink();
1889         MapVote_Think();
1890
1891         SetDefaultAlpha();
1892
1893         if (intermission_running) // someone else quit the game already
1894         {
1895                 if(player_count == 0) // Nobody there? Then let's go to the next map
1896                         MapVote_Start();
1897                         // this will actually check the player count in the next frame
1898                         // again, but this shouldn't hurt
1899                 return;
1900         }
1901
1902         float timelimit = autocvar_timelimit * 60;
1903         float fraglimit = autocvar_fraglimit;
1904         float leadlimit = autocvar_leadlimit;
1905         if (leadlimit < 0) leadlimit = 0;
1906
1907         if(warmup_stage || time <= game_starttime) // NOTE: this is <= to prevent problems in the very tic where the game starts
1908         {
1909                 if(timelimit > 0)
1910                         timelimit = 0; // timelimit is not made for warmup
1911                 if(fraglimit > 0)
1912                         fraglimit = 0; // no fraglimit for now
1913                 leadlimit = 0; // no leadlimit for now
1914         }
1915
1916         if(timelimit > 0)
1917         {
1918                 timelimit += game_starttime;
1919         }
1920         else if (timelimit < 0)
1921         {
1922                 // endmatch
1923                 NextLevel();
1924                 return;
1925         }
1926
1927         float wantovertime;
1928         wantovertime = 0;
1929
1930         if(checkrules_suddendeathend)
1931         {
1932                 if(!checkrules_suddendeathwarning)
1933                 {
1934                         checkrules_suddendeathwarning = true;
1935                         if(g_race && !g_race_qualifying)
1936                                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_RACE_FINISHLAP);
1937                         else
1938                                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_FRAG);
1939                 }
1940         }
1941         else
1942         {
1943                 if (timelimit && time >= timelimit)
1944                 {
1945                         if(g_race && (g_race_qualifying == 2) && timelimit > 0)
1946                         {
1947                                 float totalplayers;
1948                                 float playerswithlaps;
1949                                 float readyplayers;
1950                                 totalplayers = playerswithlaps = readyplayers = 0;
1951                                 FOREACH_CLIENT(IS_PLAYER(it), {
1952                                         ++totalplayers;
1953                                         if(GameRules_scoring_add(it, RACE_FASTEST, 0))
1954                                                 ++playerswithlaps;
1955                                         if(it.ready)
1956                                                 ++readyplayers;
1957                                 });
1958
1959                                 // at least 2 of the players have completed a lap: start the RACE
1960                                 // otherwise, the players should end the qualifying on their own
1961                                 if(readyplayers || playerswithlaps >= 2)
1962                                 {
1963                                         checkrules_suddendeathend = 0;
1964                                         ReadyRestart(); // go to race
1965                                         return;
1966                                 }
1967                                 else
1968                                         wantovertime |= InitiateSuddenDeath();
1969                         }
1970                         else
1971                                 wantovertime |= InitiateSuddenDeath();
1972                 }
1973         }
1974
1975         if (checkrules_suddendeathend && time >= checkrules_suddendeathend)
1976         {
1977                 NextLevel();
1978                 return;
1979         }
1980
1981         int checkrules_status = WinningCondition_RanOutOfSpawns();
1982         if(checkrules_status == WINNING_YES)
1983                 bprint("Hey! Someone ran out of spawns!\n");
1984         else if(MUTATOR_CALLHOOK(CheckRules_World, checkrules_status, timelimit, fraglimit))
1985                 checkrules_status = M_ARGV(0, float);
1986         else
1987                 checkrules_status = WinningCondition_Scores(fraglimit, leadlimit);
1988
1989         if(checkrules_status == WINNING_STARTSUDDENDEATHOVERTIME)
1990         {
1991                 checkrules_status = WINNING_NEVER;
1992                 checkrules_overtimesadded = -1;
1993                 wantovertime |= InitiateSuddenDeath();
1994         }
1995
1996         if(checkrules_status == WINNING_NEVER)
1997                 // equality cases! Nobody wins if the overtime ends in a draw.
1998                 ClearWinners();
1999
2000         if(wantovertime)
2001         {
2002                 if(checkrules_status == WINNING_NEVER)
2003                         InitiateOvertime();
2004                 else
2005                         checkrules_status = WINNING_YES;
2006         }
2007
2008         if(checkrules_suddendeathend)
2009                 if(checkrules_status != WINNING_NEVER || time >= checkrules_suddendeathend)
2010                         checkrules_status = WINNING_YES;
2011
2012         if(checkrules_status == WINNING_YES)
2013         {
2014                 //print("WINNING\n");
2015                 NextLevel();
2016         }
2017 }
2018
2019 string GotoMap(string m)
2020 {
2021         m = GameTypeVote_MapInfo_FixName(m);
2022         if (!m)
2023                 return "The map you suggested is not available on this server.";
2024         if (!autocvar_sv_vote_gametype)
2025         if(!MapInfo_CheckMap(m))
2026                 return "The map you suggested does not support the current game mode.";
2027         cvar_set("nextmap", m);
2028         cvar_set("timelimit", "-1");
2029         if(mapvote_initialized || alreadychangedlevel)
2030         {
2031                 if(DoNextMapOverride(0))
2032                         return "Map switch initiated.";
2033                 else
2034                         return "Hm... no. For some reason I like THIS map more.";
2035         }
2036         else
2037                 return "Map switch will happen after scoreboard.";
2038 }
2039
2040 bool autocvar_sv_gameplayfix_multiplethinksperframe = true;
2041 void RunThink(entity this)
2042 {
2043         // don't let things stay in the past.
2044         // it is possible to start that way by a trigger with a local time.
2045         if(this.nextthink <= 0 || this.nextthink > time + frametime)
2046                 return;
2047
2048         float oldtime = time; // do we need to save this?
2049
2050         for (int iterations = 0; iterations < 128 && !wasfreed(this); iterations++)
2051         {
2052                 time = max(oldtime, this.nextthink);
2053                 this.nextthink = 0;
2054
2055                 if(getthink(this))
2056                         getthink(this)(this);
2057                 // mods often set nextthink to time to cause a think every frame,
2058                 // we don't want to loop in that case, so exit if the new nextthink is
2059                 // <= the time the qc was told, also exit if it is past the end of the
2060                 // frame
2061                 if(this.nextthink <= time || this.nextthink > oldtime + frametime || !autocvar_sv_gameplayfix_multiplethinksperframe)
2062                         break;
2063         }
2064
2065         time = oldtime;
2066 }
2067
2068 bool autocvar_sv_freezenonclients;
2069 bool autocvar_sv_gameplayfix_delayprojectiles = false;
2070 void Physics_Frame()
2071 {
2072         if(autocvar_sv_freezenonclients)
2073                 return;
2074
2075         IL_EACH(g_moveables, true,
2076         {
2077                 if(IS_CLIENT(it) || it.classname == "" || it.move_movetype == MOVETYPE_PHYSICS)
2078                         continue;
2079
2080                 //set_movetype(it, it.move_movetype);
2081                 // inline the set_movetype function, since this is called a lot
2082                 it.movetype = (it.move_qcphysics) ? MOVETYPE_QCENTITY : it.move_movetype;
2083
2084                 if(it.move_qcphysics && it.move_movetype != MOVETYPE_NONE)
2085                         Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false);
2086
2087                 if(it.movetype >= MOVETYPE_USER_FIRST && it.movetype <= MOVETYPE_USER_LAST) // these cases have no think handling
2088                 {
2089                         if(it.move_movetype == MOVETYPE_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH)
2090                                 continue; // these movetypes have no regular think function
2091                         // handle thinking here
2092                         if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + frametime)
2093                                 RunThink(it);
2094                 }
2095         });
2096
2097         if(autocvar_sv_gameplayfix_delayprojectiles >= 0)
2098                 return;
2099
2100         IL_EACH(g_moveables, it.move_qcphysics,
2101         {
2102                 if(IS_CLIENT(it) || it.classname == "" || it.move_movetype == MOVETYPE_NONE)
2103                         continue;
2104                 Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false);
2105         });
2106 }
2107
2108 void systems_update();
2109 void EndFrame()
2110 {
2111         anticheat_endframe();
2112
2113         Physics_Frame();
2114
2115         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
2116                 entity e = IS_SPEC(it) ? it.enemy : it;
2117                 if (e.typehitsound) {
2118                         STAT(TYPEHIT_TIME, it) = time;
2119                 } else if (e.killsound) {
2120                         STAT(KILL_TIME, it) = time;
2121                 } else if (e.damage_dealt) {
2122                         STAT(HIT_TIME, it) = time;
2123                         STAT(DAMAGE_DEALT_TOTAL, it) += ceil(e.damage_dealt);
2124                 }
2125         });
2126         // add 1 frametime because after this, engine SV_Physics
2127         // increases time by a frametime and then networks the frame
2128         // add another frametime because client shows everything with
2129         // 1 frame of lag (cl_nolerp 0). The last +1 however should not be
2130         // needed!
2131         float altime = time + frametime * (1 + autocvar_g_antilag_nudge);
2132         FOREACH_CLIENT(true, {
2133                 it.typehitsound = false;
2134                 it.damage_dealt = 0;
2135                 it.killsound = false;
2136                 antilag_record(it, CS(it), altime);
2137         });
2138         IL_EACH(g_monsters, true,
2139         {
2140                 antilag_record(it, it, altime);
2141         });
2142         IL_EACH(g_projectiles, it.classname == "nade",
2143         {
2144                 antilag_record(it, it, altime);
2145         });
2146         systems_update();
2147         IL_ENDFRAME();
2148 }
2149
2150
2151 /*
2152  * RedirectionThink:
2153  * returns true if redirecting
2154  */
2155 float redirection_timeout;
2156 float redirection_nextthink;
2157 float RedirectionThink()
2158 {
2159         float clients_found;
2160
2161         if(redirection_target == "")
2162                 return false;
2163
2164         if(!redirection_timeout)
2165         {
2166                 cvar_set("sv_public", "-2");
2167                 redirection_timeout = time + 0.6; // this will only try twice... should be able to keep more clients
2168                 if(redirection_target == "self")
2169                         bprint("^3SERVER NOTICE:^7 restarting the server\n");
2170                 else
2171                         bprint("^3SERVER NOTICE:^7 redirecting everyone to ", redirection_target, "\n");
2172         }
2173
2174         if(time < redirection_nextthink)
2175                 return true;
2176
2177         redirection_nextthink = time + 1;
2178
2179         clients_found = 0;
2180         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
2181                 // TODO add timer
2182                 LOG_INFO("Redirecting: sending connect command to ", it.netname);
2183                 if(redirection_target == "self")
2184                         stuffcmd(it, "\ndisconnect; defer ", ftos(autocvar_quit_and_redirect_timer), " reconnect\n");
2185                 else
2186                         stuffcmd(it, strcat("\ndisconnect; defer ", ftos(autocvar_quit_and_redirect_timer), " \"connect ", redirection_target, "\"\n"));
2187                 ++clients_found;
2188         });
2189
2190         LOG_INFO("Redirecting: ", ftos(clients_found), " clients left.");
2191
2192         if(time > redirection_timeout || clients_found == 0)
2193                 localcmd("\nwait; wait; wait; quit\n");
2194
2195         return true;
2196 }
2197
2198 void RestoreGame()
2199 {
2200         // Loaded from a save game
2201         // some things then break, so let's work around them...
2202
2203         // Progs DB (capture records)
2204         ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid));
2205
2206         // Mapinfo
2207         MapInfo_Shutdown();
2208         MapInfo_Enumerate();
2209         MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1);
2210         WeaponStats_Init();
2211
2212         TargetMusic_RestoreGame();
2213 }
2214
2215 void Shutdown()
2216 {
2217         game_stopped = 2;
2218
2219         if(world_initialized > 0)
2220         {
2221                 world_initialized = 0;
2222
2223                 // if a timeout is active, reset the slowmo value to normal
2224                 if(timeout_status == TIMEOUT_ACTIVE)
2225                         cvar_set("slowmo", ftos(orig_slowmo));
2226
2227                 LOG_TRACE("Saving persistent data...");
2228                 Ban_SaveBans();
2229
2230                 // playerstats with unfinished match
2231                 PlayerStats_GameReport(false);
2232
2233                 if(!cheatcount_total)
2234                 {
2235                         if(autocvar_sv_db_saveasdump)
2236                                 db_dump(ServerProgsDB, strcat("server.db", autocvar_sessionid));
2237                         else
2238                                 db_save(ServerProgsDB, strcat("server.db", autocvar_sessionid));
2239                 }
2240                 if(autocvar_developer > 0)
2241                 {
2242                         if(autocvar_sv_db_saveasdump)
2243                                 db_dump(TemporaryDB, "server-temp.db");
2244                         else
2245                                 db_save(TemporaryDB, "server-temp.db");
2246                 }
2247                 CheatShutdown(); // must be after cheatcount check
2248                 db_close(ServerProgsDB);
2249                 db_close(TemporaryDB);
2250                 LOG_TRACE("Saving persistent data... done!");
2251                 // tell the bot system the game is ending now
2252                 bot_endgame();
2253
2254                 WeaponStats_Shutdown();
2255                 MapInfo_Shutdown();
2256         }
2257         else if(world_initialized == 0)
2258         {
2259                 LOG_INFO("NOTE: crashed before even initializing the world, not saving persistent data");
2260         }
2261         else
2262         {
2263                 __init_dedicated_server_shutdown();
2264         }
2265 }