X-Git-Url: https://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=blobdiff_plain;f=qcsrc%2Fserver%2Fworld.qc;h=6628b4236c39ab3ed7d1592af909b50e9b4c1356;hp=c37998545ff8c7dabd7d87152d06dbd2ef313195;hb=480de525d9378ac5baa42568c212dc6b13363879;hpb=6ae488fa94396ad9ffa0dc3dbfbedb54c8b7983a diff --git a/qcsrc/server/world.qc b/qcsrc/server/world.qc index c37998545..41c9705da 100644 --- a/qcsrc/server/world.qc +++ b/qcsrc/server/world.qc @@ -1,49 +1,51 @@ #include "world.qh" -#include "anticheat.qh" -#include "antilag.qh" -#include "bot/api.qh" -#include "campaign.qh" -#include "cheats.qh" -#include "client.qh" -#include "command/common.qh" -#include "command/getreplies.qh" -#include "command/sv_cmd.qh" -#include "command/vote.qh" -#include "hook.qh" -#include -#include -#include "ipban.qh" -#include "mapvoting.qh" -#include -#include "race.qh" -#include "scores.qh" -#include "scores_rules.qh" -#include "spawnpoints.qh" -#include "teamplay.qh" -#include "weapons/weaponstats.qh" -#include -#include "../common/constants.qh" -#include -#include "../common/deathtypes/all.qh" +#include +#include #include -#include "../common/gamemodes/sv_rules.qh" -#include "../common/mapinfo.qh" -#include "../common/monsters/_mod.qh" -#include "../common/monsters/sv_monsters.qh" -#include "../common/vehicles/all.qh" -#include "../common/notifications/all.qh" -#include "../common/physics/player.qh" -#include "../common/playerstats.qh" -#include "../common/stats.qh" -#include "../common/teams.qh" +#include +#include +#include +#include +#include +#include #include -#include "../common/mapobjects/trigger/secret.qh" -#include "../common/mapobjects/target/music.qh" -#include "../common/util.qh" -#include "../common/items/_mod.qh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include "../common/state.qh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include const float LATENCY_THINKRATE = 10; .float latency_sum; @@ -65,7 +67,7 @@ void PingPLReport_Think(entity this) { WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT); WriteByte(MSG_BROADCAST, this.cnt); - WriteShort(MSG_BROADCAST, bound(1, CS(e).ping, 65535)); + WriteShort(MSG_BROADCAST, bound(1, rint(CS(e).ping), 32767)); WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_packetloss * 255), 255)); WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_movementloss * 255), 255)); @@ -96,8 +98,6 @@ void PingPLReport_Spawn() } const float SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS = 1; -string redirection_target; -float world_initialized; void SetDefaultAlpha() { @@ -168,6 +168,7 @@ void cvar_changes_init() #define BADPREFIX(p) if(substring(k, 0, strlen(p)) == p) continue #define BADPRESUFFIX(p,s) if(substring(k, 0, strlen(p)) == p && substring(k, -strlen(s), -1) == s) continue #define BADCVAR(p) if(k == p) continue +#define BADVALUE(p, val) if (k == p && v == val) continue // general excludes and namespaces for server admin used cvars BADPREFIX("help_"); // PN's server has this listed as changed, let's not rat him out for THAT @@ -276,7 +277,6 @@ void cvar_changes_init() BADCVAR("g_duel_not_dm_maps"); BADCVAR("g_freezetag"); BADCVAR("g_freezetag_teams"); - BADCVAR("g_invasion_teams"); BADCVAR("g_invasion_type"); BADCVAR("g_jailbreak"); BADCVAR("g_jailbreak_teams"); @@ -334,7 +334,6 @@ void cvar_changes_init() // does nothing gameplay relevant BADCVAR("captureleadlimit_override"); BADCVAR("condump_stripcolors"); - BADCVAR("gameversion"); BADCVAR("fs_gamedir"); BADCVAR("g_allow_oldvortexbeam"); BADCVAR("g_balance_kill_delay"); @@ -345,10 +344,12 @@ void cvar_changes_init() BADCVAR("g_chatsounds"); BADCVAR("g_ca_point_leadlimit"); BADCVAR("g_ca_point_limit"); + BADCVAR("g_ca_spectate_enemies"); BADCVAR("g_ctf_captimerecord_always"); BADCVAR("g_ctf_flag_glowtrails"); BADCVAR("g_ctf_dynamiclights"); BADCVAR("g_ctf_flag_pickup_verbosename"); + BADCVAR("g_ctf_flagcarrier_auto_helpme_damage"); BADPRESUFFIX("g_ctf_flag_", "_model"); BADPRESUFFIX("g_ctf_flag_", "_skin"); BADCVAR("g_domination_point_leadlimit"); @@ -384,6 +385,7 @@ void cvar_changes_init() BADCVAR("sv_minigames"); BADCVAR("sv_namechangetimer"); BADCVAR("sv_precacheplayermodels"); + BADCVAR("sv_qcphysics"); BADCVAR("sv_radio"); BADCVAR("sv_stepheight"); BADCVAR("sv_timeout"); @@ -391,7 +393,7 @@ void cvar_changes_init() BADCVAR("w_prop_interval"); BADPREFIX("chat_"); BADPREFIX("crypto_"); - BADPREFIX("gameversion_"); + BADPREFIX("gameversion"); BADPREFIX("g_chat_"); BADPREFIX("g_ctf_captimerecord_"); BADPREFIX("g_hats_"); @@ -404,12 +406,13 @@ void cvar_changes_init() BADPREFIX("skill_"); BADPREFIX("sv_allow_"); BADPREFIX("sv_cullentities_"); - BADPREFIX("sv_maxidle_"); + BADPREFIX("sv_maxidle"); BADPREFIX("sv_minigames_"); BADPREFIX("sv_radio_"); BADPREFIX("sv_timeout_"); BADPREFIX("sv_vote_"); BADPREFIX("timelimit_"); + BADPRESUFFIX("g_", "_round_timelimit"); // allowed changes to server admins (please sync this to server.cfg) // vi commands: @@ -439,6 +442,7 @@ void cvar_changes_init() BADCVAR("g_ctf_leaderboard"); BADCVAR("g_domination_point_limit"); BADCVAR("g_domination_teams_override"); + BADCVAR("g_freezetag_revive_spawnshield"); BADCVAR("g_freezetag_teams_override"); BADCVAR("g_friendlyfire"); BADCVAR("g_fullbrightitems"); @@ -454,10 +458,11 @@ void cvar_changes_init() BADCVAR("g_physics_clientselect"); BADCVAR("g_pinata"); BADCVAR("g_powerups"); + BADCVAR("g_powerups_drop_ondeath"); BADCVAR("g_player_brightness"); BADCVAR("g_rocket_flying"); BADCVAR("g_rocket_flying_disabledelays"); - BADCVAR("g_spawnshieldtime"); + BADPREFIX("g_spawnshield"); BADCVAR("g_start_delay"); BADCVAR("g_superspectate"); BADCVAR("g_tdm_teams_override"); @@ -483,11 +488,11 @@ void cvar_changes_init() BADCVAR("sv_defaultplayercolors"); BADCVAR("sv_defaultplayermodel"); BADCVAR("sv_defaultplayerskin"); - BADCVAR("sv_maxidle"); BADCVAR("sv_maxrate"); BADCVAR("sv_motd"); BADCVAR("sv_public"); - BADCVAR("sv_ready_restart"); + BADCVAR("sv_showfps"); + BADCVAR("sv_showspectators"); BADCVAR("sv_status_privacy"); BADCVAR("sv_taunt"); BADCVAR("sv_vote_call"); @@ -497,6 +502,8 @@ void cvar_changes_init() BADCVAR("sv_vote_master_commands"); BADCVAR("sv_vote_master_password"); BADCVAR("sv_vote_simple_majority_factor"); + BADVALUE("sys_ticrate", "0.0166667"); + BADVALUE("sys_ticrate", "0.0333333"); BADCVAR("teamplay_mode"); BADCVAR("timelimit_override"); BADPREFIX("g_warmup_"); @@ -510,22 +517,10 @@ void cvar_changes_init() BADCVAR("g_grappling_hook"); BADCVAR("g_jetpack"); - // temporary for testing - // TODO remove before 0.8.3 release - BADCVAR("g_ca_weaponarena"); - BADCVAR("g_freezetag_weaponarena"); - BADCVAR("g_lms_weaponarena"); - BADCVAR("g_ctf_stalemate_time"); - - if(cvar_string("g_mod_balance") == "Testing") - { - // (temporary) while using the Testing balance, any weapon balance cvars are allowed to be changed - BADPREFIX("g_balance_"); - } - #undef BADPRESUFFIX #undef BADPREFIX #undef BADCVAR +#undef BADVALUE if(pureadding) { @@ -588,7 +583,7 @@ spawnfunc(__init_dedicated_server) delete_fn = remove_unsafely; - entity e = spawn(); + entity e = new(GotoFirstMap); setthink(e, GotoFirstMap); e.nextthink = time; // this is usually 1 at this point @@ -627,7 +622,7 @@ STATIC_INIT_EARLY(maxclients) } } -void default_delayedinit(entity this) +void GameplayMode_DelayedInit(entity this) { if(!scores_initialized) ScoreRules_generic(); @@ -677,15 +672,23 @@ void InitGameplayMode() cache_mutatormsg = strzone(""); cache_lastmutatormsg = strzone(""); - InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK); + InitializeEntity(NULL, GameplayMode_DelayedInit, INITPRIO_GAMETYPE_FALLBACK); } -void Map_MarkAsRecent(string m); -float world_already_spawned; +bool world_already_spawned; spawnfunc(worldspawn) { server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated"))); + if (autocvar_sv_termsofservice_url && autocvar_sv_termsofservice_url != "") + { + strcpy(sv_termsofservice_url_escaped, strreplace(":", "|", autocvar_sv_termsofservice_url)); + } + else + { + strcpy(sv_termsofservice_url_escaped, "INVALID"); + } + bool wantrestart = false; { if (!server_is_dedicated) @@ -755,6 +758,9 @@ spawnfunc(worldspawn) cvar_changes_init(); // do this very early now so it REALLY matches the server config + // default to RACE_RECORD, can be overwritten by gamemodes + record_type = RACE_RECORD; + // needs to be done so early because of the constants they create static_init(); @@ -805,16 +811,15 @@ spawnfunc(worldspawn) if(autocvar_g_campaign) CampaignPreInit(); + else + PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode Map_MarkAsRecent(mapname); - PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode - InitGameplayMode(); static_init_late(); static_init_precache(); readlevelcvars(); - GrappleHookInit(); GameRules_limit_fallbacks(); @@ -829,51 +834,15 @@ spawnfunc(worldspawn) WaypointSprite_Init(); - GameLogInit(); // prepare everything // NOTE for matchid: // changing the logic generating it is okay. But: // it HAS to stay <= 64 chars // character set: ASCII 33-126 without the following characters: : ; ' " \ $ - if(autocvar_sv_eventlog) - { - string s = sprintf("%s.%s.%06d", itos(autocvar_sv_eventlog_files_counter), strftime(false, "%s"), floor(random() * 1000000)); - matchid = strzone(s); + // strftime(false, "%s") isn't reliable, see strftime_s description + matchid = strzone(sprintf("%d.%s.%06d", autocvar_sv_eventlog_files_counter, strftime_s(), random() * 1000000)); - GameLogEcho(strcat(":gamestart:", GetGametype(), "_", GetMapname(), ":", s)); - s = ":gameinfo:mutators:LIST"; - - MUTATOR_CALLHOOK(BuildMutatorsString, s); - s = M_ARGV(0, string); - - // initialiation stuff, not good in the mutator system - if(!autocvar_g_use_ammunition) - s = strcat(s, ":no_use_ammunition"); - - // initialiation stuff, not good in the mutator system - if(autocvar_g_pickup_items == 0) - s = strcat(s, ":no_pickup_items"); - if(autocvar_g_pickup_items > 0) - s = strcat(s, ":pickup_items"); - - // initialiation stuff, not good in the mutator system - if(autocvar_g_weaponarena != "0") - s = strcat(s, ":", autocvar_g_weaponarena, " arena"); - - // TODO to mutator system - if(autocvar_g_norecoil) - s = strcat(s, ":norecoil"); - - // TODO to mutator system - if(autocvar_g_powerups == 0) - s = strcat(s, ":no_powerups"); - if(autocvar_g_powerups > 0) - s = strcat(s, ":powerups"); - - GameLogEcho(s); - GameLogEcho(":gameinfo:end"); - } - else - matchid = strzone(ftos(random())); + if(autocvar_sv_eventlog) + GameLogInit(); // requires matchid to be set cvar_set("nextmap", ""); @@ -887,11 +856,8 @@ spawnfunc(worldspawn) MapInfo_Enumerate(); MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1); - if(fexists(strcat("scripts/", mapname, ".arena"))) - cvar_settemp("sv_q3acompat_machineshotgunswap", "1"); - - if(fexists(strcat("scripts/", mapname, ".defi"))) - cvar_settemp("sv_q3defragcompat", "1"); + q3compat = BITSET(q3compat, Q3COMPAT_ARENA, fexists(strcat("scripts/", mapname, ".arena"))); + q3compat = BITSET(q3compat, Q3COMPAT_DEFI, fexists(strcat("scripts/", mapname, ".defi"))); if(whichpack(strcat("maps/", mapname, ".cfg")) != "") { @@ -943,12 +909,18 @@ spawnfunc(worldspawn) maplist_reply = strzone(getmaplist()); lsmaps_reply = strzone(getlsmaps()); monsterlist_reply = strzone(getmonsterlist()); + bool records_available = false; for(int i = 0; i < 10; ++i) { string s = getrecords(i); - if (s) + if (s != "") + { records_reply[i] = strzone(s); + records_available = true; + } } + if (!records_available) + records_reply[0] = "No records available for the current game mode.\n"; ladder_reply = strzone(getladder()); rankings_reply = strzone(getrankings()); @@ -964,7 +936,7 @@ spawnfunc(worldspawn) // fill sv_curl_serverpackages from .serverpackage files if (autocvar_sv_curl_serverpackages_auto) { - string s = "csprogs-" WATERMARK ".txt"; + string s = "csprogs-" WATERMARK ".dat"; // 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) { @@ -1020,413 +992,139 @@ spawnfunc(light) delete(this); } -string GetGametype() -{ - return MapInfo_Type_ToString(MapInfo_LoadedGametype); -} - -string GetMapname() -{ - return mapname; -} - -float Map_Count, Map_Current; -string Map_Current_Name; - -// NOTE: this now expects the map list to be already tokenized and the count in Map_Count -int GetMaplistPosition() -{ - string map = GetMapname(); - int idx = autocvar_g_maplist_index; - - if(idx >= 0) - { - if(idx < Map_Count) - { - if(map == argv(idx)) - { - return idx; - } - } - } - - for(int pos = 0; pos < Map_Count; ++pos) - { - if(map == argv(pos)) - return pos; - } - - // resume normal maplist rotation if current map is not in g_maplist - return idx; -} - -bool MapHasRightSize(string map) +bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance, bool frompos) { - int minplayers = max(0, floor(autocvar_minplayers)); - if (teamplay) - minplayers = max(0, floor(autocvar_minplayers_per_team) * AvailableTeams()); - if (autocvar_g_maplist_check_waypoints - && (currentbots || autocvar_bot_number || player_count < minplayers)) - { - string checkwp_msg = strcat("checkwp ", map); - if(!fexists(strcat("maps/", map, ".waypoints"))) + float m = e.dphitcontentsmask; + e.dphitcontentsmask = goodcontents | badcontents; + + vector org = boundmin; + vector delta = boundmax - boundmin; + + vector start, end; + start = end = org; + int j; // used after the loop + for(j = 0; j < attempts; ++j) + { + start.x = org.x + random() * delta.x; + start.y = org.y + random() * delta.y; + start.z = org.z + random() * delta.z; + + // rule 1: start inside world bounds, and outside + // solid, and don't start from somewhere where you can + // fall down to evil + tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e); + if (trace_fraction >= 1) + continue; + if (trace_startsolid) + continue; + if (trace_dphitcontents & badcontents) + continue; + if (trace_dphitq3surfaceflags & badsurfaceflags) + continue; + + // rule 2: if we are too high, lower the point + if (trace_fraction * delta.z > maxaboveground) + start = trace_endpos + '0 0 1' * maxaboveground; + vector enddown = trace_endpos; + + // rule 3: make sure we aren't outside the map. This only works + // for somewhat well formed maps. A good rule of thumb is that + // the map should have a convex outside hull. + // these can be traceLINES as we already verified the starting box + vector mstart = start + 0.5 * (e.mins + e.maxs); + traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e); + if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") + continue; + traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e); + if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") + continue; + traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e); + if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") + continue; + traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e); + if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") + continue; + traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e); + if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk") + continue; + + // rule 4: we must "see" some spawnpoint or item + entity sp = NULL; + if(frompos) { - LOG_TRACE(checkwp_msg, ": no waypoints"); - return false; + if((traceline(mstart, e.origin, MOVE_NORMAL, e), trace_fraction) >= 1) + sp = e; } - LOG_TRACE(checkwp_msg, ": has waypoints"); - } - - if(autocvar_g_maplist_ignore_sizes) - return true; - - // open map size restriction file - string opensize_msg = strcat("opensize ", map); - float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ); - int player_limit = ((autocvar_g_maplist_sizes_count_maxplayers) ? GetPlayerLimit() : 0); - 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 - if(!autocvar_g_maplist_sizes_count_bots) - pcount -= currentbots; - if(fh >= 0) - { - opensize_msg = strcat(opensize_msg, ": ok, "); - int mapmin = stoi(fgets(fh)); - int mapmax = stoi(fgets(fh)); - fclose(fh); - if(pcount < mapmin) + if(!sp) { - LOG_TRACE(opensize_msg, "not enough"); - return false; + IL_EACH(g_spawnpoints, checkpvs(mstart, it), + { + if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1) + { + sp = it; + break; + } + }); } - if(mapmax && pcount > mapmax) + if(!sp) { - LOG_TRACE(opensize_msg, "too many"); - return false; - } - LOG_TRACE(opensize_msg, "right size"); - return true; - } - LOG_TRACE(opensize_msg, ": not found"); - return true; -} - -string Map_Filename(float position) -{ - return strcat("maps/", argv(position), ".bsp"); -} - -void Map_MarkAsRecent(string m) -{ - cvar_set("g_maplist_mostrecent", strwords(cons(m, autocvar_g_maplist_mostrecent), max(0, autocvar_g_maplist_mostrecent_count))); -} - -float Map_IsRecent(string m) -{ - return strhasword(autocvar_g_maplist_mostrecent, m); -} - -float Map_Check(float position, float pass) -{ - string filename; - string map_next; - map_next = argv(position); - if(pass <= 1) - { - if(Map_IsRecent(map_next)) - return 0; - } - filename = Map_Filename(position); - if(MapInfo_CheckMap(map_next)) - { - if(pass == 2) - return 1; - if(MapHasRightSize(map_next)) - return 1; - return 0; - } - else - LOG_DEBUG( "Couldn't select '", filename, "'..." ); - - return 0; -} - -void Map_Goto_SetStr(string nextmapname) -{ - if(getmapname_stored != "") - strunzone(getmapname_stored); - if(nextmapname == "") - getmapname_stored = ""; - else - getmapname_stored = strzone(nextmapname); -} - -void Map_Goto_SetFloat(float position) -{ - cvar_set("g_maplist_index", ftos(position)); - Map_Goto_SetStr(argv(position)); -} - -void Map_Goto(float reinit) -{ - MapInfo_LoadMap(getmapname_stored, reinit); -} - -// return codes of map selectors: -// -1 = temporary failure (that is, try some method that is guaranteed to succeed) -// -2 = permanent failure -float MaplistMethod_Iterate() // usual method -{ - float pass, i; + int items_checked = 0; + IL_EACH(g_items, checkpvs(mstart, it), + { + if((traceline(mstart, it.origin + (it.mins + it.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1) + { + sp = it; + break; + } - LOG_TRACE("Trying MaplistMethod_Iterate"); + ++items_checked; + if(items_checked >= attempts) + break; // sanity + }); - for(pass = 1; pass <= 2; ++pass) - { - for(i = 1; i < Map_Count; ++i) - { - float mapindex; - mapindex = (i + Map_Current) % Map_Count; - if(Map_Check(mapindex, pass)) - return mapindex; + if(!sp) + continue; } - } - return -1; -} -float MaplistMethod_Repeat() // fallback method -{ - LOG_TRACE("Trying MaplistMethod_Repeat"); + // find a random vector to "look at" + end.x = org.x + random() * delta.x; + end.y = org.y + random() * delta.y; + end.z = org.z + random() * delta.z; + end = start + normalize(end - start) * vlen(delta); - if(Map_Check(Map_Current, 2)) - return Map_Current; - return -2; -} + // rule 4: start TO end must not be too short + tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e); + if(trace_startsolid) + continue; + if(trace_fraction < minviewdistance / vlen(delta)) + continue; -float MaplistMethod_Random() // random map selection -{ - float i, imax; + // rule 5: don't want to look at sky + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) + continue; - LOG_TRACE("Trying MaplistMethod_Random"); - - imax = 42; - - for(i = 0; i <= imax; ++i) - { - float mapindex; - mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map - if(Map_Check(mapindex, 1)) - return mapindex; - } - return -1; -} - -float MaplistMethod_Shuffle(float exponent) // more clever shuffling -// the exponent sets a bias on the map selection: -// the higher the exponent, the less likely "shortly repeated" same maps are -{ - float i, j, imax, insertpos; - - LOG_TRACE("Trying MaplistMethod_Shuffle"); - - imax = 42; - - for(i = 0; i <= imax; ++i) - { - string newlist; - - // now reinsert this at another position - insertpos = (random() ** (1 / exponent)); // ]0, 1] - insertpos = insertpos * (Map_Count - 1); // ]0, Map_Count - 1] - insertpos = ceil(insertpos) + 1; // {2, 3, 4, ..., Map_Count} - LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos)); - - // insert the current map there - newlist = ""; - for(j = 1; j < insertpos; ++j) // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above - newlist = strcat(newlist, " ", argv(j)); - newlist = strcat(newlist, " ", argv(0)); // now insert the just selected map - for(j = insertpos; j < Map_Count; ++j) // i == Map_Count: no loop, has just been inserted as last - newlist = strcat(newlist, " ", argv(j)); - newlist = substring(newlist, 1, strlen(newlist) - 1); - cvar_set("g_maplist", newlist); - Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); - - // NOTE: the selected map has just been inserted at (insertpos-1)th position - Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working - if(Map_Check(Map_Current, 1)) - return Map_Current; - } - return -1; -} - -void Maplist_Init() -{ - float i = Map_Count = 0; - if(autocvar_g_maplist != "") - { - Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); - for (i = 0; i < Map_Count; ++i) - { - if (Map_Check(i, 2)) - break; - } - } + // rule 6: we must not end up in trigger_hurt + if(tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown)) + continue; - if (i == Map_Count) - { - bprint( "Maplist contains no usable maps! Resetting it to default map list.\n" ); - cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags() | MAPINFO_FLAG_NOAUTOMAPLIST)); - if(autocvar_g_maplist_shuffle) - ShuffleMaplist(); - if(!server_is_dedicated) - localcmd("\nmenu_cmd sync\n"); - Map_Count = tokenizebyseparator(autocvar_g_maplist, " "); - } - if(Map_Count == 0) - error("empty maplist, cannot select a new map"); - Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1); - - strcpy(Map_Current_Name, argv(Map_Current)); // will be automatically freed on exit thanks to DP - // this may or may not be correct, but who cares, in the worst case a map - // isn't chosen in the first pass that should have been -} - -string GetNextMap() -{ - Maplist_Init(); - float nextMap = -1; - - if(nextMap == -1) - if(autocvar_g_maplist_shuffle > 0) - nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1); - - if(nextMap == -1) - if(autocvar_g_maplist_selectrandom) - nextMap = MaplistMethod_Random(); - - if(nextMap == -1) - nextMap = MaplistMethod_Iterate(); - - if(nextMap == -1) - nextMap = MaplistMethod_Repeat(); - - if(nextMap >= 0) - { - Map_Goto_SetFloat(nextMap); - return getmapname_stored; - } - - return ""; -} - -float DoNextMapOverride(float reinit) -{ - if(autocvar_g_campaign) - { - CampaignPostIntermission(); - alreadychangedlevel = true; - return true; - } - if(autocvar_quit_when_empty) - { - if(player_count <= currentbots) - { - localcmd("quit\n"); - alreadychangedlevel = true; - return true; - } - } - if(autocvar_quit_and_redirect != "") - { - redirection_target = strzone(autocvar_quit_and_redirect); - alreadychangedlevel = true; - return true; - } - if (!reinit && autocvar_samelevel) // if samelevel is set, stay on same level - { - localcmd("restart\n"); - alreadychangedlevel = true; - return true; - } - if(autocvar_nextmap != "") - { - string m; - m = GameTypeVote_MapInfo_FixName(autocvar_nextmap); - cvar_set("nextmap",m); - - if(!m || gametypevote) - return false; - if(autocvar_sv_vote_gametype) - { - Map_Goto_SetStr(m); - return false; - } - - if(MapInfo_CheckMap(m)) - { - Map_Goto_SetStr(m); - Map_Goto(reinit); - alreadychangedlevel = true; - return true; - } - } - if(!reinit && autocvar_lastlevel) - { - cvar_settemp_restore(); - localcmd("set lastlevel 0\ntogglemenu 1\n"); - alreadychangedlevel = true; - return true; - } - return false; -} + break; + } -void GotoNextMap(float reinit) -{ - //string nextmap; - //float n, nummaps; - //string s; - if (alreadychangedlevel) - return; - alreadychangedlevel = true; + e.dphitcontentsmask = m; - string nextMap = GetNextMap(); - if(nextMap == "") - error("Everything is broken - cannot find a next map. Please report this to the developers."); - Map_Goto(reinit); + if(j < attempts) + { + setorigin(e, start); + e.angles = vectoangles(end - start); + LOG_DEBUG("Needed ", ftos(j + 1), " attempts"); + return true; + } + return false; } - -/* -============ -IntermissionThink - -When the player presses attack or jump, change to the next level -============ -*/ -.float autoscreenshot; -void IntermissionThink(entity this) +float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance) { - FixIntermissionClient(this); - - float server_screenshot = (autocvar_sv_autoscreenshot && CS(this).cvar_cl_autoscreenshot); - float client_screenshot = (CS(this).cvar_cl_autoscreenshot == 2); - - if( (server_screenshot || client_screenshot) - && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) ) - { - this.autoscreenshot = -1; - if(IS_REAL_CLIENT(this)) { stuffcmd(this, sprintf("\nscreenshot screenshots/autoscreenshot/%s-%s.jpg; echo \"^5A screenshot has been taken at request of the server.\"\n", GetMapname(), strftime(false, "%s"))); } - return; - } - - if (time < intermission_exittime) - return; - - if(!mapvote_initialized) - if (time < intermission_exittime + 10 && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_ATCK2(this) || PHYS_INPUT_BUTTON_HOOK(this) || PHYS_INPUT_BUTTON_USE(this))) - return; - - MapVote_Start(); + return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance, false); } /* @@ -1467,7 +1165,7 @@ void DumpStats(float final) s = strcat(s, GetGametype(), "_", GetMapname(), ":", ftos(rint(time))); if(to_console) - LOG_INFO(s); + LOG_HELP(s); if(to_eventlog) GameLogEcho(s); @@ -1483,7 +1181,7 @@ void DumpStats(float final) s = strcat(":labels:player:", GetPlayerScoreString(NULL, 0)); if(to_console) - LOG_INFO(s); + LOG_HELP(s); if(to_eventlog) GameLogEcho(s); if(to_file) @@ -1492,24 +1190,24 @@ void DumpStats(float final) FOREACH_CLIENT(IS_REAL_CLIENT(it) || (IS_BOT_CLIENT(it) && autocvar_sv_logscores_bots), { s = strcat(":player:see-labels:", GetPlayerScoreString(it, 0), ":"); s = strcat(s, ftos(rint(time - CS(it).jointime)), ":"); - if(IS_PLAYER(it) || MUTATOR_CALLHOOK(GetPlayerStatus, it)) + if(IS_PLAYER(it) || INGAME_JOINED(it)) s = strcat(s, ftos(it.team), ":"); else s = strcat(s, "spectator:"); if(to_console) - LOG_INFO(s, playername(it, false)); + LOG_HELP(s, playername(it.netname, it.team, false)); if(to_eventlog) - GameLogEcho(strcat(s, ftos(it.playerid), ":", playername(it, false))); + GameLogEcho(strcat(s, ftos(it.playerid), ":", playername(it.netname, it.team, false))); if(to_file) - fputs(file, strcat(s, playername(it, false), "\n")); + fputs(file, strcat(s, playername(it.netname, it.team, false), "\n")); }); if(teamplay) { s = strcat(":labels:teamscores:", GetTeamScoreString(0, 0)); if(to_console) - LOG_INFO(s); + LOG_HELP(s); if(to_eventlog) GameLogEcho(s); if(to_file) @@ -1520,7 +1218,7 @@ void DumpStats(float final) s = strcat(":teamscores:see-labels:", GetTeamScoreString(i, 0)); s = strcat(s, ":", ftos(i)); if(to_console) - LOG_INFO(s); + LOG_HELP(s); if(to_eventlog) GameLogEcho(s); if(to_file) @@ -1529,7 +1227,7 @@ void DumpStats(float final) } if(to_console) - LOG_INFO(":end"); + LOG_HELP(":end"); if(to_eventlog) GameLogEcho(":end"); if(to_file) @@ -1539,48 +1237,15 @@ void DumpStats(float final) } } -void FixIntermissionClient(entity e) -{ - if(!e.autoscreenshot) // initial call - { - e.autoscreenshot = time + 0.8; // used for autoscreenshot - SetResourceExplicit(e, RES_HEALTH, -2342); - // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not) - for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .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"); - RandomSelection_Init(); - FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, { - RandomSelection_AddString(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); - } - } -} - /* go to the next level for deathmatch only called if a time or frag limit has expired */ void NextLevel() { + cvar_set("_endmatch", "0"); game_stopped = true; - intermission_running = 1; // game over + intermission_running = true; // game over // enforce a wait time before allowing changelevel if(player_count > 0) @@ -1612,10 +1277,18 @@ void NextLevel() GameLogClose(); - FOREACH_CLIENT(IS_PLAYER(it), { + int winner_team = 0; + FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { FixIntermissionClient(it); if(it.winning) - bprint(playername(it, false), " ^7wins.\n"); + { + if (teamplay && !winner_team) + { + winner_team = it.team; + bprint(Team_ColorCode(winner_team), Team_ColorName_Upper(winner_team), "^7 team wins the match\n"); + } + bprint(playername(it.netname, it.team, false), " ^7wins\n"); + } }); target_music_kill(); @@ -1629,7 +1302,7 @@ void NextLevel() } -float InitiateSuddenDeath() +int InitiateSuddenDeath() { // Check first whether normal overtimes could be added before initiating suddendeath mode // - for this timelimit_overtime needs to be >0 of course @@ -1646,9 +1319,14 @@ float InitiateSuddenDeath() if(!checkrules_suddendeathend) { if(autocvar_g_campaign) + { checkrules_suddendeathend = time; // no suddendeath in campaign + } else + { checkrules_suddendeathend = time + 60 * autocvar_timelimit_suddendeath; + overtimes = -1; + } if(g_race && !g_race_qualifying) race_StartCompleting(); } @@ -1659,6 +1337,7 @@ float InitiateSuddenDeath() void InitiateOvertime() // ONLY call this if InitiateSuddenDeath returned true { ++checkrules_overtimesadded; + overtimes = checkrules_overtimesadded; //add one more overtime by simply extending the timelimit cvar_set("timelimit", ftos(autocvar_timelimit + autocvar_timelimit_overtime)); Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60); @@ -1695,13 +1374,13 @@ float GetWinningCode(float fraglimitreached, float equality) // set the .winning flag for exactly those players with a given field value void SetWinners(.float field, float value) { - FOREACH_CLIENT(IS_PLAYER(it), { it.winning = (it.(field) == value); }); + FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { it.winning = (it.(field) == value); }); } // set the .winning flag for those players with a given field value void AddWinners(.float field, float value) { - FOREACH_CLIENT(IS_PLAYER(it), { + FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { if(it.(field) == value) it.winning = 1; }); @@ -1710,12 +1389,7 @@ void AddWinners(.float field, float value) // clear the .winning flags void ClearWinners() { - FOREACH_CLIENT(IS_PLAYER(it), { it.winning = 0; }); -} - -void ShuffleMaplist() -{ - cvar_set("g_maplist", shufflewords(autocvar_g_maplist)); + FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { it.winning = 0; }); } int fragsleft_last; @@ -1914,19 +1588,18 @@ void CheckRules_World() leadlimit = 0; // no leadlimit for now } - if(timelimit > 0) - { - timelimit += game_starttime; - } - else if (timelimit < 0) + if (autocvar__endmatch || timelimit < 0) { // endmatch NextLevel(); return; } - float wantovertime; - wantovertime = 0; + if(timelimit > 0) + timelimit += game_starttime; + + int overtimes_prev = overtimes; + int wantovertime = 0; if(checkrules_suddendeathend) { @@ -1962,7 +1635,7 @@ void CheckRules_World() if(readyplayers || playerswithlaps >= 2) { checkrules_suddendeathend = 0; - ReadyRestart(); // go to race + ReadyRestart(true); // go to race return; } else @@ -2012,38 +1685,531 @@ void CheckRules_World() if(checkrules_status == WINNING_YES) { + if (overtimes == -1 && overtimes != overtimes_prev) + { + // if suddendeathend overtime has just begun, revert it + checkrules_suddendeathend = 0; + overtimes = overtimes_prev; + } //print("WINNING\n"); NextLevel(); } } -string GotoMap(string m) +float want_weapon(entity weaponinfo, float allguns) { - m = GameTypeVote_MapInfo_FixName(m); - if (!m) - return "The map you suggested is not available on this server."; - if (!autocvar_sv_vote_gametype) - if(!MapInfo_CheckMap(m)) - return "The map you suggested does not support the current game mode."; - cvar_set("nextmap", m); - cvar_set("timelimit", "-1"); - if(mapvote_initialized || alreadychangedlevel) - { - if(DoNextMapOverride(0)) - return "Map switch initiated."; - else - return "Hm... no. For some reason I like THIS map more."; + int d = 0; + bool allow_mutatorblocked = false; + + if(!weaponinfo.m_id) + return 0; + + bool mutator_returnvalue = MUTATOR_CALLHOOK(WantWeapon, weaponinfo, d, allguns, allow_mutatorblocked); + d = M_ARGV(1, float); + allguns = M_ARGV(2, bool); + allow_mutatorblocked = M_ARGV(3, bool); + + if(allguns) + d = boolean((weaponinfo.spawnflags & WEP_FLAG_NORMAL) && !(weaponinfo.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_SPECIALATTACK))); + else if(!mutator_returnvalue) + d = !(!weaponinfo.weaponstart); + + if(!allow_mutatorblocked && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns + d = 0; + + float t = weaponinfo.weaponstartoverride; + + //LOG_INFOF("want_weapon: %s - d: %d t: %d\n", weaponinfo.netname, d, t); + + // bit order in t: + // 1: want or not + // 2: is default? + // 4: is set by default? + if(t < 0) + t = 4 | (3 * d); + else + t |= (2 * d); + + return t; +} + +/// Weapons the player normally starts with outside weapon arena. +WepSet weapons_start() +{ + WepSet ret = '0 0 0'; + FOREACH(Weapons, it != WEP_Null, { + int w = want_weapon(it, false); + if (w & 1) + ret |= it.m_wepset; + }); + return ret; +} + +WepSet weapons_all() +{ + WepSet ret = '0 0 0'; + FOREACH(Weapons, it != WEP_Null, { + if (!(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))) + ret |= it.m_wepset; + }); + return ret; +} + +WepSet weapons_devall() +{ + WepSet ret = '0 0 0'; + FOREACH(Weapons, it != WEP_Null, + { + ret |= it.m_wepset; + }); + return ret; +} + +WepSet weapons_most() +{ + WepSet ret = '0 0 0'; + FOREACH(Weapons, it != WEP_Null, { + if ((it.spawnflags & WEP_FLAG_NORMAL) && !(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_SPECIALATTACK))) + ret |= it.m_wepset; + }); + return ret; +} + +void weaponarena_available_all_update(entity this) +{ + if (weaponsInMapAll) + { + start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | (weaponsInMapAll & weapons_all()); + } + else + { + // if no weapons are available on the map, just fall back to all weapons arena + start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_all(); + } +} + +void weaponarena_available_devall_update(entity this) +{ + if (weaponsInMapAll) + { + start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | weaponsInMapAll; } else - return "Map switch will happen after scoreboard."; + { + // if no weapons are available on the map, just fall back to devall weapons arena + start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_devall(); + } +} + +void weaponarena_available_most_update(entity this) +{ + if (weaponsInMapAll) + { + start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | (weaponsInMapAll & weapons_most()); + } + else + { + // if no weapons are available on the map, just fall back to most weapons arena + start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_most(); + } +} + +void readplayerstartcvars() +{ + // initialize starting values for players + start_weapons = '0 0 0'; + start_weapons_default = '0 0 0'; + start_weapons_defaultmask = '0 0 0'; + start_items = 0; + start_ammo_shells = 0; + start_ammo_nails = 0; + start_ammo_rockets = 0; + start_ammo_cells = 0; + start_ammo_plasma = 0; + if (random_start_ammo == NULL) + { + random_start_ammo = new_pure(random_start_ammo); + } + start_health = cvar("g_balance_health_start"); + start_armorvalue = cvar("g_balance_armor_start"); + + g_weaponarena = 0; + g_weaponarena_weapons = '0 0 0'; + + string s = cvar_string("g_weaponarena"); + + MUTATOR_CALLHOOK(SetWeaponArena, s); + s = M_ARGV(0, string); + + if (s == "0" || s == "") + { + // no arena + } + else if (s == "off") + { + // forcibly turn off weaponarena + } + else if (s == "all" || s == "1") + { + g_weaponarena = 1; + g_weaponarena_list = "All Weapons Arena"; + g_weaponarena_weapons = weapons_all(); + } + else if (s == "devall") + { + g_weaponarena = 1; + g_weaponarena_list = "Dev All Weapons Arena"; + g_weaponarena_weapons = weapons_devall(); + } + else if (s == "most") + { + g_weaponarena = 1; + g_weaponarena_list = "Most Weapons Arena"; + g_weaponarena_weapons = weapons_most(); + } + else if (s == "all_available") + { + g_weaponarena = 1; + g_weaponarena_list = "All Available Weapons Arena"; + + // this needs to run after weaponsInMapAll is initialized + InitializeEntity(NULL, weaponarena_available_all_update, INITPRIO_FINDTARGET); + } + else if (s == "devall_available") + { + g_weaponarena = 1; + g_weaponarena_list = "Dev All Available Weapons Arena"; + + // this needs to run after weaponsInMapAll is initialized + InitializeEntity(NULL, weaponarena_available_devall_update, INITPRIO_FINDTARGET); + } + else if (s == "most_available") + { + g_weaponarena = 1; + g_weaponarena_list = "Most Available Weapons Arena"; + + // this needs to run after weaponsInMapAll is initialized + InitializeEntity(NULL, weaponarena_available_most_update, INITPRIO_FINDTARGET); + } + else if (s == "none") + { + g_weaponarena = 1; + g_weaponarena_list = "No Weapons Arena"; + } + else + { + g_weaponarena = 1; + float t = tokenize_console(s); + g_weaponarena_list = ""; + for (int j = 0; j < t; ++j) + { + s = argv(j); + Weapon wep = Weapon_from_name(s); + if(wep != WEP_Null) + { + g_weaponarena_weapons |= (wep.m_wepset); + g_weaponarena_list = strcat(g_weaponarena_list, wep.netname, " & "); + } + } + if (g_weaponarena_list != "") // remove trailing " & " + g_weaponarena_list = substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3); + else // no valid weapon found + g_weaponarena_list = "No Weapons Arena"; + } + + if (g_weaponarena) + { + g_weapon_stay = 0; // incompatible + start_weapons = g_weaponarena_weapons; + start_items |= IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS; + g_weaponarena_list = strzone(g_weaponarena_list); + } + else + { + FOREACH(Weapons, it != WEP_Null, { + int w = want_weapon(it, false); + WepSet s = it.m_wepset; + if(w & 1) + start_weapons |= s; + if(w & 2) + start_weapons_default |= s; + if(w & 4) + start_weapons_defaultmask |= s; + }); + } + + if(cvar("g_balance_superweapons_time") < 0) + start_items |= IT_UNLIMITED_SUPERWEAPONS; + + if(!cvar("g_use_ammunition")) + start_items |= IT_UNLIMITED_AMMO; + + if(start_items & IT_UNLIMITED_AMMO) + { + start_ammo_shells = 999; + start_ammo_nails = 999; + start_ammo_rockets = 999; + start_ammo_cells = 999; + start_ammo_plasma = 999; + start_ammo_fuel = 999; + } + else + { + start_ammo_shells = cvar("g_start_ammo_shells"); + start_ammo_nails = cvar("g_start_ammo_nails"); + start_ammo_rockets = cvar("g_start_ammo_rockets"); + start_ammo_cells = cvar("g_start_ammo_cells"); + start_ammo_plasma = cvar("g_start_ammo_plasma"); + start_ammo_fuel = cvar("g_start_ammo_fuel"); + random_start_weapons_count = cvar("g_random_start_weapons_count"); + SetResource(random_start_ammo, RES_SHELLS, cvar("g_random_start_shells")); + SetResource(random_start_ammo, RES_BULLETS, cvar("g_random_start_bullets")); + SetResource(random_start_ammo, RES_ROCKETS, cvar("g_random_start_rockets")); + SetResource(random_start_ammo, RES_CELLS, cvar("g_random_start_cells")); + SetResource(random_start_ammo, RES_PLASMA, cvar("g_random_start_plasma")); + } + + warmup_start_ammo_shells = start_ammo_shells; + warmup_start_ammo_nails = start_ammo_nails; + warmup_start_ammo_rockets = start_ammo_rockets; + warmup_start_ammo_cells = start_ammo_cells; + warmup_start_ammo_plasma = start_ammo_plasma; + warmup_start_ammo_fuel = start_ammo_fuel; + warmup_start_health = start_health; + warmup_start_armorvalue = start_armorvalue; + warmup_start_weapons = start_weapons; + warmup_start_weapons_default = start_weapons_default; + warmup_start_weapons_defaultmask = start_weapons_defaultmask; + + if (!g_weaponarena) + { + warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells"); + warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails"); + warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets"); + warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells"); + warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma"); + warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel"); + warmup_start_health = cvar("g_warmup_start_health"); + warmup_start_armorvalue = cvar("g_warmup_start_armor"); + warmup_start_weapons = '0 0 0'; + warmup_start_weapons_default = '0 0 0'; + warmup_start_weapons_defaultmask = '0 0 0'; + FOREACH(Weapons, it != WEP_Null, { + int w = want_weapon(it, autocvar_g_warmup_allguns); + WepSet s = it.m_wepset; + if(w & 1) + warmup_start_weapons |= s; + if(w & 2) + warmup_start_weapons_default |= s; + if(w & 4) + warmup_start_weapons_defaultmask |= s; + }); + } + + if (autocvar_g_jetpack) + start_items |= ITEM_Jetpack.m_itemid; + + MUTATOR_CALLHOOK(SetStartItems); + + if (start_items & ITEM_Jetpack.m_itemid) + { + start_items |= ITEM_JetpackRegen.m_itemid; + start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable")); + warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable")); + } + + start_ammo_shells = max(0, start_ammo_shells); + start_ammo_nails = max(0, start_ammo_nails); + start_ammo_rockets = max(0, start_ammo_rockets); + start_ammo_cells = max(0, start_ammo_cells); + start_ammo_plasma = max(0, start_ammo_plasma); + start_ammo_fuel = max(0, start_ammo_fuel); + SetResource(random_start_ammo, RES_SHELLS, max(0, GetResource(random_start_ammo, RES_SHELLS))); + SetResource(random_start_ammo, RES_BULLETS, max(0, GetResource(random_start_ammo, RES_BULLETS))); + SetResource(random_start_ammo, RES_ROCKETS, max(0, GetResource(random_start_ammo, RES_ROCKETS))); + SetResource(random_start_ammo, RES_CELLS, max(0, GetResource(random_start_ammo, RES_CELLS))); + SetResource(random_start_ammo, RES_PLASMA, max(0, GetResource(random_start_ammo, RES_PLASMA))); + + warmup_start_ammo_shells = max(0, warmup_start_ammo_shells); + warmup_start_ammo_nails = max(0, warmup_start_ammo_nails); + warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets); + warmup_start_ammo_cells = max(0, warmup_start_ammo_cells); + warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma); + warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel); +} + +void readlevelcvars() +{ + if(cvar("sv_allow_fullbright")) + serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT; + + sv_ready_restart_after_countdown = cvar("sv_ready_restart_after_countdown"); + + warmup_stage = cvar("g_warmup"); + warmup_limit = cvar("g_warmup_limit"); + + if(cvar("g_campaign")) + warmup_stage = 0; // no warmup during campaign + + g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon"); + g_pickup_respawntime_superweapon = cvar("g_pickup_respawntime_superweapon"); + g_pickup_respawntime_ammo = cvar("g_pickup_respawntime_ammo"); + g_pickup_respawntime_short = cvar("g_pickup_respawntime_short"); + g_pickup_respawntime_medium = cvar("g_pickup_respawntime_medium"); + g_pickup_respawntime_long = cvar("g_pickup_respawntime_long"); + g_pickup_respawntime_powerup = cvar("g_pickup_respawntime_powerup"); + g_pickup_respawntimejitter_weapon = cvar("g_pickup_respawntimejitter_weapon"); + g_pickup_respawntimejitter_superweapon = cvar("g_pickup_respawntimejitter_superweapon"); + g_pickup_respawntimejitter_ammo = cvar("g_pickup_respawntimejitter_ammo"); + g_pickup_respawntimejitter_short = cvar("g_pickup_respawntimejitter_short"); + g_pickup_respawntimejitter_medium = cvar("g_pickup_respawntimejitter_medium"); + g_pickup_respawntimejitter_long = cvar("g_pickup_respawntimejitter_long"); + g_pickup_respawntimejitter_powerup = cvar("g_pickup_respawntimejitter_powerup"); + + g_pickup_shells = cvar("g_pickup_shells"); + g_pickup_shells_max = cvar("g_pickup_shells_max"); + g_pickup_nails = cvar("g_pickup_nails"); + g_pickup_nails_max = cvar("g_pickup_nails_max"); + g_pickup_rockets = cvar("g_pickup_rockets"); + g_pickup_rockets_max = cvar("g_pickup_rockets_max"); + g_pickup_cells = cvar("g_pickup_cells"); + g_pickup_cells_max = cvar("g_pickup_cells_max"); + g_pickup_plasma = cvar("g_pickup_plasma"); + g_pickup_plasma_max = cvar("g_pickup_plasma_max"); + g_pickup_fuel = cvar("g_pickup_fuel"); + g_pickup_fuel_jetpack = cvar("g_pickup_fuel_jetpack"); + g_pickup_fuel_max = cvar("g_pickup_fuel_max"); + g_pickup_armorsmall = cvar("g_pickup_armorsmall"); + g_pickup_armorsmall_max = cvar("g_pickup_armorsmall_max"); + g_pickup_armorsmall_anyway = cvar("g_pickup_armorsmall_anyway"); + g_pickup_armormedium = cvar("g_pickup_armormedium"); + g_pickup_armormedium_max = cvar("g_pickup_armormedium_max"); + g_pickup_armormedium_anyway = cvar("g_pickup_armormedium_anyway"); + g_pickup_armorbig = cvar("g_pickup_armorbig"); + g_pickup_armorbig_max = cvar("g_pickup_armorbig_max"); + g_pickup_armorbig_anyway = cvar("g_pickup_armorbig_anyway"); + g_pickup_armormega = cvar("g_pickup_armormega"); + g_pickup_armormega_max = cvar("g_pickup_armormega_max"); + g_pickup_armormega_anyway = cvar("g_pickup_armormega_anyway"); + g_pickup_healthsmall = cvar("g_pickup_healthsmall"); + g_pickup_healthsmall_max = cvar("g_pickup_healthsmall_max"); + g_pickup_healthsmall_anyway = cvar("g_pickup_healthsmall_anyway"); + g_pickup_healthmedium = cvar("g_pickup_healthmedium"); + g_pickup_healthmedium_max = cvar("g_pickup_healthmedium_max"); + g_pickup_healthmedium_anyway = cvar("g_pickup_healthmedium_anyway"); + g_pickup_healthbig = cvar("g_pickup_healthbig"); + g_pickup_healthbig_max = cvar("g_pickup_healthbig_max"); + g_pickup_healthbig_anyway = cvar("g_pickup_healthbig_anyway"); + g_pickup_healthmega = cvar("g_pickup_healthmega"); + g_pickup_healthmega_max = cvar("g_pickup_healthmega_max"); + g_pickup_healthmega_anyway = cvar("g_pickup_healthmega_anyway"); + + g_pickup_ammo_anyway = cvar("g_pickup_ammo_anyway"); + g_pickup_weapons_anyway = cvar("g_pickup_weapons_anyway"); + + g_weapon_stay = cvar(strcat("g_", GetGametype(), "_weapon_stay")); + if(!g_weapon_stay) + g_weapon_stay = cvar("g_weapon_stay"); + + MUTATOR_CALLHOOK(ReadLevelCvars); + + if (!warmup_stage && !autocvar_g_campaign) + game_starttime = time + cvar("g_start_delay"); + + FOREACH(Weapons, it != WEP_Null, { it.wr_init(it); }); + + readplayerstartcvars(); +} + +void InitializeEntity(entity e, void(entity this) func, int order) +{ + entity prev, cur; + + if (!e || e.initialize_entity) + { + // make a proxy initializer entity + entity e_old = e; + e = new(initialize_entity); + e.enemy = e_old; + } + + e.initialize_entity = func; + e.initialize_entity_order = order; + + cur = initialize_entity_first; + prev = NULL; + for (;;) + { + if (!cur || cur.initialize_entity_order > order) + { + // insert between prev and cur + if (prev) + prev.initialize_entity_next = e; + else + initialize_entity_first = e; + e.initialize_entity_next = cur; + return; + } + prev = cur; + cur = cur.initialize_entity_next; + } +} +void InitializeEntitiesRun() +{ + entity startoflist = initialize_entity_first; + initialize_entity_first = NULL; + delete_fn = remove_except_protected; + for (entity e = startoflist; e; e = e.initialize_entity_next) + { + e.remove_except_protected_forbidden = 1; + } + for (entity e = startoflist; e; ) + { + e.remove_except_protected_forbidden = 0; + e.initialize_entity_order = 0; + entity next = e.initialize_entity_next; + e.initialize_entity_next = NULL; + var void(entity this) func = e.initialize_entity; + e.initialize_entity = func_null; + if (e.classname == "initialize_entity") + { + entity wrappee = e.enemy; + builtin_remove(e); + e = wrappee; + } + //dprint("Delayed initialization: ", e.classname, "\n"); + if (func) + { + func(e); + } + else + { + eprint(e); + backtrace(strcat("Null function in: ", e.classname, "\n")); + } + e = next; + } + delete_fn = remove_unsafely; +} + +// deferred dropping +void DropToFloor_Handler(entity this) +{ + WITHSELF(this, builtin_droptofloor()); + this.dropped_origin = this.origin; +} + +void droptofloor(entity this) +{ + InitializeEntity(this, DropToFloor_Handler, INITPRIO_DROPTOFLOOR); } bool autocvar_sv_gameplayfix_multiplethinksperframe = true; -void RunThink(entity this) +void RunThink(entity this, float dt) { // don't let things stay in the past. // it is possible to start that way by a trigger with a local time. - if(this.nextthink <= 0 || this.nextthink > time + frametime) + if(this.nextthink <= 0 || this.nextthink > time + dt) return; float oldtime = time; // do we need to save this? @@ -2059,7 +2225,7 @@ void RunThink(entity this) // we don't want to loop in that case, so exit if the new nextthink is // <= the time the qc was told, also exit if it is past the end of the // frame - if(this.nextthink <= time || this.nextthink > oldtime + frametime || !autocvar_sv_gameplayfix_multiplethinksperframe) + if(this.nextthink <= time || this.nextthink > oldtime + dt || !autocvar_sv_gameplayfix_multiplethinksperframe) break; } @@ -2089,8 +2255,8 @@ void Physics_Frame() if(it.move_movetype == MOVETYPE_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH) continue; // these movetypes have no regular think function // handle thinking here - if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + frametime) - RunThink(it); + if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + PHYS_INPUT_TIMELENGTH) + RunThink(it, PHYS_INPUT_TIMELENGTH); } }); @@ -2099,6 +2265,7 @@ void Physics_Frame() // make a second pass to see if any ents spawned this frame and make // sure they run their move/think. this is verified by checking .move_time, which will never be 0 if the entity has moved + // MOVETYPE_NONE is also checked as .move_time WILL be 0 with that movetype IL_EACH(g_moveables, it.move_qcphysics, { if(IS_CLIENT(it) || it.move_time || it.move_movetype == MOVETYPE_NONE || it.move_movetype == MOVETYPE_PHYSICS) @@ -2120,9 +2287,10 @@ void EndFrame() STAT(TYPEHIT_TIME, it) = time; } else if (e.killsound) { STAT(KILL_TIME, it) = time; - } else if (e.damage_dealt) { + } else if (e.hitsound_damage_dealt) { STAT(HIT_TIME, it) = time; - STAT(DAMAGE_DEALT_TOTAL, it) += ceil(e.damage_dealt); + // NOTE: this is not accurate as client code doesn't need so much accuracy for its purposes + STAT(HITSOUND_DAMAGE_DEALT_TOTAL, it) += ceil(e.hitsound_damage_dealt); } }); // add 1 frametime because after this, engine SV_Physics @@ -2133,7 +2301,7 @@ void EndFrame() float altime = time + frametime * (1 + autocvar_g_antilag_nudge); FOREACH_CLIENT(true, { it.typehitsound = false; - it.damage_dealt = 0; + it.hitsound_damage_dealt = 0; it.killsound = false; antilag_record(it, CS(it), altime); }); @@ -2255,6 +2423,8 @@ void Shutdown() WeaponStats_Shutdown(); MapInfo_Shutdown(); + + strfree(sv_termsofservice_url_escaped); } else if(world_initialized == 0) {