1 #include "gamemode_ctf.qh"
6 #include "../vehicles/vehicles_def.qh"
8 #include "../../warpzonelib/common.qh"
9 #include "../../warpzonelib/mathlib.qh"
11 // ================================================================
12 // Official capture the flag game mode coding, reworked by Samual
13 // Last updated: September, 2012
14 // ================================================================
16 void ctf_FakeTimeLimit(entity e, float t)
19 WriteByte(MSG_ONE, 3); // svc_updatestat
20 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
22 WriteCoord(MSG_ONE, autocvar_timelimit);
24 WriteCoord(MSG_ONE, (t + 1) / 60);
27 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
29 if(autocvar_sv_eventlog)
30 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
33 void ctf_CaptureRecord(entity flag, entity player)
35 float cap_record = ctf_captimerecord;
36 float cap_time = (time - flag.ctf_pickuptime);
37 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
40 if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
41 else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
42 else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
44 // write that shit in the database
45 if((!ctf_captimerecord) || (cap_time < cap_record))
47 ctf_captimerecord = cap_time;
48 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
49 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
50 write_recordmarker(player, (time - cap_time), cap_time);
54 void ctf_FlagcarrierWaypoints(entity player)
56 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
57 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
58 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
59 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
62 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
64 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
65 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
66 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
67 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
70 if(current_height) // make sure we can actually do this arcing path
72 targpos = (to + ('0 0 1' * current_height));
73 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
74 if(trace_fraction < 1)
76 //print("normal arc line failed, trying to find new pos...");
77 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
78 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
79 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
80 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
81 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
84 else { targpos = to; }
86 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
88 vector desired_direction = normalize(targpos - from);
89 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
90 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
93 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
95 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
97 // directional tracing only
99 makevectors(passer_angle);
101 // find the closest point on the enemy to the center of the attack
102 float h; // hypotenuse, which is the distance between attacker to head
103 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
105 h = vlen(head_center - passer_center);
106 a = h * (normalize(head_center - passer_center) * v_forward);
108 vector nearest_on_line = (passer_center + a * v_forward);
109 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
111 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
112 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
114 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
119 else { return true; }
123 // =======================
124 // CaptureShield Functions
125 // =======================
127 float ctf_CaptureShield_CheckStatus(entity p)
131 float players_worseeq, players_total;
133 if(ctf_captureshield_max_ratio <= 0)
136 s = PlayerScore_Add(p, SP_SCORE, 0);
137 if(s >= -ctf_captureshield_min_negscore)
140 players_total = players_worseeq = 0;
145 se = PlayerScore_Add(e, SP_SCORE, 0);
151 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
152 // use this rule here
154 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
160 void ctf_CaptureShield_Update(entity player, float wanted_status)
162 float updated_status = ctf_CaptureShield_CheckStatus(player);
163 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
165 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
166 player.ctf_captureshielded = updated_status;
170 float ctf_CaptureShield_Customize()
172 if(!other.ctf_captureshielded) { return false; }
173 if(SAME_TEAM(self, other)) { return false; }
178 void ctf_CaptureShield_Touch()
180 if(!other.ctf_captureshielded) { return; }
181 if(SAME_TEAM(self, other)) { return; }
183 vector mymid = (self.absmin + self.absmax) * 0.5;
184 vector othermid = (other.absmin + other.absmax) * 0.5;
186 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
187 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
190 void ctf_CaptureShield_Spawn(entity flag)
192 entity shield = spawn();
195 shield.team = self.team;
196 shield.touch = ctf_CaptureShield_Touch;
197 shield.customizeentityforclient = ctf_CaptureShield_Customize;
198 shield.classname = "ctf_captureshield";
199 shield.effects = EF_ADDITIVE;
200 shield.movetype = MOVETYPE_NOCLIP;
201 shield.solid = SOLID_TRIGGER;
202 shield.avelocity = '7 0 11';
205 setorigin(shield, self.origin);
206 setmodel(shield, "models/ctf/shield.md3");
207 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
211 // ====================
212 // Drop/Pass/Throw Code
213 // ====================
215 void ctf_Handle_Drop(entity flag, entity player, float droptype)
218 player = (player ? player : flag.pass_sender);
221 flag.movetype = MOVETYPE_TOSS;
222 flag.takedamage = DAMAGE_YES;
223 flag.angles = '0 0 0';
224 flag.health = flag.max_flag_health;
225 flag.ctf_droptime = time;
226 flag.ctf_dropper = player;
227 flag.ctf_status = FLAG_DROPPED;
229 // messages and sounds
230 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
231 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
232 ctf_EventLog("dropped", player.team, player);
235 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
236 PlayerScore_Add(player, SP_CTF_DROPS, 1);
239 if(autocvar_g_ctf_flag_dropped_waypoint)
240 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));
242 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
244 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
245 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
248 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
250 if(droptype == DROP_PASS)
252 flag.pass_distance = 0;
253 flag.pass_sender = world;
254 flag.pass_target = world;
258 void ctf_Handle_Retrieve(entity flag, entity player)
260 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
261 entity sender = flag.pass_sender;
263 // transfer flag to player
265 flag.owner.flagcarried = flag;
270 setattachment(flag, player.vehicle, "");
271 setorigin(flag, VEHICLE_FLAG_OFFSET);
272 flag.scale = VEHICLE_FLAG_SCALE;
276 setattachment(flag, player, "");
277 setorigin(flag, FLAG_CARRY_OFFSET);
279 flag.movetype = MOVETYPE_NONE;
280 flag.takedamage = DAMAGE_NO;
281 flag.solid = SOLID_NOT;
282 flag.angles = '0 0 0';
283 flag.ctf_status = FLAG_CARRY;
285 // messages and sounds
286 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
287 ctf_EventLog("receive", flag.team, player);
289 FOR_EACH_REALPLAYER(tmp_player)
291 if(tmp_player == sender)
292 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
293 else if(tmp_player == player)
294 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
295 else if(SAME_TEAM(tmp_player, sender))
296 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
299 // create new waypoint
300 ctf_FlagcarrierWaypoints(player);
302 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
303 player.throw_antispam = sender.throw_antispam;
305 flag.pass_distance = 0;
306 flag.pass_sender = world;
307 flag.pass_target = world;
310 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
312 entity flag = player.flagcarried;
313 vector targ_origin, flag_velocity;
315 if(!flag) { return; }
316 if((droptype == DROP_PASS) && !receiver) { return; }
318 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
321 setattachment(flag, world, "");
322 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
323 flag.owner.flagcarried = world;
325 flag.solid = SOLID_TRIGGER;
326 flag.ctf_dropper = player;
327 flag.ctf_droptime = time;
329 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
336 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
337 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
338 WarpZone_RefSys_Copy(flag, receiver);
339 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
340 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
342 flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' * player.origin.x) + ('0 1 0' * player.origin.y))); // for the sake of this check, exclude Z axis
343 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
346 flag.movetype = MOVETYPE_FLY;
347 flag.takedamage = DAMAGE_NO;
348 flag.pass_sender = player;
349 flag.pass_target = receiver;
350 flag.ctf_status = FLAG_PASSING;
353 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
354 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
355 ctf_EventLog("pass", flag.team, player);
361 makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
363 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
364 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
365 ctf_Handle_Drop(flag, player, droptype);
371 flag.velocity = '0 0 0'; // do nothing
378 flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
379 ctf_Handle_Drop(flag, player, droptype);
384 // kill old waypointsprite
385 WaypointSprite_Ping(player.wps_flagcarrier);
386 WaypointSprite_Kill(player.wps_flagcarrier);
388 if(player.wps_enemyflagcarrier)
389 WaypointSprite_Kill(player.wps_enemyflagcarrier);
392 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
400 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
402 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
403 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
404 float old_time, new_time;
406 if (!player) { return; } // without someone to give the reward to, we can't possibly cap
408 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
410 // messages and sounds
411 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
412 ctf_CaptureRecord(enemy_flag, player);
413 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
417 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
418 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
423 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
424 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
426 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
427 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
428 if(!old_time || new_time < old_time)
429 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
432 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
433 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
436 if(capturetype == CAPTURE_NORMAL)
438 WaypointSprite_Kill(player.wps_flagcarrier);
439 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
441 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
442 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
446 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
447 ctf_RespawnFlag(enemy_flag);
450 void ctf_Handle_Return(entity flag, entity player)
452 // messages and sounds
453 if(player.flags & FL_MONSTER)
455 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
459 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
460 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
462 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
463 ctf_EventLog("return", flag.team, player);
466 if(IS_PLAYER(player))
468 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
469 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
471 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
474 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
478 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
479 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
480 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
484 ctf_RespawnFlag(flag);
487 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
490 float pickup_dropped_score; // used to calculate dropped pickup score
492 // attach the flag to the player
494 player.flagcarried = flag;
497 setattachment(flag, player.vehicle, "");
498 setorigin(flag, VEHICLE_FLAG_OFFSET);
499 flag.scale = VEHICLE_FLAG_SCALE;
503 setattachment(flag, player, "");
504 setorigin(flag, FLAG_CARRY_OFFSET);
508 flag.movetype = MOVETYPE_NONE;
509 flag.takedamage = DAMAGE_NO;
510 flag.solid = SOLID_NOT;
511 flag.angles = '0 0 0';
512 flag.ctf_status = FLAG_CARRY;
516 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
517 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
521 // messages and sounds
522 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
523 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
524 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
526 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
527 Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
529 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
532 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
533 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
538 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
539 ctf_EventLog("steal", flag.team, player);
545 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);
546 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);
547 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
548 PlayerTeamScore_AddScore(player, pickup_dropped_score);
549 ctf_EventLog("pickup", flag.team, player);
557 if(pickuptype == PICKUP_BASE)
559 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
560 if((player.speedrunning) && (ctf_captimerecord))
561 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
565 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
568 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
569 ctf_FlagcarrierWaypoints(player);
570 WaypointSprite_Ping(player.wps_flagcarrier);
574 // ===================
575 // Main Flag Functions
576 // ===================
578 void ctf_CheckFlagReturn(entity flag, float returntype)
580 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
582 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
584 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
588 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
589 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
590 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
591 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
595 { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
597 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
598 ctf_EventLog("returned", flag.team, world);
599 ctf_RespawnFlag(flag);
604 void ctf_CheckStalemate(void)
607 float stale_red_flags = 0, stale_blue_flags = 0;
610 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
612 // build list of stale flags
613 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
615 if(autocvar_g_ctf_stalemate)
616 if(tmp_entity.ctf_status != FLAG_BASE)
617 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
619 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
620 ctf_staleflaglist = tmp_entity;
622 switch(tmp_entity.team)
624 case NUM_TEAM_1: ++stale_red_flags; break;
625 case NUM_TEAM_2: ++stale_blue_flags; break;
630 if(stale_red_flags && stale_blue_flags)
631 ctf_stalemate = true;
632 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
633 { ctf_stalemate = false; wpforenemy_announced = false; }
634 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
635 { ctf_stalemate = false; wpforenemy_announced = false; }
637 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
640 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
642 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
643 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));
646 if (!wpforenemy_announced)
648 FOR_EACH_REALPLAYER(tmp_entity)
649 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
651 wpforenemy_announced = true;
656 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
658 if(ITEM_DAMAGE_NEEDKILL(deathtype))
660 // automatically kill the flag and return it
662 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
665 if(autocvar_g_ctf_flag_return_damage)
667 // reduce health and check if it should be returned
668 self.health = self.health - damage;
669 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
679 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
682 if(self == ctf_worldflaglist) // only for the first flag
683 FOR_EACH_CLIENT(tmp_entity)
684 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
687 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
688 dprint("wtf the flag got squashed?\n");
689 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
690 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
691 setsize(self, FLAG_MIN, FLAG_MAX); }
693 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
697 self.angles = '0 0 0';
705 switch(self.ctf_status)
709 if(autocvar_g_ctf_dropped_capture_radius)
711 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
712 if(tmp_entity.ctf_status == FLAG_DROPPED)
713 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
714 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
715 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
722 if(autocvar_g_ctf_flag_dropped_floatinwater)
724 vector midpoint = ((self.absmin + self.absmax) * 0.5);
725 if(pointcontents(midpoint) == CONTENT_WATER)
727 self.velocity = self.velocity * 0.5;
729 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
730 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
732 { self.movetype = MOVETYPE_FLY; }
734 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
736 if(autocvar_g_ctf_flag_return_dropped)
738 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
741 ctf_CheckFlagReturn(self, RETURN_DROPPED);
745 if(autocvar_g_ctf_flag_return_time)
747 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
748 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
756 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
759 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
763 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
767 if(autocvar_g_ctf_stalemate)
769 if(time >= wpforenemy_nextthink)
771 ctf_CheckStalemate();
772 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
780 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
781 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
782 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
784 if((self.pass_target == world)
785 || (self.pass_target.deadflag != DEAD_NO)
786 || (self.pass_target.flagcarried)
787 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
788 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
789 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
791 // give up, pass failed
792 ctf_Handle_Drop(self, world, DROP_PASS);
796 // still a viable target, go for it
797 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
802 default: // this should never happen
804 dprint("ctf_FlagThink(): Flag exists with no status?\n");
812 if(gameover) { return; }
813 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
815 entity toucher = other;
816 float is_not_monster = (!(toucher.flags & FL_MONSTER));
818 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
819 if(ITEM_TOUCH_NEEDKILL())
822 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
826 // special touch behaviors
827 if(toucher.frozen) { return; }
828 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
830 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
831 toucher = toucher.owner; // the player is actually the vehicle owner, not other
833 return; // do nothing
835 else if(toucher.flags & FL_MONSTER)
837 if(!autocvar_g_ctf_allow_monster_touch)
838 return; // do nothing
840 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
842 if(time > self.wait) // if we haven't in a while, play a sound/effect
844 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
845 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
846 self.wait = time + FLAG_TOUCHRATE;
850 else if(toucher.deadflag != DEAD_NO) { return; }
852 switch(self.ctf_status)
856 if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
857 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
858 else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
859 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
865 if(SAME_TEAM(toucher, self))
866 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
867 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
868 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
874 dprint("Someone touched a flag even though it was being carried?\n");
880 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
882 if(DIFF_TEAM(toucher, self.pass_sender))
883 ctf_Handle_Return(self, toucher);
885 ctf_Handle_Retrieve(self, toucher);
893 void ctf_RespawnFlag(entity flag)
895 // check for flag respawn being called twice in a row
896 if(flag.last_respawn > time - 0.5)
897 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
899 flag.last_respawn = time;
901 // reset the player (if there is one)
902 if((flag.owner) && (flag.owner.flagcarried == flag))
904 if(flag.owner.wps_enemyflagcarrier)
905 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
907 WaypointSprite_Kill(flag.wps_flagcarrier);
909 flag.owner.flagcarried = world;
911 if(flag.speedrunning)
912 ctf_FakeTimeLimit(flag.owner, -1);
915 if((flag.owner) && (flag.owner.vehicle))
916 flag.scale = FLAG_SCALE;
918 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
919 { WaypointSprite_Kill(flag.wps_flagdropped); }
922 setattachment(flag, world, "");
923 setorigin(flag, flag.ctf_spawnorigin);
925 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
926 flag.takedamage = DAMAGE_NO;
927 flag.health = flag.max_flag_health;
928 flag.solid = SOLID_TRIGGER;
929 flag.velocity = '0 0 0';
930 flag.angles = flag.mangle;
931 flag.flags = FL_ITEM | FL_NOTARGET;
933 flag.ctf_status = FLAG_BASE;
935 flag.pass_distance = 0;
936 flag.pass_sender = world;
937 flag.pass_target = world;
938 flag.ctf_dropper = world;
939 flag.ctf_pickuptime = 0;
940 flag.ctf_droptime = 0;
942 ctf_CheckStalemate();
948 if(IS_PLAYER(self.owner))
949 ctf_Handle_Throw(self.owner, world, DROP_RESET);
951 ctf_RespawnFlag(self);
954 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
957 waypoint_spawnforitem_force(self, self.origin);
958 self.nearestwaypointtimeout = 0; // activate waypointing again
959 self.bot_basewaypoint = self.nearestwaypoint;
962 WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
963 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
965 // captureshield setup
966 ctf_CaptureShield_Spawn(self);
969 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
972 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.
973 self = flag; // for later usage with droptofloor()
976 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
977 ctf_worldflaglist = flag;
979 setattachment(flag, world, "");
981 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
982 flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
983 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
984 flag.classname = "item_flag_team";
985 flag.target = "###item###"; // wut?
986 flag.flags = FL_ITEM | FL_NOTARGET;
987 flag.solid = SOLID_TRIGGER;
988 flag.takedamage = DAMAGE_NO;
989 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
990 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
991 flag.health = flag.max_flag_health;
992 flag.event_damage = ctf_FlagDamage;
993 flag.pushable = true;
994 flag.teleportable = TELEPORT_NORMAL;
995 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
996 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
997 flag.velocity = '0 0 0';
998 flag.mangle = flag.angles;
999 flag.reset = ctf_Reset;
1000 flag.touch = ctf_FlagTouch;
1001 flag.think = ctf_FlagThink;
1002 flag.nextthink = time + FLAG_THINKRATE;
1003 flag.ctf_status = FLAG_BASE;
1006 if(flag.model == "") { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
1007 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1008 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
1009 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
1010 if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
1011 if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
1014 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
1015 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
1016 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
1017 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.
1018 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
1019 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1020 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1023 precache_sound(flag.snd_flag_taken);
1024 precache_sound(flag.snd_flag_returned);
1025 precache_sound(flag.snd_flag_capture);
1026 precache_sound(flag.snd_flag_respawn);
1027 precache_sound(flag.snd_flag_dropped);
1028 precache_sound(flag.snd_flag_touch);
1029 precache_sound(flag.snd_flag_pass);
1030 precache_model(flag.model);
1031 precache_model("models/ctf/shield.md3");
1032 precache_model("models/ctf/shockwavetransring.md3");
1035 setmodel(flag, flag.model); // precision set below
1036 setsize(flag, FLAG_MIN, FLAG_MAX);
1037 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1039 if(autocvar_g_ctf_flag_glowtrails)
1041 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1042 flag.glow_size = 25;
1043 flag.glow_trail = 1;
1046 flag.effects |= EF_LOWPRECISION;
1047 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1048 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1051 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1053 flag.dropped_origin = flag.origin;
1054 flag.noalign = true;
1055 flag.movetype = MOVETYPE_NONE;
1057 else // drop to floor, automatically find a platform and set that as spawn origin
1059 flag.noalign = false;
1062 flag.movetype = MOVETYPE_TOSS;
1065 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1073 // NOTE: LEGACY CODE, needs to be re-written!
1075 void havocbot_calculate_middlepoint()
1079 vector fo = '0 0 0';
1082 f = ctf_worldflaglist;
1087 f = f.ctf_worldflagnext;
1091 havocbot_ctf_middlepoint = s * (1.0 / n);
1092 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1096 entity havocbot_ctf_find_flag(entity bot)
1099 f = ctf_worldflaglist;
1102 if (bot.team == f.team)
1104 f = f.ctf_worldflagnext;
1109 entity havocbot_ctf_find_enemy_flag(entity bot)
1112 f = ctf_worldflaglist;
1115 if (bot.team != f.team)
1117 f = f.ctf_worldflagnext;
1122 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1130 FOR_EACH_PLAYER(head)
1132 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1135 if(vlen(head.origin - org) < tc_radius)
1142 void havocbot_goalrating_ctf_ourflag(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_ourbase(float ratingscale)
1159 head = ctf_worldflaglist;
1162 if (self.team == head.team)
1164 head = head.ctf_worldflagnext;
1169 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1172 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1175 head = ctf_worldflaglist;
1178 if (self.team != head.team)
1180 head = head.ctf_worldflagnext;
1183 navigation_routerating(head, ratingscale, 10000);
1186 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1188 if (!bot_waypoints_for_items)
1190 havocbot_goalrating_ctf_enemyflag(ratingscale);
1196 head = havocbot_ctf_find_enemy_flag(self);
1201 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1204 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1208 mf = havocbot_ctf_find_flag(self);
1210 if(mf.ctf_status == FLAG_BASE)
1214 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1217 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1220 head = ctf_worldflaglist;
1223 // flag is out in the field
1224 if(head.ctf_status != FLAG_BASE)
1225 if(head.tag_entity==world) // dropped
1229 if(vlen(org-head.origin)<df_radius)
1230 navigation_routerating(head, ratingscale, 10000);
1233 navigation_routerating(head, ratingscale, 10000);
1236 head = head.ctf_worldflagnext;
1240 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1244 head = findchainfloat(bot_pickup, true);
1247 // gather health and armor only
1249 if (head.health || head.armorvalue)
1250 if (vlen(head.origin - org) < sradius)
1252 // get the value of the item
1253 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1255 navigation_routerating(head, t * ratingscale, 500);
1261 void havocbot_ctf_reset_role(entity bot)
1263 float cdefense, cmiddle, coffense;
1264 entity mf, ef, head;
1267 if(bot.deadflag != DEAD_NO)
1270 if(vlen(havocbot_ctf_middlepoint)==0)
1271 havocbot_calculate_middlepoint();
1274 if (bot.flagcarried)
1276 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1280 mf = havocbot_ctf_find_flag(bot);
1281 ef = havocbot_ctf_find_enemy_flag(bot);
1283 // Retrieve stolen flag
1284 if(mf.ctf_status!=FLAG_BASE)
1286 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1290 // If enemy flag is taken go to the middle to intercept pursuers
1291 if(ef.ctf_status!=FLAG_BASE)
1293 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1297 // if there is only me on the team switch to offense
1299 FOR_EACH_PLAYER(head)
1300 if(SAME_TEAM(head, bot))
1305 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1309 // Evaluate best position to take
1310 // Count mates on middle position
1311 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1313 // Count mates on defense position
1314 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1316 // Count mates on offense position
1317 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1319 if(cdefense<=coffense)
1320 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1321 else if(coffense<=cmiddle)
1322 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1324 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1327 void havocbot_role_ctf_carrier()
1329 if(self.deadflag != DEAD_NO)
1331 havocbot_ctf_reset_role(self);
1335 if (self.flagcarried == world)
1337 havocbot_ctf_reset_role(self);
1341 if (self.bot_strategytime < time)
1343 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1345 navigation_goalrating_start();
1346 havocbot_goalrating_ctf_ourbase(50000);
1349 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1351 navigation_goalrating_end();
1353 if (self.navigation_hasgoals)
1354 self.havocbot_cantfindflag = time + 10;
1355 else if (time > self.havocbot_cantfindflag)
1357 // Can't navigate to my own base, suicide!
1358 // TODO: drop it and wander around
1359 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1365 void havocbot_role_ctf_escort()
1369 if(self.deadflag != DEAD_NO)
1371 havocbot_ctf_reset_role(self);
1375 if (self.flagcarried)
1377 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1381 // If enemy flag is back on the base switch to previous role
1382 ef = havocbot_ctf_find_enemy_flag(self);
1383 if(ef.ctf_status==FLAG_BASE)
1385 self.havocbot_role = self.havocbot_previous_role;
1386 self.havocbot_role_timeout = 0;
1390 // If the flag carrier reached the base switch to defense
1391 mf = havocbot_ctf_find_flag(self);
1392 if(mf.ctf_status!=FLAG_BASE)
1393 if(vlen(ef.origin - mf.dropped_origin) < 300)
1395 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1399 // Set the role timeout if necessary
1400 if (!self.havocbot_role_timeout)
1402 self.havocbot_role_timeout = time + random() * 30 + 60;
1405 // If nothing happened just switch to previous role
1406 if (time > self.havocbot_role_timeout)
1408 self.havocbot_role = self.havocbot_previous_role;
1409 self.havocbot_role_timeout = 0;
1413 // Chase the flag carrier
1414 if (self.bot_strategytime < time)
1416 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1417 navigation_goalrating_start();
1418 havocbot_goalrating_ctf_enemyflag(30000);
1419 havocbot_goalrating_ctf_ourstolenflag(40000);
1420 havocbot_goalrating_items(10000, self.origin, 10000);
1421 navigation_goalrating_end();
1425 void havocbot_role_ctf_offense()
1430 if(self.deadflag != DEAD_NO)
1432 havocbot_ctf_reset_role(self);
1436 if (self.flagcarried)
1438 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1443 mf = havocbot_ctf_find_flag(self);
1444 ef = havocbot_ctf_find_enemy_flag(self);
1447 if(mf.ctf_status!=FLAG_BASE)
1450 pos = mf.tag_entity.origin;
1454 // Try to get it if closer than the enemy base
1455 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1457 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1462 // Escort flag carrier
1463 if(ef.ctf_status!=FLAG_BASE)
1466 pos = ef.tag_entity.origin;
1470 if(vlen(pos-mf.dropped_origin)>700)
1472 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1477 // About to fail, switch to middlefield
1480 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1484 // Set the role timeout if necessary
1485 if (!self.havocbot_role_timeout)
1486 self.havocbot_role_timeout = time + 120;
1488 if (time > self.havocbot_role_timeout)
1490 havocbot_ctf_reset_role(self);
1494 if (self.bot_strategytime < time)
1496 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1497 navigation_goalrating_start();
1498 havocbot_goalrating_ctf_ourstolenflag(50000);
1499 havocbot_goalrating_ctf_enemybase(20000);
1500 havocbot_goalrating_items(5000, self.origin, 1000);
1501 havocbot_goalrating_items(1000, self.origin, 10000);
1502 navigation_goalrating_end();
1506 // Retriever (temporary role):
1507 void havocbot_role_ctf_retriever()
1511 if(self.deadflag != DEAD_NO)
1513 havocbot_ctf_reset_role(self);
1517 if (self.flagcarried)
1519 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1523 // If flag is back on the base switch to previous role
1524 mf = havocbot_ctf_find_flag(self);
1525 if(mf.ctf_status==FLAG_BASE)
1527 havocbot_ctf_reset_role(self);
1531 if (!self.havocbot_role_timeout)
1532 self.havocbot_role_timeout = time + 20;
1534 if (time > self.havocbot_role_timeout)
1536 havocbot_ctf_reset_role(self);
1540 if (self.bot_strategytime < time)
1545 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1546 navigation_goalrating_start();
1547 havocbot_goalrating_ctf_ourstolenflag(50000);
1548 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1549 havocbot_goalrating_ctf_enemybase(30000);
1550 havocbot_goalrating_items(500, self.origin, rt_radius);
1551 navigation_goalrating_end();
1555 void havocbot_role_ctf_middle()
1559 if(self.deadflag != DEAD_NO)
1561 havocbot_ctf_reset_role(self);
1565 if (self.flagcarried)
1567 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1571 mf = havocbot_ctf_find_flag(self);
1572 if(mf.ctf_status!=FLAG_BASE)
1574 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1578 if (!self.havocbot_role_timeout)
1579 self.havocbot_role_timeout = time + 10;
1581 if (time > self.havocbot_role_timeout)
1583 havocbot_ctf_reset_role(self);
1587 if (self.bot_strategytime < time)
1591 org = havocbot_ctf_middlepoint;
1592 org.z = self.origin.z;
1594 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1595 navigation_goalrating_start();
1596 havocbot_goalrating_ctf_ourstolenflag(50000);
1597 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1598 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1599 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1600 havocbot_goalrating_items(2500, self.origin, 10000);
1601 havocbot_goalrating_ctf_enemybase(2500);
1602 navigation_goalrating_end();
1606 void havocbot_role_ctf_defense()
1610 if(self.deadflag != DEAD_NO)
1612 havocbot_ctf_reset_role(self);
1616 if (self.flagcarried)
1618 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1622 // If own flag was captured
1623 mf = havocbot_ctf_find_flag(self);
1624 if(mf.ctf_status!=FLAG_BASE)
1626 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1630 if (!self.havocbot_role_timeout)
1631 self.havocbot_role_timeout = time + 30;
1633 if (time > self.havocbot_role_timeout)
1635 havocbot_ctf_reset_role(self);
1638 if (self.bot_strategytime < time)
1643 org = mf.dropped_origin;
1644 mp_radius = havocbot_ctf_middlepoint_radius;
1646 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1647 navigation_goalrating_start();
1649 // if enemies are closer to our base, go there
1650 entity head, closestplayer = world;
1651 float distance, bestdistance = 10000;
1652 FOR_EACH_PLAYER(head)
1654 if(head.deadflag!=DEAD_NO)
1657 distance = vlen(org - head.origin);
1658 if(distance<bestdistance)
1660 closestplayer = head;
1661 bestdistance = distance;
1666 if(DIFF_TEAM(closestplayer, self))
1667 if(vlen(org - self.origin)>1000)
1668 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1669 havocbot_goalrating_ctf_ourbase(30000);
1671 havocbot_goalrating_ctf_ourstolenflag(20000);
1672 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1673 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1674 havocbot_goalrating_items(10000, org, mp_radius);
1675 havocbot_goalrating_items(5000, self.origin, 10000);
1676 navigation_goalrating_end();
1680 void havocbot_role_ctf_setrole(entity bot, float role)
1682 dprint(strcat(bot.netname," switched to "));
1685 case HAVOCBOT_CTF_ROLE_CARRIER:
1687 bot.havocbot_role = havocbot_role_ctf_carrier;
1688 bot.havocbot_role_timeout = 0;
1689 bot.havocbot_cantfindflag = time + 10;
1690 bot.bot_strategytime = 0;
1692 case HAVOCBOT_CTF_ROLE_DEFENSE:
1694 bot.havocbot_role = havocbot_role_ctf_defense;
1695 bot.havocbot_role_timeout = 0;
1697 case HAVOCBOT_CTF_ROLE_MIDDLE:
1699 bot.havocbot_role = havocbot_role_ctf_middle;
1700 bot.havocbot_role_timeout = 0;
1702 case HAVOCBOT_CTF_ROLE_OFFENSE:
1704 bot.havocbot_role = havocbot_role_ctf_offense;
1705 bot.havocbot_role_timeout = 0;
1707 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1708 dprint("retriever");
1709 bot.havocbot_previous_role = bot.havocbot_role;
1710 bot.havocbot_role = havocbot_role_ctf_retriever;
1711 bot.havocbot_role_timeout = time + 10;
1712 bot.bot_strategytime = 0;
1714 case HAVOCBOT_CTF_ROLE_ESCORT:
1716 bot.havocbot_previous_role = bot.havocbot_role;
1717 bot.havocbot_role = havocbot_role_ctf_escort;
1718 bot.havocbot_role_timeout = time + 30;
1719 bot.bot_strategytime = 0;
1730 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1734 // initially clear items so they can be set as necessary later.
1735 self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1736 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1738 // scan through all the flags and notify the client about them
1739 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1741 switch(flag.ctf_status)
1746 if((flag.owner == self) || (flag.pass_sender == self))
1747 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1749 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1754 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1760 // item for stopping players from capturing the flag too often
1761 if(self.ctf_captureshielded)
1762 self.items |= IT_CTF_SHIELDED;
1764 // update the health of the flag carrier waypointsprite
1765 if(self.wps_flagcarrier)
1766 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1771 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1773 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1775 if(frag_target == frag_attacker) // damage done to yourself
1777 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1778 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1780 else // damage done to everyone else
1782 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1783 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1786 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1788 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)))
1789 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1791 frag_target.wps_helpme_time = time;
1792 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1794 // todo: add notification for when flag carrier needs help?
1799 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1801 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1803 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1804 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1807 if(frag_target.flagcarried)
1808 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1813 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1816 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1819 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1821 entity flag; // temporary entity for the search method
1823 if(self.flagcarried)
1824 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1826 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1828 if(flag.pass_sender == self) { flag.pass_sender = world; }
1829 if(flag.pass_target == self) { flag.pass_target = world; }
1830 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1836 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1838 if(self.flagcarried)
1839 if(!autocvar_g_ctf_portalteleport)
1840 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1845 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1847 if(MUTATOR_RETURNVALUE || gameover) { return false; }
1849 entity player = self;
1851 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1853 // pass the flag to a team mate
1854 if(autocvar_g_ctf_pass)
1856 entity head, closest_target = world;
1857 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
1859 while(head) // find the closest acceptable target to pass to
1861 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1862 if(head != player && SAME_TEAM(head, player))
1863 if(!head.speedrunning && !head.vehicle)
1865 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1866 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1867 vector passer_center = CENTER_OR_VIEWOFS(player);
1869 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1871 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1873 if(IS_BOT_CLIENT(head))
1875 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1876 ctf_Handle_Throw(head, player, DROP_PASS);
1880 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1881 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1883 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1886 else if(player.flagcarried)
1890 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1891 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1892 { closest_target = head; }
1894 else { closest_target = head; }
1901 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
1904 // throw the flag in front of you
1905 if(autocvar_g_ctf_throw && player.flagcarried)
1907 if(player.throw_count == -1)
1909 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1911 player.throw_prevtime = time;
1912 player.throw_count = 1;
1913 ctf_Handle_Throw(player, world, DROP_THROW);
1918 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1924 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1925 else { player.throw_count += 1; }
1926 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1928 player.throw_prevtime = time;
1929 ctf_Handle_Throw(player, world, DROP_THROW);
1938 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1940 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1942 self.wps_helpme_time = time;
1943 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1945 else // create a normal help me waypointsprite
1947 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');
1948 WaypointSprite_Ping(self.wps_helpme);
1954 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1956 if(vh_player.flagcarried)
1958 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1960 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1964 setattachment(vh_player.flagcarried, vh_vehicle, "");
1965 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1966 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1967 //vh_player.flagcarried.angles = '0 0 0';
1975 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1977 if(vh_player.flagcarried)
1979 setattachment(vh_player.flagcarried, vh_player, "");
1980 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1981 vh_player.flagcarried.scale = FLAG_SCALE;
1982 vh_player.flagcarried.angles = '0 0 0';
1989 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1991 if(self.flagcarried)
1993 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1994 ctf_RespawnFlag(self.flagcarried);
2001 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2003 entity flag; // temporary entity for the search method
2005 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2007 switch(flag.ctf_status)
2012 // lock the flag, game is over
2013 flag.movetype = MOVETYPE_NONE;
2014 flag.takedamage = DAMAGE_NO;
2015 flag.solid = SOLID_NOT;
2016 flag.nextthink = false; // stop thinking
2018 //dprint("stopping the ", flag.netname, " from moving.\n");
2026 // do nothing for these flags
2035 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2037 havocbot_ctf_reset_role(self);
2046 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2047 CTF Starting point for a player in team one (Red).
2048 Keys: "angle" viewing angle when spawning. */
2049 void spawnfunc_info_player_team1()
2051 if(g_assault) { remove(self); return; }
2053 self.team = NUM_TEAM_1; // red
2054 spawnfunc_info_player_deathmatch();
2058 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2059 CTF Starting point for a player in team two (Blue).
2060 Keys: "angle" viewing angle when spawning. */
2061 void spawnfunc_info_player_team2()
2063 if(g_assault) { remove(self); return; }
2065 self.team = NUM_TEAM_2; // blue
2066 spawnfunc_info_player_deathmatch();
2069 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2070 CTF Starting point for a player in team three (Yellow).
2071 Keys: "angle" viewing angle when spawning. */
2072 void spawnfunc_info_player_team3()
2074 if(g_assault) { remove(self); return; }
2076 self.team = NUM_TEAM_3; // yellow
2077 spawnfunc_info_player_deathmatch();
2081 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2082 CTF Starting point for a player in team four (Purple).
2083 Keys: "angle" viewing angle when spawning. */
2084 void spawnfunc_info_player_team4()
2086 if(g_assault) { remove(self); return; }
2088 self.team = NUM_TEAM_4; // purple
2089 spawnfunc_info_player_deathmatch();
2092 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2093 CTF flag for team one (Red).
2095 "angle" Angle the flag will point (minus 90 degrees)...
2096 "model" model to use, note this needs red and blue as skins 0 and 1...
2097 "noise" sound played when flag is picked up...
2098 "noise1" sound played when flag is returned by a teammate...
2099 "noise2" sound played when flag is captured...
2100 "noise3" sound played when flag is lost in the field and respawns itself...
2101 "noise4" sound played when flag is dropped by a player...
2102 "noise5" sound played when flag touches the ground... */
2103 void spawnfunc_item_flag_team1()
2105 if(!g_ctf) { remove(self); return; }
2107 ctf_FlagSetup(1, self); // 1 = red
2110 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2111 CTF flag for team two (Blue).
2113 "angle" Angle the flag will point (minus 90 degrees)...
2114 "model" model to use, note this needs red and blue as skins 0 and 1...
2115 "noise" sound played when flag is picked up...
2116 "noise1" sound played when flag is returned by a teammate...
2117 "noise2" sound played when flag is captured...
2118 "noise3" sound played when flag is lost in the field and respawns itself...
2119 "noise4" sound played when flag is dropped by a player...
2120 "noise5" sound played when flag touches the ground... */
2121 void spawnfunc_item_flag_team2()
2123 if(!g_ctf) { remove(self); return; }
2125 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2128 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2129 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2130 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.
2132 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2133 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2134 void spawnfunc_ctf_team()
2136 if(!g_ctf) { remove(self); return; }
2138 self.classname = "ctf_team";
2139 self.team = self.cnt + 1;
2142 // compatibility for quake maps
2143 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2144 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2145 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2146 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2147 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2148 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2156 void ctf_ScoreRules()
2158 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, true);
2159 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2160 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2161 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2162 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2163 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2164 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2165 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2166 ScoreRules_basics_end();
2169 // code from here on is just to support maps that don't have flag and team entities
2170 void ctf_SpawnTeam (string teamname, float teamcolor)
2175 self.classname = "ctf_team";
2176 self.netname = teamname;
2177 self.cnt = teamcolor;
2179 spawnfunc_ctf_team();
2184 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2186 // if no teams are found, spawn defaults
2187 if(find(world, classname, "ctf_team") == world)
2189 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2190 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2191 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2197 void ctf_Initialize()
2199 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2201 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2202 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2203 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2205 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2209 MUTATOR_DEFINITION(gamemode_ctf)
2211 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2212 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2213 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2214 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2215 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2216 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2217 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2218 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2219 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2220 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2221 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2222 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2223 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2224 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2228 if(time > 1) // game loads at time 1
2229 error("This is a game type and it cannot be added at runtime.");
2233 MUTATOR_ONROLLBACK_OR_REMOVE
2235 // we actually cannot roll back ctf_Initialize here
2236 // BUT: we don't need to! If this gets called, adding always
2242 print("This is a game type and it cannot be removed at runtime.");