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 cap_record = ctf_captimerecord;
27 float cap_time = (time - flag.ctf_pickuptime);
28 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
31 FOR_EACH_REALCLIENT(tmp_entity)
33 if(tmp_entity.CAPTURE_VERBOSE)
35 if(!ctf_captimerecord) { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
36 else if(cap_time < cap_record) { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
37 else { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
39 else { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_), player.netname); }
42 // notify server log too
43 if(autocvar_notification_ctf_capture_verbose)
45 if(!ctf_captimerecord) { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
46 else if(cap_time < cap_record) { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
47 else { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
49 else { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_), player.netname); }
51 // write that shit in the database
52 if((!ctf_captimerecord) || (cap_time < cap_record))
54 ctf_captimerecord = cap_time;
55 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
56 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
57 write_recordmarker(player, (time - cap_time), cap_time);
61 void ctf_FlagcarrierWaypoints(entity player)
63 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
64 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
65 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
66 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
69 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
71 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
72 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
73 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
74 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
77 if(current_height) // make sure we can actually do this arcing path
79 targpos = (to + ('0 0 1' * current_height));
80 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
81 if(trace_fraction < 1)
83 //print("normal arc line failed, trying to find new pos...");
84 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
85 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
86 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
87 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
88 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
91 else { targpos = to; }
93 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
95 vector desired_direction = normalize(targpos - from);
96 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
97 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
100 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
102 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
104 // directional tracing only
106 makevectors(passer_angle);
108 // find the closest point on the enemy to the center of the attack
109 float ang; // angle between shotdir and h
110 float h; // hypotenuse, which is the distance between attacker to head
111 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
113 h = vlen(head_center - passer_center);
114 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
117 vector nearest_on_line = (passer_center + a * v_forward);
118 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
120 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
121 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
123 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
128 else { return TRUE; }
132 // =======================
133 // CaptureShield Functions
134 // =======================
136 float ctf_CaptureShield_CheckStatus(entity p)
140 float players_worseeq, players_total;
142 if(ctf_captureshield_max_ratio <= 0)
145 s = PlayerScore_Add(p, SP_SCORE, 0);
146 if(s >= -ctf_captureshield_min_negscore)
149 players_total = players_worseeq = 0;
152 if(IsDifferentTeam(e, p))
154 se = PlayerScore_Add(e, SP_SCORE, 0);
160 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
161 // use this rule here
163 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
169 void ctf_CaptureShield_Update(entity player, float wanted_status)
171 float updated_status = ctf_CaptureShield_CheckStatus(player);
172 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
174 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
175 player.ctf_captureshielded = updated_status;
179 float ctf_CaptureShield_Customize()
181 if(!other.ctf_captureshielded) { return FALSE; }
182 if(!IsDifferentTeam(self, other)) { return FALSE; }
187 void ctf_CaptureShield_Touch()
189 if(!other.ctf_captureshielded) { return; }
190 if(!IsDifferentTeam(self, other)) { return; }
192 vector mymid = (self.absmin + self.absmax) * 0.5;
193 vector othermid = (other.absmin + other.absmax) * 0.5;
195 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
196 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED);
199 void ctf_CaptureShield_Spawn(entity flag)
201 entity shield = spawn();
204 shield.team = self.team;
205 shield.touch = ctf_CaptureShield_Touch;
206 shield.customizeentityforclient = ctf_CaptureShield_Customize;
207 shield.classname = "ctf_captureshield";
208 shield.effects = EF_ADDITIVE;
209 shield.movetype = MOVETYPE_NOCLIP;
210 shield.solid = SOLID_TRIGGER;
211 shield.avelocity = '7 0 11';
214 setorigin(shield, self.origin);
215 setmodel(shield, "models/ctf/shield.md3");
216 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
220 // ====================
221 // Drop/Pass/Throw Code
222 // ====================
224 void ctf_Handle_Drop(entity flag, entity player, float droptype)
227 player = (player ? player : flag.pass_sender);
230 flag.movetype = MOVETYPE_TOSS;
231 flag.takedamage = DAMAGE_YES;
232 flag.angles = '0 0 0';
233 flag.health = flag.max_flag_health;
234 flag.ctf_droptime = time;
235 flag.ctf_dropper = player;
236 flag.ctf_status = FLAG_DROPPED;
238 // messages and sounds
239 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
240 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
241 ctf_EventLog("dropped", player.team, player);
244 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
245 PlayerScore_Add(player, SP_CTF_DROPS, 1);
248 if(autocvar_g_ctf_flag_dropped_waypoint)
249 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));
251 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
253 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
254 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
257 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
259 if(droptype == DROP_PASS)
261 flag.pass_distance = 0;
262 flag.pass_sender = world;
263 flag.pass_target = world;
267 void ctf_Handle_Retrieve(entity flag, entity player)
269 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
270 entity sender = flag.pass_sender;
272 // transfer flag to player
274 flag.owner.flagcarried = flag;
277 setattachment(flag, player, "");
278 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, ATTN_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(!IsDifferentTeam(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, ATTN_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 not(player) { return; } // without someone to give the reward to, we can't possibly cap
408 // messages and sounds
409 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
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(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
452 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
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(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
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(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
508 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
510 else if(!IsDifferentTeam(tmp_player, player) && tmp_player != player)
512 if(tmp_player.PICKUP_TEAM_VERBOSE)
513 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_TEAM_VERBOSE, Team_ColorCode(player.team), player.netname);
515 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_TEAM, Team_ColorCode(player.team));
517 else if(IsDifferentTeam(tmp_player, player))
519 if(tmp_player.PICKUP_ENEMY_VERBOSE)
520 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY_VERBOSE, Team_ColorCode(player.team), player.netname);
522 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY, Team_ColorCode(player.team));
527 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
532 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
533 ctf_EventLog("steal", flag.team, player);
539 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);
540 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);
541 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
542 PlayerTeamScore_AddScore(player, pickup_dropped_score);
543 ctf_EventLog("pickup", flag.team, player);
551 if(pickuptype == PICKUP_BASE)
553 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
554 if((player.speedrunning) && (ctf_captimerecord))
555 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
559 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
562 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
563 ctf_FlagcarrierWaypoints(player);
564 WaypointSprite_Ping(player.wps_flagcarrier);
568 // ===================
569 // Main Flag Functions
570 // ===================
572 void ctf_CheckFlagReturn(entity flag, float returntype)
574 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
576 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
578 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
582 case RETURN_DROPPED: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
583 case RETURN_DAMAGE: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
584 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
585 case RETURN_NEEDKILL: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
589 { Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
591 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
592 ctf_EventLog("returned", flag.team, world);
593 ctf_RespawnFlag(flag);
598 void ctf_CheckStalemate(void)
601 float stale_red_flags = 0, stale_blue_flags = 0;
604 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
606 // build list of stale flags
607 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
609 if(autocvar_g_ctf_stalemate)
610 if(tmp_entity.ctf_status != FLAG_BASE)
611 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
613 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
614 ctf_staleflaglist = tmp_entity;
616 switch(tmp_entity.team)
618 case FL_TEAM_1: ++stale_red_flags; break;
619 case FL_TEAM_2: ++stale_blue_flags; break;
624 if(stale_red_flags && stale_blue_flags)
625 ctf_stalemate = TRUE;
626 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
627 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
628 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
629 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
631 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
634 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
636 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
637 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));
640 if not(wpforenemy_announced)
642 FOR_EACH_REALPLAYER(tmp_entity)
643 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
645 wpforenemy_announced = TRUE;
650 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
652 if(ITEM_DAMAGE_NEEDKILL(deathtype))
654 // automatically kill the flag and return it
656 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
659 if(autocvar_g_ctf_flag_return_damage)
661 // reduce health and check if it should be returned
662 self.health = self.health - damage;
663 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
673 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
676 if(self == ctf_worldflaglist) // only for the first flag
677 FOR_EACH_CLIENT(tmp_entity)
678 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
681 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
682 dprint("wtf the flag got squashed?\n");
683 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
684 if(!trace_startsolid) // can we resize it without getting stuck?
685 setsize(self, FLAG_MIN, FLAG_MAX); }
687 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
691 self.angles = '0 0 0';
699 switch(self.ctf_status)
703 if(autocvar_g_ctf_dropped_capture_radius)
705 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
706 if(tmp_entity.ctf_status == FLAG_DROPPED)
707 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
708 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
709 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
716 if(autocvar_g_ctf_flag_dropped_floatinwater)
718 vector midpoint = ((self.absmin + self.absmax) * 0.5);
719 if(pointcontents(midpoint) == CONTENT_WATER)
721 self.velocity = self.velocity * 0.5;
723 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
724 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
726 { self.movetype = MOVETYPE_FLY; }
728 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
730 if(autocvar_g_ctf_flag_return_dropped)
732 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
735 ctf_CheckFlagReturn(self, RETURN_DROPPED);
739 if(autocvar_g_ctf_flag_return_time)
741 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
742 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
750 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
753 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
757 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
761 if(autocvar_g_ctf_stalemate)
763 if(time >= wpforenemy_nextthink)
765 ctf_CheckStalemate();
766 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
774 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
775 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
776 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
778 if((self.pass_target == world)
779 || (self.pass_target.deadflag != DEAD_NO)
780 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
781 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
782 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
784 // give up, pass failed
785 ctf_Handle_Drop(self, world, DROP_PASS);
789 // still a viable target, go for it
790 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
795 default: // this should never happen
797 dprint("ctf_FlagThink(): Flag exists with no status?\n");
805 if(gameover) { return; }
807 entity toucher = other;
809 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
810 if(ITEM_TOUCH_NEEDKILL())
813 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
817 // special touch behaviors
818 if(toucher.vehicle_flags & VHF_ISVEHICLE)
820 if(autocvar_g_ctf_allow_vehicle_touch)
821 toucher = toucher.owner; // the player is actually the vehicle owner, not other
823 return; // do nothing
825 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
827 if(time > self.wait) // if we haven't in a while, play a sound/effect
829 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
830 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
831 self.wait = time + FLAG_TOUCHRATE;
835 else if(toucher.deadflag != DEAD_NO) { return; }
837 switch(self.ctf_status)
841 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
842 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
843 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
844 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
850 if(!IsDifferentTeam(toucher, self))
851 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
852 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
853 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
859 dprint("Someone touched a flag even though it was being carried?\n");
865 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
867 if(IsDifferentTeam(toucher, self.pass_sender))
868 ctf_Handle_Return(self, toucher);
870 ctf_Handle_Retrieve(self, toucher);
878 void ctf_RespawnFlag(entity flag)
880 // check for flag respawn being called twice in a row
881 if(flag.last_respawn > time - 0.5)
882 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
884 flag.last_respawn = time;
886 // reset the player (if there is one)
887 if((flag.owner) && (flag.owner.flagcarried == flag))
889 if(flag.owner.wps_enemyflagcarrier)
890 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
892 WaypointSprite_Kill(flag.wps_flagcarrier);
894 flag.owner.flagcarried = world;
896 if(flag.speedrunning)
897 ctf_FakeTimeLimit(flag.owner, -1);
900 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
901 { WaypointSprite_Kill(flag.wps_flagdropped); }
904 setattachment(flag, world, "");
905 setorigin(flag, flag.ctf_spawnorigin);
907 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
908 flag.takedamage = DAMAGE_NO;
909 flag.health = flag.max_flag_health;
910 flag.solid = SOLID_TRIGGER;
911 flag.velocity = '0 0 0';
912 flag.angles = flag.mangle;
913 flag.flags = FL_ITEM | FL_NOTARGET;
915 flag.ctf_status = FLAG_BASE;
917 flag.pass_distance = 0;
918 flag.pass_sender = world;
919 flag.pass_target = world;
920 flag.ctf_dropper = world;
921 flag.ctf_pickuptime = 0;
922 flag.ctf_droptime = 0;
928 if(self.owner.classname == "player")
929 ctf_Handle_Throw(self.owner, world, DROP_RESET);
931 ctf_RespawnFlag(self);
934 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
937 waypoint_spawnforitem_force(self, self.origin);
938 self.nearestwaypointtimeout = 0; // activate waypointing again
939 self.bot_basewaypoint = self.nearestwaypoint;
942 WaypointSprite_SpawnFixed(((self.team == FL_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
943 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
945 // captureshield setup
946 ctf_CaptureShield_Spawn(self);
949 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
952 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.
953 self = flag; // for later usage with droptofloor()
956 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
957 ctf_worldflaglist = flag;
959 setattachment(flag, world, "");
961 flag.netname = ((teamnumber) ? "^1REPLACETHIS^7" : "^4REPLACETHIS^7"); // ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
962 flag.team = ((teamnumber) ? FL_TEAM_1 : FL_TEAM_2); // FL_TEAM_1: color 4 team (red) - FL_TEAM_2: color 13 team (blue)
963 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
964 flag.classname = "item_flag_team";
965 flag.target = "###item###"; // wut?
966 flag.flags = FL_ITEM | FL_NOTARGET;
967 flag.solid = SOLID_TRIGGER;
968 flag.takedamage = DAMAGE_NO;
969 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
970 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
971 flag.health = flag.max_flag_health;
972 flag.event_damage = ctf_FlagDamage;
973 flag.pushable = TRUE;
974 flag.teleportable = TELEPORT_NORMAL;
975 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
976 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
977 flag.velocity = '0 0 0';
978 flag.mangle = flag.angles;
979 flag.reset = ctf_Reset;
980 flag.touch = ctf_FlagTouch;
981 flag.think = ctf_FlagThink;
982 flag.nextthink = time + FLAG_THINKRATE;
983 flag.ctf_status = FLAG_BASE;
985 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
986 if(!flag.scale) { flag.scale = FLAG_SCALE; }
987 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
988 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
989 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
990 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
993 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
994 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
995 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
996 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.
997 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
998 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
999 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1002 precache_sound(flag.snd_flag_taken);
1003 precache_sound(flag.snd_flag_returned);
1004 precache_sound(flag.snd_flag_capture);
1005 precache_sound(flag.snd_flag_respawn);
1006 precache_sound(flag.snd_flag_dropped);
1007 precache_sound(flag.snd_flag_touch);
1008 precache_sound(flag.snd_flag_pass);
1009 precache_model(flag.model);
1010 precache_model("models/ctf/shield.md3");
1011 precache_model("models/ctf/shockwavetransring.md3");
1014 setmodel(flag, flag.model); // precision set below
1015 setsize(flag, FLAG_MIN, FLAG_MAX);
1016 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1018 if(autocvar_g_ctf_flag_glowtrails)
1020 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1021 flag.glow_size = 25;
1022 flag.glow_trail = 1;
1025 flag.effects |= EF_LOWPRECISION;
1026 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1027 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1030 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1032 flag.dropped_origin = flag.origin;
1033 flag.noalign = TRUE;
1034 flag.movetype = MOVETYPE_NONE;
1036 else // drop to floor, automatically find a platform and set that as spawn origin
1038 flag.noalign = FALSE;
1041 flag.movetype = MOVETYPE_TOSS;
1044 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1052 // NOTE: LEGACY CODE, needs to be re-written!
1054 void havocbot_calculate_middlepoint()
1058 vector fo = '0 0 0';
1061 f = ctf_worldflaglist;
1066 f = f.ctf_worldflagnext;
1070 havocbot_ctf_middlepoint = s * (1.0 / n);
1071 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1075 entity havocbot_ctf_find_flag(entity bot)
1078 f = ctf_worldflaglist;
1081 if (bot.team == f.team)
1083 f = f.ctf_worldflagnext;
1088 entity havocbot_ctf_find_enemy_flag(entity bot)
1091 f = ctf_worldflaglist;
1094 if (bot.team != f.team)
1096 f = f.ctf_worldflagnext;
1101 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1109 FOR_EACH_PLAYER(head)
1111 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1114 if(vlen(head.origin - org) < tc_radius)
1121 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1124 head = ctf_worldflaglist;
1127 if (self.team == head.team)
1129 head = head.ctf_worldflagnext;
1132 navigation_routerating(head, ratingscale, 10000);
1135 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1138 head = ctf_worldflaglist;
1141 if (self.team == head.team)
1143 head = head.ctf_worldflagnext;
1148 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1151 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1154 head = ctf_worldflaglist;
1157 if (self.team != head.team)
1159 head = head.ctf_worldflagnext;
1162 navigation_routerating(head, ratingscale, 10000);
1165 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1167 if not(bot_waypoints_for_items)
1169 havocbot_goalrating_ctf_enemyflag(ratingscale);
1175 head = havocbot_ctf_find_enemy_flag(self);
1180 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1183 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1187 mf = havocbot_ctf_find_flag(self);
1189 if(mf.ctf_status == FLAG_BASE)
1193 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1196 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1199 head = ctf_worldflaglist;
1202 // flag is out in the field
1203 if(head.ctf_status != FLAG_BASE)
1204 if(head.tag_entity==world) // dropped
1208 if(vlen(org-head.origin)<df_radius)
1209 navigation_routerating(head, ratingscale, 10000);
1212 navigation_routerating(head, ratingscale, 10000);
1215 head = head.ctf_worldflagnext;
1219 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1223 head = findchainfloat(bot_pickup, TRUE);
1226 // gather health and armor only
1228 if (head.health || head.armorvalue)
1229 if (vlen(head.origin - org) < sradius)
1231 // get the value of the item
1232 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1234 navigation_routerating(head, t * ratingscale, 500);
1240 void havocbot_ctf_reset_role(entity bot)
1242 float cdefense, cmiddle, coffense;
1243 entity mf, ef, head;
1246 if(bot.deadflag != DEAD_NO)
1249 if(vlen(havocbot_ctf_middlepoint)==0)
1250 havocbot_calculate_middlepoint();
1253 if (bot.flagcarried)
1255 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1259 mf = havocbot_ctf_find_flag(bot);
1260 ef = havocbot_ctf_find_enemy_flag(bot);
1262 // Retrieve stolen flag
1263 if(mf.ctf_status!=FLAG_BASE)
1265 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1269 // If enemy flag is taken go to the middle to intercept pursuers
1270 if(ef.ctf_status!=FLAG_BASE)
1272 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1276 // if there is only me on the team switch to offense
1278 FOR_EACH_PLAYER(head)
1279 if(head.team==bot.team)
1284 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1288 // Evaluate best position to take
1289 // Count mates on middle position
1290 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1292 // Count mates on defense position
1293 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1295 // Count mates on offense position
1296 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1298 if(cdefense<=coffense)
1299 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1300 else if(coffense<=cmiddle)
1301 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1303 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1306 void havocbot_role_ctf_carrier()
1308 if(self.deadflag != DEAD_NO)
1310 havocbot_ctf_reset_role(self);
1314 if (self.flagcarried == world)
1316 havocbot_ctf_reset_role(self);
1320 if (self.bot_strategytime < time)
1322 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1324 navigation_goalrating_start();
1325 havocbot_goalrating_ctf_ourbase(50000);
1328 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1330 navigation_goalrating_end();
1332 if (self.navigation_hasgoals)
1333 self.havocbot_cantfindflag = time + 10;
1334 else if (time > self.havocbot_cantfindflag)
1336 // Can't navigate to my own base, suicide!
1337 // TODO: drop it and wander around
1338 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1344 void havocbot_role_ctf_escort()
1348 if(self.deadflag != DEAD_NO)
1350 havocbot_ctf_reset_role(self);
1354 if (self.flagcarried)
1356 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1360 // If enemy flag is back on the base switch to previous role
1361 ef = havocbot_ctf_find_enemy_flag(self);
1362 if(ef.ctf_status==FLAG_BASE)
1364 self.havocbot_role = self.havocbot_previous_role;
1365 self.havocbot_role_timeout = 0;
1369 // If the flag carrier reached the base switch to defense
1370 mf = havocbot_ctf_find_flag(self);
1371 if(mf.ctf_status!=FLAG_BASE)
1372 if(vlen(ef.origin - mf.dropped_origin) < 300)
1374 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1378 // Set the role timeout if necessary
1379 if (!self.havocbot_role_timeout)
1381 self.havocbot_role_timeout = time + random() * 30 + 60;
1384 // If nothing happened just switch to previous role
1385 if (time > self.havocbot_role_timeout)
1387 self.havocbot_role = self.havocbot_previous_role;
1388 self.havocbot_role_timeout = 0;
1392 // Chase the flag carrier
1393 if (self.bot_strategytime < time)
1395 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1396 navigation_goalrating_start();
1397 havocbot_goalrating_ctf_enemyflag(30000);
1398 havocbot_goalrating_ctf_ourstolenflag(40000);
1399 havocbot_goalrating_items(10000, self.origin, 10000);
1400 navigation_goalrating_end();
1404 void havocbot_role_ctf_offense()
1409 if(self.deadflag != DEAD_NO)
1411 havocbot_ctf_reset_role(self);
1415 if (self.flagcarried)
1417 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1422 mf = havocbot_ctf_find_flag(self);
1423 ef = havocbot_ctf_find_enemy_flag(self);
1426 if(mf.ctf_status!=FLAG_BASE)
1429 pos = mf.tag_entity.origin;
1433 // Try to get it if closer than the enemy base
1434 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1436 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1441 // Escort flag carrier
1442 if(ef.ctf_status!=FLAG_BASE)
1445 pos = ef.tag_entity.origin;
1449 if(vlen(pos-mf.dropped_origin)>700)
1451 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1456 // About to fail, switch to middlefield
1459 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1463 // Set the role timeout if necessary
1464 if (!self.havocbot_role_timeout)
1465 self.havocbot_role_timeout = time + 120;
1467 if (time > self.havocbot_role_timeout)
1469 havocbot_ctf_reset_role(self);
1473 if (self.bot_strategytime < time)
1475 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1476 navigation_goalrating_start();
1477 havocbot_goalrating_ctf_ourstolenflag(50000);
1478 havocbot_goalrating_ctf_enemybase(20000);
1479 havocbot_goalrating_items(5000, self.origin, 1000);
1480 havocbot_goalrating_items(1000, self.origin, 10000);
1481 navigation_goalrating_end();
1485 // Retriever (temporary role):
1486 void havocbot_role_ctf_retriever()
1490 if(self.deadflag != DEAD_NO)
1492 havocbot_ctf_reset_role(self);
1496 if (self.flagcarried)
1498 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1502 // If flag is back on the base switch to previous role
1503 mf = havocbot_ctf_find_flag(self);
1504 if(mf.ctf_status==FLAG_BASE)
1506 havocbot_ctf_reset_role(self);
1510 if (!self.havocbot_role_timeout)
1511 self.havocbot_role_timeout = time + 20;
1513 if (time > self.havocbot_role_timeout)
1515 havocbot_ctf_reset_role(self);
1519 if (self.bot_strategytime < time)
1524 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1525 navigation_goalrating_start();
1526 havocbot_goalrating_ctf_ourstolenflag(50000);
1527 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1528 havocbot_goalrating_ctf_enemybase(30000);
1529 havocbot_goalrating_items(500, self.origin, rt_radius);
1530 navigation_goalrating_end();
1534 void havocbot_role_ctf_middle()
1538 if(self.deadflag != DEAD_NO)
1540 havocbot_ctf_reset_role(self);
1544 if (self.flagcarried)
1546 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1550 mf = havocbot_ctf_find_flag(self);
1551 if(mf.ctf_status!=FLAG_BASE)
1553 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1557 if (!self.havocbot_role_timeout)
1558 self.havocbot_role_timeout = time + 10;
1560 if (time > self.havocbot_role_timeout)
1562 havocbot_ctf_reset_role(self);
1566 if (self.bot_strategytime < time)
1570 org = havocbot_ctf_middlepoint;
1571 org_z = self.origin_z;
1573 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1574 navigation_goalrating_start();
1575 havocbot_goalrating_ctf_ourstolenflag(50000);
1576 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1577 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1578 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1579 havocbot_goalrating_items(2500, self.origin, 10000);
1580 havocbot_goalrating_ctf_enemybase(2500);
1581 navigation_goalrating_end();
1585 void havocbot_role_ctf_defense()
1589 if(self.deadflag != DEAD_NO)
1591 havocbot_ctf_reset_role(self);
1595 if (self.flagcarried)
1597 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1601 // If own flag was captured
1602 mf = havocbot_ctf_find_flag(self);
1603 if(mf.ctf_status!=FLAG_BASE)
1605 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1609 if (!self.havocbot_role_timeout)
1610 self.havocbot_role_timeout = time + 30;
1612 if (time > self.havocbot_role_timeout)
1614 havocbot_ctf_reset_role(self);
1617 if (self.bot_strategytime < time)
1622 org = mf.dropped_origin;
1623 mp_radius = havocbot_ctf_middlepoint_radius;
1625 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1626 navigation_goalrating_start();
1628 // if enemies are closer to our base, go there
1629 entity head, closestplayer = world;
1630 float distance, bestdistance = 10000;
1631 FOR_EACH_PLAYER(head)
1633 if(head.deadflag!=DEAD_NO)
1636 distance = vlen(org - head.origin);
1637 if(distance<bestdistance)
1639 closestplayer = head;
1640 bestdistance = distance;
1645 if(closestplayer.team!=self.team)
1646 if(vlen(org - self.origin)>1000)
1647 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1648 havocbot_goalrating_ctf_ourbase(30000);
1650 havocbot_goalrating_ctf_ourstolenflag(20000);
1651 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1652 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1653 havocbot_goalrating_items(10000, org, mp_radius);
1654 havocbot_goalrating_items(5000, self.origin, 10000);
1655 navigation_goalrating_end();
1659 void havocbot_role_ctf_setrole(entity bot, float role)
1661 dprint(strcat(bot.netname," switched to "));
1664 case HAVOCBOT_CTF_ROLE_CARRIER:
1666 bot.havocbot_role = havocbot_role_ctf_carrier;
1667 bot.havocbot_role_timeout = 0;
1668 bot.havocbot_cantfindflag = time + 10;
1669 bot.bot_strategytime = 0;
1671 case HAVOCBOT_CTF_ROLE_DEFENSE:
1673 bot.havocbot_role = havocbot_role_ctf_defense;
1674 bot.havocbot_role_timeout = 0;
1676 case HAVOCBOT_CTF_ROLE_MIDDLE:
1678 bot.havocbot_role = havocbot_role_ctf_middle;
1679 bot.havocbot_role_timeout = 0;
1681 case HAVOCBOT_CTF_ROLE_OFFENSE:
1683 bot.havocbot_role = havocbot_role_ctf_offense;
1684 bot.havocbot_role_timeout = 0;
1686 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1687 dprint("retriever");
1688 bot.havocbot_previous_role = bot.havocbot_role;
1689 bot.havocbot_role = havocbot_role_ctf_retriever;
1690 bot.havocbot_role_timeout = time + 10;
1691 bot.bot_strategytime = 0;
1693 case HAVOCBOT_CTF_ROLE_ESCORT:
1695 bot.havocbot_previous_role = bot.havocbot_role;
1696 bot.havocbot_role = havocbot_role_ctf_escort;
1697 bot.havocbot_role_timeout = time + 30;
1698 bot.bot_strategytime = 0;
1709 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1713 // initially clear items so they can be set as necessary later.
1714 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1715 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1717 // scan through all the flags and notify the client about them
1718 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1720 switch(flag.ctf_status)
1725 if((flag.owner == self) || (flag.pass_sender == self))
1726 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1728 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1733 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1739 // item for stopping players from capturing the flag too often
1740 if(self.ctf_captureshielded)
1741 self.items |= IT_CTF_SHIELDED;
1743 // update the health of the flag carrier waypointsprite
1744 if(self.wps_flagcarrier)
1745 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1750 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1752 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1754 if(frag_target == frag_attacker) // damage done to yourself
1756 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1757 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1759 else // damage done to everyone else
1761 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1762 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1765 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1767 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1768 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1770 frag_target.wps_helpme_time = time;
1771 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1773 // todo: add notification for when flag carrier needs help?
1778 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1780 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1782 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1783 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1786 if(frag_target.flagcarried)
1787 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1792 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1795 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1798 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1800 entity flag; // temporary entity for the search method
1802 if(self.flagcarried)
1803 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1805 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1807 if(flag.pass_sender == self) { flag.pass_sender = world; }
1808 if(flag.pass_target == self) { flag.pass_target = world; }
1809 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1815 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1817 if(self.flagcarried)
1818 if(!autocvar_g_ctf_portalteleport)
1819 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1824 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1826 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1828 entity player = self;
1830 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1832 // pass the flag to a team mate
1833 if(autocvar_g_ctf_pass)
1835 entity head, closest_target = world;
1836 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1838 while(head) // find the closest acceptable target to pass to
1840 if(head.classname == "player" && head.deadflag == DEAD_NO)
1841 if(head != player && !IsDifferentTeam(head, player))
1842 if(!head.speedrunning && !head.vehicle)
1844 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1845 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1846 vector passer_center = CENTER_OR_VIEWOFS(player);
1848 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1850 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1852 if(clienttype(head) == CLIENTTYPE_BOT)
1854 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1855 ctf_Handle_Throw(head, player, DROP_PASS);
1859 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1860 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1862 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1865 else if(player.flagcarried)
1869 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1870 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1871 { closest_target = head; }
1873 else { closest_target = head; }
1880 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1883 // throw the flag in front of you
1884 if(autocvar_g_ctf_throw && player.flagcarried)
1886 if(player.throw_count == -1)
1888 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1890 player.throw_prevtime = time;
1891 player.throw_count = 1;
1892 ctf_Handle_Throw(player, world, DROP_THROW);
1897 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1903 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1904 else { player.throw_count += 1; }
1905 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1907 player.throw_prevtime = time;
1908 ctf_Handle_Throw(player, world, DROP_THROW);
1917 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1919 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1921 self.wps_helpme_time = time;
1922 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1924 else // create a normal help me waypointsprite
1926 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');
1927 WaypointSprite_Ping(self.wps_helpme);
1933 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1935 if(vh_player.flagcarried)
1937 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1939 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1943 setattachment(vh_player.flagcarried, vh_vehicle, "");
1944 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1945 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1946 //vh_player.flagcarried.angles = '0 0 0';
1954 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1956 if(vh_player.flagcarried)
1958 setattachment(vh_player.flagcarried, vh_player, "");
1959 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1960 vh_player.flagcarried.scale = FLAG_SCALE;
1961 vh_player.flagcarried.angles = '0 0 0';
1968 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1970 if(self.flagcarried)
1972 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1973 ctf_RespawnFlag(self.flagcarried);
1980 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1982 entity flag; // temporary entity for the search method
1984 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1986 switch(flag.ctf_status)
1991 // lock the flag, game is over
1992 flag.movetype = MOVETYPE_NONE;
1993 flag.takedamage = DAMAGE_NO;
1994 flag.solid = SOLID_NOT;
1995 flag.nextthink = FALSE; // stop thinking
1997 //dprint("stopping the ", flag.netname, " from moving.\n");
2005 // do nothing for these flags
2014 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2016 havocbot_ctf_reset_role(self);
2020 MUTATOR_HOOKFUNCTION(ctf_GetCvars)
2022 GetCvars_handleFloat(get_cvars_s, get_cvars_f, CAPTURE_VERBOSE, "notification_ctf_capture_verbose");
2023 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_TEAM_VERBOSE, "notification_ctf_pickup_team_verbose");
2024 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_ENEMY_VERBOSE, "notification_ctf_pickup_enemy_verbose");
2033 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2034 CTF Starting point for a player in team one (Red).
2035 Keys: "angle" viewing angle when spawning. */
2036 void spawnfunc_info_player_team1()
2038 if(g_assault) { remove(self); return; }
2040 self.team = FL_TEAM_1; // red
2041 spawnfunc_info_player_deathmatch();
2045 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2046 CTF Starting point for a player in team two (Blue).
2047 Keys: "angle" viewing angle when spawning. */
2048 void spawnfunc_info_player_team2()
2050 if(g_assault) { remove(self); return; }
2052 self.team = FL_TEAM_2; // blue
2053 spawnfunc_info_player_deathmatch();
2056 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2057 CTF Starting point for a player in team three (Yellow).
2058 Keys: "angle" viewing angle when spawning. */
2059 void spawnfunc_info_player_team3()
2061 if(g_assault) { remove(self); return; }
2063 self.team = FL_TEAM_3; // yellow
2064 spawnfunc_info_player_deathmatch();
2068 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2069 CTF Starting point for a player in team four (Purple).
2070 Keys: "angle" viewing angle when spawning. */
2071 void spawnfunc_info_player_team4()
2073 if(g_assault) { remove(self); return; }
2075 self.team = FL_TEAM_4; // purple
2076 spawnfunc_info_player_deathmatch();
2079 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2080 CTF flag for team one (Red).
2082 "angle" Angle the flag will point (minus 90 degrees)...
2083 "model" model to use, note this needs red and blue as skins 0 and 1...
2084 "noise" sound played when flag is picked up...
2085 "noise1" sound played when flag is returned by a teammate...
2086 "noise2" sound played when flag is captured...
2087 "noise3" sound played when flag is lost in the field and respawns itself...
2088 "noise4" sound played when flag is dropped by a player...
2089 "noise5" sound played when flag touches the ground... */
2090 void spawnfunc_item_flag_team1()
2092 if(!g_ctf) { remove(self); return; }
2094 ctf_FlagSetup(1, self); // 1 = red
2097 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2098 CTF flag for team two (Blue).
2100 "angle" Angle the flag will point (minus 90 degrees)...
2101 "model" model to use, note this needs red and blue as skins 0 and 1...
2102 "noise" sound played when flag is picked up...
2103 "noise1" sound played when flag is returned by a teammate...
2104 "noise2" sound played when flag is captured...
2105 "noise3" sound played when flag is lost in the field and respawns itself...
2106 "noise4" sound played when flag is dropped by a player...
2107 "noise5" sound played when flag touches the ground... */
2108 void spawnfunc_item_flag_team2()
2110 if(!g_ctf) { remove(self); return; }
2112 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2115 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2116 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2117 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.
2119 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2120 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2121 void spawnfunc_ctf_team()
2123 if(!g_ctf) { remove(self); return; }
2125 self.classname = "ctf_team";
2126 self.team = self.cnt + 1;
2129 // compatibility for quake maps
2130 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2131 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2132 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2133 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2134 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2135 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2143 void ctf_ScoreRules()
2145 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2146 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2147 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2148 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2149 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2150 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2151 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2152 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2153 ScoreRules_basics_end();
2156 // code from here on is just to support maps that don't have flag and team entities
2157 void ctf_SpawnTeam (string teamname, float teamcolor)
2162 self.classname = "ctf_team";
2163 self.netname = teamname;
2164 self.cnt = teamcolor;
2166 spawnfunc_ctf_team();
2171 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2173 // if no teams are found, spawn defaults
2174 if(find(world, classname, "ctf_team") == world)
2176 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2177 ctf_SpawnTeam("Red", FL_TEAM_1 - 1);
2178 ctf_SpawnTeam("Blue", FL_TEAM_2 - 1);
2184 void ctf_Initialize()
2186 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2188 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2189 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2190 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2192 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2196 MUTATOR_DEFINITION(gamemode_ctf)
2198 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2199 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2200 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2201 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2202 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2203 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2204 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2205 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2206 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2207 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2208 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2209 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2210 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2211 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2212 MUTATOR_HOOK(GetCvars, ctf_GetCvars, CBC_ORDER_ANY);
2216 if(time > 1) // game loads at time 1
2217 error("This is a game type and it cannot be added at runtime.");
2221 MUTATOR_ONROLLBACK_OR_REMOVE
2223 // we actually cannot roll back ctf_Initialize here
2224 // BUT: we don't need to! If this gets called, adding always
2230 print("This is a game type and it cannot be removed at runtime.");