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 string ctf_CaptureRecord(entity flag, entity player)
25 float cap_time, cap_record, success;
26 string cap_message = string_null, refername;
28 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots))
30 cap_record = ctf_captimerecord;
31 cap_time = (time - flag.ctf_pickuptime);
33 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
34 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
36 if(!ctf_captimerecord)
37 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
38 else if(cap_time < cap_record)
39 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
41 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
45 ctf_captimerecord = cap_time;
46 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
47 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
48 write_recordmarker(player, (time - cap_time), cap_time);
55 void ctf_FlagcarrierWaypoints(entity player)
57 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
58 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
59 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
60 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
63 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
65 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
66 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
67 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
68 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
71 if(current_height) // make sure we can actually do this arcing path
73 targpos = (to + ('0 0 1' * current_height));
74 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
75 if(trace_fraction < 1)
77 //print("normal arc line failed, trying to find new pos...");
78 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
79 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
80 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
81 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
82 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
85 else { targpos = to; }
87 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
89 vector desired_direction = normalize(targpos - from);
90 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
91 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
94 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
96 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
98 // directional tracing only
100 makevectors(passer_angle);
102 // find the closest point on the enemy to the center of the attack
103 float ang; // angle between shotdir and h
104 float h; // hypotenuse, which is the distance between attacker to head
105 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
107 h = vlen(head_center - passer_center);
108 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
111 vector nearest_on_line = (passer_center + a * v_forward);
112 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
114 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
115 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
117 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
122 else { return TRUE; }
126 // =======================
127 // CaptureShield Functions
128 // =======================
130 float ctf_CaptureShield_CheckStatus(entity p)
134 float players_worseeq, players_total;
136 if(ctf_captureshield_max_ratio <= 0)
139 s = PlayerScore_Add(p, SP_SCORE, 0);
140 if(s >= -ctf_captureshield_min_negscore)
143 players_total = players_worseeq = 0;
146 if(IsDifferentTeam(e, p))
148 se = PlayerScore_Add(e, SP_SCORE, 0);
154 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
155 // use this rule here
157 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
163 void ctf_CaptureShield_Update(entity player, float wanted_status)
165 float updated_status = ctf_CaptureShield_CheckStatus(player);
166 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
168 if(updated_status) // TODO csqc notifier for this // Samual: How?
169 Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Make some defensive scores before trying again.", 5, 0);
171 Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.", 5, 0);
173 player.ctf_captureshielded = updated_status;
177 float ctf_CaptureShield_Customize()
179 if(!other.ctf_captureshielded) { return FALSE; }
180 if(!IsDifferentTeam(self, other)) { return FALSE; }
185 void ctf_CaptureShield_Touch()
187 if(!other.ctf_captureshielded) { return; }
188 if(!IsDifferentTeam(self, other)) { return; }
190 vector mymid = (self.absmin + self.absmax) * 0.5;
191 vector othermid = (other.absmin + other.absmax) * 0.5;
193 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
194 Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
197 void ctf_CaptureShield_Spawn(entity flag)
199 entity shield = spawn();
202 shield.team = self.team;
203 shield.touch = ctf_CaptureShield_Touch;
204 shield.customizeentityforclient = ctf_CaptureShield_Customize;
205 shield.classname = "ctf_captureshield";
206 shield.effects = EF_ADDITIVE;
207 shield.movetype = MOVETYPE_NOCLIP;
208 shield.solid = SOLID_TRIGGER;
209 shield.avelocity = '7 0 11';
212 setorigin(shield, self.origin);
213 setmodel(shield, "models/ctf/shield.md3");
214 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
218 // ====================
219 // Drop/Pass/Throw Code
220 // ====================
222 void ctf_Handle_Drop(entity flag, entity player, float droptype)
225 player = (player ? player : flag.pass_sender);
228 flag.movetype = MOVETYPE_TOSS;
229 flag.takedamage = DAMAGE_YES;
230 flag.angles = '0 0 0';
231 flag.health = flag.max_flag_health;
232 flag.ctf_droptime = time;
233 flag.ctf_dropper = player;
234 flag.ctf_status = FLAG_DROPPED;
236 // messages and sounds
237 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
238 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
239 ctf_EventLog("dropped", player.team, player);
242 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
243 PlayerScore_Add(player, SP_CTF_DROPS, 1);
246 if(autocvar_g_ctf_flag_dropped_waypoint)
247 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));
249 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
251 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
252 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
255 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
257 if(droptype == DROP_PASS)
259 flag.pass_distance = 0;
260 flag.pass_sender = world;
261 flag.pass_target = world;
265 void ctf_Handle_Retrieve(entity flag, entity player)
267 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
268 entity sender = flag.pass_sender;
270 // transfer flag to player
272 flag.owner.flagcarried = flag;
275 setattachment(flag, player, "");
276 setorigin(flag, FLAG_CARRY_OFFSET);
277 flag.movetype = MOVETYPE_NONE;
278 flag.takedamage = DAMAGE_NO;
279 flag.solid = SOLID_NOT;
280 flag.angles = '0 0 0';
281 flag.ctf_status = FLAG_CARRY;
283 // messages and sounds
284 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
285 ctf_EventLog("receive", flag.team, player);
287 FOR_EACH_REALPLAYER(tmp_player)
289 if(tmp_player == sender)
290 centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
291 else if(tmp_player == player)
292 centerprint(tmp_player, strcat("You received the ", flag.netname, " from ", sender.netname));
293 else if(!IsDifferentTeam(tmp_player, sender))
294 centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
297 // create new waypoint
298 ctf_FlagcarrierWaypoints(player);
300 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
301 player.throw_antispam = sender.throw_antispam;
303 flag.pass_distance = 0;
304 flag.pass_sender = world;
305 flag.pass_target = world;
308 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
310 entity flag = player.flagcarried;
311 vector targ_origin, flag_velocity;
313 if(!flag) { return; }
314 if((droptype == DROP_PASS) && !receiver) { return; }
316 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
319 setattachment(flag, world, "");
320 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
321 flag.owner.flagcarried = world;
323 flag.solid = SOLID_TRIGGER;
324 flag.ctf_dropper = player;
325 flag.ctf_droptime = time;
327 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
334 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
335 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
336 WarpZone_RefSys_Copy(flag, receiver);
337 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
338 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
340 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
341 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
344 flag.movetype = MOVETYPE_FLY;
345 flag.takedamage = DAMAGE_NO;
346 flag.pass_sender = player;
347 flag.pass_target = receiver;
348 flag.ctf_status = FLAG_PASSING;
351 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
352 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
353 ctf_EventLog("pass", flag.team, player);
359 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'));
361 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)));
362 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
363 ctf_Handle_Drop(flag, player, droptype);
369 flag.velocity = '0 0 0'; // do nothing
376 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);
377 ctf_Handle_Drop(flag, player, droptype);
382 // kill old waypointsprite
383 WaypointSprite_Ping(player.wps_flagcarrier);
384 WaypointSprite_Kill(player.wps_flagcarrier);
386 if(player.wps_enemyflagcarrier)
387 WaypointSprite_Kill(player.wps_enemyflagcarrier);
390 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
398 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
400 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
401 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
402 float old_time, new_time;
404 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
406 // messages and sounds
407 Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
408 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
412 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
413 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
418 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
419 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
421 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
422 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
423 if(!old_time || new_time < old_time)
424 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
427 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
428 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
431 if(capturetype == CAPTURE_NORMAL)
433 WaypointSprite_Kill(player.wps_flagcarrier);
434 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
436 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
437 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
441 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
442 ctf_RespawnFlag(enemy_flag);
445 void ctf_Handle_Return(entity flag, entity player)
447 // messages and sounds
448 //centerprint(player, strcat("You returned the ", flag.netname));
449 Send_KillNotification(player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
450 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
451 ctf_EventLog("return", flag.team, player);
454 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
455 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
457 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
461 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
462 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
463 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
467 ctf_RespawnFlag(flag);
470 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
473 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
474 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
475 float pickup_dropped_score; // used to calculate dropped pickup score
477 // attach the flag to the player
479 player.flagcarried = flag;
480 setattachment(flag, player, "");
481 setorigin(flag, FLAG_CARRY_OFFSET);
484 flag.movetype = MOVETYPE_NONE;
485 flag.takedamage = DAMAGE_NO;
486 flag.solid = SOLID_NOT;
487 flag.angles = '0 0 0';
488 flag.ctf_status = FLAG_CARRY;
492 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
493 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
497 // messages and sounds
498 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
499 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
500 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
502 FOR_EACH_REALPLAYER(tmp_player)
504 if(tmp_player == player)
506 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
507 //if(ctf_stalemate) { centerprint(tmp_player, "Stalemate! Enemies can see you on radar!"); }
509 //else if(!IsDifferentTeam(tmp_player, player))
510 // centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
511 else if(!IsDifferentTeam(tmp_player, flag))
512 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
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: bprint("The ", flag.netname, " was dropped in the base and returned itself\n"); break;
572 case RETURN_DAMAGE: bprint("The ", flag.netname, " was destroyed and returned to base\n"); break;
573 case RETURN_SPEEDRUN: bprint("The ", flag.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n"); break;
574 case RETURN_NEEDKILL: bprint("The ", flag.netname, " fell somewhere it couldn't be reached and returned to base\n"); break;
578 { bprint("The ", flag.netname, " has returned to base\n"); 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 COLOR_TEAM1: ++stale_red_flags; break;
608 case COLOR_TEAM2: ++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 if(tmp_entity.flagcarried)
633 centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
635 centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
637 wpforenemy_announced = TRUE;
642 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
644 if(ITEM_DAMAGE_NEEDKILL(deathtype))
646 // automatically kill the flag and return it
648 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
651 if(autocvar_g_ctf_flag_return_damage)
653 // reduce health and check if it should be returned
654 self.health = self.health - damage;
655 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
665 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
668 if(self == ctf_worldflaglist) // only for the first flag
669 FOR_EACH_CLIENT(tmp_entity)
670 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
673 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
674 dprint("wtf the flag got squashed?\n");
675 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
676 if(!trace_startsolid) // can we resize it without getting stuck?
677 setsize(self, FLAG_MIN, FLAG_MAX); }
679 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
683 self.angles = '0 0 0';
691 switch(self.ctf_status)
695 if(autocvar_g_ctf_dropped_capture_radius)
697 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
698 if(tmp_entity.ctf_status == FLAG_DROPPED)
699 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
700 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
701 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
708 if(autocvar_g_ctf_flag_dropped_floatinwater)
710 vector midpoint = ((self.absmin + self.absmax) * 0.5);
711 if(pointcontents(midpoint) == CONTENT_WATER)
713 self.velocity = self.velocity * 0.5;
715 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
716 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
718 { self.movetype = MOVETYPE_FLY; }
720 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
722 if(autocvar_g_ctf_flag_return_dropped)
724 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
727 ctf_CheckFlagReturn(self, RETURN_DROPPED);
731 if(autocvar_g_ctf_flag_return_time)
733 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
734 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
742 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
745 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
749 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
753 if(autocvar_g_ctf_stalemate)
755 if(time >= wpforenemy_nextthink)
757 ctf_CheckStalemate();
758 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
766 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
767 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
768 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
770 if((self.pass_target == world)
771 || (self.pass_target.deadflag != DEAD_NO)
772 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
773 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
774 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
776 // give up, pass failed
777 ctf_Handle_Drop(self, world, DROP_PASS);
781 // still a viable target, go for it
782 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
787 default: // this should never happen
789 dprint("ctf_FlagThink(): Flag exists with no status?\n");
797 if(gameover) { return; }
799 entity toucher = other;
801 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
802 if(ITEM_TOUCH_NEEDKILL())
805 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
809 // special touch behaviors
810 if(toucher.vehicle_flags & VHF_ISVEHICLE)
812 if(autocvar_g_ctf_allow_vehicle_touch)
813 toucher = toucher.owner; // the player is actually the vehicle owner, not other
815 return; // do nothing
817 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
819 if(time > self.wait) // if we haven't in a while, play a sound/effect
821 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
822 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
823 self.wait = time + FLAG_TOUCHRATE;
827 else if(toucher.deadflag != DEAD_NO) { return; }
829 switch(self.ctf_status)
833 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
834 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
835 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
836 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
842 if(!IsDifferentTeam(toucher, self))
843 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
844 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
845 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
851 dprint("Someone touched a flag even though it was being carried?\n");
857 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
859 if(IsDifferentTeam(toucher, self.pass_sender))
860 ctf_Handle_Return(self, toucher);
862 ctf_Handle_Retrieve(self, toucher);
870 void ctf_RespawnFlag(entity flag)
872 // check for flag respawn being called twice in a row
873 if(flag.last_respawn > time - 0.5)
874 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
876 flag.last_respawn = time;
878 // reset the player (if there is one)
879 if((flag.owner) && (flag.owner.flagcarried == flag))
881 if(flag.owner.wps_enemyflagcarrier)
882 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
884 WaypointSprite_Kill(flag.wps_flagcarrier);
886 flag.owner.flagcarried = world;
888 if(flag.speedrunning)
889 ctf_FakeTimeLimit(flag.owner, -1);
892 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
893 { WaypointSprite_Kill(flag.wps_flagdropped); }
896 setattachment(flag, world, "");
897 setorigin(flag, flag.ctf_spawnorigin);
899 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
900 flag.takedamage = DAMAGE_NO;
901 flag.health = flag.max_flag_health;
902 flag.solid = SOLID_TRIGGER;
903 flag.velocity = '0 0 0';
904 flag.angles = flag.mangle;
905 flag.flags = FL_ITEM | FL_NOTARGET;
907 flag.ctf_status = FLAG_BASE;
909 flag.pass_distance = 0;
910 flag.pass_sender = world;
911 flag.pass_target = world;
912 flag.ctf_dropper = world;
913 flag.ctf_pickuptime = 0;
914 flag.ctf_droptime = 0;
920 if(self.owner.classname == "player")
921 ctf_Handle_Throw(self.owner, world, DROP_RESET);
923 ctf_RespawnFlag(self);
926 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
929 waypoint_spawnforitem_force(self, self.origin);
930 self.nearestwaypointtimeout = 0; // activate waypointing again
931 self.bot_basewaypoint = self.nearestwaypoint;
934 WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
935 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
937 // captureshield setup
938 ctf_CaptureShield_Spawn(self);
941 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
944 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.
945 self = flag; // for later usage with droptofloor()
948 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
949 ctf_worldflaglist = flag;
951 setattachment(flag, world, "");
953 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
954 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
955 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
956 flag.classname = "item_flag_team";
957 flag.target = "###item###"; // wut?
958 flag.flags = FL_ITEM | FL_NOTARGET;
959 flag.solid = SOLID_TRIGGER;
960 flag.takedamage = DAMAGE_NO;
961 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
962 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
963 flag.health = flag.max_flag_health;
964 flag.event_damage = ctf_FlagDamage;
965 flag.pushable = TRUE;
966 flag.teleportable = TELEPORT_NORMAL;
967 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
968 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
969 flag.velocity = '0 0 0';
970 flag.mangle = flag.angles;
971 flag.reset = ctf_Reset;
972 flag.touch = ctf_FlagTouch;
973 flag.think = ctf_FlagThink;
974 flag.nextthink = time + FLAG_THINKRATE;
975 flag.ctf_status = FLAG_BASE;
977 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
978 if(!flag.scale) { flag.scale = FLAG_SCALE; }
979 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
980 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
981 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
982 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
985 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
986 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
987 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
988 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.
989 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
990 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
991 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
994 precache_sound(flag.snd_flag_taken);
995 precache_sound(flag.snd_flag_returned);
996 precache_sound(flag.snd_flag_capture);
997 precache_sound(flag.snd_flag_respawn);
998 precache_sound(flag.snd_flag_dropped);
999 precache_sound(flag.snd_flag_touch);
1000 precache_sound(flag.snd_flag_pass);
1001 precache_model(flag.model);
1002 precache_model("models/ctf/shield.md3");
1003 precache_model("models/ctf/shockwavetransring.md3");
1006 setmodel(flag, flag.model); // precision set below
1007 setsize(flag, FLAG_MIN, FLAG_MAX);
1008 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1010 if(autocvar_g_ctf_flag_glowtrails)
1012 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1013 flag.glow_size = 25;
1014 flag.glow_trail = 1;
1017 flag.effects |= EF_LOWPRECISION;
1018 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1019 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1022 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1024 flag.dropped_origin = flag.origin;
1025 flag.noalign = TRUE;
1026 flag.movetype = MOVETYPE_NONE;
1028 else // drop to floor, automatically find a platform and set that as spawn origin
1030 flag.noalign = FALSE;
1033 flag.movetype = MOVETYPE_TOSS;
1036 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1044 // NOTE: LEGACY CODE, needs to be re-written!
1046 void havocbot_calculate_middlepoint()
1050 vector fo = '0 0 0';
1053 f = ctf_worldflaglist;
1058 f = f.ctf_worldflagnext;
1062 havocbot_ctf_middlepoint = s * (1.0 / n);
1063 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1067 entity havocbot_ctf_find_flag(entity bot)
1070 f = ctf_worldflaglist;
1073 if (bot.team == f.team)
1075 f = f.ctf_worldflagnext;
1080 entity havocbot_ctf_find_enemy_flag(entity bot)
1083 f = ctf_worldflaglist;
1086 if (bot.team != f.team)
1088 f = f.ctf_worldflagnext;
1093 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1101 FOR_EACH_PLAYER(head)
1103 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1106 if(vlen(head.origin - org) < tc_radius)
1113 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1116 head = ctf_worldflaglist;
1119 if (self.team == head.team)
1121 head = head.ctf_worldflagnext;
1124 navigation_routerating(head, ratingscale, 10000);
1127 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1130 head = ctf_worldflaglist;
1133 if (self.team == head.team)
1135 head = head.ctf_worldflagnext;
1140 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1143 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1146 head = ctf_worldflaglist;
1149 if (self.team != head.team)
1151 head = head.ctf_worldflagnext;
1154 navigation_routerating(head, ratingscale, 10000);
1157 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1159 if not(bot_waypoints_for_items)
1161 havocbot_goalrating_ctf_enemyflag(ratingscale);
1167 head = havocbot_ctf_find_enemy_flag(self);
1172 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1175 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1179 mf = havocbot_ctf_find_flag(self);
1181 if(mf.ctf_status == FLAG_BASE)
1185 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1188 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1191 head = ctf_worldflaglist;
1194 // flag is out in the field
1195 if(head.ctf_status != FLAG_BASE)
1196 if(head.tag_entity==world) // dropped
1200 if(vlen(org-head.origin)<df_radius)
1201 navigation_routerating(head, ratingscale, 10000);
1204 navigation_routerating(head, ratingscale, 10000);
1207 head = head.ctf_worldflagnext;
1211 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1215 head = findchainfloat(bot_pickup, TRUE);
1218 // gather health and armor only
1220 if (head.health || head.armorvalue)
1221 if (vlen(head.origin - org) < sradius)
1223 // get the value of the item
1224 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1226 navigation_routerating(head, t * ratingscale, 500);
1232 void havocbot_ctf_reset_role(entity bot)
1234 float cdefense, cmiddle, coffense;
1235 entity mf, ef, head;
1238 if(bot.deadflag != DEAD_NO)
1241 if(vlen(havocbot_ctf_middlepoint)==0)
1242 havocbot_calculate_middlepoint();
1245 if (bot.flagcarried)
1247 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1251 mf = havocbot_ctf_find_flag(bot);
1252 ef = havocbot_ctf_find_enemy_flag(bot);
1254 // Retrieve stolen flag
1255 if(mf.ctf_status!=FLAG_BASE)
1257 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1261 // If enemy flag is taken go to the middle to intercept pursuers
1262 if(ef.ctf_status!=FLAG_BASE)
1264 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1268 // if there is only me on the team switch to offense
1270 FOR_EACH_PLAYER(head)
1271 if(head.team==bot.team)
1276 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1280 // Evaluate best position to take
1281 // Count mates on middle position
1282 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1284 // Count mates on defense position
1285 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1287 // Count mates on offense position
1288 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1290 if(cdefense<=coffense)
1291 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1292 else if(coffense<=cmiddle)
1293 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1295 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1298 void havocbot_role_ctf_carrier()
1300 if(self.deadflag != DEAD_NO)
1302 havocbot_ctf_reset_role(self);
1306 if (self.flagcarried == world)
1308 havocbot_ctf_reset_role(self);
1312 if (self.bot_strategytime < time)
1314 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1316 navigation_goalrating_start();
1317 havocbot_goalrating_ctf_ourbase(50000);
1320 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1322 navigation_goalrating_end();
1324 if (self.navigation_hasgoals)
1325 self.havocbot_cantfindflag = time + 10;
1326 else if (time > self.havocbot_cantfindflag)
1328 // Can't navigate to my own base, suicide!
1329 // TODO: drop it and wander around
1330 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1336 void havocbot_role_ctf_escort()
1340 if(self.deadflag != DEAD_NO)
1342 havocbot_ctf_reset_role(self);
1346 if (self.flagcarried)
1348 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1352 // If enemy flag is back on the base switch to previous role
1353 ef = havocbot_ctf_find_enemy_flag(self);
1354 if(ef.ctf_status==FLAG_BASE)
1356 self.havocbot_role = self.havocbot_previous_role;
1357 self.havocbot_role_timeout = 0;
1361 // If the flag carrier reached the base switch to defense
1362 mf = havocbot_ctf_find_flag(self);
1363 if(mf.ctf_status!=FLAG_BASE)
1364 if(vlen(ef.origin - mf.dropped_origin) < 300)
1366 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1370 // Set the role timeout if necessary
1371 if (!self.havocbot_role_timeout)
1373 self.havocbot_role_timeout = time + random() * 30 + 60;
1376 // If nothing happened just switch to previous role
1377 if (time > self.havocbot_role_timeout)
1379 self.havocbot_role = self.havocbot_previous_role;
1380 self.havocbot_role_timeout = 0;
1384 // Chase the flag carrier
1385 if (self.bot_strategytime < time)
1387 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1388 navigation_goalrating_start();
1389 havocbot_goalrating_ctf_enemyflag(30000);
1390 havocbot_goalrating_ctf_ourstolenflag(40000);
1391 havocbot_goalrating_items(10000, self.origin, 10000);
1392 navigation_goalrating_end();
1396 void havocbot_role_ctf_offense()
1401 if(self.deadflag != DEAD_NO)
1403 havocbot_ctf_reset_role(self);
1407 if (self.flagcarried)
1409 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1414 mf = havocbot_ctf_find_flag(self);
1415 ef = havocbot_ctf_find_enemy_flag(self);
1418 if(mf.ctf_status!=FLAG_BASE)
1421 pos = mf.tag_entity.origin;
1425 // Try to get it if closer than the enemy base
1426 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1428 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1433 // Escort flag carrier
1434 if(ef.ctf_status!=FLAG_BASE)
1437 pos = ef.tag_entity.origin;
1441 if(vlen(pos-mf.dropped_origin)>700)
1443 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1448 // About to fail, switch to middlefield
1451 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1455 // Set the role timeout if necessary
1456 if (!self.havocbot_role_timeout)
1457 self.havocbot_role_timeout = time + 120;
1459 if (time > self.havocbot_role_timeout)
1461 havocbot_ctf_reset_role(self);
1465 if (self.bot_strategytime < time)
1467 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1468 navigation_goalrating_start();
1469 havocbot_goalrating_ctf_ourstolenflag(50000);
1470 havocbot_goalrating_ctf_enemybase(20000);
1471 havocbot_goalrating_items(5000, self.origin, 1000);
1472 havocbot_goalrating_items(1000, self.origin, 10000);
1473 navigation_goalrating_end();
1477 // Retriever (temporary role):
1478 void havocbot_role_ctf_retriever()
1482 if(self.deadflag != DEAD_NO)
1484 havocbot_ctf_reset_role(self);
1488 if (self.flagcarried)
1490 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1494 // If flag is back on the base switch to previous role
1495 mf = havocbot_ctf_find_flag(self);
1496 if(mf.ctf_status==FLAG_BASE)
1498 havocbot_ctf_reset_role(self);
1502 if (!self.havocbot_role_timeout)
1503 self.havocbot_role_timeout = time + 20;
1505 if (time > self.havocbot_role_timeout)
1507 havocbot_ctf_reset_role(self);
1511 if (self.bot_strategytime < time)
1516 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1517 navigation_goalrating_start();
1518 havocbot_goalrating_ctf_ourstolenflag(50000);
1519 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1520 havocbot_goalrating_ctf_enemybase(30000);
1521 havocbot_goalrating_items(500, self.origin, rt_radius);
1522 navigation_goalrating_end();
1526 void havocbot_role_ctf_middle()
1530 if(self.deadflag != DEAD_NO)
1532 havocbot_ctf_reset_role(self);
1536 if (self.flagcarried)
1538 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1542 mf = havocbot_ctf_find_flag(self);
1543 if(mf.ctf_status!=FLAG_BASE)
1545 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1549 if (!self.havocbot_role_timeout)
1550 self.havocbot_role_timeout = time + 10;
1552 if (time > self.havocbot_role_timeout)
1554 havocbot_ctf_reset_role(self);
1558 if (self.bot_strategytime < time)
1562 org = havocbot_ctf_middlepoint;
1563 org_z = self.origin_z;
1565 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1566 navigation_goalrating_start();
1567 havocbot_goalrating_ctf_ourstolenflag(50000);
1568 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1569 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1570 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1571 havocbot_goalrating_items(2500, self.origin, 10000);
1572 havocbot_goalrating_ctf_enemybase(2500);
1573 navigation_goalrating_end();
1577 void havocbot_role_ctf_defense()
1581 if(self.deadflag != DEAD_NO)
1583 havocbot_ctf_reset_role(self);
1587 if (self.flagcarried)
1589 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1593 // If own flag was captured
1594 mf = havocbot_ctf_find_flag(self);
1595 if(mf.ctf_status!=FLAG_BASE)
1597 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1601 if (!self.havocbot_role_timeout)
1602 self.havocbot_role_timeout = time + 30;
1604 if (time > self.havocbot_role_timeout)
1606 havocbot_ctf_reset_role(self);
1609 if (self.bot_strategytime < time)
1614 org = mf.dropped_origin;
1615 mp_radius = havocbot_ctf_middlepoint_radius;
1617 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1618 navigation_goalrating_start();
1620 // if enemies are closer to our base, go there
1621 entity head, closestplayer = world;
1622 float distance, bestdistance = 10000;
1623 FOR_EACH_PLAYER(head)
1625 if(head.deadflag!=DEAD_NO)
1628 distance = vlen(org - head.origin);
1629 if(distance<bestdistance)
1631 closestplayer = head;
1632 bestdistance = distance;
1637 if(closestplayer.team!=self.team)
1638 if(vlen(org - self.origin)>1000)
1639 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1640 havocbot_goalrating_ctf_ourbase(30000);
1642 havocbot_goalrating_ctf_ourstolenflag(20000);
1643 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1644 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1645 havocbot_goalrating_items(10000, org, mp_radius);
1646 havocbot_goalrating_items(5000, self.origin, 10000);
1647 navigation_goalrating_end();
1651 void havocbot_role_ctf_setrole(entity bot, float role)
1653 dprint(strcat(bot.netname," switched to "));
1656 case HAVOCBOT_CTF_ROLE_CARRIER:
1658 bot.havocbot_role = havocbot_role_ctf_carrier;
1659 bot.havocbot_role_timeout = 0;
1660 bot.havocbot_cantfindflag = time + 10;
1661 bot.bot_strategytime = 0;
1663 case HAVOCBOT_CTF_ROLE_DEFENSE:
1665 bot.havocbot_role = havocbot_role_ctf_defense;
1666 bot.havocbot_role_timeout = 0;
1668 case HAVOCBOT_CTF_ROLE_MIDDLE:
1670 bot.havocbot_role = havocbot_role_ctf_middle;
1671 bot.havocbot_role_timeout = 0;
1673 case HAVOCBOT_CTF_ROLE_OFFENSE:
1675 bot.havocbot_role = havocbot_role_ctf_offense;
1676 bot.havocbot_role_timeout = 0;
1678 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1679 dprint("retriever");
1680 bot.havocbot_previous_role = bot.havocbot_role;
1681 bot.havocbot_role = havocbot_role_ctf_retriever;
1682 bot.havocbot_role_timeout = time + 10;
1683 bot.bot_strategytime = 0;
1685 case HAVOCBOT_CTF_ROLE_ESCORT:
1687 bot.havocbot_previous_role = bot.havocbot_role;
1688 bot.havocbot_role = havocbot_role_ctf_escort;
1689 bot.havocbot_role_timeout = time + 30;
1690 bot.bot_strategytime = 0;
1701 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1705 // initially clear items so they can be set as necessary later.
1706 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1707 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1709 // scan through all the flags and notify the client about them
1710 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1712 switch(flag.ctf_status)
1717 if((flag.owner == self) || (flag.pass_sender == self))
1718 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1720 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1725 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1731 // item for stopping players from capturing the flag too often
1732 if(self.ctf_captureshielded)
1733 self.items |= IT_CTF_SHIELDED;
1735 // update the health of the flag carrier waypointsprite
1736 if(self.wps_flagcarrier)
1737 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1742 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1744 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1746 if(frag_target == frag_attacker) // damage done to yourself
1748 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1749 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1751 else // damage done to everyone else
1753 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1754 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1757 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1759 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1760 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1762 frag_target.wps_helpme_time = time;
1763 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1769 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1771 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1773 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1774 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1777 if(frag_target.flagcarried)
1778 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1783 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1786 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1789 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1791 entity flag; // temporary entity for the search method
1793 if(self.flagcarried)
1794 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1796 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1798 if(flag.pass_sender == self) { flag.pass_sender = world; }
1799 if(flag.pass_target == self) { flag.pass_target = world; }
1800 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1806 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1808 if(self.flagcarried)
1809 if(!autocvar_g_ctf_portalteleport)
1810 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1815 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1817 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1819 entity player = self;
1821 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1823 // pass the flag to a team mate
1824 if(autocvar_g_ctf_pass)
1826 entity head, closest_target = world;
1827 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1829 while(head) // find the closest acceptable target to pass to
1831 if(head.classname == "player" && head.deadflag == DEAD_NO)
1832 if(head != player && !IsDifferentTeam(head, player))
1833 if(!head.speedrunning && !head.vehicle)
1835 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1836 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1837 vector passer_center = CENTER_OR_VIEWOFS(player);
1839 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1841 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1843 if(clienttype(head) == CLIENTTYPE_BOT)
1845 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1846 ctf_Handle_Throw(head, player, DROP_PASS);
1850 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname));
1851 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1853 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1856 else if(player.flagcarried)
1860 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1861 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1862 { closest_target = head; }
1864 else { closest_target = head; }
1871 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1874 // throw the flag in front of you
1875 if(autocvar_g_ctf_throw && player.flagcarried)
1877 if(player.throw_count == -1)
1879 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1881 player.throw_prevtime = time;
1882 player.throw_count = 1;
1883 ctf_Handle_Throw(player, world, DROP_THROW);
1888 centerprint(player, strcat("Too many flag throws, throwing disabled for ", ftos(rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time)), " seconds."));
1894 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1895 else { player.throw_count += 1; }
1896 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1898 player.throw_prevtime = time;
1899 ctf_Handle_Throw(player, world, DROP_THROW);
1908 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1910 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1912 self.wps_helpme_time = time;
1913 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1915 else // create a normal help me waypointsprite
1917 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');
1918 WaypointSprite_Ping(self.wps_helpme);
1924 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1926 if(vh_player.flagcarried)
1928 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1930 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1934 setattachment(vh_player.flagcarried, vh_vehicle, "");
1935 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1936 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1937 //vh_player.flagcarried.angles = '0 0 0';
1945 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1947 if(vh_player.flagcarried)
1949 setattachment(vh_player.flagcarried, vh_player, "");
1950 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1951 vh_player.flagcarried.scale = FLAG_SCALE;
1952 vh_player.flagcarried.angles = '0 0 0';
1959 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1961 if(self.flagcarried)
1963 bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
1964 ctf_RespawnFlag(self);
1971 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1973 entity flag; // temporary entity for the search method
1975 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1977 switch(flag.ctf_status)
1982 // lock the flag, game is over
1983 flag.movetype = MOVETYPE_NONE;
1984 flag.takedamage = DAMAGE_NO;
1985 flag.solid = SOLID_NOT;
1986 flag.nextthink = FALSE; // stop thinking
1988 //dprint("stopping the ", flag.netname, " from moving.\n");
1996 // do nothing for these flags
2005 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2007 havocbot_ctf_reset_role(self);
2016 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2017 CTF Starting point for a player in team one (Red).
2018 Keys: "angle" viewing angle when spawning. */
2019 void spawnfunc_info_player_team1()
2021 if(g_assault) { remove(self); return; }
2023 self.team = COLOR_TEAM1; // red
2024 spawnfunc_info_player_deathmatch();
2028 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2029 CTF Starting point for a player in team two (Blue).
2030 Keys: "angle" viewing angle when spawning. */
2031 void spawnfunc_info_player_team2()
2033 if(g_assault) { remove(self); return; }
2035 self.team = COLOR_TEAM2; // blue
2036 spawnfunc_info_player_deathmatch();
2039 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2040 CTF Starting point for a player in team three (Yellow).
2041 Keys: "angle" viewing angle when spawning. */
2042 void spawnfunc_info_player_team3()
2044 if(g_assault) { remove(self); return; }
2046 self.team = COLOR_TEAM3; // yellow
2047 spawnfunc_info_player_deathmatch();
2051 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2052 CTF Starting point for a player in team four (Purple).
2053 Keys: "angle" viewing angle when spawning. */
2054 void spawnfunc_info_player_team4()
2056 if(g_assault) { remove(self); return; }
2058 self.team = COLOR_TEAM4; // purple
2059 spawnfunc_info_player_deathmatch();
2062 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2063 CTF flag for team one (Red).
2065 "angle" Angle the flag will point (minus 90 degrees)...
2066 "model" model to use, note this needs red and blue as skins 0 and 1...
2067 "noise" sound played when flag is picked up...
2068 "noise1" sound played when flag is returned by a teammate...
2069 "noise2" sound played when flag is captured...
2070 "noise3" sound played when flag is lost in the field and respawns itself...
2071 "noise4" sound played when flag is dropped by a player...
2072 "noise5" sound played when flag touches the ground... */
2073 void spawnfunc_item_flag_team1()
2075 if(!g_ctf) { remove(self); return; }
2077 ctf_FlagSetup(1, self); // 1 = red
2080 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2081 CTF flag for team two (Blue).
2083 "angle" Angle the flag will point (minus 90 degrees)...
2084 "model" model to use, note this needs red and blue as skins 0 and 1...
2085 "noise" sound played when flag is picked up...
2086 "noise1" sound played when flag is returned by a teammate...
2087 "noise2" sound played when flag is captured...
2088 "noise3" sound played when flag is lost in the field and respawns itself...
2089 "noise4" sound played when flag is dropped by a player...
2090 "noise5" sound played when flag touches the ground... */
2091 void spawnfunc_item_flag_team2()
2093 if(!g_ctf) { remove(self); return; }
2095 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2098 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2099 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2100 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.
2102 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2103 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2104 void spawnfunc_ctf_team()
2106 if(!g_ctf) { remove(self); return; }
2108 self.classname = "ctf_team";
2109 self.team = self.cnt + 1;
2112 // compatibility for quake maps
2113 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2114 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2115 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2116 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2117 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2118 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2126 void ctf_ScoreRules()
2128 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2129 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2130 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2131 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2132 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2133 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2134 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2135 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2136 ScoreRules_basics_end();
2139 // code from here on is just to support maps that don't have flag and team entities
2140 void ctf_SpawnTeam (string teamname, float teamcolor)
2145 self.classname = "ctf_team";
2146 self.netname = teamname;
2147 self.cnt = teamcolor;
2149 spawnfunc_ctf_team();
2154 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2156 // if no teams are found, spawn defaults
2157 if(find(world, classname, "ctf_team") == world)
2159 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2160 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
2161 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
2167 void ctf_Initialize()
2169 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2171 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2172 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2173 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2175 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2179 MUTATOR_DEFINITION(gamemode_ctf)
2181 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2182 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2183 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2184 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2185 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2186 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2187 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2188 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2189 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2190 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2191 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2192 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2193 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2194 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2198 if(time > 1) // game loads at time 1
2199 error("This is a game type and it cannot be added at runtime.");
2203 MUTATOR_ONROLLBACK_OR_REMOVE
2205 // we actually cannot roll back ctf_Initialize here
2206 // BUT: we don't need to! If this gets called, adding always
2212 print("This is a game type and it cannot be removed at runtime.");