alias cl_hook_gamestart_cts
alias cl_hook_gamestart_ka
alias cl_hook_gamestart_ft
+alias cl_hook_gamestart_inf
alias cl_hook_gamestart_inv
alias cl_hook_gameend "rpn /cl_matchcount dup load 1 + =" // increase match count every time a game ends
alias cl_hook_activeweapon
alias sv_hook_gamestart_cts
alias sv_hook_gamestart_ka
alias sv_hook_gamestart_ft
+alias sv_hook_gamestart_inf
alias sv_hook_gamestart_inv
alias sv_hook_gamerestart
alias sv_hook_gameend
alias sv_vote_gametype_hook_dm
alias sv_vote_gametype_hook_dom
alias sv_vote_gametype_hook_ft
+alias sv_vote_gametype_hook_inf
alias sv_vote_gametype_hook_inv
alias sv_vote_gametype_hook_ka
alias sv_vote_gametype_hook_kh
set g_ft_respawn_delay_max 0
set g_ft_respawn_waves 0
set g_ft_weapon_stay 0
+set g_inf_respawn_delay_small 0
+set g_inf_respawn_delay_small_count 0
+set g_inf_respawn_delay_large 0
+set g_inf_respawn_delay_large_count 0
+set g_inf_respawn_delay_max 0
+set g_inf_respawn_waves 0
+set g_inf_weapon_stay 0
set g_inv_respawn_delay_small 0
set g_inv_respawn_delay_small_count 0
set g_inv_respawn_delay_large 0
// capture the flag
// ==================
set g_ctf 0 "Capture The Flag: take the enemy flag and bring it to yours at your base to score"
- set g_ctf_oneflag 1 "Allow oneflag CTF mode on maps that support it"
+ set g_ctf_oneflag 0 "Allow oneflag CTF mode on maps that support it"
set g_ctf_oneflag_reverse 0 "apply reverse mode to oneflag CTF (take flag to enemy bases to cap), overrides g_ctf_reverse only in oneflag, g_ctf_reverse still affects oneflag"
set g_ctf_flag_return 1 "auto return the flag to base when touched by a teammate"
set g_ctf_flag_return_carrying 0 "(manual return mode) auto return the flag to base if touched by a flag carrier"
set g_domination_point_amt 0 "override: how many points to get per ping"
set g_domination_point_fullbright 0 "domination point fullbright"
set g_domination_point_rate 0 "override: how often to give those points"
- set g_domination_point_capturetime 0.1 "how long it takes to capture a point (given no interference)"
+ //set g_domination_point_capturetime 0.1 "how long it takes to capture a point (given no interference)"
set g_domination_point_glow 0 "domination point glow (warning, slow)"
set g_domination_roundbased 0 "enable round-based domination (capture all control points to win the round)"
set g_domination_roundbased_point_limit 5 "capture limit in round-based domination mode"
set g_onslaught_point_limit 1 "Onslaught point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
set g_onslaught_warmup 5
set g_onslaught_round_timelimit 280
- set g_onslaught_debug 0 "show debug prints in onslaught"
set g_onslaught_teleport_radius 200 "Allows teleporting from a control point to another"
set g_onslaught_teleport_wait 5 "Time before player can teleport again"
set g_onslaught_spawn_choose 1 "Allow players to choose the control point to be spawned at"
set g_race_qualifying_timelimit_override -1
set g_race_teams 0 "when 2, 3, or 4, the race is played as a team game (the team members can add up their laps)"
+
+// ===========
+// infection
+// ===========
+set g_infection 0 "Infection: Infect everyone with your color"
+set g_infection_round_timelimit 180 "round time limit in seconds"
+set g_infection_warmup 5 "Time players get to run around before the round starts"
+set g_infection_teams 4 "Maximum number of teams (from 2 to 15 inclusive)"
+set g_infection_conversions 1 "Enable stealing when killing an alpha"
+
+
// ==========
// invasion
// ==========
float f; // every function should have that
int redflag, blueflag, yellowflag, pinkflag, neutralflag; // current status
- float redflag_statuschange_elapsedtime, blueflag_statuschange_elapsedtime, yellowflag_statuschange_elapsedtime, pinkflag_statuschange_elapsedtime, neutralflag_statuschange_elapsedtime; // time since the status changed
+ float redflag_statuschange_elapsedtime = 0, blueflag_statuschange_elapsedtime = 0, yellowflag_statuschange_elapsedtime = 0, pinkflag_statuschange_elapsedtime = 0, neutralflag_statuschange_elapsedtime = 0; // time since the status changed
bool ctf_oneflag; // one-flag CTF mode enabled/disabled
int stat_items = STAT(CTF_FLAGSTATUS);
float fs, fs2, fs3, size1, size2;
}
// when status CHANGES, set old status into prevstatus and current status into status
- #define X(team) do { \
+ #define X(team) MACRO_BEGIN { \
if (team##flag != team##flag_prevframe) { \
team##flag_statuschange_time = time; \
team##flag_prevstatus = team##flag_prevframe; \
team##flag_prevframe = team##flag; \
} \
team##flag_statuschange_elapsedtime = time - team##flag_statuschange_time; \
- } while (0)
+ } MACRO_END
X(red);
X(blue);
X(yellow);
const float BLINK_FREQ = 5; // circle frequency, = 2*pi*frequency in hertz
#define X(team, cond) \
- string team##_icon, team##_icon_prevstatus; \
+ string team##_icon = string_null, team##_icon_prevstatus = string_null; \
int team##_alpha, team##_alpha_prevstatus; \
team##_alpha = team##_alpha_prevstatus = 1; \
- do { \
+ MACRO_BEGIN { \
switch (team##flag) { \
case 1: team##_icon = "flag_" #team "_taken"; break; \
case 2: team##_icon = "flag_" #team "_lost"; break; \
} \
break; \
} \
- } while (0)
+ } MACRO_END
X(red, myteam != NUM_TEAM_1);
X(blue, myteam != NUM_TEAM_2);
X(yellow, myteam != NUM_TEAM_3);
neutralflag_pos = pos;
flag_size = e1 * fs * size1 + e2 * size2;
- #define X(team) do { \
+ #define X(team) MACRO_BEGIN { \
f = bound(0, team##flag_statuschange_elapsedtime * 2, 1); \
if (team##_icon_prevstatus && f < 1) \
drawpic_aspect_skin_expanding(team##flag_pos, team##_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * team##_alpha_prevstatus, DRAWFLAG_NORMAL, f); \
if (team##_icon) \
drawpic_aspect_skin(team##flag_pos, team##_icon, flag_size, '1 1 1', panel_fg_alpha * team##_alpha * f, DRAWFLAG_NORMAL); \
- } while (0)
+ } MACRO_END
X(red);
X(blue);
X(yellow);
drawpic_aspect_skin(KH_SLOTS[i++], "kh_dropped", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
}
+void HUD_Mod_Infection(vector pos, vector mySize)
+{
+ mod_active = 1; // Infection should always show the mod HUD
+ int f = stof(getplayerkeyvalue(player_localentnum - 1, "colors"));
+ vector color = colormapPaletteColor(floor(f / 16), 0);
+ drawpic_aspect_skin(pos, "player_neutral", mySize, color, panel_fg_alpha, DRAWFLAG_NORMAL);
+}
+
// Keepaway HUD mod icon
int kaball_prevstatus; // last remembered status
float kaball_statuschange_time; // time when the status changed
float BLINK_FREQ = 5;
float kaball_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ);
- int stat_items = getstati(STAT_ITEMS, 0, 24);
+ int stat_items = STAT(ITEMS);
int kaball = (stat_items/IT_KEY1) & 1;
if(kaball != kaball_prevstatus)
float nb_pb_starttime, dt, p;
int stat_items;
- stat_items = getstati(STAT_ITEMS, 0, 24);
+ stat_items = STAT(ITEMS);
nb_pb_starttime = STAT(NB_METERSTART);
if (stat_items & IT_KEY1)
mod_active = 1; // race should never hide the mod icons panel
entity me;
me = playerslots[player_localnum];
- float t, score;
+ float score;
float f; // yet another function has this
score = me.(scores[ps_primary]);
rr = CTS_RECORD;
else
rr = RACE_RECORD;
- t = stof(db_get(ClientProgsDB, strcat(shortmapname, rr, "time")));
+ float t = stof(db_get(ClientProgsDB, strcat(shortmapname, rr, "time")));
if(score && (score < t || !t)) {
db_put(ClientProgsDB, strcat(shortmapname, rr, "time"), ftos(score));
case MAPINFO_TYPE_FREEZETAG: HUD_ModIcons_GameType = HUD_Mod_CA; break;
case MAPINFO_TYPE_DOMINATION: HUD_ModIcons_GameType = HUD_Mod_Dom; break;
case MAPINFO_TYPE_KEEPAWAY: HUD_ModIcons_GameType = HUD_Mod_Keepaway; break;
+ case MAPINFO_TYPE_INFECTION: HUD_ModIcons_GameType = HUD_Mod_Infection; break;
}
}
#if defined(CSQC)
#include "../client/defs.qh"
#include "util.qh"
- #include "weapons/all.qh"
+ #include <common/weapons/all.qh>
#include "mapinfo.qh"
#elif defined(MENUQC)
#elif defined(SVQC)
#include "util.qh"
- #include "monsters/all.qh"
+ #include <common/monsters/all.qh>
#include "mapinfo.qh"
#endif
}
if(inWorldspawn)
{
- LOG_INFO(fn, " ended still in worldspawn, BUG\n");
+ LOG_MAPWARN(fn, " ended still in worldspawn, BUG\n");
return 0;
}
diameter = vlen(mapMaxs - mapMins);
cvar_set("fraglimit", sa);
s = cdr(s);
}
+
+ if (pWantedType == MAPINFO_TYPE_INFECTION)
+ {
+ sa = car(s);
+ if (sa != "") cvar_set("fraglimit", sa);
+ s = cdr(s);
+ }
/* keepaway wuz here
if(pWantedType == MAPINFO_TYPE_KEEPAWAY)
if (sa == "") continue;
int p = strstrofs(sa, "=", 0);
if (p < 0) {
- LOG_WARNINGF("Invalid gametype setting in mapinfo for gametype %s: %s\n", MapInfo_Type_ToString(pWantedType), sa);
+ LOG_MAPWARNF("Invalid gametype setting in mapinfo for gametype %s: %s\n", MapInfo_Type_ToString(pWantedType), sa);
continue;
}
string k = substring(sa, 0, p);
}
FOREACH(Gametypes, true, LAMBDA(handled |= it.m_parse_mapinfo(k, v)));
if (!handled)
- LOG_WARNINGF("Invalid gametype setting in mapinfo for gametype %s: %s\n", MapInfo_Type_ToString(pWantedType), sa);
+ LOG_MAPWARNF("Invalid gametype setting in mapinfo for gametype %s: %s\n", MapInfo_Type_ToString(pWantedType), sa);
}
if (pWantedType == MAPINFO_TYPE_RACE && cvar("g_race_teams") >= 2)
int MapInfo_Type_FromString(string t)
{
- #define deprecate(from, to) do { \
+ #define deprecate(from, to) MACRO_BEGIN { \
if (t == #from) { \
string replacement = #to; \
- LOG_WARNINGF("MapInfo_Type_FromString (probably %s): using deprecated name '%s'. Should use '%s'.\n", MapInfo_Map_bspname, t, replacement); \
+ LOG_MAPWARNF("MapInfo_Type_FromString (probably %s): using deprecated name '%s'. Should use '%s'.\n", MapInfo_Map_bspname, t, replacement); \
t = replacement; \
} \
- } while (0)
+ } MACRO_END
deprecate(nexball, nb);
deprecate(freezetag, ft);
deprecate(keepaway, ka);
{
fh = fopen(s, FILE_READ);
if(fh < 0)
- LOG_INFO("Map ", pFilename, " references not existing config file ", s, "\n");
+ LOG_MAPWARN("Map ", pFilename, " references not existing config file ", s, "\n");
else
{
for (;;)
}
}
else
- LOG_INFO("Map ", pFilename, " uses too many levels of inclusion\n");
+ LOG_MAPWARN("Map ", pFilename, " uses too many levels of inclusion\n");
}
else if(t == "")
- LOG_INFO("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
else if (!cvar_value_issafe(t))
- LOG_INFO("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
else if (!cvar_value_issafe(s))
- LOG_INFO("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
else if(matchacl(MAPINFO_SETTEMP_ACL_SYSTEM, t) <= 0)
- LOG_INFO("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " contains a potentially harmful setting, ignored\n");
else if(matchacl(acl, t) <= 0)
- LOG_INFO("Map ", pFilename, " contains a denied setting, ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " contains a denied setting, ignored\n");
else
{
if(type == 0) // server set
}
// load info about a map by name into the MapInfo_Map_* globals
-float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, int pGametypeToSet)
+float MapInfo_Get_ByName_NoFallbacks(string pFilename, bool pAllowGenerate, int pGametypeToSet)
{
string fn;
string s, t;
if(strstrofs(pFilename, "/", 0) >= 0)
{
- LOG_INFO("Invalid character in map name, ignored\n");
+ LOG_MAPWARN("Invalid character in map name, ignored\n");
return 0;
}
error("... but I just wrote it!");
}
- LOG_INFO("WARNING: autogenerated mapinfo file ", fn, " has been loaded; please edit that file and move it to maps/", pFilename, ".mapinfo\n");
+ LOG_MAPWARN("autogenerated mapinfo file ", fn, " has been loaded; please edit that file and move it to maps/", pFilename, ".mapinfo\n");
}
_MapInfo_Map_Reset();
else if(t == "monsters") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
else if(t == "new_toys") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
else
- LOG_TRACE("Map ", pFilename, " supports unknown feature ", t, ", ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " supports unknown feature ", t, ", ignored\n");
}
else if(t == "hidden")
{
{
t = car(s); s = cdr(s);
f = MapInfo_Type_FromString(t);
- LOG_WARNING("Map ", pFilename, " contains the legacy 'type' keyword which is deprecated and will be removed in the future. Please migrate the mapinfo file to 'gametype'.\n");
+ LOG_MAPWARN("Map ", pFilename, " contains the legacy 'type' keyword which is deprecated and will be removed in the future. Please migrate the mapinfo file to 'gametype'.\n");
if(f)
_MapInfo_Map_ApplyGametype (s, pGametypeToSet, f, true);
else
- LOG_TRACE("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
}
else if(t == "gametype")
{
if(f)
_MapInfo_Map_ApplyGametypeEx (s, pGametypeToSet, f);
else
- LOG_TRACE("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
}
else if(t == "size")
{
t = car(s); s = cdr(s); d = stof(t);
t = car(s); s = cdr(s); e = stof(t);
if(s == "")
- LOG_INFO("Map ", pFilename, " contains an incorrect size line (not enough params), syntax: size mins_x mins_y mins_z maxs_x maxs_y maxs_z\n");
+ LOG_MAPWARN("Map ", pFilename, " contains an incorrect size line (not enough params), syntax: size mins_x mins_y mins_z maxs_x maxs_y maxs_z\n");
else
{
t = car(s); s = cdr(s); f = stof(t);
if(s != "")
- LOG_INFO("Map ", pFilename, " contains an incorrect size line (too many params), syntax: size mins_x mins_y mins_z maxs_x maxs_y maxs_z\n");
+ LOG_MAPWARN("Map ", pFilename, " contains an incorrect size line (too many params), syntax: size mins_x mins_y mins_z maxs_x maxs_y maxs_z\n");
else
{
if(a >= d || b >= e || c >= f)
- LOG_INFO("Map ", pFilename, " contains an incorrect size line, mins have to be < maxs\n");
+ LOG_MAPWARN("Map ", pFilename, " contains an incorrect size line, mins have to be < maxs\n");
else
{
MapInfo_Map_mins.x = a;
}
else
{
- LOG_TRACE("Map ", pFilename, " has a setting for unknown game type ", t, ", ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " has a setting for unknown game type ", t, ", ignored\n");
}
}
else if(t == "clientsettemp_for_type")
}
else
{
- LOG_TRACE("Map ", pFilename, " has a client setting for unknown game type ", t, ", ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " has a client setting for unknown game type ", t, ", ignored\n");
}
}
else if(t == "fog")
{
if (!cvar_value_issafe(s))
- LOG_INFO("Map ", pFilename, " contains a potentially harmful fog setting, ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " contains a potentially harmful fog setting, ignored\n");
else
MapInfo_Map_fog = s;
}
if(pGametypeToSet)
{
if (!cvar_value_issafe(t))
- LOG_INFO("Map ", pFilename, " contains a potentially harmful cdtrack, ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " contains a potentially harmful cdtrack, ignored\n");
else
MapInfo_Map_clientstuff = strcat(
MapInfo_Map_clientstuff, "cd loop \"", t, "\"\n"
}
}
else
- LOG_TRACE("Map ", pFilename, " provides unknown info item ", t, ", ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " provides unknown info item ", t, ", ignored\n");
}
fclose(fh);
MapInfo_Cache_Store();
if(MapInfo_Map_supportedGametypes != 0)
return r;
- LOG_TRACE("Map ", pFilename, " supports no game types, ignored\n");
+ LOG_MAPWARN("Map ", pFilename, " supports no game types, ignored\n");
return 0;
}
-float MapInfo_Get_ByName(string pFilename, float pAllowGenerate, int pGametypeToSet)
+float MapInfo_Get_ByName(string pFilename, bool pAllowGenerate, int pGametypeToSet)
{
float r = MapInfo_Get_ByName_NoFallbacks(pFilename, pAllowGenerate, pGametypeToSet);
if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH)
_MapInfo_Map_ApplyGametypeEx ("", pGametypeToSet, MAPINFO_TYPE_TEAM_DEATHMATCH);
}
+ _MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, MAPINFO_TYPE_INFECTION); // infection always works
if(pGametypeToSet)
{
return MAPINFO_TYPE_DEATHMATCH;
}
-float _MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise
+bool _MapInfo_CheckMap(string s) // returns false if the map can't be played with the current settings, true otherwise
{
- if(!MapInfo_Get_ByName(s, 1, 0))
- return 0;
+ if(!MapInfo_Get_ByName(s, true, 0))
+ return false;
if((MapInfo_Map_supportedGametypes & MapInfo_CurrentGametype()) == 0)
- return 0;
+ return false;
if((MapInfo_Map_supportedFeatures & MapInfo_CurrentFeatures()) != MapInfo_CurrentFeatures())
- return 0;
- return 1;
+ return false;
+ return true;
}
float MapInfo_CheckMap(string s) // returns 0 if the map can't be played with the current settings, 1 otherwise
void MapInfo_LoadMapSettings(string s) // to be called from worldspawn
{
- float t;
-
- t = MapInfo_CurrentGametype();
+ int t = MapInfo_CurrentGametype();
MapInfo_LoadMapSettings_SaveGameType(t);
if(!_MapInfo_CheckMap(s)) // with underscore, it keeps temps
{
if(cvar("g_mapinfo_allow_unsupported_modes_and_let_stuff_break"))
{
- LOG_INFO("EMERGENCY: can't play the selected map in the given game mode. Working with only the override settings.\n");
+ LOG_SEVERE("can't play the selected map in the given game mode. Working with only the override settings.\n");
_MapInfo_Map_ApplyGametypeEx("", t, t);
return; // do not call Get_ByName!
}
if(MapInfo_Map_supportedGametypes == 0)
{
- LOG_INFO("Mapinfo system is not functional at all. Assuming deathmatch.\n");
+ LOG_SEVERE("Mapinfo system is not functional at all. Assuming deathmatch.\n");
MapInfo_Map_supportedGametypes = MAPINFO_TYPE_DEATHMATCH;
MapInfo_LoadMapSettings_SaveGameType(MAPINFO_TYPE_DEATHMATCH);
_MapInfo_Map_ApplyGametypeEx("", MAPINFO_TYPE_DEATHMATCH, MAPINFO_TYPE_DEATHMATCH);
}
// t is now a supported mode!
- LOG_INFO("EMERGENCY: can't play the selected map in the given game mode. Falling back to a supported mode.\n");
+ LOG_WARNING("can't play the selected map in the given game mode. Falling back to a supported mode.\n");
MapInfo_LoadMapSettings_SaveGameType(t);
}
MapInfo_Get_ByName(s, 1, t);
#ifndef MAPINFO_H
#define MAPINFO_H
+ bool autocvar_developer_mapper;
+
+ #define LOG_MAPWARN(...) MACRO_BEGIN { if (autocvar_developer_mapper) LOG_WARNING(__VA_ARGS__); } MACRO_END
+ #define LOG_MAPWARNF(...) MACRO_BEGIN { if (autocvar_developer_mapper) LOG_WARNINGF(__VA_ARGS__); } MACRO_END
+
#include "util.qh"
CLASS(Gametype, Object)
}
ENDCLASS(Gametype)
-REGISTRY(Gametypes, BITS(4))
+REGISTRY(Gametypes, BITS(5))
#define Gametypes_from(i) _Gametypes_from(i, NULL)
REGISTER_REGISTRY(Gametypes)
REGISTRY_CHECK(Gametypes)
}
}
+REGISTER_GAMETYPE(_("Infection"),inf,g_infection,INFECTION,true,"","timelimit=20",_("Survive against the infection"));
+
const int MAPINFO_FEATURE_WEAPONS = 1; // not defined for instagib-only maps
const int MAPINFO_FEATURE_VEHICLES = 2;
const int MAPINFO_FEATURE_TURRETS = 4;
#include "../item.qc"
#include "../menu.qh"
- #include "../../common/campaign_common.qh"
- #include "../../common/constants.qh"
- #include "../../common/mapinfo.qh"
- #include "../../common/util.qh"
- #include "../../common/command/generic.qh"
+ #include <common/campaign_common.qh>
+ #include <common/constants.qh>
+ #include <common/mapinfo.qh>
+ #include <common/util.qh>
+ #include <common/command/generic.qh>
float GL_CheckExtension(string ext)
{
GAMETYPE(MAPINFO_TYPE_CTF) \
GAMETYPE(MAPINFO_TYPE_CA) \
GAMETYPE(MAPINFO_TYPE_FREEZETAG) \
+ GAMETYPE(MAPINFO_TYPE_INFECTION) \
GAMETYPE(MAPINFO_TYPE_KEEPAWAY) \
GAMETYPE(MAPINFO_TYPE_KEYHUNT) \
GAMETYPE(MAPINFO_TYPE_LMS) \
--- /dev/null
- entity e;
- FOR_EACH_PLAYER(e)
- {
- if (me.infectioncolor == e.infectioncolor_original)
- {
- color_owner_green = sprintf("%s^2's", e.netname);
- color_owner_red = sprintf("%s^1's", e.netname);
- break;
- }
- }
+#ifdef IMPLEMENTATION
+
+float autocvar_g_infection_round_timelimit;
+float autocvar_g_infection_warmup;
+int autocvar_g_infection_teams;
+bool autocvar_g_infection_conversions;
+
+int infection_players_count;
+
+.int infectioncolor;
+.int infectioncolor_original;
+
+const int INFECTIONTEAM_NONE = -1;
+const int INFECTIONTEAM_UNDEFINED = -2;
+
+// safe team comparisons
+#define INF_SAMETEAM(a, b) (a.infectioncolor == b.infectioncolor)
+#define INF_DIFFTEAM(a, b) (a.infectioncolor != b.infectioncolor)
+
+void infection_SetColor(entity e, int _color)
+{
+ e.infectioncolor = _color;
+ setcolor(e, (_color << 4) | _color);
+}
+
+string color_owner_green, color_owner_red;
+// find whose color a player is carrying, true if it's his own, otherwise set color_owner_* to the other owner
+void infection_GetColorOwner(entity me)
+{
+ if (me.infectioncolor == me.infectioncolor_original)
+ {
+ color_owner_green = "^2your own";
+ color_owner_red = "^1their own";
+ return;
+ }
- entity e;
- FOR_EACH_PLAYER(e)
- {
++ FOREACH_CLIENT(IS_PLAYER(it) && me.infectioncolor == it.infectioncolor_original, {
++ color_owner_green = sprintf("%s^2's", it.netname);
++ color_owner_red = sprintf("%s^1's", it.netname);
++ break;
++ });
+}
+
+void infection_Assign(bool late)
+{
++ SELFPARAM();
+ if (!late)
+ {
+ int infection_coloridx = 0;
- e.infectioncolor_original = infection_coloridx;
- infection_SetColor(e, infection_coloridx++ % bound(0, autocvar_g_infection_teams, 15));
- }
++ FOREACH_CLIENT(IS_PLAYER(it), {
+ if (infection_coloridx < autocvar_g_infection_teams) // Limit alphas
- entity e;
- FOR_EACH_PLAYER(e)
- {
- if (e == self || IS_OBSERVER(e)) continue;
- if (!skip-- > 0) break;
- }
- dprintf("[INFECTION] copying %s's color\n", e.netname);
++ it.infectioncolor_original = infection_coloridx;
++ infection_SetColor(it, infection_coloridx++ % bound(0, autocvar_g_infection_teams, 15));
++ });
+ }
+ else
+ {
+ // Spawn too late, give player a random color
+ int color = 15;
+ int skip = rint(random() * (infection_players_count - 1)); // Ignore self
- self.infectioncolor_original = INFECTIONTEAM_NONE; // Can't win if player didn't spawn during the round delay
- infection_SetColor(self, color);
++ entity e = NULL;
++ FOREACH_CLIENT(IS_PLAYER(it), {
++ if (it == this || IS_OBSERVER(it)) continue;
++ if (!skip-- > 0) {
++ e = it;
++ break;
++ }
++ });
++ LOG_DEBUGF("[INFECTION] copying %s's color", e.netname);
+ color = e.infectioncolor;
- entity e;
- FOR_EACH_PLAYER(e)
- {
++ this.infectioncolor_original = INFECTIONTEAM_NONE; // Can't win if player didn't spawn during the round delay
++ infection_SetColor(this, color);
+ }
+}
+
+bool infection_CheckTeams()
+{
+ return infection_players_count > 1;
+}
+
+bool infection_CheckWinner()
+{
+ if (infection_players_count <= 1) return false; // There can be no winner
+
+ if (0 < round_handler_GetEndTime() && round_handler_GetEndTime() <= time) // Round over
+ {
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
+ round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
+ return true;
+ }
+
+ // Check if only one color remains
+ int previnfectioncolor = -1;
+ bool we_have_a_winner = true; // until loop below proves us wrong
- if (previnfectioncolor != -1 && previnfectioncolor != e.infectioncolor)
++ FOREACH_CLIENT(IS_PLAYER(it), {
+ // All infection colors are the same if we have a winner
- previnfectioncolor = e.infectioncolor;
- }
++ if (previnfectioncolor != -1 && previnfectioncolor != it.infectioncolor)
+ {
+ // In this case we still have more than one color alive
+ we_have_a_winner = false;
+ break;
+ }
- FOR_EACH_PLAYER(e)
- {
- if (e.infectioncolor == e.infectioncolor_original)
- {
- UpdateFrags(e, 10); // Bonus points
- Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, e.netname);
- Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, e.netname);
- }
- }
++ previnfectioncolor = it.infectioncolor;
++ });
+
+ if (!we_have_a_winner) return false;
+
+ // Who is it?
- if (!IS_PLAYER(self)) return true; // Wasn't playing
++ FOREACH_CLIENT(IS_PLAYER(it) && it.infectioncolor == it.infectioncolor_original, {
++ UpdateFrags(it, 10); // Bonus points
++ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, it.netname);
++ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, it.netname);
++ });
+
+ round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
+ return true;
+}
+
+void infection_RoundStart()
+{
+ infection_Assign(false);
+ infection_CheckWinner();
+}
+
+MUTATOR_HOOKFUNCTION(inf, PlayerDies)
+{
+ infection_CheckWinner();
+ return true;
+}
+
+bool inf_RemovePlayer();
+MUTATOR_HOOKFUNCTION(inf, MakePlayerObserver)
+{
+ return inf_RemovePlayer();
+}
+MUTATOR_HOOKFUNCTION(inf, ClientDisconnect)
+{
+ return inf_RemovePlayer();
+}
+bool inf_RemovePlayer()
+{
- self.infectioncolor_original = INFECTIONTEAM_UNDEFINED;
++ SELFPARAM();
++ if (!IS_PLAYER(this)) return true; // Wasn't playing
+
+ infection_players_count--;
+
- entity e;
- FOR_EACH_PLAYER(e)
- {
- if (e.infectioncolor == self.infectioncolor_original)
- {
- e.infectioncolor_original = self.infectioncolor;
- centerprint(e, "^2You are now an alpha.\n");
- break;
- }
- }
++ this.infectioncolor_original = INFECTIONTEAM_UNDEFINED;
+
+ // Grant alpha status to next of kin
- if (self.infectioncolor_original != INFECTIONTEAM_UNDEFINED) return true; // Wasn't observing
++ FOREACH_CLIENT(IS_PLAYER(it) && it.infectioncolor == this.infectioncolor_original, {
++ it.infectioncolor_original = this.infectioncolor;
++ centerprint(it, "^2You are now an alpha.\n");
++ break;
++ });
+
+ infection_CheckWinner();
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inf, PlayerSpawn)
+{
- entity e;
- FOR_EACH_PLAYER(e) // check other players...
- {
- if (INF_SAMETEAM(e, frag_target)) // And see if they have our original infection color
- { // If so, remove it, our infection color has now "died out" from this round and we can not win anymore.
- // The attacker will "summon" all of our previously fragged targets, and also us.
- centerprint(e, sprintf("^1Your alpha ^7%s^1 was infected by ^7%s^1 with ^7%s^1 color.\n",
- frag_target.netname, frag_attacker.netname, color_owner_red));
- infection_SetColor(e, frag_attacker.infectioncolor);
- frag_score++;
- }
- }
++ SELFPARAM();
++ if (this.infectioncolor_original != INFECTIONTEAM_UNDEFINED) return true; // Wasn't observing
+ infection_players_count++;
+
+ infection_Assign(true);
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inf, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+ frag_score = 0;
+ infection_GetColorOwner(frag_attacker);
+ // If this is the first time we die... (our infectioncolor remained unchanged)
+ if (autocvar_g_infection_conversions && frag_target.infectioncolor == frag_target.infectioncolor_original)
+ {
- if (IS_PLAYER(self))
++ // check other players and see if they have our original infection color
++ FOREACH_CLIENT(IS_PLAYER(it) && INF_SAMETEAM(it, frag_target), {
++ // If so, remove it, our infection color has now "died out" from this round and we can not win anymore.
++ // The attacker will "summon" all of our previously fragged targets, and also us.
++ centerprint(it, sprintf("^1Your alpha ^7%s^1 was infected by ^7%s^1 with ^7%s^1 color.\n",
++ frag_target.netname, frag_attacker.netname, color_owner_red));
++ infection_SetColor(it, frag_attacker.infectioncolor);
++ frag_score++;
++ });
+ }
+ else
+ {
+ infection_SetColor(frag_target, frag_attacker.infectioncolor);
+ frag_score++;
+ }
+
+ string target = frag_target.netname, attacker = frag_attacker.netname;
+
+ centerprint(frag_attacker, sprintf("^2You infected ^7%s^2 with ^7%s^2 color.\n", target, color_owner_green));
+ centerprint(frag_target, sprintf("^1You were infected by ^7%s^1 with ^7%s^1 color.\n", attacker, color_owner_red));
+
+ bprint(sprintf("^7%s^1 was infected by ^7%s^1 with ^7%s^1 color.\n", frag_target.netname, attacker,
+ color_owner_red));
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inf, PlayerPreThink, CBC_ORDER_FIRST)
+{
- infection_SetColor(self, round_handler_IsRoundStarted()
- ? self.infectioncolor : 15
- );
++ SELFPARAM();
++ if (IS_PLAYER(this))
+ {
+ // Prevent cheating by changing player colors
- return INF_SAMETEAM(checkentity, self);
++ infection_SetColor(this, round_handler_IsRoundStarted()
++ ? this.infectioncolor
++ : 15);
+ }
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inf, PlayerDamage_Calculate)
+{
+ if (IS_PLAYER(frag_attacker) // Allow environment damage
+ && frag_attacker != frag_target // Allow self damage
+ && INF_SAMETEAM(frag_attacker, frag_target) // Block friendly fire
+ )
+ {
+ frag_damage = 0;
+ frag_force = '0 0 0';
+ }
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(inf, BotShouldAttack)
+{
- self.infectioncolor_original = INFECTIONTEAM_UNDEFINED;
- stuffcmd(self, "settemp cl_forceplayercolors 0\n");
++ SELFPARAM();
++ return INF_SAMETEAM(checkentity, this);
+}
+
+MUTATOR_HOOKFUNCTION(inf, ClientConnect)
+{
++ SELFPARAM();
++ this.infectioncolor_original = INFECTIONTEAM_UNDEFINED;
++ stuffcmd(this, "settemp cl_forceplayercolors 0\n");
+
+ return false;
+}
+
+REGISTER_MUTATOR(inf, false)
+{
+ MUTATOR_ONADD
+ {
+ if (time > 1) // game loads at time 1
+ error("This is a game type and it cannot be added at runtime.");
+ infection_players_count = 0;
+ round_handler_Spawn(infection_CheckTeams, infection_CheckWinner, infection_RoundStart);
+ round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
+ }
+
+ MUTATOR_ONROLLBACK_OR_REMOVE
+ {
+ // we actually cannot roll back inf_Initialize here
+ // BUT: we don't need to! If this gets called, adding always
+ // succeeds.
+ }
+
+ MUTATOR_ONREMOVE
+ {
+ print("This is a game type and it cannot be removed at runtime.");
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif