1 #include "gamemode_ctf.qh"
7 #include "../../common/vehicles/all.qh"
10 #include "../../warpzonelib/common.qh"
11 #include "../../warpzonelib/mathlib.qh"
13 void ctf_FakeTimeLimit(entity e, float t)
16 WriteByte(MSG_ONE, 3); // svc_updatestat
17 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
19 WriteCoord(MSG_ONE, autocvar_timelimit);
21 WriteCoord(MSG_ONE, (t + 1) / 60);
24 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
26 if(autocvar_sv_eventlog)
27 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
28 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
31 void ctf_CaptureRecord(entity flag, entity player)
33 float cap_record = ctf_captimerecord;
34 float cap_time = (time - flag.ctf_pickuptime);
35 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
38 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
39 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)); }
40 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)); }
41 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)); }
43 // write that shit in the database
44 if(!ctf_oneflag) // but not in 1-flag mode
45 if((!ctf_captimerecord) || (cap_time < cap_record))
47 ctf_captimerecord = cap_time;
48 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
49 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
50 write_recordmarker(player, (time - cap_time), cap_time);
54 void ctf_FlagcarrierWaypoints(entity player)
56 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
57 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
58 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
59 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
62 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
64 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
65 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
66 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
67 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
70 if(current_height) // make sure we can actually do this arcing path
72 targpos = (to + ('0 0 1' * current_height));
73 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
74 if(trace_fraction < 1)
76 //print("normal arc line failed, trying to find new pos...");
77 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
78 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
79 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
80 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
81 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
84 else { targpos = to; }
86 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
88 vector desired_direction = normalize(targpos - from);
89 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
90 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
93 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
95 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
97 // directional tracing only
99 makevectors(passer_angle);
101 // find the closest point on the enemy to the center of the attack
102 float h; // hypotenuse, which is the distance between attacker to head
103 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
105 h = vlen(head_center - passer_center);
106 a = h * (normalize(head_center - passer_center) * v_forward);
108 vector nearest_on_line = (passer_center + a * v_forward);
109 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
111 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
112 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
114 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
119 else { return true; }
123 // =======================
124 // CaptureShield Functions
125 // =======================
127 bool ctf_CaptureShield_CheckStatus(entity p)
129 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
131 int players_worseeq, players_total;
133 if(ctf_captureshield_max_ratio <= 0)
136 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
137 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
138 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
139 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
141 sr = ((s - s2) + (s3 + s4));
143 if(sr >= -ctf_captureshield_min_negscore)
146 players_total = players_worseeq = 0;
151 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
152 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
153 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
154 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
156 ser = ((se - se2) + (se3 + se4));
163 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
164 // use this rule here
166 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
172 void ctf_CaptureShield_Update(entity player, bool wanted_status)
174 bool updated_status = ctf_CaptureShield_CheckStatus(player);
175 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
177 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
178 player.ctf_captureshielded = updated_status;
182 bool ctf_CaptureShield_Customize()
184 if(!other.ctf_captureshielded) { return false; }
185 if(CTF_SAMETEAM(self, other)) { return false; }
190 void ctf_CaptureShield_Touch()
192 if(!other.ctf_captureshielded) { return; }
193 if(CTF_SAMETEAM(self, other)) { return; }
195 vector mymid = (self.absmin + self.absmax) * 0.5;
196 vector othermid = (other.absmin + other.absmax) * 0.5;
198 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
199 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
202 void ctf_CaptureShield_Spawn(entity flag)
204 entity shield = spawn();
207 shield.team = self.team;
208 shield.touch = ctf_CaptureShield_Touch;
209 shield.customizeentityforclient = ctf_CaptureShield_Customize;
210 shield.classname = "ctf_captureshield";
211 shield.effects = EF_ADDITIVE;
212 shield.movetype = MOVETYPE_NOCLIP;
213 shield.solid = SOLID_TRIGGER;
214 shield.avelocity = '7 0 11';
217 setorigin(shield, self.origin);
218 setmodel(shield, "models/ctf/shield.md3");
219 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
223 // ====================
224 // Drop/Pass/Throw Code
225 // ====================
227 void ctf_Handle_Drop(entity flag, entity player, int droptype)
230 player = (player ? player : flag.pass_sender);
233 flag.movetype = MOVETYPE_TOSS;
234 flag.takedamage = DAMAGE_YES;
235 flag.angles = '0 0 0';
236 flag.health = flag.max_flag_health;
237 flag.ctf_droptime = time;
238 flag.ctf_dropper = player;
239 flag.ctf_status = FLAG_DROPPED;
241 // messages and sounds
242 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
243 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
244 ctf_EventLog("dropped", player.team, player);
247 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
248 PlayerScore_Add(player, SP_CTF_DROPS, 1);
251 if(autocvar_g_ctf_flag_dropped_waypoint) {
252 entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
253 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
256 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
258 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
259 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
262 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
264 if(droptype == DROP_PASS)
266 flag.pass_distance = 0;
267 flag.pass_sender = world;
268 flag.pass_target = world;
272 void ctf_Handle_Retrieve(entity flag, entity player)
274 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
275 entity sender = flag.pass_sender;
277 // transfer flag to player
279 flag.owner.flagcarried = flag;
284 setattachment(flag, player.vehicle, "");
285 setorigin(flag, VEHICLE_FLAG_OFFSET);
286 flag.scale = VEHICLE_FLAG_SCALE;
290 setattachment(flag, player, "");
291 setorigin(flag, FLAG_CARRY_OFFSET);
293 flag.movetype = MOVETYPE_NONE;
294 flag.takedamage = DAMAGE_NO;
295 flag.solid = SOLID_NOT;
296 flag.angles = '0 0 0';
297 flag.ctf_status = FLAG_CARRY;
299 // messages and sounds
300 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
301 ctf_EventLog("receive", flag.team, player);
303 FOR_EACH_REALPLAYER(tmp_player)
305 if(tmp_player == sender)
306 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);
307 else if(tmp_player == player)
308 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);
309 else if(SAME_TEAM(tmp_player, sender))
310 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);
313 // create new waypoint
314 ctf_FlagcarrierWaypoints(player);
316 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
317 player.throw_antispam = sender.throw_antispam;
319 flag.pass_distance = 0;
320 flag.pass_sender = world;
321 flag.pass_target = world;
324 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
326 entity flag = player.flagcarried;
327 vector targ_origin, flag_velocity;
329 if(!flag) { return; }
330 if((droptype == DROP_PASS) && !receiver) { return; }
332 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
335 setattachment(flag, world, "");
336 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
337 flag.owner.flagcarried = world;
339 flag.solid = SOLID_TRIGGER;
340 flag.ctf_dropper = player;
341 flag.ctf_droptime = time;
343 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
350 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
351 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
352 WarpZone_RefSys_Copy(flag, receiver);
353 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
354 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
356 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
357 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
360 flag.movetype = MOVETYPE_FLY;
361 flag.takedamage = DAMAGE_NO;
362 flag.pass_sender = player;
363 flag.pass_target = receiver;
364 flag.ctf_status = FLAG_PASSING;
367 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
368 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
369 ctf_EventLog("pass", flag.team, player);
375 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'));
377 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)));
378 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
379 ctf_Handle_Drop(flag, player, droptype);
385 flag.velocity = '0 0 0'; // do nothing
392 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);
393 ctf_Handle_Drop(flag, player, droptype);
398 // kill old waypointsprite
399 WaypointSprite_Ping(player.wps_flagcarrier);
400 WaypointSprite_Kill(player.wps_flagcarrier);
402 if(player.wps_enemyflagcarrier)
403 WaypointSprite_Kill(player.wps_enemyflagcarrier);
406 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
414 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
416 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
417 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
418 entity player_team_flag = world, tmp_entity;
419 float old_time, new_time;
421 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
422 if(CTF_DIFFTEAM(player, flag)) { return; }
425 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
426 if(SAME_TEAM(tmp_entity, player))
428 player_team_flag = tmp_entity;
432 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
434 player.throw_prevtime = time;
435 player.throw_count = 0;
437 // messages and sounds
438 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
439 ctf_CaptureRecord(enemy_flag, player);
440 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);
444 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
445 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
450 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
451 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
453 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
454 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
455 if(!old_time || new_time < old_time)
456 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
459 Send_Effect(flag.capeffect, flag.origin, '0 0 0', 1);
460 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
463 if(capturetype == CAPTURE_NORMAL)
465 WaypointSprite_Kill(player.wps_flagcarrier);
466 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
468 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
469 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
473 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
474 ctf_RespawnFlag(enemy_flag);
477 void ctf_Handle_Return(entity flag, entity player)
479 // messages and sounds
480 if(IS_MONSTER(player))
482 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
486 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
487 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
489 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
490 ctf_EventLog("return", flag.team, player);
493 if(IS_PLAYER(player))
495 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
496 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
498 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
501 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
505 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
506 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
507 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
511 if(player.flagcarried == flag)
512 WaypointSprite_Kill(player.wps_flagcarrier);
515 ctf_RespawnFlag(flag);
518 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
521 float pickup_dropped_score; // used to calculate dropped pickup score
522 entity tmp_entity; // temporary entity
524 // attach the flag to the player
526 player.flagcarried = flag;
529 setattachment(flag, player.vehicle, "");
530 setorigin(flag, VEHICLE_FLAG_OFFSET);
531 flag.scale = VEHICLE_FLAG_SCALE;
535 setattachment(flag, player, "");
536 setorigin(flag, FLAG_CARRY_OFFSET);
540 flag.movetype = MOVETYPE_NONE;
541 flag.takedamage = DAMAGE_NO;
542 flag.solid = SOLID_NOT;
543 flag.angles = '0 0 0';
544 flag.ctf_status = FLAG_CARRY;
548 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
549 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
553 // messages and sounds
554 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
555 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
556 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
557 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
558 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)); }
560 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);
563 FOR_EACH_PLAYER(tmp_entity)
564 if(tmp_entity != player)
565 if(DIFF_TEAM(player, tmp_entity))
566 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
569 FOR_EACH_PLAYER(tmp_entity)
570 if(tmp_entity != player)
571 if(CTF_SAMETEAM(flag, tmp_entity))
572 if(SAME_TEAM(player, tmp_entity))
573 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
575 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);
577 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
580 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
581 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
586 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
587 ctf_EventLog("steal", flag.team, player);
593 pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
594 pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
595 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
596 PlayerTeamScore_AddScore(player, pickup_dropped_score);
597 ctf_EventLog("pickup", flag.team, player);
605 if(pickuptype == PICKUP_BASE)
607 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
608 if((player.speedrunning) && (ctf_captimerecord))
609 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
613 Send_Effect(flag.toucheffect, player.origin, '0 0 0', 1);
616 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
617 ctf_FlagcarrierWaypoints(player);
618 WaypointSprite_Ping(player.wps_flagcarrier);
622 // ===================
623 // Main Flag Functions
624 // ===================
626 void ctf_CheckFlagReturn(entity flag, int returntype)
628 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
630 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
632 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
636 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;
637 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;
638 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;
639 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;
643 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
645 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
646 ctf_EventLog("returned", flag.team, world);
647 ctf_RespawnFlag(flag);
652 bool ctf_Stalemate_Customize()
654 // make spectators see what the player would see
656 e = WaypointSprite_getviewentity(other);
657 wp_owner = self.owner;
660 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
661 if(SAME_TEAM(wp_owner, e)) { return false; }
662 if(!IS_PLAYER(e)) { return false; }
667 void ctf_CheckStalemate(void)
670 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
673 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
675 // build list of stale flags
676 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
678 if(autocvar_g_ctf_stalemate)
679 if(tmp_entity.ctf_status != FLAG_BASE)
680 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
682 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
683 ctf_staleflaglist = tmp_entity;
685 switch(tmp_entity.team)
687 case NUM_TEAM_1: ++stale_red_flags; break;
688 case NUM_TEAM_2: ++stale_blue_flags; break;
689 case NUM_TEAM_3: ++stale_yellow_flags; break;
690 case NUM_TEAM_4: ++stale_pink_flags; break;
691 default: ++stale_neutral_flags; break;
697 stale_flags = (stale_neutral_flags >= 1);
699 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
701 if(ctf_oneflag && stale_flags == 1)
702 ctf_stalemate = true;
703 else if(stale_flags >= 2)
704 ctf_stalemate = true;
705 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
706 { ctf_stalemate = false; wpforenemy_announced = false; }
707 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
708 { ctf_stalemate = false; wpforenemy_announced = false; }
710 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
713 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
715 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
717 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
718 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
719 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
723 if (!wpforenemy_announced)
725 FOR_EACH_REALPLAYER(tmp_entity)
726 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
728 wpforenemy_announced = true;
733 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
735 if(ITEM_DAMAGE_NEEDKILL(deathtype))
737 if(autocvar_g_ctf_flag_return_damage_delay)
739 self.ctf_flagdamaged = true;
744 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
748 if(autocvar_g_ctf_flag_return_damage)
750 // reduce health and check if it should be returned
751 self.health = self.health - damage;
752 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
762 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
765 if(self == ctf_worldflaglist) // only for the first flag
766 FOR_EACH_CLIENT(tmp_entity)
767 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
770 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
771 dprint("wtf the flag got squashed?\n");
772 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
773 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
774 setsize(self, FLAG_MIN, FLAG_MAX); }
776 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
780 self.angles = '0 0 0';
788 switch(self.ctf_status)
792 if(autocvar_g_ctf_dropped_capture_radius)
794 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
795 if(tmp_entity.ctf_status == FLAG_DROPPED)
796 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
797 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
798 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
805 if(autocvar_g_ctf_flag_dropped_floatinwater)
807 vector midpoint = ((self.absmin + self.absmax) * 0.5);
808 if(pointcontents(midpoint) == CONTENT_WATER)
810 self.velocity = self.velocity * 0.5;
812 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
813 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
815 { self.movetype = MOVETYPE_FLY; }
817 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
819 if(autocvar_g_ctf_flag_return_dropped)
821 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
824 ctf_CheckFlagReturn(self, RETURN_DROPPED);
828 if(self.ctf_flagdamaged)
830 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
831 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
834 else if(autocvar_g_ctf_flag_return_time)
836 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
837 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
845 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
848 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
852 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
856 if(autocvar_g_ctf_stalemate)
858 if(time >= wpforenemy_nextthink)
860 ctf_CheckStalemate();
861 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
864 if(CTF_SAMETEAM(self, self.owner) && self.team)
866 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
867 ctf_Handle_Throw(self.owner, world, DROP_THROW);
868 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
869 ctf_Handle_Return(self, self.owner);
876 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
877 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
878 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
880 if((self.pass_target == world)
881 || (self.pass_target.deadflag != DEAD_NO)
882 || (self.pass_target.flagcarried)
883 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
884 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
885 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
887 // give up, pass failed
888 ctf_Handle_Drop(self, world, DROP_PASS);
892 // still a viable target, go for it
893 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
898 default: // this should never happen
900 dprint("ctf_FlagThink(): Flag exists with no status?\n");
908 if(gameover) { return; }
909 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
911 entity toucher = other, tmp_entity;
912 bool is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0;
914 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
915 if(ITEM_TOUCH_NEEDKILL())
917 if(!autocvar_g_ctf_flag_return_damage_delay)
920 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
922 if(!self.ctf_flagdamaged) { return; }
925 FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
927 // special touch behaviors
928 if(toucher.frozen) { return; }
929 else if(IS_VEHICLE(toucher))
931 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
932 toucher = toucher.owner; // the player is actually the vehicle owner, not other
934 return; // do nothing
936 else if(IS_MONSTER(toucher))
938 if(!autocvar_g_ctf_allow_monster_touch)
939 return; // do nothing
941 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
943 if(time > self.wait) // if we haven't in a while, play a sound/effect
945 Send_Effect(self.toucheffect, self.origin, '0 0 0', 1);
946 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
947 self.wait = time + FLAG_TOUCHRATE;
951 else if(toucher.deadflag != DEAD_NO) { return; }
953 switch(self.ctf_status)
959 if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
960 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
961 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
962 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
964 else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
965 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
966 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
967 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
973 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
974 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
975 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
976 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
982 dprint("Someone touched a flag even though it was being carried?\n");
988 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
990 if(DIFF_TEAM(toucher, self.pass_sender))
991 ctf_Handle_Return(self, toucher);
993 ctf_Handle_Retrieve(self, toucher);
1000 .float last_respawn;
1001 void ctf_RespawnFlag(entity flag)
1003 // check for flag respawn being called twice in a row
1004 if(flag.last_respawn > time - 0.5)
1005 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1007 flag.last_respawn = time;
1009 // reset the player (if there is one)
1010 if((flag.owner) && (flag.owner.flagcarried == flag))
1012 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1013 WaypointSprite_Kill(flag.wps_flagcarrier);
1015 flag.owner.flagcarried = world;
1017 if(flag.speedrunning)
1018 ctf_FakeTimeLimit(flag.owner, -1);
1021 if((flag.owner) && (flag.owner.vehicle))
1022 flag.scale = FLAG_SCALE;
1024 if(flag.ctf_status == FLAG_DROPPED)
1025 { WaypointSprite_Kill(flag.wps_flagdropped); }
1028 setattachment(flag, world, "");
1029 setorigin(flag, flag.ctf_spawnorigin);
1031 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1032 flag.takedamage = DAMAGE_NO;
1033 flag.health = flag.max_flag_health;
1034 flag.solid = SOLID_TRIGGER;
1035 flag.velocity = '0 0 0';
1036 flag.angles = flag.mangle;
1037 flag.flags = FL_ITEM | FL_NOTARGET;
1039 flag.ctf_status = FLAG_BASE;
1041 flag.pass_distance = 0;
1042 flag.pass_sender = world;
1043 flag.pass_target = world;
1044 flag.ctf_dropper = world;
1045 flag.ctf_pickuptime = 0;
1046 flag.ctf_droptime = 0;
1047 flag.ctf_flagdamaged = 0;
1049 ctf_CheckStalemate();
1055 if(IS_PLAYER(self.owner))
1056 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1058 ctf_RespawnFlag(self);
1061 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1064 waypoint_spawnforitem_force(self, self.origin);
1065 self.nearestwaypointtimeout = 0; // activate waypointing again
1066 self.bot_basewaypoint = self.nearestwaypoint;
1072 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1073 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1074 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1075 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1076 default: basename = WP_FlagBaseNeutral; break;
1079 entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG);
1080 wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
1081 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1083 // captureshield setup
1084 ctf_CaptureShield_Spawn(self);
1087 void set_flag_string(entity flag, .string field, string value, string teamname)
1089 if(flag.field == "")
1090 flag.field = strzone(sprintf(value,teamname));
1093 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1096 string teamname = Static_Team_ColorName_Lower(teamnumber);
1097 self = flag; // for later usage with droptofloor()
1100 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1101 ctf_worldflaglist = flag;
1103 setattachment(flag, world, "");
1105 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1106 flag.team = teamnumber;
1107 flag.classname = "item_flag_team";
1108 flag.target = "###item###"; // wut?
1109 flag.flags = FL_ITEM | FL_NOTARGET;
1110 flag.solid = SOLID_TRIGGER;
1111 flag.takedamage = DAMAGE_NO;
1112 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1113 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1114 flag.health = flag.max_flag_health;
1115 flag.event_damage = ctf_FlagDamage;
1116 flag.pushable = true;
1117 flag.teleportable = TELEPORT_NORMAL;
1118 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1119 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1120 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1121 flag.velocity = '0 0 0';
1122 flag.mangle = flag.angles;
1123 flag.reset = ctf_Reset;
1124 flag.touch = ctf_FlagTouch;
1125 flag.think = ctf_FlagThink;
1126 flag.nextthink = time + FLAG_THINKRATE;
1127 flag.ctf_status = FLAG_BASE;
1130 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1131 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1132 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1133 set_flag_string(flag, toucheffect, "%sflag_touch", teamname);
1134 set_flag_string(flag, passeffect, "%s_pass", teamname);
1135 set_flag_string(flag, capeffect, "%s_cap", teamname);
1138 set_flag_string(flag, snd_flag_taken, "ctf/%s_taken.wav", teamname);
1139 set_flag_string(flag, snd_flag_returned, "ctf/%s_returned.wav", teamname);
1140 set_flag_string(flag, snd_flag_capture, "ctf/%s_capture.wav", teamname);
1141 set_flag_string(flag, snd_flag_dropped, "ctf/%s_dropped.wav", teamname);
1142 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.
1143 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1144 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1147 precache_sound(flag.snd_flag_taken);
1148 precache_sound(flag.snd_flag_returned);
1149 precache_sound(flag.snd_flag_capture);
1150 precache_sound(flag.snd_flag_respawn);
1151 precache_sound(flag.snd_flag_dropped);
1152 precache_sound(flag.snd_flag_touch);
1153 precache_sound(flag.snd_flag_pass);
1154 precache_model(flag.model);
1155 precache_model("models/ctf/shield.md3");
1156 precache_model("models/ctf/shockwavetransring.md3");
1159 setmodel(flag, flag.model); // precision set below
1160 setsize(flag, FLAG_MIN, FLAG_MAX);
1161 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1163 if(autocvar_g_ctf_flag_glowtrails)
1167 case NUM_TEAM_1: flag.glow_color = 251; break;
1168 case NUM_TEAM_2: flag.glow_color = 210; break;
1169 case NUM_TEAM_3: flag.glow_color = 110; break;
1170 case NUM_TEAM_4: flag.glow_color = 145; break;
1171 default: flag.glow_color = 254; break;
1173 flag.glow_size = 25;
1174 flag.glow_trail = 1;
1177 flag.effects |= EF_LOWPRECISION;
1178 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1179 if(autocvar_g_ctf_dynamiclights)
1183 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1184 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1185 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1186 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1187 default: flag.effects |= EF_DIMLIGHT; break;
1192 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1194 flag.dropped_origin = flag.origin;
1195 flag.noalign = true;
1196 flag.movetype = MOVETYPE_NONE;
1198 else // drop to floor, automatically find a platform and set that as spawn origin
1200 flag.noalign = false;
1203 flag.movetype = MOVETYPE_TOSS;
1206 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1214 // NOTE: LEGACY CODE, needs to be re-written!
1216 void havocbot_calculate_middlepoint()
1220 vector fo = '0 0 0';
1223 f = ctf_worldflaglist;
1228 f = f.ctf_worldflagnext;
1232 havocbot_ctf_middlepoint = s * (1.0 / n);
1233 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1237 entity havocbot_ctf_find_flag(entity bot)
1240 f = ctf_worldflaglist;
1243 if (CTF_SAMETEAM(bot, f))
1245 f = f.ctf_worldflagnext;
1250 entity havocbot_ctf_find_enemy_flag(entity bot)
1253 f = ctf_worldflaglist;
1258 if(CTF_DIFFTEAM(bot, f))
1265 else if(!bot.flagcarried)
1269 else if (CTF_DIFFTEAM(bot, f))
1271 f = f.ctf_worldflagnext;
1276 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1284 FOR_EACH_PLAYER(head)
1286 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1289 if(vlen(head.origin - org) < tc_radius)
1296 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1299 head = ctf_worldflaglist;
1302 if (CTF_SAMETEAM(self, head))
1304 head = head.ctf_worldflagnext;
1307 navigation_routerating(head, ratingscale, 10000);
1310 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1313 head = ctf_worldflaglist;
1316 if (CTF_SAMETEAM(self, head))
1318 head = head.ctf_worldflagnext;
1323 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1326 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1329 head = ctf_worldflaglist;
1334 if(CTF_DIFFTEAM(self, head))
1338 if(self.flagcarried)
1341 else if(!self.flagcarried)
1345 else if(CTF_DIFFTEAM(self, head))
1347 head = head.ctf_worldflagnext;
1350 navigation_routerating(head, ratingscale, 10000);
1353 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1355 if (!bot_waypoints_for_items)
1357 havocbot_goalrating_ctf_enemyflag(ratingscale);
1363 head = havocbot_ctf_find_enemy_flag(self);
1368 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1371 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1375 mf = havocbot_ctf_find_flag(self);
1377 if(mf.ctf_status == FLAG_BASE)
1381 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1384 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1387 head = ctf_worldflaglist;
1390 // flag is out in the field
1391 if(head.ctf_status != FLAG_BASE)
1392 if(head.tag_entity==world) // dropped
1396 if(vlen(org-head.origin)<df_radius)
1397 navigation_routerating(head, ratingscale, 10000);
1400 navigation_routerating(head, ratingscale, 10000);
1403 head = head.ctf_worldflagnext;
1407 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1411 head = findchainfloat(bot_pickup, true);
1414 // gather health and armor only
1416 if (head.health || head.armorvalue)
1417 if (vlen(head.origin - org) < sradius)
1419 // get the value of the item
1420 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1422 navigation_routerating(head, t * ratingscale, 500);
1428 void havocbot_ctf_reset_role(entity bot)
1430 float cdefense, cmiddle, coffense;
1431 entity mf, ef, head;
1434 if(bot.deadflag != DEAD_NO)
1437 if(vlen(havocbot_ctf_middlepoint)==0)
1438 havocbot_calculate_middlepoint();
1441 if (bot.flagcarried)
1443 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1447 mf = havocbot_ctf_find_flag(bot);
1448 ef = havocbot_ctf_find_enemy_flag(bot);
1450 // Retrieve stolen flag
1451 if(mf.ctf_status!=FLAG_BASE)
1453 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1457 // If enemy flag is taken go to the middle to intercept pursuers
1458 if(ef.ctf_status!=FLAG_BASE)
1460 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1464 // if there is only me on the team switch to offense
1466 FOR_EACH_PLAYER(head)
1467 if(SAME_TEAM(head, bot))
1472 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1476 // Evaluate best position to take
1477 // Count mates on middle position
1478 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1480 // Count mates on defense position
1481 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1483 // Count mates on offense position
1484 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1486 if(cdefense<=coffense)
1487 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1488 else if(coffense<=cmiddle)
1489 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1491 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1494 void havocbot_role_ctf_carrier()
1496 if(self.deadflag != DEAD_NO)
1498 havocbot_ctf_reset_role(self);
1502 if (self.flagcarried == world)
1504 havocbot_ctf_reset_role(self);
1508 if (self.bot_strategytime < time)
1510 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1512 navigation_goalrating_start();
1514 havocbot_goalrating_ctf_enemybase(50000);
1516 havocbot_goalrating_ctf_ourbase(50000);
1519 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1521 navigation_goalrating_end();
1523 if (self.navigation_hasgoals)
1524 self.havocbot_cantfindflag = time + 10;
1525 else if (time > self.havocbot_cantfindflag)
1527 // Can't navigate to my own base, suicide!
1528 // TODO: drop it and wander around
1529 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1535 void havocbot_role_ctf_escort()
1539 if(self.deadflag != DEAD_NO)
1541 havocbot_ctf_reset_role(self);
1545 if (self.flagcarried)
1547 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1551 // If enemy flag is back on the base switch to previous role
1552 ef = havocbot_ctf_find_enemy_flag(self);
1553 if(ef.ctf_status==FLAG_BASE)
1555 self.havocbot_role = self.havocbot_previous_role;
1556 self.havocbot_role_timeout = 0;
1560 // If the flag carrier reached the base switch to defense
1561 mf = havocbot_ctf_find_flag(self);
1562 if(mf.ctf_status!=FLAG_BASE)
1563 if(vlen(ef.origin - mf.dropped_origin) < 300)
1565 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1569 // Set the role timeout if necessary
1570 if (!self.havocbot_role_timeout)
1572 self.havocbot_role_timeout = time + random() * 30 + 60;
1575 // If nothing happened just switch to previous role
1576 if (time > self.havocbot_role_timeout)
1578 self.havocbot_role = self.havocbot_previous_role;
1579 self.havocbot_role_timeout = 0;
1583 // Chase the flag carrier
1584 if (self.bot_strategytime < time)
1586 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1587 navigation_goalrating_start();
1588 havocbot_goalrating_ctf_enemyflag(30000);
1589 havocbot_goalrating_ctf_ourstolenflag(40000);
1590 havocbot_goalrating_items(10000, self.origin, 10000);
1591 navigation_goalrating_end();
1595 void havocbot_role_ctf_offense()
1600 if(self.deadflag != DEAD_NO)
1602 havocbot_ctf_reset_role(self);
1606 if (self.flagcarried)
1608 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1613 mf = havocbot_ctf_find_flag(self);
1614 ef = havocbot_ctf_find_enemy_flag(self);
1617 if(mf.ctf_status!=FLAG_BASE)
1620 pos = mf.tag_entity.origin;
1624 // Try to get it if closer than the enemy base
1625 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1627 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1632 // Escort flag carrier
1633 if(ef.ctf_status!=FLAG_BASE)
1636 pos = ef.tag_entity.origin;
1640 if(vlen(pos-mf.dropped_origin)>700)
1642 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1647 // About to fail, switch to middlefield
1650 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1654 // Set the role timeout if necessary
1655 if (!self.havocbot_role_timeout)
1656 self.havocbot_role_timeout = time + 120;
1658 if (time > self.havocbot_role_timeout)
1660 havocbot_ctf_reset_role(self);
1664 if (self.bot_strategytime < time)
1666 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1667 navigation_goalrating_start();
1668 havocbot_goalrating_ctf_ourstolenflag(50000);
1669 havocbot_goalrating_ctf_enemybase(20000);
1670 havocbot_goalrating_items(5000, self.origin, 1000);
1671 havocbot_goalrating_items(1000, self.origin, 10000);
1672 navigation_goalrating_end();
1676 // Retriever (temporary role):
1677 void havocbot_role_ctf_retriever()
1681 if(self.deadflag != DEAD_NO)
1683 havocbot_ctf_reset_role(self);
1687 if (self.flagcarried)
1689 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1693 // If flag is back on the base switch to previous role
1694 mf = havocbot_ctf_find_flag(self);
1695 if(mf.ctf_status==FLAG_BASE)
1697 havocbot_ctf_reset_role(self);
1701 if (!self.havocbot_role_timeout)
1702 self.havocbot_role_timeout = time + 20;
1704 if (time > self.havocbot_role_timeout)
1706 havocbot_ctf_reset_role(self);
1710 if (self.bot_strategytime < time)
1715 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1716 navigation_goalrating_start();
1717 havocbot_goalrating_ctf_ourstolenflag(50000);
1718 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1719 havocbot_goalrating_ctf_enemybase(30000);
1720 havocbot_goalrating_items(500, self.origin, rt_radius);
1721 navigation_goalrating_end();
1725 void havocbot_role_ctf_middle()
1729 if(self.deadflag != DEAD_NO)
1731 havocbot_ctf_reset_role(self);
1735 if (self.flagcarried)
1737 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1741 mf = havocbot_ctf_find_flag(self);
1742 if(mf.ctf_status!=FLAG_BASE)
1744 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1748 if (!self.havocbot_role_timeout)
1749 self.havocbot_role_timeout = time + 10;
1751 if (time > self.havocbot_role_timeout)
1753 havocbot_ctf_reset_role(self);
1757 if (self.bot_strategytime < time)
1761 org = havocbot_ctf_middlepoint;
1762 org.z = self.origin.z;
1764 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1765 navigation_goalrating_start();
1766 havocbot_goalrating_ctf_ourstolenflag(50000);
1767 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1768 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1769 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1770 havocbot_goalrating_items(2500, self.origin, 10000);
1771 havocbot_goalrating_ctf_enemybase(2500);
1772 navigation_goalrating_end();
1776 void havocbot_role_ctf_defense()
1780 if(self.deadflag != DEAD_NO)
1782 havocbot_ctf_reset_role(self);
1786 if (self.flagcarried)
1788 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1792 // If own flag was captured
1793 mf = havocbot_ctf_find_flag(self);
1794 if(mf.ctf_status!=FLAG_BASE)
1796 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1800 if (!self.havocbot_role_timeout)
1801 self.havocbot_role_timeout = time + 30;
1803 if (time > self.havocbot_role_timeout)
1805 havocbot_ctf_reset_role(self);
1808 if (self.bot_strategytime < time)
1813 org = mf.dropped_origin;
1814 mp_radius = havocbot_ctf_middlepoint_radius;
1816 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1817 navigation_goalrating_start();
1819 // if enemies are closer to our base, go there
1820 entity head, closestplayer = world;
1821 float distance, bestdistance = 10000;
1822 FOR_EACH_PLAYER(head)
1824 if(head.deadflag!=DEAD_NO)
1827 distance = vlen(org - head.origin);
1828 if(distance<bestdistance)
1830 closestplayer = head;
1831 bestdistance = distance;
1836 if(DIFF_TEAM(closestplayer, self))
1837 if(vlen(org - self.origin)>1000)
1838 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1839 havocbot_goalrating_ctf_ourbase(30000);
1841 havocbot_goalrating_ctf_ourstolenflag(20000);
1842 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1843 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1844 havocbot_goalrating_items(10000, org, mp_radius);
1845 havocbot_goalrating_items(5000, self.origin, 10000);
1846 navigation_goalrating_end();
1850 void havocbot_role_ctf_setrole(entity bot, int role)
1852 dprint(strcat(bot.netname," switched to "));
1855 case HAVOCBOT_CTF_ROLE_CARRIER:
1857 bot.havocbot_role = havocbot_role_ctf_carrier;
1858 bot.havocbot_role_timeout = 0;
1859 bot.havocbot_cantfindflag = time + 10;
1860 bot.bot_strategytime = 0;
1862 case HAVOCBOT_CTF_ROLE_DEFENSE:
1864 bot.havocbot_role = havocbot_role_ctf_defense;
1865 bot.havocbot_role_timeout = 0;
1867 case HAVOCBOT_CTF_ROLE_MIDDLE:
1869 bot.havocbot_role = havocbot_role_ctf_middle;
1870 bot.havocbot_role_timeout = 0;
1872 case HAVOCBOT_CTF_ROLE_OFFENSE:
1874 bot.havocbot_role = havocbot_role_ctf_offense;
1875 bot.havocbot_role_timeout = 0;
1877 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1878 dprint("retriever");
1879 bot.havocbot_previous_role = bot.havocbot_role;
1880 bot.havocbot_role = havocbot_role_ctf_retriever;
1881 bot.havocbot_role_timeout = time + 10;
1882 bot.bot_strategytime = 0;
1884 case HAVOCBOT_CTF_ROLE_ESCORT:
1886 bot.havocbot_previous_role = bot.havocbot_role;
1887 bot.havocbot_role = havocbot_role_ctf_escort;
1888 bot.havocbot_role_timeout = time + 30;
1889 bot.bot_strategytime = 0;
1900 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1903 int t = 0, t2 = 0, t3 = 0;
1905 // initially clear items so they can be set as necessary later.
1906 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
1907 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
1908 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
1909 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
1910 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
1911 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
1913 // scan through all the flags and notify the client about them
1914 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1916 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
1917 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
1918 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
1919 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
1920 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; }
1922 switch(flag.ctf_status)
1927 if((flag.owner == self) || (flag.pass_sender == self))
1928 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
1930 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
1935 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
1941 // item for stopping players from capturing the flag too often
1942 if(self.ctf_captureshielded)
1943 self.ctf_flagstatus |= CTF_SHIELDED;
1945 // update the health of the flag carrier waypointsprite
1946 if(self.wps_flagcarrier)
1947 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1952 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1954 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1956 if(frag_target == frag_attacker) // damage done to yourself
1958 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1959 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1961 else // damage done to everyone else
1963 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1964 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1967 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1969 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)))
1970 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1972 frag_target.wps_helpme_time = time;
1973 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1975 // todo: add notification for when flag carrier needs help?
1980 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1982 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1984 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1985 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1988 if(frag_target.flagcarried)
1990 entity tmp_entity = frag_target.flagcarried;
1991 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
1992 tmp_entity.ctf_dropper = world;
1998 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
2001 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2004 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
2006 entity flag; // temporary entity for the search method
2008 if(self.flagcarried)
2009 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2011 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2013 if(flag.pass_sender == self) { flag.pass_sender = world; }
2014 if(flag.pass_target == self) { flag.pass_target = world; }
2015 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
2021 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
2023 if(self.flagcarried)
2024 if(!autocvar_g_ctf_portalteleport)
2025 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2030 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
2032 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2034 entity player = self;
2036 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2038 // pass the flag to a team mate
2039 if(autocvar_g_ctf_pass)
2041 entity head, closest_target = world;
2042 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2044 while(head) // find the closest acceptable target to pass to
2046 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2047 if(head != player && SAME_TEAM(head, player))
2048 if(!head.speedrunning && !head.vehicle)
2050 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2051 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2052 vector passer_center = CENTER_OR_VIEWOFS(player);
2054 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2056 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2058 if(IS_BOT_CLIENT(head))
2060 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2061 ctf_Handle_Throw(head, player, DROP_PASS);
2065 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2066 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2068 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2071 else if(player.flagcarried)
2075 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2076 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2077 { closest_target = head; }
2079 else { closest_target = head; }
2086 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2089 // throw the flag in front of you
2090 if(autocvar_g_ctf_throw && player.flagcarried)
2092 if(player.throw_count == -1)
2094 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2096 player.throw_prevtime = time;
2097 player.throw_count = 1;
2098 ctf_Handle_Throw(player, world, DROP_THROW);
2103 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2109 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2110 else { player.throw_count += 1; }
2111 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2113 player.throw_prevtime = time;
2114 ctf_Handle_Throw(player, world, DROP_THROW);
2123 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
2125 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2127 self.wps_helpme_time = time;
2128 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2130 else // create a normal help me waypointsprite
2132 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME);
2133 WaypointSprite_Ping(self.wps_helpme);
2139 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
2141 if(vh_player.flagcarried)
2143 vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
2145 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2147 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2151 setattachment(vh_player.flagcarried, vh_vehicle, "");
2152 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2153 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2154 //vh_player.flagcarried.angles = '0 0 0';
2162 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2164 if(vh_player.flagcarried)
2166 setattachment(vh_player.flagcarried, vh_player, "");
2167 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2168 vh_player.flagcarried.scale = FLAG_SCALE;
2169 vh_player.flagcarried.angles = '0 0 0';
2170 vh_player.flagcarried.nodrawtoclient = world;
2177 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2179 if(self.flagcarried)
2181 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));
2182 ctf_RespawnFlag(self.flagcarried);
2189 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2191 entity flag; // temporary entity for the search method
2193 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2195 switch(flag.ctf_status)
2200 // lock the flag, game is over
2201 flag.movetype = MOVETYPE_NONE;
2202 flag.takedamage = DAMAGE_NO;
2203 flag.solid = SOLID_NOT;
2204 flag.nextthink = false; // stop thinking
2206 //dprint("stopping the ", flag.netname, " from moving.\n");
2214 // do nothing for these flags
2223 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2225 havocbot_ctf_reset_role(self);
2229 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2231 //ret_float = ctf_teams;
2232 ret_string = "ctf_team";
2236 MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
2238 self.ctf_flagstatus = other.ctf_flagstatus;
2247 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2248 CTF Starting point for a player in team one (Red).
2249 Keys: "angle" viewing angle when spawning. */
2250 void spawnfunc_info_player_team1()
2252 if(g_assault) { remove(self); return; }
2254 self.team = NUM_TEAM_1; // red
2255 spawnfunc_info_player_deathmatch();
2259 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2260 CTF Starting point for a player in team two (Blue).
2261 Keys: "angle" viewing angle when spawning. */
2262 void spawnfunc_info_player_team2()
2264 if(g_assault) { remove(self); return; }
2266 self.team = NUM_TEAM_2; // blue
2267 spawnfunc_info_player_deathmatch();
2270 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2271 CTF Starting point for a player in team three (Yellow).
2272 Keys: "angle" viewing angle when spawning. */
2273 void spawnfunc_info_player_team3()
2275 if(g_assault) { remove(self); return; }
2277 self.team = NUM_TEAM_3; // yellow
2278 spawnfunc_info_player_deathmatch();
2282 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2283 CTF Starting point for a player in team four (Purple).
2284 Keys: "angle" viewing angle when spawning. */
2285 void spawnfunc_info_player_team4()
2287 if(g_assault) { remove(self); return; }
2289 self.team = NUM_TEAM_4; // purple
2290 spawnfunc_info_player_deathmatch();
2293 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2294 CTF flag for team one (Red).
2296 "angle" Angle the flag will point (minus 90 degrees)...
2297 "model" model to use, note this needs red and blue as skins 0 and 1...
2298 "noise" sound played when flag is picked up...
2299 "noise1" sound played when flag is returned by a teammate...
2300 "noise2" sound played when flag is captured...
2301 "noise3" sound played when flag is lost in the field and respawns itself...
2302 "noise4" sound played when flag is dropped by a player...
2303 "noise5" sound played when flag touches the ground... */
2304 void spawnfunc_item_flag_team1()
2306 if(!g_ctf) { remove(self); return; }
2308 ctf_FlagSetup(NUM_TEAM_1, self);
2311 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2312 CTF flag for team two (Blue).
2314 "angle" Angle the flag will point (minus 90 degrees)...
2315 "model" model to use, note this needs red and blue as skins 0 and 1...
2316 "noise" sound played when flag is picked up...
2317 "noise1" sound played when flag is returned by a teammate...
2318 "noise2" sound played when flag is captured...
2319 "noise3" sound played when flag is lost in the field and respawns itself...
2320 "noise4" sound played when flag is dropped by a player...
2321 "noise5" sound played when flag touches the ground... */
2322 void spawnfunc_item_flag_team2()
2324 if(!g_ctf) { remove(self); return; }
2326 ctf_FlagSetup(NUM_TEAM_2, self);
2329 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2330 CTF flag for team three (Yellow).
2332 "angle" Angle the flag will point (minus 90 degrees)...
2333 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2334 "noise" sound played when flag is picked up...
2335 "noise1" sound played when flag is returned by a teammate...
2336 "noise2" sound played when flag is captured...
2337 "noise3" sound played when flag is lost in the field and respawns itself...
2338 "noise4" sound played when flag is dropped by a player...
2339 "noise5" sound played when flag touches the ground... */
2340 void spawnfunc_item_flag_team3()
2342 if(!g_ctf) { remove(self); return; }
2344 ctf_FlagSetup(NUM_TEAM_3, self);
2347 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2348 CTF flag for team four (Pink).
2350 "angle" Angle the flag will point (minus 90 degrees)...
2351 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2352 "noise" sound played when flag is picked up...
2353 "noise1" sound played when flag is returned by a teammate...
2354 "noise2" sound played when flag is captured...
2355 "noise3" sound played when flag is lost in the field and respawns itself...
2356 "noise4" sound played when flag is dropped by a player...
2357 "noise5" sound played when flag touches the ground... */
2358 void spawnfunc_item_flag_team4()
2360 if(!g_ctf) { remove(self); return; }
2362 ctf_FlagSetup(NUM_TEAM_4, self);
2365 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2368 "angle" Angle the flag will point (minus 90 degrees)...
2369 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2370 "noise" sound played when flag is picked up...
2371 "noise1" sound played when flag is returned by a teammate...
2372 "noise2" sound played when flag is captured...
2373 "noise3" sound played when flag is lost in the field and respawns itself...
2374 "noise4" sound played when flag is dropped by a player...
2375 "noise5" sound played when flag touches the ground... */
2376 void spawnfunc_item_flag_neutral()
2378 if(!g_ctf) { remove(self); return; }
2379 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2381 ctf_FlagSetup(0, self);
2384 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2385 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2386 Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too.
2388 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2389 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2390 void spawnfunc_ctf_team()
2392 if(!g_ctf) { remove(self); return; }
2394 self.classname = "ctf_team";
2395 self.team = self.cnt + 1;
2398 // compatibility for quake maps
2399 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2400 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2401 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2402 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2403 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2404 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2406 void team_CTF_neutralflag() { spawnfunc_item_flag_neutral(); }
2407 void team_neutralobelisk() { spawnfunc_item_flag_neutral(); }
2415 void ctf_ScoreRules(int teams)
2417 CheckAllowedTeams(world);
2418 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2419 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2420 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2421 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2422 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2423 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2424 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2425 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2426 ScoreRules_basics_end();
2429 // code from here on is just to support maps that don't have flag and team entities
2430 void ctf_SpawnTeam (string teamname, int teamcolor)
2435 self.classname = "ctf_team";
2436 self.netname = teamname;
2437 self.cnt = teamcolor;
2439 spawnfunc_ctf_team();
2444 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2449 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2451 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2452 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2453 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2456 ctf_teams = bound(2, ctf_teams, 4);
2458 // if no teams are found, spawn defaults
2459 if(find(world, classname, "ctf_team") == world)
2461 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2462 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2463 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2465 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2467 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2470 ctf_ScoreRules(ctf_teams);
2473 void ctf_Initialize()
2475 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2477 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2478 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2479 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2481 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2483 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2487 MUTATOR_DEFINITION(gamemode_ctf)
2489 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2490 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2491 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2492 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2493 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2494 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2495 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2496 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2497 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2498 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2499 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2500 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2501 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2502 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2503 MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
2504 MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
2508 if(time > 1) // game loads at time 1
2509 error("This is a game type and it cannot be added at runtime.");
2513 MUTATOR_ONROLLBACK_OR_REMOVE
2515 // we actually cannot roll back ctf_Initialize here
2516 // BUT: we don't need to! If this gets called, adding always
2522 print("This is a game type and it cannot be removed at runtime.");