X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=blobdiff_plain;f=qcsrc%2Fserver%2Fg_world.qc;h=5fc7b9f99042cc71b31e82f02c84c5c7a7b80fb8;hp=d570cad530e9c18a8d7c99914e95283e89108228;hb=6551ba10c2a3a64b7232f0e5fb599af4e9b0b40a;hpb=18e2cd311a581f77ba8eb9c5421dd219ff5d760d diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index d570cad530..5fc7b9f990 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -13,12 +13,11 @@ #include "g_hook.qh" #include "ipban.qh" #include "mapvoting.qh" -#include "mutators/mutators_include.qh" +#include "mutators/all.qh" #include "race.qh" #include "scores.qh" #include "teamplay.qh" #include "weapons/weaponstats.qh" -#include "../common/buffs/all.qh" #include "../common/constants.qh" #include "../common/deathtypes/all.qh" #include "../common/mapinfo.qh" @@ -26,7 +25,7 @@ #include "../common/monsters/sv_monsters.qh" #include "../common/vehicles/all.qh" #include "../common/notifications.qh" -#include "../common/physics.qh" +#include "../common/physics/player.qh" #include "../common/playerstats.qh" #include "../common/stats.qh" #include "../common/teams.qh" @@ -54,8 +53,7 @@ void PingPLReport_Think() e = edict_num(self.cnt + 1); if(IS_REAL_CLIENT(e)) { - WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); - WriteByte(MSG_BROADCAST, TE_CSQC_PINGPLREPORT); + WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT); WriteByte(MSG_BROADCAST, self.cnt); WriteShort(MSG_BROADCAST, max(1, e.ping)); WriteByte(MSG_BROADCAST, ceil(e.ping_packetloss * 255)); @@ -72,8 +70,7 @@ void PingPLReport_Think() } else { - WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); - WriteByte(MSG_BROADCAST, TE_CSQC_PINGPLREPORT); + WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT); WriteByte(MSG_BROADCAST, self.cnt); WriteShort(MSG_BROADCAST, 0); WriteByte(MSG_BROADCAST, 0); @@ -83,8 +80,8 @@ void PingPLReport_Think() } void PingPLReport_Spawn() { - pingplreport = spawn(); - pingplreport.classname = "pingplreport"; + pingplreport = new(pingplreport); + make_pure(pingplreport); pingplreport.think = PingPLReport_Think; pingplreport.nextthink = time; } @@ -98,17 +95,7 @@ void ShuffleMaplist(); void SetDefaultAlpha() { - if(autocvar_g_running_guns) - { - default_player_alpha = -1; - default_weapon_alpha = +1; - } - else if(g_cloaked) - { - default_player_alpha = autocvar_g_balance_cloaked_alpha; - default_weapon_alpha = default_player_alpha; - } - else + if (!MUTATOR_CALLHOOK(SetDefaultAlpha)) { default_player_alpha = autocvar_g_player_alpha; if(default_player_alpha == 0) @@ -493,7 +480,7 @@ void detect_maptype() o.y += random() * (world.maxs.y - world.mins.y); o.z += random() * (world.maxs.z - world.mins.z); - tracebox(o, PL_MIN, PL_MAX, o - '0 0 32768', MOVE_WORLDONLY, world); + tracebox(o, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), o - '0 0 32768', MOVE_WORLDONLY, world); if(trace_fraction == 1) continue; @@ -515,7 +502,7 @@ void detect_maptype() entity randomseed; bool RandomSeed_Send(entity this, entity to, int sf) { - WriteByte(MSG_ENTITY, ENT_CLIENT_RANDOMSEED); + WriteHeader(MSG_ENTITY, ENT_CLIENT_RANDOMSEED); WriteShort(MSG_ENTITY, self.cnt); return true; } @@ -528,7 +515,8 @@ void RandomSeed_Think() } void RandomSeed_Spawn() {SELFPARAM(); - randomseed = spawn(); + randomseed = new(randomseed); + make_pure(randomseed); randomseed.think = RandomSeed_Think; Net_LinkEntity(randomseed, false, 0, RandomSeed_Send); @@ -546,33 +534,100 @@ spawnfunc(__init_dedicated_server) remove = remove_unsafely; - entity e; - e = spawn(); + entity e = spawn(); e.think = GotoFirstMap; e.nextthink = time; // this is usually 1 at this point - e = spawn(); - e.classname = "info_player_deathmatch"; // safeguard against player joining + e = new(info_player_deathmatch); // safeguard against player joining self.classname = "worldspawn"; // safeguard against various stuff ;) // needs to be done so early because of the constants they create static_init(); + static_init_late(); + static_init_precache(); MapInfo_Enumerate(); MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0); } +void __init_dedicated_server_shutdown() { + MapInfo_Shutdown(); +} + void Map_MarkAsRecent(string m); float world_already_spawned; void Nagger_Init(); void ClientInit_Spawn(); void WeaponStats_Init(); void WeaponStats_Shutdown(); -void Physics_AddStats(); spawnfunc(worldspawn) { - float fd, l, j, n; + server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated"))); + + { + bool wantrestart = false; + + if (!server_is_dedicated) + { + // force unloading of server pk3 files when starting a listen server + // localcmd("\nfs_rescan\n"); // FIXME: does more harm than good, has unintended side effects. What we really want is to unload temporary pk3s only + // restore csqc_progname too + string expect = "csprogs.dat"; + wantrestart = cvar_string_normal("csqc_progname") != expect; + cvar_set_normal("csqc_progname", expect); + } + else + { + // Try to use versioned csprogs from pk3 + // Only ever use versioned csprogs.dat files on dedicated servers; + // we need to reset csqc_progname on clients ourselves, and it's easier if the client's release name is constant + string pk3csprogs = "csprogs-" WATERMARK ".dat"; + // This always works; fall back to it if a versioned csprogs.dat is suddenly missing + string select = "csprogs.dat"; + if (fexists(pk3csprogs)) select = pk3csprogs; + if (cvar_string_normal("csqc_progname") != select) + { + cvar_set_normal("csqc_progname", select); + wantrestart = true; + } + // Check for updates on startup + // We do it this way for atomicity so that connecting clients still match the server progs and don't disconnect + int sentinel = fopen("progs.txt", FILE_READ); + if (sentinel >= 0) + { + string switchversion = fgets(sentinel); + fclose(sentinel); + if (switchversion != "" && switchversion != WATERMARK) + { + LOG_INFOF("Switching progs: " WATERMARK " -> %s\n", switchversion); + // if it doesn't exist, assume either: + // a) the current program was overwritten + // b) this is a client only update + string newprogs = sprintf("progs-%s.dat", switchversion); + if (fexists(newprogs)) + { + cvar_set_normal("sv_progs", newprogs); + wantrestart = true; + } + string newcsprogs = sprintf("csprogs-%s.dat", switchversion); + if (fexists(newcsprogs)) + { + cvar_set_normal("csqc_progname", newcsprogs); + wantrestart = true; + } + } + } + } + if (wantrestart) + { + LOG_INFOF("Restart requested\n"); + changelevel(mapname); + // let initialization continue, shutdown depends on it + } + } + + float fd, l; string s; cvar = cvar_normal; @@ -589,17 +644,12 @@ spawnfunc(worldspawn) compressShortVector_init(); - entity head; - head = nextent(world); maxclients = 0; - while(head) + for (entity head = nextent(world); head; head = nextent(head)) { ++maxclients; - head = nextent(head); } - server_is_dedicated = (stof(cvar_defstring("is_dedicated")) ? true : false); - // needs to be done so early because of the constants they create static_init(); @@ -656,6 +706,8 @@ spawnfunc(worldspawn) PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode InitGameplayMode(); + static_init_late(); + static_init_precache(); readlevelcvars(); GrappleHookInit(); @@ -764,77 +816,8 @@ spawnfunc(worldspawn) WeaponStats_Init(); - WepSet_AddStat(); - WepSet_AddStat_InMap(); - addstat(STAT_SWITCHWEAPON, AS_INT, switchweapon); - addstat(STAT_SWITCHINGWEAPON, AS_INT, switchingweapon); - addstat(STAT_GAMESTARTTIME, AS_FLOAT, stat_game_starttime); - addstat(STAT_ROUNDSTARTTIME, AS_FLOAT, stat_round_starttime); - addstat(STAT_ALLOW_OLDVORTEXBEAM, AS_INT, stat_allow_oldvortexbeam); Nagger_Init(); - addstat(STAT_STRENGTH_FINISHED, AS_FLOAT, strength_finished); - addstat(STAT_INVINCIBLE_FINISHED, AS_FLOAT, invincible_finished); - addstat(STAT_SUPERWEAPONS_FINISHED, AS_FLOAT, superweapons_finished); - addstat(STAT_PRESSED_KEYS, AS_FLOAT, pressedkeys); - addstat(STAT_FUEL, AS_INT, ammo_fuel); - addstat(STAT_PLASMA, AS_INT, ammo_plasma); - addstat(STAT_SHOTORG, AS_INT, stat_shotorg); - addstat(STAT_LEADLIMIT, AS_FLOAT, stat_leadlimit); - addstat(STAT_WEAPON_CLIPLOAD, AS_INT, clip_load); - addstat(STAT_WEAPON_CLIPSIZE, AS_INT, clip_size); - addstat(STAT_LAST_PICKUP, AS_FLOAT, last_pickup); - addstat(STAT_HIT_TIME, AS_FLOAT, hit_time); - addstat(STAT_DAMAGE_DEALT_TOTAL, AS_INT, damage_dealt_total); - addstat(STAT_TYPEHIT_TIME, AS_FLOAT, typehit_time); - addstat(STAT_LAYED_MINES, AS_INT, minelayer_mines); - - addstat(STAT_VORTEX_CHARGE, AS_FLOAT, vortex_charge); - addstat(STAT_VORTEX_CHARGEPOOL, AS_FLOAT, vortex_chargepool_ammo); - - addstat(STAT_HAGAR_LOAD, AS_INT, hagar_load); - - addstat(STAT_ARC_HEAT, AS_FLOAT, arc_heat_percent); - - // freeze attacks - addstat(STAT_FROZEN, AS_INT, frozen); - addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, revive_progress); - - // physics - Physics_AddStats(); - - // new properties - addstat(STAT_MOVEVARS_JUMPVELOCITY, AS_FLOAT, stat_sv_jumpvelocity); - addstat(STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, AS_FLOAT, stat_sv_airaccel_qw_stretchfactor); - addstat(STAT_MOVEVARS_MAXAIRSTRAFESPEED, AS_FLOAT, stat_sv_maxairstrafespeed); - addstat(STAT_MOVEVARS_MAXAIRSPEED, AS_FLOAT, stat_sv_maxairspeed); - addstat(STAT_MOVEVARS_AIRSTRAFEACCELERATE, AS_FLOAT, stat_sv_airstrafeaccelerate); - addstat(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL, AS_FLOAT, stat_sv_warsowbunny_turnaccel); - addstat(STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, AS_FLOAT, stat_sv_airaccel_sideways_friction); - addstat(STAT_MOVEVARS_AIRCONTROL, AS_FLOAT, stat_sv_aircontrol); - addstat(STAT_MOVEVARS_AIRCONTROL_POWER, AS_FLOAT, stat_sv_aircontrol_power); - addstat(STAT_MOVEVARS_AIRCONTROL_PENALTY, AS_FLOAT, stat_sv_aircontrol_penalty); - addstat(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, AS_FLOAT, stat_sv_warsowbunny_airforwardaccel); - addstat(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED, AS_FLOAT, stat_sv_warsowbunny_topspeed); - addstat(STAT_MOVEVARS_WARSOWBUNNY_ACCEL, AS_FLOAT, stat_sv_warsowbunny_accel); - addstat(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, AS_FLOAT, stat_sv_warsowbunny_backtosideratio); - addstat(STAT_MOVEVARS_FRICTION, AS_FLOAT, stat_sv_friction); - addstat(STAT_MOVEVARS_ACCELERATE, AS_FLOAT, stat_sv_accelerate); - addstat(STAT_MOVEVARS_STOPSPEED, AS_FLOAT, stat_sv_stopspeed); - addstat(STAT_MOVEVARS_AIRACCELERATE, AS_FLOAT, stat_sv_airaccelerate); - addstat(STAT_MOVEVARS_AIRSTOPACCELERATE, AS_FLOAT, stat_sv_airstopaccelerate); - - // secrets - addstat(STAT_SECRETS_TOTAL, AS_FLOAT, stat_secrets_total); - addstat(STAT_SECRETS_FOUND, AS_FLOAT, stat_secrets_found); - - // monsters - addstat(STAT_MONSTERS_TOTAL, AS_FLOAT, stat_monsters_total); - addstat(STAT_MONSTERS_KILLED, AS_FLOAT, stat_monsters_killed); - - // misc - addstat(STAT_RESPAWN_TIME, AS_FLOAT, stat_respawn_time); - next_pingtime = time + 5; detect_maptype(); @@ -862,31 +845,34 @@ spawnfunc(worldspawn) localcmd("\n_sv_hook_gamestart ", GetGametype(), "\n"); // fill sv_curl_serverpackages from .serverpackage files - if(autocvar_sv_curl_serverpackages_auto) + if (autocvar_sv_curl_serverpackages_auto) { - s = ""; - n = tokenize_console(cvar_string("sv_curl_serverpackages")); - for(int i = 0; i < n; ++i) - if(substring(argv(i), -18, -1) != "-serverpackage.txt") - if(substring(argv(i), -14, -1) != ".serverpackage") // OLD legacy - s = strcat(s, " ", argv(i)); - fd = search_begin("*-serverpackage.txt", true, false); - if(fd >= 0) + s = "csprogs-" WATERMARK ".txt"; + // remove automatically managed files from the list to prevent duplicates + for (int i = 0, n = tokenize_console(cvar_string("sv_curl_serverpackages")); i < n; ++i) { - j = search_getsize(fd); - for(int i = 0; i < j; ++i) - s = strcat(s, " ", search_getfilename(fd, i)); - search_end(fd); + string pkg = argv(i); + if (startsWith(pkg, "csprogs-")) continue; + if (endsWith(pkg, "-serverpackage.txt")) continue; + if (endsWith(pkg, ".serverpackage")) continue; // OLD legacy + s = cons(s, pkg); } - fd = search_begin("*.serverpackage", true, false); - if(fd >= 0) - { - j = search_getsize(fd); - for(int i = 0; i < j; ++i) - s = strcat(s, " ", search_getfilename(fd, i)); - search_end(fd); - } - cvar_set("sv_curl_serverpackages", substring(s, 1, -1)); + // add automatically managed files to the list + #define X(match) MACRO_BEGIN { \ + fd = search_begin(match, true, false); \ + if (fd >= 0) \ + { \ + for (int i = 0, j = search_getsize(fd); i < j; ++i) \ + { \ + s = cons(s, search_getfilename(fd, i)); \ + } \ + search_end(fd); \ + } \ + } MACRO_END + X("*-serverpackage.txt"); + X("*.serverpackage"); + #undef X + cvar_set("sv_curl_serverpackages", s); } // MOD AUTHORS: change this, and possibly remove a few of the blocks below to ignore certain changes @@ -1418,25 +1404,21 @@ void DumpStats(float final) if(to_file) fputs(file, strcat(s, "\n")); - FOR_EACH_CLIENT(other) - { - if ((IS_REAL_CLIENT(other)) || (IS_BOT_CLIENT(other) && autocvar_sv_logscores_bots)) - { - s = strcat(":player:see-labels:", GetPlayerScoreString(other, 0), ":"); - s = strcat(s, ftos(rint(time - other.jointime)), ":"); - if(IS_PLAYER(other) || other.caplayer == 1 || g_lms) - s = strcat(s, ftos(other.team), ":"); - else - s = strcat(s, "spectator:"); + FOREACH_CLIENT(IS_REAL_CLIENT(it) || (IS_BOT_CLIENT(it) && autocvar_sv_logscores_bots), LAMBDA( + s = strcat(":player:see-labels:", GetPlayerScoreString(it, 0), ":"); + s = strcat(s, ftos(rint(time - it.jointime)), ":"); + if(IS_PLAYER(it) || MUTATOR_CALLHOOK(GetPlayerStatus, it, s)) + s = strcat(s, ftos(it.team), ":"); + else + s = strcat(s, "spectator:"); - if(to_console) - LOG_INFO(s, other.netname, "\n"); - if(to_eventlog) - GameLogEcho(strcat(s, ftos(other.playerid), ":", other.netname)); - if(to_file) - fputs(file, strcat(s, other.netname, "\n")); - } - } + if(to_console) + LOG_INFO(s, it.netname, "\n"); + if(to_eventlog) + GameLogEcho(strcat(s, ftos(it.playerid), ":", it.netname)); + if(to_file) + fputs(file, strcat(s, it.netname, "\n")); + )); if(teamplay) { @@ -1474,7 +1456,6 @@ void DumpStats(float final) void FixIntermissionClient(entity e) { - string s; if(!e.autoscreenshot) // initial call { e.autoscreenshot = time + 0.8; // used for autoscreenshot @@ -1483,18 +1464,27 @@ void FixIntermissionClient(entity e) e.solid = SOLID_NOT; e.movetype = MOVETYPE_NONE; e.takedamage = DAMAGE_NO; - if(e.weaponentity) + for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) { - e.weaponentity.effects = EF_NODRAW; - if (e.weaponentity.weaponentity) - e.weaponentity.weaponentity.effects = EF_NODRAW; + .entity weaponentity = weaponentities[slot]; + if(e.(weaponentity)) + { + e.(weaponentity).effects = EF_NODRAW; + if (e.(weaponentity).weaponchild) + e.(weaponentity).weaponchild.effects = EF_NODRAW; + } } if(IS_REAL_CLIENT(e)) { stuffcmd(e, "\nscr_printspeed 1000000\n"); - s = autocvar_sv_intermission_cdtrack; - if(s != "") - stuffcmd(e, strcat("\ncd loop ", s, "\n")); + RandomSelection_Init(); + FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, LAMBDA( + RandomSelection_Add(NULL, 0, it, 1, 1); + )); + if (RandomSelection_chosen_string != "") + { + stuffcmd(e, sprintf("\ncd loop %s\n", RandomSelection_chosen_string)); + } msg_entity = e; WriteByte(MSG_ONE, SVC_INTERMISSION); } @@ -1541,11 +1531,11 @@ void NextLevel() GameLogClose(); - FOR_EACH_PLAYER(other) { - FixIntermissionClient(other); - if(other.winning) - bprint(other.netname, " ^7wins.\n"); - } + FOREACH_CLIENT(IS_PLAYER(it), LAMBDA( + FixIntermissionClient(it); + if(it.winning) + bprint(it.netname, " ^7wins.\n"); + )); entity oldself = self; target_music_kill(); @@ -1571,7 +1561,7 @@ void CheckRules_Player() if (gameover) // someone else quit the game already return; - if(self.deadflag == DEAD_NO) + if(!IS_DEAD(self)) self.play_time += frametime; // fixme: don't check players; instead check spawnfunc_dom_team and spawnfunc_ctf_team entities @@ -1640,150 +1630,22 @@ float GetWinningCode(float fraglimitreached, float equality) // set the .winning flag for exactly those players with a given field value void SetWinners(.float field, float value) { - entity head; - FOR_EACH_PLAYER(head) - head.winning = (head.(field) == value); + FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.winning = (it.(field) == value))); } // set the .winning flag for those players with a given field value void AddWinners(.float field, float value) { - entity head; - FOR_EACH_PLAYER(head) - if (head.(field) == value) - head.winning = 1; + FOREACH_CLIENT(IS_PLAYER(it), LAMBDA( + if(it.(field) == value) + it.winning = 1; + )); } // clear the .winning flags -void ClearWinners(void) -{ - entity head; - FOR_EACH_PLAYER(head) - head.winning = 0; -} - -// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives) -// they win. Otherwise the defending team wins once the timelimit passes. -void assault_new_round(); -float WinningCondition_Assault() -{SELFPARAM(); - float status; - - WinningConditionHelper(); // set worldstatus - - status = WINNING_NO; - // as the timelimit has not yet passed just assume the defending team will win - if(assault_attacker_team == NUM_TEAM_1) - { - SetWinners(team, NUM_TEAM_2); - } - else - { - SetWinners(team, NUM_TEAM_1); - } - - entity ent; - ent = find(world, classname, "target_assault_roundend"); - if(ent) - { - if(ent.winning) // round end has been triggered by attacking team - { - bprint("ASSAULT: round completed...\n"); - SetWinners(team, assault_attacker_team); - - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0)); - - if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round - { - status = WINNING_YES; - } - else - { - WITH(entity, self, ent, assault_new_round()); - } - } - } - - return status; -} - -// LMS winning condition: game terminates if and only if there's at most one -// one player who's living lives. Top two scores being equal cancels the time -// limit. -float WinningCondition_LMS() +void ClearWinners() { - entity head, head2; - float have_player; - float have_players; - float l; - - have_player = false; - have_players = false; - l = LMS_NewPlayerLives(); - - head = find(world, classname, "player"); - if(head) - have_player = true; - head2 = find(head, classname, "player"); - if(head2) - have_players = true; - - if(have_player) - { - // we have at least one player - if(have_players) - { - // two or more active players - continue with the game - } - else - { - // exactly one player? - - ClearWinners(); - SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out - - if(l) - { - // game still running (that is, nobody got removed from the game by a frag yet)? then continue - return WINNING_NO; - } - else - { - // a winner! - // and assign him his first place - PlayerScore_Add(head, SP_LMS_RANK, 1); - return WINNING_YES; - } - } - } - else - { - // nobody is playing at all... - if(l) - { - // wait for players... - } - else - { - // SNAFU (maybe a draw game?) - ClearWinners(); - LOG_TRACE("No players, ending game.\n"); - return WINNING_YES; - } - } - - // When we get here, we have at least two players who are actually LIVING, - // now check if the top two players have equal score. - WinningConditionHelper(); - - ClearWinners(); - if(WinningConditionHelper_winner) - WinningConditionHelper_winner.winning = true; - if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore) - return WINNING_NEVER; - - // Top two have different scores? Way to go for our beloved TIMELIMIT! - return WINNING_NO; + FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.winning = 0)); } void ShuffleMaplist() @@ -1863,50 +1725,8 @@ float WinningCondition_Scores(float limit, float leadlimit) ); } -float WinningCondition_Race(float fraglimit) -{ - float wc; - entity p; - float n, c; - - n = 0; - c = 0; - FOR_EACH_PLAYER(p) - { - ++n; - if(p.race_completed) - ++c; - } - if(n && (n == c)) - return WINNING_YES; - wc = WinningCondition_Scores(fraglimit, 0); - - // ALWAYS initiate overtime, unless EVERYONE has finished the race! - if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME) - // do NOT support equality when the laps are all raced! - return WINNING_STARTSUDDENDEATHOVERTIME; - else - return WINNING_NEVER; -} - -float WinningCondition_QualifyingThenRace(float limit) -{ - float wc; - wc = WinningCondition_Scores(limit, 0); - - // NEVER initiate overtime - if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME) - { - return WINNING_YES; - } - - return wc; -} - float WinningCondition_RanOutOfSpawns() { - entity head; - if(have_team_spawns <= 0) return WINNING_NO; @@ -1918,29 +1738,25 @@ float WinningCondition_RanOutOfSpawns() team1_score = team2_score = team3_score = team4_score = 0; - FOR_EACH_PLAYER(head) if(head.deadflag == DEAD_NO) - { - if(head.team == NUM_TEAM_1) - team1_score = 1; - else if(head.team == NUM_TEAM_2) - team2_score = 1; - else if(head.team == NUM_TEAM_3) - team3_score = 1; - else if(head.team == NUM_TEAM_4) - team4_score = 1; - } + FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA( + switch(it.team) + { + case NUM_TEAM_1: team1_score = 1; break; + case NUM_TEAM_2: team2_score = 1; break; + case NUM_TEAM_3: team3_score = 1; break; + case NUM_TEAM_4: team4_score = 1; break; + } + )); - for(head = world; (head = find(head, classname, "info_player_deathmatch")) != world; ) - { - if(head.team == NUM_TEAM_1) - team1_score = 1; - else if(head.team == NUM_TEAM_2) - team2_score = 1; - else if(head.team == NUM_TEAM_3) - team3_score = 1; - else if(head.team == NUM_TEAM_4) - team4_score = 1; - } + FOREACH_ENTITY_CLASS("info_player_deathmatch", true, LAMBDA( + switch(it.team) + { + case NUM_TEAM_1: team1_score = 1; break; + case NUM_TEAM_2: team2_score = 1; break; + case NUM_TEAM_3: team3_score = 1; break; + case NUM_TEAM_4: team4_score = 1; break; + } + )); ClearWinners(); if(team1_score + team2_score + team3_score + team4_score == 0) @@ -2054,16 +1870,14 @@ void CheckRules_World() float totalplayers; float playerswithlaps; float readyplayers; - entity head; totalplayers = playerswithlaps = readyplayers = 0; - FOR_EACH_PLAYER(head) - { + FOREACH_CLIENT(IS_PLAYER(it), LAMBDA( ++totalplayers; - if(PlayerScore_Add(head, SP_RACE_FASTEST, 0)) + if(PlayerScore_Add(it, SP_RACE_FASTEST, 0)) ++playerswithlaps; - if(head.ready) + if(it.ready) ++readyplayers; - } + )); // at least 2 of the players have completed a lap: start the RACE // otherwise, the players should end the qualifying on their own @@ -2087,35 +1901,13 @@ void CheckRules_World() return; } - float checkrules_status; - checkrules_status = WinningCondition_RanOutOfSpawns(); + int checkrules_status = WinningCondition_RanOutOfSpawns(); if(checkrules_status == WINNING_YES) - { bprint("Hey! Someone ran out of spawns!\n"); - } - else if(g_race && !g_race_qualifying && timelimit >= 0) - { - checkrules_status = WinningCondition_Race(fraglimit); - //print("WC_RACE yields ", ftos(checkrules_status), "\n"); - } - else if(g_race && g_race_qualifying == 2 && timelimit >= 0) - { - checkrules_status = WinningCondition_QualifyingThenRace(fraglimit); - //print("WC_QUALIFYING_THEN_RACE yields ", ftos(checkrules_status), "\n"); - } - else if(g_assault) - { - checkrules_status = WinningCondition_Assault(); // TODO remove this? - } - else if(g_lms) - { - checkrules_status = WinningCondition_LMS(); - } + else if(MUTATOR_CALLHOOK(CheckRules_World, checkrules_status, timelimit, fraglimit)) + checkrules_status = ret_float; else - { checkrules_status = WinningCondition_Scores(fraglimit, leadlimit); - //print("WC_SCORES yields ", ftos(checkrules_status), "\n"); - } if(checkrules_status == WINNING_STARTSUDDENDEATHOVERTIME) { @@ -2174,36 +1966,36 @@ void EndFrame() anticheat_endframe(); float altime; - entity e_; - FOR_EACH_REALCLIENT(e_) - { - entity e = IS_SPEC(e_) ? e_.enemy : e_; + FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA( + entity e = IS_SPEC(it) ? it.enemy : it; if(e.typehitsound) - e_.typehit_time = time; + it.typehit_time = time; else if(e.damage_dealt) { - e_.hit_time = time; - e_.damage_dealt_total += ceil(e.damage_dealt); + it.hit_time = time; + it.damage_dealt_total += ceil(e.damage_dealt); } - } + )); altime = time + frametime * (1 + autocvar_g_antilag_nudge); // add 1 frametime because after this, engine SV_Physics // increases time by a frametime and then networks the frame // add another frametime because client shows everything with // 1 frame of lag (cl_nolerp 0). The last +1 however should not be // needed! - FOR_EACH_CLIENT(e_) - { - e_.typehitsound = false; - e_.damage_dealt = 0; - setself(e_); - antilag_record(e_, altime); - } - FOR_EACH_MONSTER(e_) - { - setself(e_); - antilag_record(e_, altime); - } + FOREACH_CLIENT(true, LAMBDA( + it.typehitsound = false; + it.damage_dealt = 0; + setself(it); + antilag_record(it, altime); + )); + FOREACH_ENTITY_FLAGS(flags, FL_MONSTER, LAMBDA( + setself(it); + antilag_record(it, altime); + )); + FOREACH_CLIENT(PS(it), LAMBDA( + PlayerState s = PS(it); + s.ps_push(s, it); + )); } @@ -2236,10 +2028,8 @@ float RedirectionThink() redirection_nextthink = time + 1; clients_found = 0; - entity e; - FOR_EACH_REALCLIENT(e) - { - setself(e); + FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA( + setself(it); // TODO add timer LOG_INFO("Redirecting: sending connect command to ", self.netname, "\n"); if(redirection_target == "self") @@ -2247,7 +2037,7 @@ float RedirectionThink() else stuffcmd(self, strcat("\ndisconnect; defer ", ftos(autocvar_quit_and_redirect_timer), " \"connect ", redirection_target, "\"\n")); ++clients_found; - } + )); LOG_INFO("Redirecting: ", ftos(clients_found), " clients left.\n"); @@ -2282,7 +2072,7 @@ void Shutdown() if(world_initialized > 0) { world_initialized = 0; - LOG_INFO("Saving persistent data...\n"); + LOG_TRACE("Saving persistent data...\n"); Ban_SaveBans(); // playerstats with unfinished match @@ -2305,7 +2095,7 @@ void Shutdown() CheatShutdown(); // must be after cheatcount check db_close(ServerProgsDB); db_close(TemporaryDB); - LOG_INFO("done!\n"); + LOG_TRACE("Saving persistent data... done!\n"); // tell the bot system the game is ending now bot_endgame(); @@ -2316,4 +2106,8 @@ void Shutdown() { LOG_INFO("NOTE: crashed before even initializing the world, not saving persistent data\n"); } + else + { + __init_dedicated_server_shutdown(); + } }