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