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 cap_record = ctf_captimerecord;
27 float cap_time = (time - flag.ctf_pickuptime);
28 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
31 FOR_EACH_REALCLIENT(tmp_entity)
33 if(tmp_entity.CAPTURE_VERBOSE)
35 if(!ctf_captimerecord) { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
36 else if(cap_time < cap_record) { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
37 else { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
39 else { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_), player.netname); }
42 // notify server log too
43 if(autocvar_notification_ctf_capture_verbose)
45 if(!ctf_captimerecord) { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
46 else if(cap_time < cap_record) { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
47 else { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
49 else { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_), player.netname); }
51 // write that shit in the database
52 if((!ctf_captimerecord) || (cap_time < cap_record))
54 ctf_captimerecord = cap_time;
55 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
56 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
57 write_recordmarker(player, (time - cap_time), cap_time);
61 void ctf_FlagcarrierWaypoints(entity player)
63 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
64 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
65 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
66 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
69 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
71 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
72 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
73 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
74 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
77 if(current_height) // make sure we can actually do this arcing path
79 targpos = (to + ('0 0 1' * current_height));
80 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
81 if(trace_fraction < 1)
83 //print("normal arc line failed, trying to find new pos...");
84 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
85 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
86 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
87 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
88 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
91 else { targpos = to; }
93 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
95 vector desired_direction = normalize(targpos - from);
96 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
97 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
100 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
102 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
104 // directional tracing only
106 makevectors(passer_angle);
108 // find the closest point on the enemy to the center of the attack
109 float ang; // angle between shotdir and h
110 float h; // hypotenuse, which is the distance between attacker to head
111 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
113 h = vlen(head_center - passer_center);
114 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
117 vector nearest_on_line = (passer_center + a * v_forward);
118 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
120 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
121 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
123 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
128 else { return TRUE; }
132 // =======================
133 // CaptureShield Functions
134 // =======================
136 float ctf_CaptureShield_CheckStatus(entity p)
140 float players_worseeq, players_total;
142 if(ctf_captureshield_max_ratio <= 0)
145 s = PlayerScore_Add(p, SP_SCORE, 0);
146 if(s >= -ctf_captureshield_min_negscore)
149 players_total = players_worseeq = 0;
152 if(IsDifferentTeam(e, p))
154 se = PlayerScore_Add(e, SP_SCORE, 0);
160 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
161 // use this rule here
163 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
169 void ctf_CaptureShield_Update(entity player, float wanted_status)
171 float updated_status = ctf_CaptureShield_CheckStatus(player);
172 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
174 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
175 player.ctf_captureshielded = updated_status;
179 float ctf_CaptureShield_Customize()
181 if(!other.ctf_captureshielded) { return FALSE; }
182 if(!IsDifferentTeam(self, other)) { return FALSE; }
187 void ctf_CaptureShield_Touch()
189 if(!other.ctf_captureshielded) { return; }
190 if(!IsDifferentTeam(self, other)) { return; }
192 vector mymid = (self.absmin + self.absmax) * 0.5;
193 vector othermid = (other.absmin + other.absmax) * 0.5;
195 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
196 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED);
199 void ctf_CaptureShield_Spawn(entity flag)
201 entity shield = spawn();
204 shield.team = self.team;
205 shield.touch = ctf_CaptureShield_Touch;
206 shield.customizeentityforclient = ctf_CaptureShield_Customize;
207 shield.classname = "ctf_captureshield";
208 shield.effects = EF_ADDITIVE;
209 shield.movetype = MOVETYPE_NOCLIP;
210 shield.solid = SOLID_TRIGGER;
211 shield.avelocity = '7 0 11';
214 setorigin(shield, self.origin);
215 setmodel(shield, "models/ctf/shield.md3");
216 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
220 // ====================
221 // Drop/Pass/Throw Code
222 // ====================
224 void ctf_Handle_Drop(entity flag, entity player, float droptype)
227 player = (player ? player : flag.pass_sender);
230 flag.movetype = MOVETYPE_TOSS;
231 flag.takedamage = DAMAGE_YES;
232 flag.angles = '0 0 0';
233 flag.health = flag.max_flag_health;
234 flag.ctf_droptime = time;
235 flag.ctf_dropper = player;
236 flag.ctf_status = FLAG_DROPPED;
238 // messages and sounds
239 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
240 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
241 ctf_EventLog("dropped", player.team, player);
244 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
245 PlayerScore_Add(player, SP_CTF_DROPS, 1);
248 if(autocvar_g_ctf_flag_dropped_waypoint)
249 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));
251 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
253 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
254 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
257 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
259 if(droptype == DROP_PASS)
261 flag.pass_distance = 0;
262 flag.pass_sender = world;
263 flag.pass_target = world;
267 void ctf_Handle_Retrieve(entity flag, entity player)
269 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
270 entity sender = flag.pass_sender;
272 // transfer flag to player
274 flag.owner.flagcarried = flag;
277 setattachment(flag, player, "");
278 setorigin(flag, FLAG_CARRY_OFFSET);
279 flag.movetype = MOVETYPE_NONE;
280 flag.takedamage = DAMAGE_NO;
281 flag.solid = SOLID_NOT;
282 flag.angles = '0 0 0';
283 flag.ctf_status = FLAG_CARRY;
285 // messages and sounds
286 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
287 ctf_EventLog("receive", flag.team, player);
289 FOR_EACH_REALPLAYER(tmp_player)
291 if(tmp_player == sender)
292 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
293 else if(tmp_player == player)
294 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
295 else if(!IsDifferentTeam(tmp_player, sender))
296 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
299 // create new waypoint
300 ctf_FlagcarrierWaypoints(player);
302 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
303 player.throw_antispam = sender.throw_antispam;
305 flag.pass_distance = 0;
306 flag.pass_sender = world;
307 flag.pass_target = world;
310 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
312 entity flag = player.flagcarried;
313 vector targ_origin, flag_velocity;
315 if(!flag) { return; }
316 if((droptype == DROP_PASS) && !receiver) { return; }
318 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
321 setattachment(flag, world, "");
322 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
323 flag.owner.flagcarried = world;
325 flag.solid = SOLID_TRIGGER;
326 flag.ctf_dropper = player;
327 flag.ctf_droptime = time;
329 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
336 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
337 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
338 WarpZone_RefSys_Copy(flag, receiver);
339 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
340 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
342 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
343 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
346 flag.movetype = MOVETYPE_FLY;
347 flag.takedamage = DAMAGE_NO;
348 flag.pass_sender = player;
349 flag.pass_target = receiver;
350 flag.ctf_status = FLAG_PASSING;
353 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
354 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
355 ctf_EventLog("pass", flag.team, player);
361 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'));
363 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)));
364 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
365 ctf_Handle_Drop(flag, player, droptype);
371 flag.velocity = '0 0 0'; // do nothing
378 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);
379 ctf_Handle_Drop(flag, player, droptype);
384 // kill old waypointsprite
385 WaypointSprite_Ping(player.wps_flagcarrier);
386 WaypointSprite_Kill(player.wps_flagcarrier);
388 if(player.wps_enemyflagcarrier)
389 WaypointSprite_Kill(player.wps_enemyflagcarrier);
392 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
400 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
402 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
403 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
404 float old_time, new_time;
406 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
408 // messages and sounds
409 ctf_CaptureRecord(enemy_flag, player);
410 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
414 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
415 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
420 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
421 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
423 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
424 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
425 if(!old_time || new_time < old_time)
426 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
429 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
430 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
433 if(capturetype == CAPTURE_NORMAL)
435 WaypointSprite_Kill(player.wps_flagcarrier);
436 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
438 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
439 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
443 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
444 ctf_RespawnFlag(enemy_flag);
447 void ctf_Handle_Return(entity flag, entity player)
449 // messages and sounds
450 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
451 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
452 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
453 ctf_EventLog("return", flag.team, player);
456 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
457 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
459 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
463 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
464 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
465 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
469 ctf_RespawnFlag(flag);
472 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
475 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
476 float pickup_dropped_score; // used to calculate dropped pickup score
478 // attach the flag to the player
480 player.flagcarried = flag;
481 setattachment(flag, player, "");
482 setorigin(flag, FLAG_CARRY_OFFSET);
485 flag.movetype = MOVETYPE_NONE;
486 flag.takedamage = DAMAGE_NO;
487 flag.solid = SOLID_NOT;
488 flag.angles = '0 0 0';
489 flag.ctf_status = FLAG_CARRY;
493 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
494 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
498 // messages and sounds
499 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
500 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
502 FOR_EACH_REALPLAYER(tmp_player)
504 if(tmp_player == player)
506 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
507 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
509 else if(!IsDifferentTeam(tmp_player, player) && tmp_player != player)
510 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 : ""), 0, 0, 0);
511 else if(IsDifferentTeam(tmp_player, player))
512 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 : ""), 0, 0, 0);
516 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
521 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
522 ctf_EventLog("steal", flag.team, player);
528 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);
529 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);
530 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
531 PlayerTeamScore_AddScore(player, pickup_dropped_score);
532 ctf_EventLog("pickup", flag.team, player);
540 if(pickuptype == PICKUP_BASE)
542 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
543 if((player.speedrunning) && (ctf_captimerecord))
544 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
548 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
551 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
552 ctf_FlagcarrierWaypoints(player);
553 WaypointSprite_Ping(player.wps_flagcarrier);
557 // ===================
558 // Main Flag Functions
559 // ===================
561 void ctf_CheckFlagReturn(entity flag, float returntype)
563 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
565 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
567 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
571 case RETURN_DROPPED: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
572 case RETURN_DAMAGE: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
573 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
574 case RETURN_NEEDKILL: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
578 { Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
580 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
581 ctf_EventLog("returned", flag.team, world);
582 ctf_RespawnFlag(flag);
587 void ctf_CheckStalemate(void)
590 float stale_red_flags = 0, stale_blue_flags = 0;
593 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
595 // build list of stale flags
596 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
598 if(autocvar_g_ctf_stalemate)
599 if(tmp_entity.ctf_status != FLAG_BASE)
600 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
602 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
603 ctf_staleflaglist = tmp_entity;
605 switch(tmp_entity.team)
607 case FL_TEAM_1: ++stale_red_flags; break;
608 case FL_TEAM_2: ++stale_blue_flags; break;
613 if(stale_red_flags && stale_blue_flags)
614 ctf_stalemate = TRUE;
615 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
616 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
617 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
618 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
620 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
623 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
625 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
626 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));
629 if not(wpforenemy_announced)
631 FOR_EACH_REALPLAYER(tmp_entity)
632 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
634 wpforenemy_announced = TRUE;
639 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
641 if(ITEM_DAMAGE_NEEDKILL(deathtype))
643 // automatically kill the flag and return it
645 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
648 if(autocvar_g_ctf_flag_return_damage)
650 // reduce health and check if it should be returned
651 self.health = self.health - damage;
652 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
662 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
665 if(self == ctf_worldflaglist) // only for the first flag
666 FOR_EACH_CLIENT(tmp_entity)
667 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
670 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
671 dprint("wtf the flag got squashed?\n");
672 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
673 if(!trace_startsolid) // can we resize it without getting stuck?
674 setsize(self, FLAG_MIN, FLAG_MAX); }
676 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
680 self.angles = '0 0 0';
688 switch(self.ctf_status)
692 if(autocvar_g_ctf_dropped_capture_radius)
694 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
695 if(tmp_entity.ctf_status == FLAG_DROPPED)
696 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
697 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
698 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
705 if(autocvar_g_ctf_flag_dropped_floatinwater)
707 vector midpoint = ((self.absmin + self.absmax) * 0.5);
708 if(pointcontents(midpoint) == CONTENT_WATER)
710 self.velocity = self.velocity * 0.5;
712 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
713 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
715 { self.movetype = MOVETYPE_FLY; }
717 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
719 if(autocvar_g_ctf_flag_return_dropped)
721 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
724 ctf_CheckFlagReturn(self, RETURN_DROPPED);
728 if(autocvar_g_ctf_flag_return_time)
730 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
731 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
739 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
742 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
746 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
750 if(autocvar_g_ctf_stalemate)
752 if(time >= wpforenemy_nextthink)
754 ctf_CheckStalemate();
755 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
763 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
764 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
765 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
767 if((self.pass_target == world)
768 || (self.pass_target.deadflag != DEAD_NO)
769 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
770 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
771 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
773 // give up, pass failed
774 ctf_Handle_Drop(self, world, DROP_PASS);
778 // still a viable target, go for it
779 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
784 default: // this should never happen
786 dprint("ctf_FlagThink(): Flag exists with no status?\n");
794 if(gameover) { return; }
796 entity toucher = other;
798 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
799 if(ITEM_TOUCH_NEEDKILL())
802 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
806 // special touch behaviors
807 if(toucher.vehicle_flags & VHF_ISVEHICLE)
809 if(autocvar_g_ctf_allow_vehicle_touch)
810 toucher = toucher.owner; // the player is actually the vehicle owner, not other
812 return; // do nothing
814 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
816 if(time > self.wait) // if we haven't in a while, play a sound/effect
818 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
819 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
820 self.wait = time + FLAG_TOUCHRATE;
824 else if(toucher.deadflag != DEAD_NO) { return; }
826 switch(self.ctf_status)
830 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
831 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
832 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
833 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
839 if(!IsDifferentTeam(toucher, self))
840 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
841 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
842 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
848 dprint("Someone touched a flag even though it was being carried?\n");
854 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
856 if(IsDifferentTeam(toucher, self.pass_sender))
857 ctf_Handle_Return(self, toucher);
859 ctf_Handle_Retrieve(self, toucher);
867 void ctf_RespawnFlag(entity flag)
869 // check for flag respawn being called twice in a row
870 if(flag.last_respawn > time - 0.5)
871 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
873 flag.last_respawn = time;
875 // reset the player (if there is one)
876 if((flag.owner) && (flag.owner.flagcarried == flag))
878 if(flag.owner.wps_enemyflagcarrier)
879 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
881 WaypointSprite_Kill(flag.wps_flagcarrier);
883 flag.owner.flagcarried = world;
885 if(flag.speedrunning)
886 ctf_FakeTimeLimit(flag.owner, -1);
889 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
890 { WaypointSprite_Kill(flag.wps_flagdropped); }
893 setattachment(flag, world, "");
894 setorigin(flag, flag.ctf_spawnorigin);
896 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
897 flag.takedamage = DAMAGE_NO;
898 flag.health = flag.max_flag_health;
899 flag.solid = SOLID_TRIGGER;
900 flag.velocity = '0 0 0';
901 flag.angles = flag.mangle;
902 flag.flags = FL_ITEM | FL_NOTARGET;
904 flag.ctf_status = FLAG_BASE;
906 flag.pass_distance = 0;
907 flag.pass_sender = world;
908 flag.pass_target = world;
909 flag.ctf_dropper = world;
910 flag.ctf_pickuptime = 0;
911 flag.ctf_droptime = 0;
917 if(self.owner.classname == "player")
918 ctf_Handle_Throw(self.owner, world, DROP_RESET);
920 ctf_RespawnFlag(self);
923 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
926 waypoint_spawnforitem_force(self, self.origin);
927 self.nearestwaypointtimeout = 0; // activate waypointing again
928 self.bot_basewaypoint = self.nearestwaypoint;
931 WaypointSprite_SpawnFixed(((self.team == FL_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
932 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
934 // captureshield setup
935 ctf_CaptureShield_Spawn(self);
938 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
941 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.
942 self = flag; // for later usage with droptofloor()
945 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
946 ctf_worldflaglist = flag;
948 setattachment(flag, world, "");
950 flag.netname = ((teamnumber) ? "^1REPLACETHIS^7" : "^4REPLACETHIS^7"); // ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
951 flag.team = ((teamnumber) ? FL_TEAM_1 : FL_TEAM_2); // FL_TEAM_1: color 4 team (red) - FL_TEAM_2: color 13 team (blue)
952 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
953 flag.classname = "item_flag_team";
954 flag.target = "###item###"; // wut?
955 flag.flags = FL_ITEM | FL_NOTARGET;
956 flag.solid = SOLID_TRIGGER;
957 flag.takedamage = DAMAGE_NO;
958 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
959 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
960 flag.health = flag.max_flag_health;
961 flag.event_damage = ctf_FlagDamage;
962 flag.pushable = TRUE;
963 flag.teleportable = TELEPORT_NORMAL;
964 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
965 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
966 flag.velocity = '0 0 0';
967 flag.mangle = flag.angles;
968 flag.reset = ctf_Reset;
969 flag.touch = ctf_FlagTouch;
970 flag.think = ctf_FlagThink;
971 flag.nextthink = time + FLAG_THINKRATE;
972 flag.ctf_status = FLAG_BASE;
974 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
975 if(!flag.scale) { flag.scale = FLAG_SCALE; }
976 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
977 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
978 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
979 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
982 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
983 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
984 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
985 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.
986 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
987 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
988 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
991 precache_sound(flag.snd_flag_taken);
992 precache_sound(flag.snd_flag_returned);
993 precache_sound(flag.snd_flag_capture);
994 precache_sound(flag.snd_flag_respawn);
995 precache_sound(flag.snd_flag_dropped);
996 precache_sound(flag.snd_flag_touch);
997 precache_sound(flag.snd_flag_pass);
998 precache_model(flag.model);
999 precache_model("models/ctf/shield.md3");
1000 precache_model("models/ctf/shockwavetransring.md3");
1003 setmodel(flag, flag.model); // precision set below
1004 setsize(flag, FLAG_MIN, FLAG_MAX);
1005 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1007 if(autocvar_g_ctf_flag_glowtrails)
1009 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1010 flag.glow_size = 25;
1011 flag.glow_trail = 1;
1014 flag.effects |= EF_LOWPRECISION;
1015 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1016 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1019 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1021 flag.dropped_origin = flag.origin;
1022 flag.noalign = TRUE;
1023 flag.movetype = MOVETYPE_NONE;
1025 else // drop to floor, automatically find a platform and set that as spawn origin
1027 flag.noalign = FALSE;
1030 flag.movetype = MOVETYPE_TOSS;
1033 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1041 // NOTE: LEGACY CODE, needs to be re-written!
1043 void havocbot_calculate_middlepoint()
1047 vector fo = '0 0 0';
1050 f = ctf_worldflaglist;
1055 f = f.ctf_worldflagnext;
1059 havocbot_ctf_middlepoint = s * (1.0 / n);
1060 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1064 entity havocbot_ctf_find_flag(entity bot)
1067 f = ctf_worldflaglist;
1070 if (bot.team == f.team)
1072 f = f.ctf_worldflagnext;
1077 entity havocbot_ctf_find_enemy_flag(entity bot)
1080 f = ctf_worldflaglist;
1083 if (bot.team != f.team)
1085 f = f.ctf_worldflagnext;
1090 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1098 FOR_EACH_PLAYER(head)
1100 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1103 if(vlen(head.origin - org) < tc_radius)
1110 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1113 head = ctf_worldflaglist;
1116 if (self.team == head.team)
1118 head = head.ctf_worldflagnext;
1121 navigation_routerating(head, ratingscale, 10000);
1124 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1127 head = ctf_worldflaglist;
1130 if (self.team == head.team)
1132 head = head.ctf_worldflagnext;
1137 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1140 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1143 head = ctf_worldflaglist;
1146 if (self.team != head.team)
1148 head = head.ctf_worldflagnext;
1151 navigation_routerating(head, ratingscale, 10000);
1154 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1156 if not(bot_waypoints_for_items)
1158 havocbot_goalrating_ctf_enemyflag(ratingscale);
1164 head = havocbot_ctf_find_enemy_flag(self);
1169 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1172 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1176 mf = havocbot_ctf_find_flag(self);
1178 if(mf.ctf_status == FLAG_BASE)
1182 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1185 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1188 head = ctf_worldflaglist;
1191 // flag is out in the field
1192 if(head.ctf_status != FLAG_BASE)
1193 if(head.tag_entity==world) // dropped
1197 if(vlen(org-head.origin)<df_radius)
1198 navigation_routerating(head, ratingscale, 10000);
1201 navigation_routerating(head, ratingscale, 10000);
1204 head = head.ctf_worldflagnext;
1208 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1212 head = findchainfloat(bot_pickup, TRUE);
1215 // gather health and armor only
1217 if (head.health || head.armorvalue)
1218 if (vlen(head.origin - org) < sradius)
1220 // get the value of the item
1221 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1223 navigation_routerating(head, t * ratingscale, 500);
1229 void havocbot_ctf_reset_role(entity bot)
1231 float cdefense, cmiddle, coffense;
1232 entity mf, ef, head;
1235 if(bot.deadflag != DEAD_NO)
1238 if(vlen(havocbot_ctf_middlepoint)==0)
1239 havocbot_calculate_middlepoint();
1242 if (bot.flagcarried)
1244 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1248 mf = havocbot_ctf_find_flag(bot);
1249 ef = havocbot_ctf_find_enemy_flag(bot);
1251 // Retrieve stolen flag
1252 if(mf.ctf_status!=FLAG_BASE)
1254 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1258 // If enemy flag is taken go to the middle to intercept pursuers
1259 if(ef.ctf_status!=FLAG_BASE)
1261 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1265 // if there is only me on the team switch to offense
1267 FOR_EACH_PLAYER(head)
1268 if(head.team==bot.team)
1273 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1277 // Evaluate best position to take
1278 // Count mates on middle position
1279 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1281 // Count mates on defense position
1282 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1284 // Count mates on offense position
1285 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1287 if(cdefense<=coffense)
1288 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1289 else if(coffense<=cmiddle)
1290 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1292 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1295 void havocbot_role_ctf_carrier()
1297 if(self.deadflag != DEAD_NO)
1299 havocbot_ctf_reset_role(self);
1303 if (self.flagcarried == world)
1305 havocbot_ctf_reset_role(self);
1309 if (self.bot_strategytime < time)
1311 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1313 navigation_goalrating_start();
1314 havocbot_goalrating_ctf_ourbase(50000);
1317 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1319 navigation_goalrating_end();
1321 if (self.navigation_hasgoals)
1322 self.havocbot_cantfindflag = time + 10;
1323 else if (time > self.havocbot_cantfindflag)
1325 // Can't navigate to my own base, suicide!
1326 // TODO: drop it and wander around
1327 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1333 void havocbot_role_ctf_escort()
1337 if(self.deadflag != DEAD_NO)
1339 havocbot_ctf_reset_role(self);
1343 if (self.flagcarried)
1345 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1349 // If enemy flag is back on the base switch to previous role
1350 ef = havocbot_ctf_find_enemy_flag(self);
1351 if(ef.ctf_status==FLAG_BASE)
1353 self.havocbot_role = self.havocbot_previous_role;
1354 self.havocbot_role_timeout = 0;
1358 // If the flag carrier reached the base switch to defense
1359 mf = havocbot_ctf_find_flag(self);
1360 if(mf.ctf_status!=FLAG_BASE)
1361 if(vlen(ef.origin - mf.dropped_origin) < 300)
1363 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1367 // Set the role timeout if necessary
1368 if (!self.havocbot_role_timeout)
1370 self.havocbot_role_timeout = time + random() * 30 + 60;
1373 // If nothing happened just switch to previous role
1374 if (time > self.havocbot_role_timeout)
1376 self.havocbot_role = self.havocbot_previous_role;
1377 self.havocbot_role_timeout = 0;
1381 // Chase the flag carrier
1382 if (self.bot_strategytime < time)
1384 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1385 navigation_goalrating_start();
1386 havocbot_goalrating_ctf_enemyflag(30000);
1387 havocbot_goalrating_ctf_ourstolenflag(40000);
1388 havocbot_goalrating_items(10000, self.origin, 10000);
1389 navigation_goalrating_end();
1393 void havocbot_role_ctf_offense()
1398 if(self.deadflag != DEAD_NO)
1400 havocbot_ctf_reset_role(self);
1404 if (self.flagcarried)
1406 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1411 mf = havocbot_ctf_find_flag(self);
1412 ef = havocbot_ctf_find_enemy_flag(self);
1415 if(mf.ctf_status!=FLAG_BASE)
1418 pos = mf.tag_entity.origin;
1422 // Try to get it if closer than the enemy base
1423 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1425 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1430 // Escort flag carrier
1431 if(ef.ctf_status!=FLAG_BASE)
1434 pos = ef.tag_entity.origin;
1438 if(vlen(pos-mf.dropped_origin)>700)
1440 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1445 // About to fail, switch to middlefield
1448 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1452 // Set the role timeout if necessary
1453 if (!self.havocbot_role_timeout)
1454 self.havocbot_role_timeout = time + 120;
1456 if (time > self.havocbot_role_timeout)
1458 havocbot_ctf_reset_role(self);
1462 if (self.bot_strategytime < time)
1464 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1465 navigation_goalrating_start();
1466 havocbot_goalrating_ctf_ourstolenflag(50000);
1467 havocbot_goalrating_ctf_enemybase(20000);
1468 havocbot_goalrating_items(5000, self.origin, 1000);
1469 havocbot_goalrating_items(1000, self.origin, 10000);
1470 navigation_goalrating_end();
1474 // Retriever (temporary role):
1475 void havocbot_role_ctf_retriever()
1479 if(self.deadflag != DEAD_NO)
1481 havocbot_ctf_reset_role(self);
1485 if (self.flagcarried)
1487 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1491 // If flag is back on the base switch to previous role
1492 mf = havocbot_ctf_find_flag(self);
1493 if(mf.ctf_status==FLAG_BASE)
1495 havocbot_ctf_reset_role(self);
1499 if (!self.havocbot_role_timeout)
1500 self.havocbot_role_timeout = time + 20;
1502 if (time > self.havocbot_role_timeout)
1504 havocbot_ctf_reset_role(self);
1508 if (self.bot_strategytime < time)
1513 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1514 navigation_goalrating_start();
1515 havocbot_goalrating_ctf_ourstolenflag(50000);
1516 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1517 havocbot_goalrating_ctf_enemybase(30000);
1518 havocbot_goalrating_items(500, self.origin, rt_radius);
1519 navigation_goalrating_end();
1523 void havocbot_role_ctf_middle()
1527 if(self.deadflag != DEAD_NO)
1529 havocbot_ctf_reset_role(self);
1533 if (self.flagcarried)
1535 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1539 mf = havocbot_ctf_find_flag(self);
1540 if(mf.ctf_status!=FLAG_BASE)
1542 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1546 if (!self.havocbot_role_timeout)
1547 self.havocbot_role_timeout = time + 10;
1549 if (time > self.havocbot_role_timeout)
1551 havocbot_ctf_reset_role(self);
1555 if (self.bot_strategytime < time)
1559 org = havocbot_ctf_middlepoint;
1560 org_z = self.origin_z;
1562 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1563 navigation_goalrating_start();
1564 havocbot_goalrating_ctf_ourstolenflag(50000);
1565 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1566 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1567 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1568 havocbot_goalrating_items(2500, self.origin, 10000);
1569 havocbot_goalrating_ctf_enemybase(2500);
1570 navigation_goalrating_end();
1574 void havocbot_role_ctf_defense()
1578 if(self.deadflag != DEAD_NO)
1580 havocbot_ctf_reset_role(self);
1584 if (self.flagcarried)
1586 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1590 // If own flag was captured
1591 mf = havocbot_ctf_find_flag(self);
1592 if(mf.ctf_status!=FLAG_BASE)
1594 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1598 if (!self.havocbot_role_timeout)
1599 self.havocbot_role_timeout = time + 30;
1601 if (time > self.havocbot_role_timeout)
1603 havocbot_ctf_reset_role(self);
1606 if (self.bot_strategytime < time)
1611 org = mf.dropped_origin;
1612 mp_radius = havocbot_ctf_middlepoint_radius;
1614 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1615 navigation_goalrating_start();
1617 // if enemies are closer to our base, go there
1618 entity head, closestplayer = world;
1619 float distance, bestdistance = 10000;
1620 FOR_EACH_PLAYER(head)
1622 if(head.deadflag!=DEAD_NO)
1625 distance = vlen(org - head.origin);
1626 if(distance<bestdistance)
1628 closestplayer = head;
1629 bestdistance = distance;
1634 if(closestplayer.team!=self.team)
1635 if(vlen(org - self.origin)>1000)
1636 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1637 havocbot_goalrating_ctf_ourbase(30000);
1639 havocbot_goalrating_ctf_ourstolenflag(20000);
1640 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1641 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1642 havocbot_goalrating_items(10000, org, mp_radius);
1643 havocbot_goalrating_items(5000, self.origin, 10000);
1644 navigation_goalrating_end();
1648 void havocbot_role_ctf_setrole(entity bot, float role)
1650 dprint(strcat(bot.netname," switched to "));
1653 case HAVOCBOT_CTF_ROLE_CARRIER:
1655 bot.havocbot_role = havocbot_role_ctf_carrier;
1656 bot.havocbot_role_timeout = 0;
1657 bot.havocbot_cantfindflag = time + 10;
1658 bot.bot_strategytime = 0;
1660 case HAVOCBOT_CTF_ROLE_DEFENSE:
1662 bot.havocbot_role = havocbot_role_ctf_defense;
1663 bot.havocbot_role_timeout = 0;
1665 case HAVOCBOT_CTF_ROLE_MIDDLE:
1667 bot.havocbot_role = havocbot_role_ctf_middle;
1668 bot.havocbot_role_timeout = 0;
1670 case HAVOCBOT_CTF_ROLE_OFFENSE:
1672 bot.havocbot_role = havocbot_role_ctf_offense;
1673 bot.havocbot_role_timeout = 0;
1675 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1676 dprint("retriever");
1677 bot.havocbot_previous_role = bot.havocbot_role;
1678 bot.havocbot_role = havocbot_role_ctf_retriever;
1679 bot.havocbot_role_timeout = time + 10;
1680 bot.bot_strategytime = 0;
1682 case HAVOCBOT_CTF_ROLE_ESCORT:
1684 bot.havocbot_previous_role = bot.havocbot_role;
1685 bot.havocbot_role = havocbot_role_ctf_escort;
1686 bot.havocbot_role_timeout = time + 30;
1687 bot.bot_strategytime = 0;
1698 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1702 // initially clear items so they can be set as necessary later.
1703 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1704 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1706 // scan through all the flags and notify the client about them
1707 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1709 switch(flag.ctf_status)
1714 if((flag.owner == self) || (flag.pass_sender == self))
1715 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1717 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1722 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1728 // item for stopping players from capturing the flag too often
1729 if(self.ctf_captureshielded)
1730 self.items |= IT_CTF_SHIELDED;
1732 // update the health of the flag carrier waypointsprite
1733 if(self.wps_flagcarrier)
1734 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1739 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1741 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1743 if(frag_target == frag_attacker) // damage done to yourself
1745 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1746 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1748 else // damage done to everyone else
1750 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1751 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1754 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1756 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1757 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1759 frag_target.wps_helpme_time = time;
1760 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1762 // todo: add notification for when flag carrier needs help?
1767 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1769 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1771 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1772 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1775 if(frag_target.flagcarried)
1776 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1781 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1784 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1787 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1789 entity flag; // temporary entity for the search method
1791 if(self.flagcarried)
1792 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1794 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1796 if(flag.pass_sender == self) { flag.pass_sender = world; }
1797 if(flag.pass_target == self) { flag.pass_target = world; }
1798 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1804 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1806 if(self.flagcarried)
1807 if(!autocvar_g_ctf_portalteleport)
1808 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1813 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1815 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1817 entity player = self;
1819 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1821 // pass the flag to a team mate
1822 if(autocvar_g_ctf_pass)
1824 entity head, closest_target = world;
1825 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1827 while(head) // find the closest acceptable target to pass to
1829 if(head.classname == "player" && head.deadflag == DEAD_NO)
1830 if(head != player && !IsDifferentTeam(head, player))
1831 if(!head.speedrunning && !head.vehicle)
1833 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1834 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1835 vector passer_center = CENTER_OR_VIEWOFS(player);
1837 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1839 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1841 if(clienttype(head) == CLIENTTYPE_BOT)
1843 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1844 ctf_Handle_Throw(head, player, DROP_PASS);
1848 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1849 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1851 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1854 else if(player.flagcarried)
1858 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1859 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1860 { closest_target = head; }
1862 else { closest_target = head; }
1869 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1872 // throw the flag in front of you
1873 if(autocvar_g_ctf_throw && player.flagcarried)
1875 if(player.throw_count == -1)
1877 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1879 player.throw_prevtime = time;
1880 player.throw_count = 1;
1881 ctf_Handle_Throw(player, world, DROP_THROW);
1886 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1892 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1893 else { player.throw_count += 1; }
1894 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1896 player.throw_prevtime = time;
1897 ctf_Handle_Throw(player, world, DROP_THROW);
1906 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1908 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1910 self.wps_helpme_time = time;
1911 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1913 else // create a normal help me waypointsprite
1915 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');
1916 WaypointSprite_Ping(self.wps_helpme);
1922 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1924 if(vh_player.flagcarried)
1926 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1928 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1932 setattachment(vh_player.flagcarried, vh_vehicle, "");
1933 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1934 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1935 //vh_player.flagcarried.angles = '0 0 0';
1943 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1945 if(vh_player.flagcarried)
1947 setattachment(vh_player.flagcarried, vh_player, "");
1948 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1949 vh_player.flagcarried.scale = FLAG_SCALE;
1950 vh_player.flagcarried.angles = '0 0 0';
1957 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1959 if(self.flagcarried)
1961 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1962 ctf_RespawnFlag(self.flagcarried);
1969 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1971 entity flag; // temporary entity for the search method
1973 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1975 switch(flag.ctf_status)
1980 // lock the flag, game is over
1981 flag.movetype = MOVETYPE_NONE;
1982 flag.takedamage = DAMAGE_NO;
1983 flag.solid = SOLID_NOT;
1984 flag.nextthink = FALSE; // stop thinking
1986 //dprint("stopping the ", flag.netname, " from moving.\n");
1994 // do nothing for these flags
2003 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2005 havocbot_ctf_reset_role(self);
2009 MUTATOR_HOOKFUNCTION(ctf_GetCvars)
2011 GetCvars_handleFloat(get_cvars_s, get_cvars_f, CAPTURE_VERBOSE, "notification_ctf_capture_verbose");
2012 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_TEAM_VERBOSE, "notification_ctf_pickup_team_verbose");
2013 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_ENEMY_VERBOSE, "notification_ctf_pickup_enemy_verbose");
2022 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2023 CTF Starting point for a player in team one (Red).
2024 Keys: "angle" viewing angle when spawning. */
2025 void spawnfunc_info_player_team1()
2027 if(g_assault) { remove(self); return; }
2029 self.team = FL_TEAM_1; // red
2030 spawnfunc_info_player_deathmatch();
2034 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2035 CTF Starting point for a player in team two (Blue).
2036 Keys: "angle" viewing angle when spawning. */
2037 void spawnfunc_info_player_team2()
2039 if(g_assault) { remove(self); return; }
2041 self.team = FL_TEAM_2; // blue
2042 spawnfunc_info_player_deathmatch();
2045 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2046 CTF Starting point for a player in team three (Yellow).
2047 Keys: "angle" viewing angle when spawning. */
2048 void spawnfunc_info_player_team3()
2050 if(g_assault) { remove(self); return; }
2052 self.team = FL_TEAM_3; // yellow
2053 spawnfunc_info_player_deathmatch();
2057 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2058 CTF Starting point for a player in team four (Purple).
2059 Keys: "angle" viewing angle when spawning. */
2060 void spawnfunc_info_player_team4()
2062 if(g_assault) { remove(self); return; }
2064 self.team = FL_TEAM_4; // purple
2065 spawnfunc_info_player_deathmatch();
2068 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2069 CTF flag for team one (Red).
2071 "angle" Angle the flag will point (minus 90 degrees)...
2072 "model" model to use, note this needs red and blue as skins 0 and 1...
2073 "noise" sound played when flag is picked up...
2074 "noise1" sound played when flag is returned by a teammate...
2075 "noise2" sound played when flag is captured...
2076 "noise3" sound played when flag is lost in the field and respawns itself...
2077 "noise4" sound played when flag is dropped by a player...
2078 "noise5" sound played when flag touches the ground... */
2079 void spawnfunc_item_flag_team1()
2081 if(!g_ctf) { remove(self); return; }
2083 ctf_FlagSetup(1, self); // 1 = red
2086 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2087 CTF flag for team two (Blue).
2089 "angle" Angle the flag will point (minus 90 degrees)...
2090 "model" model to use, note this needs red and blue as skins 0 and 1...
2091 "noise" sound played when flag is picked up...
2092 "noise1" sound played when flag is returned by a teammate...
2093 "noise2" sound played when flag is captured...
2094 "noise3" sound played when flag is lost in the field and respawns itself...
2095 "noise4" sound played when flag is dropped by a player...
2096 "noise5" sound played when flag touches the ground... */
2097 void spawnfunc_item_flag_team2()
2099 if(!g_ctf) { remove(self); return; }
2101 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2104 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2105 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2106 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.
2108 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2109 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2110 void spawnfunc_ctf_team()
2112 if(!g_ctf) { remove(self); return; }
2114 self.classname = "ctf_team";
2115 self.team = self.cnt + 1;
2118 // compatibility for quake maps
2119 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2120 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2121 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2122 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2123 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2124 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2132 void ctf_ScoreRules()
2134 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2135 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2136 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2137 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2138 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2139 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2140 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2141 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2142 ScoreRules_basics_end();
2145 // code from here on is just to support maps that don't have flag and team entities
2146 void ctf_SpawnTeam (string teamname, float teamcolor)
2151 self.classname = "ctf_team";
2152 self.netname = teamname;
2153 self.cnt = teamcolor;
2155 spawnfunc_ctf_team();
2160 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2162 // if no teams are found, spawn defaults
2163 if(find(world, classname, "ctf_team") == world)
2165 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2166 ctf_SpawnTeam("Red", FL_TEAM_1 - 1);
2167 ctf_SpawnTeam("Blue", FL_TEAM_2 - 1);
2173 void ctf_Initialize()
2175 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2177 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2178 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2179 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2181 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2185 MUTATOR_DEFINITION(gamemode_ctf)
2187 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2188 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2189 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2190 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2191 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2192 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2193 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2194 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2195 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2196 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2197 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2198 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2199 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2200 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2201 MUTATOR_HOOK(GetCvars, ctf_GetCvars, CBC_ORDER_ANY);
2205 if(time > 1) // game loads at time 1
2206 error("This is a game type and it cannot be added at runtime.");
2210 MUTATOR_ONROLLBACK_OR_REMOVE
2212 // we actually cannot roll back ctf_Initialize here
2213 // BUT: we don't need to! If this gets called, adding always
2219 print("This is a game type and it cannot be removed at runtime.");