#include "sv_ctf.qh"
#include <common/effects/all.qh>
+#include <common/mapobjects/teleporters.qh>
+#include <common/mapobjects/triggers.qh>
+#include <common/mutators/mutator/powerups/_mod.qh>
#include <common/vehicles/all.qh>
+#include <server/command/vote.qh>
+#include <server/client.qh>
+#include <server/gamelog.qh>
+#include <server/intermission.qh>
+#include <server/damage.qh>
+#include <server/world.qh>
+#include <server/items/items.qh>
+#include <server/race.qh>
#include <server/teamplay.qh>
#include <lib/warpzone/common.qh>
float autocvar_g_ctf_flag_return_damage;
float autocvar_g_ctf_flag_return_damage_delay;
float autocvar_g_ctf_flag_return_dropped;
+bool autocvar_g_ctf_flag_waypoint = true;
+float autocvar_g_ctf_flag_waypoint_maxdistance;
float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
float autocvar_g_ctf_flagcarrier_auto_helpme_time;
float autocvar_g_ctf_flagcarrier_selfdamagefactor;
if(!flag) { return; }
if((droptype == DROP_PASS) && !receiver) { return; }
- if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
+ if(flag.speedrunning)
+ {
+ // ensure old waypoints are removed before resetting the flag
+ WaypointSprite_Kill(player.wps_flagcarrier);
+
+ if(player.wps_enemyflagcarrier)
+ WaypointSprite_Kill(player.wps_enemyflagcarrier);
+
+ if(player.wps_flagreturn)
+ WaypointSprite_Kill(player.wps_flagreturn);
+ ctf_RespawnFlag(flag);
+ return;
+ }
// reset the flag
setattachment(flag, NULL, "");
{
makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
- flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
+ flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((StatusEffects_active(STATUSEFFECT_Strength, player)) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
ctf_Handle_Drop(flag, player, droptype);
navigation_dynamicgoal_set(flag, player);
ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
}
+#if 0
void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
{
return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
}
+#endif
// ==============
// Event Handlers
// effects
Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
- //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
+#if 0
+ shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
+#endif
// other
if(capturetype == CAPTURE_NORMAL)
.float last_respawn;
void ctf_RespawnFlag(entity flag)
{
+ flag.watertype = CONTENT_EMPTY; // TODO: it is unclear why this workaround is needed, likely many other potential breakage points!!
// check for flag respawn being called twice in a row
if(flag.last_respawn > time - 0.5)
{ backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
default: basename = WP_FlagBaseNeutral; break;
}
- entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
- wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
- WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
- setcefc(wp, ctf_FlagBase_Customize);
+ if(autocvar_g_ctf_flag_waypoint)
+ {
+ entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
+ wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
+ wp.fade_rate = autocvar_g_ctf_flag_waypoint_maxdistance;
+ WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
+ setcefc(wp, ctf_FlagBase_Customize);
+ }
// captureshield setup
ctf_CaptureShield_Spawn(this);
.bool pushable;
-void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
+void ctf_FlagSetup(int teamnum, entity flag) // called when spawning a flag entity on the map as a spawnfunc
{
// main setup
flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
setattachment(flag, NULL, "");
- flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
- flag.team = teamnumber;
+ flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnum), Team_ColorName_Upper(teamnum)));
+ flag.team = teamnum;
flag.classname = "item_flag_team";
flag.target = "###item###"; // for finding the nearest item using findnearest
flag.flags = FL_ITEM | FL_NOTARGET;
flag.nextthink = time + FLAG_THINKRATE;
flag.ctf_status = FLAG_BASE;
+ // set correct team colors
+ flag.glowmod = Team_ColorRGB(teamnum);
+ flag.colormap = (teamnum) ? (teamnum - 1) * 0x11 : 0x00;
+ flag.colormap |= BIT(10); // RENDER_COLORMAPPED
+
// crudely force them all to 0
if(autocvar_g_ctf_score_ignore_fields)
flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
- string teamname = Static_Team_ColorName_Lower(teamnumber);
+ string teamname = Static_Team_ColorName_Lower(teamnum);
// appearence
if(!flag.scale) { flag.scale = FLAG_SCALE; }
if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
- if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
- if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
- if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
+ if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnum).eent_eff_name; }
+ if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnum).eent_eff_name; }
+ if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnum).eent_eff_name; }
// sounds
#define X(s,b) \
if(flag.s == "") flag.s = b; \
precache_sound(flag.s);
- X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
- X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
- X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
- X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
+ X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnum))))
+ X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnum))))
+ X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnum))))
+ X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnum))))
X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
X(snd_flag_pass, strzone(SND(CTF_PASS)))
if(autocvar_g_ctf_flag_glowtrails)
{
- switch(teamnumber)
+ switch(teamnum)
{
case NUM_TEAM_1: flag.glow_color = 251; break;
case NUM_TEAM_2: flag.glow_color = 210; break;
if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
if(autocvar_g_ctf_dynamiclights)
{
- switch(teamnumber)
+ switch(teamnum)
{
case NUM_TEAM_1: flag.effects |= EF_RED; break;
case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
// for symmetrical editing of waypoints
entity f1 = ctf_worldflaglist;
entity f2 = f1.ctf_worldflagnext;
- float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
+ float m = -(f1.origin.y - f2.origin.y) / (max(f1.origin.x - f2.origin.x, FLOAT_EPSILON));
float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
havocbot_symmetry_axis_m = m;
havocbot_symmetry_axis_q = q;
bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
// initially clear items so they can be set as necessary later.
- STAT(CTF_FLAGSTATUS, player) &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
+ STAT(OBJECTIVE_STATUS, player) &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
| CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
| CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
| CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
- if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; }
+ if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; STAT(OBJECTIVE_STATUS, player) |= CTF_FLAG_NEUTRAL; }
switch(flag.ctf_status)
{
case FLAG_CARRY:
{
if((flag.owner == player) || (flag.pass_sender == player))
- STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag
+ STAT(OBJECTIVE_STATUS, player) |= t; // carrying: player is currently carrying the flag
else
- STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag
+ STAT(OBJECTIVE_STATUS, player) |= t2; // taken: someone else is carrying the flag
break;
}
case FLAG_DROPPED:
{
- STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
+ STAT(OBJECTIVE_STATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
break;
}
}
// item for stopping players from capturing the flag too often
if(player.ctf_captureshielded)
- STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED;
+ STAT(OBJECTIVE_STATUS, player) |= CTF_SHIELDED;
if(ctf_stalemate)
- STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE;
+ STAT(OBJECTIVE_STATUS, player) |= CTF_STALEMATE;
// update the health of the flag carrier waypointsprite
if(player.wps_flagcarrier)
WaypointSprite_UpdateHealth(player.wps_flagcarrier, healtharmor_maxdamage(GetResource(player, RES_HEALTH), GetResource(player, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
}
-MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in damage.qc
{
entity frag_attacker = M_ARGV(1, entity);
entity frag_target = M_ARGV(2, entity);
entity player = M_ARGV(0, entity);
- if(IS_REAL_CLIENT(player))
- {
- int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
- race_send_rankings_cnt(MSG_ONE);
- for (int i = 1; i <= m; ++i)
- {
- race_SendRankings(i, 0, 0, MSG_ONE);
- }
- }
+ race_SendAll(player, true);
}
MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
entity player = M_ARGV(0, entity);
- if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
- {
- if (!player.stored_netname)
- player.stored_netname = strzone(uid2name(player.crypto_idfp));
- if(player.stored_netname != player.netname)
- {
- db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
- strcpy(player.stored_netname, player.netname);
- }
- }
+ race_checkAndWriteName(player);
}
MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
if(head != player && SAME_TEAM(head, player))
if(!head.speedrunning && !head.vehicle)
{
- // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
+ // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in damage.qc)
vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
vector passer_center = CENTER_OR_VIEWOFS(player);
M_ARGV(1, string) = "ctf_team";
}
-MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
-{
- entity spectatee = M_ARGV(0, entity);
- entity client = M_ARGV(1, entity);
-
- STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee);
-}
-
MUTATOR_HOOKFUNCTION(ctf, GetRecords)
{
int record_page = M_ARGV(0, int);
{
if(!g_ctf) { delete(this); return; }
- this.classname = "ctf_team";
this.team = this.cnt + 1;
}
void ctf_Initialize()
{
+ CTF_FLAG = NEW(Flag);
+ record_type = CTF_RECORD;
ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;