1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: September, 2012
4 // ================================================================
6 void ctf_FakeTimeLimit(entity e, float t)
9 WriteByte(MSG_ONE, 3); // svc_updatestat
10 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
12 WriteCoord(MSG_ONE, autocvar_timelimit);
14 WriteCoord(MSG_ONE, (t + 1) / 60);
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
23 void ctf_CaptureRecord(entity flag, entity player)
26 float notification, success;
27 float cap_record = ctf_captimerecord;
28 float cap_time = (time - flag.ctf_pickuptime);
29 float f1, f2 = NO_FL_ARG;
30 string s1, s2 = NO_STR_ARG;
31 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
34 if(!ctf_captimerecord)
35 { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_); s1 = player.netname; f1 = (cap_time * 100); success = TRUE; }
36 else if(cap_time < cap_record)
37 { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = TRUE; }
39 { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = FALSE; }
42 FOR_EACH_REALCLIENT(tmp_entity)
44 if not(tmp_entity.CAPTURE_VERBOSE) { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_); s2 = NO_STR_ARG; f1 = f2 = NO_FL_ARG; }
45 Send_Notification_Legacy_Wrapper(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, notification, s1, s2, f1, f2, NO_FL_ARG);
48 // notify server log too
49 if not(autocvar_notification_ctf_capture_verbose) { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_); s2 = NO_STR_ARG; f1 = f2 = NO_FL_ARG; }
50 Local_Notification_Without_VarArgs(MSG_INFO, notification, Get_Notif_Strnum(MSG_INFO, notification), Get_Notif_Flnum(MSG_INFO, notification), s1, s2, NO_STR_ARG, NO_STR_ARG, f1, f2, NO_FL_ARG, NO_FL_ARG);
52 // write that shit in the database
55 ctf_captimerecord = cap_time;
56 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
57 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
58 write_recordmarker(player, (time - cap_time), cap_time);
62 void ctf_FlagcarrierWaypoints(entity player)
64 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
65 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
66 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
67 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
70 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
72 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
73 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
74 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
75 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
78 if(current_height) // make sure we can actually do this arcing path
80 targpos = (to + ('0 0 1' * current_height));
81 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
82 if(trace_fraction < 1)
84 //print("normal arc line failed, trying to find new pos...");
85 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
86 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
87 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
88 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
89 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
92 else { targpos = to; }
94 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
96 vector desired_direction = normalize(targpos - from);
97 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
98 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
101 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
103 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
105 // directional tracing only
107 makevectors(passer_angle);
109 // find the closest point on the enemy to the center of the attack
110 float ang; // angle between shotdir and h
111 float h; // hypotenuse, which is the distance between attacker to head
112 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
114 h = vlen(head_center - passer_center);
115 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
118 vector nearest_on_line = (passer_center + a * v_forward);
119 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
121 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
122 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
124 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
129 else { return TRUE; }
133 // =======================
134 // CaptureShield Functions
135 // =======================
137 float ctf_CaptureShield_CheckStatus(entity p)
141 float players_worseeq, players_total;
143 if(ctf_captureshield_max_ratio <= 0)
146 s = PlayerScore_Add(p, SP_SCORE, 0);
147 if(s >= -ctf_captureshield_min_negscore)
150 players_total = players_worseeq = 0;
153 if(IsDifferentTeam(e, p))
155 se = PlayerScore_Add(e, SP_SCORE, 0);
161 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
162 // use this rule here
164 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
170 void ctf_CaptureShield_Update(entity player, float wanted_status)
172 float updated_status = ctf_CaptureShield_CheckStatus(player);
173 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
175 if(updated_status) // TODO csqc notifier for this // Samual: How?
176 Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
178 Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_FREE, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
180 player.ctf_captureshielded = updated_status;
184 float ctf_CaptureShield_Customize()
186 if(!other.ctf_captureshielded) { return FALSE; }
187 if(!IsDifferentTeam(self, other)) { return FALSE; }
192 void ctf_CaptureShield_Touch()
194 if(!other.ctf_captureshielded) { return; }
195 if(!IsDifferentTeam(self, other)) { return; }
197 vector mymid = (self.absmin + self.absmax) * 0.5;
198 vector othermid = (other.absmin + other.absmax) * 0.5;
200 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
201 Send_Notification_Legacy_Wrapper(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
204 void ctf_CaptureShield_Spawn(entity flag)
206 entity shield = spawn();
209 shield.team = self.team;
210 shield.touch = ctf_CaptureShield_Touch;
211 shield.customizeentityforclient = ctf_CaptureShield_Customize;
212 shield.classname = "ctf_captureshield";
213 shield.effects = EF_ADDITIVE;
214 shield.movetype = MOVETYPE_NOCLIP;
215 shield.solid = SOLID_TRIGGER;
216 shield.avelocity = '7 0 11';
219 setorigin(shield, self.origin);
220 setmodel(shield, "models/ctf/shield.md3");
221 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
225 // ====================
226 // Drop/Pass/Throw Code
227 // ====================
229 void ctf_Handle_Drop(entity flag, entity player, float droptype)
232 player = (player ? player : flag.pass_sender);
235 flag.movetype = MOVETYPE_TOSS;
236 flag.takedamage = DAMAGE_YES;
237 flag.angles = '0 0 0';
238 flag.health = flag.max_flag_health;
239 flag.ctf_droptime = time;
240 flag.ctf_dropper = player;
241 flag.ctf_status = FLAG_DROPPED;
243 // messages and sounds
244 Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
245 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
246 ctf_EventLog("dropped", player.team, player);
249 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
250 PlayerScore_Add(player, SP_CTF_DROPS, 1);
253 if(autocvar_g_ctf_flag_dropped_waypoint)
254 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));
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;
282 setattachment(flag, player, "");
283 setorigin(flag, FLAG_CARRY_OFFSET);
284 flag.movetype = MOVETYPE_NONE;
285 flag.takedamage = DAMAGE_NO;
286 flag.solid = SOLID_NOT;
287 flag.angles = '0 0 0';
288 flag.ctf_status = FLAG_CARRY;
290 // messages and sounds
291 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
292 ctf_EventLog("receive", flag.team, player);
294 FOR_EACH_REALPLAYER(tmp_player)
296 if(tmp_player == sender)
297 Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
298 else if(tmp_player == player)
299 Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
300 else if(!IsDifferentTeam(tmp_player, sender))
301 Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
304 // create new waypoint
305 ctf_FlagcarrierWaypoints(player);
307 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
308 player.throw_antispam = sender.throw_antispam;
310 flag.pass_distance = 0;
311 flag.pass_sender = world;
312 flag.pass_target = world;
315 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
317 entity flag = player.flagcarried;
318 vector targ_origin, flag_velocity;
320 if(!flag) { return; }
321 if((droptype == DROP_PASS) && !receiver) { return; }
323 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
326 setattachment(flag, world, "");
327 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
328 flag.owner.flagcarried = world;
330 flag.solid = SOLID_TRIGGER;
331 flag.ctf_dropper = player;
332 flag.ctf_droptime = time;
334 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
341 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
342 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
343 WarpZone_RefSys_Copy(flag, receiver);
344 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
345 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
347 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
348 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
351 flag.movetype = MOVETYPE_FLY;
352 flag.takedamage = DAMAGE_NO;
353 flag.pass_sender = player;
354 flag.pass_target = receiver;
355 flag.ctf_status = FLAG_PASSING;
358 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
359 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
360 ctf_EventLog("pass", flag.team, player);
366 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'));
368 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)));
369 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
370 ctf_Handle_Drop(flag, player, droptype);
376 flag.velocity = '0 0 0'; // do nothing
383 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);
384 ctf_Handle_Drop(flag, player, droptype);
389 // kill old waypointsprite
390 WaypointSprite_Ping(player.wps_flagcarrier);
391 WaypointSprite_Kill(player.wps_flagcarrier);
393 if(player.wps_enemyflagcarrier)
394 WaypointSprite_Kill(player.wps_enemyflagcarrier);
397 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
405 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
407 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
408 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
409 float old_time, new_time;
411 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
413 // messages and sounds
414 ctf_CaptureRecord(enemy_flag, player);
415 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
419 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
420 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
425 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
426 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
428 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
429 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
430 if(!old_time || new_time < old_time)
431 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
434 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
435 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
438 if(capturetype == CAPTURE_NORMAL)
440 WaypointSprite_Kill(player.wps_flagcarrier);
441 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
443 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
444 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
448 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
449 ctf_RespawnFlag(enemy_flag);
452 void ctf_Handle_Return(entity flag, entity player)
454 // messages and sounds
455 Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
456 Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
457 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
458 ctf_EventLog("return", flag.team, player);
461 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
462 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
464 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
468 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
469 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
470 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
474 ctf_RespawnFlag(flag);
477 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
480 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
481 float pickup_dropped_score; // used to calculate dropped pickup score
483 // attach the flag to the player
485 player.flagcarried = flag;
486 setattachment(flag, player, "");
487 setorigin(flag, FLAG_CARRY_OFFSET);
490 flag.movetype = MOVETYPE_NONE;
491 flag.takedamage = DAMAGE_NO;
492 flag.solid = SOLID_NOT;
493 flag.angles = '0 0 0';
494 flag.ctf_status = FLAG_CARRY;
498 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
499 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
503 // messages and sounds
504 Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
505 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
507 FOR_EACH_REALPLAYER(tmp_player)
509 if(tmp_player == player)
511 Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
512 if(ctf_stalemate) { Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); }
514 else if(!IsDifferentTeam(tmp_player, player) && tmp_player != player)
515 Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, (tmp_player.PICKUP_TEAM_VERBOSE ? CENTER_CTF_PICKUP_TEAM_VERBOSE : CENTER_CTF_PICKUP_TEAM), Team_ColorCode(player.team), (tmp_player.PICKUP_TEAM_VERBOSE ? player.netname : NO_STR_ARG), NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
516 else if(IsDifferentTeam(tmp_player, player))
517 Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, (tmp_player.PICKUP_ENEMY_VERBOSE ? CENTER_CTF_PICKUP_ENEMY_VERBOSE : CENTER_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), (tmp_player.PICKUP_ENEMY_VERBOSE ? player.netname : NO_STR_ARG), NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
521 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
526 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
527 ctf_EventLog("steal", flag.team, player);
533 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);
534 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);
535 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
536 PlayerTeamScore_AddScore(player, pickup_dropped_score);
537 ctf_EventLog("pickup", flag.team, player);
545 if(pickuptype == PICKUP_BASE)
547 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
548 if((player.speedrunning) && (ctf_captimerecord))
549 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
553 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
556 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
557 ctf_FlagcarrierWaypoints(player);
558 WaypointSprite_Ping(player.wps_flagcarrier);
562 // ===================
563 // Main Flag Functions
564 // ===================
566 void ctf_CheckFlagReturn(entity flag, float returntype)
568 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
570 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
572 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
576 case RETURN_DROPPED: Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
577 case RETURN_DAMAGE: Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
578 case RETURN_SPEEDRUN: Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), NO_STR_ARG, NO_STR_ARG, ctf_captimerecord, NO_FL_ARG, NO_FL_ARG); break;
579 case RETURN_NEEDKILL: Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
583 { Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break; }
585 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
586 ctf_EventLog("returned", flag.team, world);
587 ctf_RespawnFlag(flag);
592 void ctf_CheckStalemate(void)
595 float stale_red_flags = 0, stale_blue_flags = 0;
598 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
600 // build list of stale flags
601 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
603 if(autocvar_g_ctf_stalemate)
604 if(tmp_entity.ctf_status != FLAG_BASE)
605 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
607 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
608 ctf_staleflaglist = tmp_entity;
610 switch(tmp_entity.team)
612 case FL_TEAM_1: ++stale_red_flags; break;
613 case FL_TEAM_2: ++stale_blue_flags; break;
618 if(stale_red_flags && stale_blue_flags)
619 ctf_stalemate = TRUE;
620 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
621 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
622 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
623 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
625 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
628 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
630 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
631 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));
634 if not(wpforenemy_announced)
636 FOR_EACH_REALPLAYER(tmp_entity)
637 if(tmp_entity.flagcarried)
638 Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_entity, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
640 Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_entity, MSG_CENTER, CENTER_CTF_STALEMATE_OTHER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
642 wpforenemy_announced = TRUE;
647 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
649 if(ITEM_DAMAGE_NEEDKILL(deathtype))
651 // automatically kill the flag and return it
653 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
656 if(autocvar_g_ctf_flag_return_damage)
658 // reduce health and check if it should be returned
659 self.health = self.health - damage;
660 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
670 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
673 if(self == ctf_worldflaglist) // only for the first flag
674 FOR_EACH_CLIENT(tmp_entity)
675 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
678 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
679 dprint("wtf the flag got squashed?\n");
680 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
681 if(!trace_startsolid) // can we resize it without getting stuck?
682 setsize(self, FLAG_MIN, FLAG_MAX); }
684 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
688 self.angles = '0 0 0';
696 switch(self.ctf_status)
700 if(autocvar_g_ctf_dropped_capture_radius)
702 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
703 if(tmp_entity.ctf_status == FLAG_DROPPED)
704 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
705 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
706 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
713 if(autocvar_g_ctf_flag_dropped_floatinwater)
715 vector midpoint = ((self.absmin + self.absmax) * 0.5);
716 if(pointcontents(midpoint) == CONTENT_WATER)
718 self.velocity = self.velocity * 0.5;
720 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
721 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
723 { self.movetype = MOVETYPE_FLY; }
725 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
727 if(autocvar_g_ctf_flag_return_dropped)
729 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
732 ctf_CheckFlagReturn(self, RETURN_DROPPED);
736 if(autocvar_g_ctf_flag_return_time)
738 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
739 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
747 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
750 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
754 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
758 if(autocvar_g_ctf_stalemate)
760 if(time >= wpforenemy_nextthink)
762 ctf_CheckStalemate();
763 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
771 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
772 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
773 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
775 if((self.pass_target == world)
776 || (self.pass_target.deadflag != DEAD_NO)
777 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
778 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
779 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
781 // give up, pass failed
782 ctf_Handle_Drop(self, world, DROP_PASS);
786 // still a viable target, go for it
787 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
792 default: // this should never happen
794 dprint("ctf_FlagThink(): Flag exists with no status?\n");
802 if(gameover) { return; }
804 entity toucher = other;
806 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
807 if(ITEM_TOUCH_NEEDKILL())
810 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
814 // special touch behaviors
815 if(toucher.vehicle_flags & VHF_ISVEHICLE)
817 if(autocvar_g_ctf_allow_vehicle_touch)
818 toucher = toucher.owner; // the player is actually the vehicle owner, not other
820 return; // do nothing
822 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
824 if(time > self.wait) // if we haven't in a while, play a sound/effect
826 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
827 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
828 self.wait = time + FLAG_TOUCHRATE;
832 else if(toucher.deadflag != DEAD_NO) { return; }
834 switch(self.ctf_status)
838 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
839 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
840 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
841 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
847 if(!IsDifferentTeam(toucher, self))
848 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
849 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
850 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
856 dprint("Someone touched a flag even though it was being carried?\n");
862 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
864 if(IsDifferentTeam(toucher, self.pass_sender))
865 ctf_Handle_Return(self, toucher);
867 ctf_Handle_Retrieve(self, toucher);
875 void ctf_RespawnFlag(entity flag)
877 // check for flag respawn being called twice in a row
878 if(flag.last_respawn > time - 0.5)
879 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
881 flag.last_respawn = time;
883 // reset the player (if there is one)
884 if((flag.owner) && (flag.owner.flagcarried == flag))
886 if(flag.owner.wps_enemyflagcarrier)
887 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
889 WaypointSprite_Kill(flag.wps_flagcarrier);
891 flag.owner.flagcarried = world;
893 if(flag.speedrunning)
894 ctf_FakeTimeLimit(flag.owner, -1);
897 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
898 { WaypointSprite_Kill(flag.wps_flagdropped); }
901 setattachment(flag, world, "");
902 setorigin(flag, flag.ctf_spawnorigin);
904 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
905 flag.takedamage = DAMAGE_NO;
906 flag.health = flag.max_flag_health;
907 flag.solid = SOLID_TRIGGER;
908 flag.velocity = '0 0 0';
909 flag.angles = flag.mangle;
910 flag.flags = FL_ITEM | FL_NOTARGET;
912 flag.ctf_status = FLAG_BASE;
914 flag.pass_distance = 0;
915 flag.pass_sender = world;
916 flag.pass_target = world;
917 flag.ctf_dropper = world;
918 flag.ctf_pickuptime = 0;
919 flag.ctf_droptime = 0;
925 if(self.owner.classname == "player")
926 ctf_Handle_Throw(self.owner, world, DROP_RESET);
928 ctf_RespawnFlag(self);
931 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
934 waypoint_spawnforitem_force(self, self.origin);
935 self.nearestwaypointtimeout = 0; // activate waypointing again
936 self.bot_basewaypoint = self.nearestwaypoint;
939 WaypointSprite_SpawnFixed(((self.team == FL_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
940 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
942 // captureshield setup
943 ctf_CaptureShield_Spawn(self);
946 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
949 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.
950 self = flag; // for later usage with droptofloor()
953 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
954 ctf_worldflaglist = flag;
956 setattachment(flag, world, "");
958 flag.netname = ((teamnumber) ? "^1REPLACETHIS^7" : "^4REPLACETHIS^7"); // ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
959 flag.team = ((teamnumber) ? FL_TEAM_1 : FL_TEAM_2); // FL_TEAM_1: color 4 team (red) - FL_TEAM_2: color 13 team (blue)
960 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
961 flag.classname = "item_flag_team";
962 flag.target = "###item###"; // wut?
963 flag.flags = FL_ITEM | FL_NOTARGET;
964 flag.solid = SOLID_TRIGGER;
965 flag.takedamage = DAMAGE_NO;
966 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
967 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
968 flag.health = flag.max_flag_health;
969 flag.event_damage = ctf_FlagDamage;
970 flag.pushable = TRUE;
971 flag.teleportable = TELEPORT_NORMAL;
972 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
973 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
974 flag.velocity = '0 0 0';
975 flag.mangle = flag.angles;
976 flag.reset = ctf_Reset;
977 flag.touch = ctf_FlagTouch;
978 flag.think = ctf_FlagThink;
979 flag.nextthink = time + FLAG_THINKRATE;
980 flag.ctf_status = FLAG_BASE;
982 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
983 if(!flag.scale) { flag.scale = FLAG_SCALE; }
984 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
985 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
986 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
987 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
990 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
991 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
992 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
993 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.
994 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
995 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
996 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
999 precache_sound(flag.snd_flag_taken);
1000 precache_sound(flag.snd_flag_returned);
1001 precache_sound(flag.snd_flag_capture);
1002 precache_sound(flag.snd_flag_respawn);
1003 precache_sound(flag.snd_flag_dropped);
1004 precache_sound(flag.snd_flag_touch);
1005 precache_sound(flag.snd_flag_pass);
1006 precache_model(flag.model);
1007 precache_model("models/ctf/shield.md3");
1008 precache_model("models/ctf/shockwavetransring.md3");
1011 setmodel(flag, flag.model); // precision set below
1012 setsize(flag, FLAG_MIN, FLAG_MAX);
1013 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1015 if(autocvar_g_ctf_flag_glowtrails)
1017 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1018 flag.glow_size = 25;
1019 flag.glow_trail = 1;
1022 flag.effects |= EF_LOWPRECISION;
1023 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1024 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1027 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1029 flag.dropped_origin = flag.origin;
1030 flag.noalign = TRUE;
1031 flag.movetype = MOVETYPE_NONE;
1033 else // drop to floor, automatically find a platform and set that as spawn origin
1035 flag.noalign = FALSE;
1038 flag.movetype = MOVETYPE_TOSS;
1041 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1049 // NOTE: LEGACY CODE, needs to be re-written!
1051 void havocbot_calculate_middlepoint()
1055 vector fo = '0 0 0';
1058 f = ctf_worldflaglist;
1063 f = f.ctf_worldflagnext;
1067 havocbot_ctf_middlepoint = s * (1.0 / n);
1068 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1072 entity havocbot_ctf_find_flag(entity bot)
1075 f = ctf_worldflaglist;
1078 if (bot.team == f.team)
1080 f = f.ctf_worldflagnext;
1085 entity havocbot_ctf_find_enemy_flag(entity bot)
1088 f = ctf_worldflaglist;
1091 if (bot.team != f.team)
1093 f = f.ctf_worldflagnext;
1098 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1106 FOR_EACH_PLAYER(head)
1108 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1111 if(vlen(head.origin - org) < tc_radius)
1118 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1121 head = ctf_worldflaglist;
1124 if (self.team == head.team)
1126 head = head.ctf_worldflagnext;
1129 navigation_routerating(head, ratingscale, 10000);
1132 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1135 head = ctf_worldflaglist;
1138 if (self.team == head.team)
1140 head = head.ctf_worldflagnext;
1145 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1148 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1151 head = ctf_worldflaglist;
1154 if (self.team != head.team)
1156 head = head.ctf_worldflagnext;
1159 navigation_routerating(head, ratingscale, 10000);
1162 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1164 if not(bot_waypoints_for_items)
1166 havocbot_goalrating_ctf_enemyflag(ratingscale);
1172 head = havocbot_ctf_find_enemy_flag(self);
1177 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1180 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1184 mf = havocbot_ctf_find_flag(self);
1186 if(mf.ctf_status == FLAG_BASE)
1190 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1193 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1196 head = ctf_worldflaglist;
1199 // flag is out in the field
1200 if(head.ctf_status != FLAG_BASE)
1201 if(head.tag_entity==world) // dropped
1205 if(vlen(org-head.origin)<df_radius)
1206 navigation_routerating(head, ratingscale, 10000);
1209 navigation_routerating(head, ratingscale, 10000);
1212 head = head.ctf_worldflagnext;
1216 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1220 head = findchainfloat(bot_pickup, TRUE);
1223 // gather health and armor only
1225 if (head.health || head.armorvalue)
1226 if (vlen(head.origin - org) < sradius)
1228 // get the value of the item
1229 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1231 navigation_routerating(head, t * ratingscale, 500);
1237 void havocbot_ctf_reset_role(entity bot)
1239 float cdefense, cmiddle, coffense;
1240 entity mf, ef, head;
1243 if(bot.deadflag != DEAD_NO)
1246 if(vlen(havocbot_ctf_middlepoint)==0)
1247 havocbot_calculate_middlepoint();
1250 if (bot.flagcarried)
1252 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1256 mf = havocbot_ctf_find_flag(bot);
1257 ef = havocbot_ctf_find_enemy_flag(bot);
1259 // Retrieve stolen flag
1260 if(mf.ctf_status!=FLAG_BASE)
1262 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1266 // If enemy flag is taken go to the middle to intercept pursuers
1267 if(ef.ctf_status!=FLAG_BASE)
1269 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1273 // if there is only me on the team switch to offense
1275 FOR_EACH_PLAYER(head)
1276 if(head.team==bot.team)
1281 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1285 // Evaluate best position to take
1286 // Count mates on middle position
1287 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1289 // Count mates on defense position
1290 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1292 // Count mates on offense position
1293 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1295 if(cdefense<=coffense)
1296 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1297 else if(coffense<=cmiddle)
1298 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1300 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1303 void havocbot_role_ctf_carrier()
1305 if(self.deadflag != DEAD_NO)
1307 havocbot_ctf_reset_role(self);
1311 if (self.flagcarried == world)
1313 havocbot_ctf_reset_role(self);
1317 if (self.bot_strategytime < time)
1319 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1321 navigation_goalrating_start();
1322 havocbot_goalrating_ctf_ourbase(50000);
1325 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1327 navigation_goalrating_end();
1329 if (self.navigation_hasgoals)
1330 self.havocbot_cantfindflag = time + 10;
1331 else if (time > self.havocbot_cantfindflag)
1333 // Can't navigate to my own base, suicide!
1334 // TODO: drop it and wander around
1335 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1341 void havocbot_role_ctf_escort()
1345 if(self.deadflag != DEAD_NO)
1347 havocbot_ctf_reset_role(self);
1351 if (self.flagcarried)
1353 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1357 // If enemy flag is back on the base switch to previous role
1358 ef = havocbot_ctf_find_enemy_flag(self);
1359 if(ef.ctf_status==FLAG_BASE)
1361 self.havocbot_role = self.havocbot_previous_role;
1362 self.havocbot_role_timeout = 0;
1366 // If the flag carrier reached the base switch to defense
1367 mf = havocbot_ctf_find_flag(self);
1368 if(mf.ctf_status!=FLAG_BASE)
1369 if(vlen(ef.origin - mf.dropped_origin) < 300)
1371 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1375 // Set the role timeout if necessary
1376 if (!self.havocbot_role_timeout)
1378 self.havocbot_role_timeout = time + random() * 30 + 60;
1381 // If nothing happened just switch to previous role
1382 if (time > self.havocbot_role_timeout)
1384 self.havocbot_role = self.havocbot_previous_role;
1385 self.havocbot_role_timeout = 0;
1389 // Chase the flag carrier
1390 if (self.bot_strategytime < time)
1392 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1393 navigation_goalrating_start();
1394 havocbot_goalrating_ctf_enemyflag(30000);
1395 havocbot_goalrating_ctf_ourstolenflag(40000);
1396 havocbot_goalrating_items(10000, self.origin, 10000);
1397 navigation_goalrating_end();
1401 void havocbot_role_ctf_offense()
1406 if(self.deadflag != DEAD_NO)
1408 havocbot_ctf_reset_role(self);
1412 if (self.flagcarried)
1414 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1419 mf = havocbot_ctf_find_flag(self);
1420 ef = havocbot_ctf_find_enemy_flag(self);
1423 if(mf.ctf_status!=FLAG_BASE)
1426 pos = mf.tag_entity.origin;
1430 // Try to get it if closer than the enemy base
1431 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1433 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1438 // Escort flag carrier
1439 if(ef.ctf_status!=FLAG_BASE)
1442 pos = ef.tag_entity.origin;
1446 if(vlen(pos-mf.dropped_origin)>700)
1448 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1453 // About to fail, switch to middlefield
1456 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1460 // Set the role timeout if necessary
1461 if (!self.havocbot_role_timeout)
1462 self.havocbot_role_timeout = time + 120;
1464 if (time > self.havocbot_role_timeout)
1466 havocbot_ctf_reset_role(self);
1470 if (self.bot_strategytime < time)
1472 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1473 navigation_goalrating_start();
1474 havocbot_goalrating_ctf_ourstolenflag(50000);
1475 havocbot_goalrating_ctf_enemybase(20000);
1476 havocbot_goalrating_items(5000, self.origin, 1000);
1477 havocbot_goalrating_items(1000, self.origin, 10000);
1478 navigation_goalrating_end();
1482 // Retriever (temporary role):
1483 void havocbot_role_ctf_retriever()
1487 if(self.deadflag != DEAD_NO)
1489 havocbot_ctf_reset_role(self);
1493 if (self.flagcarried)
1495 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1499 // If flag is back on the base switch to previous role
1500 mf = havocbot_ctf_find_flag(self);
1501 if(mf.ctf_status==FLAG_BASE)
1503 havocbot_ctf_reset_role(self);
1507 if (!self.havocbot_role_timeout)
1508 self.havocbot_role_timeout = time + 20;
1510 if (time > self.havocbot_role_timeout)
1512 havocbot_ctf_reset_role(self);
1516 if (self.bot_strategytime < time)
1521 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1522 navigation_goalrating_start();
1523 havocbot_goalrating_ctf_ourstolenflag(50000);
1524 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1525 havocbot_goalrating_ctf_enemybase(30000);
1526 havocbot_goalrating_items(500, self.origin, rt_radius);
1527 navigation_goalrating_end();
1531 void havocbot_role_ctf_middle()
1535 if(self.deadflag != DEAD_NO)
1537 havocbot_ctf_reset_role(self);
1541 if (self.flagcarried)
1543 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1547 mf = havocbot_ctf_find_flag(self);
1548 if(mf.ctf_status!=FLAG_BASE)
1550 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1554 if (!self.havocbot_role_timeout)
1555 self.havocbot_role_timeout = time + 10;
1557 if (time > self.havocbot_role_timeout)
1559 havocbot_ctf_reset_role(self);
1563 if (self.bot_strategytime < time)
1567 org = havocbot_ctf_middlepoint;
1568 org_z = self.origin_z;
1570 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1571 navigation_goalrating_start();
1572 havocbot_goalrating_ctf_ourstolenflag(50000);
1573 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1574 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1575 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1576 havocbot_goalrating_items(2500, self.origin, 10000);
1577 havocbot_goalrating_ctf_enemybase(2500);
1578 navigation_goalrating_end();
1582 void havocbot_role_ctf_defense()
1586 if(self.deadflag != DEAD_NO)
1588 havocbot_ctf_reset_role(self);
1592 if (self.flagcarried)
1594 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1598 // If own flag was captured
1599 mf = havocbot_ctf_find_flag(self);
1600 if(mf.ctf_status!=FLAG_BASE)
1602 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1606 if (!self.havocbot_role_timeout)
1607 self.havocbot_role_timeout = time + 30;
1609 if (time > self.havocbot_role_timeout)
1611 havocbot_ctf_reset_role(self);
1614 if (self.bot_strategytime < time)
1619 org = mf.dropped_origin;
1620 mp_radius = havocbot_ctf_middlepoint_radius;
1622 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1623 navigation_goalrating_start();
1625 // if enemies are closer to our base, go there
1626 entity head, closestplayer = world;
1627 float distance, bestdistance = 10000;
1628 FOR_EACH_PLAYER(head)
1630 if(head.deadflag!=DEAD_NO)
1633 distance = vlen(org - head.origin);
1634 if(distance<bestdistance)
1636 closestplayer = head;
1637 bestdistance = distance;
1642 if(closestplayer.team!=self.team)
1643 if(vlen(org - self.origin)>1000)
1644 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1645 havocbot_goalrating_ctf_ourbase(30000);
1647 havocbot_goalrating_ctf_ourstolenflag(20000);
1648 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1649 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1650 havocbot_goalrating_items(10000, org, mp_radius);
1651 havocbot_goalrating_items(5000, self.origin, 10000);
1652 navigation_goalrating_end();
1656 void havocbot_role_ctf_setrole(entity bot, float role)
1658 dprint(strcat(bot.netname," switched to "));
1661 case HAVOCBOT_CTF_ROLE_CARRIER:
1663 bot.havocbot_role = havocbot_role_ctf_carrier;
1664 bot.havocbot_role_timeout = 0;
1665 bot.havocbot_cantfindflag = time + 10;
1666 bot.bot_strategytime = 0;
1668 case HAVOCBOT_CTF_ROLE_DEFENSE:
1670 bot.havocbot_role = havocbot_role_ctf_defense;
1671 bot.havocbot_role_timeout = 0;
1673 case HAVOCBOT_CTF_ROLE_MIDDLE:
1675 bot.havocbot_role = havocbot_role_ctf_middle;
1676 bot.havocbot_role_timeout = 0;
1678 case HAVOCBOT_CTF_ROLE_OFFENSE:
1680 bot.havocbot_role = havocbot_role_ctf_offense;
1681 bot.havocbot_role_timeout = 0;
1683 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1684 dprint("retriever");
1685 bot.havocbot_previous_role = bot.havocbot_role;
1686 bot.havocbot_role = havocbot_role_ctf_retriever;
1687 bot.havocbot_role_timeout = time + 10;
1688 bot.bot_strategytime = 0;
1690 case HAVOCBOT_CTF_ROLE_ESCORT:
1692 bot.havocbot_previous_role = bot.havocbot_role;
1693 bot.havocbot_role = havocbot_role_ctf_escort;
1694 bot.havocbot_role_timeout = time + 30;
1695 bot.bot_strategytime = 0;
1706 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1710 // initially clear items so they can be set as necessary later.
1711 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1712 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1714 // scan through all the flags and notify the client about them
1715 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1717 switch(flag.ctf_status)
1722 if((flag.owner == self) || (flag.pass_sender == self))
1723 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1725 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1730 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1736 // item for stopping players from capturing the flag too often
1737 if(self.ctf_captureshielded)
1738 self.items |= IT_CTF_SHIELDED;
1740 // update the health of the flag carrier waypointsprite
1741 if(self.wps_flagcarrier)
1742 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1747 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1749 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1751 if(frag_target == frag_attacker) // damage done to yourself
1753 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1754 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1756 else // damage done to everyone else
1758 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1759 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1762 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1764 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1765 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1767 frag_target.wps_helpme_time = time;
1768 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1770 // todo: add notification for when flag carrier needs help?
1775 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1777 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1779 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1780 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1783 if(frag_target.flagcarried)
1784 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1789 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1792 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1795 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1797 entity flag; // temporary entity for the search method
1799 if(self.flagcarried)
1800 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1802 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1804 if(flag.pass_sender == self) { flag.pass_sender = world; }
1805 if(flag.pass_target == self) { flag.pass_target = world; }
1806 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1812 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1814 if(self.flagcarried)
1815 if(!autocvar_g_ctf_portalteleport)
1816 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1821 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1823 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1825 entity player = self;
1827 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1829 // pass the flag to a team mate
1830 if(autocvar_g_ctf_pass)
1832 entity head, closest_target = world;
1833 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1835 while(head) // find the closest acceptable target to pass to
1837 if(head.classname == "player" && head.deadflag == DEAD_NO)
1838 if(head != player && !IsDifferentTeam(head, player))
1839 if(!head.speedrunning && !head.vehicle)
1841 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1842 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1843 vector passer_center = CENTER_OR_VIEWOFS(player);
1845 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1847 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1849 if(clienttype(head) == CLIENTTYPE_BOT)
1851 Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1852 ctf_Handle_Throw(head, player, DROP_PASS);
1856 Send_Notification_Legacy_Wrapper(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1857 Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1859 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1862 else if(player.flagcarried)
1866 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1867 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1868 { closest_target = head; }
1870 else { closest_target = head; }
1877 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1880 // throw the flag in front of you
1881 if(autocvar_g_ctf_throw && player.flagcarried)
1883 if(player.throw_count == -1)
1885 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1887 player.throw_prevtime = time;
1888 player.throw_count = 1;
1889 ctf_Handle_Throw(player, world, DROP_THROW);
1894 Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, NO_STR_ARG, NO_STR_ARG, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time), NO_FL_ARG, NO_FL_ARG);
1900 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1901 else { player.throw_count += 1; }
1902 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1904 player.throw_prevtime = time;
1905 ctf_Handle_Throw(player, world, DROP_THROW);
1914 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1916 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1918 self.wps_helpme_time = time;
1919 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1921 else // create a normal help me waypointsprite
1923 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');
1924 WaypointSprite_Ping(self.wps_helpme);
1930 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1932 if(vh_player.flagcarried)
1934 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1936 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1940 setattachment(vh_player.flagcarried, vh_vehicle, "");
1941 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1942 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1943 //vh_player.flagcarried.angles = '0 0 0';
1951 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1953 if(vh_player.flagcarried)
1955 setattachment(vh_player.flagcarried, vh_player, "");
1956 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1957 vh_player.flagcarried.scale = FLAG_SCALE;
1958 vh_player.flagcarried.angles = '0 0 0';
1965 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1967 if(self.flagcarried)
1969 Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1970 ctf_RespawnFlag(self.flagcarried);
1977 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1979 entity flag; // temporary entity for the search method
1981 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1983 switch(flag.ctf_status)
1988 // lock the flag, game is over
1989 flag.movetype = MOVETYPE_NONE;
1990 flag.takedamage = DAMAGE_NO;
1991 flag.solid = SOLID_NOT;
1992 flag.nextthink = FALSE; // stop thinking
1994 //dprint("stopping the ", flag.netname, " from moving.\n");
2002 // do nothing for these flags
2011 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2013 havocbot_ctf_reset_role(self);
2017 MUTATOR_HOOKFUNCTION(ctf_GetCvars)
2019 GetCvars_handleFloat(get_cvars_s, get_cvars_f, CAPTURE_VERBOSE, "notification_ctf_capture_verbose");
2020 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_TEAM_VERBOSE, "notification_ctf_pickup_team_verbose");
2021 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_ENEMY_VERBOSE, "notification_ctf_pickup_enemy_verbose");
2030 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2031 CTF Starting point for a player in team one (Red).
2032 Keys: "angle" viewing angle when spawning. */
2033 void spawnfunc_info_player_team1()
2035 if(g_assault) { remove(self); return; }
2037 self.team = FL_TEAM_1; // red
2038 spawnfunc_info_player_deathmatch();
2042 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2043 CTF Starting point for a player in team two (Blue).
2044 Keys: "angle" viewing angle when spawning. */
2045 void spawnfunc_info_player_team2()
2047 if(g_assault) { remove(self); return; }
2049 self.team = FL_TEAM_2; // blue
2050 spawnfunc_info_player_deathmatch();
2053 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2054 CTF Starting point for a player in team three (Yellow).
2055 Keys: "angle" viewing angle when spawning. */
2056 void spawnfunc_info_player_team3()
2058 if(g_assault) { remove(self); return; }
2060 self.team = FL_TEAM_3; // yellow
2061 spawnfunc_info_player_deathmatch();
2065 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2066 CTF Starting point for a player in team four (Purple).
2067 Keys: "angle" viewing angle when spawning. */
2068 void spawnfunc_info_player_team4()
2070 if(g_assault) { remove(self); return; }
2072 self.team = FL_TEAM_4; // purple
2073 spawnfunc_info_player_deathmatch();
2076 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2077 CTF flag for team one (Red).
2079 "angle" Angle the flag will point (minus 90 degrees)...
2080 "model" model to use, note this needs red and blue as skins 0 and 1...
2081 "noise" sound played when flag is picked up...
2082 "noise1" sound played when flag is returned by a teammate...
2083 "noise2" sound played when flag is captured...
2084 "noise3" sound played when flag is lost in the field and respawns itself...
2085 "noise4" sound played when flag is dropped by a player...
2086 "noise5" sound played when flag touches the ground... */
2087 void spawnfunc_item_flag_team1()
2089 if(!g_ctf) { remove(self); return; }
2091 ctf_FlagSetup(1, self); // 1 = red
2094 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2095 CTF flag for team two (Blue).
2097 "angle" Angle the flag will point (minus 90 degrees)...
2098 "model" model to use, note this needs red and blue as skins 0 and 1...
2099 "noise" sound played when flag is picked up...
2100 "noise1" sound played when flag is returned by a teammate...
2101 "noise2" sound played when flag is captured...
2102 "noise3" sound played when flag is lost in the field and respawns itself...
2103 "noise4" sound played when flag is dropped by a player...
2104 "noise5" sound played when flag touches the ground... */
2105 void spawnfunc_item_flag_team2()
2107 if(!g_ctf) { remove(self); return; }
2109 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2112 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2113 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2114 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.
2116 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2117 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2118 void spawnfunc_ctf_team()
2120 if(!g_ctf) { remove(self); return; }
2122 self.classname = "ctf_team";
2123 self.team = self.cnt + 1;
2126 // compatibility for quake maps
2127 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2128 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2129 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2130 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2131 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2132 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2140 void ctf_ScoreRules()
2142 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2143 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2144 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2145 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2146 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2147 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2148 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2149 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2150 ScoreRules_basics_end();
2153 // code from here on is just to support maps that don't have flag and team entities
2154 void ctf_SpawnTeam (string teamname, float teamcolor)
2159 self.classname = "ctf_team";
2160 self.netname = teamname;
2161 self.cnt = teamcolor;
2163 spawnfunc_ctf_team();
2168 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2170 // if no teams are found, spawn defaults
2171 if(find(world, classname, "ctf_team") == world)
2173 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2174 ctf_SpawnTeam("Red", FL_TEAM_1 - 1);
2175 ctf_SpawnTeam("Blue", FL_TEAM_2 - 1);
2181 void ctf_Initialize()
2183 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2185 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2186 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2187 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2189 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2193 MUTATOR_DEFINITION(gamemode_ctf)
2195 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2196 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2197 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2198 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2199 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2200 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2201 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2202 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2203 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2204 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2205 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2206 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2207 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2208 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2209 MUTATOR_HOOK(GetCvars, ctf_GetCvars, CBC_ORDER_ANY);
2213 if(time > 1) // game loads at time 1
2214 error("This is a game type and it cannot be added at runtime.");
2218 MUTATOR_ONROLLBACK_OR_REMOVE
2220 // we actually cannot roll back ctf_Initialize here
2221 // BUT: we don't need to! If this gets called, adding always
2227 print("This is a game type and it cannot be removed at runtime.");