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, 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)
505 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
506 else if(!IsDifferentTeam(tmp_player, player))
507 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
508 else if(!IsDifferentTeam(tmp_player, flag))
509 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
513 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
518 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
519 ctf_EventLog("steal", flag.team, player);
525 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);
526 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);
527 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
528 PlayerTeamScore_AddScore(player, pickup_dropped_score);
529 ctf_EventLog("pickup", flag.team, player);
537 if(pickuptype == PICKUP_BASE)
539 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
540 if((player.speedrunning) && (ctf_captimerecord))
541 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
545 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
548 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
549 ctf_FlagcarrierWaypoints(player);
550 WaypointSprite_Ping(player.wps_flagcarrier);
554 // ===================
555 // Main Flag Functions
556 // ===================
558 void ctf_CheckFlagReturn(entity flag, float returntype)
560 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
562 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
564 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
568 case RETURN_DROPPED: bprint("The ", flag.netname, " was dropped in the base and returned itself\n"); break;
569 case RETURN_DAMAGE: bprint("The ", flag.netname, " was destroyed and returned to base\n"); break;
570 case RETURN_SPEEDRUN: bprint("The ", flag.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n"); break;
571 case RETURN_NEEDKILL: bprint("The ", flag.netname, " fell somewhere it couldn't be reached and returned to base\n"); break;
575 { bprint("The ", flag.netname, " has returned to base\n"); break; }
577 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
578 ctf_EventLog("returned", flag.team, world);
579 ctf_RespawnFlag(flag);
584 void ctf_CheckStalemate(void)
587 float stale_red_flags, stale_blue_flags;
590 entity ctf_staleflaglist; // reset the list, we need to build the list each time this function runs
592 // build list of stale flags
593 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
595 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
596 if(tmp_entity.ctf_status != FLAG_BASE)
597 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
599 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
600 ctf_staleflaglist = tmp_entity;
602 switch(tmp_entity.team)
604 case COLOR_TEAM1: ++stale_red_flags; break;
605 case COLOR_TEAM2: ++stale_blue_flags; break;
610 if(stale_red_flags && stale_blue_flags)
611 ctf_stalemate = TRUE;
612 else if(!stale_red_flags && !stale_blue_flags)
613 ctf_stalemate = FALSE;
615 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
618 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
620 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
621 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));
624 if not(wpforenemy_announced)
626 FOR_EACH_REALPLAYER(tmp_entity)
627 if(tmp_entity.flagcarried)
628 centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
630 centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
632 wpforenemy_announced = TRUE;
637 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
639 if(ITEM_DAMAGE_NEEDKILL(deathtype))
641 // automatically kill the flag and return it
643 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
646 if(autocvar_g_ctf_flag_return_damage)
648 // reduce health and check if it should be returned
649 self.health = self.health - damage;
650 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
660 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
663 if(self == ctf_worldflaglist) // only for the first flag
664 FOR_EACH_CLIENT(tmp_entity)
665 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
668 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
669 dprint("wtf the flag got squashed?\n");
670 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
671 if(!trace_startsolid) // can we resize it without getting stuck?
672 setsize(self, FLAG_MIN, FLAG_MAX); }
674 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
678 self.angles = '0 0 0';
686 switch(self.ctf_status)
690 if(autocvar_g_ctf_dropped_capture_radius)
692 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
693 if(tmp_entity.ctf_status == FLAG_DROPPED)
694 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
695 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
702 if(autocvar_g_ctf_flag_dropped_floatinwater)
704 vector midpoint = ((self.absmin + self.absmax) * 0.5);
705 if(pointcontents(midpoint) == CONTENT_WATER)
707 self.velocity = self.velocity * 0.5;
709 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
710 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
712 { self.movetype = MOVETYPE_FLY; }
714 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
716 if(autocvar_g_ctf_flag_return_dropped)
718 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
721 ctf_CheckFlagReturn(self, RETURN_DROPPED);
725 if(autocvar_g_ctf_flag_return_time)
727 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
728 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
736 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
739 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
743 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
747 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
749 if(time >= wpforenemy_nextthink)
751 ctf_CheckStalemate();
752 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
760 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
761 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
762 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
764 if((self.pass_target == world)
765 || (self.pass_target.deadflag != DEAD_NO)
766 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
767 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
768 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
770 // give up, pass failed
771 ctf_Handle_Drop(self, world, DROP_PASS);
775 // still a viable target, go for it
776 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
781 default: // this should never happen
783 dprint("ctf_FlagThink(): Flag exists with no status?\n");
791 if(gameover) { return; }
793 entity toucher = other;
795 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
796 if(ITEM_TOUCH_NEEDKILL())
799 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
803 // special touch behaviors
804 if(toucher.vehicle_flags & VHF_ISVEHICLE)
806 if(autocvar_g_ctf_allow_vehicle_touch)
807 toucher = toucher.owner; // the player is actually the vehicle owner, not other
809 return; // do nothing
811 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
813 if(time > self.wait) // if we haven't in a while, play a sound/effect
815 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
816 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
817 self.wait = time + FLAG_TOUCHRATE;
821 else if(toucher.deadflag != DEAD_NO) { return; }
823 switch(self.ctf_status)
827 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
828 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
829 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
830 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
836 if(!IsDifferentTeam(toucher, self))
837 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
838 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
839 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
845 dprint("Someone touched a flag even though it was being carried?\n");
851 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
853 if(IsDifferentTeam(toucher, self.pass_sender))
854 ctf_Handle_Return(self, toucher);
856 ctf_Handle_Retrieve(self, toucher);
864 void ctf_RespawnFlag(entity flag)
866 // check for flag respawn being called twice in a row
867 if(flag.last_respawn > time - 0.5)
868 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
870 flag.last_respawn = time;
872 // reset the player (if there is one)
873 if((flag.owner) && (flag.owner.flagcarried == flag))
875 if(flag.owner.wps_enemyflagcarrier)
876 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
878 WaypointSprite_Kill(flag.wps_flagcarrier);
880 flag.owner.flagcarried = world;
882 if(flag.speedrunning)
883 ctf_FakeTimeLimit(flag.owner, -1);
886 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
887 { WaypointSprite_Kill(flag.wps_flagdropped); }
890 setattachment(flag, world, "");
891 setorigin(flag, flag.ctf_spawnorigin);
893 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
894 flag.takedamage = DAMAGE_NO;
895 flag.health = flag.max_flag_health;
896 flag.solid = SOLID_TRIGGER;
897 flag.velocity = '0 0 0';
898 flag.angles = flag.mangle;
899 flag.flags = FL_ITEM | FL_NOTARGET;
901 flag.ctf_status = FLAG_BASE;
903 flag.pass_distance = 0;
904 flag.pass_sender = world;
905 flag.pass_target = world;
906 flag.ctf_dropper = world;
907 flag.ctf_pickuptime = 0;
908 flag.ctf_droptime = 0;
910 wpforenemy_announced = FALSE;
916 if(self.owner.classname == "player")
917 ctf_Handle_Throw(self.owner, world, DROP_RESET);
919 ctf_RespawnFlag(self);
922 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
925 waypoint_spawnforitem_force(self, self.origin);
926 self.nearestwaypointtimeout = 0; // activate waypointing again
927 self.bot_basewaypoint = self.nearestwaypoint;
930 WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
931 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
933 // captureshield setup
934 ctf_CaptureShield_Spawn(self);
937 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
940 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.
941 self = flag; // for later usage with droptofloor()
944 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
945 ctf_worldflaglist = flag;
947 setattachment(flag, world, "");
949 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
950 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
951 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
952 flag.classname = "item_flag_team";
953 flag.target = "###item###"; // wut?
954 flag.flags = FL_ITEM | FL_NOTARGET;
955 flag.solid = SOLID_TRIGGER;
956 flag.takedamage = DAMAGE_NO;
957 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
958 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
959 flag.health = flag.max_flag_health;
960 flag.event_damage = ctf_FlagDamage;
961 flag.pushable = TRUE;
962 flag.teleportable = TELEPORT_NORMAL;
963 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
964 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
965 flag.velocity = '0 0 0';
966 flag.mangle = flag.angles;
967 flag.reset = ctf_Reset;
968 flag.touch = ctf_FlagTouch;
969 flag.think = ctf_FlagThink;
970 flag.nextthink = time + FLAG_THINKRATE;
971 flag.ctf_status = FLAG_BASE;
973 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
974 if(!flag.scale) { flag.scale = FLAG_SCALE; }
975 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
976 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
977 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
978 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
981 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
982 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
983 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
984 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.
985 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
986 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
987 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
990 precache_sound(flag.snd_flag_taken);
991 precache_sound(flag.snd_flag_returned);
992 precache_sound(flag.snd_flag_capture);
993 precache_sound(flag.snd_flag_respawn);
994 precache_sound(flag.snd_flag_dropped);
995 precache_sound(flag.snd_flag_touch);
996 precache_sound(flag.snd_flag_pass);
997 precache_model(flag.model);
998 precache_model("models/ctf/shield.md3");
999 precache_model("models/ctf/shockwavetransring.md3");
1002 setmodel(flag, flag.model); // precision set below
1003 setsize(flag, FLAG_MIN, FLAG_MAX);
1004 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1006 if(autocvar_g_ctf_flag_glowtrails)
1008 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1009 flag.glow_size = 25;
1010 flag.glow_trail = 1;
1013 flag.effects |= EF_LOWPRECISION;
1014 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1015 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1018 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1020 flag.dropped_origin = flag.origin;
1021 flag.noalign = TRUE;
1022 flag.movetype = MOVETYPE_NONE;
1024 else // drop to floor, automatically find a platform and set that as spawn origin
1026 flag.noalign = FALSE;
1029 flag.movetype = MOVETYPE_TOSS;
1032 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1040 // NOTE: LEGACY CODE, needs to be re-written!
1042 void havocbot_calculate_middlepoint()
1046 vector fo = '0 0 0';
1049 f = ctf_worldflaglist;
1054 f = f.ctf_worldflagnext;
1058 havocbot_ctf_middlepoint = s * (1.0 / n);
1059 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1063 entity havocbot_ctf_find_flag(entity bot)
1066 f = ctf_worldflaglist;
1069 if (bot.team == f.team)
1071 f = f.ctf_worldflagnext;
1076 entity havocbot_ctf_find_enemy_flag(entity bot)
1079 f = ctf_worldflaglist;
1082 if (bot.team != f.team)
1084 f = f.ctf_worldflagnext;
1089 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1097 FOR_EACH_PLAYER(head)
1099 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1102 if(vlen(head.origin - org) < tc_radius)
1109 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1112 head = ctf_worldflaglist;
1115 if (self.team == head.team)
1117 head = head.ctf_worldflagnext;
1120 navigation_routerating(head, ratingscale, 10000);
1123 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1126 head = ctf_worldflaglist;
1129 if (self.team == head.team)
1131 head = head.ctf_worldflagnext;
1136 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1139 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1142 head = ctf_worldflaglist;
1145 if (self.team != head.team)
1147 head = head.ctf_worldflagnext;
1150 navigation_routerating(head, ratingscale, 10000);
1153 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1155 if not(bot_waypoints_for_items)
1157 havocbot_goalrating_ctf_enemyflag(ratingscale);
1163 head = havocbot_ctf_find_enemy_flag(self);
1168 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1171 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1175 mf = havocbot_ctf_find_flag(self);
1177 if(mf.ctf_status == FLAG_BASE)
1181 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1184 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1187 head = ctf_worldflaglist;
1190 // flag is out in the field
1191 if(head.ctf_status != FLAG_BASE)
1192 if(head.tag_entity==world) // dropped
1196 if(vlen(org-head.origin)<df_radius)
1197 navigation_routerating(head, ratingscale, 10000);
1200 navigation_routerating(head, ratingscale, 10000);
1203 head = head.ctf_worldflagnext;
1207 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1211 head = findchainfloat(bot_pickup, TRUE);
1214 // gather health and armor only
1216 if (head.health || head.armorvalue)
1217 if (vlen(head.origin - org) < sradius)
1219 // get the value of the item
1220 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1222 navigation_routerating(head, t * ratingscale, 500);
1228 void havocbot_ctf_reset_role(entity bot)
1230 float cdefense, cmiddle, coffense;
1231 entity mf, ef, head;
1234 if(bot.deadflag != DEAD_NO)
1237 if(vlen(havocbot_ctf_middlepoint)==0)
1238 havocbot_calculate_middlepoint();
1241 if (bot.flagcarried)
1243 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1247 mf = havocbot_ctf_find_flag(bot);
1248 ef = havocbot_ctf_find_enemy_flag(bot);
1250 // Retrieve stolen flag
1251 if(mf.ctf_status!=FLAG_BASE)
1253 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1257 // If enemy flag is taken go to the middle to intercept pursuers
1258 if(ef.ctf_status!=FLAG_BASE)
1260 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1264 // if there is only me on the team switch to offense
1266 FOR_EACH_PLAYER(head)
1267 if(head.team==bot.team)
1272 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1276 // Evaluate best position to take
1277 // Count mates on middle position
1278 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1280 // Count mates on defense position
1281 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1283 // Count mates on offense position
1284 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1286 if(cdefense<=coffense)
1287 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1288 else if(coffense<=cmiddle)
1289 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1291 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1294 void havocbot_role_ctf_carrier()
1296 if(self.deadflag != DEAD_NO)
1298 havocbot_ctf_reset_role(self);
1302 if (self.flagcarried == world)
1304 havocbot_ctf_reset_role(self);
1308 if (self.bot_strategytime < time)
1310 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1312 navigation_goalrating_start();
1313 havocbot_goalrating_ctf_ourbase(50000);
1316 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1318 navigation_goalrating_end();
1320 if (self.navigation_hasgoals)
1321 self.havocbot_cantfindflag = time + 10;
1322 else if (time > self.havocbot_cantfindflag)
1324 // Can't navigate to my own base, suicide!
1325 // TODO: drop it and wander around
1326 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1332 void havocbot_role_ctf_escort()
1336 if(self.deadflag != DEAD_NO)
1338 havocbot_ctf_reset_role(self);
1342 if (self.flagcarried)
1344 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1348 // If enemy flag is back on the base switch to previous role
1349 ef = havocbot_ctf_find_enemy_flag(self);
1350 if(ef.ctf_status==FLAG_BASE)
1352 self.havocbot_role = self.havocbot_previous_role;
1353 self.havocbot_role_timeout = 0;
1357 // If the flag carrier reached the base switch to defense
1358 mf = havocbot_ctf_find_flag(self);
1359 if(mf.ctf_status!=FLAG_BASE)
1360 if(vlen(ef.origin - mf.dropped_origin) < 300)
1362 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1366 // Set the role timeout if necessary
1367 if (!self.havocbot_role_timeout)
1369 self.havocbot_role_timeout = time + random() * 30 + 60;
1372 // If nothing happened just switch to previous role
1373 if (time > self.havocbot_role_timeout)
1375 self.havocbot_role = self.havocbot_previous_role;
1376 self.havocbot_role_timeout = 0;
1380 // Chase the flag carrier
1381 if (self.bot_strategytime < time)
1383 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1384 navigation_goalrating_start();
1385 havocbot_goalrating_ctf_enemyflag(30000);
1386 havocbot_goalrating_ctf_ourstolenflag(40000);
1387 havocbot_goalrating_items(10000, self.origin, 10000);
1388 navigation_goalrating_end();
1392 void havocbot_role_ctf_offense()
1397 if(self.deadflag != DEAD_NO)
1399 havocbot_ctf_reset_role(self);
1403 if (self.flagcarried)
1405 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1410 mf = havocbot_ctf_find_flag(self);
1411 ef = havocbot_ctf_find_enemy_flag(self);
1414 if(mf.ctf_status!=FLAG_BASE)
1417 pos = mf.tag_entity.origin;
1421 // Try to get it if closer than the enemy base
1422 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1424 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1429 // Escort flag carrier
1430 if(ef.ctf_status!=FLAG_BASE)
1433 pos = ef.tag_entity.origin;
1437 if(vlen(pos-mf.dropped_origin)>700)
1439 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1444 // About to fail, switch to middlefield
1447 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1451 // Set the role timeout if necessary
1452 if (!self.havocbot_role_timeout)
1453 self.havocbot_role_timeout = time + 120;
1455 if (time > self.havocbot_role_timeout)
1457 havocbot_ctf_reset_role(self);
1461 if (self.bot_strategytime < time)
1463 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1464 navigation_goalrating_start();
1465 havocbot_goalrating_ctf_ourstolenflag(50000);
1466 havocbot_goalrating_ctf_enemybase(20000);
1467 havocbot_goalrating_items(5000, self.origin, 1000);
1468 havocbot_goalrating_items(1000, self.origin, 10000);
1469 navigation_goalrating_end();
1473 // Retriever (temporary role):
1474 void havocbot_role_ctf_retriever()
1478 if(self.deadflag != DEAD_NO)
1480 havocbot_ctf_reset_role(self);
1484 if (self.flagcarried)
1486 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1490 // If flag is back on the base switch to previous role
1491 mf = havocbot_ctf_find_flag(self);
1492 if(mf.ctf_status==FLAG_BASE)
1494 havocbot_ctf_reset_role(self);
1498 if (!self.havocbot_role_timeout)
1499 self.havocbot_role_timeout = time + 20;
1501 if (time > self.havocbot_role_timeout)
1503 havocbot_ctf_reset_role(self);
1507 if (self.bot_strategytime < time)
1512 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1513 navigation_goalrating_start();
1514 havocbot_goalrating_ctf_ourstolenflag(50000);
1515 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1516 havocbot_goalrating_ctf_enemybase(30000);
1517 havocbot_goalrating_items(500, self.origin, rt_radius);
1518 navigation_goalrating_end();
1522 void havocbot_role_ctf_middle()
1526 if(self.deadflag != DEAD_NO)
1528 havocbot_ctf_reset_role(self);
1532 if (self.flagcarried)
1534 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1538 mf = havocbot_ctf_find_flag(self);
1539 if(mf.ctf_status!=FLAG_BASE)
1541 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1545 if (!self.havocbot_role_timeout)
1546 self.havocbot_role_timeout = time + 10;
1548 if (time > self.havocbot_role_timeout)
1550 havocbot_ctf_reset_role(self);
1554 if (self.bot_strategytime < time)
1558 org = havocbot_ctf_middlepoint;
1559 org_z = self.origin_z;
1561 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1562 navigation_goalrating_start();
1563 havocbot_goalrating_ctf_ourstolenflag(50000);
1564 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1565 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1566 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1567 havocbot_goalrating_items(2500, self.origin, 10000);
1568 havocbot_goalrating_ctf_enemybase(2500);
1569 navigation_goalrating_end();
1573 void havocbot_role_ctf_defense()
1577 if(self.deadflag != DEAD_NO)
1579 havocbot_ctf_reset_role(self);
1583 if (self.flagcarried)
1585 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1589 // If own flag was captured
1590 mf = havocbot_ctf_find_flag(self);
1591 if(mf.ctf_status!=FLAG_BASE)
1593 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1597 if (!self.havocbot_role_timeout)
1598 self.havocbot_role_timeout = time + 30;
1600 if (time > self.havocbot_role_timeout)
1602 havocbot_ctf_reset_role(self);
1605 if (self.bot_strategytime < time)
1610 org = mf.dropped_origin;
1611 mp_radius = havocbot_ctf_middlepoint_radius;
1613 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1614 navigation_goalrating_start();
1616 // if enemies are closer to our base, go there
1617 entity head, closestplayer = world;
1618 float distance, bestdistance = 10000;
1619 FOR_EACH_PLAYER(head)
1621 if(head.deadflag!=DEAD_NO)
1624 distance = vlen(org - head.origin);
1625 if(distance<bestdistance)
1627 closestplayer = head;
1628 bestdistance = distance;
1633 if(closestplayer.team!=self.team)
1634 if(vlen(org - self.origin)>1000)
1635 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1636 havocbot_goalrating_ctf_ourbase(30000);
1638 havocbot_goalrating_ctf_ourstolenflag(20000);
1639 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1640 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1641 havocbot_goalrating_items(10000, org, mp_radius);
1642 havocbot_goalrating_items(5000, self.origin, 10000);
1643 navigation_goalrating_end();
1647 void havocbot_role_ctf_setrole(entity bot, float role)
1649 dprint(strcat(bot.netname," switched to "));
1652 case HAVOCBOT_CTF_ROLE_CARRIER:
1654 bot.havocbot_role = havocbot_role_ctf_carrier;
1655 bot.havocbot_role_timeout = 0;
1656 bot.havocbot_cantfindflag = time + 10;
1657 bot.bot_strategytime = 0;
1659 case HAVOCBOT_CTF_ROLE_DEFENSE:
1661 bot.havocbot_role = havocbot_role_ctf_defense;
1662 bot.havocbot_role_timeout = 0;
1664 case HAVOCBOT_CTF_ROLE_MIDDLE:
1666 bot.havocbot_role = havocbot_role_ctf_middle;
1667 bot.havocbot_role_timeout = 0;
1669 case HAVOCBOT_CTF_ROLE_OFFENSE:
1671 bot.havocbot_role = havocbot_role_ctf_offense;
1672 bot.havocbot_role_timeout = 0;
1674 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1675 dprint("retriever");
1676 bot.havocbot_previous_role = bot.havocbot_role;
1677 bot.havocbot_role = havocbot_role_ctf_retriever;
1678 bot.havocbot_role_timeout = time + 10;
1679 bot.bot_strategytime = 0;
1681 case HAVOCBOT_CTF_ROLE_ESCORT:
1683 bot.havocbot_previous_role = bot.havocbot_role;
1684 bot.havocbot_role = havocbot_role_ctf_escort;
1685 bot.havocbot_role_timeout = time + 30;
1686 bot.bot_strategytime = 0;
1697 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1701 // initially clear items so they can be set as necessary later.
1702 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1703 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1705 // scan through all the flags and notify the client about them
1706 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1708 switch(flag.ctf_status)
1713 if((flag.owner == self) || (flag.pass_sender == self))
1714 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1716 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1721 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1727 // item for stopping players from capturing the flag too often
1728 if(self.ctf_captureshielded)
1729 self.items |= IT_CTF_SHIELDED;
1731 // update the health of the flag carrier waypointsprite
1732 if(self.wps_flagcarrier)
1733 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1738 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1740 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1742 if(frag_target == frag_attacker) // damage done to yourself
1744 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1745 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1747 else // damage done to everyone else
1749 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1750 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1753 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1755 if(autocvar_g_ctf_flagcarrier_auto_helpme_when_damaged > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1756 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1761 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1763 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1765 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1766 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1769 if(frag_target.flagcarried)
1770 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1775 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1778 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1781 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1783 entity flag; // temporary entity for the search method
1785 if(self.flagcarried)
1786 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1788 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1790 if(flag.pass_sender == self) { flag.pass_sender = world; }
1791 if(flag.pass_target == self) { flag.pass_target = world; }
1792 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1798 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1800 if(self.flagcarried)
1801 if(!autocvar_g_ctf_portalteleport)
1802 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1807 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1809 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1811 entity player = self;
1813 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1815 // pass the flag to a team mate
1816 if(autocvar_g_ctf_pass)
1818 entity head, closest_target;
1819 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1821 while(head) // find the closest acceptable target to pass to
1823 if(head.classname == "player" && head.deadflag == DEAD_NO)
1824 if(head != player && !IsDifferentTeam(head, player))
1825 if(!head.speedrunning && !head.vehicle)
1827 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1828 vector head_center = WarpZone_UnTransformOrigin(head, PLAYER_CENTER(head));
1829 vector passer_center = PLAYER_CENTER(player);
1831 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1833 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1835 if(clienttype(head) == CLIENTTYPE_BOT)
1837 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1838 ctf_Handle_Throw(head, player, DROP_PASS);
1842 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname));
1843 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1845 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1848 else if(player.flagcarried)
1852 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, PLAYER_CENTER(closest_target));
1853 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1854 { closest_target = head; }
1856 else { closest_target = head; }
1863 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1866 // throw the flag in front of you
1867 if(autocvar_g_ctf_throw && player.flagcarried)
1868 { ctf_Handle_Throw(player, world, DROP_THROW); return TRUE; }
1874 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1876 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1878 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1880 else // create a normal help me waypointsprite
1882 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');
1883 WaypointSprite_Ping(self.wps_helpme);
1889 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1891 if(vh_player.flagcarried)
1893 if(!autocvar_g_ctf_allow_vehicle_carry)
1895 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1899 setattachment(vh_player.flagcarried, vh_vehicle, "");
1900 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1901 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1902 //vh_player.flagcarried.angles = '0 0 0';
1910 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1912 if(vh_player.flagcarried)
1914 setattachment(vh_player.flagcarried, vh_player, "");
1915 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1916 vh_player.flagcarried.scale = FLAG_SCALE;
1917 vh_player.flagcarried.angles = '0 0 0';
1924 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1926 if(self.flagcarried)
1928 bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
1929 ctf_RespawnFlag(self);
1936 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1938 entity flag; // temporary entity for the search method
1940 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1942 switch(flag.ctf_status)
1947 // lock the flag, game is over
1948 flag.movetype = MOVETYPE_NONE;
1949 flag.takedamage = DAMAGE_NO;
1950 flag.solid = SOLID_NOT;
1951 flag.nextthink = FALSE; // stop thinking
1953 print("stopping the ", flag.netname, " from moving.\n");
1961 // do nothing for these flags
1970 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
1972 havocbot_ctf_reset_role(self);
1981 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1982 CTF Starting point for a player in team one (Red).
1983 Keys: "angle" viewing angle when spawning. */
1984 void spawnfunc_info_player_team1()
1986 if(g_assault) { remove(self); return; }
1988 self.team = COLOR_TEAM1; // red
1989 spawnfunc_info_player_deathmatch();
1993 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
1994 CTF Starting point for a player in team two (Blue).
1995 Keys: "angle" viewing angle when spawning. */
1996 void spawnfunc_info_player_team2()
1998 if(g_assault) { remove(self); return; }
2000 self.team = COLOR_TEAM2; // blue
2001 spawnfunc_info_player_deathmatch();
2004 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2005 CTF Starting point for a player in team three (Yellow).
2006 Keys: "angle" viewing angle when spawning. */
2007 void spawnfunc_info_player_team3()
2009 if(g_assault) { remove(self); return; }
2011 self.team = COLOR_TEAM3; // yellow
2012 spawnfunc_info_player_deathmatch();
2016 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2017 CTF Starting point for a player in team four (Purple).
2018 Keys: "angle" viewing angle when spawning. */
2019 void spawnfunc_info_player_team4()
2021 if(g_assault) { remove(self); return; }
2023 self.team = COLOR_TEAM4; // purple
2024 spawnfunc_info_player_deathmatch();
2027 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2028 CTF flag for team one (Red).
2030 "angle" Angle the flag will point (minus 90 degrees)...
2031 "model" model to use, note this needs red and blue as skins 0 and 1...
2032 "noise" sound played when flag is picked up...
2033 "noise1" sound played when flag is returned by a teammate...
2034 "noise2" sound played when flag is captured...
2035 "noise3" sound played when flag is lost in the field and respawns itself...
2036 "noise4" sound played when flag is dropped by a player...
2037 "noise5" sound played when flag touches the ground... */
2038 void spawnfunc_item_flag_team1()
2040 if(!g_ctf) { remove(self); return; }
2042 ctf_FlagSetup(1, self); // 1 = red
2045 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2046 CTF flag for team two (Blue).
2048 "angle" Angle the flag will point (minus 90 degrees)...
2049 "model" model to use, note this needs red and blue as skins 0 and 1...
2050 "noise" sound played when flag is picked up...
2051 "noise1" sound played when flag is returned by a teammate...
2052 "noise2" sound played when flag is captured...
2053 "noise3" sound played when flag is lost in the field and respawns itself...
2054 "noise4" sound played when flag is dropped by a player...
2055 "noise5" sound played when flag touches the ground... */
2056 void spawnfunc_item_flag_team2()
2058 if(!g_ctf) { remove(self); return; }
2060 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2063 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2064 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2065 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.
2067 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2068 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2069 void spawnfunc_ctf_team()
2071 if(!g_ctf) { remove(self); return; }
2073 self.classname = "ctf_team";
2074 self.team = self.cnt + 1;
2077 // compatibility for quake maps
2078 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2079 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2080 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2081 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2082 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2083 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2091 void ctf_ScoreRules()
2093 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2094 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2095 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2096 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2097 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2098 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2099 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2100 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2101 ScoreRules_basics_end();
2104 // code from here on is just to support maps that don't have flag and team entities
2105 void ctf_SpawnTeam (string teamname, float teamcolor)
2110 self.classname = "ctf_team";
2111 self.netname = teamname;
2112 self.cnt = teamcolor;
2114 spawnfunc_ctf_team();
2119 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2121 // if no teams are found, spawn defaults
2122 if(find(world, classname, "ctf_team") == world)
2124 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2125 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
2126 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
2132 void ctf_Initialize()
2134 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2136 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2137 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2138 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2140 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2144 MUTATOR_DEFINITION(gamemode_ctf)
2146 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2147 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2148 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2149 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2150 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2151 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2152 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2153 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2154 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2155 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2156 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2157 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2158 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2159 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2163 if(time > 1) // game loads at time 1
2164 error("This is a game type and it cannot be added at runtime.");
2172 error("This is a game type and it cannot be removed at runtime.");