X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=blobdiff_plain;f=qcsrc%2Fserver%2Fmutators%2Fgamemode_ctf.qc;h=a42f2919a48433ded4a83f31e2b80e85e4a58b0d;hp=c9c9b4584b14af089b437c5b81f4c26e105dae95;hb=df24112faf15050091bd940e7ed0c625b3c76c62;hpb=7b15197339e0263e56a6684f8a1b3890ba6d367b diff --git a/qcsrc/server/mutators/gamemode_ctf.qc b/qcsrc/server/mutators/gamemode_ctf.qc index c9c9b4584..a42f2919a 100644 --- a/qcsrc/server/mutators/gamemode_ctf.qc +++ b/qcsrc/server/mutators/gamemode_ctf.qc @@ -14,10 +14,11 @@ void ctf_FakeTimeLimit(entity e, float t) WriteCoord(MSG_ONE, (t + 1) / 60); } -void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later +void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later { if(autocvar_sv_eventlog) - GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); + GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : ""))); + //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); } void ctf_CaptureRecord(entity flag, entity player) @@ -27,11 +28,13 @@ void ctf_CaptureRecord(entity flag, entity player) string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname")); // notify about shit - if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); } - else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } - else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } + if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); } + else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); } + else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } + else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } // write that shit in the database + if(!ctf_oneflag) // but not in 1-flag mode if((!ctf_captimerecord) || (cap_time < cap_record)) { ctf_captimerecord = cap_time; @@ -43,7 +46,7 @@ void ctf_CaptureRecord(entity flag, entity player) void ctf_FlagcarrierWaypoints(entity player) { - WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team)); + WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team)); WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2); WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)); WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team)); @@ -51,7 +54,7 @@ void ctf_FlagcarrierWaypoints(entity player) void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate) { - float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis + float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc))); float current_height = (initial_height * min(1, (current_distance / flag.pass_distance))); //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n"); @@ -80,7 +83,7 @@ void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnra else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); } } -float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer) +bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer) { if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min) { @@ -89,13 +92,11 @@ float ctf_CheckPassDirection(vector head_center, vector passer_center, vector pa makevectors(passer_angle); // find the closest point on the enemy to the center of the attack - float ang; // angle between shotdir and h float h; // hypotenuse, which is the distance between attacker to head float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin h = vlen(head_center - passer_center); - ang = acos(dotproduct(normalize(head_center - passer_center), v_forward)); - a = h * cos(ang); + a = h * (normalize(head_center - passer_center) * v_forward); vector nearest_on_line = (passer_center + a * v_forward); float distance_from_line = vlen(nearest_to_passer - nearest_on_line); @@ -104,11 +105,11 @@ float ctf_CheckPassDirection(vector head_center, vector passer_center, vector pa spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit); if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90)) - { return TRUE; } + { return true; } else - { return FALSE; } + { return false; } } - else { return TRUE; } + else { return true; } } @@ -116,26 +117,38 @@ float ctf_CheckPassDirection(vector head_center, vector passer_center, vector pa // CaptureShield Functions // ======================= -float ctf_CaptureShield_CheckStatus(entity p) +bool ctf_CaptureShield_CheckStatus(entity p) { - float s, se; + int s, s2, s3, s4, se, se2, se3, se4, sr, ser; entity e; - float players_worseeq, players_total; + int players_worseeq, players_total; if(ctf_captureshield_max_ratio <= 0) - return FALSE; + return false; - s = PlayerScore_Add(p, SP_SCORE, 0); - if(s >= -ctf_captureshield_min_negscore) - return FALSE; + s = PlayerScore_Add(p, SP_CTF_CAPS, 0); + s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0); + s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0); + s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0); + + sr = ((s - s2) + (s3 + s4)); + + if(sr >= -ctf_captureshield_min_negscore) + return false; players_total = players_worseeq = 0; FOR_EACH_PLAYER(e) { if(DIFF_TEAM(e, p)) continue; - se = PlayerScore_Add(e, SP_SCORE, 0); - if(se <= s) + se = PlayerScore_Add(e, SP_CTF_CAPS, 0); + se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0); + se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0); + se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0); + + ser = ((se - se2) + (se3 + se4)); + + if(ser <= sr) ++players_worseeq; ++players_total; } @@ -144,14 +157,14 @@ float ctf_CaptureShield_CheckStatus(entity p) // use this rule here if(players_worseeq >= players_total * ctf_captureshield_max_ratio) - return FALSE; + return false; - return TRUE; + return true; } -void ctf_CaptureShield_Update(entity player, float wanted_status) +void ctf_CaptureShield_Update(entity player, bool wanted_status) { - float updated_status = ctf_CaptureShield_CheckStatus(player); + bool updated_status = ctf_CaptureShield_CheckStatus(player); if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE)); @@ -159,24 +172,24 @@ void ctf_CaptureShield_Update(entity player, float wanted_status) } } -float ctf_CaptureShield_Customize() +bool ctf_CaptureShield_Customize() { - if(!other.ctf_captureshielded) { return FALSE; } - if(SAME_TEAM(self, other)) { return FALSE; } + if(!other.ctf_captureshielded) { return false; } + if(CTF_SAMETEAM(self, other)) { return false; } - return TRUE; + return true; } void ctf_CaptureShield_Touch() { if(!other.ctf_captureshielded) { return; } - if(SAME_TEAM(self, other)) { return; } + if(CTF_SAMETEAM(self, other)) { return; } vector mymid = (self.absmin + self.absmax) * 0.5; vector othermid = (other.absmin + other.absmax) * 0.5; Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force); - Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); + if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); } } void ctf_CaptureShield_Spawn(entity flag) @@ -204,7 +217,7 @@ void ctf_CaptureShield_Spawn(entity flag) // Drop/Pass/Throw Code // ==================== -void ctf_Handle_Drop(entity flag, entity player, float droptype) +void ctf_Handle_Drop(entity flag, entity player, int droptype) { // declarations player = (player ? player : flag.pass_sender); @@ -219,7 +232,7 @@ void ctf_Handle_Drop(entity flag, entity player, float droptype) flag.ctf_status = FLAG_DROPPED; // messages and sounds - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname); + Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname); sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE); ctf_EventLog("dropped", player.team, player); @@ -229,7 +242,7 @@ void ctf_Handle_Drop(entity flag, entity player, float droptype) // waypoints if(autocvar_g_ctf_flag_dropped_waypoint) - WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team)); + WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team)); if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health)) { @@ -257,8 +270,17 @@ void ctf_Handle_Retrieve(entity flag, entity player) flag.owner.flagcarried = flag; // reset flag - setattachment(flag, player, ""); - setorigin(flag, FLAG_CARRY_OFFSET); + if(player.vehicle) + { + setattachment(flag, player.vehicle, ""); + setorigin(flag, VEHICLE_FLAG_OFFSET); + flag.scale = VEHICLE_FLAG_SCALE; + } + else + { + setattachment(flag, player, ""); + setorigin(flag, FLAG_CARRY_OFFSET); + } flag.movetype = MOVETYPE_NONE; flag.takedamage = DAMAGE_NO; flag.solid = SOLID_NOT; @@ -272,11 +294,11 @@ void ctf_Handle_Retrieve(entity flag, entity player) FOR_EACH_REALPLAYER(tmp_player) { if(tmp_player == sender) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname); + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname); else if(tmp_player == player) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname); + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname); else if(SAME_TEAM(tmp_player, sender)) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname); + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname); } // create new waypoint @@ -290,7 +312,7 @@ void ctf_Handle_Retrieve(entity flag, entity player) flag.pass_target = world; } -void ctf_Handle_Throw(entity player, entity receiver, float droptype) +void ctf_Handle_Throw(entity player, entity receiver, int droptype) { entity flag = player.flagcarried; vector targ_origin, flag_velocity; @@ -322,8 +344,8 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype) WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag - flag.pass_distance = vlen((('1 0 0' * targ_origin_x) + ('0 1 0' * targ_origin_y)) - (('1 0 0' * player.origin_x) + ('0 1 0' * player.origin_y))); // for the sake of this check, exclude Z axis - ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE); + flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' * player.origin.x) + ('0 1 0' * player.origin.y))); // for the sake of this check, exclude Z axis + ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false); // main flag.movetype = MOVETYPE_FLY; @@ -341,10 +363,10 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype) case DROP_THROW: { - 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')); + 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 & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1))); - flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE); + flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false); ctf_Handle_Drop(flag, player, droptype); break; } @@ -358,7 +380,7 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype) default: case DROP_NORMAL: { - flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), FALSE); + flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false); ctf_Handle_Drop(flag, player, droptype); break; } @@ -380,18 +402,33 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype) // Event Handlers // ============== -void ctf_Handle_Capture(entity flag, entity toucher, float capturetype) +void ctf_Handle_Capture(entity flag, entity toucher, int capturetype) { entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher); entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper); + entity player_team_flag = world, tmp_entity; float old_time, new_time; - if (!player) { return; } // without someone to give the reward to, we can't possibly cap + if(!player) { return; } // without someone to give the reward to, we can't possibly cap + if(CTF_DIFFTEAM(player, flag)) { return; } + + if(ctf_oneflag) + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + if(SAME_TEAM(tmp_entity, player)) + { + player_team_flag = tmp_entity; + break; + } + + nades_GiveBonus(player, autocvar_g_nades_bonus_score_high ); + + player.throw_prevtime = time; + player.throw_count = 0; // messages and sounds - Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_)); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL)); ctf_CaptureRecord(enemy_flag, player); - sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE); + sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE); switch(capturetype) { @@ -433,12 +470,12 @@ void ctf_Handle_Return(entity flag, entity player) // messages and sounds if(player.flags & FL_MONSTER) { - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name); } - else + else if(flag.team) { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_)); - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname); } sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE); ctf_EventLog("return", flag.team, player); @@ -448,6 +485,8 @@ void ctf_Handle_Return(entity flag, entity player) { PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns + + nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium); } TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it @@ -459,20 +498,34 @@ void ctf_Handle_Return(entity flag, entity player) flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time } + // other + if(player.flagcarried == flag) + WaypointSprite_Kill(player.wps_flagcarrier); + // reset the flag ctf_RespawnFlag(flag); } -void ctf_Handle_Pickup(entity flag, entity player, float pickuptype) +void ctf_Handle_Pickup(entity flag, entity player, int pickuptype) { // declarations float pickup_dropped_score; // used to calculate dropped pickup score + entity tmp_entity; // temporary entity // attach the flag to the player flag.owner = player; player.flagcarried = flag; - setattachment(flag, player, ""); - setorigin(flag, FLAG_CARRY_OFFSET); + if(player.vehicle) + { + setattachment(flag, player.vehicle, ""); + setorigin(flag, VEHICLE_FLAG_OFFSET); + flag.scale = VEHICLE_FLAG_SCALE; + } + else + { + setattachment(flag, player, ""); + setorigin(flag, FLAG_CARRY_OFFSET); + } // flag setup flag.movetype = MOVETYPE_NONE; @@ -489,17 +542,34 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype) } // messages and sounds - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname); - Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname); if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); } - - Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname); - Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname); + if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); } + else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); } + else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); } + + Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname); + + if(!flag.team) + FOR_EACH_PLAYER(tmp_entity) + if(tmp_entity != player) + if(DIFF_TEAM(player, tmp_entity)) + Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); + + if(flag.team) + FOR_EACH_PLAYER(tmp_entity) + if(tmp_entity != player) + if(CTF_SAMETEAM(flag, tmp_entity)) + if(SAME_TEAM(player, tmp_entity)) + Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname); + else + Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname); sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE); // scoring PlayerScore_Add(player, SP_CTF_PICKUPS, 1); + nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor); switch(pickuptype) { case PICKUP_BASE: @@ -544,7 +614,7 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype) // Main Flag Functions // =================== -void ctf_CheckFlagReturn(entity flag, float returntype) +void ctf_CheckFlagReturn(entity flag, int returntype) { if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING)) { @@ -554,14 +624,14 @@ void ctf_CheckFlagReturn(entity flag, float returntype) { switch(returntype) { - case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break; - case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break; - case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break; - case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break; + case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break; + case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break; + case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break; + case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break; default: case RETURN_TIMEOUT: - { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; } + { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; } } sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE); ctf_EventLog("returned", flag.team, world); @@ -570,10 +640,25 @@ void ctf_CheckFlagReturn(entity flag, float returntype) } } +bool ctf_Stalemate_Customize() +{ + // make spectators see what the player would see + entity e, wp_owner; + e = WaypointSprite_getviewentity(other); + wp_owner = self.owner; + + // team waypoints + if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; } + if(SAME_TEAM(wp_owner, e)) { return false; } + if(!IS_PLAYER(e)) { return false; } + + return true; +} + void ctf_CheckStalemate(void) { // declarations - float stale_red_flags = 0, stale_blue_flags = 0; + int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0; entity tmp_entity; entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs @@ -583,7 +668,7 @@ void ctf_CheckStalemate(void) { if(autocvar_g_ctf_stalemate) if(tmp_entity.ctf_status != FLAG_BASE) - if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time) + if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag { tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist ctf_staleflaglist = tmp_entity; @@ -592,16 +677,26 @@ void ctf_CheckStalemate(void) { case NUM_TEAM_1: ++stale_red_flags; break; case NUM_TEAM_2: ++stale_blue_flags; break; + case NUM_TEAM_3: ++stale_yellow_flags; break; + case NUM_TEAM_4: ++stale_pink_flags; break; + default: ++stale_neutral_flags; break; } } } - if(stale_red_flags && stale_blue_flags) - ctf_stalemate = TRUE; - else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2) - { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; } - else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1) - { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; } + if(ctf_oneflag) + stale_flags = (stale_neutral_flags >= 1); + else + stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1); + + if(ctf_oneflag && stale_flags == 1) + ctf_stalemate = true; + else if(stale_flags >= 2) + ctf_stalemate = true; + else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2) + { ctf_stalemate = false; wpforenemy_announced = false; } + else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1) + { ctf_stalemate = false; wpforenemy_announced = false; } // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary if(ctf_stalemate) @@ -609,7 +704,10 @@ void ctf_CheckStalemate(void) for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext) { if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier)) - WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team)); + { + WaypointSprite_Spawn(((ctf_oneflag) ? "flagcarrier" : "enemyflagcarrier"), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team)); + tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize; + } } if (!wpforenemy_announced) @@ -617,18 +715,24 @@ void ctf_CheckStalemate(void) FOR_EACH_REALPLAYER(tmp_entity) Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); - wpforenemy_announced = TRUE; + wpforenemy_announced = true; } } } -void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) { if(ITEM_DAMAGE_NEEDKILL(deathtype)) { - // automatically kill the flag and return it - self.health = 0; - ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + if(autocvar_g_ctf_flag_return_damage_delay) + { + self.ctf_flagdamaged = true; + } + else + { + self.health = 0; + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + } return; } if(autocvar_g_ctf_flag_return_damage) @@ -711,7 +815,13 @@ void ctf_FlagThink() return; } } - if(autocvar_g_ctf_flag_return_time) + if(self.ctf_flagdamaged) + { + self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE); + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + return; + } + else if(autocvar_g_ctf_flag_return_time) { self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE); ctf_CheckFlagReturn(self, RETURN_TIMEOUT); @@ -741,6 +851,13 @@ void ctf_FlagThink() wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check) } } + if(CTF_SAMETEAM(self, self.owner) && self.team) + { + if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed + ctf_Handle_Throw(self.owner, world, DROP_THROW); + else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius) + ctf_Handle_Return(self, self.owner); + } return; } @@ -752,6 +869,7 @@ void ctf_FlagThink() if((self.pass_target == world) || (self.pass_target.deadflag != DEAD_NO) + || (self.pass_target.flagcarried) || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius) || ((trace_fraction < 1) && (trace_ent != self.pass_target)) || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit)) @@ -762,7 +880,7 @@ void ctf_FlagThink() else { // still a viable target, go for it - ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE); + ctf_CalculatePassVelocity(self, targ_origin, self.origin, true); } return; } @@ -778,20 +896,27 @@ void ctf_FlagThink() void ctf_FlagTouch() { if(gameover) { return; } + if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; } - entity toucher = other; - float is_not_monster = (!(toucher.flags & FL_MONSTER)); + entity toucher = other, tmp_entity; + bool is_not_monster = (!(toucher.flags & FL_MONSTER)), num_perteam = 0; // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces if(ITEM_TOUCH_NEEDKILL()) { - self.health = 0; - ctf_CheckFlagReturn(self, RETURN_NEEDKILL); - return; + if(!autocvar_g_ctf_flag_return_damage_delay) + { + self.health = 0; + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + } + if(!self.ctf_flagdamaged) { return; } } + FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; } + // special touch behaviors - if(toucher.vehicle_flags & VHF_ISVEHICLE) + if(toucher.frozen) { return; } + else if(toucher.vehicle_flags & VHF_ISVEHICLE) { if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner) toucher = toucher.owner; // the player is actually the vehicle owner, not other @@ -819,16 +944,23 @@ void ctf_FlagTouch() { case FLAG_BASE: { - if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster) + if(ctf_oneflag) + { + if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster) + ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base + else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) + ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag + } + else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster) ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base - else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) + else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag break; } case FLAG_DROPPED: { - if(SAME_TEAM(toucher, self)) + if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team ctf_Handle_Return(self, toucher); // toucher just returned his own flag else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay))) ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag @@ -867,9 +999,7 @@ void ctf_RespawnFlag(entity flag) // reset the player (if there is one) if((flag.owner) && (flag.owner.flagcarried == flag)) { - if(flag.owner.wps_enemyflagcarrier) - WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier); - + WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier); WaypointSprite_Kill(flag.wps_flagcarrier); flag.owner.flagcarried = world; @@ -878,7 +1008,10 @@ void ctf_RespawnFlag(entity flag) ctf_FakeTimeLimit(flag.owner, -1); } - if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped)) + if((flag.owner) && (flag.owner.vehicle)) + flag.scale = FLAG_SCALE; + + if(flag.ctf_status == FLAG_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); } // reset the flag @@ -901,6 +1034,9 @@ void ctf_RespawnFlag(entity flag) flag.ctf_dropper = world; flag.ctf_pickuptime = 0; flag.ctf_droptime = 0; + flag.ctf_flagdamaged = 0; + + ctf_CheckStalemate(); } void ctf_Reset() @@ -920,17 +1056,34 @@ void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf self.bot_basewaypoint = self.nearestwaypoint; // waypointsprites - WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE)); - WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE)); + string basename = "base"; + + switch(self.team) + { + case NUM_TEAM_1: basename = "redbase"; break; + case NUM_TEAM_2: basename = "bluebase"; break; + case NUM_TEAM_3: basename = "yellowbase"; break; + case NUM_TEAM_4: basename = "pinkbase"; break; + default: basename = "neutralbase"; break; + } + + WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1')); + WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1')); // captureshield setup ctf_CaptureShield_Spawn(self); } -void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc +void set_flag_string(entity flag, .string field, string value, string teamname) +{ + if(flag.field == "") + flag.field = strzone(sprintf(value,teamname)); +} + +void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc { // declarations - teamnumber = fabs(teamnumber - bound(0, autocvar_g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1. + string teamname = Static_Team_ColorName_Lower(teamnumber); self = flag; // for later usage with droptofloor() // main setup @@ -939,9 +1092,8 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag setattachment(flag, world, ""); - flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name - flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue) - flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough) + flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber))); + flag.team = teamnumber; flag.classname = "item_flag_team"; flag.target = "###item###"; // wut? flag.flags = FL_ITEM | FL_NOTARGET; @@ -951,8 +1103,9 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100); flag.health = flag.max_flag_health; flag.event_damage = ctf_FlagDamage; - flag.pushable = TRUE; + flag.pushable = true; flag.teleportable = TELEPORT_NORMAL; + flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable; flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable; flag.velocity = '0 0 0'; @@ -964,21 +1117,21 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag flag.ctf_status = FLAG_BASE; // appearence - if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); } - if(!flag.scale) { flag.scale = FLAG_SCALE; } - if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); } - if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); } - if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); } - if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); } - - // sound - if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); } - if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); } - if(flag.snd_flag_capture == "") { flag.snd_flag_capture = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag - if(flag.snd_flag_respawn == "") { flag.snd_flag_respawn = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match. - if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); } - if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound - if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here + 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)); } + set_flag_string(flag, toucheffect, "%sflag_touch", teamname); + set_flag_string(flag, passeffect, "%sflag_pass", teamname); + set_flag_string(flag, capeffect, "%sflag_cap", teamname); + + // sounds + set_flag_string(flag, snd_flag_taken, "ctf/%s_taken.wav", teamname); + set_flag_string(flag, snd_flag_returned, "ctf/%s_returned.wav", teamname); + set_flag_string(flag, snd_flag_capture, "ctf/%s_capture.wav", teamname); + set_flag_string(flag, snd_flag_dropped, "ctf/%s_dropped.wav", teamname); + if(flag.snd_flag_respawn == "") { flag.snd_flag_respawn = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match. + if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound + if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here // precache precache_sound(flag.snd_flag_taken); @@ -999,25 +1152,42 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag if(autocvar_g_ctf_flag_glowtrails) { - flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue + switch(teamnumber) + { + case NUM_TEAM_1: flag.glow_color = 251; break; + case NUM_TEAM_2: flag.glow_color = 210; break; + case NUM_TEAM_3: flag.glow_color = 110; break; + case NUM_TEAM_4: flag.glow_color = 145; break; + default: flag.glow_color = 254; break; + } flag.glow_size = 25; flag.glow_trail = 1; } flag.effects |= EF_LOWPRECISION; if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; } - if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); } - + if(autocvar_g_ctf_dynamiclights) + { + switch(teamnumber) + { + case NUM_TEAM_1: flag.effects |= EF_RED; break; + case NUM_TEAM_2: flag.effects |= EF_BLUE; break; + case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break; + case NUM_TEAM_4: flag.effects |= EF_RED; break; + default: flag.effects |= EF_DIMLIGHT; break; + } + } + // flag placement if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location { flag.dropped_origin = flag.origin; - flag.noalign = TRUE; + flag.noalign = true; flag.movetype = MOVETYPE_NONE; } else // drop to floor, automatically find a platform and set that as spawn origin { - flag.noalign = FALSE; + flag.noalign = false; self = flag; droptofloor(); flag.movetype = MOVETYPE_TOSS; @@ -1060,7 +1230,7 @@ entity havocbot_ctf_find_flag(entity bot) f = ctf_worldflaglist; while (f) { - if (bot.team == f.team) + if (CTF_SAMETEAM(bot, f)) return f; f = f.ctf_worldflagnext; } @@ -1073,24 +1243,37 @@ entity havocbot_ctf_find_enemy_flag(entity bot) f = ctf_worldflaglist; while (f) { - if (bot.team != f.team) + if(ctf_oneflag) + { + if(CTF_DIFFTEAM(bot, f)) + { + if(f.team) + { + if(bot.flagcarried) + return f; + } + else if(!bot.flagcarried) + return f; + } + } + else if (CTF_DIFFTEAM(bot, f)) return f; f = f.ctf_worldflagnext; } return world; } -float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius) +int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius) { if (!teamplay) return 0; - float c = 0; + int c = 0; entity head; FOR_EACH_PLAYER(head) { - if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot) + if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot) continue; if(vlen(head.origin - org) < tc_radius) @@ -1106,7 +1289,7 @@ void havocbot_goalrating_ctf_ourflag(float ratingscale) head = ctf_worldflaglist; while (head) { - if (self.team == head.team) + if (CTF_SAMETEAM(self, head)) break; head = head.ctf_worldflagnext; } @@ -1120,7 +1303,7 @@ void havocbot_goalrating_ctf_ourbase(float ratingscale) head = ctf_worldflaglist; while (head) { - if (self.team == head.team) + if (CTF_SAMETEAM(self, head)) break; head = head.ctf_worldflagnext; } @@ -1136,7 +1319,20 @@ void havocbot_goalrating_ctf_enemyflag(float ratingscale) head = ctf_worldflaglist; while (head) { - if (self.team != head.team) + if(ctf_oneflag) + { + if(CTF_DIFFTEAM(self, head)) + { + if(head.team) + { + if(self.flagcarried) + break; + } + else if(!self.flagcarried) + break; + } + } + else if(CTF_DIFFTEAM(self, head)) break; head = head.ctf_worldflagnext; } @@ -1202,7 +1398,7 @@ void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float s { entity head; float t; - head = findchainfloat(bot_pickup, TRUE); + head = findchainfloat(bot_pickup, true); while (head) { // gather health and armor only @@ -1258,7 +1454,7 @@ void havocbot_ctf_reset_role(entity bot) // if there is only me on the team switch to offense c = 0; FOR_EACH_PLAYER(head) - if(head.team==bot.team) + if(SAME_TEAM(head, bot)) ++c; if(c==1) @@ -1304,7 +1500,10 @@ void havocbot_role_ctf_carrier() self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; navigation_goalrating_start(); - havocbot_goalrating_ctf_ourbase(50000); + if(ctf_oneflag) + havocbot_goalrating_ctf_enemybase(50000); + else + havocbot_goalrating_ctf_ourbase(50000); if(self.health<100) havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000); @@ -1550,7 +1749,7 @@ void havocbot_role_ctf_middle() vector org; org = havocbot_ctf_middlepoint; - org_z = self.origin_z; + org.z = self.origin.z; self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; navigation_goalrating_start(); @@ -1624,7 +1823,7 @@ void havocbot_role_ctf_defense() } if(closestplayer) - if(closestplayer.team!=self.team) + if(DIFF_TEAM(closestplayer, self)) if(vlen(org - self.origin)>1000) if(checkpvs(self.origin,closestplayer)||random()<0.5) havocbot_goalrating_ctf_ourbase(30000); @@ -1638,7 +1837,7 @@ void havocbot_role_ctf_defense() } } -void havocbot_role_ctf_setrole(entity bot, float role) +void havocbot_role_ctf_setrole(entity bot, int role) { dprint(strcat(bot.netname," switched to ")); switch(role) @@ -1691,28 +1890,39 @@ void havocbot_role_ctf_setrole(entity bot, float role) MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink) { entity flag; + int t = 0, t2 = 0, t3 = 0; // initially clear items so they can be set as necessary later. - self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST - | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED); + self.ctf_flagstatus &= ~(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 + | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST + | CTF_FLAG_NEUTRAL | CTF_SHIELDED); // scan through all the flags and notify the client about them for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) { + if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; } + if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; } + if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; } + if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; } + if(flag.team == 0) { t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; } + switch(flag.ctf_status) { case FLAG_PASSING: case FLAG_CARRY: { if((flag.owner == self) || (flag.pass_sender == self)) - self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag - else - self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag + self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag + else + self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag break; } case FLAG_DROPPED: { - self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map + self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map break; } } @@ -1720,13 +1930,13 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink) // item for stopping players from capturing the flag too often if(self.ctf_captureshielded) - self.items |= IT_CTF_SHIELDED; + self.ctf_flagstatus |= CTF_SHIELDED; // update the health of the flag carrier waypointsprite if(self.wps_flagcarrier) WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)); - return FALSE; + return false; } MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc @@ -1744,7 +1954,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values t frag_force *= autocvar_g_ctf_flagcarrier_forcefactor; } } - else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier + else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier { if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON))) if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time) @@ -1754,7 +1964,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values t } // todo: add notification for when flag carrier needs help? } - return FALSE; + return false; } MUTATOR_HOOKFUNCTION(ctf_PlayerDies) @@ -1766,9 +1976,13 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDies) } if(frag_target.flagcarried) - { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); } + { + entity tmp_entity = frag_target.flagcarried; + ctf_Handle_Throw(frag_target, world, DROP_NORMAL); + tmp_entity.ctf_dropper = world; + } - return FALSE; + return false; } MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill) @@ -1791,7 +2005,7 @@ MUTATOR_HOOKFUNCTION(ctf_RemovePlayer) if(flag.ctf_dropper == self) { flag.ctf_dropper = world; } } - return FALSE; + return false; } MUTATOR_HOOKFUNCTION(ctf_PortalTeleport) @@ -1800,12 +2014,12 @@ MUTATOR_HOOKFUNCTION(ctf_PortalTeleport) if(!autocvar_g_ctf_portalteleport) { ctf_Handle_Throw(self, world, DROP_NORMAL); } - return FALSE; + return false; } MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey) { - if(MUTATOR_RETURNVALUE || gameover) { return FALSE; } + if(MUTATOR_RETURNVALUE || gameover) { return false; } entity player = self; @@ -1815,7 +2029,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey) if(autocvar_g_ctf_pass) { entity head, closest_target = world; - head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE); + head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true); while(head) // find the closest acceptable target to pass to { @@ -1842,7 +2056,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey) Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname); } player.throw_antispam = time + autocvar_g_ctf_pass_wait; - return TRUE; + return true; } else if(player.flagcarried) { @@ -1859,7 +2073,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey) head = head.chain; } - if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; } + if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; } } // throw the flag in front of you @@ -1872,12 +2086,12 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey) player.throw_prevtime = time; player.throw_count = 1; ctf_Handle_Throw(player, world, DROP_THROW); - return TRUE; + return true; } else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time)); - return FALSE; + return false; } } else @@ -1888,12 +2102,12 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey) player.throw_prevtime = time; ctf_Handle_Throw(player, world, DROP_THROW); - return TRUE; + return true; } } } - return FALSE; + return false; } MUTATOR_HOOKFUNCTION(ctf_HelpMePing) @@ -1905,11 +2119,11 @@ MUTATOR_HOOKFUNCTION(ctf_HelpMePing) } else // create a normal help me waypointsprite { - WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, FALSE, RADARICON_HELPME, '1 0.5 0'); + WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME, '1 0.5 0'); WaypointSprite_Ping(self.wps_helpme); } - return TRUE; + return true; } MUTATOR_HOOKFUNCTION(ctf_VehicleEnter) @@ -1927,10 +2141,10 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleEnter) vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE; //vh_player.flagcarried.angles = '0 0 0'; } - return TRUE; + return true; } - return FALSE; + return false; } MUTATOR_HOOKFUNCTION(ctf_VehicleExit) @@ -1941,22 +2155,22 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleExit) setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET); vh_player.flagcarried.scale = FLAG_SCALE; vh_player.flagcarried.angles = '0 0 0'; - return TRUE; + return true; } - return FALSE; + return false; } MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun) { if(self.flagcarried) { - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL)); ctf_RespawnFlag(self.flagcarried); - return TRUE; + return true; } - return FALSE; + return false; } MUTATOR_HOOKFUNCTION(ctf_MatchEnd) @@ -1974,7 +2188,7 @@ MUTATOR_HOOKFUNCTION(ctf_MatchEnd) flag.movetype = MOVETYPE_NONE; flag.takedamage = DAMAGE_NO; flag.solid = SOLID_NOT; - flag.nextthink = FALSE; // stop thinking + flag.nextthink = false; // stop thinking //dprint("stopping the ", flag.netname, " from moving.\n"); break; @@ -1990,13 +2204,26 @@ MUTATOR_HOOKFUNCTION(ctf_MatchEnd) } } - return FALSE; + return false; } MUTATOR_HOOKFUNCTION(ctf_BotRoles) { havocbot_ctf_reset_role(self); - return TRUE; + return true; +} + +MUTATOR_HOOKFUNCTION(ctf_GetTeamCount) +{ + //ret_float = ctf_teams; + ret_string = "ctf_team"; + return true; +} + +MUTATOR_HOOKFUNCTION(ctf_SpectateCopy) +{ + self.ctf_flagstatus = other.ctf_flagstatus; + return false; } @@ -2065,7 +2292,7 @@ void spawnfunc_item_flag_team1() { if(!g_ctf) { remove(self); return; } - ctf_FlagSetup(1, self); // 1 = red + ctf_FlagSetup(NUM_TEAM_1, self); } /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37) @@ -2083,7 +2310,62 @@ void spawnfunc_item_flag_team2() { if(!g_ctf) { remove(self); return; } - ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue. + ctf_FlagSetup(NUM_TEAM_2, self); +} + +/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team three (Yellow). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +void spawnfunc_item_flag_team3() +{ + if(!g_ctf) { remove(self); return; } + + ctf_FlagSetup(NUM_TEAM_3, self); +} + +/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team four (Pink). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +void spawnfunc_item_flag_team4() +{ + if(!g_ctf) { remove(self); return; } + + ctf_FlagSetup(NUM_TEAM_4, self); +} + +/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag (Neutral). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +void spawnfunc_item_flag_neutral() +{ + if(!g_ctf) { remove(self); return; } + if(!cvar("g_ctf_oneflag")) { remove(self); return; } + + ctf_FlagSetup(0, self); } /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32) @@ -2114,9 +2396,10 @@ void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); } // ============== // scoreboard setup -void ctf_ScoreRules() +void ctf_ScoreRules(int teams) { - ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE); + CheckAllowedTeams(world); + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true); ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME); @@ -2128,7 +2411,7 @@ void ctf_ScoreRules() } // code from here on is just to support maps that don't have flag and team entities -void ctf_SpawnTeam (string teamname, float teamcolor) +void ctf_SpawnTeam (string teamname, int teamcolor) { entity oldself; oldself = self; @@ -2144,15 +2427,31 @@ void ctf_SpawnTeam (string teamname, float teamcolor) void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up. { + ctf_teams = 2; + + entity tmp_entity; + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + { + if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); } + if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); } + if(tmp_entity.team == 0) { ctf_oneflag = true; } + } + + ctf_teams = bound(2, ctf_teams, 4); + // if no teams are found, spawn defaults if(find(world, classname, "ctf_team") == world) { print("No ""ctf_team"" entities found on this map, creating them anyway.\n"); ctf_SpawnTeam("Red", NUM_TEAM_1 - 1); ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1); + if(ctf_teams >= 3) + ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1); + if(ctf_teams >= 4) + ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1); } - ctf_ScoreRules(); + ctf_ScoreRules(ctf_teams); } void ctf_Initialize() @@ -2163,6 +2462,8 @@ void ctf_Initialize() ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio; ctf_captureshield_force = autocvar_g_ctf_shield_force; + addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus); + InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE); } @@ -2183,6 +2484,8 @@ MUTATOR_DEFINITION(gamemode_ctf) MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY); MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY); MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY); + MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY); MUTATOR_ONADD {