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 ctf_CaptureRecord(enemy_flag, player);
410 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
414 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
415 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
420 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
421 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
423 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
424 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
425 if(!old_time || new_time < old_time)
426 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
429 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
430 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
433 if(capturetype == CAPTURE_NORMAL)
435 WaypointSprite_Kill(player.wps_flagcarrier);
436 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
438 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
439 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
443 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
444 ctf_RespawnFlag(enemy_flag);
447 void ctf_Handle_Return(entity flag, entity player)
449 // messages and sounds
450 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
451 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
452 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
453 ctf_EventLog("return", flag.team, player);
456 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
457 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
459 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
463 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
464 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
465 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
469 ctf_RespawnFlag(flag);
472 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
475 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
476 float pickup_dropped_score; // used to calculate dropped pickup score
478 // attach the flag to the player
480 player.flagcarried = flag;
481 setattachment(flag, player, "");
482 setorigin(flag, FLAG_CARRY_OFFSET);
485 flag.movetype = MOVETYPE_NONE;
486 flag.takedamage = DAMAGE_NO;
487 flag.solid = SOLID_NOT;
488 flag.angles = '0 0 0';
489 flag.ctf_status = FLAG_CARRY;
493 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
494 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
498 // messages and sounds
499 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
500 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
502 FOR_EACH_REALPLAYER(tmp_player)
504 if(tmp_player == player)
506 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
507 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
509 else if(!IsDifferentTeam(tmp_player, player) && tmp_player != player)
511 if(tmp_player.PICKUP_TEAM_VERBOSE)
512 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_TEAM_VERBOSE, Team_ColorCode(player.team), player.netname);
514 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_TEAM, Team_ColorCode(player.team));
516 else if(IsDifferentTeam(tmp_player, player))
518 if(tmp_player.PICKUP_ENEMY_VERBOSE)
519 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY_VERBOSE, Team_ColorCode(player.team), player.netname);
521 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY, Team_ColorCode(player.team));
526 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
531 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
532 ctf_EventLog("steal", flag.team, player);
538 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);
539 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);
540 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
541 PlayerTeamScore_AddScore(player, pickup_dropped_score);
542 ctf_EventLog("pickup", flag.team, player);
550 if(pickuptype == PICKUP_BASE)
552 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
553 if((player.speedrunning) && (ctf_captimerecord))
554 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
558 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
561 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
562 ctf_FlagcarrierWaypoints(player);
563 WaypointSprite_Ping(player.wps_flagcarrier);
567 // ===================
568 // Main Flag Functions
569 // ===================
571 void ctf_CheckFlagReturn(entity flag, float returntype)
573 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
575 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
577 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
581 case RETURN_DROPPED: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
582 case RETURN_DAMAGE: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
583 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
584 case RETURN_NEEDKILL: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
588 { Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
590 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
591 ctf_EventLog("returned", flag.team, world);
592 ctf_RespawnFlag(flag);
597 void ctf_CheckStalemate(void)
600 float stale_red_flags = 0, stale_blue_flags = 0;
603 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
605 // build list of stale flags
606 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
608 if(autocvar_g_ctf_stalemate)
609 if(tmp_entity.ctf_status != FLAG_BASE)
610 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
612 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
613 ctf_staleflaglist = tmp_entity;
615 switch(tmp_entity.team)
617 case FL_TEAM_1: ++stale_red_flags; break;
618 case FL_TEAM_2: ++stale_blue_flags; break;
623 if(stale_red_flags && stale_blue_flags)
624 ctf_stalemate = TRUE;
625 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
626 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
627 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
628 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
630 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
633 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
635 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
636 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));
639 if not(wpforenemy_announced)
641 FOR_EACH_REALPLAYER(tmp_entity)
642 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
644 wpforenemy_announced = TRUE;
649 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
651 if(ITEM_DAMAGE_NEEDKILL(deathtype))
653 // automatically kill the flag and return it
655 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
658 if(autocvar_g_ctf_flag_return_damage)
660 // reduce health and check if it should be returned
661 self.health = self.health - damage;
662 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
672 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
675 if(self == ctf_worldflaglist) // only for the first flag
676 FOR_EACH_CLIENT(tmp_entity)
677 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
680 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
681 dprint("wtf the flag got squashed?\n");
682 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
683 if(!trace_startsolid) // can we resize it without getting stuck?
684 setsize(self, FLAG_MIN, FLAG_MAX); }
686 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
690 self.angles = '0 0 0';
698 switch(self.ctf_status)
702 if(autocvar_g_ctf_dropped_capture_radius)
704 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
705 if(tmp_entity.ctf_status == FLAG_DROPPED)
706 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
707 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
708 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
715 if(autocvar_g_ctf_flag_dropped_floatinwater)
717 vector midpoint = ((self.absmin + self.absmax) * 0.5);
718 if(pointcontents(midpoint) == CONTENT_WATER)
720 self.velocity = self.velocity * 0.5;
722 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
723 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
725 { self.movetype = MOVETYPE_FLY; }
727 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
729 if(autocvar_g_ctf_flag_return_dropped)
731 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
734 ctf_CheckFlagReturn(self, RETURN_DROPPED);
738 if(autocvar_g_ctf_flag_return_time)
740 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
741 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
749 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
752 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
756 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
760 if(autocvar_g_ctf_stalemate)
762 if(time >= wpforenemy_nextthink)
764 ctf_CheckStalemate();
765 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
773 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
774 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
775 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
777 if((self.pass_target == world)
778 || (self.pass_target.deadflag != DEAD_NO)
779 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
780 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
781 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
783 // give up, pass failed
784 ctf_Handle_Drop(self, world, DROP_PASS);
788 // still a viable target, go for it
789 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
794 default: // this should never happen
796 dprint("ctf_FlagThink(): Flag exists with no status?\n");
804 if(gameover) { return; }
806 entity toucher = other;
808 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
809 if(ITEM_TOUCH_NEEDKILL())
812 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
816 // special touch behaviors
817 if(toucher.vehicle_flags & VHF_ISVEHICLE)
819 if(autocvar_g_ctf_allow_vehicle_touch)
820 toucher = toucher.owner; // the player is actually the vehicle owner, not other
822 return; // do nothing
824 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
826 if(time > self.wait) // if we haven't in a while, play a sound/effect
828 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
829 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
830 self.wait = time + FLAG_TOUCHRATE;
834 else if(toucher.deadflag != DEAD_NO) { return; }
836 switch(self.ctf_status)
840 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
841 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
842 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
843 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
849 if(!IsDifferentTeam(toucher, self))
850 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
851 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
852 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
858 dprint("Someone touched a flag even though it was being carried?\n");
864 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
866 if(IsDifferentTeam(toucher, self.pass_sender))
867 ctf_Handle_Return(self, toucher);
869 ctf_Handle_Retrieve(self, toucher);
877 void ctf_RespawnFlag(entity flag)
879 // check for flag respawn being called twice in a row
880 if(flag.last_respawn > time - 0.5)
881 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
883 flag.last_respawn = time;
885 // reset the player (if there is one)
886 if((flag.owner) && (flag.owner.flagcarried == flag))
888 if(flag.owner.wps_enemyflagcarrier)
889 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
891 WaypointSprite_Kill(flag.wps_flagcarrier);
893 flag.owner.flagcarried = world;
895 if(flag.speedrunning)
896 ctf_FakeTimeLimit(flag.owner, -1);
899 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
900 { WaypointSprite_Kill(flag.wps_flagdropped); }
903 setattachment(flag, world, "");
904 setorigin(flag, flag.ctf_spawnorigin);
906 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
907 flag.takedamage = DAMAGE_NO;
908 flag.health = flag.max_flag_health;
909 flag.solid = SOLID_TRIGGER;
910 flag.velocity = '0 0 0';
911 flag.angles = flag.mangle;
912 flag.flags = FL_ITEM | FL_NOTARGET;
914 flag.ctf_status = FLAG_BASE;
916 flag.pass_distance = 0;
917 flag.pass_sender = world;
918 flag.pass_target = world;
919 flag.ctf_dropper = world;
920 flag.ctf_pickuptime = 0;
921 flag.ctf_droptime = 0;
927 if(self.owner.classname == "player")
928 ctf_Handle_Throw(self.owner, world, DROP_RESET);
930 ctf_RespawnFlag(self);
933 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
936 waypoint_spawnforitem_force(self, self.origin);
937 self.nearestwaypointtimeout = 0; // activate waypointing again
938 self.bot_basewaypoint = self.nearestwaypoint;
941 WaypointSprite_SpawnFixed(((self.team == FL_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
942 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
944 // captureshield setup
945 ctf_CaptureShield_Spawn(self);
948 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
951 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.
952 self = flag; // for later usage with droptofloor()
955 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
956 ctf_worldflaglist = flag;
958 setattachment(flag, world, "");
960 flag.netname = ((teamnumber) ? "^1REPLACETHIS^7" : "^4REPLACETHIS^7"); // ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
961 flag.team = ((teamnumber) ? FL_TEAM_1 : FL_TEAM_2); // FL_TEAM_1: color 4 team (red) - FL_TEAM_2: color 13 team (blue)
962 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
963 flag.classname = "item_flag_team";
964 flag.target = "###item###"; // wut?
965 flag.flags = FL_ITEM | FL_NOTARGET;
966 flag.solid = SOLID_TRIGGER;
967 flag.takedamage = DAMAGE_NO;
968 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
969 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
970 flag.health = flag.max_flag_health;
971 flag.event_damage = ctf_FlagDamage;
972 flag.pushable = TRUE;
973 flag.teleportable = TELEPORT_NORMAL;
974 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
975 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
976 flag.velocity = '0 0 0';
977 flag.mangle = flag.angles;
978 flag.reset = ctf_Reset;
979 flag.touch = ctf_FlagTouch;
980 flag.think = ctf_FlagThink;
981 flag.nextthink = time + FLAG_THINKRATE;
982 flag.ctf_status = FLAG_BASE;
984 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
985 if(!flag.scale) { flag.scale = FLAG_SCALE; }
986 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
987 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
988 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
989 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
992 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
993 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
994 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
995 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.
996 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
997 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
998 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1001 precache_sound(flag.snd_flag_taken);
1002 precache_sound(flag.snd_flag_returned);
1003 precache_sound(flag.snd_flag_capture);
1004 precache_sound(flag.snd_flag_respawn);
1005 precache_sound(flag.snd_flag_dropped);
1006 precache_sound(flag.snd_flag_touch);
1007 precache_sound(flag.snd_flag_pass);
1008 precache_model(flag.model);
1009 precache_model("models/ctf/shield.md3");
1010 precache_model("models/ctf/shockwavetransring.md3");
1013 setmodel(flag, flag.model); // precision set below
1014 setsize(flag, FLAG_MIN, FLAG_MAX);
1015 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1017 if(autocvar_g_ctf_flag_glowtrails)
1019 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1020 flag.glow_size = 25;
1021 flag.glow_trail = 1;
1024 flag.effects |= EF_LOWPRECISION;
1025 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1026 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1029 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1031 flag.dropped_origin = flag.origin;
1032 flag.noalign = TRUE;
1033 flag.movetype = MOVETYPE_NONE;
1035 else // drop to floor, automatically find a platform and set that as spawn origin
1037 flag.noalign = FALSE;
1040 flag.movetype = MOVETYPE_TOSS;
1043 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1051 // NOTE: LEGACY CODE, needs to be re-written!
1053 void havocbot_calculate_middlepoint()
1057 vector fo = '0 0 0';
1060 f = ctf_worldflaglist;
1065 f = f.ctf_worldflagnext;
1069 havocbot_ctf_middlepoint = s * (1.0 / n);
1070 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1074 entity havocbot_ctf_find_flag(entity bot)
1077 f = ctf_worldflaglist;
1080 if (bot.team == f.team)
1082 f = f.ctf_worldflagnext;
1087 entity havocbot_ctf_find_enemy_flag(entity bot)
1090 f = ctf_worldflaglist;
1093 if (bot.team != f.team)
1095 f = f.ctf_worldflagnext;
1100 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1108 FOR_EACH_PLAYER(head)
1110 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1113 if(vlen(head.origin - org) < tc_radius)
1120 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1123 head = ctf_worldflaglist;
1126 if (self.team == head.team)
1128 head = head.ctf_worldflagnext;
1131 navigation_routerating(head, ratingscale, 10000);
1134 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1137 head = ctf_worldflaglist;
1140 if (self.team == head.team)
1142 head = head.ctf_worldflagnext;
1147 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1150 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1153 head = ctf_worldflaglist;
1156 if (self.team != head.team)
1158 head = head.ctf_worldflagnext;
1161 navigation_routerating(head, ratingscale, 10000);
1164 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1166 if not(bot_waypoints_for_items)
1168 havocbot_goalrating_ctf_enemyflag(ratingscale);
1174 head = havocbot_ctf_find_enemy_flag(self);
1179 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1182 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1186 mf = havocbot_ctf_find_flag(self);
1188 if(mf.ctf_status == FLAG_BASE)
1192 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1195 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1198 head = ctf_worldflaglist;
1201 // flag is out in the field
1202 if(head.ctf_status != FLAG_BASE)
1203 if(head.tag_entity==world) // dropped
1207 if(vlen(org-head.origin)<df_radius)
1208 navigation_routerating(head, ratingscale, 10000);
1211 navigation_routerating(head, ratingscale, 10000);
1214 head = head.ctf_worldflagnext;
1218 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1222 head = findchainfloat(bot_pickup, TRUE);
1225 // gather health and armor only
1227 if (head.health || head.armorvalue)
1228 if (vlen(head.origin - org) < sradius)
1230 // get the value of the item
1231 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1233 navigation_routerating(head, t * ratingscale, 500);
1239 void havocbot_ctf_reset_role(entity bot)
1241 float cdefense, cmiddle, coffense;
1242 entity mf, ef, head;
1245 if(bot.deadflag != DEAD_NO)
1248 if(vlen(havocbot_ctf_middlepoint)==0)
1249 havocbot_calculate_middlepoint();
1252 if (bot.flagcarried)
1254 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1258 mf = havocbot_ctf_find_flag(bot);
1259 ef = havocbot_ctf_find_enemy_flag(bot);
1261 // Retrieve stolen flag
1262 if(mf.ctf_status!=FLAG_BASE)
1264 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1268 // If enemy flag is taken go to the middle to intercept pursuers
1269 if(ef.ctf_status!=FLAG_BASE)
1271 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1275 // if there is only me on the team switch to offense
1277 FOR_EACH_PLAYER(head)
1278 if(head.team==bot.team)
1283 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1287 // Evaluate best position to take
1288 // Count mates on middle position
1289 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1291 // Count mates on defense position
1292 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1294 // Count mates on offense position
1295 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1297 if(cdefense<=coffense)
1298 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1299 else if(coffense<=cmiddle)
1300 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1302 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1305 void havocbot_role_ctf_carrier()
1307 if(self.deadflag != DEAD_NO)
1309 havocbot_ctf_reset_role(self);
1313 if (self.flagcarried == world)
1315 havocbot_ctf_reset_role(self);
1319 if (self.bot_strategytime < time)
1321 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1323 navigation_goalrating_start();
1324 havocbot_goalrating_ctf_ourbase(50000);
1327 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1329 navigation_goalrating_end();
1331 if (self.navigation_hasgoals)
1332 self.havocbot_cantfindflag = time + 10;
1333 else if (time > self.havocbot_cantfindflag)
1335 // Can't navigate to my own base, suicide!
1336 // TODO: drop it and wander around
1337 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1343 void havocbot_role_ctf_escort()
1347 if(self.deadflag != DEAD_NO)
1349 havocbot_ctf_reset_role(self);
1353 if (self.flagcarried)
1355 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1359 // If enemy flag is back on the base switch to previous role
1360 ef = havocbot_ctf_find_enemy_flag(self);
1361 if(ef.ctf_status==FLAG_BASE)
1363 self.havocbot_role = self.havocbot_previous_role;
1364 self.havocbot_role_timeout = 0;
1368 // If the flag carrier reached the base switch to defense
1369 mf = havocbot_ctf_find_flag(self);
1370 if(mf.ctf_status!=FLAG_BASE)
1371 if(vlen(ef.origin - mf.dropped_origin) < 300)
1373 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1377 // Set the role timeout if necessary
1378 if (!self.havocbot_role_timeout)
1380 self.havocbot_role_timeout = time + random() * 30 + 60;
1383 // If nothing happened just switch to previous role
1384 if (time > self.havocbot_role_timeout)
1386 self.havocbot_role = self.havocbot_previous_role;
1387 self.havocbot_role_timeout = 0;
1391 // Chase the flag carrier
1392 if (self.bot_strategytime < time)
1394 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1395 navigation_goalrating_start();
1396 havocbot_goalrating_ctf_enemyflag(30000);
1397 havocbot_goalrating_ctf_ourstolenflag(40000);
1398 havocbot_goalrating_items(10000, self.origin, 10000);
1399 navigation_goalrating_end();
1403 void havocbot_role_ctf_offense()
1408 if(self.deadflag != DEAD_NO)
1410 havocbot_ctf_reset_role(self);
1414 if (self.flagcarried)
1416 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1421 mf = havocbot_ctf_find_flag(self);
1422 ef = havocbot_ctf_find_enemy_flag(self);
1425 if(mf.ctf_status!=FLAG_BASE)
1428 pos = mf.tag_entity.origin;
1432 // Try to get it if closer than the enemy base
1433 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1435 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1440 // Escort flag carrier
1441 if(ef.ctf_status!=FLAG_BASE)
1444 pos = ef.tag_entity.origin;
1448 if(vlen(pos-mf.dropped_origin)>700)
1450 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1455 // About to fail, switch to middlefield
1458 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1462 // Set the role timeout if necessary
1463 if (!self.havocbot_role_timeout)
1464 self.havocbot_role_timeout = time + 120;
1466 if (time > self.havocbot_role_timeout)
1468 havocbot_ctf_reset_role(self);
1472 if (self.bot_strategytime < time)
1474 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1475 navigation_goalrating_start();
1476 havocbot_goalrating_ctf_ourstolenflag(50000);
1477 havocbot_goalrating_ctf_enemybase(20000);
1478 havocbot_goalrating_items(5000, self.origin, 1000);
1479 havocbot_goalrating_items(1000, self.origin, 10000);
1480 navigation_goalrating_end();
1484 // Retriever (temporary role):
1485 void havocbot_role_ctf_retriever()
1489 if(self.deadflag != DEAD_NO)
1491 havocbot_ctf_reset_role(self);
1495 if (self.flagcarried)
1497 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1501 // If flag is back on the base switch to previous role
1502 mf = havocbot_ctf_find_flag(self);
1503 if(mf.ctf_status==FLAG_BASE)
1505 havocbot_ctf_reset_role(self);
1509 if (!self.havocbot_role_timeout)
1510 self.havocbot_role_timeout = time + 20;
1512 if (time > self.havocbot_role_timeout)
1514 havocbot_ctf_reset_role(self);
1518 if (self.bot_strategytime < time)
1523 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1524 navigation_goalrating_start();
1525 havocbot_goalrating_ctf_ourstolenflag(50000);
1526 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1527 havocbot_goalrating_ctf_enemybase(30000);
1528 havocbot_goalrating_items(500, self.origin, rt_radius);
1529 navigation_goalrating_end();
1533 void havocbot_role_ctf_middle()
1537 if(self.deadflag != DEAD_NO)
1539 havocbot_ctf_reset_role(self);
1543 if (self.flagcarried)
1545 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1549 mf = havocbot_ctf_find_flag(self);
1550 if(mf.ctf_status!=FLAG_BASE)
1552 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1556 if (!self.havocbot_role_timeout)
1557 self.havocbot_role_timeout = time + 10;
1559 if (time > self.havocbot_role_timeout)
1561 havocbot_ctf_reset_role(self);
1565 if (self.bot_strategytime < time)
1569 org = havocbot_ctf_middlepoint;
1570 org_z = self.origin_z;
1572 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1573 navigation_goalrating_start();
1574 havocbot_goalrating_ctf_ourstolenflag(50000);
1575 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1576 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1577 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1578 havocbot_goalrating_items(2500, self.origin, 10000);
1579 havocbot_goalrating_ctf_enemybase(2500);
1580 navigation_goalrating_end();
1584 void havocbot_role_ctf_defense()
1588 if(self.deadflag != DEAD_NO)
1590 havocbot_ctf_reset_role(self);
1594 if (self.flagcarried)
1596 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1600 // If own flag was captured
1601 mf = havocbot_ctf_find_flag(self);
1602 if(mf.ctf_status!=FLAG_BASE)
1604 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1608 if (!self.havocbot_role_timeout)
1609 self.havocbot_role_timeout = time + 30;
1611 if (time > self.havocbot_role_timeout)
1613 havocbot_ctf_reset_role(self);
1616 if (self.bot_strategytime < time)
1621 org = mf.dropped_origin;
1622 mp_radius = havocbot_ctf_middlepoint_radius;
1624 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1625 navigation_goalrating_start();
1627 // if enemies are closer to our base, go there
1628 entity head, closestplayer = world;
1629 float distance, bestdistance = 10000;
1630 FOR_EACH_PLAYER(head)
1632 if(head.deadflag!=DEAD_NO)
1635 distance = vlen(org - head.origin);
1636 if(distance<bestdistance)
1638 closestplayer = head;
1639 bestdistance = distance;
1644 if(closestplayer.team!=self.team)
1645 if(vlen(org - self.origin)>1000)
1646 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1647 havocbot_goalrating_ctf_ourbase(30000);
1649 havocbot_goalrating_ctf_ourstolenflag(20000);
1650 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1651 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1652 havocbot_goalrating_items(10000, org, mp_radius);
1653 havocbot_goalrating_items(5000, self.origin, 10000);
1654 navigation_goalrating_end();
1658 void havocbot_role_ctf_setrole(entity bot, float role)
1660 dprint(strcat(bot.netname," switched to "));
1663 case HAVOCBOT_CTF_ROLE_CARRIER:
1665 bot.havocbot_role = havocbot_role_ctf_carrier;
1666 bot.havocbot_role_timeout = 0;
1667 bot.havocbot_cantfindflag = time + 10;
1668 bot.bot_strategytime = 0;
1670 case HAVOCBOT_CTF_ROLE_DEFENSE:
1672 bot.havocbot_role = havocbot_role_ctf_defense;
1673 bot.havocbot_role_timeout = 0;
1675 case HAVOCBOT_CTF_ROLE_MIDDLE:
1677 bot.havocbot_role = havocbot_role_ctf_middle;
1678 bot.havocbot_role_timeout = 0;
1680 case HAVOCBOT_CTF_ROLE_OFFENSE:
1682 bot.havocbot_role = havocbot_role_ctf_offense;
1683 bot.havocbot_role_timeout = 0;
1685 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1686 dprint("retriever");
1687 bot.havocbot_previous_role = bot.havocbot_role;
1688 bot.havocbot_role = havocbot_role_ctf_retriever;
1689 bot.havocbot_role_timeout = time + 10;
1690 bot.bot_strategytime = 0;
1692 case HAVOCBOT_CTF_ROLE_ESCORT:
1694 bot.havocbot_previous_role = bot.havocbot_role;
1695 bot.havocbot_role = havocbot_role_ctf_escort;
1696 bot.havocbot_role_timeout = time + 30;
1697 bot.bot_strategytime = 0;
1708 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1712 // initially clear items so they can be set as necessary later.
1713 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1714 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1716 // scan through all the flags and notify the client about them
1717 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1719 switch(flag.ctf_status)
1724 if((flag.owner == self) || (flag.pass_sender == self))
1725 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1727 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1732 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1738 // item for stopping players from capturing the flag too often
1739 if(self.ctf_captureshielded)
1740 self.items |= IT_CTF_SHIELDED;
1742 // update the health of the flag carrier waypointsprite
1743 if(self.wps_flagcarrier)
1744 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1749 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1751 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1753 if(frag_target == frag_attacker) // damage done to yourself
1755 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1756 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1758 else // damage done to everyone else
1760 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1761 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1764 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1766 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1767 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1769 frag_target.wps_helpme_time = time;
1770 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1772 // todo: add notification for when flag carrier needs help?
1777 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1779 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1781 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1782 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1785 if(frag_target.flagcarried)
1786 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1791 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1794 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1797 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1799 entity flag; // temporary entity for the search method
1801 if(self.flagcarried)
1802 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1804 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1806 if(flag.pass_sender == self) { flag.pass_sender = world; }
1807 if(flag.pass_target == self) { flag.pass_target = world; }
1808 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1814 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1816 if(self.flagcarried)
1817 if(!autocvar_g_ctf_portalteleport)
1818 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1823 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1825 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1827 entity player = self;
1829 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1831 // pass the flag to a team mate
1832 if(autocvar_g_ctf_pass)
1834 entity head, closest_target = world;
1835 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1837 while(head) // find the closest acceptable target to pass to
1839 if(head.classname == "player" && head.deadflag == DEAD_NO)
1840 if(head != player && !IsDifferentTeam(head, player))
1841 if(!head.speedrunning && !head.vehicle)
1843 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1844 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1845 vector passer_center = CENTER_OR_VIEWOFS(player);
1847 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1849 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1851 if(clienttype(head) == CLIENTTYPE_BOT)
1853 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1854 ctf_Handle_Throw(head, player, DROP_PASS);
1858 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1859 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1861 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1864 else if(player.flagcarried)
1868 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1869 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1870 { closest_target = head; }
1872 else { closest_target = head; }
1879 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1882 // throw the flag in front of you
1883 if(autocvar_g_ctf_throw && player.flagcarried)
1885 if(player.throw_count == -1)
1887 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1889 player.throw_prevtime = time;
1890 player.throw_count = 1;
1891 ctf_Handle_Throw(player, world, DROP_THROW);
1896 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1902 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1903 else { player.throw_count += 1; }
1904 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1906 player.throw_prevtime = time;
1907 ctf_Handle_Throw(player, world, DROP_THROW);
1916 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1918 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1920 self.wps_helpme_time = time;
1921 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1923 else // create a normal help me waypointsprite
1925 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');
1926 WaypointSprite_Ping(self.wps_helpme);
1932 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1934 if(vh_player.flagcarried)
1936 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1938 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1942 setattachment(vh_player.flagcarried, vh_vehicle, "");
1943 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1944 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1945 //vh_player.flagcarried.angles = '0 0 0';
1953 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1955 if(vh_player.flagcarried)
1957 setattachment(vh_player.flagcarried, vh_player, "");
1958 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1959 vh_player.flagcarried.scale = FLAG_SCALE;
1960 vh_player.flagcarried.angles = '0 0 0';
1967 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1969 if(self.flagcarried)
1971 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1972 ctf_RespawnFlag(self.flagcarried);
1979 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1981 entity flag; // temporary entity for the search method
1983 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1985 switch(flag.ctf_status)
1990 // lock the flag, game is over
1991 flag.movetype = MOVETYPE_NONE;
1992 flag.takedamage = DAMAGE_NO;
1993 flag.solid = SOLID_NOT;
1994 flag.nextthink = FALSE; // stop thinking
1996 //dprint("stopping the ", flag.netname, " from moving.\n");
2004 // do nothing for these flags
2013 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2015 havocbot_ctf_reset_role(self);
2019 MUTATOR_HOOKFUNCTION(ctf_GetCvars)
2021 GetCvars_handleFloat(get_cvars_s, get_cvars_f, CAPTURE_VERBOSE, "notification_ctf_capture_verbose");
2022 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_TEAM_VERBOSE, "notification_ctf_pickup_team_verbose");
2023 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_ENEMY_VERBOSE, "notification_ctf_pickup_enemy_verbose");
2032 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2033 CTF Starting point for a player in team one (Red).
2034 Keys: "angle" viewing angle when spawning. */
2035 void spawnfunc_info_player_team1()
2037 if(g_assault) { remove(self); return; }
2039 self.team = FL_TEAM_1; // red
2040 spawnfunc_info_player_deathmatch();
2044 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2045 CTF Starting point for a player in team two (Blue).
2046 Keys: "angle" viewing angle when spawning. */
2047 void spawnfunc_info_player_team2()
2049 if(g_assault) { remove(self); return; }
2051 self.team = FL_TEAM_2; // blue
2052 spawnfunc_info_player_deathmatch();
2055 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2056 CTF Starting point for a player in team three (Yellow).
2057 Keys: "angle" viewing angle when spawning. */
2058 void spawnfunc_info_player_team3()
2060 if(g_assault) { remove(self); return; }
2062 self.team = FL_TEAM_3; // yellow
2063 spawnfunc_info_player_deathmatch();
2067 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2068 CTF Starting point for a player in team four (Purple).
2069 Keys: "angle" viewing angle when spawning. */
2070 void spawnfunc_info_player_team4()
2072 if(g_assault) { remove(self); return; }
2074 self.team = FL_TEAM_4; // purple
2075 spawnfunc_info_player_deathmatch();
2078 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2079 CTF flag for team one (Red).
2081 "angle" Angle the flag will point (minus 90 degrees)...
2082 "model" model to use, note this needs red and blue as skins 0 and 1...
2083 "noise" sound played when flag is picked up...
2084 "noise1" sound played when flag is returned by a teammate...
2085 "noise2" sound played when flag is captured...
2086 "noise3" sound played when flag is lost in the field and respawns itself...
2087 "noise4" sound played when flag is dropped by a player...
2088 "noise5" sound played when flag touches the ground... */
2089 void spawnfunc_item_flag_team1()
2091 if(!g_ctf) { remove(self); return; }
2093 ctf_FlagSetup(1, self); // 1 = red
2096 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2097 CTF flag for team two (Blue).
2099 "angle" Angle the flag will point (minus 90 degrees)...
2100 "model" model to use, note this needs red and blue as skins 0 and 1...
2101 "noise" sound played when flag is picked up...
2102 "noise1" sound played when flag is returned by a teammate...
2103 "noise2" sound played when flag is captured...
2104 "noise3" sound played when flag is lost in the field and respawns itself...
2105 "noise4" sound played when flag is dropped by a player...
2106 "noise5" sound played when flag touches the ground... */
2107 void spawnfunc_item_flag_team2()
2109 if(!g_ctf) { remove(self); return; }
2111 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2114 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2115 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2116 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.
2118 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2119 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2120 void spawnfunc_ctf_team()
2122 if(!g_ctf) { remove(self); return; }
2124 self.classname = "ctf_team";
2125 self.team = self.cnt + 1;
2128 // compatibility for quake maps
2129 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2130 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2131 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2132 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2133 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2134 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2142 void ctf_ScoreRules()
2144 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2145 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2146 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2147 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2148 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2149 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2150 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2151 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2152 ScoreRules_basics_end();
2155 // code from here on is just to support maps that don't have flag and team entities
2156 void ctf_SpawnTeam (string teamname, float teamcolor)
2161 self.classname = "ctf_team";
2162 self.netname = teamname;
2163 self.cnt = teamcolor;
2165 spawnfunc_ctf_team();
2170 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2172 // if no teams are found, spawn defaults
2173 if(find(world, classname, "ctf_team") == world)
2175 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2176 ctf_SpawnTeam("Red", FL_TEAM_1 - 1);
2177 ctf_SpawnTeam("Blue", FL_TEAM_2 - 1);
2183 void ctf_Initialize()
2185 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2187 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2188 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2189 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2191 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2195 MUTATOR_DEFINITION(gamemode_ctf)
2197 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2198 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2199 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2200 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2201 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2202 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2203 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2204 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2205 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2206 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2207 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2208 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2209 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2210 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2211 MUTATOR_HOOK(GetCvars, ctf_GetCvars, CBC_ORDER_ANY);
2215 if(time > 1) // game loads at time 1
2216 error("This is a game type and it cannot be added at runtime.");
2220 MUTATOR_ONROLLBACK_OR_REMOVE
2222 // we actually cannot roll back ctf_Initialize here
2223 // BUT: we don't need to! If this gets called, adding always
2229 print("This is a game type and it cannot be removed at runtime.");