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);
92 flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity);
94 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
97 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
99 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
101 // directional tracing only
103 makevectors(passer_angle);
105 // find the closest point on the enemy to the center of the attack
106 float ang; // angle between shotdir and h
107 float h; // hypotenuse, which is the distance between attacker to head
108 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
110 h = vlen(head_center - passer_center);
111 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
114 vector nearest_on_line = (passer_center + a * v_forward);
115 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
117 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
118 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
120 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
125 else { return TRUE; }
129 // =======================
130 // CaptureShield Functions
131 // =======================
133 float ctf_CaptureShield_CheckStatus(entity p)
137 float players_worseeq, players_total;
139 if(ctf_captureshield_max_ratio <= 0)
142 s = PlayerScore_Add(p, SP_SCORE, 0);
143 if(s >= -ctf_captureshield_min_negscore)
146 players_total = players_worseeq = 0;
149 if(IsDifferentTeam(e, p))
151 se = PlayerScore_Add(e, SP_SCORE, 0);
157 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
158 // use this rule here
160 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
166 void ctf_CaptureShield_Update(entity player, float wanted_status)
168 float updated_status = ctf_CaptureShield_CheckStatus(player);
169 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
171 if(updated_status) // TODO csqc notifier for this // Samual: How?
172 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);
174 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);
176 player.ctf_captureshielded = updated_status;
180 float ctf_CaptureShield_Customize()
182 if(!other.ctf_captureshielded) { return FALSE; }
183 if(!IsDifferentTeam(self, other)) { return FALSE; }
188 void ctf_CaptureShield_Touch()
190 if(!other.ctf_captureshielded) { return; }
191 if(!IsDifferentTeam(self, other)) { return; }
193 vector mymid = (self.absmin + self.absmax) * 0.5;
194 vector othermid = (other.absmin + other.absmax) * 0.5;
196 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
197 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);
200 void ctf_CaptureShield_Spawn(entity flag)
202 entity shield = spawn();
205 shield.team = self.team;
206 shield.touch = ctf_CaptureShield_Touch;
207 shield.customizeentityforclient = ctf_CaptureShield_Customize;
208 shield.classname = "ctf_captureshield";
209 shield.effects = EF_ADDITIVE;
210 shield.movetype = MOVETYPE_NOCLIP;
211 shield.solid = SOLID_TRIGGER;
212 shield.avelocity = '7 0 11';
215 setorigin(shield, self.origin);
216 setmodel(shield, "models/ctf/shield.md3");
217 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
221 // ====================
222 // Drop/Pass/Throw Code
223 // ====================
225 void ctf_Handle_Drop(entity flag, entity player, float droptype)
228 player = (player ? player : flag.pass_sender);
231 flag.movetype = MOVETYPE_TOSS;
232 flag.takedamage = DAMAGE_YES;
233 flag.angles = '0 0 0';
234 flag.health = flag.max_flag_health;
235 flag.ctf_droptime = time;
236 flag.ctf_dropper = player;
237 flag.ctf_status = FLAG_DROPPED;
239 // messages and sounds
240 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
241 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
242 ctf_EventLog("dropped", player.team, player);
245 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
246 PlayerScore_Add(player, SP_CTF_DROPS, 1);
249 if(autocvar_g_ctf_flag_dropped_waypoint)
250 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));
252 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
254 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
255 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
258 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
260 if(droptype == DROP_PASS)
262 flag.pass_distance = 0;
263 flag.pass_sender = world;
264 flag.pass_target = world;
268 void ctf_Handle_Retrieve(entity flag, entity player)
270 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
271 entity sender = flag.pass_sender;
273 // transfer flag to player
275 flag.owner.flagcarried = flag;
278 setattachment(flag, player, "");
279 setorigin(flag, FLAG_CARRY_OFFSET);
280 flag.movetype = MOVETYPE_NONE;
281 flag.takedamage = DAMAGE_NO;
282 flag.solid = SOLID_NOT;
283 flag.angles = '0 0 0';
284 flag.ctf_status = FLAG_CARRY;
286 // messages and sounds
287 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
288 ctf_EventLog("receive", flag.team, player);
290 FOR_EACH_REALPLAYER(tmp_player)
292 if(tmp_player == sender)
293 centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
294 else if(tmp_player == player)
295 centerprint(tmp_player, strcat("You received the ", flag.netname, " from ", sender.netname));
296 else if(!IsDifferentTeam(tmp_player, sender))
297 centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
300 // create new waypoint
301 ctf_FlagcarrierWaypoints(player);
303 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
304 player.throw_antispam = sender.throw_antispam;
306 flag.pass_distance = 0;
307 flag.pass_sender = world;
308 flag.pass_target = world;
311 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
313 entity flag = player.flagcarried;
314 vector targ_origin, flag_velocity;
316 if(!flag) { return; }
317 if((droptype == DROP_PASS) && !receiver) { return; }
319 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
322 setattachment(flag, world, "");
323 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
324 flag.owner.flagcarried = world;
326 flag.solid = SOLID_TRIGGER;
327 flag.ctf_dropper = player;
328 flag.ctf_droptime = time;
330 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
337 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
338 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
339 WarpZone_RefSys_Copy(flag, receiver);
340 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
341 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
343 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
344 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
347 flag.movetype = MOVETYPE_FLY;
348 flag.takedamage = DAMAGE_NO;
349 flag.pass_sender = player;
350 flag.pass_target = receiver;
351 flag.ctf_status = FLAG_PASSING;
354 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
355 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
356 ctf_EventLog("pass", flag.team, player);
362 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'));
364 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)));
365 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
366 ctf_Handle_Drop(flag, player, droptype);
372 flag.velocity = '0 0 0'; // do nothing
379 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);
380 ctf_Handle_Drop(flag, player, droptype);
385 // kill old waypointsprite
386 WaypointSprite_Ping(player.wps_flagcarrier);
387 WaypointSprite_Kill(player.wps_flagcarrier);
389 if(player.wps_enemyflagcarrier)
390 WaypointSprite_Kill(player.wps_enemyflagcarrier);
393 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
401 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
403 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
404 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
405 float old_time, new_time;
407 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
409 // messages and sounds
410 Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
411 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
415 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
416 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
421 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
422 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
424 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
425 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
426 if(!old_time || new_time < old_time)
427 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
430 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
431 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
434 if(capturetype == CAPTURE_NORMAL)
436 WaypointSprite_Kill(player.wps_flagcarrier);
437 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
439 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
440 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
444 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
445 ctf_RespawnFlag(enemy_flag);
448 void ctf_Handle_Return(entity flag, entity player)
450 // messages and sounds
451 //centerprint(player, strcat("You returned the ", flag.netname));
452 Send_KillNotification(player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
453 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
454 ctf_EventLog("return", flag.team, player);
457 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
458 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
460 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
464 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
465 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
466 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
470 ctf_RespawnFlag(flag);
473 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
476 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
477 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
478 float pickup_dropped_score; // used to calculate dropped pickup score
480 // attach the flag to the player
482 player.flagcarried = flag;
483 setattachment(flag, player, "");
484 setorigin(flag, FLAG_CARRY_OFFSET);
487 flag.movetype = MOVETYPE_NONE;
488 flag.takedamage = DAMAGE_NO;
489 flag.solid = SOLID_NOT;
490 flag.angles = '0 0 0';
491 flag.ctf_status = FLAG_CARRY;
495 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
496 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
500 // messages and sounds
501 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
502 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
503 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
505 FOR_EACH_REALPLAYER(tmp_player)
507 if(tmp_player == player)
508 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
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, stale_blue_flags;
593 entity ctf_staleflaglist; // 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_flagcarrier_waypointforenemy_stalemate)
599 if(tmp_entity.ctf_status != FLAG_BASE)
600 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
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)
616 ctf_stalemate = FALSE;
618 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
621 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
623 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
624 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));
627 if not(wpforenemy_announced)
629 FOR_EACH_REALPLAYER(tmp_entity)
630 if(tmp_entity.flagcarried)
631 centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
633 centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
635 wpforenemy_announced = TRUE;
640 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
642 if(ITEM_DAMAGE_NEEDKILL(deathtype))
644 // automatically kill the flag and return it
646 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
649 if(autocvar_g_ctf_flag_return_damage)
651 // reduce health and check if it should be returned
652 self.health = self.health - damage;
653 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
663 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
666 if(self == ctf_worldflaglist) // only for the first flag
667 FOR_EACH_CLIENT(tmp_entity)
668 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
671 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
672 dprint("wtf the flag got squashed?\n");
673 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
674 if(!trace_startsolid) // can we resize it without getting stuck?
675 setsize(self, FLAG_MIN, FLAG_MAX); }
677 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
681 self.angles = '0 0 0';
689 switch(self.ctf_status)
693 if(autocvar_g_ctf_dropped_capture_radius)
695 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
696 if(tmp_entity.ctf_status == FLAG_DROPPED)
697 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
698 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
705 if(autocvar_g_ctf_flag_dropped_floatinwater)
707 vector midpoint = ((self.absmin + self.absmax) * 0.5);
708 if(pointcontents(midpoint) == CONTENT_WATER)
710 self.velocity = self.velocity * 0.5;
712 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
713 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
715 { self.movetype = MOVETYPE_FLY; }
717 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
719 if(autocvar_g_ctf_flag_return_dropped)
721 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
724 ctf_CheckFlagReturn(self, RETURN_DROPPED);
728 if(autocvar_g_ctf_flag_return_time)
730 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
731 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
739 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
742 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
746 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
750 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
752 if(time >= wpforenemy_nextthink)
754 ctf_CheckStalemate();
755 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
763 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
764 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
765 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
767 if((self.pass_target == world)
768 || (self.pass_target.deadflag != DEAD_NO)
769 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
770 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
771 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
773 // give up, pass failed
774 ctf_Handle_Drop(self, world, DROP_PASS);
778 // still a viable target, go for it
779 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
784 default: // this should never happen
786 dprint("ctf_FlagThink(): Flag exists with no status?\n");
794 if(gameover) { return; }
796 entity toucher = other;
798 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
799 if(ITEM_TOUCH_NEEDKILL())
802 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
806 // special touch behaviors
807 if(toucher.vehicle_flags & VHF_ISVEHICLE)
809 if(autocvar_g_ctf_allow_vehicle_touch)
810 toucher = toucher.owner; // the player is actually the vehicle owner, not other
812 return; // do nothing
814 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
816 if(time > self.wait) // if we haven't in a while, play a sound/effect
818 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
819 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
820 self.wait = time + FLAG_TOUCHRATE;
824 else if(toucher.deadflag != DEAD_NO) { return; }
826 switch(self.ctf_status)
830 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
831 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
832 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
833 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
839 if(!IsDifferentTeam(toucher, self))
840 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
841 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
842 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
848 dprint("Someone touched a flag even though it was being carried?\n");
854 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
856 if(IsDifferentTeam(toucher, self.pass_sender))
857 ctf_Handle_Return(self, toucher);
859 ctf_Handle_Retrieve(self, toucher);
867 void ctf_RespawnFlag(entity flag)
869 // check for flag respawn being called twice in a row
870 if(flag.last_respawn > time - 0.5)
871 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
873 flag.last_respawn = time;
875 // reset the player (if there is one)
876 if((flag.owner) && (flag.owner.flagcarried == flag))
878 if(flag.owner.wps_enemyflagcarrier)
879 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
881 WaypointSprite_Kill(flag.wps_flagcarrier);
883 flag.owner.flagcarried = world;
885 if(flag.speedrunning)
886 ctf_FakeTimeLimit(flag.owner, -1);
889 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
890 { WaypointSprite_Kill(flag.wps_flagdropped); }
893 setattachment(flag, world, "");
894 setorigin(flag, flag.ctf_spawnorigin);
896 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
897 flag.takedamage = DAMAGE_NO;
898 flag.health = flag.max_flag_health;
899 flag.solid = SOLID_TRIGGER;
900 flag.velocity = '0 0 0';
901 flag.angles = flag.mangle;
902 flag.flags = FL_ITEM | FL_NOTARGET;
904 flag.ctf_status = FLAG_BASE;
906 flag.pass_distance = 0;
907 flag.pass_sender = world;
908 flag.pass_target = world;
909 flag.ctf_dropper = world;
910 flag.ctf_pickuptime = 0;
911 flag.ctf_droptime = 0;
913 wpforenemy_announced = FALSE;
919 if(self.owner.classname == "player")
920 ctf_Handle_Throw(self.owner, world, DROP_RESET);
922 ctf_RespawnFlag(self);
925 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
928 waypoint_spawnforitem_force(self, self.origin);
929 self.nearestwaypointtimeout = 0; // activate waypointing again
930 self.bot_basewaypoint = self.nearestwaypoint;
933 WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
934 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
936 // captureshield setup
937 ctf_CaptureShield_Spawn(self);
940 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
943 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.
944 self = flag; // for later usage with droptofloor()
947 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
948 ctf_worldflaglist = flag;
950 setattachment(flag, world, "");
952 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
953 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
954 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
955 flag.classname = "item_flag_team";
956 flag.target = "###item###"; // wut?
957 flag.flags = FL_ITEM | FL_NOTARGET;
958 flag.solid = SOLID_TRIGGER;
959 flag.takedamage = DAMAGE_NO;
960 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
961 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
962 flag.health = flag.max_flag_health;
963 flag.event_damage = ctf_FlagDamage;
964 flag.pushable = TRUE;
965 flag.teleportable = TELEPORT_NORMAL;
966 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
967 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
968 flag.velocity = '0 0 0';
969 flag.mangle = flag.angles;
970 flag.reset = ctf_Reset;
971 flag.touch = ctf_FlagTouch;
972 flag.think = ctf_FlagThink;
973 flag.nextthink = time + FLAG_THINKRATE;
974 flag.ctf_status = FLAG_BASE;
976 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
977 if(!flag.scale) { flag.scale = FLAG_SCALE; }
978 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
979 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
980 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
981 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
984 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
985 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
986 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
987 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.
988 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
989 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
990 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
993 precache_sound(flag.snd_flag_taken);
994 precache_sound(flag.snd_flag_returned);
995 precache_sound(flag.snd_flag_capture);
996 precache_sound(flag.snd_flag_respawn);
997 precache_sound(flag.snd_flag_dropped);
998 precache_sound(flag.snd_flag_touch);
999 precache_sound(flag.snd_flag_pass);
1000 precache_model(flag.model);
1001 precache_model("models/ctf/shield.md3");
1002 precache_model("models/ctf/shockwavetransring.md3");
1005 setmodel(flag, flag.model); // precision set below
1006 setsize(flag, FLAG_MIN, FLAG_MAX);
1007 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1009 if(autocvar_g_ctf_flag_glowtrails)
1011 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1012 flag.glow_size = 25;
1013 flag.glow_trail = 1;
1016 flag.effects |= EF_LOWPRECISION;
1017 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1018 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1021 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1023 flag.dropped_origin = flag.origin;
1024 flag.noalign = TRUE;
1025 flag.movetype = MOVETYPE_NONE;
1027 else // drop to floor, automatically find a platform and set that as spawn origin
1029 flag.noalign = FALSE;
1032 flag.movetype = MOVETYPE_TOSS;
1035 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1043 // NOTE: LEGACY CODE, needs to be re-written!
1045 void havocbot_calculate_middlepoint()
1049 vector fo = '0 0 0';
1052 f = ctf_worldflaglist;
1057 f = f.ctf_worldflagnext;
1061 havocbot_ctf_middlepoint = s * (1.0 / n);
1062 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1066 entity havocbot_ctf_find_flag(entity bot)
1069 f = ctf_worldflaglist;
1072 if (bot.team == f.team)
1074 f = f.ctf_worldflagnext;
1079 entity havocbot_ctf_find_enemy_flag(entity bot)
1082 f = ctf_worldflaglist;
1085 if (bot.team != f.team)
1087 f = f.ctf_worldflagnext;
1092 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1100 FOR_EACH_PLAYER(head)
1102 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1105 if(vlen(head.origin - org) < tc_radius)
1112 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1115 head = ctf_worldflaglist;
1118 if (self.team == head.team)
1120 head = head.ctf_worldflagnext;
1123 navigation_routerating(head, ratingscale, 10000);
1126 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1129 head = ctf_worldflaglist;
1132 if (self.team == head.team)
1134 head = head.ctf_worldflagnext;
1139 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1142 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1145 head = ctf_worldflaglist;
1148 if (self.team != head.team)
1150 head = head.ctf_worldflagnext;
1153 navigation_routerating(head, ratingscale, 10000);
1156 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1158 if not(bot_waypoints_for_items)
1160 havocbot_goalrating_ctf_enemyflag(ratingscale);
1166 head = havocbot_ctf_find_enemy_flag(self);
1171 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1174 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1178 mf = havocbot_ctf_find_flag(self);
1180 if(mf.ctf_status == FLAG_BASE)
1184 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1187 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1190 head = ctf_worldflaglist;
1193 // flag is out in the field
1194 if(head.ctf_status != FLAG_BASE)
1195 if(head.tag_entity==world) // dropped
1199 if(vlen(org-head.origin)<df_radius)
1200 navigation_routerating(head, ratingscale, 10000);
1203 navigation_routerating(head, ratingscale, 10000);
1206 head = head.ctf_worldflagnext;
1210 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1214 head = findchainfloat(bot_pickup, TRUE);
1217 // gather health and armor only
1219 if (head.health || head.armorvalue)
1220 if (vlen(head.origin - org) < sradius)
1222 // get the value of the item
1223 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1225 navigation_routerating(head, t * ratingscale, 500);
1231 void havocbot_ctf_reset_role(entity bot)
1233 float cdefense, cmiddle, coffense;
1234 entity mf, ef, head;
1237 if(bot.deadflag != DEAD_NO)
1240 if(vlen(havocbot_ctf_middlepoint)==0)
1241 havocbot_calculate_middlepoint();
1244 if (bot.flagcarried)
1246 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1250 mf = havocbot_ctf_find_flag(bot);
1251 ef = havocbot_ctf_find_enemy_flag(bot);
1253 // Retrieve stolen flag
1254 if(mf.ctf_status!=FLAG_BASE)
1256 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1260 // If enemy flag is taken go to the middle to intercept pursuers
1261 if(ef.ctf_status!=FLAG_BASE)
1263 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1267 // if there is only me on the team switch to offense
1269 FOR_EACH_PLAYER(head)
1270 if(head.team==bot.team)
1275 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1279 // Evaluate best position to take
1280 // Count mates on middle position
1281 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1283 // Count mates on defense position
1284 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1286 // Count mates on offense position
1287 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1289 if(cdefense<=coffense)
1290 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1291 else if(coffense<=cmiddle)
1292 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1294 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1297 void havocbot_role_ctf_carrier()
1299 if(self.deadflag != DEAD_NO)
1301 havocbot_ctf_reset_role(self);
1305 if (self.flagcarried == world)
1307 havocbot_ctf_reset_role(self);
1311 if (self.bot_strategytime < time)
1313 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1315 navigation_goalrating_start();
1316 havocbot_goalrating_ctf_ourbase(50000);
1319 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1321 navigation_goalrating_end();
1323 if (self.navigation_hasgoals)
1324 self.havocbot_cantfindflag = time + 10;
1325 else if (time > self.havocbot_cantfindflag)
1327 // Can't navigate to my own base, suicide!
1328 // TODO: drop it and wander around
1329 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1335 void havocbot_role_ctf_escort()
1339 if(self.deadflag != DEAD_NO)
1341 havocbot_ctf_reset_role(self);
1345 if (self.flagcarried)
1347 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1351 // If enemy flag is back on the base switch to previous role
1352 ef = havocbot_ctf_find_enemy_flag(self);
1353 if(ef.ctf_status==FLAG_BASE)
1355 self.havocbot_role = self.havocbot_previous_role;
1356 self.havocbot_role_timeout = 0;
1360 // If the flag carrier reached the base switch to defense
1361 mf = havocbot_ctf_find_flag(self);
1362 if(mf.ctf_status!=FLAG_BASE)
1363 if(vlen(ef.origin - mf.dropped_origin) < 300)
1365 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1369 // Set the role timeout if necessary
1370 if (!self.havocbot_role_timeout)
1372 self.havocbot_role_timeout = time + random() * 30 + 60;
1375 // If nothing happened just switch to previous role
1376 if (time > self.havocbot_role_timeout)
1378 self.havocbot_role = self.havocbot_previous_role;
1379 self.havocbot_role_timeout = 0;
1383 // Chase the flag carrier
1384 if (self.bot_strategytime < time)
1386 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1387 navigation_goalrating_start();
1388 havocbot_goalrating_ctf_enemyflag(30000);
1389 havocbot_goalrating_ctf_ourstolenflag(40000);
1390 havocbot_goalrating_items(10000, self.origin, 10000);
1391 navigation_goalrating_end();
1395 void havocbot_role_ctf_offense()
1400 if(self.deadflag != DEAD_NO)
1402 havocbot_ctf_reset_role(self);
1406 if (self.flagcarried)
1408 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1413 mf = havocbot_ctf_find_flag(self);
1414 ef = havocbot_ctf_find_enemy_flag(self);
1417 if(mf.ctf_status!=FLAG_BASE)
1420 pos = mf.tag_entity.origin;
1424 // Try to get it if closer than the enemy base
1425 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1427 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1432 // Escort flag carrier
1433 if(ef.ctf_status!=FLAG_BASE)
1436 pos = ef.tag_entity.origin;
1440 if(vlen(pos-mf.dropped_origin)>700)
1442 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1447 // About to fail, switch to middlefield
1450 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1454 // Set the role timeout if necessary
1455 if (!self.havocbot_role_timeout)
1456 self.havocbot_role_timeout = time + 120;
1458 if (time > self.havocbot_role_timeout)
1460 havocbot_ctf_reset_role(self);
1464 if (self.bot_strategytime < time)
1466 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1467 navigation_goalrating_start();
1468 havocbot_goalrating_ctf_ourstolenflag(50000);
1469 havocbot_goalrating_ctf_enemybase(20000);
1470 havocbot_goalrating_items(5000, self.origin, 1000);
1471 havocbot_goalrating_items(1000, self.origin, 10000);
1472 navigation_goalrating_end();
1476 // Retriever (temporary role):
1477 void havocbot_role_ctf_retriever()
1481 if(self.deadflag != DEAD_NO)
1483 havocbot_ctf_reset_role(self);
1487 if (self.flagcarried)
1489 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1493 // If flag is back on the base switch to previous role
1494 mf = havocbot_ctf_find_flag(self);
1495 if(mf.ctf_status==FLAG_BASE)
1497 havocbot_ctf_reset_role(self);
1501 if (!self.havocbot_role_timeout)
1502 self.havocbot_role_timeout = time + 20;
1504 if (time > self.havocbot_role_timeout)
1506 havocbot_ctf_reset_role(self);
1510 if (self.bot_strategytime < time)
1515 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1516 navigation_goalrating_start();
1517 havocbot_goalrating_ctf_ourstolenflag(50000);
1518 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1519 havocbot_goalrating_ctf_enemybase(30000);
1520 havocbot_goalrating_items(500, self.origin, rt_radius);
1521 navigation_goalrating_end();
1525 void havocbot_role_ctf_middle()
1529 if(self.deadflag != DEAD_NO)
1531 havocbot_ctf_reset_role(self);
1535 if (self.flagcarried)
1537 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1541 mf = havocbot_ctf_find_flag(self);
1542 if(mf.ctf_status!=FLAG_BASE)
1544 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1548 if (!self.havocbot_role_timeout)
1549 self.havocbot_role_timeout = time + 10;
1551 if (time > self.havocbot_role_timeout)
1553 havocbot_ctf_reset_role(self);
1557 if (self.bot_strategytime < time)
1561 org = havocbot_ctf_middlepoint;
1562 org_z = self.origin_z;
1564 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1565 navigation_goalrating_start();
1566 havocbot_goalrating_ctf_ourstolenflag(50000);
1567 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1568 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1569 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1570 havocbot_goalrating_items(2500, self.origin, 10000);
1571 havocbot_goalrating_ctf_enemybase(2500);
1572 navigation_goalrating_end();
1576 void havocbot_role_ctf_defense()
1580 if(self.deadflag != DEAD_NO)
1582 havocbot_ctf_reset_role(self);
1586 if (self.flagcarried)
1588 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1592 // If own flag was captured
1593 mf = havocbot_ctf_find_flag(self);
1594 if(mf.ctf_status!=FLAG_BASE)
1596 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1600 if (!self.havocbot_role_timeout)
1601 self.havocbot_role_timeout = time + 30;
1603 if (time > self.havocbot_role_timeout)
1605 havocbot_ctf_reset_role(self);
1608 if (self.bot_strategytime < time)
1613 org = mf.dropped_origin;
1614 mp_radius = havocbot_ctf_middlepoint_radius;
1616 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1617 navigation_goalrating_start();
1619 // if enemies are closer to our base, go there
1620 entity head, closestplayer = world;
1621 float distance, bestdistance = 10000;
1622 FOR_EACH_PLAYER(head)
1624 if(head.deadflag!=DEAD_NO)
1627 distance = vlen(org - head.origin);
1628 if(distance<bestdistance)
1630 closestplayer = head;
1631 bestdistance = distance;
1636 if(closestplayer.team!=self.team)
1637 if(vlen(org - self.origin)>1000)
1638 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1639 havocbot_goalrating_ctf_ourbase(30000);
1641 havocbot_goalrating_ctf_ourstolenflag(20000);
1642 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1643 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1644 havocbot_goalrating_items(10000, org, mp_radius);
1645 havocbot_goalrating_items(5000, self.origin, 10000);
1646 navigation_goalrating_end();
1650 void havocbot_role_ctf_setrole(entity bot, float role)
1652 dprint(strcat(bot.netname," switched to "));
1655 case HAVOCBOT_CTF_ROLE_CARRIER:
1657 bot.havocbot_role = havocbot_role_ctf_carrier;
1658 bot.havocbot_role_timeout = 0;
1659 bot.havocbot_cantfindflag = time + 10;
1660 bot.bot_strategytime = 0;
1662 case HAVOCBOT_CTF_ROLE_DEFENSE:
1664 bot.havocbot_role = havocbot_role_ctf_defense;
1665 bot.havocbot_role_timeout = 0;
1667 case HAVOCBOT_CTF_ROLE_MIDDLE:
1669 bot.havocbot_role = havocbot_role_ctf_middle;
1670 bot.havocbot_role_timeout = 0;
1672 case HAVOCBOT_CTF_ROLE_OFFENSE:
1674 bot.havocbot_role = havocbot_role_ctf_offense;
1675 bot.havocbot_role_timeout = 0;
1677 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1678 dprint("retriever");
1679 bot.havocbot_previous_role = bot.havocbot_role;
1680 bot.havocbot_role = havocbot_role_ctf_retriever;
1681 bot.havocbot_role_timeout = time + 10;
1682 bot.bot_strategytime = 0;
1684 case HAVOCBOT_CTF_ROLE_ESCORT:
1686 bot.havocbot_previous_role = bot.havocbot_role;
1687 bot.havocbot_role = havocbot_role_ctf_escort;
1688 bot.havocbot_role_timeout = time + 30;
1689 bot.bot_strategytime = 0;
1700 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1704 // initially clear items so they can be set as necessary later.
1705 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1706 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1708 // scan through all the flags and notify the client about them
1709 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1711 switch(flag.ctf_status)
1716 if((flag.owner == self) || (flag.pass_sender == self))
1717 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1719 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1724 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1730 // item for stopping players from capturing the flag too often
1731 if(self.ctf_captureshielded)
1732 self.items |= IT_CTF_SHIELDED;
1734 // update the health of the flag carrier waypointsprite
1735 if(self.wps_flagcarrier)
1736 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1741 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1743 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1745 if(frag_target == frag_attacker) // damage done to yourself
1747 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1748 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1750 else // damage done to everyone else
1752 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1753 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1756 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1758 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)))
1759 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1764 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1766 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1768 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1769 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1772 if(frag_target.flagcarried)
1773 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1778 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1781 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1784 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1786 entity flag; // temporary entity for the search method
1788 if(self.flagcarried)
1789 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1791 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1793 if(flag.pass_sender == self) { flag.pass_sender = world; }
1794 if(flag.pass_target == self) { flag.pass_target = world; }
1795 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1801 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1803 if(self.flagcarried)
1804 if(!autocvar_g_ctf_portalteleport)
1805 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1810 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1812 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1814 entity player = self;
1816 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1818 // pass the flag to a team mate
1819 if(autocvar_g_ctf_pass)
1821 entity head, closest_target;
1822 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1824 while(head) // find the closest acceptable target to pass to
1826 if(head.classname == "player" && head.deadflag == DEAD_NO)
1827 if(head != player && !IsDifferentTeam(head, player))
1828 if(!head.speedrunning && !head.vehicle)
1830 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1831 vector head_center = WarpZone_UnTransformOrigin(head, PLAYER_CENTER(head));
1832 vector passer_center = PLAYER_CENTER(player);
1834 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1836 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1838 if(clienttype(head) == CLIENTTYPE_BOT)
1840 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1841 ctf_Handle_Throw(head, player, DROP_PASS);
1845 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname));
1846 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1848 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1851 else if(player.flagcarried)
1855 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, PLAYER_CENTER(closest_target));
1856 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1857 { closest_target = head; }
1859 else { closest_target = head; }
1866 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1869 // throw the flag in front of you
1870 if(autocvar_g_ctf_throw && player.flagcarried)
1871 { ctf_Handle_Throw(player, world, DROP_THROW); return TRUE; }
1877 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1879 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1881 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1883 else // create a normal help me waypointsprite
1885 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');
1886 WaypointSprite_Ping(self.wps_helpme);
1892 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1894 if(vh_player.flagcarried)
1896 if(!autocvar_g_ctf_allow_vehicle_carry)
1898 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1902 setattachment(vh_player.flagcarried, vh_vehicle, "");
1903 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1904 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1905 //vh_player.flagcarried.angles = '0 0 0';
1913 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1915 if(vh_player.flagcarried)
1917 setattachment(vh_player.flagcarried, vh_player, "");
1918 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1919 vh_player.flagcarried.scale = FLAG_SCALE;
1920 vh_player.flagcarried.angles = '0 0 0';
1927 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1929 if(self.flagcarried)
1931 bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
1932 ctf_RespawnFlag(self);
1939 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1941 entity flag; // temporary entity for the search method
1943 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1945 switch(flag.ctf_status)
1950 // lock the flag, game is over
1951 flag.movetype = MOVETYPE_NONE;
1952 flag.takedamage = DAMAGE_NO;
1953 flag.solid = SOLID_NOT;
1954 flag.nextthink = FALSE; // stop thinking
1956 print("stopping the ", flag.netname, " from moving.\n");
1964 // do nothing for these flags
1973 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
1975 havocbot_ctf_reset_role(self);
1984 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1985 CTF Starting point for a player in team one (Red).
1986 Keys: "angle" viewing angle when spawning. */
1987 void spawnfunc_info_player_team1()
1989 if(g_assault) { remove(self); return; }
1991 self.team = COLOR_TEAM1; // red
1992 spawnfunc_info_player_deathmatch();
1996 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
1997 CTF Starting point for a player in team two (Blue).
1998 Keys: "angle" viewing angle when spawning. */
1999 void spawnfunc_info_player_team2()
2001 if(g_assault) { remove(self); return; }
2003 self.team = COLOR_TEAM2; // blue
2004 spawnfunc_info_player_deathmatch();
2007 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2008 CTF Starting point for a player in team three (Yellow).
2009 Keys: "angle" viewing angle when spawning. */
2010 void spawnfunc_info_player_team3()
2012 if(g_assault) { remove(self); return; }
2014 self.team = COLOR_TEAM3; // yellow
2015 spawnfunc_info_player_deathmatch();
2019 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2020 CTF Starting point for a player in team four (Purple).
2021 Keys: "angle" viewing angle when spawning. */
2022 void spawnfunc_info_player_team4()
2024 if(g_assault) { remove(self); return; }
2026 self.team = COLOR_TEAM4; // purple
2027 spawnfunc_info_player_deathmatch();
2030 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2031 CTF flag for team one (Red).
2033 "angle" Angle the flag will point (minus 90 degrees)...
2034 "model" model to use, note this needs red and blue as skins 0 and 1...
2035 "noise" sound played when flag is picked up...
2036 "noise1" sound played when flag is returned by a teammate...
2037 "noise2" sound played when flag is captured...
2038 "noise3" sound played when flag is lost in the field and respawns itself...
2039 "noise4" sound played when flag is dropped by a player...
2040 "noise5" sound played when flag touches the ground... */
2041 void spawnfunc_item_flag_team1()
2043 if(!g_ctf) { remove(self); return; }
2045 ctf_FlagSetup(1, self); // 1 = red
2048 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2049 CTF flag for team two (Blue).
2051 "angle" Angle the flag will point (minus 90 degrees)...
2052 "model" model to use, note this needs red and blue as skins 0 and 1...
2053 "noise" sound played when flag is picked up...
2054 "noise1" sound played when flag is returned by a teammate...
2055 "noise2" sound played when flag is captured...
2056 "noise3" sound played when flag is lost in the field and respawns itself...
2057 "noise4" sound played when flag is dropped by a player...
2058 "noise5" sound played when flag touches the ground... */
2059 void spawnfunc_item_flag_team2()
2061 if(!g_ctf) { remove(self); return; }
2063 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2066 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2067 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2068 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.
2070 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2071 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2072 void spawnfunc_ctf_team()
2074 if(!g_ctf) { remove(self); return; }
2076 self.classname = "ctf_team";
2077 self.team = self.cnt + 1;
2080 // compatibility for quake maps
2081 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2082 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2083 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2084 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2085 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2086 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2094 void ctf_ScoreRules()
2096 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2097 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2098 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2099 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2100 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2101 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2102 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2103 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2104 ScoreRules_basics_end();
2107 // code from here on is just to support maps that don't have flag and team entities
2108 void ctf_SpawnTeam (string teamname, float teamcolor)
2113 self.classname = "ctf_team";
2114 self.netname = teamname;
2115 self.cnt = teamcolor;
2117 spawnfunc_ctf_team();
2122 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2124 // if no teams are found, spawn defaults
2125 if(find(world, classname, "ctf_team") == world)
2127 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2128 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
2129 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
2135 void ctf_Initialize()
2137 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2139 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2140 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2141 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2143 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2147 MUTATOR_DEFINITION(gamemode_ctf)
2149 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2150 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2151 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2152 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2153 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2154 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2155 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2156 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2157 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2158 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2159 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2160 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2161 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2162 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2166 if(time > 1) // game loads at time 1
2167 error("This is a game type and it cannot be added at runtime.");
2175 error("This is a game type and it cannot be removed at runtime.");