1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: September, 2012
4 // ================================================================
6 void ctf_FakeTimeLimit(entity e, float t)
9 WriteByte(MSG_ONE, 3); // svc_updatestat
10 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
12 WriteCoord(MSG_ONE, autocvar_timelimit);
14 WriteCoord(MSG_ONE, (t + 1) / 60);
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
23 void ctf_CaptureRecord(entity flag, entity player)
26 float notification, success;
27 float cap_record = ctf_captimerecord;
28 float cap_time = (time - flag.ctf_pickuptime);
29 float f1, f2 = NO_FL_ARG;
30 string s1, s2 = NO_STR_ARG;
31 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
34 if(!ctf_captimerecord)
35 { notification = RED_OR_BLUE(flag, INFO_CTF_CAPTURE_TIME_); s1 = player.netname; f1 = (cap_time * 100); success = TRUE; }
36 else if(cap_time < cap_record)
37 { notification = RED_OR_BLUE(flag, INFO_CTF_CAPTURE_BROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = TRUE; }
39 { notification = RED_OR_BLUE(flag, INFO_CTF_CAPTURE_UNBROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = FALSE; }
42 FOR_EACH_REALCLIENT(tmp_entity)
44 if not(tmp_entity.CAPTURE_VERBOSE) { notification = RED_OR_BLUE(flag, INFO_CTF_CAPTURE_); s2 = NO_STR_ARG; f1 = f2 = NO_FL_ARG; }
45 Send_Notification(tmp_entity, MSG_INFO, notification, s1, s2, f1, f2, NO_FL_ARG);
48 // write that shit in the database
51 ctf_captimerecord = cap_time;
52 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
53 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
54 write_recordmarker(player, (time - cap_time), cap_time);
58 void ctf_FlagcarrierWaypoints(entity player)
60 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
61 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
62 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
63 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
66 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
68 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
69 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
70 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
71 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
74 if(current_height) // make sure we can actually do this arcing path
76 targpos = (to + ('0 0 1' * current_height));
77 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
78 if(trace_fraction < 1)
80 //print("normal arc line failed, trying to find new pos...");
81 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
82 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
83 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
84 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
85 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
88 else { targpos = to; }
90 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
92 vector desired_direction = normalize(targpos - from);
93 if(turnrate) { 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_Notification(player, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
174 Send_Notification(player, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_FREE, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
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_Notification(other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
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_Notification(world, MSG_INFO, RED_OR_BLUE(flag, INFO_CTF_LOST_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
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 Send_Notification(tmp_player, MSG_CENTER, RED_OR_BLUE(flag, CENTER_CTF_PASS_SENT_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
294 else if(tmp_player == player)
295 Send_Notification(tmp_player, MSG_CENTER, RED_OR_BLUE(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
296 else if(!IsDifferentTeam(tmp_player, sender))
297 Send_Notification(tmp_player, MSG_CENTER, RED_OR_BLUE(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
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 ctf_CaptureRecord(enemy_flag, player);
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 Send_Notification(player, MSG_CENTER, RED_OR_BLUE(flag, CENTER_CTF_RETURN_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
452 Send_Notification(world, MSG_INFO, RED_OR_BLUE(flag, INFO_CTF_RETURN_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
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 float pickup_dropped_score; // used to calculate dropped pickup score
479 // attach the flag to the player
481 player.flagcarried = flag;
482 setattachment(flag, player, "");
483 setorigin(flag, FLAG_CARRY_OFFSET);
486 flag.movetype = MOVETYPE_NONE;
487 flag.takedamage = DAMAGE_NO;
488 flag.solid = SOLID_NOT;
489 flag.angles = '0 0 0';
490 flag.ctf_status = FLAG_CARRY;
494 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
495 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
499 // messages and sounds
500 Send_Notification(world, MSG_INFO, RED_OR_BLUE(flag, INFO_CTF_PICKUP_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
501 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
503 FOR_EACH_REALPLAYER(tmp_player)
505 if(tmp_player == player)
507 Send_Notification(tmp_player, MSG_CENTER, RED_OR_BLUE(flag, CENTER_CTF_PICKUP_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
508 if(ctf_stalemate) { Send_Notification(player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); }
510 else if(!IsDifferentTeam(tmp_player, player) && tmp_player != player)
511 Send_Notification(tmp_player, MSG_CENTER, (tmp_player.PICKUP_TEAM_VERBOSE ? CENTER_CTF_PICKUP_TEAM_VERBOSE : CENTER_CTF_PICKUP_TEAM), Team_ColorCode(player.team), (tmp_player.PICKUP_TEAM_VERBOSE ? player.netname : NO_STR_ARG), NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
512 else if(IsDifferentTeam(tmp_player, player))
513 Send_Notification(tmp_player, MSG_CENTER, (tmp_player.PICKUP_ENEMY_VERBOSE ? CENTER_CTF_PICKUP_ENEMY_VERBOSE : CENTER_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), (tmp_player.PICKUP_ENEMY_VERBOSE ? player.netname : NO_STR_ARG), NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
517 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
522 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
523 ctf_EventLog("steal", flag.team, player);
529 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);
530 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);
531 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
532 PlayerTeamScore_AddScore(player, pickup_dropped_score);
533 ctf_EventLog("pickup", flag.team, player);
541 if(pickuptype == PICKUP_BASE)
543 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
544 if((player.speedrunning) && (ctf_captimerecord))
545 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
549 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
552 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
553 ctf_FlagcarrierWaypoints(player);
554 WaypointSprite_Ping(player.wps_flagcarrier);
558 // ===================
559 // Main Flag Functions
560 // ===================
562 void ctf_CheckFlagReturn(entity flag, float returntype)
564 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
566 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
568 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
572 case RETURN_DROPPED: Send_Notification(world, MSG_INFO, RED_OR_BLUE(flag, INFO_CTF_FLAGRETURN_DROPPED_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
573 case RETURN_DAMAGE: Send_Notification(world, MSG_INFO, RED_OR_BLUE(flag, INFO_CTF_FLAGRETURN_DAMAGED_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
574 case RETURN_SPEEDRUN: Send_Notification(world, MSG_INFO, RED_OR_BLUE(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), NO_STR_ARG, NO_STR_ARG, ctf_captimerecord, NO_FL_ARG, NO_FL_ARG); break;
575 case RETURN_NEEDKILL: Send_Notification(world, MSG_INFO, RED_OR_BLUE(flag, INFO_CTF_FLAGRETURN_NEEDKILL_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
579 { Send_Notification(world, MSG_INFO, RED_OR_BLUE(flag, INFO_CTF_FLAGRETURN_TIMEOUT_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break; }
581 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
582 ctf_EventLog("returned", flag.team, world);
583 ctf_RespawnFlag(flag);
588 void ctf_CheckStalemate(void)
591 float stale_red_flags, stale_blue_flags;
594 entity ctf_staleflaglist; // reset the list, we need to build the list each time this function runs
596 // build list of stale flags
597 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
599 if(autocvar_g_ctf_stalemate)
600 if(tmp_entity.ctf_status != FLAG_BASE)
601 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
603 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
604 ctf_staleflaglist = tmp_entity;
606 switch(tmp_entity.team)
608 case COLOR_TEAM1: ++stale_red_flags; break;
609 case COLOR_TEAM2: ++stale_blue_flags; break;
614 if(stale_red_flags && stale_blue_flags)
615 ctf_stalemate = TRUE;
616 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
617 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
618 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
619 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
621 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
624 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
626 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
627 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));
630 if not(wpforenemy_announced)
632 FOR_EACH_REALPLAYER(tmp_entity)
633 if(tmp_entity.flagcarried)
634 Send_Notification(tmp_entity, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
636 Send_Notification(tmp_entity, MSG_CENTER, CENTER_CTF_STALEMATE_OTHER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
638 wpforenemy_announced = TRUE;
643 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
645 if(ITEM_DAMAGE_NEEDKILL(deathtype))
647 // automatically kill the flag and return it
649 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
652 if(autocvar_g_ctf_flag_return_damage)
654 // reduce health and check if it should be returned
655 self.health = self.health - damage;
656 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
666 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
669 if(self == ctf_worldflaglist) // only for the first flag
670 FOR_EACH_CLIENT(tmp_entity)
671 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
674 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
675 dprint("wtf the flag got squashed?\n");
676 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
677 if(!trace_startsolid) // can we resize it without getting stuck?
678 setsize(self, FLAG_MIN, FLAG_MAX); }
680 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
684 self.angles = '0 0 0';
692 switch(self.ctf_status)
696 if(autocvar_g_ctf_dropped_capture_radius)
698 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
699 if(tmp_entity.ctf_status == FLAG_DROPPED)
700 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
701 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
702 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
709 if(autocvar_g_ctf_flag_dropped_floatinwater)
711 vector midpoint = ((self.absmin + self.absmax) * 0.5);
712 if(pointcontents(midpoint) == CONTENT_WATER)
714 self.velocity = self.velocity * 0.5;
716 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
717 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
719 { self.movetype = MOVETYPE_FLY; }
721 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
723 if(autocvar_g_ctf_flag_return_dropped)
725 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
728 ctf_CheckFlagReturn(self, RETURN_DROPPED);
732 if(autocvar_g_ctf_flag_return_time)
734 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
735 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
743 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
746 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
750 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
754 if(autocvar_g_ctf_stalemate)
756 if(time >= wpforenemy_nextthink)
758 ctf_CheckStalemate();
759 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
767 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
768 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
769 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
771 if((self.pass_target == world)
772 || (self.pass_target.deadflag != DEAD_NO)
773 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
774 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
775 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
777 // give up, pass failed
778 ctf_Handle_Drop(self, world, DROP_PASS);
782 // still a viable target, go for it
783 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
788 default: // this should never happen
790 dprint("ctf_FlagThink(): Flag exists with no status?\n");
798 if(gameover) { return; }
800 entity toucher = other;
802 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
803 if(ITEM_TOUCH_NEEDKILL())
806 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
810 // special touch behaviors
811 if(toucher.vehicle_flags & VHF_ISVEHICLE)
813 if(autocvar_g_ctf_allow_vehicle_touch)
814 toucher = toucher.owner; // the player is actually the vehicle owner, not other
816 return; // do nothing
818 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
820 if(time > self.wait) // if we haven't in a while, play a sound/effect
822 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
823 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
824 self.wait = time + FLAG_TOUCHRATE;
828 else if(toucher.deadflag != DEAD_NO) { return; }
830 switch(self.ctf_status)
834 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
835 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
836 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
837 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
843 if(!IsDifferentTeam(toucher, self))
844 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
845 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
846 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
852 dprint("Someone touched a flag even though it was being carried?\n");
858 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
860 if(IsDifferentTeam(toucher, self.pass_sender))
861 ctf_Handle_Return(self, toucher);
863 ctf_Handle_Retrieve(self, toucher);
871 void ctf_RespawnFlag(entity flag)
873 // check for flag respawn being called twice in a row
874 if(flag.last_respawn > time - 0.5)
875 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
877 flag.last_respawn = time;
879 // reset the player (if there is one)
880 if((flag.owner) && (flag.owner.flagcarried == flag))
882 if(flag.owner.wps_enemyflagcarrier)
883 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
885 WaypointSprite_Kill(flag.wps_flagcarrier);
887 flag.owner.flagcarried = world;
889 if(flag.speedrunning)
890 ctf_FakeTimeLimit(flag.owner, -1);
893 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
894 { WaypointSprite_Kill(flag.wps_flagdropped); }
897 setattachment(flag, world, "");
898 setorigin(flag, flag.ctf_spawnorigin);
900 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
901 flag.takedamage = DAMAGE_NO;
902 flag.health = flag.max_flag_health;
903 flag.solid = SOLID_TRIGGER;
904 flag.velocity = '0 0 0';
905 flag.angles = flag.mangle;
906 flag.flags = FL_ITEM | FL_NOTARGET;
908 flag.ctf_status = FLAG_BASE;
910 flag.pass_distance = 0;
911 flag.pass_sender = world;
912 flag.pass_target = world;
913 flag.ctf_dropper = world;
914 flag.ctf_pickuptime = 0;
915 flag.ctf_droptime = 0;
921 if(self.owner.classname == "player")
922 ctf_Handle_Throw(self.owner, world, DROP_RESET);
924 ctf_RespawnFlag(self);
927 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
930 waypoint_spawnforitem_force(self, self.origin);
931 self.nearestwaypointtimeout = 0; // activate waypointing again
932 self.bot_basewaypoint = self.nearestwaypoint;
935 WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
936 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
938 // captureshield setup
939 ctf_CaptureShield_Spawn(self);
942 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
945 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.
946 self = flag; // for later usage with droptofloor()
949 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
950 ctf_worldflaglist = flag;
952 setattachment(flag, world, "");
954 flag.netname = ((teamnumber) ? "^1REPLACETHIS^7" : "^4REPLACETHIS^7"); // ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
955 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
956 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
957 flag.classname = "item_flag_team";
958 flag.target = "###item###"; // wut?
959 flag.flags = FL_ITEM | FL_NOTARGET;
960 flag.solid = SOLID_TRIGGER;
961 flag.takedamage = DAMAGE_NO;
962 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
963 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
964 flag.health = flag.max_flag_health;
965 flag.event_damage = ctf_FlagDamage;
966 flag.pushable = TRUE;
967 flag.teleportable = TELEPORT_NORMAL;
968 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
969 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
970 flag.velocity = '0 0 0';
971 flag.mangle = flag.angles;
972 flag.reset = ctf_Reset;
973 flag.touch = ctf_FlagTouch;
974 flag.think = ctf_FlagThink;
975 flag.nextthink = time + FLAG_THINKRATE;
976 flag.ctf_status = FLAG_BASE;
978 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
979 if(!flag.scale) { flag.scale = FLAG_SCALE; }
980 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
981 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
982 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
983 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
986 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
987 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
988 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
989 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.
990 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
991 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
992 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
995 precache_sound(flag.snd_flag_taken);
996 precache_sound(flag.snd_flag_returned);
997 precache_sound(flag.snd_flag_capture);
998 precache_sound(flag.snd_flag_respawn);
999 precache_sound(flag.snd_flag_dropped);
1000 precache_sound(flag.snd_flag_touch);
1001 precache_sound(flag.snd_flag_pass);
1002 precache_model(flag.model);
1003 precache_model("models/ctf/shield.md3");
1004 precache_model("models/ctf/shockwavetransring.md3");
1007 setmodel(flag, flag.model); // precision set below
1008 setsize(flag, FLAG_MIN, FLAG_MAX);
1009 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1011 if(autocvar_g_ctf_flag_glowtrails)
1013 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1014 flag.glow_size = 25;
1015 flag.glow_trail = 1;
1018 flag.effects |= EF_LOWPRECISION;
1019 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1020 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1023 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1025 flag.dropped_origin = flag.origin;
1026 flag.noalign = TRUE;
1027 flag.movetype = MOVETYPE_NONE;
1029 else // drop to floor, automatically find a platform and set that as spawn origin
1031 flag.noalign = FALSE;
1034 flag.movetype = MOVETYPE_TOSS;
1037 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1045 // NOTE: LEGACY CODE, needs to be re-written!
1047 void havocbot_calculate_middlepoint()
1051 vector fo = '0 0 0';
1054 f = ctf_worldflaglist;
1059 f = f.ctf_worldflagnext;
1063 havocbot_ctf_middlepoint = s * (1.0 / n);
1064 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1068 entity havocbot_ctf_find_flag(entity bot)
1071 f = ctf_worldflaglist;
1074 if (bot.team == f.team)
1076 f = f.ctf_worldflagnext;
1081 entity havocbot_ctf_find_enemy_flag(entity bot)
1084 f = ctf_worldflaglist;
1087 if (bot.team != f.team)
1089 f = f.ctf_worldflagnext;
1094 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1102 FOR_EACH_PLAYER(head)
1104 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1107 if(vlen(head.origin - org) < tc_radius)
1114 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1117 head = ctf_worldflaglist;
1120 if (self.team == head.team)
1122 head = head.ctf_worldflagnext;
1125 navigation_routerating(head, ratingscale, 10000);
1128 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1131 head = ctf_worldflaglist;
1134 if (self.team == head.team)
1136 head = head.ctf_worldflagnext;
1141 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1144 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1147 head = ctf_worldflaglist;
1150 if (self.team != head.team)
1152 head = head.ctf_worldflagnext;
1155 navigation_routerating(head, ratingscale, 10000);
1158 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1160 if not(bot_waypoints_for_items)
1162 havocbot_goalrating_ctf_enemyflag(ratingscale);
1168 head = havocbot_ctf_find_enemy_flag(self);
1173 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1176 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1180 mf = havocbot_ctf_find_flag(self);
1182 if(mf.ctf_status == FLAG_BASE)
1186 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1189 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1192 head = ctf_worldflaglist;
1195 // flag is out in the field
1196 if(head.ctf_status != FLAG_BASE)
1197 if(head.tag_entity==world) // dropped
1201 if(vlen(org-head.origin)<df_radius)
1202 navigation_routerating(head, ratingscale, 10000);
1205 navigation_routerating(head, ratingscale, 10000);
1208 head = head.ctf_worldflagnext;
1212 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1216 head = findchainfloat(bot_pickup, TRUE);
1219 // gather health and armor only
1221 if (head.health || head.armorvalue)
1222 if (vlen(head.origin - org) < sradius)
1224 // get the value of the item
1225 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1227 navigation_routerating(head, t * ratingscale, 500);
1233 void havocbot_ctf_reset_role(entity bot)
1235 float cdefense, cmiddle, coffense;
1236 entity mf, ef, head;
1239 if(bot.deadflag != DEAD_NO)
1242 if(vlen(havocbot_ctf_middlepoint)==0)
1243 havocbot_calculate_middlepoint();
1246 if (bot.flagcarried)
1248 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1252 mf = havocbot_ctf_find_flag(bot);
1253 ef = havocbot_ctf_find_enemy_flag(bot);
1255 // Retrieve stolen flag
1256 if(mf.ctf_status!=FLAG_BASE)
1258 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1262 // If enemy flag is taken go to the middle to intercept pursuers
1263 if(ef.ctf_status!=FLAG_BASE)
1265 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1269 // if there is only me on the team switch to offense
1271 FOR_EACH_PLAYER(head)
1272 if(head.team==bot.team)
1277 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1281 // Evaluate best position to take
1282 // Count mates on middle position
1283 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1285 // Count mates on defense position
1286 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1288 // Count mates on offense position
1289 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1291 if(cdefense<=coffense)
1292 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1293 else if(coffense<=cmiddle)
1294 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1296 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1299 void havocbot_role_ctf_carrier()
1301 if(self.deadflag != DEAD_NO)
1303 havocbot_ctf_reset_role(self);
1307 if (self.flagcarried == world)
1309 havocbot_ctf_reset_role(self);
1313 if (self.bot_strategytime < time)
1315 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1317 navigation_goalrating_start();
1318 havocbot_goalrating_ctf_ourbase(50000);
1321 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1323 navigation_goalrating_end();
1325 if (self.navigation_hasgoals)
1326 self.havocbot_cantfindflag = time + 10;
1327 else if (time > self.havocbot_cantfindflag)
1329 // Can't navigate to my own base, suicide!
1330 // TODO: drop it and wander around
1331 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1337 void havocbot_role_ctf_escort()
1341 if(self.deadflag != DEAD_NO)
1343 havocbot_ctf_reset_role(self);
1347 if (self.flagcarried)
1349 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1353 // If enemy flag is back on the base switch to previous role
1354 ef = havocbot_ctf_find_enemy_flag(self);
1355 if(ef.ctf_status==FLAG_BASE)
1357 self.havocbot_role = self.havocbot_previous_role;
1358 self.havocbot_role_timeout = 0;
1362 // If the flag carrier reached the base switch to defense
1363 mf = havocbot_ctf_find_flag(self);
1364 if(mf.ctf_status!=FLAG_BASE)
1365 if(vlen(ef.origin - mf.dropped_origin) < 300)
1367 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1371 // Set the role timeout if necessary
1372 if (!self.havocbot_role_timeout)
1374 self.havocbot_role_timeout = time + random() * 30 + 60;
1377 // If nothing happened just switch to previous role
1378 if (time > self.havocbot_role_timeout)
1380 self.havocbot_role = self.havocbot_previous_role;
1381 self.havocbot_role_timeout = 0;
1385 // Chase the flag carrier
1386 if (self.bot_strategytime < time)
1388 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1389 navigation_goalrating_start();
1390 havocbot_goalrating_ctf_enemyflag(30000);
1391 havocbot_goalrating_ctf_ourstolenflag(40000);
1392 havocbot_goalrating_items(10000, self.origin, 10000);
1393 navigation_goalrating_end();
1397 void havocbot_role_ctf_offense()
1402 if(self.deadflag != DEAD_NO)
1404 havocbot_ctf_reset_role(self);
1408 if (self.flagcarried)
1410 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1415 mf = havocbot_ctf_find_flag(self);
1416 ef = havocbot_ctf_find_enemy_flag(self);
1419 if(mf.ctf_status!=FLAG_BASE)
1422 pos = mf.tag_entity.origin;
1426 // Try to get it if closer than the enemy base
1427 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1429 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1434 // Escort flag carrier
1435 if(ef.ctf_status!=FLAG_BASE)
1438 pos = ef.tag_entity.origin;
1442 if(vlen(pos-mf.dropped_origin)>700)
1444 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1449 // About to fail, switch to middlefield
1452 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1456 // Set the role timeout if necessary
1457 if (!self.havocbot_role_timeout)
1458 self.havocbot_role_timeout = time + 120;
1460 if (time > self.havocbot_role_timeout)
1462 havocbot_ctf_reset_role(self);
1466 if (self.bot_strategytime < time)
1468 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1469 navigation_goalrating_start();
1470 havocbot_goalrating_ctf_ourstolenflag(50000);
1471 havocbot_goalrating_ctf_enemybase(20000);
1472 havocbot_goalrating_items(5000, self.origin, 1000);
1473 havocbot_goalrating_items(1000, self.origin, 10000);
1474 navigation_goalrating_end();
1478 // Retriever (temporary role):
1479 void havocbot_role_ctf_retriever()
1483 if(self.deadflag != DEAD_NO)
1485 havocbot_ctf_reset_role(self);
1489 if (self.flagcarried)
1491 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1495 // If flag is back on the base switch to previous role
1496 mf = havocbot_ctf_find_flag(self);
1497 if(mf.ctf_status==FLAG_BASE)
1499 havocbot_ctf_reset_role(self);
1503 if (!self.havocbot_role_timeout)
1504 self.havocbot_role_timeout = time + 20;
1506 if (time > self.havocbot_role_timeout)
1508 havocbot_ctf_reset_role(self);
1512 if (self.bot_strategytime < time)
1517 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1518 navigation_goalrating_start();
1519 havocbot_goalrating_ctf_ourstolenflag(50000);
1520 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1521 havocbot_goalrating_ctf_enemybase(30000);
1522 havocbot_goalrating_items(500, self.origin, rt_radius);
1523 navigation_goalrating_end();
1527 void havocbot_role_ctf_middle()
1531 if(self.deadflag != DEAD_NO)
1533 havocbot_ctf_reset_role(self);
1537 if (self.flagcarried)
1539 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1543 mf = havocbot_ctf_find_flag(self);
1544 if(mf.ctf_status!=FLAG_BASE)
1546 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1550 if (!self.havocbot_role_timeout)
1551 self.havocbot_role_timeout = time + 10;
1553 if (time > self.havocbot_role_timeout)
1555 havocbot_ctf_reset_role(self);
1559 if (self.bot_strategytime < time)
1563 org = havocbot_ctf_middlepoint;
1564 org_z = self.origin_z;
1566 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1567 navigation_goalrating_start();
1568 havocbot_goalrating_ctf_ourstolenflag(50000);
1569 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1570 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1571 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1572 havocbot_goalrating_items(2500, self.origin, 10000);
1573 havocbot_goalrating_ctf_enemybase(2500);
1574 navigation_goalrating_end();
1578 void havocbot_role_ctf_defense()
1582 if(self.deadflag != DEAD_NO)
1584 havocbot_ctf_reset_role(self);
1588 if (self.flagcarried)
1590 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1594 // If own flag was captured
1595 mf = havocbot_ctf_find_flag(self);
1596 if(mf.ctf_status!=FLAG_BASE)
1598 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1602 if (!self.havocbot_role_timeout)
1603 self.havocbot_role_timeout = time + 30;
1605 if (time > self.havocbot_role_timeout)
1607 havocbot_ctf_reset_role(self);
1610 if (self.bot_strategytime < time)
1615 org = mf.dropped_origin;
1616 mp_radius = havocbot_ctf_middlepoint_radius;
1618 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1619 navigation_goalrating_start();
1621 // if enemies are closer to our base, go there
1622 entity head, closestplayer = world;
1623 float distance, bestdistance = 10000;
1624 FOR_EACH_PLAYER(head)
1626 if(head.deadflag!=DEAD_NO)
1629 distance = vlen(org - head.origin);
1630 if(distance<bestdistance)
1632 closestplayer = head;
1633 bestdistance = distance;
1638 if(closestplayer.team!=self.team)
1639 if(vlen(org - self.origin)>1000)
1640 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1641 havocbot_goalrating_ctf_ourbase(30000);
1643 havocbot_goalrating_ctf_ourstolenflag(20000);
1644 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1645 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1646 havocbot_goalrating_items(10000, org, mp_radius);
1647 havocbot_goalrating_items(5000, self.origin, 10000);
1648 navigation_goalrating_end();
1652 void havocbot_role_ctf_setrole(entity bot, float role)
1654 dprint(strcat(bot.netname," switched to "));
1657 case HAVOCBOT_CTF_ROLE_CARRIER:
1659 bot.havocbot_role = havocbot_role_ctf_carrier;
1660 bot.havocbot_role_timeout = 0;
1661 bot.havocbot_cantfindflag = time + 10;
1662 bot.bot_strategytime = 0;
1664 case HAVOCBOT_CTF_ROLE_DEFENSE:
1666 bot.havocbot_role = havocbot_role_ctf_defense;
1667 bot.havocbot_role_timeout = 0;
1669 case HAVOCBOT_CTF_ROLE_MIDDLE:
1671 bot.havocbot_role = havocbot_role_ctf_middle;
1672 bot.havocbot_role_timeout = 0;
1674 case HAVOCBOT_CTF_ROLE_OFFENSE:
1676 bot.havocbot_role = havocbot_role_ctf_offense;
1677 bot.havocbot_role_timeout = 0;
1679 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1680 dprint("retriever");
1681 bot.havocbot_previous_role = bot.havocbot_role;
1682 bot.havocbot_role = havocbot_role_ctf_retriever;
1683 bot.havocbot_role_timeout = time + 10;
1684 bot.bot_strategytime = 0;
1686 case HAVOCBOT_CTF_ROLE_ESCORT:
1688 bot.havocbot_previous_role = bot.havocbot_role;
1689 bot.havocbot_role = havocbot_role_ctf_escort;
1690 bot.havocbot_role_timeout = time + 30;
1691 bot.bot_strategytime = 0;
1702 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1706 // initially clear items so they can be set as necessary later.
1707 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1708 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1710 // scan through all the flags and notify the client about them
1711 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1713 switch(flag.ctf_status)
1718 if((flag.owner == self) || (flag.pass_sender == self))
1719 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1721 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1726 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1732 // item for stopping players from capturing the flag too often
1733 if(self.ctf_captureshielded)
1734 self.items |= IT_CTF_SHIELDED;
1736 // update the health of the flag carrier waypointsprite
1737 if(self.wps_flagcarrier)
1738 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1743 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1745 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1747 if(frag_target == frag_attacker) // damage done to yourself
1749 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1750 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1752 else // damage done to everyone else
1754 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1755 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1758 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1760 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1761 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1763 frag_target.wps_helpme_time = time;
1764 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1770 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1772 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1774 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1775 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1778 if(frag_target.flagcarried)
1779 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1784 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1787 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1790 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1792 entity flag; // temporary entity for the search method
1794 if(self.flagcarried)
1795 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1797 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1799 if(flag.pass_sender == self) { flag.pass_sender = world; }
1800 if(flag.pass_target == self) { flag.pass_target = world; }
1801 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1807 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1809 if(self.flagcarried)
1810 if(!autocvar_g_ctf_portalteleport)
1811 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1816 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1818 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1820 entity player = self;
1822 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1824 // pass the flag to a team mate
1825 if(autocvar_g_ctf_pass)
1827 entity head, closest_target;
1828 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1830 while(head) // find the closest acceptable target to pass to
1832 if(head.classname == "player" && head.deadflag == DEAD_NO)
1833 if(head != player && !IsDifferentTeam(head, player))
1834 if(!head.speedrunning && !head.vehicle)
1836 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1837 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1838 vector passer_center = CENTER_OR_VIEWOFS(player);
1840 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1842 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1844 if(clienttype(head) == CLIENTTYPE_BOT)
1846 Send_Notification(player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1847 ctf_Handle_Throw(head, player, DROP_PASS);
1851 Send_Notification(head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1852 Send_Notification(player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1854 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1857 else if(player.flagcarried)
1861 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1862 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1863 { closest_target = head; }
1865 else { closest_target = head; }
1872 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1875 // throw the flag in front of you
1876 if(autocvar_g_ctf_throw && player.flagcarried)
1878 if(player.throw_count == -1)
1880 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1882 player.throw_prevtime = time;
1883 player.throw_count = 1;
1884 ctf_Handle_Throw(player, world, DROP_THROW);
1889 Send_Notification(player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, NO_STR_ARG, NO_STR_ARG, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time), NO_FL_ARG, NO_FL_ARG);
1895 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1896 else { player.throw_count += 1; }
1897 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1899 player.throw_prevtime = time;
1900 ctf_Handle_Throw(player, world, DROP_THROW);
1909 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1911 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1913 self.wps_helpme_time = time;
1914 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1916 else // create a normal help me waypointsprite
1918 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');
1919 WaypointSprite_Ping(self.wps_helpme);
1925 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1927 if(vh_player.flagcarried)
1929 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1931 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1935 setattachment(vh_player.flagcarried, vh_vehicle, "");
1936 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1937 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1938 //vh_player.flagcarried.angles = '0 0 0';
1946 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1948 if(vh_player.flagcarried)
1950 setattachment(vh_player.flagcarried, vh_player, "");
1951 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1952 vh_player.flagcarried.scale = FLAG_SCALE;
1953 vh_player.flagcarried.angles = '0 0 0';
1960 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1962 if(self.flagcarried)
1964 Send_Notification(world, MSG_INFO, RED_OR_BLUE(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1965 ctf_RespawnFlag(self.flagcarried);
1972 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1974 entity flag; // temporary entity for the search method
1976 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1978 switch(flag.ctf_status)
1983 // lock the flag, game is over
1984 flag.movetype = MOVETYPE_NONE;
1985 flag.takedamage = DAMAGE_NO;
1986 flag.solid = SOLID_NOT;
1987 flag.nextthink = FALSE; // stop thinking
1989 print("stopping the ", flag.netname, " from moving.\n");
1997 // do nothing for these flags
2006 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2008 havocbot_ctf_reset_role(self);
2012 MUTATOR_HOOKFUNCTION(ctf_GetCvars)
2014 GetCvars_handleFloat(get_cvars_s, get_cvars_f, CAPTURE_VERBOSE, "notification_ctf_capture_verbose");
2015 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_TEAM_VERBOSE, "notification_ctf_pickup_team_verbose");
2016 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_ENEMY_VERBOSE, "notification_ctf_pickup_enemy_verbose");
2025 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2026 CTF Starting point for a player in team one (Red).
2027 Keys: "angle" viewing angle when spawning. */
2028 void spawnfunc_info_player_team1()
2030 if(g_assault) { remove(self); return; }
2032 self.team = COLOR_TEAM1; // red
2033 spawnfunc_info_player_deathmatch();
2037 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2038 CTF Starting point for a player in team two (Blue).
2039 Keys: "angle" viewing angle when spawning. */
2040 void spawnfunc_info_player_team2()
2042 if(g_assault) { remove(self); return; }
2044 self.team = COLOR_TEAM2; // blue
2045 spawnfunc_info_player_deathmatch();
2048 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2049 CTF Starting point for a player in team three (Yellow).
2050 Keys: "angle" viewing angle when spawning. */
2051 void spawnfunc_info_player_team3()
2053 if(g_assault) { remove(self); return; }
2055 self.team = COLOR_TEAM3; // yellow
2056 spawnfunc_info_player_deathmatch();
2060 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2061 CTF Starting point for a player in team four (Purple).
2062 Keys: "angle" viewing angle when spawning. */
2063 void spawnfunc_info_player_team4()
2065 if(g_assault) { remove(self); return; }
2067 self.team = COLOR_TEAM4; // purple
2068 spawnfunc_info_player_deathmatch();
2071 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2072 CTF flag for team one (Red).
2074 "angle" Angle the flag will point (minus 90 degrees)...
2075 "model" model to use, note this needs red and blue as skins 0 and 1...
2076 "noise" sound played when flag is picked up...
2077 "noise1" sound played when flag is returned by a teammate...
2078 "noise2" sound played when flag is captured...
2079 "noise3" sound played when flag is lost in the field and respawns itself...
2080 "noise4" sound played when flag is dropped by a player...
2081 "noise5" sound played when flag touches the ground... */
2082 void spawnfunc_item_flag_team1()
2084 if(!g_ctf) { remove(self); return; }
2086 ctf_FlagSetup(1, self); // 1 = red
2089 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2090 CTF flag for team two (Blue).
2092 "angle" Angle the flag will point (minus 90 degrees)...
2093 "model" model to use, note this needs red and blue as skins 0 and 1...
2094 "noise" sound played when flag is picked up...
2095 "noise1" sound played when flag is returned by a teammate...
2096 "noise2" sound played when flag is captured...
2097 "noise3" sound played when flag is lost in the field and respawns itself...
2098 "noise4" sound played when flag is dropped by a player...
2099 "noise5" sound played when flag touches the ground... */
2100 void spawnfunc_item_flag_team2()
2102 if(!g_ctf) { remove(self); return; }
2104 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2107 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2108 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2109 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.
2111 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2112 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2113 void spawnfunc_ctf_team()
2115 if(!g_ctf) { remove(self); return; }
2117 self.classname = "ctf_team";
2118 self.team = self.cnt + 1;
2121 // compatibility for quake maps
2122 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2123 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2124 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2125 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2126 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2127 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2135 void ctf_ScoreRules()
2137 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2138 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2139 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2140 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2141 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2142 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2143 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2144 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2145 ScoreRules_basics_end();
2148 // code from here on is just to support maps that don't have flag and team entities
2149 void ctf_SpawnTeam (string teamname, float teamcolor)
2154 self.classname = "ctf_team";
2155 self.netname = teamname;
2156 self.cnt = teamcolor;
2158 spawnfunc_ctf_team();
2163 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2165 // if no teams are found, spawn defaults
2166 if(find(world, classname, "ctf_team") == world)
2168 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2169 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
2170 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
2176 void ctf_Initialize()
2178 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2180 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2181 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2182 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2184 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2188 MUTATOR_DEFINITION(gamemode_ctf)
2190 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2191 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2192 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2193 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2194 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2195 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2196 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2197 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2198 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2199 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2200 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2201 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2202 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2203 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2204 MUTATOR_HOOK(GetCvars, ctf_GetCvars, CBC_ORDER_ANY);
2208 if(time > 1) // game loads at time 1
2209 error("This is a game type and it cannot be added at runtime.");
2217 error("This is a game type and it cannot be removed at runtime.");