1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: September, 2012
4 // ================================================================
6 void ctf_FakeTimeLimit(entity e, float t)
9 WriteByte(MSG_ONE, 3); // svc_updatestat
10 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
12 WriteCoord(MSG_ONE, autocvar_timelimit);
14 WriteCoord(MSG_ONE, (t + 1) / 60);
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
23 void ctf_CaptureRecord(entity flag, entity player)
26 float notification, success;
27 float cap_record = ctf_captimerecord;
28 float cap_time = (time - flag.ctf_pickuptime);
31 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
34 if(!ctf_captimerecord)
35 { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_); s1 = player.netname; f1 = (cap_time * 100); success = TRUE; }
36 else if(cap_time < cap_record)
37 { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = TRUE; }
39 { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = FALSE; }
42 FOR_EACH_REALCLIENT(tmp_entity)
44 if not(tmp_entity.CAPTURE_VERBOSE) { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_); s2 = ""; f1 = f2 = 0; }
45 Send_Notification_Legacy_Wrapper(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, notification, s1, s2, f1, f2, 0);
48 // notify server log too
49 if not(autocvar_notification_ctf_capture_verbose) { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_); s2 = ""; f1 = f2 = 0; }
50 Local_Notification_Without_VarArgs(MSG_INFO, notification, Get_Notif_Strnum(MSG_INFO, notification), Get_Notif_Flnum(MSG_INFO, notification), s1, s2, "", "", f1, f2, 0, 0);
52 // write that shit in the database
55 ctf_captimerecord = cap_time;
56 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
57 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
58 write_recordmarker(player, (time - cap_time), cap_time);
62 void ctf_FlagcarrierWaypoints(entity player)
64 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
65 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
66 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
67 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
70 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
72 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
73 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
74 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
75 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
78 if(current_height) // make sure we can actually do this arcing path
80 targpos = (to + ('0 0 1' * current_height));
81 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
82 if(trace_fraction < 1)
84 //print("normal arc line failed, trying to find new pos...");
85 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
86 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
87 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
88 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
89 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
92 else { targpos = to; }
94 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
96 vector desired_direction = normalize(targpos - from);
97 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
98 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
101 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
103 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
105 // directional tracing only
107 makevectors(passer_angle);
109 // find the closest point on the enemy to the center of the attack
110 float ang; // angle between shotdir and h
111 float h; // hypotenuse, which is the distance between attacker to head
112 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
114 h = vlen(head_center - passer_center);
115 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
118 vector nearest_on_line = (passer_center + a * v_forward);
119 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
121 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
122 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
124 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
129 else { return TRUE; }
133 // =======================
134 // CaptureShield Functions
135 // =======================
137 float ctf_CaptureShield_CheckStatus(entity p)
141 float players_worseeq, players_total;
143 if(ctf_captureshield_max_ratio <= 0)
146 s = PlayerScore_Add(p, SP_SCORE, 0);
147 if(s >= -ctf_captureshield_min_negscore)
150 players_total = players_worseeq = 0;
153 if(IsDifferentTeam(e, p))
155 se = PlayerScore_Add(e, SP_SCORE, 0);
161 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
162 // use this rule here
164 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
170 void ctf_CaptureShield_Update(entity player, float wanted_status)
172 float updated_status = ctf_CaptureShield_CheckStatus(player);
173 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
175 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
176 player.ctf_captureshielded = updated_status;
180 float ctf_CaptureShield_Customize()
182 if(!other.ctf_captureshielded) { return FALSE; }
183 if(!IsDifferentTeam(self, other)) { return FALSE; }
188 void ctf_CaptureShield_Touch()
190 if(!other.ctf_captureshielded) { return; }
191 if(!IsDifferentTeam(self, other)) { return; }
193 vector mymid = (self.absmin + self.absmax) * 0.5;
194 vector othermid = (other.absmin + other.absmax) * 0.5;
196 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
197 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED);
200 void ctf_CaptureShield_Spawn(entity flag)
202 entity shield = spawn();
205 shield.team = self.team;
206 shield.touch = ctf_CaptureShield_Touch;
207 shield.customizeentityforclient = ctf_CaptureShield_Customize;
208 shield.classname = "ctf_captureshield";
209 shield.effects = EF_ADDITIVE;
210 shield.movetype = MOVETYPE_NOCLIP;
211 shield.solid = SOLID_TRIGGER;
212 shield.avelocity = '7 0 11';
215 setorigin(shield, self.origin);
216 setmodel(shield, "models/ctf/shield.md3");
217 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
221 // ====================
222 // Drop/Pass/Throw Code
223 // ====================
225 void ctf_Handle_Drop(entity flag, entity player, float droptype)
228 player = (player ? player : flag.pass_sender);
231 flag.movetype = MOVETYPE_TOSS;
232 flag.takedamage = DAMAGE_YES;
233 flag.angles = '0 0 0';
234 flag.health = flag.max_flag_health;
235 flag.ctf_droptime = time;
236 flag.ctf_dropper = player;
237 flag.ctf_status = FLAG_DROPPED;
239 // messages and sounds
240 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
241 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
242 ctf_EventLog("dropped", player.team, player);
245 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
246 PlayerScore_Add(player, SP_CTF_DROPS, 1);
249 if(autocvar_g_ctf_flag_dropped_waypoint)
250 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
252 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
254 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
255 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
258 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
260 if(droptype == DROP_PASS)
262 flag.pass_distance = 0;
263 flag.pass_sender = world;
264 flag.pass_target = world;
268 void ctf_Handle_Retrieve(entity flag, entity player)
270 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
271 entity sender = flag.pass_sender;
273 // transfer flag to player
275 flag.owner.flagcarried = flag;
278 setattachment(flag, player, "");
279 setorigin(flag, FLAG_CARRY_OFFSET);
280 flag.movetype = MOVETYPE_NONE;
281 flag.takedamage = DAMAGE_NO;
282 flag.solid = SOLID_NOT;
283 flag.angles = '0 0 0';
284 flag.ctf_status = FLAG_CARRY;
286 // messages and sounds
287 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
288 ctf_EventLog("receive", flag.team, player);
290 FOR_EACH_REALPLAYER(tmp_player)
292 if(tmp_player == sender)
293 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
294 else if(tmp_player == player)
295 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
296 else if(!IsDifferentTeam(tmp_player, sender))
297 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
300 // create new waypoint
301 ctf_FlagcarrierWaypoints(player);
303 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
304 player.throw_antispam = sender.throw_antispam;
306 flag.pass_distance = 0;
307 flag.pass_sender = world;
308 flag.pass_target = world;
311 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
313 entity flag = player.flagcarried;
314 vector targ_origin, flag_velocity;
316 if(!flag) { return; }
317 if((droptype == DROP_PASS) && !receiver) { return; }
319 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
322 setattachment(flag, world, "");
323 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
324 flag.owner.flagcarried = world;
326 flag.solid = SOLID_TRIGGER;
327 flag.ctf_dropper = player;
328 flag.ctf_droptime = time;
330 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
337 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
338 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
339 WarpZone_RefSys_Copy(flag, receiver);
340 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
341 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
343 flag.pass_distance = vlen((('1 0 0' * targ_origin_x) + ('0 1 0' * targ_origin_y)) - (('1 0 0' * player.origin_x) + ('0 1 0' * player.origin_y))); // for the sake of this check, exclude Z axis
344 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
347 flag.movetype = MOVETYPE_FLY;
348 flag.takedamage = DAMAGE_NO;
349 flag.pass_sender = player;
350 flag.pass_target = receiver;
351 flag.ctf_status = FLAG_PASSING;
354 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
355 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
356 ctf_EventLog("pass", flag.team, player);
362 makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle_x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
364 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
365 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
366 ctf_Handle_Drop(flag, player, droptype);
372 flag.velocity = '0 0 0'; // do nothing
379 flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), FALSE);
380 ctf_Handle_Drop(flag, player, droptype);
385 // kill old waypointsprite
386 WaypointSprite_Ping(player.wps_flagcarrier);
387 WaypointSprite_Kill(player.wps_flagcarrier);
389 if(player.wps_enemyflagcarrier)
390 WaypointSprite_Kill(player.wps_enemyflagcarrier);
393 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
401 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
403 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
404 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
405 float old_time, new_time;
407 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
409 // messages and sounds
410 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)
511 Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, (tmp_player.PICKUP_TEAM_VERBOSE ? CENTER_CTF_PICKUP_TEAM_VERBOSE : CENTER_CTF_PICKUP_TEAM), Team_ColorCode(player.team), (tmp_player.PICKUP_TEAM_VERBOSE ? player.netname : ""), 0, 0, 0);
512 else if(IsDifferentTeam(tmp_player, player))
513 Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, (tmp_player.PICKUP_ENEMY_VERBOSE ? CENTER_CTF_PICKUP_ENEMY_VERBOSE : CENTER_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), (tmp_player.PICKUP_ENEMY_VERBOSE ? player.netname : ""), 0, 0, 0);
517 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
522 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
523 ctf_EventLog("steal", flag.team, player);
529 pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
530 pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
531 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
532 PlayerTeamScore_AddScore(player, pickup_dropped_score);
533 ctf_EventLog("pickup", flag.team, player);
541 if(pickuptype == PICKUP_BASE)
543 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
544 if((player.speedrunning) && (ctf_captimerecord))
545 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
549 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
552 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
553 ctf_FlagcarrierWaypoints(player);
554 WaypointSprite_Ping(player.wps_flagcarrier);
558 // ===================
559 // Main Flag Functions
560 // ===================
562 void ctf_CheckFlagReturn(entity flag, float returntype)
564 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
566 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
568 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
572 case RETURN_DROPPED: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
573 case RETURN_DAMAGE: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
574 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
575 case RETURN_NEEDKILL: Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
579 { Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
581 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
582 ctf_EventLog("returned", flag.team, world);
583 ctf_RespawnFlag(flag);
588 void ctf_CheckStalemate(void)
591 float stale_red_flags = 0, stale_blue_flags = 0;
594 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
596 // build list of stale flags
597 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
599 if(autocvar_g_ctf_stalemate)
600 if(tmp_entity.ctf_status != FLAG_BASE)
601 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
603 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
604 ctf_staleflaglist = tmp_entity;
606 switch(tmp_entity.team)
608 case FL_TEAM_1: ++stale_red_flags; break;
609 case FL_TEAM_2: ++stale_blue_flags; break;
614 if(stale_red_flags && stale_blue_flags)
615 ctf_stalemate = TRUE;
616 else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
617 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
618 else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
619 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
621 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
624 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
626 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
627 WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
630 if not(wpforenemy_announced)
632 FOR_EACH_REALPLAYER(tmp_entity)
633 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
635 wpforenemy_announced = TRUE;
640 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
642 if(ITEM_DAMAGE_NEEDKILL(deathtype))
644 // automatically kill the flag and return it
646 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
649 if(autocvar_g_ctf_flag_return_damage)
651 // reduce health and check if it should be returned
652 self.health = self.health - damage;
653 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
663 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
666 if(self == ctf_worldflaglist) // only for the first flag
667 FOR_EACH_CLIENT(tmp_entity)
668 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
671 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
672 dprint("wtf the flag got squashed?\n");
673 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
674 if(!trace_startsolid) // can we resize it without getting stuck?
675 setsize(self, FLAG_MIN, FLAG_MAX); }
677 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
681 self.angles = '0 0 0';
689 switch(self.ctf_status)
693 if(autocvar_g_ctf_dropped_capture_radius)
695 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
696 if(tmp_entity.ctf_status == FLAG_DROPPED)
697 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
698 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
699 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
706 if(autocvar_g_ctf_flag_dropped_floatinwater)
708 vector midpoint = ((self.absmin + self.absmax) * 0.5);
709 if(pointcontents(midpoint) == CONTENT_WATER)
711 self.velocity = self.velocity * 0.5;
713 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
714 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
716 { self.movetype = MOVETYPE_FLY; }
718 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
720 if(autocvar_g_ctf_flag_return_dropped)
722 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
725 ctf_CheckFlagReturn(self, RETURN_DROPPED);
729 if(autocvar_g_ctf_flag_return_time)
731 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
732 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
740 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
743 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
747 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
751 if(autocvar_g_ctf_stalemate)
753 if(time >= wpforenemy_nextthink)
755 ctf_CheckStalemate();
756 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
764 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
765 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
766 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
768 if((self.pass_target == world)
769 || (self.pass_target.deadflag != DEAD_NO)
770 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
771 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
772 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
774 // give up, pass failed
775 ctf_Handle_Drop(self, world, DROP_PASS);
779 // still a viable target, go for it
780 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
785 default: // this should never happen
787 dprint("ctf_FlagThink(): Flag exists with no status?\n");
795 if(gameover) { return; }
797 entity toucher = other;
799 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
800 if(ITEM_TOUCH_NEEDKILL())
803 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
807 // special touch behaviors
808 if(toucher.vehicle_flags & VHF_ISVEHICLE)
810 if(autocvar_g_ctf_allow_vehicle_touch)
811 toucher = toucher.owner; // the player is actually the vehicle owner, not other
813 return; // do nothing
815 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
817 if(time > self.wait) // if we haven't in a while, play a sound/effect
819 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
820 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
821 self.wait = time + FLAG_TOUCHRATE;
825 else if(toucher.deadflag != DEAD_NO) { return; }
827 switch(self.ctf_status)
831 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
832 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
833 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
834 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
840 if(!IsDifferentTeam(toucher, self))
841 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
842 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
843 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
849 dprint("Someone touched a flag even though it was being carried?\n");
855 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
857 if(IsDifferentTeam(toucher, self.pass_sender))
858 ctf_Handle_Return(self, toucher);
860 ctf_Handle_Retrieve(self, toucher);
868 void ctf_RespawnFlag(entity flag)
870 // check for flag respawn being called twice in a row
871 if(flag.last_respawn > time - 0.5)
872 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
874 flag.last_respawn = time;
876 // reset the player (if there is one)
877 if((flag.owner) && (flag.owner.flagcarried == flag))
879 if(flag.owner.wps_enemyflagcarrier)
880 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
882 WaypointSprite_Kill(flag.wps_flagcarrier);
884 flag.owner.flagcarried = world;
886 if(flag.speedrunning)
887 ctf_FakeTimeLimit(flag.owner, -1);
890 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
891 { WaypointSprite_Kill(flag.wps_flagdropped); }
894 setattachment(flag, world, "");
895 setorigin(flag, flag.ctf_spawnorigin);
897 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
898 flag.takedamage = DAMAGE_NO;
899 flag.health = flag.max_flag_health;
900 flag.solid = SOLID_TRIGGER;
901 flag.velocity = '0 0 0';
902 flag.angles = flag.mangle;
903 flag.flags = FL_ITEM | FL_NOTARGET;
905 flag.ctf_status = FLAG_BASE;
907 flag.pass_distance = 0;
908 flag.pass_sender = world;
909 flag.pass_target = world;
910 flag.ctf_dropper = world;
911 flag.ctf_pickuptime = 0;
912 flag.ctf_droptime = 0;
918 if(self.owner.classname == "player")
919 ctf_Handle_Throw(self.owner, world, DROP_RESET);
921 ctf_RespawnFlag(self);
924 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
927 waypoint_spawnforitem_force(self, self.origin);
928 self.nearestwaypointtimeout = 0; // activate waypointing again
929 self.bot_basewaypoint = self.nearestwaypoint;
932 WaypointSprite_SpawnFixed(((self.team == FL_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
933 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
935 // captureshield setup
936 ctf_CaptureShield_Spawn(self);
939 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
942 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.
943 self = flag; // for later usage with droptofloor()
946 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
947 ctf_worldflaglist = flag;
949 setattachment(flag, world, "");
951 flag.netname = ((teamnumber) ? "^1REPLACETHIS^7" : "^4REPLACETHIS^7"); // ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
952 flag.team = ((teamnumber) ? FL_TEAM_1 : FL_TEAM_2); // FL_TEAM_1: color 4 team (red) - FL_TEAM_2: color 13 team (blue)
953 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
954 flag.classname = "item_flag_team";
955 flag.target = "###item###"; // wut?
956 flag.flags = FL_ITEM | FL_NOTARGET;
957 flag.solid = SOLID_TRIGGER;
958 flag.takedamage = DAMAGE_NO;
959 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
960 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
961 flag.health = flag.max_flag_health;
962 flag.event_damage = ctf_FlagDamage;
963 flag.pushable = TRUE;
964 flag.teleportable = TELEPORT_NORMAL;
965 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
966 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
967 flag.velocity = '0 0 0';
968 flag.mangle = flag.angles;
969 flag.reset = ctf_Reset;
970 flag.touch = ctf_FlagTouch;
971 flag.think = ctf_FlagThink;
972 flag.nextthink = time + FLAG_THINKRATE;
973 flag.ctf_status = FLAG_BASE;
975 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
976 if(!flag.scale) { flag.scale = FLAG_SCALE; }
977 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
978 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
979 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
980 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
983 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
984 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
985 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
986 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.
987 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
988 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
989 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
992 precache_sound(flag.snd_flag_taken);
993 precache_sound(flag.snd_flag_returned);
994 precache_sound(flag.snd_flag_capture);
995 precache_sound(flag.snd_flag_respawn);
996 precache_sound(flag.snd_flag_dropped);
997 precache_sound(flag.snd_flag_touch);
998 precache_sound(flag.snd_flag_pass);
999 precache_model(flag.model);
1000 precache_model("models/ctf/shield.md3");
1001 precache_model("models/ctf/shockwavetransring.md3");
1004 setmodel(flag, flag.model); // precision set below
1005 setsize(flag, FLAG_MIN, FLAG_MAX);
1006 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1008 if(autocvar_g_ctf_flag_glowtrails)
1010 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1011 flag.glow_size = 25;
1012 flag.glow_trail = 1;
1015 flag.effects |= EF_LOWPRECISION;
1016 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1017 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1020 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1022 flag.dropped_origin = flag.origin;
1023 flag.noalign = TRUE;
1024 flag.movetype = MOVETYPE_NONE;
1026 else // drop to floor, automatically find a platform and set that as spawn origin
1028 flag.noalign = FALSE;
1031 flag.movetype = MOVETYPE_TOSS;
1034 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1042 // NOTE: LEGACY CODE, needs to be re-written!
1044 void havocbot_calculate_middlepoint()
1048 vector fo = '0 0 0';
1051 f = ctf_worldflaglist;
1056 f = f.ctf_worldflagnext;
1060 havocbot_ctf_middlepoint = s * (1.0 / n);
1061 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1065 entity havocbot_ctf_find_flag(entity bot)
1068 f = ctf_worldflaglist;
1071 if (bot.team == f.team)
1073 f = f.ctf_worldflagnext;
1078 entity havocbot_ctf_find_enemy_flag(entity bot)
1081 f = ctf_worldflaglist;
1084 if (bot.team != f.team)
1086 f = f.ctf_worldflagnext;
1091 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1099 FOR_EACH_PLAYER(head)
1101 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1104 if(vlen(head.origin - org) < tc_radius)
1111 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1114 head = ctf_worldflaglist;
1117 if (self.team == head.team)
1119 head = head.ctf_worldflagnext;
1122 navigation_routerating(head, ratingscale, 10000);
1125 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1128 head = ctf_worldflaglist;
1131 if (self.team == head.team)
1133 head = head.ctf_worldflagnext;
1138 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1141 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1144 head = ctf_worldflaglist;
1147 if (self.team != head.team)
1149 head = head.ctf_worldflagnext;
1152 navigation_routerating(head, ratingscale, 10000);
1155 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1157 if not(bot_waypoints_for_items)
1159 havocbot_goalrating_ctf_enemyflag(ratingscale);
1165 head = havocbot_ctf_find_enemy_flag(self);
1170 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1173 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1177 mf = havocbot_ctf_find_flag(self);
1179 if(mf.ctf_status == FLAG_BASE)
1183 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1186 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1189 head = ctf_worldflaglist;
1192 // flag is out in the field
1193 if(head.ctf_status != FLAG_BASE)
1194 if(head.tag_entity==world) // dropped
1198 if(vlen(org-head.origin)<df_radius)
1199 navigation_routerating(head, ratingscale, 10000);
1202 navigation_routerating(head, ratingscale, 10000);
1205 head = head.ctf_worldflagnext;
1209 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1213 head = findchainfloat(bot_pickup, TRUE);
1216 // gather health and armor only
1218 if (head.health || head.armorvalue)
1219 if (vlen(head.origin - org) < sradius)
1221 // get the value of the item
1222 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1224 navigation_routerating(head, t * ratingscale, 500);
1230 void havocbot_ctf_reset_role(entity bot)
1232 float cdefense, cmiddle, coffense;
1233 entity mf, ef, head;
1236 if(bot.deadflag != DEAD_NO)
1239 if(vlen(havocbot_ctf_middlepoint)==0)
1240 havocbot_calculate_middlepoint();
1243 if (bot.flagcarried)
1245 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1249 mf = havocbot_ctf_find_flag(bot);
1250 ef = havocbot_ctf_find_enemy_flag(bot);
1252 // Retrieve stolen flag
1253 if(mf.ctf_status!=FLAG_BASE)
1255 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1259 // If enemy flag is taken go to the middle to intercept pursuers
1260 if(ef.ctf_status!=FLAG_BASE)
1262 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1266 // if there is only me on the team switch to offense
1268 FOR_EACH_PLAYER(head)
1269 if(head.team==bot.team)
1274 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1278 // Evaluate best position to take
1279 // Count mates on middle position
1280 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1282 // Count mates on defense position
1283 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1285 // Count mates on offense position
1286 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1288 if(cdefense<=coffense)
1289 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1290 else if(coffense<=cmiddle)
1291 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1293 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1296 void havocbot_role_ctf_carrier()
1298 if(self.deadflag != DEAD_NO)
1300 havocbot_ctf_reset_role(self);
1304 if (self.flagcarried == world)
1306 havocbot_ctf_reset_role(self);
1310 if (self.bot_strategytime < time)
1312 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1314 navigation_goalrating_start();
1315 havocbot_goalrating_ctf_ourbase(50000);
1318 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1320 navigation_goalrating_end();
1322 if (self.navigation_hasgoals)
1323 self.havocbot_cantfindflag = time + 10;
1324 else if (time > self.havocbot_cantfindflag)
1326 // Can't navigate to my own base, suicide!
1327 // TODO: drop it and wander around
1328 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1334 void havocbot_role_ctf_escort()
1338 if(self.deadflag != DEAD_NO)
1340 havocbot_ctf_reset_role(self);
1344 if (self.flagcarried)
1346 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1350 // If enemy flag is back on the base switch to previous role
1351 ef = havocbot_ctf_find_enemy_flag(self);
1352 if(ef.ctf_status==FLAG_BASE)
1354 self.havocbot_role = self.havocbot_previous_role;
1355 self.havocbot_role_timeout = 0;
1359 // If the flag carrier reached the base switch to defense
1360 mf = havocbot_ctf_find_flag(self);
1361 if(mf.ctf_status!=FLAG_BASE)
1362 if(vlen(ef.origin - mf.dropped_origin) < 300)
1364 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1368 // Set the role timeout if necessary
1369 if (!self.havocbot_role_timeout)
1371 self.havocbot_role_timeout = time + random() * 30 + 60;
1374 // If nothing happened just switch to previous role
1375 if (time > self.havocbot_role_timeout)
1377 self.havocbot_role = self.havocbot_previous_role;
1378 self.havocbot_role_timeout = 0;
1382 // Chase the flag carrier
1383 if (self.bot_strategytime < time)
1385 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1386 navigation_goalrating_start();
1387 havocbot_goalrating_ctf_enemyflag(30000);
1388 havocbot_goalrating_ctf_ourstolenflag(40000);
1389 havocbot_goalrating_items(10000, self.origin, 10000);
1390 navigation_goalrating_end();
1394 void havocbot_role_ctf_offense()
1399 if(self.deadflag != DEAD_NO)
1401 havocbot_ctf_reset_role(self);
1405 if (self.flagcarried)
1407 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1412 mf = havocbot_ctf_find_flag(self);
1413 ef = havocbot_ctf_find_enemy_flag(self);
1416 if(mf.ctf_status!=FLAG_BASE)
1419 pos = mf.tag_entity.origin;
1423 // Try to get it if closer than the enemy base
1424 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1426 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1431 // Escort flag carrier
1432 if(ef.ctf_status!=FLAG_BASE)
1435 pos = ef.tag_entity.origin;
1439 if(vlen(pos-mf.dropped_origin)>700)
1441 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1446 // About to fail, switch to middlefield
1449 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1453 // Set the role timeout if necessary
1454 if (!self.havocbot_role_timeout)
1455 self.havocbot_role_timeout = time + 120;
1457 if (time > self.havocbot_role_timeout)
1459 havocbot_ctf_reset_role(self);
1463 if (self.bot_strategytime < time)
1465 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1466 navigation_goalrating_start();
1467 havocbot_goalrating_ctf_ourstolenflag(50000);
1468 havocbot_goalrating_ctf_enemybase(20000);
1469 havocbot_goalrating_items(5000, self.origin, 1000);
1470 havocbot_goalrating_items(1000, self.origin, 10000);
1471 navigation_goalrating_end();
1475 // Retriever (temporary role):
1476 void havocbot_role_ctf_retriever()
1480 if(self.deadflag != DEAD_NO)
1482 havocbot_ctf_reset_role(self);
1486 if (self.flagcarried)
1488 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1492 // If flag is back on the base switch to previous role
1493 mf = havocbot_ctf_find_flag(self);
1494 if(mf.ctf_status==FLAG_BASE)
1496 havocbot_ctf_reset_role(self);
1500 if (!self.havocbot_role_timeout)
1501 self.havocbot_role_timeout = time + 20;
1503 if (time > self.havocbot_role_timeout)
1505 havocbot_ctf_reset_role(self);
1509 if (self.bot_strategytime < time)
1514 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1515 navigation_goalrating_start();
1516 havocbot_goalrating_ctf_ourstolenflag(50000);
1517 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1518 havocbot_goalrating_ctf_enemybase(30000);
1519 havocbot_goalrating_items(500, self.origin, rt_radius);
1520 navigation_goalrating_end();
1524 void havocbot_role_ctf_middle()
1528 if(self.deadflag != DEAD_NO)
1530 havocbot_ctf_reset_role(self);
1534 if (self.flagcarried)
1536 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1540 mf = havocbot_ctf_find_flag(self);
1541 if(mf.ctf_status!=FLAG_BASE)
1543 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1547 if (!self.havocbot_role_timeout)
1548 self.havocbot_role_timeout = time + 10;
1550 if (time > self.havocbot_role_timeout)
1552 havocbot_ctf_reset_role(self);
1556 if (self.bot_strategytime < time)
1560 org = havocbot_ctf_middlepoint;
1561 org_z = self.origin_z;
1563 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1564 navigation_goalrating_start();
1565 havocbot_goalrating_ctf_ourstolenflag(50000);
1566 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1567 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1568 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1569 havocbot_goalrating_items(2500, self.origin, 10000);
1570 havocbot_goalrating_ctf_enemybase(2500);
1571 navigation_goalrating_end();
1575 void havocbot_role_ctf_defense()
1579 if(self.deadflag != DEAD_NO)
1581 havocbot_ctf_reset_role(self);
1585 if (self.flagcarried)
1587 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1591 // If own flag was captured
1592 mf = havocbot_ctf_find_flag(self);
1593 if(mf.ctf_status!=FLAG_BASE)
1595 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1599 if (!self.havocbot_role_timeout)
1600 self.havocbot_role_timeout = time + 30;
1602 if (time > self.havocbot_role_timeout)
1604 havocbot_ctf_reset_role(self);
1607 if (self.bot_strategytime < time)
1612 org = mf.dropped_origin;
1613 mp_radius = havocbot_ctf_middlepoint_radius;
1615 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1616 navigation_goalrating_start();
1618 // if enemies are closer to our base, go there
1619 entity head, closestplayer = world;
1620 float distance, bestdistance = 10000;
1621 FOR_EACH_PLAYER(head)
1623 if(head.deadflag!=DEAD_NO)
1626 distance = vlen(org - head.origin);
1627 if(distance<bestdistance)
1629 closestplayer = head;
1630 bestdistance = distance;
1635 if(closestplayer.team!=self.team)
1636 if(vlen(org - self.origin)>1000)
1637 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1638 havocbot_goalrating_ctf_ourbase(30000);
1640 havocbot_goalrating_ctf_ourstolenflag(20000);
1641 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1642 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1643 havocbot_goalrating_items(10000, org, mp_radius);
1644 havocbot_goalrating_items(5000, self.origin, 10000);
1645 navigation_goalrating_end();
1649 void havocbot_role_ctf_setrole(entity bot, float role)
1651 dprint(strcat(bot.netname," switched to "));
1654 case HAVOCBOT_CTF_ROLE_CARRIER:
1656 bot.havocbot_role = havocbot_role_ctf_carrier;
1657 bot.havocbot_role_timeout = 0;
1658 bot.havocbot_cantfindflag = time + 10;
1659 bot.bot_strategytime = 0;
1661 case HAVOCBOT_CTF_ROLE_DEFENSE:
1663 bot.havocbot_role = havocbot_role_ctf_defense;
1664 bot.havocbot_role_timeout = 0;
1666 case HAVOCBOT_CTF_ROLE_MIDDLE:
1668 bot.havocbot_role = havocbot_role_ctf_middle;
1669 bot.havocbot_role_timeout = 0;
1671 case HAVOCBOT_CTF_ROLE_OFFENSE:
1673 bot.havocbot_role = havocbot_role_ctf_offense;
1674 bot.havocbot_role_timeout = 0;
1676 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1677 dprint("retriever");
1678 bot.havocbot_previous_role = bot.havocbot_role;
1679 bot.havocbot_role = havocbot_role_ctf_retriever;
1680 bot.havocbot_role_timeout = time + 10;
1681 bot.bot_strategytime = 0;
1683 case HAVOCBOT_CTF_ROLE_ESCORT:
1685 bot.havocbot_previous_role = bot.havocbot_role;
1686 bot.havocbot_role = havocbot_role_ctf_escort;
1687 bot.havocbot_role_timeout = time + 30;
1688 bot.bot_strategytime = 0;
1699 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1703 // initially clear items so they can be set as necessary later.
1704 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1705 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1707 // scan through all the flags and notify the client about them
1708 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1710 switch(flag.ctf_status)
1715 if((flag.owner == self) || (flag.pass_sender == self))
1716 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1718 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1723 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1729 // item for stopping players from capturing the flag too often
1730 if(self.ctf_captureshielded)
1731 self.items |= IT_CTF_SHIELDED;
1733 // update the health of the flag carrier waypointsprite
1734 if(self.wps_flagcarrier)
1735 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1740 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1742 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1744 if(frag_target == frag_attacker) // damage done to yourself
1746 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1747 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1749 else // damage done to everyone else
1751 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1752 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1755 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1757 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1758 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1760 frag_target.wps_helpme_time = time;
1761 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1763 // todo: add notification for when flag carrier needs help?
1768 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1770 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1772 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1773 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1776 if(frag_target.flagcarried)
1777 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1782 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1785 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1788 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1790 entity flag; // temporary entity for the search method
1792 if(self.flagcarried)
1793 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1795 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1797 if(flag.pass_sender == self) { flag.pass_sender = world; }
1798 if(flag.pass_target == self) { flag.pass_target = world; }
1799 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1805 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1807 if(self.flagcarried)
1808 if(!autocvar_g_ctf_portalteleport)
1809 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1814 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1816 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1818 entity player = self;
1820 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1822 // pass the flag to a team mate
1823 if(autocvar_g_ctf_pass)
1825 entity head, closest_target = world;
1826 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1828 while(head) // find the closest acceptable target to pass to
1830 if(head.classname == "player" && head.deadflag == DEAD_NO)
1831 if(head != player && !IsDifferentTeam(head, player))
1832 if(!head.speedrunning && !head.vehicle)
1834 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1835 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1836 vector passer_center = CENTER_OR_VIEWOFS(player);
1838 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1840 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1842 if(clienttype(head) == CLIENTTYPE_BOT)
1844 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1845 ctf_Handle_Throw(head, player, DROP_PASS);
1849 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1850 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1852 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1855 else if(player.flagcarried)
1859 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1860 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1861 { closest_target = head; }
1863 else { closest_target = head; }
1870 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1873 // throw the flag in front of you
1874 if(autocvar_g_ctf_throw && player.flagcarried)
1876 if(player.throw_count == -1)
1878 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1880 player.throw_prevtime = time;
1881 player.throw_count = 1;
1882 ctf_Handle_Throw(player, world, DROP_THROW);
1887 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1893 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1894 else { player.throw_count += 1; }
1895 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1897 player.throw_prevtime = time;
1898 ctf_Handle_Throw(player, world, DROP_THROW);
1907 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1909 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1911 self.wps_helpme_time = time;
1912 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1914 else // create a normal help me waypointsprite
1916 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');
1917 WaypointSprite_Ping(self.wps_helpme);
1923 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1925 if(vh_player.flagcarried)
1927 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1929 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1933 setattachment(vh_player.flagcarried, vh_vehicle, "");
1934 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1935 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1936 //vh_player.flagcarried.angles = '0 0 0';
1944 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1946 if(vh_player.flagcarried)
1948 setattachment(vh_player.flagcarried, vh_player, "");
1949 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1950 vh_player.flagcarried.scale = FLAG_SCALE;
1951 vh_player.flagcarried.angles = '0 0 0';
1958 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1960 if(self.flagcarried)
1962 Send_Notification(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1963 ctf_RespawnFlag(self.flagcarried);
1970 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1972 entity flag; // temporary entity for the search method
1974 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1976 switch(flag.ctf_status)
1981 // lock the flag, game is over
1982 flag.movetype = MOVETYPE_NONE;
1983 flag.takedamage = DAMAGE_NO;
1984 flag.solid = SOLID_NOT;
1985 flag.nextthink = FALSE; // stop thinking
1987 //dprint("stopping the ", flag.netname, " from moving.\n");
1995 // do nothing for these flags
2004 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2006 havocbot_ctf_reset_role(self);
2010 MUTATOR_HOOKFUNCTION(ctf_GetCvars)
2012 GetCvars_handleFloat(get_cvars_s, get_cvars_f, CAPTURE_VERBOSE, "notification_ctf_capture_verbose");
2013 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_TEAM_VERBOSE, "notification_ctf_pickup_team_verbose");
2014 GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_ENEMY_VERBOSE, "notification_ctf_pickup_enemy_verbose");
2023 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2024 CTF Starting point for a player in team one (Red).
2025 Keys: "angle" viewing angle when spawning. */
2026 void spawnfunc_info_player_team1()
2028 if(g_assault) { remove(self); return; }
2030 self.team = FL_TEAM_1; // red
2031 spawnfunc_info_player_deathmatch();
2035 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2036 CTF Starting point for a player in team two (Blue).
2037 Keys: "angle" viewing angle when spawning. */
2038 void spawnfunc_info_player_team2()
2040 if(g_assault) { remove(self); return; }
2042 self.team = FL_TEAM_2; // blue
2043 spawnfunc_info_player_deathmatch();
2046 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2047 CTF Starting point for a player in team three (Yellow).
2048 Keys: "angle" viewing angle when spawning. */
2049 void spawnfunc_info_player_team3()
2051 if(g_assault) { remove(self); return; }
2053 self.team = FL_TEAM_3; // yellow
2054 spawnfunc_info_player_deathmatch();
2058 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2059 CTF Starting point for a player in team four (Purple).
2060 Keys: "angle" viewing angle when spawning. */
2061 void spawnfunc_info_player_team4()
2063 if(g_assault) { remove(self); return; }
2065 self.team = FL_TEAM_4; // purple
2066 spawnfunc_info_player_deathmatch();
2069 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2070 CTF flag for team one (Red).
2072 "angle" Angle the flag will point (minus 90 degrees)...
2073 "model" model to use, note this needs red and blue as skins 0 and 1...
2074 "noise" sound played when flag is picked up...
2075 "noise1" sound played when flag is returned by a teammate...
2076 "noise2" sound played when flag is captured...
2077 "noise3" sound played when flag is lost in the field and respawns itself...
2078 "noise4" sound played when flag is dropped by a player...
2079 "noise5" sound played when flag touches the ground... */
2080 void spawnfunc_item_flag_team1()
2082 if(!g_ctf) { remove(self); return; }
2084 ctf_FlagSetup(1, self); // 1 = red
2087 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2088 CTF flag for team two (Blue).
2090 "angle" Angle the flag will point (minus 90 degrees)...
2091 "model" model to use, note this needs red and blue as skins 0 and 1...
2092 "noise" sound played when flag is picked up...
2093 "noise1" sound played when flag is returned by a teammate...
2094 "noise2" sound played when flag is captured...
2095 "noise3" sound played when flag is lost in the field and respawns itself...
2096 "noise4" sound played when flag is dropped by a player...
2097 "noise5" sound played when flag touches the ground... */
2098 void spawnfunc_item_flag_team2()
2100 if(!g_ctf) { remove(self); return; }
2102 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2105 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2106 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2107 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.
2109 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2110 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2111 void spawnfunc_ctf_team()
2113 if(!g_ctf) { remove(self); return; }
2115 self.classname = "ctf_team";
2116 self.team = self.cnt + 1;
2119 // compatibility for quake maps
2120 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2121 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2122 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2123 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2124 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2125 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2133 void ctf_ScoreRules()
2135 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2136 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2137 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2138 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2139 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2140 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2141 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2142 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2143 ScoreRules_basics_end();
2146 // code from here on is just to support maps that don't have flag and team entities
2147 void ctf_SpawnTeam (string teamname, float teamcolor)
2152 self.classname = "ctf_team";
2153 self.netname = teamname;
2154 self.cnt = teamcolor;
2156 spawnfunc_ctf_team();
2161 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2163 // if no teams are found, spawn defaults
2164 if(find(world, classname, "ctf_team") == world)
2166 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2167 ctf_SpawnTeam("Red", FL_TEAM_1 - 1);
2168 ctf_SpawnTeam("Blue", FL_TEAM_2 - 1);
2174 void ctf_Initialize()
2176 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2178 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2179 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2180 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2182 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2186 MUTATOR_DEFINITION(gamemode_ctf)
2188 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2189 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2190 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2191 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2192 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2193 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2194 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2195 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2196 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2197 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2198 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2199 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2200 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2201 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2202 MUTATOR_HOOK(GetCvars, ctf_GetCvars, CBC_ORDER_ANY);
2206 if(time > 1) // game loads at time 1
2207 error("This is a game type and it cannot be added at runtime.");
2211 MUTATOR_ONROLLBACK_OR_REMOVE
2213 // we actually cannot roll back ctf_Initialize here
2214 // BUT: we don't need to! If this gets called, adding always
2220 print("This is a game type and it cannot be removed at runtime.");